Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Gyro E-CLP #1055

Merged
merged 71 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 68 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
83f678f
Gyro 2-CLP
joaobrunoah Oct 9, 2024
1224305
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 9, 2024
ddbbe5b
Gyro Liquidity Approx Tests
joaobrunoah Oct 9, 2024
7a20c2b
E2e tests
joaobrunoah Oct 10, 2024
e3e7a5a
Fix invariant calculation on Gyro Pools
joaobrunoah Oct 15, 2024
e05dcf2
Remove unused variable
joaobrunoah Oct 15, 2024
c99779d
Fix README
joaobrunoah Oct 15, 2024
d5b491c
Fix E2EBatchSwap tests
joaobrunoah Oct 15, 2024
ea82f17
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 15, 2024
93c9846
Comment Gyro Math
joaobrunoah Oct 15, 2024
d86085d
Fix mutability of test functions
joaobrunoah Oct 15, 2024
963e16f
Fix comments of factory
joaobrunoah Oct 15, 2024
28e213d
First version of E-CLP pool
joaobrunoah Oct 15, 2024
51202d8
Implement computeBalance
joaobrunoah Oct 15, 2024
6182916
Implement LiquidityApprox test for E-CLP
joaobrunoah Oct 15, 2024
50cb3fe
Fix LiquidityApproximation tests
joaobrunoah Oct 16, 2024
801be0c
Create EclpPoolDeployer, to reuse the same pool creation for multiple…
joaobrunoah Oct 16, 2024
a1a95eb
Fix E2E tests
joaobrunoah Oct 16, 2024
6f0dd15
Fix comments
joaobrunoah Oct 16, 2024
f5fabda
Implement deployer to 2CLP
joaobrunoah Oct 16, 2024
c0f3ef3
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 16, 2024
758df85
Merge branch 'gyro-2clp' into gyro-eclp-new
joaobrunoah Oct 16, 2024
fc2b634
Fix naming
joaobrunoah Oct 16, 2024
fc6e4a5
Fix comments
joaobrunoah Oct 16, 2024
b5330d5
Lint
joaobrunoah Oct 16, 2024
7288a9f
Implement CI of Gyro
joaobrunoah Oct 17, 2024
7bffe4e
Update pkg/pool-gyro/contracts/Gyro2CLPPool.sol
joaobrunoah Oct 17, 2024
5034e4c
Merge remote-tracking branch 'origin/gyro-2clp' into gyro-2clp
joaobrunoah Oct 17, 2024
937dca7
Fix comment
joaobrunoah Oct 17, 2024
7e18709
Implement _mulDownOrUp
joaobrunoah Oct 17, 2024
47fa22d
Restore the balance to previous balances
joaobrunoah Oct 17, 2024
e57fdfe
Trying to use Hardhat artifacts
joaobrunoah Oct 17, 2024
54868d0
Fix hardhat imports
joaobrunoah Oct 17, 2024
1bc9ca9
Merge branch 'gyro-2clp' into gyro-eclp-new
joaobrunoah Oct 17, 2024
9da4166
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 18, 2024
cd1ecfb
Add Fungibility test to Gyro 2CLP
joaobrunoah Oct 18, 2024
a5a40c2
Merge branch 'gyro-2clp' into gyro-eclp-new
joaobrunoah Oct 18, 2024
be846e1
Implement fungibility test to Gyro E-CLP
joaobrunoah Oct 18, 2024
5805336
Fix Batch Swap test
joaobrunoah Oct 21, 2024
d6990bd
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 24, 2024
fc9d33f
Merge branch 'main' into gyro-2clp
joaobrunoah Oct 24, 2024
c317780
Merge branch 'gyro-2clp' into gyro-eclp-new
joaobrunoah Oct 24, 2024
d14159c
Merge branch 'main' into gyro-eclp-new
joaobrunoah Oct 29, 2024
b6bba1e
Create interface for GyroECLPPool
joaobrunoah Oct 30, 2024
54276b6
Fix Gyro ECLP deployment
joaobrunoah Oct 30, 2024
b89427d
Fix hardhat compilation
joaobrunoah Oct 30, 2024
d801af9
Fix gyro ECLP build on hardhat
joaobrunoah Oct 30, 2024
582b2ea
Improve documentation
joaobrunoah Oct 30, 2024
34c453e
Improve documentation of the E-CLP pool
joaobrunoah Oct 30, 2024
2e4c493
Merge branch 'main' into gyro-eclp-new
joaobrunoah Oct 30, 2024
a0cf28c
Merge branch 'main' into gyro-eclp-new
joaobrunoah Nov 13, 2024
7dffc49
Fix comments in gyro eclp new port (#1140)
sschuldenzucker Nov 27, 2024
ba72c7b
Merge branch 'main' into gyro-eclp-new
joaobrunoah Nov 27, 2024
ac793f3
Remove error factor when rounding invariant up
joaobrunoah Nov 27, 2024
8741a2e
Add check to GyroECLPPool.computeBalance() against MAX_INVARIANT (#1158)
sschuldenzucker Dec 4, 2024
dcb4d73
Merge branch 'main' into gyro-eclp-new
joaobrunoah Dec 6, 2024
97f9e72
Fix ECLP tests
joaobrunoah Dec 6, 2024
48e2de7
Merge branch 'main' into gyro-eclp-new
joaobrunoah Dec 9, 2024
a4d2a14
Merge branch 'main' into gyro-eclp-new
joaobrunoah Dec 11, 2024
c6b0afc
Merge branch 'main' into gyro-eclp-new
joaobrunoah Dec 12, 2024
364803b
Fix PR comments
joaobrunoah Dec 12, 2024
e638a48
Explaining round up and down of invariant better
joaobrunoah Dec 12, 2024
9de5bd1
Fix comment
joaobrunoah Dec 12, 2024
a534c6d
Use solc 0.8.27 to Gyro E-CLP
joaobrunoah Dec 12, 2024
95461af
Use require instead of revert
joaobrunoah Dec 12, 2024
2a295d2
Replace revert by require
joaobrunoah Dec 12, 2024
51ddbef
Fix Min and Max invariant ratios
joaobrunoah Dec 12, 2024
bcd5632
Fix foundry version
joaobrunoah Dec 13, 2024
b804c8c
Adjust requires to match original code.
Dec 17, 2024
9c1a8cc
Lint.
Dec 17, 2024
773db2e
Fix hardhat config.
Dec 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
joaobrunoah marked this conversation as resolved.
Show resolved Hide resolved
}

/// @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) {
joaobrunoah marked this conversation as resolved.
Show resolved Hide resolved
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) {
joaobrunoah marked this conversation as resolved.
Show resolved Hide resolved
(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;
joaobrunoah marked this conversation as resolved.
Show resolved Hide resolved

(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;
}
}
joaobrunoah marked this conversation as resolved.
Show resolved Hide resolved

/** @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
Loading