Skip to content

Commit

Permalink
Merge branch 'main' into add-logs
Browse files Browse the repository at this point in the history
  • Loading branch information
EndymionJkb committed Dec 24, 2024
2 parents b908cb7 + 822c1cf commit bca4b2a
Show file tree
Hide file tree
Showing 97 changed files with 2,256 additions and 299 deletions.
8 changes: 8 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@
"compiler": "0.8.24"
}
},
{
"files": "**/pool-gyro/**/*.sol",
"options": {
"singleQuote": false,
"tabWidth": 4,
"compiler": "0.8.27"
}
},
{
"files": "*.md",
"options": {
Expand Down
2 changes: 1 addition & 1 deletion audits/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The audits have been conducted before the [Cantina contest](https://cantina.xyz/
| Scope | Firm | Report |
| --------------------------------- | ------------- | ---------------------------------------------------- |
| Vault, Weighted Pool, Stable Pool | Certora | [`2024-09-04`](./certora/2024-09-04.pdf) |
| Vault, Weighted Pool, Stable Pool | Trail Of Bits | [`2024-10-08`](./trail-of-bits/2024-10-08.pdf) |
| Vault, Weighted Pool, Stable Pool | Trail Of Bits | [`2024-12-11`](./trail-of-bits/2024-12-11.pdf) |
| Vault, Weighted Pool, Stable Pool | Spearbit* | [`2024-10-04`](./spearbit/2024-10-04.pdf) |

## Addenda
Expand Down
Binary file modified audits/certora/2024-09-04.pdf
Binary file not shown.
Binary file removed audits/trail-of-bits/2024-10-08.pdf
Binary file not shown.
Binary file added audits/trail-of-bits/2024-12-11.pdf
Binary file not shown.
76 changes: 76 additions & 0 deletions pkg/interfaces/contracts/pool-gyro/IGyroECLPPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;

import { IBasePool } from "../vault/IBasePool.sol";

interface IGyroECLPPool is IBasePool {
event ECLPParamsValidated(bool paramsValidated);
event ECLPDerivedParamsValidated(bool derivedParamsValidated);

/**
* @notice Gyro ECLP pool configuration.
* @param name Pool name
* @param symbol Pool symbol
* @param eclpParams Parameters to configure the E-CLP pool, with 18 decimals
* @param derivedEclpParams Parameters calculated off-chain based on eclpParams. 38 decimals for higher precision
*/
struct GyroECLPPoolParams {
string name;
string symbol;
EclpParams eclpParams;
DerivedEclpParams derivedEclpParams;
}

/**
* @notice Struct containing parameters to build the ellipse which describes the pricing curve of an E-CLP pool.
* @dev Note that all values are positive and could consist of uint's. However, this would require converting to
* int numerous times because of int operations, so we store them as int to simplify the code.
*
* @param alpha Lower price limit. alpha > 0
* @param beta Upper price limit. beta > alpha > 0
* @param c `c = cos(-phi) >= 0`, rounded to 18 decimals. Phi is the rotation angle of the ellipse
* @param s `s = sin(-phi) >= 0`, rounded to 18 decimals. Phi is the rotation angle of the ellipse
* @param lambda Stretching factor, lambda >= 1. When lambda == 1, we have a perfect circle
*/
struct EclpParams {
int256 alpha;
int256 beta;
int256 c;
int256 s;
// Invariant: c^2 + s^2 == 1, i.e., the point (c, s) is normalized.
// Due to rounding, this may not be 1. The term dSq in DerivedParams corrects for this in extra precision
int256 lambda;
}

/**
* @notice Struct containing parameters calculated based on EclpParams, off-chain.
* @dev All these parameters can be calculated using the EclpParams, but they're calculated off-chain to save gas
* and increase the precision. Therefore, the numbers are stored with 38 decimals precision. Please refer to
* https://docs.gyro.finance/gyroscope-protocol/technical-documents, document "E-CLP high-precision
* calculations.pdf", for further explanations on how to obtain the parameters below.
*
* @param tauAlpha
* @param tauBeta
* @param u from (A chi)_y = lambda * u + v
* @param v from (A chi)_y = lambda * u + v
* @param w from (A chi)_x = w / lambda + z
* @param z from (A chi)_x = w / lambda + z
* @param dSq error in c^2 + s^2 = dSq, used to correct errors in c, s, tau, u,v,w,z calculations
*/
struct DerivedEclpParams {
Vector2 tauAlpha;
Vector2 tauBeta;
int256 u;
int256 v;
int256 w;
int256 z;
int256 dSq;
}

/// @notice Struct containing a 2D vector.
struct Vector2 {
int256 x;
int256 y;
}
}
7 changes: 5 additions & 2 deletions pkg/pool-gyro/contracts/Gyro2CLPPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,10 @@ contract Gyro2CLPPool is IGyro2CLPPool, BalancerPoolToken {
}

/// @inheritdoc IBasePool
function computeInvariant(uint256[] memory balancesLiveScaled18, Rounding rounding) public view returns (uint256) {
function computeInvariant(
uint256[] memory balancesLiveScaled18,
Rounding rounding
) external view returns (uint256) {
(uint256 sqrtAlpha, uint256 sqrtBeta) = _getSqrtAlphaAndBeta();

return Gyro2CLPMath.calculateInvariant(balancesLiveScaled18, sqrtAlpha, sqrtBeta, rounding);
Expand Down Expand Up @@ -102,7 +105,7 @@ contract Gyro2CLPPool is IGyro2CLPPool, BalancerPoolToken {
}

/// @inheritdoc IBasePool
function onSwap(PoolSwapParams calldata request) public view onlyVault returns (uint256) {
function onSwap(PoolSwapParams calldata request) external view onlyVault returns (uint256) {
bool tokenInIsToken0 = request.indexIn == 0;
uint256 balanceTokenInScaled18 = request.balancesScaled18[request.indexIn];
uint256 balanceTokenOutScaled18 = request.balancesScaled18[request.indexOut];
Expand Down
229 changes: 229 additions & 0 deletions pkg/pool-gyro/contracts/GyroECLPPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// SPDX-License-Identifier: LicenseRef-Gyro-1.0
// for information on licensing please see the README in the GitHub repository
// <https://github.com/gyrostable/concentrated-lps>.

pragma solidity ^0.8.27;

import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import { IBasePool } from "@balancer-labs/v3-interfaces/contracts/vault/IBasePool.sol";
import { IGyroECLPPool } from "@balancer-labs/v3-interfaces/contracts/pool-gyro/IGyroECLPPool.sol";
import { ISwapFeePercentageBounds } from "@balancer-labs/v3-interfaces/contracts/vault/ISwapFeePercentageBounds.sol";
import {
IUnbalancedLiquidityInvariantRatioBounds
} from "@balancer-labs/v3-interfaces/contracts/vault/IUnbalancedLiquidityInvariantRatioBounds.sol";
import { PoolSwapParams, Rounding, SwapKind } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";

import { FixedPoint } from "@balancer-labs/v3-solidity-utils/contracts/math/FixedPoint.sol";

import { BalancerPoolToken } from "@balancer-labs/v3-vault/contracts/BalancerPoolToken.sol";

import { GyroECLPMath } from "./lib/GyroECLPMath.sol";

/**
* @notice Standard Gyro E-CLP Pool, with fixed E-CLP parameters.
* @dev Gyroscope's E-CLPs are AMMs where trading takes place along part of an ellipse curve. A given E-CLP is
* parameterized by the pricing range [α,β], the inclination angle `phi` and stretching parameter `lambda`. For more
* information, please refer to https://docs.gyro.finance/gyroscope-protocol/concentrated-liquidity-pools/e-clps.
*/
contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {
using FixedPoint for uint256;
using SafeCast for *;

bytes32 private constant _POOL_TYPE = "ECLP";

/// @dev Parameters of the ECLP pool
int256 internal immutable _paramsAlpha;
int256 internal immutable _paramsBeta;
int256 internal immutable _paramsC;
int256 internal immutable _paramsS;
int256 internal immutable _paramsLambda;

/**
* @dev Derived Parameters of the E-CLP pool, calculated off-chain based on the parameters above. 38 decimals
* precision.
*/
int256 internal immutable _tauAlphaX;
int256 internal immutable _tauAlphaY;
int256 internal immutable _tauBetaX;
int256 internal immutable _tauBetaY;
int256 internal immutable _u;
int256 internal immutable _v;
int256 internal immutable _w;
int256 internal immutable _z;
int256 internal immutable _dSq;

constructor(GyroECLPPoolParams memory params, IVault vault) BalancerPoolToken(vault, params.name, params.symbol) {
GyroECLPMath.validateParams(params.eclpParams);
emit ECLPParamsValidated(true);

GyroECLPMath.validateDerivedParamsLimits(params.eclpParams, params.derivedEclpParams);
emit ECLPDerivedParamsValidated(true);

(_paramsAlpha, _paramsBeta, _paramsC, _paramsS, _paramsLambda) = (
params.eclpParams.alpha,
params.eclpParams.beta,
params.eclpParams.c,
params.eclpParams.s,
params.eclpParams.lambda
);

(_tauAlphaX, _tauAlphaY, _tauBetaX, _tauBetaY, _u, _v, _w, _z, _dSq) = (
params.derivedEclpParams.tauAlpha.x,
params.derivedEclpParams.tauAlpha.y,
params.derivedEclpParams.tauBeta.x,
params.derivedEclpParams.tauBeta.y,
params.derivedEclpParams.u,
params.derivedEclpParams.v,
params.derivedEclpParams.w,
params.derivedEclpParams.z,
params.derivedEclpParams.dSq
);
}

/// @inheritdoc IBasePool
function computeInvariant(
uint256[] memory balancesLiveScaled18,
Rounding rounding
) external view returns (uint256) {
(EclpParams memory eclpParams, DerivedEclpParams memory derivedECLPParams) = _reconstructECLPParams();

(int256 currentInvariant, int256 invErr) = GyroECLPMath.calculateInvariantWithError(
balancesLiveScaled18,
eclpParams,
derivedECLPParams
);

if (rounding == Rounding.ROUND_DOWN) {
return (currentInvariant - invErr).toUint256();
} else {
return (currentInvariant + invErr).toUint256();
}
}

/// @inheritdoc IBasePool
function computeBalance(
uint256[] memory balancesLiveScaled18,
uint256 tokenInIndex,
uint256 invariantRatio
) external view returns (uint256 newBalance) {
(EclpParams memory eclpParams, DerivedEclpParams memory derivedECLPParams) = _reconstructECLPParams();

Vector2 memory invariant;
{
(int256 currentInvariant, int256 invErr) = GyroECLPMath.calculateInvariantWithError(
balancesLiveScaled18,
eclpParams,
derivedECLPParams
);

// The invariant vector contains the rounded up and rounded down invariant. Both are needed when computing
// the virtual offsets. Depending on tauAlpha and tauBeta values, we want to use the invariant rounded up
// or rounded down to make sure we're conservative in the output.
invariant = Vector2(
(currentInvariant + invErr).toUint256().mulUp(invariantRatio).toInt256(),
(currentInvariant - invErr).toUint256().mulUp(invariantRatio).toInt256()
);

// Edge case check. Should never happen except for insane tokens. If this is hit, actually adding the
// tokens would lead to a revert or (if it went through) a deadlock downstream, so we catch it here.
require(invariant.x <= GyroECLPMath._MAX_INVARIANT, GyroECLPMath.MaxInvariantExceeded());
}

if (tokenInIndex == 0) {
return
GyroECLPMath
.calcXGivenY(balancesLiveScaled18[1].toInt256(), eclpParams, derivedECLPParams, invariant)
.toUint256();
} else {
return
GyroECLPMath
.calcYGivenX(balancesLiveScaled18[0].toInt256(), eclpParams, derivedECLPParams, invariant)
.toUint256();
}
}

/// @inheritdoc IBasePool
function onSwap(PoolSwapParams memory request) external view onlyVault returns (uint256) {
// The Vault already checks that index in != index out.
bool tokenInIsToken0 = request.indexIn == 0;

(EclpParams memory eclpParams, DerivedEclpParams memory derivedECLPParams) = _reconstructECLPParams();
Vector2 memory invariant;
{
(int256 currentInvariant, int256 invErr) = GyroECLPMath.calculateInvariantWithError(
request.balancesScaled18,
eclpParams,
derivedECLPParams
);
// invariant = overestimate in x-component, underestimate in y-component
// No overflow in `+` due to constraints to the different values enforced in GyroECLPMath.
invariant = Vector2(currentInvariant + 2 * invErr, currentInvariant);
}

if (request.kind == SwapKind.EXACT_IN) {
uint256 amountOutScaled18 = GyroECLPMath.calcOutGivenIn(
request.balancesScaled18,
request.amountGivenScaled18,
tokenInIsToken0,
eclpParams,
derivedECLPParams,
invariant
);

return amountOutScaled18;
} else {
uint256 amountInScaled18 = GyroECLPMath.calcInGivenOut(
request.balancesScaled18,
request.amountGivenScaled18,
tokenInIsToken0,
eclpParams,
derivedECLPParams,
invariant
);

return amountInScaled18;
}
}

/** @dev reconstructs ECLP params structs from immutable arrays */
function _reconstructECLPParams() private view returns (EclpParams memory params, DerivedEclpParams memory d) {
(params.alpha, params.beta, params.c, params.s, params.lambda) = (
_paramsAlpha,
_paramsBeta,
_paramsC,
_paramsS,
_paramsLambda
);
(d.tauAlpha.x, d.tauAlpha.y, d.tauBeta.x, d.tauBeta.y) = (_tauAlphaX, _tauAlphaY, _tauBetaX, _tauBetaY);
(d.u, d.v, d.w, d.z, d.dSq) = (_u, _v, _w, _z, _dSq);
}

function getECLPParams() external view returns (EclpParams memory params, DerivedEclpParams memory d) {
return _reconstructECLPParams();
}

/// @inheritdoc ISwapFeePercentageBounds
function getMinimumSwapFeePercentage() external pure returns (uint256) {
// Liquidity Approximation tests shows that add/remove liquidity combinations are more profitable than a swap
// if the swap fee percentage is 0%, which is not desirable. So, a minimum percentage must be enforced.
return 1e12; // 0.000001%
}

/// @inheritdoc ISwapFeePercentageBounds
function getMaximumSwapFeePercentage() external pure returns (uint256) {
return 1e18; // 100%
}

/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
function getMinimumInvariantRatio() external pure returns (uint256) {
return GyroECLPMath.MIN_INVARIANT_RATIO;
}

/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
function getMaximumInvariantRatio() external pure returns (uint256) {
return GyroECLPMath.MAX_INVARIANT_RATIO;
}
}
Loading

0 comments on commit bca4b2a

Please sign in to comment.