Skip to content

Commit

Permalink
Merge branch 'gyro-eclp-new' into restructuring-e2e-tests
Browse files Browse the repository at this point in the history
  • Loading branch information
joaobrunoah committed Dec 13, 2024
2 parents af73b19 + bcd5632 commit 03d8831
Show file tree
Hide file tree
Showing 26 changed files with 1,908 additions and 86 deletions.
8 changes: 8 additions & 0 deletions pkg/interfaces/contracts/pool-gyro/IGyroECLPPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,14 @@ interface IGyroECLPPool is IBasePool {
* 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;
Expand Down
30 changes: 15 additions & 15 deletions pkg/pool-gyro/contracts/GyroECLPPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// for information on licensing please see the README in the GitHub repository
// <https://github.com/gyrostable/concentrated-lps>.

pragma solidity ^0.8.24;
pragma solidity ^0.8.27;

import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
Expand Down Expand Up @@ -32,6 +32,8 @@ 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;
Expand All @@ -53,8 +55,6 @@ contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {
int256 internal immutable _z;
int256 internal immutable _dSq;

bytes32 private constant _POOL_TYPE = "ECLP";

constructor(GyroECLPPoolParams memory params, IVault vault) BalancerPoolToken(vault, params.name, params.symbol) {
GyroECLPMath.validateParams(params.eclpParams);
emit ECLPParamsValidated(true);
Expand Down Expand Up @@ -97,7 +97,7 @@ contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {
);

if (rounding == Rounding.ROUND_DOWN) {
return currentInvariant.toUint256();
return (currentInvariant - invErr).toUint256();
} else {
return (currentInvariant + invErr).toUint256();
}
Expand All @@ -119,18 +119,17 @@ contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {
derivedECLPParams
);

// invariant = overestimate in x-component, underestimate in y-component.
// 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 + 2 * invErr).toUint256().mulUp(invariantRatio).toInt256(),
currentInvariant.toUint256().mulUp(invariantRatio).toInt256()
(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.
if (invariant.y > GyroECLPMath._MAX_INVARIANT) {
revert GyroECLPMath.MaxInvariantExceeded();
}
// 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) {
Expand All @@ -148,6 +147,7 @@ contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {

/// @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();
Expand Down Expand Up @@ -219,11 +219,11 @@ contract GyroECLPPool is IGyroECLPPool, BalancerPoolToken {

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

/// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds
function getMaximumInvariantRatio() external pure returns (uint256) {
return type(uint256).max;
return GyroECLPMath.MAX_INVARIANT_RATIO;
}
}
11 changes: 3 additions & 8 deletions pkg/pool-gyro/contracts/GyroECLPPoolFactory.sol
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-or-later

pragma solidity ^0.8.24;
pragma solidity ^0.8.27;

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

Expand Down Expand Up @@ -53,13 +53,8 @@ contract GyroECLPPoolFactory is BasePoolFactory {
address poolHooksContract,
bytes32 salt
) external returns (address pool) {
if (tokens.length != 2) {
revert SupportsOnlyTwoTokens();
}

if (roleAccounts.poolCreator != address(0)) {
revert StandardPoolWithCreator();
}
require(tokens.length == 2, SupportsOnlyTwoTokens());
require(roleAccounts.poolCreator == address(0), StandardPoolWithCreator());

pool = _create(
abi.encode(
Expand Down
97 changes: 46 additions & 51 deletions pkg/pool-gyro/contracts/lib/GyroECLPMath.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
// for information on licensing please see the README in the GitHub repository
// <https://github.com/gyrostable/concentrated-lps>.

pragma solidity ^0.8.24;
pragma solidity ^0.8.27;

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

Expand All @@ -24,12 +24,17 @@ library GyroECLPMath {
using SafeCast for uint256;
using SafeCast for int256;

error RotationVectorWrong();
error RotationVectorSWrong();
error RotationVectorCWrong();
error RotationVectorNotNormalized();
error AssetBoundsExceeded();
error DerivedTauNotNormalized();
error DerivedTauAlphaNotNormalized();
error DerivedTauBetaNotNormalized();
error StretchingFactorWrong();
error DerivedUvwzWrong();
error DerivedUWrong();
error DerivedVWrong();
error DerivedWWrong();
error DerivedZWrong();
error InvariantDenominatorWrong();
error MaxAssetsExceeded();
error MaxInvariantExceeded();
Expand All @@ -50,6 +55,11 @@ library GyroECLPMath {
int256 internal constant _MAX_BALANCES = 1e34; // 1e16 in normal precision
int256 internal constant _MAX_INVARIANT = 3e37; // 3e19 in normal precision

// Invariant growth limit: non-proportional add cannot cause the invariant to increase by more than this ratio.
uint256 public constant MIN_INVARIANT_RATIO = 60e16; // 60%
// Invariant shrink limit: non-proportional remove cannot cause the invariant to decrease by less than this ratio.
uint256 public constant MAX_INVARIANT_RATIO = 500e16; // 500%

struct QParams {
int256 a;
int256 b;
Expand All @@ -58,24 +68,17 @@ library GyroECLPMath {

/// @dev Enforces limits and approximate normalization of the rotation vector.
function validateParams(IGyroECLPPool.EclpParams memory params) internal pure {
if (0 > params.s || params.s > _ONE) {
revert RotationVectorWrong();
}

if (0 > params.c || params.c > _ONE) {
revert RotationVectorWrong();
}
require(params.s > 0 && params.s < _ONE, RotationVectorSWrong());
require(params.c > 0 && params.c < _ONE, RotationVectorCWrong());

IGyroECLPPool.Vector2 memory sc = IGyroECLPPool.Vector2(params.s, params.c);
int256 scnorm2 = scalarProd(sc, sc); // squared norm

if (_ONE - _ROTATION_VECTOR_NORM_ACCURACY > scnorm2 || scnorm2 > _ONE + _ROTATION_VECTOR_NORM_ACCURACY) {
revert RotationVectorNotNormalized();
}

if (params.lambda < 0 || params.lambda > _MAX_STRETCH_FACTOR) {
revert StretchingFactorWrong();
}
require(
scnorm2 > _ONE - _ROTATION_VECTOR_NORM_ACCURACY && scnorm2 < _ONE + _ROTATION_VECTOR_NORM_ACCURACY,
RotationVectorNotNormalized()
);
require(params.lambda > 0 && params.lambda < _MAX_STRETCH_FACTOR, StretchingFactorWrong());
}

/**
Expand All @@ -89,34 +92,32 @@ library GyroECLPMath {
int256 norm2;
norm2 = scalarProdXp(derived.tauAlpha, derived.tauAlpha);

if (_ONE_XP - _DERIVED_TAU_NORM_ACCURACY_XP > norm2 || norm2 > _ONE_XP + _DERIVED_TAU_NORM_ACCURACY_XP) {
revert DerivedTauNotNormalized();
}
require(
norm2 > _ONE_XP - _DERIVED_TAU_NORM_ACCURACY_XP && norm2 < _ONE_XP + _DERIVED_TAU_NORM_ACCURACY_XP,
DerivedTauAlphaNotNormalized()
);

norm2 = scalarProdXp(derived.tauBeta, derived.tauBeta);

if (_ONE_XP - _DERIVED_TAU_NORM_ACCURACY_XP > norm2 || norm2 > _ONE_XP + _DERIVED_TAU_NORM_ACCURACY_XP) {
revert DerivedTauNotNormalized();
}

if (derived.u > _ONE_XP) revert DerivedUvwzWrong();
if (derived.v > _ONE_XP) revert DerivedUvwzWrong();
if (derived.w > _ONE_XP) revert DerivedUvwzWrong();
if (derived.z > _ONE_XP) revert DerivedUvwzWrong();

if (
_ONE_XP - _DERIVED_DSQ_NORM_ACCURACY_XP > derived.dSq ||
derived.dSq > _ONE_XP + _DERIVED_DSQ_NORM_ACCURACY_XP
) {
revert DerivedDsqWrong();
}
require(
norm2 > _ONE_XP - _DERIVED_TAU_NORM_ACCURACY_XP && norm2 < _ONE_XP + _DERIVED_TAU_NORM_ACCURACY_XP,
DerivedTauBetaNotNormalized()
);
require(derived.u < _ONE_XP, DerivedUWrong());
require(derived.v < _ONE_XP, DerivedVWrong());
require(derived.w < _ONE_XP, DerivedWWrong());
require(derived.z < _ONE_XP, DerivedZWrong());

require(
derived.dSq > _ONE_XP - _DERIVED_DSQ_NORM_ACCURACY_XP &&
derived.dSq < _ONE_XP + _DERIVED_DSQ_NORM_ACCURACY_XP,
DerivedDsqWrong()
);

// NB No anti-overflow checks are required given the checks done above and in validateParams().
int256 mulDenominator = _ONE_XP.divXpU(calcAChiAChiInXp(params, derived) - _ONE_XP);

if (mulDenominator > _MAX_INV_INVARIANT_DENOMINATOR_XP) {
revert InvariantDenominatorWrong();
}
require(mulDenominator < _MAX_INV_INVARIANT_DENOMINATOR_XP, InvariantDenominatorWrong());
}

function scalarProd(
Expand Down Expand Up @@ -252,9 +253,7 @@ library GyroECLPMath {
) internal pure returns (int256, int256) {
(int256 x, int256 y) = (balances[0].toInt256(), balances[1].toInt256());

if (x + y > _MAX_BALANCES) {
revert MaxAssetsExceeded();
}
require(x + y < _MAX_BALANCES, MaxAssetsExceeded());

int256 atAChi = calcAtAChi(x, y, params, derived);
(int256 sqrt, int256 err) = calcInvariantSqrt(x, y, params, derived);
Expand Down Expand Up @@ -297,9 +296,7 @@ library GyroECLPMath {
_ONE_XP +
1;

if (invariant + err > _MAX_INVARIANT) {
revert MaxInvariantExceeded();
}
require(invariant + err < _MAX_INVARIANT, MaxInvariantExceeded());

return (invariant, err);
}
Expand Down Expand Up @@ -517,12 +514,10 @@ library GyroECLPMath {
) internal pure {
if (assetIndex == 0) {
int256 xPlus = maxBalances0(params, derived, invariant);
if (!(newBal <= _MAX_BALANCES && newBal <= xPlus)) revert AssetBoundsExceeded();
return;
}
{
require(newBal <= _MAX_BALANCES && newBal <= xPlus, AssetBoundsExceeded());
} else {
int256 yPlus = maxBalances1(params, derived, invariant);
if (!(newBal <= _MAX_BALANCES && newBal <= yPlus)) revert AssetBoundsExceeded();
require(newBal <= _MAX_BALANCES && newBal <= yPlus, AssetBoundsExceeded());
}
}

Expand Down Expand Up @@ -586,7 +581,7 @@ library GyroECLPMath {
calcGiven = calcYGivenX; // this reverses compared to calcOutGivenIn
}

if (!(amountOut <= balances[ixOut])) revert AssetBoundsExceeded();
require(amountOut <= balances[ixOut], AssetBoundsExceeded());
int256 balOutNew = (balances[ixOut] - amountOut).toInt256();
int256 balInNew = calcGiven(balOutNew, params, derived, invariant);
// The checks in the following two lines should really always succeed; we keep them as extra safety against
Expand Down
2 changes: 1 addition & 1 deletion pkg/pool-gyro/foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ remappings = [
]
optimizer = true
optimizer_runs = 999
solc_version = '0.8.26'
solc_version = '0.8.27'
auto_detect_solc = false
evm_version = 'cancun'
ignored_error_codes = [2394, 5574, 3860] # Transient storage, code size
Expand Down
2 changes: 2 additions & 0 deletions pkg/pool-gyro/hardhat.config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { HardhatUserConfig } from 'hardhat/config';
import { name } from './package.json';

import { hardhatBaseConfig } from '@balancer-labs/v3-common';

Expand All @@ -14,6 +15,7 @@ import { warnings } from '@balancer-labs/v3-common/hardhat-base-config';
const config: HardhatUserConfig = {
solidity: {
compilers: hardhatBaseConfig.compilers,
overrides: { ...hardhatBaseConfig.overrides(name) },
},
warnings,
};
Expand Down
7 changes: 3 additions & 4 deletions pkg/pool-gyro/test/foundry/E2eBatchSwapECLP.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,8 @@ contract E2eBatchSwapECLPTest is E2eBatchSwapTest, GyroEclpPoolDeployer {
minSwapAmountTokenA = 10 * PRODUCTION_MIN_TRADE_AMOUNT;
minSwapAmountTokenD = 10 * PRODUCTION_MIN_TRADE_AMOUNT;

// Divide init amount by 10 to make sure weighted math ratios are respected (Cannot trade more than 30% of pool
// balance).
maxSwapAmountTokenA = poolInitAmount / 10;
maxSwapAmountTokenD = poolInitAmount / 10;
// 25% of pool init amount, so MIN and MAX invariant ratios are not violated.
maxSwapAmountTokenA = poolInitAmount / 4;
maxSwapAmountTokenD = poolInitAmount / 4;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ contract LiquidityApproximationECLPTest is LiquidityApproximationTest, GyroEclpP
minSwapFeePercentage = IBasePool(swapPool).getMinimumSwapFeePercentage();

// The invariant of ECLP pools are smaller.
maxAmount = 1e6 * 1e18;
maxAmount = 1e5 * 1e18;
}

function _createPool(
Expand Down
Loading

0 comments on commit 03d8831

Please sign in to comment.