From 1d46422aa0fe3adef042643229d07890b6fc59b5 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 9 Jun 2023 14:32:04 +0545 Subject: [PATCH 01/69] init --- src/functions/StableSwap.sol | 133 +++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/functions/StableSwap.sol diff --git a/src/functions/StableSwap.sol b/src/functions/StableSwap.sol new file mode 100644 index 00000000..5d58a75d --- /dev/null +++ b/src/functions/StableSwap.sol @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; + +/** + * @author Publius + * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. + * developed by solidly. + * + * Stableswap Wells with 2 tokens use the formula: + * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` + * + * Where: + * `A` is the Amplication parameter. + * `D` is the supply of LP tokens + * `b_i` is the reserve at index `i` + */ +contract StableSwap is ProportionalLPToken2 { + using LibMath for uint; + + uint constant EXP_PRECISION = 1e12; + uint constant A_PRECISION = 100; + + // A paramater + uint constant A = 1; + // 2 token Pool. + uint constant N = 2; + // Ann is used everywhere `shrug` + uint constant Ann = A * N; + + /** + * D invariant calculation in non-overflowing integer operations iteratively + * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + * + * Converging solution: + * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) + * D[2] = (4 * A * sum(b_i) - (D[1] ** 3) / (4 * prod(b_i))) / (4 * A - 1) + */ + function calcLpTokenSupply( + uint[] calldata reserves, + bytes calldata + ) external pure override returns (uint d) { + uint256 s = 0; + uint256 Dprev = 0; + + s = reserves[0] + reserves[1]; + if(s == 0) return 0; + d = s; + + // wtf is this bullshit + for(uint i; i < 255; i++){ + uint256 d_p = d; + for(uint j; j < N; j++){ + // If division by 0, this will be borked: only withdrawal will work. And that is good + d_p = d_p * d / (reserves[j] * N); + } + Dprev = d; + d = (Ann * s / A_PRECISION + d_p * N) * + d / ( + (Ann - A_PRECISION) * d / + A_PRECISION + (N + 1) * d_p + ); + // Equality with the precision of 1 + if (d > Dprev){ + if(d - Dprev <= 1) return d; + } + else { + if(Dprev - d <= 1) return d; + } + } + } + + /** + * @notice Calculate x[i] if one reduces D from being calculated for xp to D + * Done by solving quadratic equation iteratively. + * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + * x_1**2 + b*x_1 = c + * x_1 = (x_1**2 + c) / (2*x_1 + b) + */ + function calcReserve( + uint[] calldata reserves, + uint j, + uint lpTokenSupply, + bytes calldata + ) external pure override returns (uint reserve) { + require(j < N); + uint256 c = lpTokenSupply; + uint256 s; + uint256 _x; + uint256 y_prev; + + + for(uint i; i < N; ++i){ + if(i != j){ + _x = reserves[j]; + } else { + continue; + } + s +=_x; + c = c * lpTokenSupply / (_x * N); + } + c = c * lpTokenSupply * A /(Ann * N); + uint256 b = s + lpTokenSupply * A_PRECISION / Ann; + reserve = lpTokenSupply; + + for(uint i; i < 255; ++i){ + y_prev = reserve; + reserve = (reserve*reserve + c) / (2 * reserve + b - lpTokenSupply); + // Equality with the precision of 1 + if(reserve > y_prev){ + if(reserve - y_prev <= 1) return reserve; + } else { + if(y_prev - reserve <= 1) return reserve; + } + } + revert("did not find convergence"); + } + + function name() external pure override returns (string memory) { + return "Stableswap"; + } + + function symbol() external pure override returns (string memory) { + return "SS"; + } + + function cube(uint256 reserve) private pure returns (uint256) { + return reserve * reserve * reserve; + } +} From fc4aa2025156a389b1ae321ba3b12f2e4921cd83 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 11 Jun 2023 12:48:02 +0545 Subject: [PATCH 02/69] added A parameter input, added well function tests. --- src/functions/StableSwap.sol | 100 ++++++++++------- test/functions/StableSwap.t.sol | 188 ++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+), 38 deletions(-) create mode 100644 test/functions/StableSwap.t.sol diff --git a/src/functions/StableSwap.sol b/src/functions/StableSwap.sol index 5d58a75d..22134d54 100644 --- a/src/functions/StableSwap.sol +++ b/src/functions/StableSwap.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.17; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {LibMath} from "src/libraries/LibMath.sol"; +import {SafeMath} from "oz/utils/math/SafeMath.sol"; /** - * @author Publius + * @author Publius, Brean * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. * developed by solidly. * @@ -20,56 +21,66 @@ import {LibMath} from "src/libraries/LibMath.sol"; */ contract StableSwap is ProportionalLPToken2 { using LibMath for uint; + using SafeMath for uint; - uint constant EXP_PRECISION = 1e12; uint constant A_PRECISION = 100; - // A paramater - uint constant A = 1; + // A parameter + uint public immutable a; // 2 token Pool. uint constant N = 2; // Ann is used everywhere `shrug` - uint constant Ann = A * N; + // uint256 constant Ann = A * N * A_PRECISION; + constructor(uint _a) { + a = _a; + } /** * D invariant calculation in non-overflowing integer operations iteratively * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) * * Converging solution: * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) - * D[2] = (4 * A * sum(b_i) - (D[1] ** 3) / (4 * prod(b_i))) / (4 * A - 1) */ function calcLpTokenSupply( uint[] calldata reserves, bytes calldata - ) external pure override returns (uint d) { - uint256 s = 0; - uint256 Dprev = 0; - - s = reserves[0] + reserves[1]; - if(s == 0) return 0; - d = s; - + ) external view override returns (uint lpTokenSupply) { + uint256 sumReserves = reserves[0] + reserves[1]; + uint256 prevD; + if(sumReserves == 0) return 0; + + lpTokenSupply = sumReserves; + uint256 Ann = a * N * N * A_PRECISION; // wtf is this bullshit - for(uint i; i < 255; i++){ - uint256 d_p = d; - for(uint j; j < N; j++){ + for(uint i = 0; i < 255; i++){ + uint256 dP = lpTokenSupply; + for(uint j = 0; j < N; j++){ // If division by 0, this will be borked: only withdrawal will work. And that is good - d_p = d_p * d / (reserves[j] * N); + dP = dP.mul(lpTokenSupply).div(reserves[j].mul(N)); } - Dprev = d; - d = (Ann * s / A_PRECISION + d_p * N) * - d / ( - (Ann - A_PRECISION) * d / - A_PRECISION + (N + 1) * d_p + prevD = lpTokenSupply; + lpTokenSupply = Ann + .mul(sumReserves) + .div(A_PRECISION) + .add(dP.mul(N)) + .mul(lpTokenSupply) + .div( + Ann + .sub(A_PRECISION) + .mul(lpTokenSupply) + .div(A_PRECISION) + .add(N.add(1).mul(dP)) ); + // Equality with the precision of 1 - if (d > Dprev){ - if(d - Dprev <= 1) return d; + + if (lpTokenSupply > prevD){ + if(lpTokenSupply - prevD <= 1) return lpTokenSupply; } else { - if(Dprev - d <= 1) return d; - } + if(prevD - lpTokenSupply <= 1) return lpTokenSupply; + } } } @@ -85,42 +96,55 @@ contract StableSwap is ProportionalLPToken2 { uint j, uint lpTokenSupply, bytes calldata - ) external pure override returns (uint reserve) { + ) external view override returns (uint reserve) { require(j < N); uint256 c = lpTokenSupply; - uint256 s; + uint256 sumReserves; uint256 _x; uint256 y_prev; + uint256 Ann = a * N * N * A_PRECISION; for(uint i; i < N; ++i){ if(i != j){ - _x = reserves[j]; + _x = reserves[i]; } else { continue; } - s +=_x; - c = c * lpTokenSupply / (_x * N); + sumReserves = sumReserves.add(_x); + c = c.mul(lpTokenSupply).div(_x.mul(N)); } - c = c * lpTokenSupply * A /(Ann * N); - uint256 b = s + lpTokenSupply * A_PRECISION / Ann; + c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + uint256 b = + sumReserves.add( + lpTokenSupply.mul(A_PRECISION).div(Ann) + ); reserve = lpTokenSupply; for(uint i; i < 255; ++i){ y_prev = reserve; - reserve = (reserve*reserve + c) / (2 * reserve + b - lpTokenSupply); + reserve = + reserve + .mul(reserve) + .add(c) + .div( + reserve + .mul(2) + .add(b) + .sub(lpTokenSupply) + ); // Equality with the precision of 1 if(reserve > y_prev){ - if(reserve - y_prev <= 1) return reserve; + if(reserve.sub(y_prev) <= 1) return reserve; } else { - if(y_prev - reserve <= 1) return reserve; + if(y_prev.sub(reserve) <= 1) return reserve; } } revert("did not find convergence"); } function name() external pure override returns (string memory) { - return "Stableswap"; + return "StableSwap"; } function symbol() external pure override returns (string memory) { diff --git a/test/functions/StableSwap.t.sol b/test/functions/StableSwap.t.sol new file mode 100644 index 00000000..88f084cb --- /dev/null +++ b/test/functions/StableSwap.t.sol @@ -0,0 +1,188 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {console, TestHelper} from "test/TestHelper.sol"; +import {WellFunctionHelper} from "./WellFunctionHelper.sol"; +import {StableSwap} from "src/functions/StableSwap.sol"; + +/// @dev Tests the {StableSwap} Well function directly. +contract StableSwapTest is WellFunctionHelper { + /** + * State A: Same decimals + * D (lpTokenSupply) should be the summation of + * the reserves, assuming they are equal. + */ + uint STATE_A_B0 = 10 * 1e18; + uint STATE_A_B1 = 10 * 1e18; + uint STATE_A_LP = 20 * 1e18; + + /** + * State B: Different decimals + * @notice the stableswap implmentation + * uses precision-adjusted to 18 decimals. + * In other words, a token with 6 decimals + * will be scaled up such that it uses 18 decimals. + * + * @dev D is the summation of the reserves, + * assuming they are equal. + * + */ + uint STATE_B_B0 = 10 * 1e18; + uint STATE_B_B1 = 20 * 1e18; + uint STATE_B_LP = 29_911_483_643_966_454_823; // ~29e18 + + + /// State C: Similar decimals + uint STATE_C_B0 = 20 * 1e18; + uint STATE_C_LP = 25 * 1e24; + uint STATE_C_B1 = 2_221_929_790_566_403_172_822_276_028; // 2.221e19 + + /// @dev See {calcLpTokenSupply}. + uint MAX_RESERVE = 6e31; + + //////////// SETUP //////////// + + function setUp() public { + _function = new StableSwap(10); + _data = ""; + } + + function test_metadata() public { + assertEq(_function.name(), "StableSwap"); + assertEq(_function.symbol(), "SS"); + } + + //////////// LP TOKEN SUPPLY //////////// + + /// @dev reverts when trying to calculate lp token supply with < 2 reserves + function test_calcLpTokenSupply_minBalancesLength() public { + check_calcLpTokenSupply_minBalancesLength(2); + } + + /// @dev calcLpTokenSupply: same decimals, manual calc for 2 equal reserves + function test_calcLpTokenSupply_sameDecimals() public { + uint[] memory reserves = new uint[](2); + reserves[0] = STATE_A_B0; + reserves[1] = STATE_A_B1; + assertEq( + _function.calcLpTokenSupply(reserves, _data), + STATE_A_LP // sqrt(10e18 * 10e18) * 2 + ); + } + + /// @dev calcLpTokenSupply: diff decimals + function test_calcLpTokenSupply_diffDecimals() public { + uint[] memory reserves = new uint[](2); + reserves[0] = STATE_B_B0; // ex. 1 WETH + reserves[1] = STATE_B_B1; // ex. 1250 BEAN + assertEq( + _function.calcLpTokenSupply(reserves, _data), + STATE_B_LP + ); + } + + //////////// RESERVES //////////// + + /// @dev calcReserve: same decimals, both positions + /// Matches example in {testLpTokenSupplySameDecimals}. + function test_calcReserve_sameDecimals() public { + uint[] memory reserves = new uint[](2); + + /// STATE A + // find reserves[0] + reserves[0] = 0; + reserves[1] = STATE_A_B1; + assertEq( + _function.calcReserve(reserves, 0, STATE_A_LP, _data), + STATE_A_B0 + ); + + // find reserves[1] + reserves[0] = STATE_A_B0; + reserves[1] = 0; + assertEq( + _function.calcReserve(reserves, 1, STATE_A_LP, _data), + STATE_A_B1 + ); + + /// STATE C + // find reserves[1] + reserves[0] = STATE_C_B0; + reserves[1] = 0; + assertEq( + _function.calcReserve(reserves, 1, STATE_C_LP, _data), + STATE_C_B1 // (50e18/2) ^ 2 / 20e18 = 31.25e19 + ); + } + + /// @dev calcReserve: diff decimals, both positions + /// Matches example in {testLpTokenSupplyDiffDecimals}. + function test_calcReserve_diffDecimals() public { + uint[] memory reserves = new uint[](2); + + /// STATE B + // find reserves[0] + reserves[0] = 0; + reserves[1] = STATE_B_B1; + assertEq( + _function.calcReserve(reserves, 0, STATE_B_LP, _data), + STATE_B_B0 // (70710678118654 / 2)^2 / 1250e6 = ~1e18 + ); + + // find reserves[1] + reserves[0] = STATE_B_B0; // placeholder + reserves[1] = 0; // ex. 1250 BEAN + assertEq( + _function.calcReserve(reserves, 1, STATE_B_LP, _data), + STATE_B_B1 // (70710678118654 / 2)^2 / 1e18 = 1250e6 + ); + } + + //////////// LP TOKEN SUPPLY //////////// + + /// @dev invariant: reserves -> lpTokenSupply -> reserves should match + function testFuzz_calcLpTokenSupply(uint[2] memory _reserves) public { + uint[] memory reserves = new uint[](2); + reserves[0] = bound(_reserves[0], 1e18, MAX_RESERVE); + reserves[1] = bound(_reserves[1], 1e18, MAX_RESERVE); + uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); + console.log("lpTokenSupply: ", lpTokenSupply); + uint[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, ""); + console.log("underlying1: ", underlying[0]); + console.log("underlying0: ", underlying[1]); + for (uint i = 0; i < reserves.length; ++i) { + assertEq(reserves[i], underlying[i], "reserves mismatch"); + } + } + + //////////// FUZZ //////////// + + function testFuzz_constantProduct2(uint x, uint y) public { + uint[] memory reserves = new uint[](2); + bytes memory _data = new bytes(0); + + reserves[0] = bound(x, 1e18, MAX_RESERVE); + reserves[1] = bound(y, 1e18, MAX_RESERVE); + + uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); + uint reserve0 = _function.calcReserve(reserves, 0, lpTokenSupply, _data); + uint reserve1 = _function.calcReserve(reserves, 1, lpTokenSupply, _data); + + if (reserves[0] < 1e12) { + assertApproxEqAbs(reserve0, reserves[0], 1); + // assertApproxEqRel(reserve0, reserves[0], 3e6); + console.log("check1"); + } else { + assertApproxEqRel(reserve0, reserves[0], 3e6); + console.log("check2"); + } + if (reserves[1] < 1e12) { + assertApproxEqAbs(reserve1, reserves[1], 1); + // assertApproxEqRel(reserve0, reserves[0], 3e6); + console.log("check3"); + } else { + assertApproxEqRel(reserve1, reserves[1], 3e6); + console.log("check4"); + } + } +} From d346be13f8a58dcb451858ded3d8339c25d4d284 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 11 Jun 2023 15:10:48 +0545 Subject: [PATCH 03/69] Bore Tests, WellFunctionData. --- src/functions/StableSwap.sol | 157 --------------------------- src/functions/StableSwap2.sol | 182 ++++++++++++++++++++++++++++++++ test/TestHelper.sol | 10 ++ test/Well.BoreStableSwap.t.sol | 141 +++++++++++++++++++++++++ test/functions/StableSwap.t.sol | 24 ++--- 5 files changed, 341 insertions(+), 173 deletions(-) delete mode 100644 src/functions/StableSwap.sol create mode 100644 src/functions/StableSwap2.sol create mode 100644 test/Well.BoreStableSwap.t.sol diff --git a/src/functions/StableSwap.sol b/src/functions/StableSwap.sol deleted file mode 100644 index 22134d54..00000000 --- a/src/functions/StableSwap.sol +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; -import {SafeMath} from "oz/utils/math/SafeMath.sol"; - -/** - * @author Publius, Brean - * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. - * developed by solidly. - * - * Stableswap Wells with 2 tokens use the formula: - * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` - * - * Where: - * `A` is the Amplication parameter. - * `D` is the supply of LP tokens - * `b_i` is the reserve at index `i` - */ -contract StableSwap is ProportionalLPToken2 { - using LibMath for uint; - using SafeMath for uint; - - uint constant A_PRECISION = 100; - - // A parameter - uint public immutable a; - // 2 token Pool. - uint constant N = 2; - // Ann is used everywhere `shrug` - // uint256 constant Ann = A * N * A_PRECISION; - - constructor(uint _a) { - a = _a; - } - /** - * D invariant calculation in non-overflowing integer operations iteratively - * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - * - * Converging solution: - * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) - */ - function calcLpTokenSupply( - uint[] calldata reserves, - bytes calldata - ) external view override returns (uint lpTokenSupply) { - uint256 sumReserves = reserves[0] + reserves[1]; - uint256 prevD; - if(sumReserves == 0) return 0; - - lpTokenSupply = sumReserves; - uint256 Ann = a * N * N * A_PRECISION; - // wtf is this bullshit - for(uint i = 0; i < 255; i++){ - uint256 dP = lpTokenSupply; - for(uint j = 0; j < N; j++){ - // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(reserves[j].mul(N)); - } - prevD = lpTokenSupply; - lpTokenSupply = Ann - .mul(sumReserves) - .div(A_PRECISION) - .add(dP.mul(N)) - .mul(lpTokenSupply) - .div( - Ann - .sub(A_PRECISION) - .mul(lpTokenSupply) - .div(A_PRECISION) - .add(N.add(1).mul(dP)) - ); - - // Equality with the precision of 1 - - if (lpTokenSupply > prevD){ - if(lpTokenSupply - prevD <= 1) return lpTokenSupply; - } - else { - if(prevD - lpTokenSupply <= 1) return lpTokenSupply; - } - } - } - - /** - * @notice Calculate x[i] if one reduces D from being calculated for xp to D - * Done by solving quadratic equation iteratively. - * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - * x_1**2 + b*x_1 = c - * x_1 = (x_1**2 + c) / (2*x_1 + b) - */ - function calcReserve( - uint[] calldata reserves, - uint j, - uint lpTokenSupply, - bytes calldata - ) external view override returns (uint reserve) { - require(j < N); - uint256 c = lpTokenSupply; - uint256 sumReserves; - uint256 _x; - uint256 y_prev; - uint256 Ann = a * N * N * A_PRECISION; - - - for(uint i; i < N; ++i){ - if(i != j){ - _x = reserves[i]; - } else { - continue; - } - sumReserves = sumReserves.add(_x); - c = c.mul(lpTokenSupply).div(_x.mul(N)); - } - c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - uint256 b = - sumReserves.add( - lpTokenSupply.mul(A_PRECISION).div(Ann) - ); - reserve = lpTokenSupply; - - for(uint i; i < 255; ++i){ - y_prev = reserve; - reserve = - reserve - .mul(reserve) - .add(c) - .div( - reserve - .mul(2) - .add(b) - .sub(lpTokenSupply) - ); - // Equality with the precision of 1 - if(reserve > y_prev){ - if(reserve.sub(y_prev) <= 1) return reserve; - } else { - if(y_prev.sub(reserve) <= 1) return reserve; - } - } - revert("did not find convergence"); - } - - function name() external pure override returns (string memory) { - return "StableSwap"; - } - - function symbol() external pure override returns (string memory) { - return "SS"; - } - - function cube(uint256 reserve) private pure returns (uint256) { - return reserve * reserve * reserve; - } -} diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol new file mode 100644 index 00000000..47cecb51 --- /dev/null +++ b/src/functions/StableSwap2.sol @@ -0,0 +1,182 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {IWellFunction} from "src/interfaces/IWellFunction.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; +import {SafeMath} from "oz/utils/math/SafeMath.sol"; +/** + * @author Publius, Brean + * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. + * developed by solidly. + * + * Stableswap Wells with 2 tokens use the formula: + * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` + * + * Where: + * `A` is the Amplication parameter. + * `D` is the supply of LP tokens + * `b_i` is the reserve at index `i` + */ +contract StableSwap2 is IWellFunction { + using LibMath for uint; + using SafeMath for uint; + + + + // A parameter, + // uint public immutable a; + // uint public immutable Ann; + + // 2 token Pool. + uint constant N = 2; + uint constant A_PRECISION = 100; + + /** + * This Well function requires 3 parameters from wellFunctionData: + * 1: A parameter + * 2: tkn0Scalar + * 3: tkn1Scalar + * + * @dev The StableSwap curve assumes that both tokens use the same decimals (max 1e18). + * TKNX_SCALAR scales up the decimals if needed to 1e18. + * For example, USDC and BEAN has 6 decimals (TKX_SCALAR = 1e12), + * while DAI has 18 decimals (TKX_SCALAR = 1). + */ + struct WellFunctionData { + uint256 a; // A parameter + uint256 tkn0Scalar; + uint256 tkn1Scalar; + } + + /** + * D invariant calculation in non-overflowing integer operations iteratively + * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + * + * Converging solution: + * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) + */ + function calcLpTokenSupply( + uint[] calldata reserves, + bytes calldata _wellFunctionData + ) external pure override returns (uint lpTokenSupply) { + (WellFunctionData memory wfd, uint256 Ann) = decodeWellFunctionData(_wellFunctionData); + uint256 sumReserves = reserves[0] + reserves[1]; + if(sumReserves == 0) return 0; + + lpTokenSupply = sumReserves; + + // wtf is this bullshit + for(uint i = 0; i < 255; i++){ + uint256 dP = lpTokenSupply; + // If division by 0, this will be borked: only withdrawal will work. And that is good + dP = dP.mul(lpTokenSupply).div(reserves[0].mul(wfd.tkn0Scalar).mul(N)); + dP = dP.mul(lpTokenSupply).div(reserves[1].mul(wfd.tkn1Scalar).mul(N)); + uint256 prevReserves = lpTokenSupply; + lpTokenSupply = Ann + .mul(sumReserves) + .div(A_PRECISION) + .add(dP.mul(N)) + .mul(lpTokenSupply) + .div( + Ann + .sub(A_PRECISION) + .mul(lpTokenSupply) + .div(A_PRECISION) + .add(N.add(1).mul(dP)) + ); + // Equality with the precision of 1 + if (lpTokenSupply > prevReserves){ + if(lpTokenSupply - prevReserves <= 1) return lpTokenSupply; + } + else { + if(prevReserves - lpTokenSupply <= 1) return lpTokenSupply; + } + } + } + + /** + * @notice Calculate x[i] if one reduces D from being calculated for xp to D + * Done by solving quadratic equation iteratively. + * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + * x_1**2 + b*x_1 = c + * x_1 = (x_1**2 + c) / (2*x_1 + b) + */ + function calcReserve( + uint[] calldata reserves, + uint j, + uint lpTokenSupply, + bytes calldata _wellFunctionData + ) external pure override returns (uint reserve) { + (WellFunctionData memory wfd, uint256 Ann) = decodeWellFunctionData(_wellFunctionData); + require(j < N); + uint256 c = lpTokenSupply; + uint256 sumReserves; + uint256 prevReserve; + + sumReserves = j == 0 ? reserves[1].mul(wfd.tkn1Scalar) : reserves[0].mul(wfd.tkn0Scalar); + c = c.mul(lpTokenSupply).div(sumReserves.mul(N)); + c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + uint256 b = + sumReserves.add( + lpTokenSupply.mul(A_PRECISION).div(Ann) + ); + reserve = lpTokenSupply; + + for(uint i; i < 255; ++i){ + prevReserve = reserve; + reserve = + reserve + .mul(reserve) + .add(c) + .div( + reserve + .mul(2) + .add(b) + .sub(lpTokenSupply) + ); + // Equality with the precision of 1 + // safeMath not needed due to conditional. + if(reserve > prevReserve){ + if(reserve - prevReserve <= 1) return reserve; + } else { + if(prevReserve - reserve <= 1) return reserve; + } + } + + revert("did not find convergence"); + } + + /** + * @notice Defines a proportional relationship between the supply of LP tokens + * and the amount of each underlying token for a two-token Well. + * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user + * recieves `s * b_i / S` of each underlying token. + * reserves are scaled as needed based on tknXScalar + */ + function calcLPTokenUnderlying( + uint lpTokenAmount, + uint[] calldata reserves, + uint lpTokenSupply, + bytes calldata _wellFunctionData + ) external view returns (uint[] memory underlyingAmounts) { + (WellFunctionData memory wfd, ) = decodeWellFunctionData(_wellFunctionData); + underlyingAmounts = new uint[](2); + // overflow cannot occur as lpTokenAmount could not be calculated. + underlyingAmounts[0] = lpTokenAmount * reserves[0] * wfd.tkn0Scalar / lpTokenSupply; + underlyingAmounts[1] = lpTokenAmount * reserves[1] * wfd.tkn1Scalar / lpTokenSupply; + } + + function name() external pure override returns (string memory) { + return "StableSwap"; + } + + function symbol() external pure override returns (string memory) { + return "SS2"; + } + + function decodeWellFunctionData(bytes memory data) public pure returns (WellFunctionData memory wfd, uint256 Ann){ + wfd = abi.decode(data, (WellFunctionData)); + Ann = wfd.a * N * N * A_PRECISION; + } +} diff --git a/test/TestHelper.sol b/test/TestHelper.sol index bf776233..9ee50113 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -197,6 +197,16 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = new bytes(0); } + function deployWellFunction(address _target) internal returns (Call memory _wellFunction) { + _wellFunction.target = _target; + _wellFunction.data = new bytes(0); + } + + function deployWellFunction(address _target, bytes memory _data) internal returns (Call memory _wellFunction) { + _wellFunction.target = _target; + _wellFunction.data = _data; + } + function deployPumps(uint n) internal returns (Call[] memory _pumps) { _pumps = new Call[](n); for (uint i = 0; i < n; i++) { diff --git a/test/Well.BoreStableSwap.t.sol b/test/Well.BoreStableSwap.t.sol new file mode 100644 index 00000000..3604d23a --- /dev/null +++ b/test/Well.BoreStableSwap.t.sol @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; +import {MockPump} from "mocks/pumps/MockPump.sol"; +import {StableSwap2} from "src/functions/StableSwap2.sol"; + +contract WellBoreStableSwapTest is TestHelper { + /// @dev Bore a 2-token Well with StableSwap2 & several pumps. + function setUp() public { + setupWell( + 2, // 2 tokens + deployWellFunction( + address(new StableSwap2()), + abi.encode(StableSwap2.WellFunctionData( + 10, // A parameter + 1, // TKN0SCALAR + 1 // TKN1SCALAR + )) + ), + deployPumps(1) + ); + + // Well.sol doesn't use wellData, so it should always return empty bytes + wellData = new bytes(0); + } + + //////////// Well Definition //////////// + + function test_tokens() public { + assertEq(well.tokens(), tokens); + } + + function test_wellFunction() public { + assertEq(well.wellFunction(), wellFunction); + } + + function test_pumps() public { + assertEq(well.pumps(), pumps); + } + + function test_wellData() public { + assertEq(well.wellData(), wellData); + } + + function test_aquifer() public { + assertEq(well.aquifer(), address(aquifer)); + } + + function test_well() public { + ( + IERC20[] memory _wellTokens, + Call memory _wellFunction, + Call[] memory _wellPumps, + bytes memory _wellData, + address _aquifer + ) = well.well(); + + assertEq(_wellTokens, tokens); + assertEq(_wellFunction, wellFunction); + assertEq(_wellPumps, pumps); + assertEq(_wellData, wellData); + assertEq(_aquifer, address(aquifer)); + } + + function test_getReserves() public { + assertEq(well.getReserves(), getBalances(address(well), well).tokens); + } + + //////////// ERC20 LP Token //////////// + + function test_name() public { + assertEq(well.name(), "TOKEN0:TOKEN1 StableSwap Well"); + } + + function test_symbol() public { + assertEq(well.symbol(), "TOKEN0TOKEN1SS2w"); + } + + function test_decimals() public { + assertEq(well.decimals(), 18); + } + + //////////// Deployment //////////// + + /// @dev Fuzz different combinations of Well configuration data and check + /// that the Aquifer deploys everything correctly. + function testFuzz_bore( + uint numberOfPumps, + bytes[4] memory pumpData, + bytes memory wellFunctionBytes, + uint nTokens + ) public { + // Constraints + numberOfPumps = bound(numberOfPumps, 0, 4); + for (uint i = 0; i < numberOfPumps; i++) { + vm.assume(pumpData[i].length <= 4 * 32); + } + vm.assume(wellFunctionBytes.length <= 4 * 32); + nTokens = bound(nTokens, 2, tokens.length); + + // Get the first `nTokens` mock tokens + IERC20[] memory wellTokens = getTokens(nTokens); + + // Deploy a Well Function + wellFunction = Call(address(new StableSwap2()), wellFunctionBytes); + + // Etch the MockPump at each `target` + Call[] memory pumps = new Call[](numberOfPumps); + for (uint i = 0; i < numberOfPumps; i++) { + pumps[i].target = address(new MockPump()); + pumps[i].data = pumpData[i]; + } + + // Deploy the Well + Well _well = + encodeAndBoreWell(address(aquifer), wellImplementation, wellTokens, wellFunction, pumps, bytes32(0)); + + // Check Pumps + assertEq(_well.numberOfPumps(), numberOfPumps, "number of pumps mismatch"); + Call[] memory _pumps = _well.pumps(); + + if (numberOfPumps > 0) { + assertEq(_well.firstPump(), pumps[0], "pump mismatch"); + } + + for (uint i = 0; i < numberOfPumps; i++) { + assertEq(_pumps[i], pumps[i], "pump mismatch"); + } + + // Check token addresses + assertEq(_well.tokens(), wellTokens); + + // Check Well Function + assertEq(_well.wellFunction(), wellFunction); + assertEq(_well.wellFunctionAddress(), wellFunction.target); + + // Check that Aquifer recorded the deployment + assertEq(aquifer.wellImplementation(address(_well)), wellImplementation); + } +} diff --git a/test/functions/StableSwap.t.sol b/test/functions/StableSwap.t.sol index 88f084cb..40bb2d9b 100644 --- a/test/functions/StableSwap.t.sol +++ b/test/functions/StableSwap.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import {console, TestHelper} from "test/TestHelper.sol"; import {WellFunctionHelper} from "./WellFunctionHelper.sol"; -import {StableSwap} from "src/functions/StableSwap.sol"; +import {StableSwap2} from "src/functions/StableSwap2.sol"; /// @dev Tests the {StableSwap} Well function directly. contract StableSwapTest is WellFunctionHelper { @@ -43,13 +43,13 @@ contract StableSwapTest is WellFunctionHelper { //////////// SETUP //////////// function setUp() public { - _function = new StableSwap(10); - _data = ""; + _function = new StableSwap2(); + _data = abi.encode(StableSwap2.WellFunctionData(10,1,1)); } function test_metadata() public { assertEq(_function.name(), "StableSwap"); - assertEq(_function.symbol(), "SS"); + assertEq(_function.symbol(), "SS2"); } //////////// LP TOKEN SUPPLY //////////// @@ -145,11 +145,9 @@ contract StableSwapTest is WellFunctionHelper { uint[] memory reserves = new uint[](2); reserves[0] = bound(_reserves[0], 1e18, MAX_RESERVE); reserves[1] = bound(_reserves[1], 1e18, MAX_RESERVE); + uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); - console.log("lpTokenSupply: ", lpTokenSupply); - uint[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, ""); - console.log("underlying1: ", underlying[0]); - console.log("underlying0: ", underlying[1]); + uint[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, _data); for (uint i = 0; i < reserves.length; ++i) { assertEq(reserves[i], underlying[i], "reserves mismatch"); } @@ -157,9 +155,9 @@ contract StableSwapTest is WellFunctionHelper { //////////// FUZZ //////////// - function testFuzz_constantProduct2(uint x, uint y) public { + function testFuzz_stableSwap(uint x, uint y) public { uint[] memory reserves = new uint[](2); - bytes memory _data = new bytes(0); + bytes memory _data = abi.encode(StableSwap2.WellFunctionData(10,1,1)); reserves[0] = bound(x, 1e18, MAX_RESERVE); reserves[1] = bound(y, 1e18, MAX_RESERVE); @@ -170,19 +168,13 @@ contract StableSwapTest is WellFunctionHelper { if (reserves[0] < 1e12) { assertApproxEqAbs(reserve0, reserves[0], 1); - // assertApproxEqRel(reserve0, reserves[0], 3e6); - console.log("check1"); } else { assertApproxEqRel(reserve0, reserves[0], 3e6); - console.log("check2"); } if (reserves[1] < 1e12) { assertApproxEqAbs(reserve1, reserves[1], 1); - // assertApproxEqRel(reserve0, reserves[0], 3e6); - console.log("check3"); } else { assertApproxEqRel(reserve1, reserves[1], 3e6); - console.log("check4"); } } } From 72a8bb1c167dfc61475fdb3e39aca9e613c7851a Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sat, 24 Jun 2023 18:19:33 +0545 Subject: [PATCH 04/69] fix test helper --- test/TestHelper.sol | 61 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 58 insertions(+), 3 deletions(-) diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 73e800d3..e07ad331 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -13,6 +13,7 @@ import {Users} from "test/helpers/Users.sol"; import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; +import {StableSwap2} from "src/functions/StableSwap2.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; @@ -125,6 +126,51 @@ abstract contract TestHelper is Test, WellDeployer { user2 = _user[1]; } + function setUpStableSwapWell(uint256 a) internal { + setUpStableSwapWell(a, deployPumps(1), deployMockTokens(2)); + } + + function setUpStableSwapWell( + uint256 a, + Call[] memory _pumps, + IERC20[] memory _tokens + ) internal { + // encode wellFunction Data + bytes memory wellFunctionData = abi.encode( + a, + _tokens[0], + _tokens[1] + ); + Call memory _wellFunction = Call( + address(new StableSwap2()), + wellFunctionData + ); + tokens = _tokens; + wellFunction = _wellFunction; + for (uint i = 0; i < _pumps.length; i++) { + pumps.push(_pumps[i]); + } + + initUser(); + + wellImplementation = deployWellImplementation(); + aquifer = new Aquifer(); + well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); + + // Mint mock tokens to user + mintTokens(user, initialLiquidity); + mintTokens(user2, initialLiquidity); + approveMaxTokens(user, address(well)); + approveMaxTokens(user2, address(well)); + + // Mint mock tokens to TestHelper + mintTokens(address(this), initialLiquidity); + approveMaxTokens(address(this), address(well)); + + // Add initial liquidity from TestHelper + addLiquidityEqualAmount(address(this), initialLiquidity); + } + //////////// Test Tokens //////////// /// @dev deploy `n` mock ERC20 tokens and sort by address @@ -199,12 +245,12 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = new bytes(0); } - function deployWellFunction(address _target) internal returns (Call memory _wellFunction) { + function deployWellFunction(address _target) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = new bytes(0); } - function deployWellFunction(address _target, bytes memory _data) internal returns (Call memory _wellFunction) { + function deployWellFunction(address _target, bytes memory _data) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = _data; } @@ -339,10 +385,19 @@ abstract contract TestHelper is Test, WellDeployer { Call memory _wellFunction = IWell(_well).wellFunction(); assertLe( IERC20(_well).totalSupply(), - IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data) + IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), + 'totalSupply() is greater than calcLpTokenSupply()' ); } + function checkStableSwapInvariant(address _well) internal { + uint[] memory _reserves = IWell(_well).getReserves(); + Call memory _wellFunction = IWell(_well).wellFunction(); + assertApproxEqAbs( + IERC20(_well).totalSupply(), + IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), + 2); + } function getPrecisionForReserves(uint256[] memory reserves) internal pure returns (uint256 precision) { precision = type(uint256).max; for (uint256 i; i < reserves.length; ++i) { From 798dbf1c94eb2a58aa3d3c43606981f31893ac00 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sat, 24 Jun 2023 18:21:58 +0545 Subject: [PATCH 05/69] add IWellErrors --- test/Well.SwapFromStableSwap.t.sol | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/test/Well.SwapFromStableSwap.t.sol b/test/Well.SwapFromStableSwap.t.sol index e239d7e3..36dcfffe 100644 --- a/test/Well.SwapFromStableSwap.t.sol +++ b/test/Well.SwapFromStableSwap.t.sol @@ -6,6 +6,8 @@ import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol"; import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + contract WellSwapFromStableSwapTest is SwapHelper { function setUp() public { @@ -49,7 +51,7 @@ contract WellSwapFromStableSwapTest is SwapHelper { /// @dev Swaps should always revert if `fromToken` = `toToken`. function test_swapFrom_revertIf_sameToken() public prank(user) { - vm.expectRevert(IWell.InvalidTokens.selector); + vm.expectRevert(IWellErrors.InvalidTokens.selector); well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint).max); } @@ -57,14 +59,14 @@ contract WellSwapFromStableSwapTest is SwapHelper { function test_swapFrom_revertIf_minAmountOutTooHigh() public prank(user) { uint amountIn = 10 * 1e18; uint amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); - uint minAmountOut = amountOut + 1e18; // actual: 500 + uint minAmountOut = amountOut + 1e18; - vm.expectRevert(abi.encodeWithSelector(IWell.SlippageOut.selector, amountOut, minAmountOut)); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint).max); } function test_swapFrom_revertIf_expired() public { - vm.expectRevert(IWell.Expired.selector); + vm.expectRevert(IWellErrors.Expired.selector); well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); } From c9a751b9325960a72928bb253f2c3ed756f330ea Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 25 Jun 2023 20:31:40 +0545 Subject: [PATCH 06/69] POC --- src/functions/BeanstalkStableSwap.sol | 295 ++++++++++++++++++ src/interfaces/beanstalk/IBeanstalkA.sol | 16 + test/TestHelper.sol | 6 +- test/Well.Skim.t.sol | 2 +- ...lkStableSwap2.calcReserveAtRatioSwap.t.sol | 120 +++++++ .../Well.AddLiquidityStableSwap.t.sol | 175 +++++++++++ .../Well.BoreStableSwap.t.sol | 2 +- test/stableSwap/Well.ShiftStable.t.sol | 162 ++++++++++ test/stableSwap/Well.SkimStableSwap.t.sol | 56 ++++ .../Well.SwapFromStableSwap.t.sol | 2 +- test/stableSwap/Well.SwapToStableSwap.t.sol | 110 +++++++ 11 files changed, 940 insertions(+), 6 deletions(-) create mode 100644 src/functions/BeanstalkStableSwap.sol create mode 100644 src/interfaces/beanstalk/IBeanstalkA.sol create mode 100644 test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol create mode 100644 test/stableSwap/Well.AddLiquidityStableSwap.t.sol rename test/{ => stableSwap}/Well.BoreStableSwap.t.sol (99%) create mode 100644 test/stableSwap/Well.ShiftStable.t.sol create mode 100644 test/stableSwap/Well.SkimStableSwap.t.sol rename test/{ => stableSwap}/Well.SwapFromStableSwap.t.sol (99%) create mode 100644 test/stableSwap/Well.SwapToStableSwap.t.sol diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol new file mode 100644 index 00000000..e129b505 --- /dev/null +++ b/src/functions/BeanstalkStableSwap.sol @@ -0,0 +1,295 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; +import {SafeMath} from "oz/utils/math/SafeMath.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {IBeanstalkA} from "src/interfaces/beanstalk/IBeanstalkA.sol"; + +/** + * @author Publius, Brean + * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. + * developed by solidly. + * @dev features an variable A parameter, queried from a beanstalk A param + * + * Stableswap Wells with 2 tokens use the formula: + * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` + * + * Where: + * `A` is the Amplication parameter. + * `D` is the supply of LP tokens + * `b_i` is the reserve at index `i` + */ +contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { + using LibMath for uint256; + using SafeMath for uint256; + + // 2 token Pool. + uint256 constant N = 2; + + // A precision + uint256 constant A_PRECISION = 100; + + // Precision that all pools tokens will be converted to. + uint256 constant POOL_PRECISION_DECIMALS = 18; + + // Maximum A parameter. + uint256 constant MAX_A = 10000 * A_PRECISION; + + uint256 constant PRECISION = 1e18; + + // Beanstalk + address constant BEANSTALK = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5; + // address constant BEAN = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab; + + + // Errors + error InvalidAParameter(uint256); + error InvalidTokens(); + error InvalidTokenDecimals(uint256); + + /** + * This Well function requires 2 parameters from wellFunctionData: + * 1: tkn0 address + * 2: tkn1 address + * + * @dev The StableSwap curve assumes that both tokens use the same decimals (max 1e18). + * tkn0 and tkn1 is used to call decimals() on the tokens to scale to 1e18. + * For example, USDC and BEAN has 6 decimals (TKX_SCALAR = 1e12), + * while DAI has 18 decimals (TKX_SCALAR = 1). + */ + struct WellFunctionData { + address tkn0; + address tkn1; + } + + /** + * D invariant calculation in non-overflowing integer operations iteratively + * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + * + * Converging solution: + * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) + */ + function calcLpTokenSupply( + uint256[] memory reserves, + bytes calldata _wellFunctionData + ) public view override returns (uint256 lpTokenSupply) { + ( + , + uint256 Ann, + uint256[2] memory precisionMultipliers + ) = verifyWellFunctionData(_wellFunctionData); + + + uint256 sumReserves = reserves[0] * precisionMultipliers[0] + reserves[1] * precisionMultipliers[1]; + if(sumReserves == 0) return 0; + lpTokenSupply = sumReserves; + + // wtf is this bullshit + for(uint256 i = 0; i < 255; i++){ + uint256 dP = lpTokenSupply; + // If division by 0, this will be borked: only withdrawal will work. And that is good + dP = dP.mul(lpTokenSupply).div(reserves[0].mul(precisionMultipliers[0]).mul(N)); + dP = dP.mul(lpTokenSupply).div(reserves[1].mul(precisionMultipliers[1]).mul(N)); + uint256 prevReserves = lpTokenSupply; + lpTokenSupply = Ann + .mul(sumReserves) + .div(A_PRECISION) + .add(dP.mul(N)) + .mul(lpTokenSupply) + .div( + Ann + .sub(A_PRECISION) + .mul(lpTokenSupply) + .div(A_PRECISION) + .add(N.add(1).mul(dP)) + ); + // Equality with the precision of 1 + if (lpTokenSupply > prevReserves){ + if(lpTokenSupply - prevReserves <= 1) return lpTokenSupply; + } + else { + if(prevReserves - lpTokenSupply <= 1) return lpTokenSupply; + } + } + } + + function getPrice( + uint256[] calldata reserves, + uint256 j, + uint256 lpTokenSupply, + bytes calldata _wellFunctionData + ) external view returns (uint256) { + (, uint256 Ann, uint256[2] memory precisionMultipliers) = verifyWellFunctionData(_wellFunctionData); + uint256 c = lpTokenSupply; + uint256 i = j == 1 ? 0 : 1; + c = c.mul(lpTokenSupply).div(reserves[i].mul(precisionMultipliers[i]).mul(N)); + c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + + uint256 b = reserves[i].mul( + precisionMultipliers[i] + ).add(lpTokenSupply.mul(A_PRECISION).div(Ann)); + uint256 yPrev; + uint256 y = lpTokenSupply; + for (uint256 k = 0; k < 256; i++) { + yPrev = y; + y = y.mul(y).add(c).div(y.mul(2).add(b).sub(lpTokenSupply)); + if(y > yPrev){ + if(y - yPrev <= 1) return y.div(precisionMultipliers[j]); + } else { + if(yPrev - y <= 1) return y.div(precisionMultipliers[j]); + } + } + revert("Approximation did not converge"); + } + + + /** + * @notice Calculate x[i] if one reduces D from being calculated for xp to D + * Done by solving quadratic equation iteratively. + * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + * x_1**2 + b*x_1 = c + * x_1 = (x_1**2 + c) / (2*x_1 + b) + */ + function calcReserve( + uint256[] calldata reserves, + uint256 j, + uint256 lpTokenSupply, + bytes calldata _wellFunctionData + ) public view override returns (uint256 reserve) { + ( + , + uint256 Ann, + uint256[2] memory precisionMultipliers + ) = verifyWellFunctionData(_wellFunctionData); + require(j < N); + uint256 c = lpTokenSupply; + uint256 sumReserves; + uint256 prevReserve; + sumReserves = j == 0 ? reserves[1].mul(precisionMultipliers[1]) : reserves[0].mul(precisionMultipliers[0]); + c = c.mul(lpTokenSupply).div(sumReserves.mul(N)); + c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + uint256 b = + sumReserves.add( + lpTokenSupply.mul(A_PRECISION).div(Ann) + ); + reserve = lpTokenSupply; + + for(uint256 i; i < 255; ++i){ + prevReserve = reserve; + reserve = + reserve + .mul(reserve) + .add(c) + .div( + reserve + .mul(2) + .add(b) + .sub(lpTokenSupply) + ); + // Equality with the precision of 1 + // safeMath not needed due to conditional. + if(reserve > prevReserve){ + if(reserve - prevReserve <= 1) return reserve.div(precisionMultipliers[j]); + } else { + if(prevReserve - reserve <= 1) return reserve.div(precisionMultipliers[j]); + } + } + + revert("did not find convergence"); + } + + + /** + * @notice Defines a proportional relationship between the supply of LP tokens + * and the amount of each underlying token for a two-token Well. + * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user + * recieves `s * b_i / S` of each underlying token. + * reserves are scaled as needed based on tknXScalar + */ + function calcLPTokenUnderlying( + uint256 lpTokenAmount, + uint256[] calldata reserves, + uint256 lpTokenSupply, + bytes calldata _wellFunctionData + ) external view returns (uint256[] memory underlyingAmounts) { + ( , , uint256[2] memory precisionMultipliers) = verifyWellFunctionData(_wellFunctionData); + underlyingAmounts = new uint256[](2); + // overflow cannot occur as lpTokenAmount could not be calculated. + underlyingAmounts[0] = lpTokenAmount * reserves[0].mul(precisionMultipliers[0]) / lpTokenSupply; + underlyingAmounts[1] = lpTokenAmount * reserves[1].mul(precisionMultipliers[1]) / lpTokenSupply; + } + + function name() external pure override returns (string memory) { + return "StableSwap"; + } + + function symbol() external pure override returns (string memory) { + return "SS2"; + } + + function verifyWellFunctionData( + bytes memory data + ) public view returns ( + uint256 a, + uint256 Ann, + uint256[2] memory precisionMultipliers + ){ + WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); + a = getBeanstalkA(); + if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); + if(IERC20(wfd.tkn0).decimals() > 18) revert InvalidTokenDecimals(IERC20(wfd.tkn0).decimals()); + Ann = a * N * N * A_PRECISION; + precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); + precisionMultipliers[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); + } + + + /** + * TODO: for deltaB minting + * `ratios` here refer to the virtual price of the non-bean asset. + * (precision 1e18). + */ + function calcReserveAtRatioSwap( + uint256[] calldata reserves, + uint256, + uint256[] calldata ratios, + bytes calldata data + ) external view returns (uint256 reserve){ + uint256[] memory _reserves = new uint256[](2); + _reserves[0] = reserves[0].mul(ratios[0]).div(PRECISION); + _reserves[1] = reserves[1].mul(ratios[1]).div(PRECISION); + uint256 D = calcLpTokenSupply(_reserves, data); + return D / 2; + } + + // TODO: for converts + // `ratios` here refer to the virtual price of the non-bean asset + // high level: calc the reserve via adding or removing liq given some reserves, to target ratio. + // + function calcReserveAtRatioLiquidity( + uint256[] calldata reserves, + uint256 j, + uint256[] calldata ratios, + bytes calldata + ) external pure returns (uint256 reserve){ + uint256 i = j == 1 ? 0 : 1; + reserve = reserves[i] * ratios[j] / ratios[i]; + } + + function getBeanstalkA() public pure returns (uint256 a) { + // return IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5).getBeanstalkA(); + return 10; + } + function getBeanstalkAnn() public pure returns (uint256 a) { + return getBeanstalkA() * N * N; + } + + // TODO: implement. + function getVirtualPrice() public pure returns (uint256 price) { + price = 1.01 * 1e18; + } + +} diff --git a/src/interfaces/beanstalk/IBeanstalkA.sol b/src/interfaces/beanstalk/IBeanstalkA.sol new file mode 100644 index 00000000..659bea19 --- /dev/null +++ b/src/interfaces/beanstalk/IBeanstalkA.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +/** + * @title IBeanstalkA + * @author Brean + * @notice Interface for the Beanstalk A, the A parameter that beanstalk sets. + */ +interface IBeanstalkA { + + /** + * @return a A parameter, precision of 2 (a of 1 == 100) + */ + function getBeanstalkA() external returns (uint256 a); +} diff --git a/test/TestHelper.sol b/test/TestHelper.sol index e07ad331..ec3271ea 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -126,11 +126,11 @@ abstract contract TestHelper is Test, WellDeployer { user2 = _user[1]; } - function setUpStableSwapWell(uint256 a) internal { - setUpStableSwapWell(a, deployPumps(1), deployMockTokens(2)); + function setupStableSwapWell(uint256 a) internal { + setupStableSwapWell(a, deployPumps(1), deployMockTokens(2)); } - function setUpStableSwapWell( + function setupStableSwapWell( uint256 a, Call[] memory _pumps, IERC20[] memory _tokens diff --git a/test/Well.Skim.t.sol b/test/Well.Skim.t.sol index 990a98cb..9e21a1cf 100644 --- a/test/Well.Skim.t.sol +++ b/test/Well.Skim.t.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.17; import {TestHelper, Balances} from "test/TestHelper.sol"; -contract WellSkimTest is TestHelper { +contract WellSkimStableSwapTest is TestHelper { function setUp() public { setupWell(2); } diff --git a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol new file mode 100644 index 00000000..367f6732 --- /dev/null +++ b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol @@ -0,0 +1,120 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + + +/// @dev Tests the {ConstantProduct2} Well function directly. +contract BeanstalkStableSwapSwapTest is TestHelper { + IBeanstalkWellFunction _f; + bytes data; + + //////////// SETUP //////////// + + function setUp() public { + _f = new BeanstalkStableSwap(); + IERC20[] memory _token = deployMockTokens(2); + data = abi.encode( + address(_token[0]), + address(_token[1]) + ); + } + + function test_calcReserveAtRatioSwap_equal_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 100); + assertEq(reserve1, 100); + } + + function test_calcReserveAtRatioSwap_equal_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 74); + assertEq(reserve1, 74); + } + + function test_calcReserveAtRatioSwap_diff_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 2; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 141); + assertEq(reserve1, 70); + } + + function test_calcReserveAtRatioSwap_diff_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 12_984_712_098_520; + ratios[1] = 12_984_712_098; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 2236); + assertEq(reserve1, 2); + } + + function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { + // Upper bound is limited by stableSwap, + // due to the stableswap reserves being extremely far apart. + reserves[0] = bound(reserves[0], 1e18, 6e31); + reserves[1] = bound(reserves[1], 1e18, 6e31); + ratios[0] = 1e18; + ratios[1] = bound(ratios[1], 1e18, 2e18); + + + uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); + console.log("lpTokenSupply:", lpTokenSupply); + + uint256[] memory reservesOut = new uint256[](2); + for (uint256 i; i < 2; ++i) { + reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); + } + console.log("reservesOut 0:", reservesOut[0]); + console.log("reservesOut 1:", reservesOut[1]); + + + // Get LP token supply with bound reserves. + uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); + console.log("lpTokenSupplyOut:", lpTokenSupplyOut); + // Precision is set to the minimum number of digits of the reserves out. + uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) + ? numDigits(reservesOut[1]) + : numDigits(reservesOut[0]); + + // Check LP Token Supply after = lp token supply before. + // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); + assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01 *1e18); + + // Check ratio of `reservesOut` = ratio of `ratios`. + // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); + } +} diff --git a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol new file mode 100644 index 00000000..a27d4221 --- /dev/null +++ b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol @@ -0,0 +1,175 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {TestHelper, IERC20, Call, Balances} from "test/TestHelper.sol"; +import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; +import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; + +contract WellAddLiquidityStableSwapTest is LiquidityHelper { + function setUp() public { + setupStableSwapWell(2); + } + + /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent + /// tests will run correctly. + function test_liquidityInitialized() public { + IERC20[] memory tokens = well.tokens(); + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + for (uint256 i; i < tokens.length; i++) { + assertEq(userBalance.tokens[i], initialLiquidity, "incorrect user token reserve"); + assertEq(wellBalance.tokens[i], initialLiquidity, "incorrect well token reserve"); + } + } + + /// @dev Adding liquidity in equal proportions should summate and be scaled + /// up by sqrt(ConstantProduct2.EXP_PRECISION) + function test_getAddLiquidityOut_equalAmounts() public { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = well.getAddLiquidityOut(amounts); + assertEq(lpAmountOut, 2000 * 1e18, "Incorrect AmountOut"); + } + + function test_getAddLiquidityOut_oneToken() public { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10 * 1e18; + amounts[1] = 0; + + uint256 amountOut = well.getAddLiquidityOut(amounts); + assertEq(amountOut, 9_995_024_785_725_378_226, "incorrect amt out"); + } + + function test_addLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = well.getAddLiquidityOut(amounts); + + vm.expectRevert(abi.encodeWithSelector(SlippageOut.selector, lpAmountOut, lpAmountOut + 1)); + well.addLiquidity(amounts, lpAmountOut + 1, user, type(uint256).max); // lpAmountOut is 2000*1e27 + } + + function test_addLiquidity_revertIf_expired() public { + vm.expectRevert(Expired.selector); + well.addLiquidity(new uint256[](tokens.length), 0, user, block.timestamp - 1); + } + + function test_addLiquidity_balanced() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = 2000 * 1e18; + + vm.expectEmit(true, true, true, true); + emit AddLiquidity(amounts, lpAmountOut, user); + well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); + + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + + assertEq(userBalance.lp, lpAmountOut); + + // Consumes all of user's tokens + assertEq(userBalance.tokens[0], 0, "incorrect token0 user amt"); + assertEq(userBalance.tokens[1], 0, "incorrect token1 user amt"); + + // Adds to the Well's reserves + assertEq(wellBalance.tokens[0], initialLiquidity + amounts[0], "incorrect token0 well amt"); + assertEq(wellBalance.tokens[1], initialLiquidity + amounts[1], "incorrect token1 well amt"); + checkInvariant(address(well)); + } + + function test_addLiquidity_oneSided() public prank(user) { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10 * 1e18; + amounts[1] = 0; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Adding and removing liquidity in sequence should return the Well to its previous state + function test_addAndRemoveLiquidity() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = 2000 * 1e18; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); + afterAddLiquidity(before, action); + + Snapshot memory beforeRemove; + RemoveLiquidityAction memory actionRemove; + actionRemove.lpAmountIn = well.getAddLiquidityOut(amounts); + actionRemove.amounts = amounts; + actionRemove.recipient = user; + + (beforeRemove, actionRemove) = beforeRemoveLiquidity(actionRemove); + well.removeLiquidity(lpAmountOut, amounts, user, type(uint256).max); + afterRemoveLiquidity(beforeRemove, actionRemove); + checkInvariant(address(well)); + } + + /// @dev Adding zero liquidity emits empty event but doesn't change reserves + function test_addLiquidity_zeroChange() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + uint256 liquidity = 0; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = liquidity; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, liquidity, user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Two-token fuzz test adding liquidity in any ratio + function testFuzz_addLiquidity(uint256 x, uint256 y) public prank(user) { + // amounts to add as liquidity + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(x, 0, type(uint104).max); + amounts[1] = bound(y, 0, type(uint104).max); + mintTokens(user, amounts); + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } +} diff --git a/test/Well.BoreStableSwap.t.sol b/test/stableSwap/Well.BoreStableSwap.t.sol similarity index 99% rename from test/Well.BoreStableSwap.t.sol rename to test/stableSwap/Well.BoreStableSwap.t.sol index 7dc9776f..daa017a7 100644 --- a/test/Well.BoreStableSwap.t.sol +++ b/test/stableSwap/Well.BoreStableSwap.t.sol @@ -9,7 +9,7 @@ contract WellBoreStableSwapTest is TestHelper { /// @dev Bore a 2-token Well with StableSwap2 & several pumps. function setUp() public { // setup a StableSwap Well with an A parameter of 10. - setUpStableSwapWell(10); + setupStableSwapWell(10); // Well.sol doesn't use wellData, so it should always return empty bytes wellData = new bytes(0); } diff --git a/test/stableSwap/Well.ShiftStable.t.sol b/test/stableSwap/Well.ShiftStable.t.sol new file mode 100644 index 00000000..ea0d5e76 --- /dev/null +++ b/test/stableSwap/Well.ShiftStable.t.sol @@ -0,0 +1,162 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Balances, ConstantProduct2, IERC20, StableSwap2} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellShiftStableTest is TestHelper { + event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); + + function setUp() public { + setupStableSwapWell(10); + } + + /// @dev Shift excess token0 into token1. + function testFuzz_shift(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that `_user` has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + well.sync(); + uint256 minAmountOut = well.getShiftOut(tokens[1]); + uint256[] memory calcReservesAfter = new uint256[](2); + calcReservesAfter[0] = well.getReserves()[0]; + calcReservesAfter[1] = well.getReserves()[1] - minAmountOut; + + vm.expectEmit(true, true, true, true); + emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); + uint256 amtOut = well.shift(tokens[1], minAmountOut, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained token1 + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); + assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); + + // Reserves should now match balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + + // The difference has been sent to _user. + assertEq( + userBalanceAfterShift.tokens[1], + wellBalanceBeforeShift.tokens[1] - wellBalanceAfterShift.tokens[1], + "User should have correct token1 balance" + ); + assertEq( + userBalanceAfterShift.tokens[1], + userBalanceBeforeShift.tokens[1] + amtOut, + "User should have correct token1 balance" + ); + checkStableSwapInvariant(address(well)); + } + + /// @dev Shift excess token0 into token0 (just transfers the excess token0 to the user). + function testFuzz_shift_tokenOut(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that the user has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + well.sync(); + uint256 minAmountOut = well.getShiftOut(tokens[0]); + uint256[] memory calcReservesAfter = new uint256[](2); + calcReservesAfter[0] = well.getReserves()[0] - minAmountOut; + calcReservesAfter[1] = well.getReserves()[1]; + + vm.expectEmit(true, true, true, true); + emit Shift(calcReservesAfter, tokens[0], minAmountOut, _user); + // Shift the imbalanced token as the token out + well.shift(tokens[0], 0, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained token0 + assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); + assertEq( + userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" + ); + + // Reserves should now match balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + + assertEq( + userBalanceAfterShift.tokens[0], + userBalanceBeforeShift.tokens[0] + amount, + "User should have gained token 1" + ); + checkInvariant(address(well)); + } + + /// @dev Calling shift() on a balanced Well should do nothing. + function test_shift_balanced_pool() public prank(user) { + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that the user has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + well.shift(tokens[1], 0, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained neither token + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); + + // Reserves should equal balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + checkInvariant(address(well)); + } + + function test_shift_fail_slippage(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + + uint256 amountOut = well.getShiftOut(tokens[1]); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); + well.shift(tokens[1], type(uint256).max, user); + } +} diff --git a/test/stableSwap/Well.SkimStableSwap.t.sol b/test/stableSwap/Well.SkimStableSwap.t.sol new file mode 100644 index 00000000..8b4962f1 --- /dev/null +++ b/test/stableSwap/Well.SkimStableSwap.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Balances} from "test/TestHelper.sol"; + +contract WellSkimTest is TestHelper { + function setUp() public { + setupStableSwapWell(2); + } + + function test_initialized() public { + // Well should have liquidity + Balances memory wellBalance = getBalances(address(well), well); + assertEq(wellBalance.tokens[0], 1000e18); + assertEq(wellBalance.tokens[1], 1000e18); + } + + function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { + vm.assume(amounts[0] <= 800e18); + vm.assume(amounts[1] <= 800e18); + + // Transfer from Test contract to Well + tokens[0].transfer(address(well), amounts[0]); + tokens[1].transfer(address(well), amounts[1]); + + Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); + // Verify that the Well has received the tokens + assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); + assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + uint256[] memory reserves = new uint256[](2); + + // Verify that the user has no tokens + Balances memory userBalanceBeforeSkim = getBalances(_user, well); + reserves[0] = userBalanceBeforeSkim.tokens[0]; + reserves[1] = userBalanceBeforeSkim.tokens[1]; + assertEq(reserves[0], 0); + assertEq(reserves[1], 0); + + well.skim(_user); + + Balances memory userBalanceAfterSkim = getBalances(_user, well); + Balances memory wellBalanceAfterSkim = getBalances(address(well), well); + + // Since only 1000e18 of each token was added as liquidity, the Well's reserve + // should be reset back to this. + assertEq(wellBalanceAfterSkim.tokens[0], 1000e18); + assertEq(wellBalanceAfterSkim.tokens[1], 1000e18); + + // The difference has been sent to _user. + assertEq(userBalanceAfterSkim.tokens[0], amounts[0]); + assertEq(userBalanceAfterSkim.tokens[1], amounts[1]); + } +} diff --git a/test/Well.SwapFromStableSwap.t.sol b/test/stableSwap/Well.SwapFromStableSwap.t.sol similarity index 99% rename from test/Well.SwapFromStableSwap.t.sol rename to test/stableSwap/Well.SwapFromStableSwap.t.sol index 36dcfffe..d04687b1 100644 --- a/test/Well.SwapFromStableSwap.t.sol +++ b/test/stableSwap/Well.SwapFromStableSwap.t.sol @@ -11,7 +11,7 @@ import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellSwapFromStableSwapTest is SwapHelper { function setUp() public { - setUpStableSwapWell(10); + setupStableSwapWell(10); } function test_getSwapOut() public { diff --git a/test/stableSwap/Well.SwapToStableSwap.t.sol b/test/stableSwap/Well.SwapToStableSwap.t.sol new file mode 100644 index 00000000..5b9c2ebc --- /dev/null +++ b/test/stableSwap/Well.SwapToStableSwap.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {IERC20, Balances, Call, MockToken, Well} from "test/TestHelper.sol"; +import {SwapHelper, SwapAction} from "test/SwapHelper.sol"; +import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; +import {IWellFunction} from "src/interfaces/IWellFunction.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellSwapToStableSwapTest is SwapHelper { + function setUp() public { + setupStableSwapWell(2); + } + + function test_getSwapIn() public { + uint256 amountOut = 100 * 1e18; + uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); + assertEq(amountIn, 102_054_486_564_986_716_193); // ~2% slippage + } + + function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { + IERC20[] memory _tokens = well.tokens(); + Balances memory wellBalances = getBalances(address(well), well); + vm.assume(i < _tokens.length); + + // Swap token `i` -> all other tokens + for (uint256 j; j < _tokens.length; ++j) { + if (j != i) { + // Request to buy more of {_tokens[j]} than the Well has. + // There is no input amount that could complete this Swap. + uint256 amountOut = wellBalances.tokens[j] + 1; + vm.expectRevert(); // underflow + well.getSwapIn(_tokens[i], _tokens[j], amountOut); + } + } + } + + /// @dev Swaps should always revert if `fromToken` = `toToken`. + function test_swapTo_revertIf_sameToken() public prank(user) { + vm.expectRevert(IWellErrors.InvalidTokens.selector); + well.swapTo(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); + } + + /// @dev Slippage revert occurs if maxAmountIn is too low + function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { + uint256 amountOut = 100 * 1e18; + uint256 amountIn = 102_054_486_564_986_716_193; + uint256 maxAmountIn = amountIn * 99 / 100; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + } + + /// @dev Note: this covers the case where there is a fee as well + function test_swapFromFeeOnTransferNoFee_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.swapTo(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); + } + + /// @dev tests assume 2 tokens in future we can extend for multiple tokens + function testFuzz_swapTo(uint256 amountOut) public prank(user) { + // User has 1000 of each token + // Given current liquidity, swapping 1000 of one token gives 500 of the other + uint256 maxAmountIn = 1000 * 1e18; + amountOut = bound(amountOut, 0, 500 * 1e18); + + Balances memory userBalancesBefore = getBalances(user, well); + Balances memory wellBalancesBefore = getBalances(address(well), well); + + // Decrease reserve of token 1 by `amountOut` which is paid to user + uint256[] memory calcBalances = new uint256[](wellBalancesBefore.tokens.length); + calcBalances[0] = wellBalancesBefore.tokens[0]; + calcBalances[1] = wellBalancesBefore.tokens[1] - amountOut; + + uint256 calcAmountIn = IWellFunction(wellFunction.target).calcReserve( + calcBalances, + 0, // j + wellBalancesBefore.lpSupply, + wellFunction.data + ) - wellBalancesBefore.tokens[0]; + + vm.expectEmit(true, true, true, true); + emit Swap(tokens[0], tokens[1], calcAmountIn, amountOut, user); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + + Balances memory userBalancesAfter = getBalances(user, well); + Balances memory wellBalancesAfter = getBalances(address(well), well); + + assertEq( + userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], calcAmountIn, "Incorrect token0 user balance" + ); + assertEq(userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], amountOut, "Incorrect token1 user balance"); + assertEq( + wellBalancesAfter.tokens[0], wellBalancesBefore.tokens[0] + calcAmountIn, "Incorrect token0 well reserve" + ); + assertEq(wellBalancesAfter.tokens[1], wellBalancesBefore.tokens[1] - amountOut, "Incorrect token1 well reserve"); + checkStableSwapInvariant(address(well)); + } + + /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result + function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { + // assume amtOut is lower due to slippage + vm.assume(token0AmtOut < 500e18); + uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); + uint256 token0In = well.swapTo(tokens[1], tokens[0], 1000e18, token1In, user, type(uint256).max); + assertEq(token0In, token0AmtOut); + checkInvariant(address(well)); + } +} From 3868ea4e49fb9ecc59b06835238b58655b90f7d2 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 10:59:23 +0545 Subject: [PATCH 07/69] add liquidity tests, revise well function --- src/functions/BeanstalkStableSwap.sol | 65 +++-- src/interfaces/beanstalk/IBeanstalkA.sol | 2 +- ...lkStableSwap2.calcReserveAtRatioSwap.t.sol | 240 +++++++++--------- ....RemoveLiquidityImbalancedStableSwap.t.sol | 192 ++++++++++++++ ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 126 +++++++++ .../Well.RemoveLiquidityStableSwap.t.sol | 142 +++++++++++ 6 files changed, 622 insertions(+), 145 deletions(-) create mode 100644 test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol create mode 100644 test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol create mode 100644 test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol index e129b505..cec55290 100644 --- a/src/functions/BeanstalkStableSwap.sol +++ b/src/functions/BeanstalkStableSwap.sol @@ -22,7 +22,7 @@ import {IBeanstalkA} from "src/interfaces/beanstalk/IBeanstalkA.sol"; * `D` is the supply of LP tokens * `b_i` is the reserve at index `i` */ -contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { +contract BeanstalkStableSwap is IBeanstalkWellFunction { using LibMath for uint256; using SafeMath for uint256; @@ -41,8 +41,7 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { uint256 constant PRECISION = 1e18; // Beanstalk - address constant BEANSTALK = 0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5; - // address constant BEAN = 0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab; + IBeanstalkA BEANSTALK = IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5); // Errors @@ -51,7 +50,8 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { error InvalidTokenDecimals(uint256); /** - * This Well function requires 2 parameters from wellFunctionData: + * This Well function requires 3 parameters from wellFunctionData: + * 0: Failsafe A parameter * 1: tkn0 address * 2: tkn1 address * @@ -59,8 +59,11 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { * tkn0 and tkn1 is used to call decimals() on the tokens to scale to 1e18. * For example, USDC and BEAN has 6 decimals (TKX_SCALAR = 1e12), * while DAI has 18 decimals (TKX_SCALAR = 1). + * + * The failsafe A parameter is used when the beanstalk A parameter is not available. */ struct WellFunctionData { + uint256 a; address tkn0; address tkn1; } @@ -230,21 +233,7 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { return "SS2"; } - function verifyWellFunctionData( - bytes memory data - ) public view returns ( - uint256 a, - uint256 Ann, - uint256[2] memory precisionMultipliers - ){ - WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - a = getBeanstalkA(); - if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); - if(IERC20(wfd.tkn0).decimals() > 18) revert InvalidTokenDecimals(IERC20(wfd.tkn0).decimals()); - Ann = a * N * N * A_PRECISION; - precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); - precisionMultipliers[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); - } + /** @@ -254,15 +243,16 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { */ function calcReserveAtRatioSwap( uint256[] calldata reserves, - uint256, + uint256 j, uint256[] calldata ratios, bytes calldata data ) external view returns (uint256 reserve){ uint256[] memory _reserves = new uint256[](2); _reserves[0] = reserves[0].mul(ratios[0]).div(PRECISION); _reserves[1] = reserves[1].mul(ratios[1]).div(PRECISION); - uint256 D = calcLpTokenSupply(_reserves, data); - return D / 2; + // uint256 oldD = calcLpTokenSupply(reserves, data) / 2; + uint256 newD = calcLpTokenSupply(_reserves, data); + return newD / 2; } // TODO: for converts @@ -280,8 +270,7 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { } function getBeanstalkA() public pure returns (uint256 a) { - // return IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5).getBeanstalkA(); - return 10; + return IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5).getBeanstalkA(); } function getBeanstalkAnn() public pure returns (uint256 a) { return getBeanstalkA() * N * N; @@ -292,4 +281,32 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction, IBeanstalkA { price = 1.01 * 1e18; } + function verifyWellFunctionData( + bytes memory data + ) public view returns ( + uint256 a, + uint256 Ann, + uint256[2] memory precisionMultipliers + ){ + WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); + + // try to get the beanstalk A. + // if it fails, use the failsafe A stored in well function data. + try BEANSTALK.getBeanstalkA() returns (uint256 _a) { + a = _a; + } catch { + a = wfd.a; + } + if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); + uint8 token0Dec = IERC20(wfd.tkn0).decimals(); + uint8 token1Dec = IERC20(wfd.tkn0).decimals(); + + if(token0Dec > 18) revert InvalidTokenDecimals(token0Dec); + if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); + + Ann = a * N * N * A_PRECISION; + precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); + precisionMultipliers[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); + } + } diff --git a/src/interfaces/beanstalk/IBeanstalkA.sol b/src/interfaces/beanstalk/IBeanstalkA.sol index 659bea19..a94c3c9e 100644 --- a/src/interfaces/beanstalk/IBeanstalkA.sol +++ b/src/interfaces/beanstalk/IBeanstalkA.sol @@ -12,5 +12,5 @@ interface IBeanstalkA { /** * @return a A parameter, precision of 2 (a of 1 == 100) */ - function getBeanstalkA() external returns (uint256 a); + function getBeanstalkA() external pure returns (uint256 a); } diff --git a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol index 367f6732..498c9eb8 100644 --- a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol @@ -1,120 +1,120 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; -import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; - - -/// @dev Tests the {ConstantProduct2} Well function directly. -contract BeanstalkStableSwapSwapTest is TestHelper { - IBeanstalkWellFunction _f; - bytes data; - - //////////// SETUP //////////// - - function setUp() public { - _f = new BeanstalkStableSwap(); - IERC20[] memory _token = deployMockTokens(2); - data = abi.encode( - address(_token[0]), - address(_token[1]) - ); - } - - function test_calcReserveAtRatioSwap_equal_equal() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 1; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 100); - assertEq(reserve1, 100); - } - - function test_calcReserveAtRatioSwap_equal_diff() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 1; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 74); - assertEq(reserve1, 74); - } - - function test_calcReserveAtRatioSwap_diff_equal() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 2; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 141); - assertEq(reserve1, 70); - } - - function test_calcReserveAtRatioSwap_diff_diff() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 12_984_712_098_520; - ratios[1] = 12_984_712_098; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 2236); - assertEq(reserve1, 2); - } - - function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { - // Upper bound is limited by stableSwap, - // due to the stableswap reserves being extremely far apart. - reserves[0] = bound(reserves[0], 1e18, 6e31); - reserves[1] = bound(reserves[1], 1e18, 6e31); - ratios[0] = 1e18; - ratios[1] = bound(ratios[1], 1e18, 2e18); - - - uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); - console.log("lpTokenSupply:", lpTokenSupply); - - uint256[] memory reservesOut = new uint256[](2); - for (uint256 i; i < 2; ++i) { - reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); - } - console.log("reservesOut 0:", reservesOut[0]); - console.log("reservesOut 1:", reservesOut[1]); - - - // Get LP token supply with bound reserves. - uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); - console.log("lpTokenSupplyOut:", lpTokenSupplyOut); - // Precision is set to the minimum number of digits of the reserves out. - uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) - ? numDigits(reservesOut[1]) - : numDigits(reservesOut[0]); - - // Check LP Token Supply after = lp token supply before. - // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); - assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01 *1e18); - - // Check ratio of `reservesOut` = ratio of `ratios`. - // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); - } -} +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.17; + +// import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +// import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; +// import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + + +// /// @dev Tests the {ConstantProduct2} Well function directly. +// contract BeanstalkStableSwapSwapTest is TestHelper { +// IBeanstalkWellFunction _f; +// bytes data; + +// //////////// SETUP //////////// + +// function setUp() public { +// _f = new BeanstalkStableSwap(); +// IERC20[] memory _token = deployMockTokens(2); +// data = abi.encode( +// address(_token[0]), +// address(_token[1]) +// ); +// } + +// function test_calcReserveAtRatioSwap_equal_equal() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 100; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 1; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 100); +// assertEq(reserve1, 100); +// } + +// function test_calcReserveAtRatioSwap_equal_diff() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 50; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 1; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 74); +// assertEq(reserve1, 74); +// } + +// function test_calcReserveAtRatioSwap_diff_equal() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 100; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 2; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 141); +// assertEq(reserve1, 70); +// } + +// function test_calcReserveAtRatioSwap_diff_diff() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 50; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 12_984_712_098_520; +// ratios[1] = 12_984_712_098; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 2236); +// assertEq(reserve1, 2); +// } + +// function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { +// // Upper bound is limited by stableSwap, +// // due to the stableswap reserves being extremely far apart. +// reserves[0] = bound(reserves[0], 1e18, 1e31); +// reserves[1] = bound(reserves[1], 1e18, 1e31); +// ratios[0] = 1e18; +// ratios[1] = 2e18; + + +// uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); +// console.log("lpTokenSupply:", lpTokenSupply); + +// uint256[] memory reservesOut = new uint256[](2); +// for (uint256 i; i < 2; ++i) { +// reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); +// } +// console.log("reservesOut 0:", reservesOut[0]); +// console.log("reservesOut 1:", reservesOut[1]); + + +// // Get LP token supply with bound reserves. +// uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); +// console.log("lpTokenSupplyOut:", lpTokenSupplyOut); +// // Precision is set to the minimum number of digits of the reserves out. +// uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) +// ? numDigits(reservesOut[1]) +// : numDigits(reservesOut[0]); + +// // Check LP Token Supply after = lp token supply before. +// // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); +// assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); + +// // Check ratio of `reservesOut` = ratio of `ratios`. +// // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); +// } +// } diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol new file mode 100644 index 00000000..d58f8e08 --- /dev/null +++ b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -0,0 +1,192 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, ConstantProduct2, Balances} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityImbalancedTest is TestHelper { + event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); + + uint256[] tokenAmountsOut; + uint256 requiredLpAmountIn; + + // Setup + ConstantProduct2 cp; + uint256 constant addedLiquidity = 1000 * 1e18; + + function setUp() public { + cp = new ConstantProduct2(); + setupWell(2); + + // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + + // Shared removal amounts + tokenAmountsOut.push(500 * 1e18); // 500 token0 + tokenAmountsOut.push(506 * 1e17); // 50.6 token1 + requiredLpAmountIn = 290 * 1e24; // LP needed to remove `tokenAmountsOut` + } + + /// @dev Assumes use of ConstantProduct2 + function test_getRemoveLiquidityImbalancedIn() public { + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); + assertEq(lpAmountIn, requiredLpAmountIn); + } + + /// @dev not enough LP to receive `tokenAmountsOut` + function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { + uint256 maxLpAmountIn = 5 * 1e24; + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + } + + function test_removeLiquidityImbalanced_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidityImbalanced(0, new uint256[](2), user, block.timestamp - 1); + } + + /// @dev Base case + function test_removeLiquidityImbalanced() public prank(user) { + Balances memory userBalanceBefore = getBalances(user, well); + + uint256 initialLpAmount = userBalanceBefore.lp; + uint256 maxLpAmountIn = requiredLpAmountIn; + + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(maxLpAmountIn, tokenAmountsOut, user); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + + Balances memory userBalanceAfter = getBalances(user, well); + Balances memory wellBalanceAfter = getBalances(address(well), well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfter.lp, initialLpAmount - maxLpAmountIn); + + // `user` balance of underlying tokens increases + // assumes initial balance of zero + assertEq(userBalanceAfter.tokens[0], tokenAmountsOut[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], tokenAmountsOut[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + assertEq(wellBalanceAfter.tokens[0], 1500 * 1e18, "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], 19_494 * 1e17, "Incorrect token1 well reserve"); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function testFuzz_removeLiquidityImbalanced(uint256 a0, uint256 a1) public prank(user) { + // Setup amounts of liquidity to remove + // NOTE: amounts may or may not be equal + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 0, 750e18); + amounts[1] = bound(a1, 0, 750e18); + + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; + + // lpAmountIn should be <= umaxLpAmountIn + uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + + // Remove all of `user`'s liquidity and deliver them the tokens + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(lpAmountBurned, amounts, user); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfterRemoveLiquidity.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + + // `user` balance of underlying tokens increases + assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + // Equal amount of liquidity of 1000e18 were added in the setup function hence the + // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity + // is 1000e18 of each token. + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[0], + (initialLiquidity + addedLiquidity) - amounts[0], + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[1], + (initialLiquidity + addedLiquidity) - amounts[1], + "Incorrect token1 well reserve" + ); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal + /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` + /// before liquidity is removed by `user`. + function testFuzz_removeLiquidityImbalanced_withSwap(uint256 a0, uint256 imbalanceBias) public { + // Setup amounts of liquidity to remove + // NOTE: amounts[0] is bounded at 1 to prevent slippage overflow + // failure, bug fix in progress + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 1, 950e18); + amounts[1] = amounts[0]; + imbalanceBias = bound(imbalanceBias, 0, 40e18); + + // `user2` performs a swap to imbalance the pool by `imbalanceBias` + vm.prank(user2); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + + // `user` has LP tokens and will perform a `removeLiquidityImbalanced` call + vm.startPrank(user); + + Balances memory wellBalanceBefore = getBalances(address(well), well); + Balances memory userBalanceBefore = getBalances(user, well); + + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBefore.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBefore.tokens[1] - amounts[1]; + + // lpAmountIn should be <= user's LP balance + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + + // Remove some of `user`'s liquidity and deliver them the tokens + uint256 maxLpAmountIn = userBalanceBefore.lp; + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(lpAmountBurned, amounts, user); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + + Balances memory wellBalanceAfter = getBalances(address(well), well); + Balances memory userBalanceAfter = getBalances(user, well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfter.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + + // `user` balance of underlying tokens increases + assertEq(userBalanceAfter.tokens[0], userBalanceBefore.tokens[0] + amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], userBalanceBefore.tokens[1] + amounts[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + assertEq(wellBalanceAfter.tokens[0], wellBalanceBefore.tokens[0] - amounts[0], "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], wellBalanceBefore.tokens[1] - amounts[1], "Incorrect token1 well reserve"); + checkInvariant(address(well)); + } +} diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol new file mode 100644 index 00000000..82de1680 --- /dev/null +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, ConstantProduct2, IERC20, Balances} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityOneTokenTest is TestHelper { + event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); + + ConstantProduct2 cp; + uint256 constant addedLiquidity = 1000 * 1e18; + + function setUp() public { + cp = new ConstantProduct2(); + setupWell(2); + + // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + } + + /// @dev Assumes use of ConstantProduct2 + function test_getRemoveLiquidityOneTokenOut() public { + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e24, tokens[0]); + assertEq(amountOut, 875 * 1e18, "incorrect tokenOut"); + } + + /// @dev not enough tokens received for `lpAmountIn`. + function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { + uint256 lpAmountIn = 500 * 1e15; + uint256 minTokenAmountOut = 876 * 1e18; // too high + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + } + + function test_removeLiquidityOneToken_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidityOneToken(0, tokens[0], 0, user, block.timestamp - 1); + } + + /// @dev Base case + function test_removeLiquidityOneToken() public prank(user) { + uint256 lpAmountIn = 500 * 1e24; + uint256 minTokenAmountOut = 875 * 1e18; + + vm.expectEmit(true, true, true, true); + emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); + + uint256 amountOut = + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + + assertEq(userBalance.lp, lpAmountIn, "Incorrect lpAmountIn"); + + assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); + assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); + + // Equal amount of liquidity of 1000e18 were added in the setup function hence the + // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity + // is 1000e18 of each token. + assertEq( + wellBalance.tokens[0], + (initialLiquidity + addedLiquidity) - minTokenAmountOut, + "Incorrect token0 well reserve" + ); + assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { + // Assume we're removing tokens[0] + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 1e6, 750e18); + amounts[1] = 0; + + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + uint256 userLpBalance = userBalanceBeforeRemoveLiquidity.lp; + + // Find the LP amount that should be burned given the fuzzed + // amounts. Works even though only amounts[0] is set. + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; // should stay the same + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + + vm.expectEmit(true, true, true, true); + emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); + well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out + + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + + assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); + assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); // should stay the same + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[0], + (initialLiquidity + addedLiquidity) - amounts[0], + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[1], + (initialLiquidity + addedLiquidity) - amounts[1], + "Incorrect token1 well reserve" + ); // should stay the same + checkInvariant(address(well)); + } + + // TODO: fuzz test: imbalanced ratio of tokens +} diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol new file mode 100644 index 00000000..03e92a7b --- /dev/null +++ b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol @@ -0,0 +1,142 @@ +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.17; + +// import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +// import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; +// import {IWell} from "src/interfaces/IWell.sol"; +// import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +// contract WellRemoveLiquidityTest is LiquidityHelper { +// StableSwap2 cp; +// bytes constant data = ""; +// uint256 constant addedLiquidity = 1000 * 1e18; + +// function setUp() public { +// cp = new StableSwap2(); +// setupWell(2); + +// // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens +// addLiquidityEqualAmount(user, addedLiquidity); +// } + +// /// @dev ensure that Well liq was initialized correctly in {setUp} +// /// currently, liquidity is added in {TestHelper} and above +// function test_liquidityInitialized() public { +// IERC20[] memory tokens = well.tokens(); +// for (uint256 i; i < tokens.length; i++) { +// assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); +// } +// assertEq(well.totalSupply(), 2000 * 1e24, "incorrect totalSupply"); +// } + +// /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying +// /// since the tokens in the Well are balanced, user receives equal amounts +// function test_getRemoveLiquidityOut() public { +// uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e24); +// for (uint256 i; i < tokens.length; i++) { +// assertEq(amountsOut[i], 1000 * 1e18, "incorrect getRemoveLiquidityOut"); +// } +// } + +// /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token +// function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { +// uint256 lpAmountIn = 1000 * 1e24; + +// uint256[] memory minTokenAmountsOut = new uint256[](2); +// minTokenAmountsOut[0] = 1001 * 1e18; // too high +// minTokenAmountsOut[1] = 1000 * 1e18; + +// vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 1000 * 1e18, minTokenAmountsOut[0])); +// well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); +// } + +// function test_removeLiquidity_revertIf_expired() public { +// vm.expectRevert(IWellErrors.Expired.selector); +// well.removeLiquidity(0, new uint256[](2), user, block.timestamp - 1); +// } + +// /// @dev removeLiquidity: remove to equal amounts of underlying +// function test_removeLiquidity() public prank(user) { +// uint256 lpAmountIn = 1000 * 1e24; + +// uint256[] memory amountsOut = new uint256[](2); +// amountsOut[0] = 1000 * 1e18; +// amountsOut[1] = 1000 * 1e18; + +// Snapshot memory before; +// RemoveLiquidityAction memory action; + +// action.amounts = amountsOut; +// action.lpAmountIn = lpAmountIn; +// action.recipient = user; +// action.fees = new uint256[](2); + +// (before, action) = beforeRemoveLiquidity(action); +// well.removeLiquidity(lpAmountIn, amountsOut, user, type(uint256).max); +// afterRemoveLiquidity(before, action); +// checkInvariant(address(well)); +// } + +// /// @dev Fuzz test: EQUAL token reserves, BALANCED removal +// /// The Well contains equal reserves of all underlying tokens before execution. +// function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { +// // Setup amounts of liquidity to remove +// // NOTE: amounts may or may not match the maximum removable by `user`. +// uint256[] memory amounts = new uint256[](2); +// amounts[0] = bound(a0, 0, 1000e18); +// amounts[1] = amounts[0]; + +// Snapshot memory before; +// RemoveLiquidityAction memory action; +// uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + +// action.amounts = amounts; +// action.lpAmountIn = lpAmountIn; +// action.recipient = user; +// action.fees = new uint256[](2); + +// (before, action) = beforeRemoveLiquidity(action); +// well.removeLiquidity(lpAmountIn, amounts, user, type(uint256).max); +// afterRemoveLiquidity(before, action); + +// assertLe( +// well.totalSupply(), +// ConstantProduct2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) +// ); +// checkInvariant(address(well)); +// } + +// /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal +// /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` +// /// before liquidity is removed by `user`. +// function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { +// Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + +// uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; +// lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); +// imbalanceBias = bound(imbalanceBias, 0, 10e18); + +// // `user2` performs a swap to imbalance the pool by `imbalanceBias` +// vm.prank(user2); +// well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + +// // `user` has LP tokens and will perform a `removeLiquidity` call +// vm.startPrank(user); + +// uint256[] memory tokenAmountsOut = new uint256[](2); +// tokenAmountsOut = well.getRemoveLiquidityOut(lpAmountBurned); + +// Snapshot memory before; +// RemoveLiquidityAction memory action; + +// action.amounts = tokenAmountsOut; +// action.lpAmountIn = lpAmountBurned; +// action.recipient = user; +// action.fees = new uint256[](2); + +// (before, action) = beforeRemoveLiquidity(action); +// well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); +// afterRemoveLiquidity(before, action); +// checkInvariant(address(well)); +// } +// } From d55d529de84e3786822c7634c82ce559266ce6b1 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 12:54:34 +0545 Subject: [PATCH 08/69] beanstalkStableSwap inheriting, AddScaledReserves --- src/functions/BeanstalkStableSwap.sol | 241 ++------------------------ src/functions/StableSwap2.sol | 82 ++++----- 2 files changed, 51 insertions(+), 272 deletions(-) diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol index cec55290..3aa4451f 100644 --- a/src/functions/BeanstalkStableSwap.sol +++ b/src/functions/BeanstalkStableSwap.sol @@ -2,239 +2,26 @@ pragma solidity ^0.8.17; -import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; -import {SafeMath} from "oz/utils/math/SafeMath.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import "src/functions/StableSwap2.sol"; import {IBeanstalkA} from "src/interfaces/beanstalk/IBeanstalkA.sol"; +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + /** - * @author Publius, Brean - * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. - * developed by solidly. - * @dev features an variable A parameter, queried from a beanstalk A param + * @author Brean + * @title Beanstalk Stableswap well function. Includes functions needed for the well + * to interact with the Beanstalk contract. * - * Stableswap Wells with 2 tokens use the formula: - * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` - * - * Where: - * `A` is the Amplication parameter. - * `D` is the supply of LP tokens - * `b_i` is the reserve at index `i` + * @dev The A parameter is an dynamic parameter, queried from the Beanstalk contract. + * With an fallback A value determined by the well data. */ -contract BeanstalkStableSwap is IBeanstalkWellFunction { - using LibMath for uint256; - using SafeMath for uint256; - - // 2 token Pool. - uint256 constant N = 2; - - // A precision - uint256 constant A_PRECISION = 100; - - // Precision that all pools tokens will be converted to. - uint256 constant POOL_PRECISION_DECIMALS = 18; - - // Maximum A parameter. - uint256 constant MAX_A = 10000 * A_PRECISION; - - uint256 constant PRECISION = 1e18; +contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { + using LibMath for uint; + using SafeMath for uint; // Beanstalk IBeanstalkA BEANSTALK = IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5); - - - // Errors - error InvalidAParameter(uint256); - error InvalidTokens(); - error InvalidTokenDecimals(uint256); - - /** - * This Well function requires 3 parameters from wellFunctionData: - * 0: Failsafe A parameter - * 1: tkn0 address - * 2: tkn1 address - * - * @dev The StableSwap curve assumes that both tokens use the same decimals (max 1e18). - * tkn0 and tkn1 is used to call decimals() on the tokens to scale to 1e18. - * For example, USDC and BEAN has 6 decimals (TKX_SCALAR = 1e12), - * while DAI has 18 decimals (TKX_SCALAR = 1). - * - * The failsafe A parameter is used when the beanstalk A parameter is not available. - */ - struct WellFunctionData { - uint256 a; - address tkn0; - address tkn1; - } - - /** - * D invariant calculation in non-overflowing integer operations iteratively - * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - * - * Converging solution: - * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) - */ - function calcLpTokenSupply( - uint256[] memory reserves, - bytes calldata _wellFunctionData - ) public view override returns (uint256 lpTokenSupply) { - ( - , - uint256 Ann, - uint256[2] memory precisionMultipliers - ) = verifyWellFunctionData(_wellFunctionData); - - - uint256 sumReserves = reserves[0] * precisionMultipliers[0] + reserves[1] * precisionMultipliers[1]; - if(sumReserves == 0) return 0; - lpTokenSupply = sumReserves; - - // wtf is this bullshit - for(uint256 i = 0; i < 255; i++){ - uint256 dP = lpTokenSupply; - // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(reserves[0].mul(precisionMultipliers[0]).mul(N)); - dP = dP.mul(lpTokenSupply).div(reserves[1].mul(precisionMultipliers[1]).mul(N)); - uint256 prevReserves = lpTokenSupply; - lpTokenSupply = Ann - .mul(sumReserves) - .div(A_PRECISION) - .add(dP.mul(N)) - .mul(lpTokenSupply) - .div( - Ann - .sub(A_PRECISION) - .mul(lpTokenSupply) - .div(A_PRECISION) - .add(N.add(1).mul(dP)) - ); - // Equality with the precision of 1 - if (lpTokenSupply > prevReserves){ - if(lpTokenSupply - prevReserves <= 1) return lpTokenSupply; - } - else { - if(prevReserves - lpTokenSupply <= 1) return lpTokenSupply; - } - } - } - - function getPrice( - uint256[] calldata reserves, - uint256 j, - uint256 lpTokenSupply, - bytes calldata _wellFunctionData - ) external view returns (uint256) { - (, uint256 Ann, uint256[2] memory precisionMultipliers) = verifyWellFunctionData(_wellFunctionData); - uint256 c = lpTokenSupply; - uint256 i = j == 1 ? 0 : 1; - c = c.mul(lpTokenSupply).div(reserves[i].mul(precisionMultipliers[i]).mul(N)); - c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - - uint256 b = reserves[i].mul( - precisionMultipliers[i] - ).add(lpTokenSupply.mul(A_PRECISION).div(Ann)); - uint256 yPrev; - uint256 y = lpTokenSupply; - for (uint256 k = 0; k < 256; i++) { - yPrev = y; - y = y.mul(y).add(c).div(y.mul(2).add(b).sub(lpTokenSupply)); - if(y > yPrev){ - if(y - yPrev <= 1) return y.div(precisionMultipliers[j]); - } else { - if(yPrev - y <= 1) return y.div(precisionMultipliers[j]); - } - } - revert("Approximation did not converge"); - } - - - /** - * @notice Calculate x[i] if one reduces D from being calculated for xp to D - * Done by solving quadratic equation iteratively. - * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - * x_1**2 + b*x_1 = c - * x_1 = (x_1**2 + c) / (2*x_1 + b) - */ - function calcReserve( - uint256[] calldata reserves, - uint256 j, - uint256 lpTokenSupply, - bytes calldata _wellFunctionData - ) public view override returns (uint256 reserve) { - ( - , - uint256 Ann, - uint256[2] memory precisionMultipliers - ) = verifyWellFunctionData(_wellFunctionData); - require(j < N); - uint256 c = lpTokenSupply; - uint256 sumReserves; - uint256 prevReserve; - sumReserves = j == 0 ? reserves[1].mul(precisionMultipliers[1]) : reserves[0].mul(precisionMultipliers[0]); - c = c.mul(lpTokenSupply).div(sumReserves.mul(N)); - c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - uint256 b = - sumReserves.add( - lpTokenSupply.mul(A_PRECISION).div(Ann) - ); - reserve = lpTokenSupply; - - for(uint256 i; i < 255; ++i){ - prevReserve = reserve; - reserve = - reserve - .mul(reserve) - .add(c) - .div( - reserve - .mul(2) - .add(b) - .sub(lpTokenSupply) - ); - // Equality with the precision of 1 - // safeMath not needed due to conditional. - if(reserve > prevReserve){ - if(reserve - prevReserve <= 1) return reserve.div(precisionMultipliers[j]); - } else { - if(prevReserve - reserve <= 1) return reserve.div(precisionMultipliers[j]); - } - } - - revert("did not find convergence"); - } - - - /** - * @notice Defines a proportional relationship between the supply of LP tokens - * and the amount of each underlying token for a two-token Well. - * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user - * recieves `s * b_i / S` of each underlying token. - * reserves are scaled as needed based on tknXScalar - */ - function calcLPTokenUnderlying( - uint256 lpTokenAmount, - uint256[] calldata reserves, - uint256 lpTokenSupply, - bytes calldata _wellFunctionData - ) external view returns (uint256[] memory underlyingAmounts) { - ( , , uint256[2] memory precisionMultipliers) = verifyWellFunctionData(_wellFunctionData); - underlyingAmounts = new uint256[](2); - // overflow cannot occur as lpTokenAmount could not be calculated. - underlyingAmounts[0] = lpTokenAmount * reserves[0].mul(precisionMultipliers[0]) / lpTokenSupply; - underlyingAmounts[1] = lpTokenAmount * reserves[1].mul(precisionMultipliers[1]) / lpTokenSupply; - } - - function name() external pure override returns (string memory) { - return "StableSwap"; - } - - function symbol() external pure override returns (string memory) { - return "SS2"; - } - - - + uint256 PRECISION = 1e18; /** * TODO: for deltaB minting @@ -281,9 +68,9 @@ contract BeanstalkStableSwap is IBeanstalkWellFunction { price = 1.01 * 1e18; } - function verifyWellFunctionData( + function decodeWFData( bytes memory data - ) public view returns ( + ) public view override returns ( uint256 a, uint256 Ann, uint256[2] memory precisionMultipliers diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol index 029a93f6..b4633d8a 100644 --- a/src/functions/StableSwap2.sol +++ b/src/functions/StableSwap2.sol @@ -8,9 +8,9 @@ import {SafeMath} from "oz/utils/math/SafeMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; /** - * @author Publius, Brean + * @author Brean * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. - * developed by solidly. + * developed by curve. * * Stableswap Wells with 2 tokens use the formula: * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` @@ -66,26 +66,21 @@ contract StableSwap2 is IWellFunction { * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) */ function calcLpTokenSupply( - uint[] calldata reserves, + uint[] memory reserves, bytes calldata _wellFunctionData - ) external view override returns (uint lpTokenSupply) { - ( - , - uint256 Ann, - uint256[2] memory precisionMultipliers - ) = verifyWellFunctionData(_wellFunctionData); - + ) public view override returns (uint lpTokenSupply) { + (, uint256 Ann,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + reserves = getScaledReserves(reserves, precisions); - uint256 sumReserves = reserves[0] * precisionMultipliers[0] + reserves[1] * precisionMultipliers[1]; + uint256 sumReserves = reserves[0] + reserves[1]; if(sumReserves == 0) return 0; lpTokenSupply = sumReserves; - // wtf is this bullshit for(uint i = 0; i < 255; i++){ uint256 dP = lpTokenSupply; // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(reserves[0].mul(precisionMultipliers[0]).mul(N)); - dP = dP.mul(lpTokenSupply).div(reserves[1].mul(precisionMultipliers[1]).mul(N)); + dP = dP.mul(lpTokenSupply).div(reserves[0].mul(N)); + dP = dP.mul(lpTokenSupply).div(reserves[1].mul(N)); uint256 prevReserves = lpTokenSupply; lpTokenSupply = Ann .mul(sumReserves) @@ -93,11 +88,9 @@ contract StableSwap2 is IWellFunction { .add(dP.mul(N)) .mul(lpTokenSupply) .div( - Ann - .sub(A_PRECISION) - .mul(lpTokenSupply) - .div(A_PRECISION) - .add(N.add(1).mul(dP)) + Ann.sub(A_PRECISION).mul(lpTokenSupply) + .div(A_PRECISION) + .add(N.add(1).mul(dP)) ); // Equality with the precision of 1 if (lpTokenSupply > prevReserves){ @@ -117,21 +110,19 @@ contract StableSwap2 is IWellFunction { * x_1 = (x_1**2 + c) / (2*x_1 + b) */ function calcReserve( - uint[] calldata reserves, + uint[] memory reserves, uint j, uint lpTokenSupply, bytes calldata _wellFunctionData ) external view override returns (uint reserve) { - ( - , - uint256 Ann, - uint256[2] memory precisionMultipliers - ) = verifyWellFunctionData(_wellFunctionData); + (, uint256 Ann, uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + reserves = getScaledReserves(reserves, precisions); + require(j < N); uint256 c = lpTokenSupply; uint256 sumReserves; uint256 prevReserve; - sumReserves = j == 0 ? reserves[1].mul(precisionMultipliers[1]) : reserves[0].mul(precisionMultipliers[0]); + sumReserves = j == 0 ? reserves[1] : reserves[0]; c = c.mul(lpTokenSupply).div(sumReserves.mul(N)); c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); uint256 b = @@ -146,12 +137,7 @@ contract StableSwap2 is IWellFunction { reserve .mul(reserve) .add(c) - .div( - reserve - .mul(2) - .add(b) - .sub(lpTokenSupply) - ); + .div(reserve.mul(2).add(b).sub(lpTokenSupply)); // Equality with the precision of 1 // safeMath not needed due to conditional. if(reserve > prevReserve){ @@ -173,19 +159,17 @@ contract StableSwap2 is IWellFunction { */ function calcLPTokenUnderlying( uint lpTokenAmount, - uint[] calldata reserves, + uint[] memory reserves, uint lpTokenSupply, bytes calldata _wellFunctionData ) external view returns (uint[] memory underlyingAmounts) { - ( - , - , - uint256[2] memory precisionMultipliers - ) = verifyWellFunctionData(_wellFunctionData); + ( , ,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + reserves = getScaledReserves(reserves, precisions); + underlyingAmounts = new uint[](2); // overflow cannot occur as lpTokenAmount could not be calculated. - underlyingAmounts[0] = lpTokenAmount * reserves[0].mul(precisionMultipliers[0]) / lpTokenSupply; - underlyingAmounts[1] = lpTokenAmount * reserves[1].mul(precisionMultipliers[1]) / lpTokenSupply; + underlyingAmounts[0] = lpTokenAmount * reserves[0] / lpTokenSupply; + underlyingAmounts[1] = lpTokenAmount * reserves[1] / lpTokenSupply; } function name() external pure override returns (string memory) { @@ -196,12 +180,12 @@ contract StableSwap2 is IWellFunction { return "SS2"; } - function verifyWellFunctionData( + function decodeWFData( bytes memory data - ) public view returns ( + ) public virtual view returns ( uint256 a, uint256 Ann, - uint256[2] memory precisionMultipliers + uint256[2] memory precisions ){ WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); a = wfd.a; @@ -209,8 +193,16 @@ contract StableSwap2 is IWellFunction { if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); if(IERC20(wfd.tkn0).decimals() > 18) revert InvalidTokenDecimals(IERC20(wfd.tkn0).decimals()); Ann = a * N * N * A_PRECISION; - precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); - precisionMultipliers[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); + precisions[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); + precisions[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); } + function getScaledReserves( + uint[] memory reserves, + uint256[2] memory precisions + ) internal pure returns (uint[] memory) { + reserves[0] = reserves[0].mul(precisions[0]); + reserves[1] = reserves[1].mul(precisions[1]); + return reserves; + } } From 544aaf37b8b90f952001b410026dd043e10ba85a Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 12:54:51 +0545 Subject: [PATCH 09/69] tests --- ...lkStableSwap2.calcReserveAtRatioSwap.t.sol | 240 +++++++++--------- ....RemoveLiquidityImbalancedStableSwap.t.sol | 34 ++- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 19 +- test/stableSwap/Well.SkimStableSwap.t.sol | 2 +- test/stableSwap/Well.SwapToStableSwap.t.sol | 6 +- 5 files changed, 160 insertions(+), 141 deletions(-) diff --git a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol index 498c9eb8..b8ff1891 100644 --- a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol @@ -1,120 +1,120 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.17; - -// import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -// import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; -// import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; - - -// /// @dev Tests the {ConstantProduct2} Well function directly. -// contract BeanstalkStableSwapSwapTest is TestHelper { -// IBeanstalkWellFunction _f; -// bytes data; - -// //////////// SETUP //////////// - -// function setUp() public { -// _f = new BeanstalkStableSwap(); -// IERC20[] memory _token = deployMockTokens(2); -// data = abi.encode( -// address(_token[0]), -// address(_token[1]) -// ); -// } - -// function test_calcReserveAtRatioSwap_equal_equal() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 100; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 1; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 100); -// assertEq(reserve1, 100); -// } - -// function test_calcReserveAtRatioSwap_equal_diff() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 50; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 1; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 74); -// assertEq(reserve1, 74); -// } - -// function test_calcReserveAtRatioSwap_diff_equal() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 100; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 2; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 141); -// assertEq(reserve1, 70); -// } - -// function test_calcReserveAtRatioSwap_diff_diff() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 50; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 12_984_712_098_520; -// ratios[1] = 12_984_712_098; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 2236); -// assertEq(reserve1, 2); -// } - -// function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { -// // Upper bound is limited by stableSwap, -// // due to the stableswap reserves being extremely far apart. -// reserves[0] = bound(reserves[0], 1e18, 1e31); -// reserves[1] = bound(reserves[1], 1e18, 1e31); -// ratios[0] = 1e18; -// ratios[1] = 2e18; - - -// uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); -// console.log("lpTokenSupply:", lpTokenSupply); - -// uint256[] memory reservesOut = new uint256[](2); -// for (uint256 i; i < 2; ++i) { -// reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); -// } -// console.log("reservesOut 0:", reservesOut[0]); -// console.log("reservesOut 1:", reservesOut[1]); - - -// // Get LP token supply with bound reserves. -// uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); -// console.log("lpTokenSupplyOut:", lpTokenSupplyOut); -// // Precision is set to the minimum number of digits of the reserves out. -// uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) -// ? numDigits(reservesOut[1]) -// : numDigits(reservesOut[0]); - -// // Check LP Token Supply after = lp token supply before. -// // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); -// assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); - -// // Check ratio of `reservesOut` = ratio of `ratios`. -// // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + + +/// @dev Tests the {ConstantProduct2} Well function directly. +contract BeanstalkStableSwapSwapTest is TestHelper { + IBeanstalkWellFunction _f; + bytes data; + + //////////// SETUP //////////// + + function setUp() public { + _f = new BeanstalkStableSwap(); + IERC20[] memory _token = deployMockTokens(2); + data = abi.encode( + address(_token[0]), + address(_token[1]) + ); + } + + function test_calcReserveAtRatioSwap_equal_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 100); + assertEq(reserve1, 100); + } + + function test_calcReserveAtRatioSwap_equal_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 74); + assertEq(reserve1, 74); + } + + function test_calcReserveAtRatioSwap_diff_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 2; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 141); + assertEq(reserve1, 70); + } + + function test_calcReserveAtRatioSwap_diff_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 12_984_712_098_520; + ratios[1] = 12_984_712_098; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 2236); + assertEq(reserve1, 2); + } + + function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { + // Upper bound is limited by stableSwap, + // due to the stableswap reserves being extremely far apart. + reserves[0] = bound(reserves[0], 1e18, 1e31); + reserves[1] = bound(reserves[1], 1e18, 1e31); + ratios[0] = 1e18; + ratios[1] = 2e18; + + + uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); + console.log("lpTokenSupply:", lpTokenSupply); + + uint256[] memory reservesOut = new uint256[](2); + for (uint256 i; i < 2; ++i) { + reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); + } + console.log("reservesOut 0:", reservesOut[0]); + console.log("reservesOut 1:", reservesOut[1]); + + + // Get LP token supply with bound reserves. + uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); + console.log("lpTokenSupplyOut:", lpTokenSupplyOut); + // Precision is set to the minimum number of digits of the reserves out. + uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) + ? numDigits(reservesOut[1]) + : numDigits(reservesOut[0]); + + // Check LP Token Supply after = lp token supply before. + // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); + assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); + + // Check ratio of `reservesOut` = ratio of `ratios`. + // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); + } +} diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol index d58f8e08..1ec92b55 100644 --- a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -1,31 +1,41 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, ConstantProduct2, Balances} from "test/TestHelper.sol"; +import {TestHelper, StableSwap2, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; -contract WellRemoveLiquidityImbalancedTest is TestHelper { +contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); uint256[] tokenAmountsOut; uint256 requiredLpAmountIn; + bytes _data; + // Setup - ConstantProduct2 cp; + StableSwap2 ss; + uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - cp = new ConstantProduct2(); - setupWell(2); + ss = new StableSwap2(); + setupStableSwapWell(10); + + _data = abi.encode( + StableSwap2.WellFunctionData( + 10, + address(tokens[0]), + address(tokens[1]) + ) + ); - // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); - // Shared removal amounts tokenAmountsOut.push(500 * 1e18); // 500 token0 tokenAmountsOut.push(506 * 1e17); // 50.6 token1 - requiredLpAmountIn = 290 * 1e24; // LP needed to remove `tokenAmountsOut` + requiredLpAmountIn = 552_016_399_701_327_563_972; // ~552e18 LP needed to remove `tokenAmountsOut` } /// @dev Assumes use of ConstantProduct2 @@ -36,7 +46,7 @@ contract WellRemoveLiquidityImbalancedTest is TestHelper { /// @dev not enough LP to receive `tokenAmountsOut` function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { - uint256 maxLpAmountIn = 5 * 1e24; + uint256 maxLpAmountIn = 5 * 1e18; vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); } @@ -97,7 +107,7 @@ contract WellRemoveLiquidityImbalancedTest is TestHelper { // Calculate the new LP token supply after the Well's reserves are changed. // The delta `lpAmountBurned` is the amount of LP that should be burned // when this liquidity is removed. - uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; // Remove all of `user`'s liquidity and deliver them the tokens @@ -129,7 +139,7 @@ contract WellRemoveLiquidityImbalancedTest is TestHelper { (initialLiquidity + addedLiquidity) - amounts[1], "Incorrect token1 well reserve" ); - checkInvariant(address(well)); + checkStableSwapInvariant(address(well)); } /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal @@ -165,7 +175,7 @@ contract WellRemoveLiquidityImbalancedTest is TestHelper { // Calculate the new LP token supply after the Well's reserves are changed. // The delta `lpAmountBurned` is the amount of LP that should be burned // when this liquidity is removed. - uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; // Remove some of `user`'s liquidity and deliver them the tokens diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index 82de1680..ca470110 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -1,25 +1,34 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, ConstantProduct2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityOneTokenTest is TestHelper { event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); - ConstantProduct2 cp; + StableSwap2 ss; uint256 constant addedLiquidity = 1000 * 1e18; + bytes _data; + function setUp() public { - cp = new ConstantProduct2(); + ss = new StableSwap2(); setupWell(2); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens addLiquidityEqualAmount(user, addedLiquidity); + _data = abi.encode( + StableSwap2.WellFunctionData( + 10, + address(tokens[0]), + address(tokens[1]) + ) + ); } - /// @dev Assumes use of ConstantProduct2 + /// @dev Assumes use of StableSwap2 function test_getRemoveLiquidityOneTokenOut() public { uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e24, tokens[0]); assertEq(amountOut, 875 * 1e18, "incorrect tokenOut"); @@ -96,7 +105,7 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { // Calculate the new LP token supply after the Well's reserves are changed. // The delta `lpAmountBurned` is the amount of LP that should be burned // when this liquidity is removed. - uint256 newLpTokenSupply = cp.calcLpTokenSupply(reserves, ""); + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; vm.expectEmit(true, true, true, true); diff --git a/test/stableSwap/Well.SkimStableSwap.t.sol b/test/stableSwap/Well.SkimStableSwap.t.sol index 8b4962f1..a8bcf8ff 100644 --- a/test/stableSwap/Well.SkimStableSwap.t.sol +++ b/test/stableSwap/Well.SkimStableSwap.t.sol @@ -5,7 +5,7 @@ import {TestHelper, Balances} from "test/TestHelper.sol"; contract WellSkimTest is TestHelper { function setUp() public { - setupStableSwapWell(2); + setupStableSwapWell(10); } function test_initialized() public { diff --git a/test/stableSwap/Well.SwapToStableSwap.t.sol b/test/stableSwap/Well.SwapToStableSwap.t.sol index 5b9c2ebc..7e165f23 100644 --- a/test/stableSwap/Well.SwapToStableSwap.t.sol +++ b/test/stableSwap/Well.SwapToStableSwap.t.sol @@ -10,13 +10,13 @@ import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellSwapToStableSwapTest is SwapHelper { function setUp() public { - setupStableSwapWell(2); + setupStableSwapWell(10); } function test_getSwapIn() public { uint256 amountOut = 100 * 1e18; uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); - assertEq(amountIn, 102_054_486_564_986_716_193); // ~2% slippage + assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage } function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { @@ -45,7 +45,7 @@ contract WellSwapToStableSwapTest is SwapHelper { /// @dev Slippage revert occurs if maxAmountIn is too low function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { uint256 amountOut = 100 * 1e18; - uint256 amountIn = 102_054_486_564_986_716_193; + uint256 amountIn = 100_482_889_020_651_556_292; uint256 maxAmountIn = amountIn * 99 / 100; vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); From f8cc75b48964db75b16962d7866be62a4d31ad7a Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 13:03:24 +0545 Subject: [PATCH 10/69] scale down reserves in calcReserve --- src/functions/StableSwap2.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol index b4633d8a..253d005a 100644 --- a/src/functions/StableSwap2.sol +++ b/src/functions/StableSwap2.sol @@ -59,6 +59,7 @@ contract StableSwap2 is IWellFunction { } /** + * @notice Calculate the amount of LP tokens minted when adding liquidity. * D invariant calculation in non-overflowing integer operations iteratively * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) * @@ -140,10 +141,11 @@ contract StableSwap2 is IWellFunction { .div(reserve.mul(2).add(b).sub(lpTokenSupply)); // Equality with the precision of 1 // safeMath not needed due to conditional. + // scale reserve down to original precision if(reserve > prevReserve){ - if(reserve - prevReserve <= 1) return reserve; + if(reserve - prevReserve <= 1) return reserve.div(precisions[j]); } else { - if(prevReserve - reserve <= 1) return reserve; + if(prevReserve - reserve <= 1) return reserve.div(precisions[j]); } } From 16b8d3d76af3d9c4856f8e1f461a1f2abcb81822 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 15:38:30 +0545 Subject: [PATCH 11/69] remove Stack too deep error, removeLiq one Tkn --- src/functions/BeanstalkStableSwap.sol | 2 +- src/functions/StableSwap2.sol | 67 +++-- ...lkStableSwap2.calcReserveAtRatioSwap.t.sol | 240 +++++++++--------- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 41 +-- 4 files changed, 185 insertions(+), 165 deletions(-) diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol index 3aa4451f..e20a9204 100644 --- a/src/functions/BeanstalkStableSwap.sol +++ b/src/functions/BeanstalkStableSwap.sol @@ -71,7 +71,6 @@ contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { function decodeWFData( bytes memory data ) public view override returns ( - uint256 a, uint256 Ann, uint256[2] memory precisionMultipliers ){ @@ -79,6 +78,7 @@ contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { // try to get the beanstalk A. // if it fails, use the failsafe A stored in well function data. + uint256 a; try BEANSTALK.getBeanstalkA() returns (uint256 _a) { a = _a; } catch { diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol index 253d005a..a987839f 100644 --- a/src/functions/StableSwap2.sol +++ b/src/functions/StableSwap2.sol @@ -70,7 +70,7 @@ contract StableSwap2 is IWellFunction { uint[] memory reserves, bytes calldata _wellFunctionData ) public view override returns (uint lpTokenSupply) { - (, uint256 Ann,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + (uint256 Ann,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); reserves = getScaledReserves(reserves, precisions); uint256 sumReserves = reserves[0] + reserves[1]; @@ -116,39 +116,31 @@ contract StableSwap2 is IWellFunction { uint lpTokenSupply, bytes calldata _wellFunctionData ) external view override returns (uint reserve) { - (, uint256 Ann, uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + (uint256 Ann, uint256[2] memory precisions) = decodeWFData(_wellFunctionData); reserves = getScaledReserves(reserves, precisions); - require(j < N); - uint256 c = lpTokenSupply; - uint256 sumReserves; - uint256 prevReserve; - sumReserves = j == 0 ? reserves[1] : reserves[0]; - c = c.mul(lpTokenSupply).div(sumReserves.mul(N)); - c = c.mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - uint256 b = - sumReserves.add( - lpTokenSupply.mul(A_PRECISION).div(Ann) - ); + + // avoid stack too deep errors. + (uint256 c, uint256 b) = getBandC( + Ann, + lpTokenSupply, + j == 0 ? reserves[1] : reserves[0] + ); reserve = lpTokenSupply; + uint256 prevReserve; for(uint i; i < 255; ++i){ prevReserve = reserve; - reserve = - reserve - .mul(reserve) - .add(c) - .div(reserve.mul(2).add(b).sub(lpTokenSupply)); + reserve = _calcReserve(reserve, b, c, lpTokenSupply); // Equality with the precision of 1 - // safeMath not needed due to conditional. // scale reserve down to original precision + // TODO: discuss with pubs if(reserve > prevReserve){ if(reserve - prevReserve <= 1) return reserve.div(precisions[j]); } else { if(prevReserve - reserve <= 1) return reserve.div(precisions[j]); } } - revert("did not find convergence"); } @@ -165,7 +157,7 @@ contract StableSwap2 is IWellFunction { uint lpTokenSupply, bytes calldata _wellFunctionData ) external view returns (uint[] memory underlyingAmounts) { - ( , ,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); + (,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); reserves = getScaledReserves(reserves, precisions); underlyingAmounts = new uint[](2); @@ -185,16 +177,14 @@ contract StableSwap2 is IWellFunction { function decodeWFData( bytes memory data ) public virtual view returns ( - uint256 a, uint256 Ann, uint256[2] memory precisions ){ WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - a = wfd.a; - if (a == 0) revert InvalidAParameter(a); + if (wfd.a == 0) revert InvalidAParameter(wfd.a); if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); if(IERC20(wfd.tkn0).decimals() > 18) revert InvalidTokenDecimals(IERC20(wfd.tkn0).decimals()); - Ann = a * N * N * A_PRECISION; + Ann = wfd.a * N * N * A_PRECISION; precisions[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); precisions[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); } @@ -207,4 +197,31 @@ contract StableSwap2 is IWellFunction { reserves[1] = reserves[1].mul(precisions[1]); return reserves; } + + function _calcReserve( + uint256 reserve, + uint256 b, + uint256 c, + uint256 lpTokenSupply + ) private pure returns (uint256){ + // NOTE: the result should be rounded up to ensure that the well benefits from the rounding error, + // rather than the user. + return reserve + .mul(reserve) + .add(c) + .div(reserve.mul(2).add(b).sub(lpTokenSupply)); + } + + function getBandC( + uint256 Ann, + uint256 lpTokenSupply, + uint256 sumReserves + ) private pure returns (uint256 c, uint256 b){ + c = lpTokenSupply + .mul(lpTokenSupply).div(sumReserves.mul(N)) + .mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + b = sumReserves.add( + lpTokenSupply.mul(A_PRECISION).div(Ann) + ); + } } diff --git a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol index b8ff1891..498c9eb8 100644 --- a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol @@ -1,120 +1,120 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; -import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; - - -/// @dev Tests the {ConstantProduct2} Well function directly. -contract BeanstalkStableSwapSwapTest is TestHelper { - IBeanstalkWellFunction _f; - bytes data; - - //////////// SETUP //////////// - - function setUp() public { - _f = new BeanstalkStableSwap(); - IERC20[] memory _token = deployMockTokens(2); - data = abi.encode( - address(_token[0]), - address(_token[1]) - ); - } - - function test_calcReserveAtRatioSwap_equal_equal() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 1; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 100); - assertEq(reserve1, 100); - } - - function test_calcReserveAtRatioSwap_equal_diff() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 1; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 74); - assertEq(reserve1, 74); - } - - function test_calcReserveAtRatioSwap_diff_equal() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 2; - ratios[1] = 1; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 141); - assertEq(reserve1, 70); - } - - function test_calcReserveAtRatioSwap_diff_diff() public { - uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; - uint256[] memory ratios = new uint256[](2); - ratios[0] = 12_984_712_098_520; - ratios[1] = 12_984_712_098; - - uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); - uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - - assertEq(reserve0, 2236); - assertEq(reserve1, 2); - } - - function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { - // Upper bound is limited by stableSwap, - // due to the stableswap reserves being extremely far apart. - reserves[0] = bound(reserves[0], 1e18, 1e31); - reserves[1] = bound(reserves[1], 1e18, 1e31); - ratios[0] = 1e18; - ratios[1] = 2e18; - - - uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); - console.log("lpTokenSupply:", lpTokenSupply); - - uint256[] memory reservesOut = new uint256[](2); - for (uint256 i; i < 2; ++i) { - reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); - } - console.log("reservesOut 0:", reservesOut[0]); - console.log("reservesOut 1:", reservesOut[1]); - - - // Get LP token supply with bound reserves. - uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); - console.log("lpTokenSupplyOut:", lpTokenSupplyOut); - // Precision is set to the minimum number of digits of the reserves out. - uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) - ? numDigits(reservesOut[1]) - : numDigits(reservesOut[0]); - - // Check LP Token Supply after = lp token supply before. - // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); - assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); - - // Check ratio of `reservesOut` = ratio of `ratios`. - // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); - } -} +// // SPDX-License-Identifier: MIT +// pragma solidity ^0.8.17; + +// import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +// import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; +// import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + + +// /// @dev Tests the {ConstantProduct2} Well function directly. +// contract BeanstalkStableSwapSwapTest is TestHelper { +// IBeanstalkWellFunction _f; +// bytes data; + +// //////////// SETUP //////////// + +// function setUp() public { +// _f = new BeanstalkStableSwap(); +// IERC20[] memory _token = deployMockTokens(2); +// data = abi.encode( +// address(_token[0]), +// address(_token[1]) +// ); +// } + +// function test_calcReserveAtRatioSwap_equal_equal() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 100; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 1; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 100); +// assertEq(reserve1, 100); +// } + +// function test_calcReserveAtRatioSwap_equal_diff() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 50; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 1; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 74); +// assertEq(reserve1, 74); +// } + +// function test_calcReserveAtRatioSwap_diff_equal() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 100; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 2; +// ratios[1] = 1; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 141); +// assertEq(reserve1, 70); +// } + +// function test_calcReserveAtRatioSwap_diff_diff() public { +// uint256[] memory reserves = new uint256[](2); +// reserves[0] = 50; +// reserves[1] = 100; +// uint256[] memory ratios = new uint256[](2); +// ratios[0] = 12_984_712_098_520; +// ratios[1] = 12_984_712_098; + +// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); +// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + +// assertEq(reserve0, 2236); +// assertEq(reserve1, 2); +// } + +// function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { +// // Upper bound is limited by stableSwap, +// // due to the stableswap reserves being extremely far apart. +// reserves[0] = bound(reserves[0], 1e18, 1e31); +// reserves[1] = bound(reserves[1], 1e18, 1e31); +// ratios[0] = 1e18; +// ratios[1] = 2e18; + + +// uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); +// console.log("lpTokenSupply:", lpTokenSupply); + +// uint256[] memory reservesOut = new uint256[](2); +// for (uint256 i; i < 2; ++i) { +// reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); +// } +// console.log("reservesOut 0:", reservesOut[0]); +// console.log("reservesOut 1:", reservesOut[1]); + + +// // Get LP token supply with bound reserves. +// uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); +// console.log("lpTokenSupplyOut:", lpTokenSupplyOut); +// // Precision is set to the minimum number of digits of the reserves out. +// uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) +// ? numDigits(reservesOut[1]) +// : numDigits(reservesOut[0]); + +// // Check LP Token Supply after = lp token supply before. +// // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); +// assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); + +// // Check ratio of `reservesOut` = ratio of `ratios`. +// // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); +// } +// } diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index ca470110..1504b1ac 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -5,7 +5,7 @@ import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; -contract WellRemoveLiquidityOneTokenTest is TestHelper { +contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); StableSwap2 ss; @@ -15,9 +15,9 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { function setUp() public { ss = new StableSwap2(); - setupWell(2); + setupStableSwapWell(10); - // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); _data = abi.encode( StableSwap2.WellFunctionData( @@ -30,14 +30,14 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { /// @dev Assumes use of StableSwap2 function test_getRemoveLiquidityOneTokenOut() public { - uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e24, tokens[0]); - assertEq(amountOut, 875 * 1e18, "incorrect tokenOut"); + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); + assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); } /// @dev not enough tokens received for `lpAmountIn`. function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { - uint256 lpAmountIn = 500 * 1e15; - uint256 minTokenAmountOut = 876 * 1e18; // too high + uint256 lpAmountIn = 500 * 1e18; + uint256 minTokenAmountOut = 500 * 1e18; uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); @@ -51,8 +51,9 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { /// @dev Base case function test_removeLiquidityOneToken() public prank(user) { - uint256 lpAmountIn = 500 * 1e24; - uint256 minTokenAmountOut = 875 * 1e18; + uint256 lpAmountIn = 500 * 1e18; + uint256 minTokenAmountOut = 498_279_423_862_830_737_827; + Balances memory prevUserBalance = getBalances(user, well); vm.expectEmit(true, true, true, true); emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); @@ -63,7 +64,7 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { Balances memory userBalance = getBalances(user, well); Balances memory wellBalance = getBalances(address(well), well); - assertEq(userBalance.lp, lpAmountIn, "Incorrect lpAmountIn"); + assertEq(userBalance.lp, prevUserBalance.lp - lpAmountIn, "Incorrect lpAmountIn"); assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); @@ -77,15 +78,16 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { "Incorrect token0 well reserve" ); assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); - checkInvariant(address(well)); + checkStableSwapInvariant(address(well)); } /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. + // TODO: well function gives the rounding error to the user instead of the Well function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { // Assume we're removing tokens[0] uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(a0, 1e6, 750e18); + amounts[0] = bound(a0, 1e18, 750e18); amounts[1] = 0; Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); @@ -107,20 +109,21 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { // when this liquidity is removed. uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; - - vm.expectEmit(true, true, true, true); + vm.expectEmit(true, true, true, false); emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); - well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out + uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out + assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); - assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); // should stay the same - assertEq( + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same + assertApproxEqAbs( wellBalanceAfterRemoveLiquidity.tokens[0], (initialLiquidity + addedLiquidity) - amounts[0], + 1, "Incorrect token0 well reserve" ); assertEq( @@ -128,7 +131,7 @@ contract WellRemoveLiquidityOneTokenTest is TestHelper { (initialLiquidity + addedLiquidity) - amounts[1], "Incorrect token1 well reserve" ); // should stay the same - checkInvariant(address(well)); + checkStableSwapInvariant(address(well)); } // TODO: fuzz test: imbalanced ratio of tokens From 463fec448de6ae27287c8b5a9f9458920179cb48 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 16:19:05 +0545 Subject: [PATCH 12/69] remove Liquidity --- .../Well.RemoveLiquidityStableSwap.t.sol | 284 +++++++++--------- 1 file changed, 142 insertions(+), 142 deletions(-) diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol index 03e92a7b..a085ae36 100644 --- a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol @@ -1,142 +1,142 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.17; - -// import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; -// import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; -// import {IWell} from "src/interfaces/IWell.sol"; -// import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -// contract WellRemoveLiquidityTest is LiquidityHelper { -// StableSwap2 cp; -// bytes constant data = ""; -// uint256 constant addedLiquidity = 1000 * 1e18; - -// function setUp() public { -// cp = new StableSwap2(); -// setupWell(2); - -// // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens -// addLiquidityEqualAmount(user, addedLiquidity); -// } - -// /// @dev ensure that Well liq was initialized correctly in {setUp} -// /// currently, liquidity is added in {TestHelper} and above -// function test_liquidityInitialized() public { -// IERC20[] memory tokens = well.tokens(); -// for (uint256 i; i < tokens.length; i++) { -// assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); -// } -// assertEq(well.totalSupply(), 2000 * 1e24, "incorrect totalSupply"); -// } - -// /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying -// /// since the tokens in the Well are balanced, user receives equal amounts -// function test_getRemoveLiquidityOut() public { -// uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e24); -// for (uint256 i; i < tokens.length; i++) { -// assertEq(amountsOut[i], 1000 * 1e18, "incorrect getRemoveLiquidityOut"); -// } -// } - -// /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token -// function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { -// uint256 lpAmountIn = 1000 * 1e24; - -// uint256[] memory minTokenAmountsOut = new uint256[](2); -// minTokenAmountsOut[0] = 1001 * 1e18; // too high -// minTokenAmountsOut[1] = 1000 * 1e18; - -// vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 1000 * 1e18, minTokenAmountsOut[0])); -// well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); -// } - -// function test_removeLiquidity_revertIf_expired() public { -// vm.expectRevert(IWellErrors.Expired.selector); -// well.removeLiquidity(0, new uint256[](2), user, block.timestamp - 1); -// } - -// /// @dev removeLiquidity: remove to equal amounts of underlying -// function test_removeLiquidity() public prank(user) { -// uint256 lpAmountIn = 1000 * 1e24; - -// uint256[] memory amountsOut = new uint256[](2); -// amountsOut[0] = 1000 * 1e18; -// amountsOut[1] = 1000 * 1e18; - -// Snapshot memory before; -// RemoveLiquidityAction memory action; - -// action.amounts = amountsOut; -// action.lpAmountIn = lpAmountIn; -// action.recipient = user; -// action.fees = new uint256[](2); - -// (before, action) = beforeRemoveLiquidity(action); -// well.removeLiquidity(lpAmountIn, amountsOut, user, type(uint256).max); -// afterRemoveLiquidity(before, action); -// checkInvariant(address(well)); -// } - -// /// @dev Fuzz test: EQUAL token reserves, BALANCED removal -// /// The Well contains equal reserves of all underlying tokens before execution. -// function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { -// // Setup amounts of liquidity to remove -// // NOTE: amounts may or may not match the maximum removable by `user`. -// uint256[] memory amounts = new uint256[](2); -// amounts[0] = bound(a0, 0, 1000e18); -// amounts[1] = amounts[0]; - -// Snapshot memory before; -// RemoveLiquidityAction memory action; -// uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - -// action.amounts = amounts; -// action.lpAmountIn = lpAmountIn; -// action.recipient = user; -// action.fees = new uint256[](2); - -// (before, action) = beforeRemoveLiquidity(action); -// well.removeLiquidity(lpAmountIn, amounts, user, type(uint256).max); -// afterRemoveLiquidity(before, action); - -// assertLe( -// well.totalSupply(), -// ConstantProduct2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) -// ); -// checkInvariant(address(well)); -// } - -// /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal -// /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` -// /// before liquidity is removed by `user`. -// function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { -// Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); - -// uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; -// lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); -// imbalanceBias = bound(imbalanceBias, 0, 10e18); - -// // `user2` performs a swap to imbalance the pool by `imbalanceBias` -// vm.prank(user2); -// well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); - -// // `user` has LP tokens and will perform a `removeLiquidity` call -// vm.startPrank(user); - -// uint256[] memory tokenAmountsOut = new uint256[](2); -// tokenAmountsOut = well.getRemoveLiquidityOut(lpAmountBurned); - -// Snapshot memory before; -// RemoveLiquidityAction memory action; - -// action.amounts = tokenAmountsOut; -// action.lpAmountIn = lpAmountBurned; -// action.recipient = user; -// action.fees = new uint256[](2); - -// (before, action) = beforeRemoveLiquidity(action); -// well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); -// afterRemoveLiquidity(before, action); -// checkInvariant(address(well)); -// } -// } +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { + StableSwap2 ss; + bytes constant data = ""; + uint256 constant addedLiquidity = 1000 * 1e18; + + function setUp() public { + ss = new StableSwap2(); + setupStableSwapWell(10); + + // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + } + + /// @dev ensure that Well liq was initialized correctly in {setUp} + /// currently, liquidity is added in {TestHelper} and above + function test_liquidityInitialized() public { + IERC20[] memory tokens = well.tokens(); + for (uint256 i; i < tokens.length; i++) { + assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); + } + assertEq(well.totalSupply(), 4000 * 1e18, "incorrect totalSupply"); + } + + /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying + /// since the tokens in the Well are balanced, user receives equal amounts + function test_getRemoveLiquidityOut() public { + uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); + for (uint256 i; i < tokens.length; i++) { + assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); + } + } + + /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token + function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { + uint256 lpAmountIn = 1000 * 1e18; + + uint256[] memory minTokenAmountsOut = new uint256[](2); + minTokenAmountsOut[0] = 501 * 1e18; // too high + minTokenAmountsOut[1] = 500 * 1e18; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 500 * 1e18, minTokenAmountsOut[0])); + well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); + } + + function test_removeLiquidity_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidity(0, new uint256[](2), user, block.timestamp - 1); + } + + /// @dev removeLiquidity: remove to equal amounts of underlying + function test_removeLiquidity() public prank(user) { + uint256 lpAmountIn = 1000 * 1e18; + + uint256[] memory amountsOut = new uint256[](2); + amountsOut[0] = 500 * 1e18; + amountsOut[1] = 500 * 1e18; + + Snapshot memory before; + RemoveLiquidityAction memory action; + + action.amounts = amountsOut; + action.lpAmountIn = lpAmountIn; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountIn, amountsOut, user, type(uint256).max); + afterRemoveLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, BALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { + // Setup amounts of liquidity to remove + // NOTE: amounts may or may not match the maximum removable by `user`. + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 0, 1000e18); + amounts[1] = amounts[0]; + + Snapshot memory before; + RemoveLiquidityAction memory action; + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + action.amounts = amounts; + action.lpAmountIn = lpAmountIn; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountIn, amounts, user, type(uint256).max); + afterRemoveLiquidity(before, action); + + assertLe( + well.totalSupply(), + StableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) + ); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal + /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` + /// before liquidity is removed by `user`. + function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + + uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; + lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); + imbalanceBias = bound(imbalanceBias, 0, 10e18); + + // `user2` performs a swap to imbalance the pool by `imbalanceBias` + vm.prank(user2); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + + // `user` has LP tokens and will perform a `removeLiquidity` call + vm.startPrank(user); + + uint256[] memory tokenAmountsOut = new uint256[](2); + tokenAmountsOut = well.getRemoveLiquidityOut(lpAmountBurned); + + Snapshot memory before; + RemoveLiquidityAction memory action; + + action.amounts = tokenAmountsOut; + action.lpAmountIn = lpAmountBurned; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); + afterRemoveLiquidity(before, action); + checkStableSwapInvariant(address(well)); + } +} From b30c0048e9974023d7e5c8ec2b9869ed2bfba996 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 30 Jun 2023 16:22:39 +0545 Subject: [PATCH 13/69] Add Liquidity --- test/stableSwap/Well.AddLiquidityStableSwap.t.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol index a27d4221..70a6fb59 100644 --- a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol @@ -8,7 +8,7 @@ import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} fr contract WellAddLiquidityStableSwapTest is LiquidityHelper { function setUp() public { - setupStableSwapWell(2); + setupStableSwapWell(10); } /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent @@ -40,7 +40,7 @@ contract WellAddLiquidityStableSwapTest is LiquidityHelper { amounts[1] = 0; uint256 amountOut = well.getAddLiquidityOut(amounts); - assertEq(amountOut, 9_995_024_785_725_378_226, "incorrect amt out"); + assertEq(amountOut, 9_998_815_419_300_522_901, "incorrect amt out"); } function test_addLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { From 616f33df5cd5cdc950f32e1446788950e61c807f Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 4 Jul 2023 12:24:52 +0545 Subject: [PATCH 14/69] mainnet gas comparisons --- src/functions/StableSwap2.sol | 17 +- test/integration/IntegrationTestHelper.sol | 34 ++- test/integration/interfaces/ICurve.sol | 79 ++++++ ...ntegrationTestGasComparisonsStableSwap.sol | 263 ++++++++++++++++++ ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 1 - 5 files changed, 385 insertions(+), 9 deletions(-) create mode 100644 test/integration/interfaces/ICurve.sol create mode 100644 test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol index a987839f..d6a3ce99 100644 --- a/src/functions/StableSwap2.sol +++ b/src/functions/StableSwap2.sol @@ -109,6 +109,8 @@ contract StableSwap2 is IWellFunction { * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) * x_1**2 + b*x_1 = c * x_1 = (x_1**2 + c) / (2*x_1 + b) + * @dev This function has a precision of +/- 1, + * which may round in favor of the well or the user. */ function calcReserve( uint[] memory reserves, @@ -134,9 +136,9 @@ contract StableSwap2 is IWellFunction { reserve = _calcReserve(reserve, b, c, lpTokenSupply); // Equality with the precision of 1 // scale reserve down to original precision - // TODO: discuss with pubs if(reserve > prevReserve){ if(reserve - prevReserve <= 1) return reserve.div(precisions[j]); + } else { if(prevReserve - reserve <= 1) return reserve.div(precisions[j]); } @@ -183,10 +185,15 @@ contract StableSwap2 is IWellFunction { WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); if (wfd.a == 0) revert InvalidAParameter(wfd.a); if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); - if(IERC20(wfd.tkn0).decimals() > 18) revert InvalidTokenDecimals(IERC20(wfd.tkn0).decimals()); + uint8 token0Dec = IERC20(wfd.tkn0).decimals(); + uint8 token1Dec = IERC20(wfd.tkn1).decimals(); + + if(token0Dec > 18) revert InvalidTokenDecimals(token0Dec); + if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); + Ann = wfd.a * N * N * A_PRECISION; - precisions[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn0).decimals())); - precisions[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(IERC20(wfd.tkn1).decimals())); + precisions[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); + precisions[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); } function getScaledReserves( @@ -204,8 +211,6 @@ contract StableSwap2 is IWellFunction { uint256 c, uint256 lpTokenSupply ) private pure returns (uint256){ - // NOTE: the result should be rounded up to ensure that the well benefits from the rounding error, - // rather than the user. return reserve .mul(reserve) .add(c) diff --git a/test/integration/IntegrationTestHelper.sol b/test/integration/IntegrationTestHelper.sol index 741264e4..9d836c2f 100644 --- a/test/integration/IntegrationTestHelper.sol +++ b/test/integration/IntegrationTestHelper.sol @@ -4,12 +4,11 @@ pragma solidity ^0.8.17; import {Test, console, stdError} from "forge-std/Test.sol"; import {Well, Call, IERC20} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; -import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {GeoEmaAndCumSmaPump} from "src/pumps/GeoEmaAndCumSmaPump.sol"; import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; import {Users} from "test/helpers/Users.sol"; -import {TestHelper, Balances} from "test/TestHelper.sol"; +import {TestHelper, Balances, ConstantProduct2, StableSwap2} from "test/TestHelper.sol"; import {from18, to18} from "test/pumps/PumpHelpers.sol"; abstract contract IntegrationTestHelper is TestHelper { @@ -25,6 +24,37 @@ abstract contract IntegrationTestHelper is TestHelper { return setupWell(_tokens, Call(address(new ConstantProduct2()), new bytes(0)), _pumps, _well); } + function setupStableSwapWell( + uint256 a, + IERC20[] memory _tokens, + Well _well + ) internal returns (Well) { + Call[] memory _pumps = new Call[](1); + _pumps[0] = Call( + address(new GeoEmaAndCumSmaPump( + from18(0.5e18), + from18(0.333333333333333333e18), + 12, + from18(0.9e18) + )), + new bytes(0) + ); + + bytes memory data = abi.encode( + StableSwap2.WellFunctionData( + a, + address(_tokens[0]), + address(_tokens[1]) + ) + ); + return setupWell( + _tokens, + Call(address(new StableSwap2()), data), + _pumps, + _well + ); + } + function setupWell( IERC20[] memory _tokens, Call memory _function, diff --git a/test/integration/interfaces/ICurve.sol b/test/integration/interfaces/ICurve.sol new file mode 100644 index 00000000..e46dae32 --- /dev/null +++ b/test/integration/interfaces/ICurve.sol @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT +pragma experimental ABIEncoderV2; +pragma solidity 0.8.17; + +interface ICurvePool { + function A_precise() external view returns (uint256); + function get_balances() external view returns (uint256[2] memory); + function totalSupply() external view returns (uint256); + function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external returns (uint256); + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external returns (uint256); + function balances(int128 i) external view returns (uint256); + function fee() external view returns (uint256); + function coins(uint256 i) external view returns (address); + function get_virtual_price() external view returns (uint256); + function calc_token_amount(uint256[2] calldata amounts, bool deposit) external view returns (uint256); + function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256); + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); + function transfer(address recipient, uint256 amount) external returns (bool); +} + +interface ICurveZap { + function add_liquidity(address _pool, uint256[4] memory _deposit_amounts, uint256 _min_mint_amount) external returns (uint256); + function calc_token_amount(address _pool, uint256[4] memory _amounts, bool _is_deposit) external returns (uint256); +} + +interface ICurvePoolR { + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver) external returns (uint256); + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver) external returns (uint256); + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount, address receiver) external returns (uint256); +} + +interface ICurvePool2R { + function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); + function remove_liquidity(uint256 _burn_amount, uint256[2] memory _min_amounts, address reciever) external returns (uint256[2] calldata); + function remove_liquidity_imbalance(uint256[2] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); +} + +interface ICurvePool3R { + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); + function remove_liquidity(uint256 _burn_amount, uint256[3] memory _min_amounts, address reciever) external returns (uint256[3] calldata); + function remove_liquidity_imbalance(uint256[3] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); +} + +interface ICurvePool4R { + function add_liquidity(uint256[4] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); + function remove_liquidity(uint256 _burn_amount, uint256[4] memory _min_amounts, address reciever) external returns (uint256[4] calldata); + function remove_liquidity_imbalance(uint256[4] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); +} + +interface I3Curve { + function get_virtual_price() external view returns (uint256); +} + +interface ICurveFactory { + function get_coins(address _pool) external view returns (address[4] calldata); + function get_underlying_coins(address _pool) external view returns (address[8] calldata); +} + +interface ICurveCryptoFactory { + function get_coins(address _pool) external view returns (address[8] calldata); +} + +interface ICurvePoolC { + function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256); +} + +interface ICurvePoolNoReturn { + function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external; + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; + function remove_liquidity(uint256 _burn_amount, uint256[3] memory _min_amounts) external; + function remove_liquidity_imbalance(uint256[3] memory _amounts, uint256 _max_burn_amount) external; + function remove_liquidity_one_coin(uint256 _token_amount, uint256 i, uint256 min_amount) external; +} + +interface ICurvePoolNoReturn128 { + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; +} diff --git a/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol b/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol new file mode 100644 index 00000000..6437e679 --- /dev/null +++ b/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol @@ -0,0 +1,263 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.17; + +import {IntegrationTestHelper, IERC20, console, Balances} from "test/integration/IntegrationTestHelper.sol"; +import {ICurvePool, ICurveZap} from "test/integration/interfaces/ICurve.sol"; +import {StableSwap2} from "test/TestHelper.sol"; +import {IPipeline, PipeCall, AdvancedPipeCall, IDepot, From, To} from "test/integration/interfaces/IPipeline.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; +import {Well} from "src/Well.sol"; + +/// @dev Tests gas usage of similar functions across Curve & Wells +contract IntegrationTestGasComparisonsStableSwap is IntegrationTestHelper { + using LibMath for uint256; + + uint256 mainnetFork; + + Well daiBeanWell; + Well daiUsdcWell; + StableSwap2 ss; + bytes data = ""; + + ICurvePool bean3Crv = ICurvePool(0xc9C32cd16Bf7eFB85Ff14e0c8603cc90F6F2eE49); + ICurveZap zap = ICurveZap(0xA79828DF1850E8a3A3064576f380D90aECDD3359); + + IERC20 constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); + IERC20 constant BEAN = IERC20(0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab); + IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); + IERC20 constant THREE3CRV = IERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); + + + IPipeline pipeline = IPipeline(0xb1bE0000bFdcDDc92A8290202830C4Ef689dCeaa); + IDepot depot = IDepot(0xDEb0f000082fD56C10f449d4f8497682494da84D); + + IERC20[] daiBeanTokens = [DAI, BEAN]; + IERC20[] daiUsdcTokens = [DAI, USDC]; + + + /// @dev pausing gas metrer + function setUp() public { + ss = new StableSwap2(); + + mainnetFork = vm.createSelectFork("mainnet"); + assertEq(vm.activeFork(), mainnetFork); + + // Test contract has 5 * {TestHelper.initialLiquidity}, with an A parameter of 10. + daiBeanWell = Well(setupStableSwapWell(10, daiBeanTokens, daiBeanWell)); + daiUsdcWell = Well(setupStableSwapWell(10, daiUsdcTokens, daiUsdcWell)); + + _wellsInitializedHelper(); + } + + /// @dev Notes on fair comparison: + /// + /// 1. Gas will be dependent on input/output amount if the user's balance or the + /// pool's balance move from zero to non-zero during execution. For example, + /// if the user has no DAI and swaps from BEAN->DAI, extra gas cost is incurred + /// to set their DAI balance from 0 to non-zero. + /// + /// 2. Believe that some tokens don't decrement allowances if infinity is approved. + /// Make sure that approval amounts are the same for each test. + /// + /// 3. The first few swaps in a new Well with a Pump attached will be more expensive, + /// as the Pump will need to be initialized. Perform several swaps before testing + /// to ensure we're at steady-state gas cost. + + //////////////////// COMPARE: BEAN/DAI //////////////////// + + ////////// Wells + + function testFuzz_wells_BeanDai_Swap(uint256 amountIn) public { + vm.pauseGasMetering(); + amountIn = bound(amountIn, 1e18, daiBeanTokens[1].balanceOf(address(this))); + vm.resumeGasMetering(); + + daiBeanWell.swapFrom(daiBeanTokens[1], daiBeanTokens[0], amountIn, 0, address(this), type(uint256).max); + } + + function testFuzz_wells_BeanDaiUsdc_Swap(uint256 amountIn) public { + vm.pauseGasMetering(); + amountIn = bound(amountIn, 1e18, 1000e18); + + BEAN.approve(address(depot), type(uint256).max); + + // any user can approve pipeline for an arbritary set of assets. + // this means that most users do not need to approve pipeline, + // unless this is the first instance of the token being used. + // the increased risk in max approving all assets within pipeline is small, + // as any user can approve any contract to use the asset within pipeline. + PipeCall[] memory _prePipeCall = new PipeCall[](2); + + // Approval transactions are done prior as pipeline is likley to have apporvals for popular + // tokens done already, and this will save gas. However, if the user has not approved pipeline + // they can check this off-chain, and decide to do the approval themselves. + + // Approve DAI:BEAN Well to use pipeline's BEAN + _prePipeCall[0].target = address(BEAN); + _prePipeCall[0].data = abi.encodeWithSelector(BEAN.approve.selector, address(daiBeanWell), type(uint256).max); + + // Approve DAI:USDC Well to use pipeline's DAI + _prePipeCall[1].target = address(DAI); + _prePipeCall[1].data = abi.encodeWithSelector(DAI.approve.selector, address(daiUsdcWell), type(uint256).max); + + pipeline.multiPipe(_prePipeCall); + + AdvancedPipeCall[] memory _pipeCall = new AdvancedPipeCall[](2); + + // Swap BEAN for DAI + _pipeCall[0].target = address(daiBeanWell); + _pipeCall[0].callData = abi.encodeWithSelector( + Well.swapFrom.selector, daiBeanTokens[1], daiBeanTokens[0], amountIn, 0, address(pipeline) + ); + _pipeCall[0].clipboard = abi.encodePacked(uint256(0)); + + // Swap DAI for USDC + _pipeCall[1].target = address(daiUsdcWell); + _pipeCall[1].callData = + abi.encodeWithSelector(Well.swapFrom.selector, daiUsdcTokens[0], daiUsdcTokens[1], 0, 0, address(this)); + _pipeCall[1].clipboard = clipboardHelper(false, 0, ClipboardType.singlePaste, 1, 0, 2); + + bytes[] memory _farmCalls = new bytes[](2); + _farmCalls[0] = abi.encodeWithSelector( + depot.transferToken.selector, BEAN, address(pipeline), amountIn, From.EXTERNAL, To.EXTERNAL + ); + _farmCalls[1] = abi.encodeWithSelector(depot.advancedPipe.selector, _pipeCall, 0); + + vm.resumeGasMetering(); + depot.farm(_farmCalls); + } + + function testFuzz_wells_BeanDaiUsdc_Shift(uint256 amountIn) public { + vm.pauseGasMetering(); + amountIn = bound(amountIn, 1e18, 1000e18); + + BEAN.approve(address(depot), type(uint256).max); + + // unlike swap test (previous test), no tokens are sent back to pipeline. + // this means that pipeline does not prior approvals. + + AdvancedPipeCall[] memory _pipeCall = new AdvancedPipeCall[](2); + + // Shift excess tokens into DAI; deliver to the DAI:USDC Well + _pipeCall[0].target = address(daiBeanWell); + _pipeCall[0].callData = abi.encodeWithSelector(Well.shift.selector, DAI, 0, address(daiUsdcWell)); + _pipeCall[0].clipboard = abi.encodePacked(uint256(0)); + + // Shift excess tokens into USDC; deliver to the user + _pipeCall[1].target = address(daiUsdcWell); + _pipeCall[1].callData = abi.encodeWithSelector(Well.shift.selector, daiUsdcTokens[1], 0, address(this)); + _pipeCall[1].clipboard = abi.encodePacked(uint256(0)); + + // Send BEAN directly to the DAI:BEAN Well, then perform the Pipe calls above. + bytes[] memory _farmCalls = new bytes[](2); + _farmCalls[0] = abi.encodeWithSelector( + depot.transferToken.selector, BEAN, address(daiBeanWell), amountIn, From.EXTERNAL, To.EXTERNAL + ); + _farmCalls[1] = abi.encodeWithSelector(depot.advancedPipe.selector, _pipeCall, 0); + + vm.resumeGasMetering(); + depot.farm(_farmCalls); + } + + function testFuzz_wells_BeanDai_AddLiquidity(uint256 amount) public { + vm.pauseGasMetering(); + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(amount, 1e18, 1000e18); + amounts[1] = bound(amount, 1e18, 1000e18); + vm.resumeGasMetering(); + + daiBeanWell.addLiquidity(amounts, 0, address(this), type(uint256).max); + } + + function testFuzz_wells_BeanDai_RemoveLiquidity(uint256 amount) public { + vm.pauseGasMetering(); + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(amount, 1e18, 1000e18); + amounts[1] = amounts[0]; + uint256 lp = daiBeanWell.addLiquidity(amounts, 0, address(this), type(uint256).max); + uint256[] memory minAmountsOut = new uint256[](2); + vm.resumeGasMetering(); + + daiBeanWell.removeLiquidity(lp, minAmountsOut, address(this), type(uint256).max); + } + + ////////// Curve + + function testFuzz_curve_BeanDai_Swap(uint256 amount) public { + vm.pauseGasMetering(); + vm.assume(amount > 0); + amount = bound(amount, 1e18, 1000 * 1e18); + _curveSetupHelper(amount); + + int128 i = 0; // from bean + int128 j = 1; // to dai + + vm.resumeGasMetering(); + + bean3Crv.exchange_underlying(i, j, amount, 0); + } + + + function testFuzz_curve_AddLiquidity(uint256 amount) public { + vm.pauseGasMetering(); + uint256[2] memory amounts; + amount = bound(amount, 1e18, 1000 * 1e18); + amounts[0] = 0; + amounts[1] = amount; + + _curveSetupHelper(amount); + vm.resumeGasMetering(); + + bean3Crv.add_liquidity(amounts, 0); + } + + function testFuzz_curve_BeanDai_RemoveLiquidity(uint256 amount) public { + vm.pauseGasMetering(); + uint256[2] memory amounts; + amount = bound(amount, 1e18, 1000 * 1e18); + amounts[0] = 0; + amounts[1] = amount; + + _curveSetupHelper(amount); + uint256 liquidity = bean3Crv.add_liquidity(amounts, 0); + vm.resumeGasMetering(); + + bean3Crv.remove_liquidity_one_coin(liquidity, 0, 0); + } + + //////////////////// SETUP HELPERS //////////////////// + + /// @dev Approve the `router` to swap Test contract's tokens. + function _curveSetupHelper(uint256 amount) private { + deal(address(BEAN), address(this), amount * 2); + deal(address(DAI), address(this), amount * 2); + deal(address(THREE3CRV), address(this), amount * 2); + + BEAN.approve(address(bean3Crv), type(uint256).max); + DAI.approve(address(bean3Crv), type(uint256).max); + THREE3CRV.approve(address(bean3Crv), type(uint256).max); + + BEAN.approve(address(zap), type(uint256).max); + DAI.approve(address(zap), type(uint256).max); + THREE3CRV.approve(address(zap), type(uint256).max); + } + + /// @dev Perform a few swaps on the provided Well to proper initialization. + function _wellsInitializedHelper() private { + // DAI -> BEAN + daiBeanWell.swapFrom( + daiBeanTokens[0], daiBeanTokens[1], 1000 * 1e18, 500 * 1e18, address(this), type(uint256).max + ); + + // BEAN -> DAI + vm.warp(block.timestamp + 1); + daiBeanWell.swapFrom( + daiBeanTokens[1], daiBeanTokens[0], 500 * 1e18, 500 * 1e18, address(this), type(uint256).max + ); + } +} + +interface IBEAN is IERC20 { + function deposit() external payable; + function withdraw(uint256 amount) external; +} diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index 1504b1ac..25f45deb 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -83,7 +83,6 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - // TODO: well function gives the rounding error to the user instead of the Well function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { // Assume we're removing tokens[0] uint256[] memory amounts = new uint256[](2); From e0a7faac6da264e66074536286784585e21b8646 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 4 Jul 2023 12:26:08 +0545 Subject: [PATCH 15/69] beanstalk stableswap decimal error --- src/functions/BeanstalkStableSwap.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol index e20a9204..9dae46a2 100644 --- a/src/functions/BeanstalkStableSwap.sol +++ b/src/functions/BeanstalkStableSwap.sol @@ -81,15 +81,15 @@ contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { uint256 a; try BEANSTALK.getBeanstalkA() returns (uint256 _a) { a = _a; - } catch { + } catch { a = wfd.a; } if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); uint8 token0Dec = IERC20(wfd.tkn0).decimals(); - uint8 token1Dec = IERC20(wfd.tkn0).decimals(); + uint8 token1Dec = IERC20(wfd.tkn1).decimals(); if(token0Dec > 18) revert InvalidTokenDecimals(token0Dec); - if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); + if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); Ann = a * N * N * A_PRECISION; precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); From 0098c7238bf3ef9e8c487597e68accd832487f29 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 14 Jul 2023 12:40:26 +0545 Subject: [PATCH 16/69] omit calcReserveAtRatio (TODO) --- src/functions/BeanstalkStableSwap.sol | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol index 9dae46a2..05818fad 100644 --- a/src/functions/BeanstalkStableSwap.sol +++ b/src/functions/BeanstalkStableSwap.sol @@ -34,12 +34,21 @@ contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { uint256[] calldata ratios, bytes calldata data ) external view returns (uint256 reserve){ - uint256[] memory _reserves = new uint256[](2); - _reserves[0] = reserves[0].mul(ratios[0]).div(PRECISION); - _reserves[1] = reserves[1].mul(ratios[1]).div(PRECISION); - // uint256 oldD = calcLpTokenSupply(reserves, data) / 2; - uint256 newD = calcLpTokenSupply(_reserves, data); - return newD / 2; + // uint256 i = j == 1 ? 0 : 1; + // uint256 D = calcLpTokenSupply(reserves, data); + // uint256 initalPegBeanPeg = D/2; + // uint256 currentBeans = reserves[i]; + // uint256 currentBeans = reserves[i]; + + + // uint256[] memory _reserves = new uint256[](2); + // uint256 i = j == 1 ? 0 : 1; + // uint256 targetPrice = ratios[j]/ratios[i]; + // _reserves[0] = reserves[0].mul(ratios[0]).div(PRECISION); + // _reserves[1] = reserves[1].mul(ratios[1]).div(PRECISION); + // // uint256 oldD = calcLpTokenSupply(reserves, data) / 2; + // uint256 newD = calcLpTokenSupply(_reserves, data); + // return newD / 2; } // TODO: for converts From ca995d75695cfb4f69da635879a32c6e5823f36e Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 7 May 2024 11:16:27 +0300 Subject: [PATCH 17/69] Add upgrade changes --- .gitignore | 2 + mocks/wells/MockWellUpgradeable.sol | 14 ++ script/helpers/WellDeployer.sol | 27 +++ src/WellUpgradeable.sol | 69 +++++++ .../LibWellUpgradeableConstructor.sol | 88 +++++++++ test/WellUpgradeable.t.sol | 185 ++++++++++++++++++ 6 files changed, 385 insertions(+) create mode 100644 mocks/wells/MockWellUpgradeable.sol create mode 100644 src/WellUpgradeable.sol create mode 100644 src/libraries/LibWellUpgradeableConstructor.sol create mode 100644 test/WellUpgradeable.t.sol diff --git a/.gitignore b/.gitignore index e3a12683..ff8f73d9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ cache/ out/ +.vscode + # Ignores development broadcast logs /broadcast diff --git a/mocks/wells/MockWellUpgradeable.sol b/mocks/wells/MockWellUpgradeable.sol new file mode 100644 index 00000000..6e6513e0 --- /dev/null +++ b/mocks/wells/MockWellUpgradeable.sol @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {WellUpgradeable} from "src/WellUpgradeable.sol"; + +// this needs to be here for upgrade checks +/// @custom:oz-upgrades-from WellUpgradeable +contract MockWellUpgradeable is WellUpgradeable { + + function getVersion(uint256 i) external pure returns (uint256) { + return i; + } +} \ No newline at end of file diff --git a/script/helpers/WellDeployer.sol b/script/helpers/WellDeployer.sol index 8a42e3d2..dbaa3c4d 100644 --- a/script/helpers/WellDeployer.sol +++ b/script/helpers/WellDeployer.sol @@ -5,6 +5,8 @@ import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; import {LibWellConstructor} from "src/libraries/LibWellConstructor.sol"; import {Well, Call, IERC20} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; +import {WellUpgradeable} from "src/WellUpgradeable.sol"; +import {LibWellUpgradeableConstructor} from "src/libraries/LibWellUpgradeableConstructor.sol"; abstract contract WellDeployer { /** @@ -28,4 +30,29 @@ abstract contract WellDeployer { LibWellConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps); _well = Well(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); } + + + /** + * @notice Encode the Well's immutable data, and deploys the well. Modified for upgradeable wells. + * @param _aquifer The address of the Aquifer which will deploy this Well. + * @param _wellImplementation The address of the Well implementation. + * @param _tokens A list of ERC20 tokens supported by the Well. + * @param _wellFunction A single Call struct representing a call to the Well Function. + * @param _pumps An array of Call structs representings calls to Pumps. + * @param _salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}. + * @param owner The address of the owner of the Well. + */ + function encodeAndBoreWellUpgradeable( + address _aquifer, + address _wellImplementation, + IERC20[] memory _tokens, + Call memory _wellFunction, + Call[] memory _pumps, + bytes32 _salt, + address owner + ) internal returns (WellUpgradeable _well) { + (bytes memory immutableData, bytes memory initData) = + LibWellUpgradeableConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps, owner); + _well = WellUpgradeable(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); + } } diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol new file mode 100644 index 00000000..bf481410 --- /dev/null +++ b/src/WellUpgradeable.sol @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Well} from "src/Well.sol"; +import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol"; +import {OwnableUpgradeable} from "ozu/access/OwnableUpgradeable.sol"; +import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; + + +/** + * @title Well + * @author Publius, Silo Chad, Brean, Deadmanwalking + * @dev A Well is a constant function AMM allowing the provisioning of liquidity + * into a single pooled on-chain liquidity position. + * + * Rebasing Tokens: + * - Positive rebasing tokens are supported by Wells, but any tokens recieved from a + * rebase will not be rewarded to LP holders and instead can be extracted by anyone + * using `skim`, `sync` or `shift`. + * - Negative rebasing tokens should not be used in Well as the effect of a negative + * rebase will be realized by users interacting with the Well, not LP token holders. + * + * Fee on Tranfer (FoT) Tokens: + * - When transferring fee on transfer tokens to a Well (swapping from or adding liquidity), + * use `swapFromFeeOnTrasfer` or `addLiquidityFeeOnTransfer`. `swapTo` does not support + * fee on transfer tokens (See {swapTo}). + * - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity), + * INCLUDE the fee that is taken on transfer when calculating amount out values. + */ +contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { + + /** + * Perform an upgrade of an ERC1967Proxy, when this contract. + * is set as the implementation behind such a proxy. + * The _authorizeUpgrade function must be overridden. + * to include access restriction to the upgrade mechanism. + */ + function _authorizeUpgrade(address) internal override onlyOwner {} + + function init(string memory _name, string memory _symbol, address owner) external initializer { + // owner of well param as the aquifier address will be the owner initially + // ownable init transfers ownership to msg.sender + __ERC20Permit_init(_name); + __ERC20_init(_name, _symbol); + __ReentrancyGuard_init(); + __UUPSUpgradeable_init(); + // first time this is called the owner will be the msg.sender + // which is the aquifer that bore the well + __Ownable_init(); + // then ownership can be transfered to the wanted address + // note: to init owner with __Ownable_init(ownerAddress); we would need to adjust the lib code + transferOwnership(owner); + + IERC20[] memory _tokens = tokens(); + uint256 tokensLength = _tokens.length; + for (uint256 i; i < tokensLength - 1; ++i) { + for (uint256 j = i + 1; j < tokensLength; ++j) { + if (_tokens[i] == _tokens[j]) { + revert DuplicateTokens(_tokens[i]); + } + } + } + } + + function getVersion() external virtual pure returns (uint256) { + return 1; + } +} \ No newline at end of file diff --git a/src/libraries/LibWellUpgradeableConstructor.sol b/src/libraries/LibWellUpgradeableConstructor.sol new file mode 100644 index 00000000..1103c6c8 --- /dev/null +++ b/src/libraries/LibWellUpgradeableConstructor.sol @@ -0,0 +1,88 @@ +// SPDX-License-Identifier: MIT +// forgefmt: disable-start + +pragma solidity ^0.8.20; + +import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; +import {Call, IERC20} from "src/Well.sol"; + +library LibWellUpgradeableConstructor { + + /** + * @notice Encode the Well's immutable data and init data with an owner. + */ + function encodeWellDeploymentData( + address _aquifer, + IERC20[] memory _tokens, + Call memory _wellFunction, + Call[] memory _pumps, + address owner + ) internal view returns (bytes memory immutableData, bytes memory initData) { + immutableData = encodeWellImmutableData(_aquifer, _tokens, _wellFunction, _pumps); + initData = encodeWellInitFunctionCall(_tokens, _wellFunction, owner); + } + + /** + * @notice Encode the Well's immutable data. + * @param _aquifer The address of the Aquifer which will deploy this Well. + * @param _tokens A list of ERC20 tokens supported by the Well. + * @param _wellFunction A single Call struct representing a call to the Well Function. + * @param _pumps An array of Call structs representings calls to Pumps. + * @dev `immutableData` is tightly packed, however since `_tokens` itself is + * an array, each address in the array will be padded up to 32 bytes. + * + * Arbitrary-length bytes are applied to the end of the encoded bytes array + * for easy reading of statically-sized data. + * + */ + function encodeWellImmutableData( + address _aquifer, + IERC20[] memory _tokens, + Call memory _wellFunction, + Call[] memory _pumps + ) internal pure returns (bytes memory immutableData) { + + immutableData = abi.encodePacked( + _aquifer, // aquifer address + _tokens.length, // number of tokens + _wellFunction.target, // well function address + _wellFunction.data.length, // well function data length + _pumps.length, // number of pumps + _tokens, // tokens array + _wellFunction.data // well function data (bytes) + ); + for (uint256 i; i < _pumps.length; ++i) { + immutableData = abi.encodePacked( + immutableData, // previously packed pumps + _pumps[i].target, // pump address + _pumps[i].data.length, // pump data length + _pumps[i].data // pump data (bytes) + ); + } + } + + function encodeWellInitFunctionCall( + IERC20[] memory _tokens, + Call memory _wellFunction, + address owner + ) public view returns (bytes memory initFunctionCall) { + string memory name = LibContractInfo.getSymbol(address(_tokens[0])); + string memory symbol = name; + for (uint256 i = 1; i < _tokens.length; ++i) { + name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i]))); + symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i]))); + } + name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Well"); + symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "w"); + + // See {Well.init}. + initFunctionCall = abi.encodeWithSignature("init(string,string,address)", name, symbol, owner); + } + + /** + * @notice Encode a Call struct representing an arbitrary call to `target` with additional data `data`. + */ + function encodeCall(address target, bytes memory data) public pure returns (Call memory) { + return Call(target, data); + } +} \ No newline at end of file diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol new file mode 100644 index 00000000..3c586059 --- /dev/null +++ b/test/WellUpgradeable.t.sol @@ -0,0 +1,185 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {Test, console} from "forge-std/Test.sol"; +import {WellUpgradeable} from "src/WellUpgradeable.sol"; +import {IERC20} from "test/TestHelper.sol"; +import {WellDeployer} from "script/helpers/WellDeployer.sol"; +import {MockPump} from "mocks/pumps/MockPump.sol"; +import {Well, Call, IWellFunction, IPump, IERC20} from "src/Well.sol"; +import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; +import {Aquifer} from "src/Aquifer.sol"; +import {WellDeployer} from "script/helpers/WellDeployer.sol"; +import {LibWellUpgradeableConstructor} from "src/libraries/LibWellUpgradeableConstructor.sol"; +import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; +import {MockToken} from "mocks/tokens/MockToken.sol"; +import {WellDeployer} from "script/helpers/WellDeployer.sol"; +import {ERC1967Proxy} from "oz/proxy/ERC1967/ERC1967Proxy.sol"; +import {MockWellUpgradeable} from "mocks/wells/MockWellUpgradeable.sol"; + +contract WellUpgradeTest is Test, WellDeployer { + + address proxyAddress; + address aquifer; + address initialOwner; + address user; + address mockPumpAddress; + address wellFunctionAddress; + address token1Address; + address token2Address; + + function setUp() public { + + // Tokens + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = new MockToken("BEAN", "BEAN", 6); + tokens[1] = new MockToken("WETH", "WETH", 18); + + token1Address = address(tokens[0]); + token2Address = address(tokens[1]); + + user = makeAddr("user"); + + // Mint tokens + MockToken(address(tokens[0])).mint(user, 10000000000000000); + MockToken(address(tokens[1])).mint(user, 10000000000000000); + // Well Function + IWellFunction cp2 = new ConstantProduct2(); + wellFunctionAddress = address(cp2); + Call memory wellFunction = Call(address(cp2), abi.encode("beanstalkFunction")); + + // Pump + IPump mockPump = new MockPump(); + mockPumpAddress = address(mockPump); + Call[] memory pumps = new Call[](1); + // init new mock pump with "beanstalk" data + pumps[0] = Call(address(mockPump), abi.encode("beanstalkPump")); + aquifer = address(new Aquifer()); + address wellImplementation = address(new WellUpgradeable()); + initialOwner = makeAddr("owner"); + + // Well + WellUpgradeable well = encodeAndBoreWellUpgradeable(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0), initialOwner); + + // Sum up of what is going on here + // We encode and bore a well upgradeable from the aquifer + // The well upgradeable additionally takes in an owner address so we modify the init function call + // to include the owner address. + // When the new well is deployed, all init data are stored in the implementation storage + // including pump and well function data --> NOTE: This could be an issue but how do we solve this? + // Then we deploy a ERC1967Proxy proxy for the well upgradeable and call the init function on the proxy + // When we deploy the proxy, the init data is stored in the proxy storage and the well is initialized + // for the second time. We can now control the well via delegate calls to the proxy address. + + // Every time we call the init function, we init the owner to be the msg.sender and + // then immidiately transfer ownership + // to an address of our choice (see WellUpgradeable.sol for more details on the init function) + + // FROM OZ + // If _data is nonempty, it’s used as data in a delegate call to _logic. + // This will typically be an encoded function call, and allows initializing + // the storage of the proxy like a Solidity constructor. + + // Deploy Proxy + ERC1967Proxy proxy = new ERC1967Proxy( + address(well), // implementation address + // init data (name, symbol, owner) ); + abi.encodeCall(WellUpgradeable.init, ("WELL", "WELL", initialOwner)) // _data + ); + proxyAddress = address(proxy); + + vm.startPrank(user); + tokens[0].approve(proxyAddress, type(uint256).max); + tokens[1].approve(proxyAddress, type(uint256).max); + vm.stopPrank(); + + } + + ///////////////////// Storage Tests ///////////////////// + + function testProxyGetAquifer() public { + assertEq(address(aquifer), WellUpgradeable(proxyAddress).aquifer()); + } + + function testProxyGetPump() public { + Call[] memory proxyPumps = WellUpgradeable(proxyAddress).pumps(); + assertEq(mockPumpAddress, proxyPumps[0].target); + // this passes but why? Pump data are supposed + // to be stored in the implementation storage from the borewell call + assertEq(abi.encode("beanstalkPump"), proxyPumps[0].data); + } + + function testProxyGetTokens() public { + IERC20[] memory proxyTokens = WellUpgradeable(proxyAddress).tokens(); + assertEq(address(proxyTokens[0]), token1Address); + assertEq(address(proxyTokens[1]), token2Address); + } + + function testProxyGetWellFunction() public { + Call memory proxyWellFunction = WellUpgradeable(proxyAddress).wellFunction(); + assertEq(address(proxyWellFunction.target), address(wellFunctionAddress)); + assertEq(proxyWellFunction.data, abi.encode("beanstalkFunction")); + } + + function testProxyGetSymbolInStorage() public { + assertEq("WELL", WellUpgradeable(proxyAddress).symbol()); + } + + function testProxyInitVersion() public { + uint256 expectedVersion = 1; + assertEq(expectedVersion, WellUpgradeable(proxyAddress).getVersion()); + } + + function testProxyNumTokens() public { + uint256 expectedNumTokens = 2; + assertEq(expectedNumTokens, WellUpgradeable(proxyAddress).numberOfTokens()); + } + + ///////////////// Interaction test ////////////////// + + function testProxyAddLiquidity() public { + vm.startPrank(user); + uint256[] memory amounts = new uint256[](2); + amounts[0] = 1000; + amounts[1] = 1000; + WellUpgradeable(proxyAddress).addLiquidity(amounts, 0 , user, type(uint256).max); + assertEq(amounts, WellUpgradeable(proxyAddress).getReserves()); + vm.stopPrank(); + } + + ////////////// Ownership Tests ////////////// + + function testProxyOwner() public { + assertEq(initialOwner, WellUpgradeable(proxyAddress).owner()); + } + + function testProxyTransferOwnership() public { + vm.prank(initialOwner); + address newOwner = makeAddr("newOwner"); + WellUpgradeable(proxyAddress).transferOwnership(newOwner); + assertEq(newOwner, WellUpgradeable(proxyAddress).owner()); + } + + function testRevertTransferOwnershipFromNotOnwer() public { + vm.expectRevert(); + address notOwner = makeAddr("notOwner"); + vm.prank(notOwner); + WellUpgradeable(proxyAddress).transferOwnership(notOwner); + } + + ////////////////////// Upgrade Tests ////////////////////// + + function testUpgradeToNewImplementation() public { + vm.startPrank(initialOwner); + WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); + MockWellUpgradeable newImpl = new MockWellUpgradeable(); + // getting error due to the onlyProxy modifier in UUPSUpgradeable.sol + // commented this out for now in UUPSUpgradeable.sol + // require(_getImplementation() == __self, "Function must be called through active proxy"); + proxy.upgradeTo(address(newImpl)); + assertEq(initialOwner, MockWellUpgradeable(proxyAddress).owner()); + vm.stopPrank(); + } + +} From 37b942b5967e1c4e822c7f8fe085bbd5b03ab975 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 16 May 2024 14:57:11 -0500 Subject: [PATCH 18/69] Update wellUpgradable Implementation --- foundry.toml | 2 +- src/WellUpgradeable.sol | 74 +++++++++++++++++++++++++++++++++++------ 2 files changed, 65 insertions(+), 11 deletions(-) diff --git a/foundry.toml b/foundry.toml index f4080ba9..fc84913c 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = 'out' libs = ['lib', 'node_modules'] fuzz = { runs = 256 } optimizer = true -optimizer_runs = 800 +optimizer_runs = 400 remappings = [ '@openzeppelin/=node_modules/@openzeppelin/', ] diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index bf481410..462211c3 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -6,8 +6,7 @@ import {Well} from "src/Well.sol"; import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "ozu/access/OwnableUpgradeable.sol"; import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; - - +import {IAquifer} from "src/interfaces/IAquifer.sol"; /** * @title Well * @author Publius, Silo Chad, Brean, Deadmanwalking @@ -29,14 +28,8 @@ import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; * INCLUDE the fee that is taken on transfer when calculating amount out values. */ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { - - /** - * Perform an upgrade of an ERC1967Proxy, when this contract. - * is set as the implementation behind such a proxy. - * The _authorizeUpgrade function must be overridden. - * to include access restriction to the upgrade mechanism. - */ - function _authorizeUpgrade(address) internal override onlyOwner {} + + address private immutable ___self = address(this); function init(string memory _name, string memory _symbol, address owner) external initializer { // owner of well param as the aquifier address will be the owner initially @@ -66,4 +59,65 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { function getVersion() external virtual pure returns (uint256) { return 1; } + + // Wells deployed by aquifers use the EIP-1167 minimal proxy pattern for gas-efficent deployments. + // This pattern breaks the UUPS upgrade pattern, as the `__self` variable is set to the initial well implmentation. + // `_authorizeUpgrade` and `upgradeTo` are modified to allow for upgrades to the Well implementation. + // verification is done by verifying the ERC1967 implmentation (the well address) maps to the aquifers well -> implmentation mapping. + + /** + * @notice Check that the execution is being performed through a delegatecall call and that the execution context is + * a proxy contract with an ERC1167 minimal proxy from an aquifier, pointing to a well implmentation. + */ + function _authorizeUpgrade(address newImplmentation) internal view override { + // verify the function is called through a delegatecall. + require(address(this) != ___self, "Function must be called through delegatecall"); + + // verify the function is called through an active proxy bored by an aquifer. + address activeProxy = IAquifer(aquifer()).wellImplementation(_getImplementation()); + require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer"); + + // verify the new implmentation is a well bored by an aquifier. + address aquifer = Well(newImplmentation).aquifer(); + require( + IAquifer(aquifer).wellImplementation(newImplmentation) != address(0), + "New implementation must be a well implmentation" + ); + + // verify the new implmentation is a valid ERC-1967 implmentation. + require( + UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT, + "New implementation must be a valid ERC-1967 implmentation" + ); + } + + /** + * @notice Upgrade the implementation of the proxy to `newImplementation`. + * @dev replaces 'onlyProxy' with `_authorizeUpgrade` restriction. + * + * Calls {_authorizeUpgrade}. + * + * Emits an {Upgraded} event. + * + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function upgradeTo(address newImplementation) public override { + _authorizeUpgrade(newImplementation); + _upgradeToAndCallUUPS(newImplementation, new bytes(0), false); + } + + /** + * @notice Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call + * encoded in `data`. + * + * Calls {_authorizeUpgrade}. + * + * Emits an {Upgraded} event. + * + * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + */ + function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { + _authorizeUpgrade(newImplementation); + _upgradeToAndCallUUPS(newImplementation, data, true); + } } \ No newline at end of file From aa30e6b74b2a2aeb7cc278924484371e6917af6e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sat, 18 May 2024 18:19:40 -0500 Subject: [PATCH 19/69] update package. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f25e0513..2dfa925c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.1.0-prerelease0", + "version": "1.2.0-prerelease0", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { From dd66d36cd70679b8b394ec76a25845a678c612da Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 18 Jun 2024 15:23:47 -0400 Subject: [PATCH 20/69] Update tests --- script/helpers/WellDeployer.sol | 6 +- src/Well.sol | 2 +- src/WellUpgradeable.sol | 108 +++++++++++------ .../LibWellUpgradeableConstructor.sol | 19 ++- test/WellUpgradeable.t.sol | 110 +++++++++++++----- 5 files changed, 170 insertions(+), 75 deletions(-) diff --git a/script/helpers/WellDeployer.sol b/script/helpers/WellDeployer.sol index dbaa3c4d..ff76d73d 100644 --- a/script/helpers/WellDeployer.sol +++ b/script/helpers/WellDeployer.sol @@ -40,7 +40,6 @@ abstract contract WellDeployer { * @param _wellFunction A single Call struct representing a call to the Well Function. * @param _pumps An array of Call structs representings calls to Pumps. * @param _salt The salt to deploy the Well with (`bytes32(0)` for none). See {LibClone}. - * @param owner The address of the owner of the Well. */ function encodeAndBoreWellUpgradeable( address _aquifer, @@ -48,11 +47,10 @@ abstract contract WellDeployer { IERC20[] memory _tokens, Call memory _wellFunction, Call[] memory _pumps, - bytes32 _salt, - address owner + bytes32 _salt ) internal returns (WellUpgradeable _well) { (bytes memory immutableData, bytes memory initData) = - LibWellUpgradeableConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps, owner); + LibWellUpgradeableConstructor.encodeWellDeploymentData(_aquifer, _tokens, _wellFunction, _pumps); _well = WellUpgradeable(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); } } diff --git a/src/Well.sol b/src/Well.sol index bdd46855..27b7dfda 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -44,7 +44,7 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr _disableInitializers(); } - function init(string memory _name, string memory _symbol) external initializer { + function init(string memory _name, string memory _symbol) external virtual initializer { __ERC20Permit_init(_name); __ERC20_init(_name, _symbol); __ReentrancyGuard_init(); diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index 462211c3..d1e4ca64 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -7,12 +7,18 @@ import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "ozu/access/OwnableUpgradeable.sol"; import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; import {IAquifer} from "src/interfaces/IAquifer.sol"; +import {console} from "forge-std/console.sol"; + /** - * @title Well + * @title WellUpgradeable * @author Publius, Silo Chad, Brean, Deadmanwalking * @dev A Well is a constant function AMM allowing the provisioning of liquidity * into a single pooled on-chain liquidity position. * + * Given the dynamic storage layout of Wells initialized by an minimal proxy, + * Creating an upgradeable Well requires a custom initializer function that allows the Well + * to be initialized with immutable storage, but does not deploy a Well token. + * * Rebasing Tokens: * - Positive rebasing tokens are supported by Wells, but any tokens recieved from a * rebase will not be rewarded to LP holders and instead can be extracted by anyone @@ -27,23 +33,34 @@ import {IAquifer} from "src/interfaces/IAquifer.sol"; * - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity), * INCLUDE the fee that is taken on transfer when calculating amount out values. */ -contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { - +contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { address private immutable ___self = address(this); - function init(string memory _name, string memory _symbol, address owner) external initializer { - // owner of well param as the aquifier address will be the owner initially + /** + * @notice verifies that the execution is called through an minimal proxy or is not a delegate call. + */ + modifier notDelegatedOrIsMinimalProxy() { + if (address(this) != ___self) { + address aquifer = aquifer(); + address wellImplmentation = IAquifer(aquifer).wellImplementation(address(this)); + require(wellImplmentation == ___self, "Function must be called by a Well bored by an aquifer"); + } else { + revert("UUPSUpgradeable: must not be called through delegatecall"); + } + _; + } + + function init( + string memory _name, + string memory _symbol + ) external override reinitializer(2) { + // owner of Well param as the aquifier address will be the owner initially // ownable init transfers ownership to msg.sender __ERC20Permit_init(_name); __ERC20_init(_name, _symbol); __ReentrancyGuard_init(); __UUPSUpgradeable_init(); - // first time this is called the owner will be the msg.sender - // which is the aquifer that bore the well __Ownable_init(); - // then ownership can be transfered to the wanted address - // note: to init owner with __Ownable_init(ownerAddress); we would need to adjust the lib code - transferOwnership(owner); IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; @@ -55,10 +72,13 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { } } } - - function getVersion() external virtual pure returns (uint256) { - return 1; - } + + /** + * @notice `initClone` allows for the Well to be initialized without deploying a Well token. + * @dev This function is required given intializing with the Well token would create two valid Wells. + * Sets ReentraryGuard to true to prevent users from interacting with the Well. + */ + function initNoWellToken() external initializer {} // Wells deployed by aquifers use the EIP-1167 minimal proxy pattern for gas-efficent deployments. // This pattern breaks the UUPS upgrade pattern, as the `__self` variable is set to the initial well implmentation. @@ -74,32 +94,31 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { require(address(this) != ___self, "Function must be called through delegatecall"); // verify the function is called through an active proxy bored by an aquifer. - address activeProxy = IAquifer(aquifer()).wellImplementation(_getImplementation()); + address aquifer = aquifer(); + address activeProxy = IAquifer(aquifer).wellImplementation(_getImplementation()); require(activeProxy == ___self, "Function must be called through active proxy bored by an aquifer"); // verify the new implmentation is a well bored by an aquifier. - address aquifer = Well(newImplmentation).aquifer(); require( - IAquifer(aquifer).wellImplementation(newImplmentation) != address(0), + IAquifer(aquifer).wellImplementation(newImplmentation) != + address(0), "New implementation must be a well implmentation" ); // verify the new implmentation is a valid ERC-1967 implmentation. + console.log("here"); require( - UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT, + UUPSUpgradeable(newImplmentation).proxiableUUID() == + _IMPLEMENTATION_SLOT, "New implementation must be a valid ERC-1967 implmentation" ); } /** - * @notice Upgrade the implementation of the proxy to `newImplementation`. - * @dev replaces 'onlyProxy' with `_authorizeUpgrade` restriction. - * + * @notice Upgrades the implementation of the proxy to `newImplementation`. * Calls {_authorizeUpgrade}. - * - * Emits an {Upgraded} event. - * - * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies + * cloned (Bored) by an Aquifer. */ function upgradeTo(address newImplementation) public override { _authorizeUpgrade(newImplementation); @@ -107,17 +126,38 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { } /** - * @notice Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call - * encoded in `data`. - * + * @notice Upgrades the implementation of the proxy to `newImplementation`. * Calls {_authorizeUpgrade}. - * - * Emits an {Upgraded} event. - * - * @custom:oz-upgrades-unsafe-allow-reachable delegatecall + * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies + * cloned (Bored) by an Aquifer. */ - function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { + function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); } -} \ No newline at end of file + + /** + * @dev Implementation of the ERC1822 {proxiableUUID} function. This returns the storage slot used by the + * implementation. It is used to validate the implementation's compatibility when performing an upgrade. + * + * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks + * bricking a proxy that upgrades to it, by delegating to itself until out of gas. However, Wells bored by Aquifers + * are ERC-1167 minimal immutable clones and cannot delgate to another proxy. Thus, `proxiableUUID` was updated to support + * this specific usecase. + */ + function proxiableUUID() external view override notDelegatedOrIsMinimalProxy returns (bytes32) { + return _IMPLEMENTATION_SLOT; + } + + function getImplementation() external view returns (address) { + return _getImplementation(); + } + + function getVersion() external pure virtual returns (uint256) { + return 1; + } + + function getInitializerVersion() external view returns (uint256) { + return _getInitializedVersion(); + } +} diff --git a/src/libraries/LibWellUpgradeableConstructor.sol b/src/libraries/LibWellUpgradeableConstructor.sol index 1103c6c8..be895335 100644 --- a/src/libraries/LibWellUpgradeableConstructor.sol +++ b/src/libraries/LibWellUpgradeableConstructor.sol @@ -5,21 +5,21 @@ pragma solidity ^0.8.20; import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; import {Call, IERC20} from "src/Well.sol"; +import {WellUpgradeable} from "src/WellUpgradeable.sol"; library LibWellUpgradeableConstructor { /** - * @notice Encode the Well's immutable data and init data with an owner. + * @notice Encode the Well's immutable data. */ function encodeWellDeploymentData( address _aquifer, IERC20[] memory _tokens, Call memory _wellFunction, - Call[] memory _pumps, - address owner - ) internal view returns (bytes memory immutableData, bytes memory initData) { + Call[] memory _pumps + ) internal pure returns (bytes memory immutableData, bytes memory initData) { immutableData = encodeWellImmutableData(_aquifer, _tokens, _wellFunction, _pumps); - initData = encodeWellInitFunctionCall(_tokens, _wellFunction, owner); + initData = abi.encodeWithSelector(WellUpgradeable.initNoWellToken.selector); } /** @@ -63,8 +63,7 @@ library LibWellUpgradeableConstructor { function encodeWellInitFunctionCall( IERC20[] memory _tokens, - Call memory _wellFunction, - address owner + Call memory _wellFunction ) public view returns (bytes memory initFunctionCall) { string memory name = LibContractInfo.getSymbol(address(_tokens[0])); string memory symbol = name; @@ -72,11 +71,11 @@ library LibWellUpgradeableConstructor { name = string.concat(name, ":", LibContractInfo.getSymbol(address(_tokens[i]))); symbol = string.concat(symbol, LibContractInfo.getSymbol(address(_tokens[i]))); } - name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Well"); - symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "w"); + name = string.concat(name, " ", LibContractInfo.getName(_wellFunction.target), " Upgradeable Well"); + symbol = string.concat(symbol, LibContractInfo.getSymbol(_wellFunction.target), "uw"); // See {Well.init}. - initFunctionCall = abi.encodeWithSignature("init(string,string,address)", name, symbol, owner); + initFunctionCall = abi.encodeWithSelector(WellUpgradeable.init.selector, name, symbol); } /** diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index 3c586059..3629cd88 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -19,7 +19,6 @@ import {ERC1967Proxy} from "oz/proxy/ERC1967/ERC1967Proxy.sol"; import {MockWellUpgradeable} from "mocks/wells/MockWellUpgradeable.sol"; contract WellUpgradeTest is Test, WellDeployer { - address proxyAddress; address aquifer; address initialOwner; @@ -28,16 +27,19 @@ contract WellUpgradeTest is Test, WellDeployer { address wellFunctionAddress; address token1Address; address token2Address; + address wellAddress; + address wellImplementation; function setUp() public { - // Tokens IERC20[] memory tokens = new IERC20[](2); tokens[0] = new MockToken("BEAN", "BEAN", 6); tokens[1] = new MockToken("WETH", "WETH", 18); token1Address = address(tokens[0]); + vm.label(token1Address, "token1"); token2Address = address(tokens[1]); + vm.label(token2Address, "token2"); user = makeAddr("user"); @@ -46,30 +48,45 @@ contract WellUpgradeTest is Test, WellDeployer { MockToken(address(tokens[1])).mint(user, 10000000000000000); // Well Function IWellFunction cp2 = new ConstantProduct2(); + vm.label(address(cp2), "CP2"); wellFunctionAddress = address(cp2); - Call memory wellFunction = Call(address(cp2), abi.encode("beanstalkFunction")); + Call memory wellFunction = Call( + address(cp2), + abi.encode("beanstalkFunction") + ); // Pump IPump mockPump = new MockPump(); mockPumpAddress = address(mockPump); + vm.label(mockPumpAddress, "mockPump"); Call[] memory pumps = new Call[](1); // init new mock pump with "beanstalk" data pumps[0] = Call(address(mockPump), abi.encode("beanstalkPump")); aquifer = address(new Aquifer()); - address wellImplementation = address(new WellUpgradeable()); + vm.label(aquifer, "aquifer"); + wellImplementation = address(new WellUpgradeable()); + vm.label(wellImplementation, "wellImplementation"); initialOwner = makeAddr("owner"); // Well - WellUpgradeable well = encodeAndBoreWellUpgradeable(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0), initialOwner); - + WellUpgradeable well = encodeAndBoreWellUpgradeable( + aquifer, + wellImplementation, + tokens, + wellFunction, + pumps, + bytes32(0) + ); + wellAddress = address(well); + vm.label(wellAddress, "upgradeableWell"); // Sum up of what is going on here // We encode and bore a well upgradeable from the aquifer // The well upgradeable additionally takes in an owner address so we modify the init function call - // to include the owner address. - // When the new well is deployed, all init data are stored in the implementation storage + // to include the owner address. + // When the new well is deployed, all init data are stored in the implementation storage // including pump and well function data --> NOTE: This could be an issue but how do we solve this? // Then we deploy a ERC1967Proxy proxy for the well upgradeable and call the init function on the proxy - // When we deploy the proxy, the init data is stored in the proxy storage and the well is initialized + // When we deploy the proxy, the init data is stored in the proxy storage and the well is initialized // for the second time. We can now control the well via delegate calls to the proxy address. // Every time we call the init function, we init the owner to be the msg.sender and @@ -77,23 +94,29 @@ contract WellUpgradeTest is Test, WellDeployer { // to an address of our choice (see WellUpgradeable.sol for more details on the init function) // FROM OZ - // If _data is nonempty, it’s used as data in a delegate call to _logic. - // This will typically be an encoded function call, and allows initializing + // If _data is nonempty, it’s used as data in a delegate call to _logic. + // This will typically be an encoded function call, and allows initializing // the storage of the proxy like a Solidity constructor. // Deploy Proxy + vm.startPrank(initialOwner); ERC1967Proxy proxy = new ERC1967Proxy( address(well), // implementation address - // init data (name, symbol, owner) ); - abi.encodeCall(WellUpgradeable.init, ("WELL", "WELL", initialOwner)) // _data + LibWellUpgradeableConstructor.encodeWellInitFunctionCall( + tokens, + wellFunction + ) // init data ); + vm.stopPrank(); proxyAddress = address(proxy); + vm.label(proxyAddress, "proxyAddress"); vm.startPrank(user); + tokens[0].approve(wellAddress, type(uint256).max); + tokens[1].approve(wellAddress, type(uint256).max); tokens[0].approve(proxyAddress, type(uint256).max); tokens[1].approve(proxyAddress, type(uint256).max); vm.stopPrank(); - } ///////////////////// Storage Tests ///////////////////// @@ -102,7 +125,7 @@ contract WellUpgradeTest is Test, WellDeployer { assertEq(address(aquifer), WellUpgradeable(proxyAddress).aquifer()); } - function testProxyGetPump() public { + function testProxyGetPump() public { Call[] memory proxyPumps = WellUpgradeable(proxyAddress).pumps(); assertEq(mockPumpAddress, proxyPumps[0].target); // this passes but why? Pump data are supposed @@ -117,13 +140,17 @@ contract WellUpgradeTest is Test, WellDeployer { } function testProxyGetWellFunction() public { - Call memory proxyWellFunction = WellUpgradeable(proxyAddress).wellFunction(); - assertEq(address(proxyWellFunction.target), address(wellFunctionAddress)); + Call memory proxyWellFunction = WellUpgradeable(proxyAddress) + .wellFunction(); + assertEq( + address(proxyWellFunction.target), + address(wellFunctionAddress) + ); assertEq(proxyWellFunction.data, abi.encode("beanstalkFunction")); } function testProxyGetSymbolInStorage() public { - assertEq("WELL", WellUpgradeable(proxyAddress).symbol()); + assertEq("BEANWETHCP2uw", WellUpgradeable(proxyAddress).symbol()); } function testProxyInitVersion() public { @@ -133,7 +160,10 @@ contract WellUpgradeTest is Test, WellDeployer { function testProxyNumTokens() public { uint256 expectedNumTokens = 2; - assertEq(expectedNumTokens, WellUpgradeable(proxyAddress).numberOfTokens()); + assertEq( + expectedNumTokens, + WellUpgradeable(proxyAddress).numberOfTokens() + ); } ///////////////// Interaction test ////////////////// @@ -143,7 +173,18 @@ contract WellUpgradeTest is Test, WellDeployer { uint256[] memory amounts = new uint256[](2); amounts[0] = 1000; amounts[1] = 1000; - WellUpgradeable(proxyAddress).addLiquidity(amounts, 0 , user, type(uint256).max); + WellUpgradeable(wellAddress).addLiquidity( + amounts, + 0, + user, + type(uint256).max + ); + WellUpgradeable(proxyAddress).addLiquidity( + amounts, + 0, + user, + type(uint256).max + ); assertEq(amounts, WellUpgradeable(proxyAddress).getReserves()); vm.stopPrank(); } @@ -171,15 +212,32 @@ contract WellUpgradeTest is Test, WellDeployer { ////////////////////// Upgrade Tests ////////////////////// function testUpgradeToNewImplementation() public { + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = new MockToken("BEAN", "BEAN", 6); + tokens[1] = new MockToken("WETH", "WETH", 18); + Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); + Call[] memory pumps = new Call[](1); + pumps[0] = Call(mockPumpAddress, abi.encode("2")); + // create new mock Well Implementation: + address wellImpl = address(new MockWellUpgradeable()); + WellUpgradeable well2 = encodeAndBoreWellUpgradeable( + aquifer, + wellImpl, + tokens, + wellFunction, + pumps, + bytes32(abi.encode("2")) + ); + vm.label(address(well2), "upgradeableWell2"); + vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); - MockWellUpgradeable newImpl = new MockWellUpgradeable(); - // getting error due to the onlyProxy modifier in UUPSUpgradeable.sol - // commented this out for now in UUPSUpgradeable.sol - // require(_getImplementation() == __self, "Function must be called through active proxy"); - proxy.upgradeTo(address(newImpl)); + proxy.upgradeTo(address(well2)); assertEq(initialOwner, MockWellUpgradeable(proxyAddress).owner()); + // verify proxy was upgraded. + assertEq(address(well2), MockWellUpgradeable(proxyAddress).getImplementation()); + assertEq(1, MockWellUpgradeable(proxyAddress).getVersion()); + assertEq(100, MockWellUpgradeable(proxyAddress).getVersion(100)); vm.stopPrank(); } - } From 3efa71fa21a7b191f5c6c3e964990b2d3480496f Mon Sep 17 00:00:00 2001 From: Brean0 Date: Mon, 24 Jun 2024 10:51:34 +0200 Subject: [PATCH 21/69] WIP --- lib/forge-std | 2 +- src/Well.sol | 263 +++++++--- src/functions/BeanstalkStableSwap.sol | 108 ---- src/functions/CurveStableSwap2.sol | 483 ++++++++++++++++++ src/functions/SolidlyStableSwap2.sol | 155 ++++++ src/functions/StableSwap2.sol | 232 --------- test/TestHelper.sol | 220 ++++++-- test/Well.Shift.t.sol | 205 ++++++-- ...lkStableSwap2.calcReserveAtRatioSwap.t.sol | 120 ----- ...bleSwap2.calcReserveAtRatioLiquidity.t.sol | 198 +++++++ ...veStableSwap2.calcReserveAtRatioSwap.t.sol | 87 ++++ test/functions/StableSwap.t.sol | 100 ++-- test/integration/IntegrationTestHelper.sol | 84 +-- test/integration/interfaces/ICurve.sol | 212 ++++++-- ...ntegrationTestGasComparisonsStableSwap.sol | 263 ---------- test/stableSwap/Well.BoreStableSwap.t.sol | 29 +- ....RemoveLiquidityImbalancedStableSwap.t.sol | 178 +++++-- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 155 ++++-- .../Well.RemoveLiquidityStableSwap.t.sol | 69 ++- test/stableSwap/Well.ShiftStable.t.sol | 213 ++++++-- test/stableSwap/Well.SkimStableSwap.t.sol | 7 +- test/stableSwap/Well.SwapFromStableSwap.t.sol | 90 +++- test/stableSwap/Well.SwapToStableSwap.t.sol | 93 +++- 23 files changed, 2429 insertions(+), 1137 deletions(-) delete mode 100644 src/functions/BeanstalkStableSwap.sol create mode 100644 src/functions/CurveStableSwap2.sol create mode 100644 src/functions/SolidlyStableSwap2.sol delete mode 100644 src/functions/StableSwap2.sol delete mode 100644 test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol create mode 100644 test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol create mode 100644 test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol delete mode 100644 test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol diff --git a/lib/forge-std b/lib/forge-std index 5125ce50..978ac6fa 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 5125ce505fa60ca747df08ab2cc77db5445bd716 +Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 diff --git a/src/Well.sol b/src/Well.sol index bdd46855..fc51a3e9 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -32,19 +32,29 @@ import {ClonePlus} from "src/utils/ClonePlus.sol"; * - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity), * INCLUDE the fee that is taken on transfer when calculating amount out values. */ -contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgradeable, ClonePlus { +contract Well is + ERC20PermitUpgradeable, + IWell, + IWellErrors, + ReentrancyGuardUpgradeable, + ClonePlus +{ using SafeERC20 for IERC20; uint256 private constant PACKED_ADDRESS = 20; uint256 private constant ONE_WORD_PLUS_PACKED_ADDRESS = 52; // For gas efficiency purposes - bytes32 private constant RESERVES_STORAGE_SLOT = 0x4bba01c388049b5ebd30398b65e8ad45b632802c5faf4964e58085ea8ab03715; // bytes32(uint256(keccak256("reserves.storage.slot")) - 1); + bytes32 private constant RESERVES_STORAGE_SLOT = + 0x4bba01c388049b5ebd30398b65e8ad45b632802c5faf4964e58085ea8ab03715; // bytes32(uint256(keccak256("reserves.storage.slot")) - 1); constructor() { // Disable Initializers to prevent the init function from being callable on the implementation contract _disableInitializers(); } - function init(string memory _name, string memory _symbol) external initializer { + function init( + string memory _name, + string memory _symbol + ) external initializer { __ERC20Permit_init(_name); __ERC20_init(_name, _symbol); __ReentrancyGuard_init(); @@ -109,7 +119,10 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr function wellFunction() public pure returns (Call memory _wellFunction) { _wellFunction.target = wellFunctionAddress(); - _wellFunction.data = _getArgBytes(LOC_VARIABLE + numberOfTokens() * ONE_WORD, wellFunctionDataLength()); + _wellFunction.data = _getArgBytes( + LOC_VARIABLE + numberOfTokens() * ONE_WORD, + wellFunctionDataLength() + ); } function pumps() public pure returns (Call[] memory _pumps) { @@ -117,7 +130,10 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr if (_numberOfPumps == 0) return _pumps; _pumps = new Call[](_numberOfPumps); - uint256 dataLoc = LOC_VARIABLE + numberOfTokens() * ONE_WORD + wellFunctionDataLength(); + uint256 dataLoc = LOC_VARIABLE + + numberOfTokens() * + ONE_WORD + + wellFunctionDataLength(); uint256 pumpDataLength; for (uint256 i; i < _pumps.length; ++i) { @@ -193,9 +209,15 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * @dev Provided as an optimization in the case where {numberOfPumps} returns 1. */ function firstPump() public pure returns (Call memory _pump) { - uint256 dataLoc = LOC_VARIABLE + numberOfTokens() * ONE_WORD + wellFunctionDataLength(); + uint256 dataLoc = LOC_VARIABLE + + numberOfTokens() * + ONE_WORD + + wellFunctionDataLength(); _pump.target = _getArgAddress(dataLoc); - _pump.data = _getArgBytes(dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, _getArgUint256(dataLoc + PACKED_ADDRESS)); + _pump.data = _getArgBytes( + dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, + _getArgUint256(dataLoc + PACKED_ADDRESS) + ); } //////////////////// SWAP: FROM //////////////////// @@ -213,7 +235,13 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 amountOut) { fromToken.safeTransferFrom(msg.sender, address(this), amountIn); - amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); + amountOut = _swapFrom( + fromToken, + toToken, + amountIn, + minAmountOut, + recipient + ); } /** @@ -229,8 +257,18 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 amountOut) { - amountIn = _safeTransferFromFeeOnTransfer(fromToken, msg.sender, amountIn); - amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); + amountIn = _safeTransferFromFeeOnTransfer( + fromToken, + msg.sender, + amountIn + ); + amountOut = _swapFrom( + fromToken, + toToken, + amountIn, + minAmountOut, + recipient + ); } function _swapFrom( @@ -275,7 +313,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr reserves[i] += amountIn; // underflow is desired; Well Function SHOULD NOT increase reserves of both `i` and `j` - amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = + reserves[j] - + _calcReserve(wellFunction(), reserves, j, totalSupply()); } //////////////////// SWAP: TO //////////////////// @@ -344,7 +384,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr reserves[j] -= amountOut; - amountIn = _calcReserve(wellFunction(), reserves, i, totalSupply()) - reserves[i]; + amountIn = + _calcReserve(wellFunction(), reserves, i, totalSupply()) - + reserves[i]; } //////////////////// SHIFT //////////////////// @@ -396,7 +438,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr reserves[i] = _tokens[i].balanceOf(address(this)); } uint256 j = _getJ(_tokens, tokenOut); - amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = + reserves[j] - + _calcReserve(wellFunction(), reserves, j, totalSupply()); if (amountOut >= minAmountOut) { tokenOut.safeTransfer(recipient, amountOut); @@ -408,7 +452,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr } } - function getShiftOut(IERC20 tokenOut) external view readOnlyNonReentrant returns (uint256 amountOut) { + function getShiftOut( + IERC20 tokenOut + ) external view readOnlyNonReentrant returns (uint256 amountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = new uint256[](tokensLength); @@ -417,7 +463,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr } uint256 j = _getJ(_tokens, tokenOut); - amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = + reserves[j] - + _calcReserve(wellFunction(), reserves, j, totalSupply()); } //////////////////// ADD LIQUIDITY //////////////////// @@ -428,7 +476,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 lpAmountOut) { - lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, false); + lpAmountOut = _addLiquidity( + tokenAmountsIn, + minLpAmountOut, + recipient, + false + ); } function addLiquidityFeeOnTransfer( @@ -437,7 +490,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 lpAmountOut) { - lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, true); + lpAmountOut = _addLiquidity( + tokenAmountsIn, + minLpAmountOut, + recipient, + true + ); } /** @@ -458,7 +516,11 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr for (uint256 i; i < tokensLength; ++i) { _tokenAmountIn = tokenAmountsIn[i]; if (_tokenAmountIn == 0) continue; - _tokenAmountIn = _safeTransferFromFeeOnTransfer(_tokens[i], msg.sender, _tokenAmountIn); + _tokenAmountIn = _safeTransferFromFeeOnTransfer( + _tokens[i], + msg.sender, + _tokenAmountIn + ); reserves[i] += _tokenAmountIn; tokenAmountsIn[i] = _tokenAmountIn; } @@ -466,12 +528,18 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr for (uint256 i; i < tokensLength; ++i) { _tokenAmountIn = tokenAmountsIn[i]; if (_tokenAmountIn == 0) continue; - _tokens[i].safeTransferFrom(msg.sender, address(this), _tokenAmountIn); + _tokens[i].safeTransferFrom( + msg.sender, + address(this), + _tokenAmountIn + ); reserves[i] += _tokenAmountIn; } } - lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply(); + lpAmountOut = + _calcLpTokenSupply(wellFunction(), reserves) - + totalSupply(); if (lpAmountOut < minLpAmountOut) { revert SlippageOut(lpAmountOut, minLpAmountOut); } @@ -484,19 +552,18 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Assumes that no tokens involved incur a fee on transfer. */ - function getAddLiquidityOut(uint256[] memory tokenAmountsIn) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountOut) - { + function getAddLiquidityOut( + uint256[] memory tokenAmountsIn + ) external view readOnlyNonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); for (uint256 i; i < tokensLength; ++i) { reserves[i] += tokenAmountsIn[i]; } - lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply(); + lpAmountOut = + _calcLpTokenSupply(wellFunction(), reserves) - + totalSupply(); } //////////////////// REMOVE LIQUIDITY: BALANCED //////////////////// @@ -506,12 +573,22 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256[] calldata minTokenAmountsOut, address recipient, uint256 deadline - ) external nonReentrant expire(deadline) returns (uint256[] memory tokenAmountsOut) { + ) + external + nonReentrant + expire(deadline) + returns (uint256[] memory tokenAmountsOut) + { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _updatePumps(tokensLength); - tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, totalSupply()); + tokenAmountsOut = _calcLPTokenUnderlying( + wellFunction(), + lpAmountIn, + reserves, + totalSupply() + ); _burn(msg.sender, lpAmountIn); uint256 _tokenAmountOut; for (uint256 i; i < tokensLength; ++i) { @@ -527,7 +604,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityOut(uint256 lpAmountIn) + function getRemoveLiquidityOut( + uint256 lpAmountIn + ) external view readOnlyNonReentrant @@ -537,7 +616,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256[] memory reserves = _getReserves(_tokens.length); uint256 lpTokenSupply = totalSupply(); - tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, lpTokenSupply); + tokenAmountsOut = _calcLPTokenUnderlying( + wellFunction(), + lpAmountIn, + reserves, + lpTokenSupply + ); } //////////////////// REMOVE LIQUIDITY: ONE TOKEN //////////////////// @@ -553,7 +637,11 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256[] memory reserves = _updatePumps(_tokens.length); uint256 j = _getJ(_tokens, tokenOut); - tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, j, reserves); + tokenAmountOut = _getRemoveLiquidityOneTokenOut( + lpAmountIn, + j, + reserves + ); if (tokenAmountOut < minTokenAmountOut) { revert SlippageOut(tokenAmountOut, minTokenAmountOut); } @@ -563,7 +651,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr reserves[j] -= tokenAmountOut; _setReserves(_tokens, reserves); - emit RemoveLiquidityOneToken(lpAmountIn, tokenOut, tokenAmountOut, recipient); + emit RemoveLiquidityOneToken( + lpAmountIn, + tokenOut, + tokenAmountOut, + recipient + ); } function getRemoveLiquidityOneTokenOut( @@ -572,7 +665,11 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr ) external view readOnlyNonReentrant returns (uint256 tokenAmountOut) { IERC20[] memory _tokens = tokens(); uint256[] memory reserves = _getReserves(_tokens.length); - tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, _getJ(_tokens, tokenOut), reserves); + tokenAmountOut = _getRemoveLiquidityOneTokenOut( + lpAmountIn, + _getJ(_tokens, tokenOut), + reserves + ); } /** @@ -587,7 +684,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256 j, uint256[] memory reserves ) private view returns (uint256 tokenAmountOut) { - uint256 newReserveJ = _calcReserve(wellFunction(), reserves, j, totalSupply() - lpAmountIn); + uint256 newReserveJ = _calcReserve( + wellFunction(), + reserves, + j, + totalSupply() - lpAmountIn + ); tokenAmountOut = reserves[j] - newReserveJ; } @@ -610,7 +712,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr reserves[i] -= _tokenAmountOut; } - lpAmountIn = totalSupply() - _calcLpTokenSupply(wellFunction(), reserves); + lpAmountIn = + totalSupply() - + _calcLpTokenSupply(wellFunction(), reserves); if (lpAmountIn > maxLpAmountIn) { revert SlippageIn(lpAmountIn, maxLpAmountIn); } @@ -620,19 +724,18 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountIn) - { + function getRemoveLiquidityImbalancedIn( + uint256[] calldata tokenAmountsOut + ) external view readOnlyNonReentrant returns (uint256 lpAmountIn) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); for (uint256 i; i < tokensLength; ++i) { reserves[i] -= tokenAmountsOut[i]; } - lpAmountIn = totalSupply() - _calcLpTokenSupply(wellFunction(), reserves); + lpAmountIn = + totalSupply() - + _calcLpTokenSupply(wellFunction(), reserves); } //////////////////// RESERVES //////////////////// @@ -641,7 +744,10 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * @dev Can be used in a multicall to add liquidity similar to how `shift` can be used to swap. * See {shift} for examples of how to use in a multicall. */ - function sync(address recipient, uint256 minLpAmountOut) external nonReentrant returns (uint256 lpAmountOut) { + function sync( + address recipient, + uint256 minLpAmountOut + ) external nonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; _updatePumps(tokensLength); @@ -664,7 +770,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit Sync(reserves, lpAmountOut, recipient); } - function getSyncOut() external view readOnlyNonReentrant returns (uint256 lpAmountOut) { + function getSyncOut() + external + view + readOnlyNonReentrant + returns (uint256 lpAmountOut) + { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; @@ -683,7 +794,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Transfer excess tokens held by the Well to `recipient`. */ - function skim(address recipient) external nonReentrant returns (uint256[] memory skimAmounts) { + function skim( + address recipient + ) external nonReentrant returns (uint256[] memory skimAmounts) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); @@ -696,14 +809,21 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr } } - function getReserves() external view readOnlyNonReentrant returns (uint256[] memory reserves) { + function getReserves() + external + view + readOnlyNonReentrant + returns (uint256[] memory reserves) + { reserves = _getReserves(numberOfTokens()); } /** * @dev Gets the Well's token reserves by reading from byte storage. */ - function _getReserves(uint256 _numberOfTokens) internal view returns (uint256[] memory reserves) { + function _getReserves( + uint256 _numberOfTokens + ) internal view returns (uint256[] memory reserves) { reserves = LibBytes.readUint128(RESERVES_STORAGE_SLOT, _numberOfTokens); } @@ -711,9 +831,13 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * @dev Checks that the balance of each ERC-20 token is >= the reserves and * sets the Well's reserves of each token by writing to byte storage. */ - function _setReserves(IERC20[] memory _tokens, uint256[] memory reserves) internal { + function _setReserves( + IERC20[] memory _tokens, + uint256[] memory reserves + ) internal { for (uint256 i; i < reserves.length; ++i) { - if (reserves[i] > _tokens[i].balanceOf(address(this))) revert InvalidReserves(); + if (reserves[i] > _tokens[i].balanceOf(address(this))) + revert InvalidReserves(); } LibBytes.storeUint128(RESERVES_STORAGE_SLOT, reserves); } @@ -724,7 +848,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * @dev Fetches the current token reserves of the Well and updates the Pumps. * Typically called before an operation that modifies the Well's reserves. */ - function _updatePumps(uint256 _numberOfTokens) internal returns (uint256[] memory reserves) { + function _updatePumps( + uint256 _numberOfTokens + ) internal returns (uint256[] memory reserves) { reserves = _getReserves(_numberOfTokens); uint256 _numberOfPumps = numberOfPumps(); @@ -736,16 +862,16 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr if (_numberOfPumps == 1) { Call memory _pump = firstPump(); // Don't revert if the update call fails. - try IPump(_pump.target).update(reserves, _pump.data) {} - catch { + try IPump(_pump.target).update(reserves, _pump.data) {} catch { // ignore reversion. If an external shutoff mechanism is added to a Pump, it could be called here. } } else { Call[] memory _pumps = pumps(); for (uint256 i; i < _pumps.length; ++i) { // Don't revert if the update call fails. - try IPump(_pumps[i].target).update(reserves, _pumps[i].data) {} - catch { + try + IPump(_pumps[i].target).update(reserves, _pumps[i].data) + {} catch { // ignore reversion. If an external shutoff mechanism is added to a Pump, it could be called here. } } @@ -765,7 +891,10 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr Call memory _wellFunction, uint256[] memory reserves ) internal view returns (uint256 lpTokenSupply) { - lpTokenSupply = IWellFunction(_wellFunction.target).calcLpTokenSupply(reserves, _wellFunction.data); + lpTokenSupply = IWellFunction(_wellFunction.target).calcLpTokenSupply( + reserves, + _wellFunction.data + ); } /** @@ -781,7 +910,12 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256 j, uint256 lpTokenSupply ) internal view returns (uint256 reserve) { - reserve = IWellFunction(_wellFunction.target).calcReserve(reserves, j, lpTokenSupply, _wellFunction.data); + reserve = IWellFunction(_wellFunction.target).calcReserve( + reserves, + j, + lpTokenSupply, + _wellFunction.data + ); } /** @@ -799,9 +933,13 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr uint256[] memory reserves, uint256 lpTokenSupply ) internal view returns (uint256[] memory tokenAmounts) { - tokenAmounts = IWellFunction(_wellFunction.target).calcLPTokenUnderlying( - lpTokenAmount, reserves, lpTokenSupply, _wellFunction.data - ); + tokenAmounts = IWellFunction(_wellFunction.target) + .calcLPTokenUnderlying( + lpTokenAmount, + reserves, + lpTokenSupply, + _wellFunction.data + ); } //////////////////// INTERNAL: WELL TOKEN INDEXING //////////////////// @@ -838,7 +976,10 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr * If `_tokens` contains multiple instances of `jToken`, this will return * the first one. A {Well} with duplicate tokens has been misconfigured. */ - function _getJ(IERC20[] memory _tokens, IERC20 jToken) internal pure returns (uint256 j) { + function _getJ( + IERC20[] memory _tokens, + IERC20 jToken + ) internal pure returns (uint256 j) { for (j; j < _tokens.length; ++j) { if (jToken == _tokens[j]) { return j; diff --git a/src/functions/BeanstalkStableSwap.sol b/src/functions/BeanstalkStableSwap.sol deleted file mode 100644 index 05818fad..00000000 --- a/src/functions/BeanstalkStableSwap.sol +++ /dev/null @@ -1,108 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import "src/functions/StableSwap2.sol"; -import {IBeanstalkA} from "src/interfaces/beanstalk/IBeanstalkA.sol"; -import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; - - -/** - * @author Brean - * @title Beanstalk Stableswap well function. Includes functions needed for the well - * to interact with the Beanstalk contract. - * - * @dev The A parameter is an dynamic parameter, queried from the Beanstalk contract. - * With an fallback A value determined by the well data. - */ -contract BeanstalkStableSwap is StableSwap2, IBeanstalkWellFunction { - using LibMath for uint; - using SafeMath for uint; - - // Beanstalk - IBeanstalkA BEANSTALK = IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5); - uint256 PRECISION = 1e18; - - /** - * TODO: for deltaB minting - * `ratios` here refer to the virtual price of the non-bean asset. - * (precision 1e18). - */ - function calcReserveAtRatioSwap( - uint256[] calldata reserves, - uint256 j, - uint256[] calldata ratios, - bytes calldata data - ) external view returns (uint256 reserve){ - // uint256 i = j == 1 ? 0 : 1; - // uint256 D = calcLpTokenSupply(reserves, data); - // uint256 initalPegBeanPeg = D/2; - // uint256 currentBeans = reserves[i]; - // uint256 currentBeans = reserves[i]; - - - // uint256[] memory _reserves = new uint256[](2); - // uint256 i = j == 1 ? 0 : 1; - // uint256 targetPrice = ratios[j]/ratios[i]; - // _reserves[0] = reserves[0].mul(ratios[0]).div(PRECISION); - // _reserves[1] = reserves[1].mul(ratios[1]).div(PRECISION); - // // uint256 oldD = calcLpTokenSupply(reserves, data) / 2; - // uint256 newD = calcLpTokenSupply(_reserves, data); - // return newD / 2; - } - - // TODO: for converts - // `ratios` here refer to the virtual price of the non-bean asset - // high level: calc the reserve via adding or removing liq given some reserves, to target ratio. - // - function calcReserveAtRatioLiquidity( - uint256[] calldata reserves, - uint256 j, - uint256[] calldata ratios, - bytes calldata - ) external pure returns (uint256 reserve){ - uint256 i = j == 1 ? 0 : 1; - reserve = reserves[i] * ratios[j] / ratios[i]; - } - - function getBeanstalkA() public pure returns (uint256 a) { - return IBeanstalkA(0xC1E088fC1323b20BCBee9bd1B9fC9546db5624C5).getBeanstalkA(); - } - function getBeanstalkAnn() public pure returns (uint256 a) { - return getBeanstalkA() * N * N; - } - - // TODO: implement. - function getVirtualPrice() public pure returns (uint256 price) { - price = 1.01 * 1e18; - } - - function decodeWFData( - bytes memory data - ) public view override returns ( - uint256 Ann, - uint256[2] memory precisionMultipliers - ){ - WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - - // try to get the beanstalk A. - // if it fails, use the failsafe A stored in well function data. - uint256 a; - try BEANSTALK.getBeanstalkA() returns (uint256 _a) { - a = _a; - } catch { - a = wfd.a; - } - if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); - uint8 token0Dec = IERC20(wfd.tkn0).decimals(); - uint8 token1Dec = IERC20(wfd.tkn1).decimals(); - - if(token0Dec > 18) revert InvalidTokenDecimals(token0Dec); - if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); - - Ann = a * N * N * A_PRECISION; - precisionMultipliers[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); - precisionMultipliers[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); - } - -} diff --git a/src/functions/CurveStableSwap2.sol b/src/functions/CurveStableSwap2.sol new file mode 100644 index 00000000..11001155 --- /dev/null +++ b/src/functions/CurveStableSwap2.sol @@ -0,0 +1,483 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; +import {SafeMath} from "oz/utils/math/SafeMath.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import "forge-std/console.sol"; + +/** + * @author Brean + * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. + * developed by curve. + * + * Stableswap Wells with 2 tokens use the formula: + * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` + * + * Where: + * `A` is the Amplication parameter. + * `D` is the supply of LP tokens + * `b_i` is the reserve at index `i` + * + * @dev Limited to tokens with a maximum of 18 decimals. + */ +contract CurveStableSwap2 is IBeanstalkWellFunction { + using LibMath for uint; + using SafeMath for uint; + + // 2 token Pool. + uint constant N = 2; + + // A precision + uint constant A_PRECISION = 100; + + // Precision that all pools tokens will be converted to. + uint constant POOL_PRECISION_DECIMALS = 18; + + // Maximum A parameter. + uint constant MAX_A = 10000 * A_PRECISION; + + // Calc Rate Precision. + uint256 constant CALC_RATE_PRECISION = 1e24; + + // + uint256 MIN_TOKEN_DECIMALS = 10; + + // Errors + error InvalidAParameter(uint256); + error InvalidTokens(); + error InvalidTokenDecimals(uint256); + + /** + * The CurveStableSwap Well Function fetches the following data from the well: + * 1: A parameter + * 2: token0 address + * 3: token1 address + */ + struct WellFunctionData { + uint256 a; + address token0; + address token1; + } + + struct DeltaB { + uint256 pegBeans; + int256 currentBeans; + int256 deltaBToPeg; + int256 deltaPriceToTarget; + int256 deltaPriceToPeg; + int256 estDeltaB; + uint256 targetPrice; + uint256 poolPrice; + } + + /** + * @notice Calculate the amount of LP tokens minted when adding liquidity. + * D invariant calculation in non-overflowing integer operations iteratively + * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + * + * Converging solution: + * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) + */ + function calcLpTokenSupply( + uint[] memory reserves, + bytes calldata data + ) public view returns (uint lpTokenSupply) { + (uint256 Ann, uint256[] memory precisions) = decodeWFData(data); + console.log(precisions[0], precisions[1]); + reserves = getScaledReserves(reserves, precisions); + + uint256 sumReserves = reserves[0] + reserves[1]; + if (sumReserves == 0) return 0; + lpTokenSupply = sumReserves; + + for (uint i = 0; i < 255; i++) { + uint256 dP = lpTokenSupply; + // If division by 0, this will be borked: only withdrawal will work. And that is good + dP = dP.mul(lpTokenSupply).div(reserves[0].mul(N)); + dP = dP.mul(lpTokenSupply).div(reserves[1].mul(N)); + uint256 prevReserves = lpTokenSupply; + lpTokenSupply = Ann + .mul(sumReserves) + .div(A_PRECISION) + .add(dP.mul(N)) + .mul(lpTokenSupply) + .div( + Ann + .sub(A_PRECISION) + .mul(lpTokenSupply) + .div(A_PRECISION) + .add(N.add(1).mul(dP)) + ); + // Equality with the precision of 1 + if (lpTokenSupply > prevReserves) { + if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; + } else { + if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; + } + } + } + + /** + * @notice Calculate x[i] if one reduces D from being calculated for reserves to D + * Done by solving quadratic equation iteratively. + * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + * x_1**2 + b*x_1 = c + * x_1 = (x_1**2 + c) / (2*x_1 + b) + * @dev This function has a precision of +/- 1, + * which may round in favor of the well or the user. + */ + function calcReserve( + uint[] memory reserves, + uint j, + uint lpTokenSupply, + bytes calldata data + ) public view returns (uint reserve) { + (uint256 Ann, uint256[] memory precisions) = decodeWFData(data); + reserves = getScaledReserves(reserves, precisions); + + // avoid stack too deep errors. + (uint256 c, uint256 b) = getBandC( + Ann, + lpTokenSupply, + j == 0 ? reserves[1] : reserves[0] + ); + reserve = lpTokenSupply; + uint256 prevReserve; + + for (uint i; i < 255; ++i) { + prevReserve = reserve; + reserve = _calcReserve(reserve, b, c, lpTokenSupply); + // Equality with the precision of 1 + // scale reserve down to original precision + if (reserve > prevReserve) { + if (reserve - prevReserve <= 1) + return reserve.div(precisions[j]); + } else { + if (prevReserve - reserve <= 1) + return reserve.div(precisions[j]); + } + } + revert("did not find convergence"); + } + + /** + * @notice Defines a proportional relationship between the supply of LP tokens + * and the amount of each underlying token for a two-token Well. + * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user + * recieves `s * b_i / S` of each underlying token. + * reserves are scaled as needed based on tknXScalar + */ + function calcLPTokenUnderlying( + uint lpTokenAmount, + uint[] memory reserves, + uint lpTokenSupply, + bytes calldata + ) external view returns (uint[] memory underlyingAmounts) { + underlyingAmounts = new uint[](2); + underlyingAmounts[0] = (lpTokenAmount * reserves[0]) / lpTokenSupply; + underlyingAmounts[1] = (lpTokenAmount * reserves[1]) / lpTokenSupply; + } + + /** + * @inheritdoc IMultiFlowPumpWellFunction + * @dev when the reserves are equal, the summation of the reserves + * is equivalent to the token supply of the Well. The LP token supply is calculated from + * `reserves`, and is scaled based on `ratios`. + */ + function calcReserveAtRatioSwap( + uint256[] memory reserves, + uint256 j, + uint256[] memory ratios, + bytes calldata data + ) external view returns (uint256 reserve) { + DeltaB memory db; + + uint256 i = j == 1 ? 0 : 1; + // scale reserves to 18 decimals. + uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); + console.log("lpTokenSupply:", lpTokenSupply); + // inital guess + db.currentBeans = int256(reserves[j]); + console.log("db.currentBeans"); + console.logInt(db.currentBeans); + db.pegBeans = lpTokenSupply / 2; + console.log("db.pegBeans"); + console.log(db.pegBeans); + db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); + console.log("db.deltaBToPeg"); + console.logInt(db.deltaBToPeg); + + uint256 prevPrice; + uint256 x; + uint256 x2; + + // fetch target and pool prices. + // scale ratio by precision: + ratios[0] = ratios[0] * CALC_RATE_PRECISION; + ratios[1] = ratios[1] * CALC_RATE_PRECISION; + console.log("ratios[0]", ratios[0]); + console.log("ratios[1]", ratios[1]); + + db.targetPrice = calcRate(ratios, i, j, data); + console.log("db.targetPrice", db.targetPrice); + console.log("reserve0", reserves[0]); + console.log("reserve1", reserves[1]); + db.poolPrice = calcRate(reserves, i, j, data); + console.log("db.poolPrice", db.poolPrice); + + for (uint k; k < 2; k++) { + db.deltaPriceToTarget = + int256(db.targetPrice) - + int256(db.poolPrice); + console.log("deltaPriceToTarget"); + console.logInt(db.deltaPriceToTarget); + db.deltaPriceToPeg = 1e18 - int256(db.poolPrice); + console.log("deltaPriceToPeg"); + + console.logInt(db.deltaPriceToPeg); + console.log("reserve0----", reserves[j]); + console.log("pegBeans----", db.pegBeans); + db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); + console.log("deltaBToPeg"); + console.logInt(db.deltaBToPeg); + console.log("estDeltaB"); + console.logInt(db.estDeltaB); + + if (db.deltaPriceToPeg != 0) { + db.estDeltaB = + (db.deltaBToPeg * + int256( + (db.deltaPriceToTarget * 1e18) / db.deltaPriceToPeg + )) / + 1e18; + } else { + db.estDeltaB = 0; + } + console.log("estDeltaB"); + console.logInt(db.estDeltaB); + x = uint256(int256(reserves[j]) + db.estDeltaB); + console.log("-----reserve0----", reserves[0]); + console.log("-----reserve1----", reserves[1]); + console.log(i); + x2 = calcReserve(reserves, i, lpTokenSupply, data); + console.log("x", x, "x2", x2); + reserves[j] = x; + reserves[i] = x2; + prevPrice = db.poolPrice; + db.poolPrice = calcRate(reserves, i, j, data); + if (prevPrice > db.poolPrice) { + if (prevPrice - db.poolPrice <= 1) break; + } else if (db.poolPrice - prevPrice <= 1) break; + } + return reserves[j]; + } + + /** + * @inheritdoc IMultiFlowPumpWellFunction + */ + function calcRate( + uint256[] memory reserves, + uint256 i, + uint256 j, + bytes calldata data + ) public view returns (uint256 rate) { + // console.log("reserves[j]", reserves[j]); + // console.log("reserves[i]", reserves[i]); + uint256[] memory _reserves = new uint256[](2); + _reserves[0] = reserves[0]; + _reserves[1] = reserves[1]; + uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); + // console.log("reserves[j]", reserves[j]); + // console.log("reserves[i]", reserves[i]); + // console.log("_reserves[j]", _reserves[j]); + // console.log("_reserves[i]", _reserves[i]); + // add the precision of opposite token to the reserve. + (uint256 padding, uint256 divPadding) = getPadding( + _reserves, + i, + j, + data + ); + // console.log("padding", padding); + // console.log("reserves[j]", _reserves[j]); + _reserves[j] = _reserves[j] + padding; + // console.log("reserves[j]", _reserves[j]); + // console.log("reserves[i]", _reserves[i]); + uint256 oldReserve = _reserves[i]; + uint256 newReserve = calcReserve(_reserves, i, lpTokenSupply, data); + // console.log("oldReserve", oldReserve); + // console.log("newReserve", newReserve); + // console.log("diff", oldReserve - newReserve); + uint256 tokenIDecimal = getTokenDecimal(i, data); + uint256 tokenJDecimal = getTokenDecimal(j, data); + // console.log("Check", (18 + tokenIDecimal - tokenJDecimal)); + // console.log("divPadding", divPadding); + + rate = + ((oldReserve - newReserve) * + (10 ** (18 + tokenIDecimal - tokenJDecimal))) / + divPadding; + } + + /** + * @inheritdoc IBeanstalkWellFunction + */ + function calcReserveAtRatioLiquidity( + uint256[] calldata reserves, + uint256 j, + uint256[] calldata ratios, + bytes calldata + ) external pure returns (uint256 reserve) { + uint256 i = j == 1 ? 0 : 1; + reserve = (reserves[i] * ratios[j]) / ratios[i]; + } + + function name() external pure returns (string memory) { + return "StableSwap"; + } + + function symbol() external pure returns (string memory) { + return "SS2"; + } + + /** + * @notice decodes the data encoded in the well. + * @return Ann (A parameter * n^2) + * @return precisions the value used to scale each token such that + * each token has 18 decimals. + */ + function decodeWFData( + bytes memory data + ) public view virtual returns (uint256 Ann, uint256[] memory precisions) { + WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); + if (wfd.a == 0) revert InvalidAParameter(wfd.a); + if (wfd.token0 == address(0) || wfd.token1 == address(0)) + revert InvalidTokens(); + + uint8 token0Decimals = IERC20(wfd.token0).decimals(); + uint8 token1Decimals = IERC20(wfd.token1).decimals(); + + if (token0Decimals > 18) revert InvalidTokenDecimals(token0Decimals); + if (token1Decimals > 18) revert InvalidTokenDecimals(token1Decimals); + + Ann = wfd.a * N * N * A_PRECISION; + + precisions = new uint256[](2); + precisions[0] = + 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Decimals)); + precisions[1] = + 10 ** (POOL_PRECISION_DECIMALS - uint256(token1Decimals)); + } + + function getTokenDecimal( + uint256 i, + bytes memory data + ) internal view returns (uint256 decimals) { + WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); + return + i == 0 + ? IERC20(wfd.token0).decimals() + : IERC20(wfd.token1).decimals(); + } + + function getPadding( + uint256[] memory reserves, + uint256 i, + uint256 j, + bytes memory data + ) internal view returns (uint256 padding, uint256 divPadding) { + uint256 k = reserves[i] < reserves[j] ? i : j; + + uint256 numDigits = getNumDigits(reserves, k); + + // Set the slippage error equal to the precision. + padding = numDigits / 2; + + uint256 tokenIDecimal = getTokenDecimal(i, data); + uint256 tokenJDecimal = getTokenDecimal(j, data); + + // console.log("tokenIDecimalI", tokenIDecimal); + // console.log("tokenJDecimalJ", tokenJDecimal); + + // console.log("paddings", padding); + if (tokenJDecimal > tokenIDecimal) { + divPadding = 10 ** padding; + // console.log("paddings", padding); + padding = padding + tokenJDecimal - tokenIDecimal; + } else { + divPadding = 10 ** (padding + (tokenIDecimal - tokenJDecimal)); + } + // console.log("paddings", padding); + + if (padding > tokenJDecimal) { + padding = tokenJDecimal; + } + // console.log("paddings", padding); + padding = 10 ** padding; + + // 10000001/10000000 + + // 10000000 + + // 100001/99999 = 1.0000200002 -> 4 zeroes. + // 1000001/99999 = 10.0001100011 -> 4 zeroes. + // 10000001/99999 = 100.0010100101 -> 4 zeroes + // 100001/999999 = 0.1000011 -> 4 zeros. + // 100001/9999999 = 0.010000101 -> 4 zeroes + // 100001/99999999 = 0.00100001001 -> 4 zeroes + } + + function getNumDigits( + uint256[] memory reserves, + uint256 i + ) internal pure returns (uint256 numDigits) { + numDigits = 0; + uint256 reserve = reserves[i]; + while (reserve != 0) { + reserve /= 10; + numDigits++; + } + } + + /** + * @notice scale `reserves` by `precision`. + * @dev this sets both reserves to 18 decimals. + */ + function getScaledReserves( + uint[] memory reserves, + uint256[] memory precisions + ) internal pure returns (uint[] memory) { + reserves[0] = reserves[0].mul(precisions[0]); + reserves[1] = reserves[1].mul(precisions[1]); + return reserves; + } + + function _calcReserve( + uint256 reserve, + uint256 b, + uint256 c, + uint256 lpTokenSupply + ) private pure returns (uint256) { + return + reserve.mul(reserve).add(c).div( + reserve.mul(2).add(b).sub(lpTokenSupply) + ); + } + + function getBandC( + uint256 Ann, + uint256 lpTokenSupply, + uint256 reserves + ) private pure returns (uint256 c, uint256 b) { + c = lpTokenSupply + .mul(lpTokenSupply) + .div(reserves.mul(N)) + .mul(lpTokenSupply) + .mul(A_PRECISION) + .div(Ann.mul(N)); + b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); + } +} diff --git a/src/functions/SolidlyStableSwap2.sol b/src/functions/SolidlyStableSwap2.sol new file mode 100644 index 00000000..e92092ea --- /dev/null +++ b/src/functions/SolidlyStableSwap2.sol @@ -0,0 +1,155 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +// import {IWellFunction} from "src/interfaces/IWellFunction.sol"; +// import {LibMath} from "src/libraries/LibMath.sol"; +// import {SafeMath} from "oz/utils/math/SafeMath.sol"; +// import {IERC20} from "forge-std/interfaces/IERC20.sol"; + +// /** +// * @author Brean +// * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. +// * developed by Solidly/Aerodome: https://github.com/aerodrome-finance/contracts +// * +// * Stableswap Wells with 2 tokens use the formula: +// * `d = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` +// * +// * Where: +// * `d` is the supply of LP tokens +// * `b_i` is the reserve at index `i` +// */ +// contract CurveStableSwap2 is IWellFunction { +// using LibMath for uint; +// using SafeMath for uint; + +// uint256 constant PRECISION = 1e18; + +// /** +// * @notice Calculates the `j`th reserve given a list of `reserves` and `lpTokenSupply`. +// * @param reserves A list of token reserves. The jth reserve will be ignored, but a placeholder must be provided. +// * @param j The index of the reserve to solve for +// * @param lpTokenSupply The supply of LP tokens +// * @param data Extra Well function data provided on every call +// * @return reserve The resulting reserve at the jth index +// * @dev Should round up to ensure that Well reserves are marginally higher to enforce calcLpTokenSupply(...) >= totalSupply() +// */ +// function calcReserve( +// uint256[] memory reserves, +// uint256 j, +// uint256 lpTokenSupply, +// bytes calldata data +// ) external view returns (uint256 reserve); + +// /** +// * @notice Gets the LP token supply given a list of reserves. +// * @param reserves A list of token reserves +// * @param data Extra Well function data provided on every call +// * @return lpTokenSupply The resulting supply of LP tokens +// * @dev Should round down to ensure so that the Well Token supply is marignally lower to enforce calcLpTokenSupply(...) >= totalSupply() +// */ +// function calcLpTokenSupply( +// uint256[] memory reserves, +// bytes calldata data +// ) external view returns (uint256 lpTokenSupply); + +// /** +// * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. +// * @param lpTokenAmount An amount of LP tokens +// * @param reserves A list of token reserves +// * @param lpTokenSupply The current supply of LP tokens +// * @param data Extra Well function data provided on every call +// * @return underlyingAmounts The amount of each reserve token that underlies the LP tokens +// * @dev The constraint totalSupply() <= calcLPTokenSupply(...) must be held in the case where +// * `lpTokenAmount` LP tokens are burned in exchanged for `underlyingAmounts`. If the constraint +// * does not hold, then the Well Function is invalid. +// */ +// function calcLPTokenUnderlying( +// uint256 lpTokenAmount, +// uint256[] memory reserves, +// uint256 lpTokenSupply, +// bytes calldata data +// ) external view returns (uint256[] memory underlyingAmounts) { +// // overflow cannot occur as lpTokenAmount could not be calculated. +// underlyingAmounts[0] = (lpTokenAmount * reserves[0]) / lpTokenSupply; +// underlyingAmounts[1] = (lpTokenAmount * reserves[1]) / lpTokenSupply; +// }; + +// /** +// * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. +// * assumes that reserve_j is being swapped for other reserves in the Well. +// * @dev used by Beanstalk to calculate the deltaB every Season +// * @param reserves The reserves of the Well +// * @param j The index of the reserve to solve for +// * @param ratios The ratios of reserves to solve for +// * @param data Well function data provided on every call +// * @return reserve The resulting reserve at the jth index +// */ +// function calcReserveAtRatioSwap( +// uint256[] calldata reserves, +// uint256 j, +// uint256[] calldata ratios, +// bytes calldata data +// ) external view returns (uint256 reserve); + +// /** +// * @inheritdoc IMultiFlowPumpWellFunction +// * @dev Implmentation from: https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L460 +// */ +// function calcRate( +// uint256[] calldata reserves, +// uint256 i, +// uint256 j, +// bytes calldata data +// ) external view returns (uint256 rate) { +// uint256[] memory _reserves = reserves; +// uint256 xy = _k(_reserves[0], _reserves[1]); +// _reserves[0] = (_reserves[0] * PRECISION) / decimals0; +// _reserves[1] = (_reserves[1] * PRECISION) / decimals1; +// (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); +// amountIn = tokenIn == token0 ? (amountIn * PRECISION) / decimals0 : (amountIn * PRECISION) / decimals1; +// uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB); +// return (y * (tokenIn == token0 ? decimals1 : decimals0)) / PRECISION; +// }; + +// /** +// * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. +// * assumes that reserve_j is being added or removed in exchange for LP Tokens. +// * @dev used by Beanstalk to calculate the max deltaB that can be converted in/out of a Well. +// * @param reserves The reserves of the Well +// * @param j The index of the reserve to solve for +// * @param ratios The ratios of reserves to solve for +// * @param data Well function data provided on every call +// * @return reserve The resulting reserve at the jth index +// */ +// function calcReserveAtRatioLiquidity( +// uint256[] calldata reserves, +// uint256 j, +// uint256[] calldata ratios, +// bytes calldata data +// ) external view returns (uint256 reserve); + +// /** +// * @notice returns k, based on the reserves of x/y. +// * @param x the reserves of `x` +// * @param y the reserves of `y`. +// * +// * @dev Implmentation from: +// * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L315 +// */ +// function _k(uint256 x, uint256 y) internal view returns (uint256) { +// uint256 _x = (x * PRECISION) / decimals0; +// uint256 _y = (y * PRECISION) / decimals1; +// uint256 _a = (_x * _y) / PRECISION; +// uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); +// return (_a * _b) / PRECISION; // x3y+y3x >= k +// } + +// function name() external pure override returns (string memory) { +// return "Solidly-StableSwap"; +// } + +// function symbol() external pure override returns (string memory) { +// return "SSS2"; +// } +// } diff --git a/src/functions/StableSwap2.sol b/src/functions/StableSwap2.sol deleted file mode 100644 index d6a3ce99..00000000 --- a/src/functions/StableSwap2.sol +++ /dev/null @@ -1,232 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import {IWellFunction} from "src/interfaces/IWellFunction.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; -import {SafeMath} from "oz/utils/math/SafeMath.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; - -/** - * @author Brean - * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. - * developed by curve. - * - * Stableswap Wells with 2 tokens use the formula: - * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` - * - * Where: - * `A` is the Amplication parameter. - * `D` is the supply of LP tokens - * `b_i` is the reserve at index `i` - */ -contract StableSwap2 is IWellFunction { - using LibMath for uint; - using SafeMath for uint; - - // 2 token Pool. - uint constant N = 2; - - // A precision - uint constant A_PRECISION = 100; - - // Precision that all pools tokens will be converted to. - uint constant POOL_PRECISION_DECIMALS = 18; - - // Maximum A parameter. - uint constant MAX_A = 10000 * A_PRECISION; - - // Errors - error InvalidAParameter(uint256); - error InvalidTokens(); - error InvalidTokenDecimals(uint256); - - /** - * This Well function requires 3 parameters from wellFunctionData: - * 1: A parameter - * 2: tkn0 address - * 3: tkn1 address - * - * @dev The StableSwap curve assumes that both tokens use the same decimals (max 1e18). - * tkn0 and tkn1 is used to call decimals() on the tokens to scale to 1e18. - * For example, USDC and BEAN has 6 decimals (TKX_SCALAR = 1e12), - * while DAI has 18 decimals (TKX_SCALAR = 1). - */ - struct WellFunctionData { - uint256 a; // A parameter - address tkn0; - address tkn1; - } - - /** - * @notice Calculate the amount of LP tokens minted when adding liquidity. - * D invariant calculation in non-overflowing integer operations iteratively - * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - * - * Converging solution: - * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) - */ - function calcLpTokenSupply( - uint[] memory reserves, - bytes calldata _wellFunctionData - ) public view override returns (uint lpTokenSupply) { - (uint256 Ann,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); - reserves = getScaledReserves(reserves, precisions); - - uint256 sumReserves = reserves[0] + reserves[1]; - if(sumReserves == 0) return 0; - lpTokenSupply = sumReserves; - - for(uint i = 0; i < 255; i++){ - uint256 dP = lpTokenSupply; - // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(reserves[0].mul(N)); - dP = dP.mul(lpTokenSupply).div(reserves[1].mul(N)); - uint256 prevReserves = lpTokenSupply; - lpTokenSupply = Ann - .mul(sumReserves) - .div(A_PRECISION) - .add(dP.mul(N)) - .mul(lpTokenSupply) - .div( - Ann.sub(A_PRECISION).mul(lpTokenSupply) - .div(A_PRECISION) - .add(N.add(1).mul(dP)) - ); - // Equality with the precision of 1 - if (lpTokenSupply > prevReserves){ - if(lpTokenSupply - prevReserves <= 1) return lpTokenSupply; - } - else { - if(prevReserves - lpTokenSupply <= 1) return lpTokenSupply; - } - } - } - - /** - * @notice Calculate x[i] if one reduces D from being calculated for xp to D - * Done by solving quadratic equation iteratively. - * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - * x_1**2 + b*x_1 = c - * x_1 = (x_1**2 + c) / (2*x_1 + b) - * @dev This function has a precision of +/- 1, - * which may round in favor of the well or the user. - */ - function calcReserve( - uint[] memory reserves, - uint j, - uint lpTokenSupply, - bytes calldata _wellFunctionData - ) external view override returns (uint reserve) { - (uint256 Ann, uint256[2] memory precisions) = decodeWFData(_wellFunctionData); - reserves = getScaledReserves(reserves, precisions); - - - // avoid stack too deep errors. - (uint256 c, uint256 b) = getBandC( - Ann, - lpTokenSupply, - j == 0 ? reserves[1] : reserves[0] - ); - reserve = lpTokenSupply; - uint256 prevReserve; - - for(uint i; i < 255; ++i){ - prevReserve = reserve; - reserve = _calcReserve(reserve, b, c, lpTokenSupply); - // Equality with the precision of 1 - // scale reserve down to original precision - if(reserve > prevReserve){ - if(reserve - prevReserve <= 1) return reserve.div(precisions[j]); - - } else { - if(prevReserve - reserve <= 1) return reserve.div(precisions[j]); - } - } - revert("did not find convergence"); - } - - /** - * @notice Defines a proportional relationship between the supply of LP tokens - * and the amount of each underlying token for a two-token Well. - * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user - * recieves `s * b_i / S` of each underlying token. - * reserves are scaled as needed based on tknXScalar - */ - function calcLPTokenUnderlying( - uint lpTokenAmount, - uint[] memory reserves, - uint lpTokenSupply, - bytes calldata _wellFunctionData - ) external view returns (uint[] memory underlyingAmounts) { - (,uint256[2] memory precisions) = decodeWFData(_wellFunctionData); - reserves = getScaledReserves(reserves, precisions); - - underlyingAmounts = new uint[](2); - // overflow cannot occur as lpTokenAmount could not be calculated. - underlyingAmounts[0] = lpTokenAmount * reserves[0] / lpTokenSupply; - underlyingAmounts[1] = lpTokenAmount * reserves[1] / lpTokenSupply; - } - - function name() external pure override returns (string memory) { - return "StableSwap"; - } - - function symbol() external pure override returns (string memory) { - return "SS2"; - } - - function decodeWFData( - bytes memory data - ) public virtual view returns ( - uint256 Ann, - uint256[2] memory precisions - ){ - WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - if (wfd.a == 0) revert InvalidAParameter(wfd.a); - if(wfd.tkn0 == address(0) || wfd.tkn1 == address(0)) revert InvalidTokens(); - uint8 token0Dec = IERC20(wfd.tkn0).decimals(); - uint8 token1Dec = IERC20(wfd.tkn1).decimals(); - - if(token0Dec > 18) revert InvalidTokenDecimals(token0Dec); - if(token1Dec > 18) revert InvalidTokenDecimals(token1Dec); - - Ann = wfd.a * N * N * A_PRECISION; - precisions[0] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); - precisions[1] = 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Dec)); - } - - function getScaledReserves( - uint[] memory reserves, - uint256[2] memory precisions - ) internal pure returns (uint[] memory) { - reserves[0] = reserves[0].mul(precisions[0]); - reserves[1] = reserves[1].mul(precisions[1]); - return reserves; - } - - function _calcReserve( - uint256 reserve, - uint256 b, - uint256 c, - uint256 lpTokenSupply - ) private pure returns (uint256){ - return reserve - .mul(reserve) - .add(c) - .div(reserve.mul(2).add(b).sub(lpTokenSupply)); - } - - function getBandC( - uint256 Ann, - uint256 lpTokenSupply, - uint256 sumReserves - ) private pure returns (uint256 c, uint256 b){ - c = lpTokenSupply - .mul(lpTokenSupply).div(sumReserves.mul(N)) - .mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - b = sumReserves.add( - lpTokenSupply.mul(A_PRECISION).div(Ann) - ); - } -} diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 962841f7..558a4053 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -13,7 +13,7 @@ import {Users} from "test/helpers/Users.sol"; import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; -import {StableSwap2} from "src/functions/StableSwap2.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; @@ -78,11 +78,19 @@ abstract contract TestHelper is Test, WellDeployer { setupWell(n, deployWellFunction(), _pumps); } - function setupWell(uint256 n, Call memory _wellFunction, Call[] memory _pumps) internal { + function setupWell( + uint256 n, + Call memory _wellFunction, + Call[] memory _pumps + ) internal { setupWell(_wellFunction, _pumps, deployMockTokens(n)); } - function setupWell(Call memory _wellFunction, Call[] memory _pumps, IERC20[] memory _tokens) internal { + function setupWell( + Call memory _wellFunction, + Call[] memory _pumps, + IERC20[] memory _tokens + ) internal { tokens = _tokens; wellFunction = _wellFunction; for (uint256 i; i < _pumps.length; i++) { @@ -93,7 +101,14 @@ abstract contract TestHelper is Test, WellDeployer { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); + well = encodeAndBoreWell( + address(aquifer), + wellImplementation, + tokens, + _wellFunction, + _pumps, + bytes32(0) + ); // Mint mock tokens to user mintTokens(user, initialLiquidity); @@ -110,7 +125,10 @@ abstract contract TestHelper is Test, WellDeployer { } function setupWellWithFeeOnTransfer(uint256 n) internal { - Call memory _wellFunction = Call(address(new ConstantProduct2()), new bytes(0)); + Call memory _wellFunction = Call( + address(new ConstantProduct2()), + new bytes(0) + ); Call[] memory _pumps = new Call[](2); _pumps[0].target = address(new MockPump()); _pumps[0].data = new bytes(1); @@ -127,27 +145,24 @@ abstract contract TestHelper is Test, WellDeployer { user2 = _user[1]; } - function setupStableSwapWell(uint256 a) internal { + function setupStableSwapWell(uint256 a) internal { setupStableSwapWell(a, deployPumps(1), deployMockTokens(2)); } function setupStableSwapWell( - uint256 a, - Call[] memory _pumps, + uint256 a, + Call[] memory _pumps, IERC20[] memory _tokens ) internal { // encode wellFunction Data - bytes memory wellFunctionData = abi.encode( - a, - _tokens[0], - _tokens[1] - ); + bytes memory wellFunctionData = abi.encode(a, _tokens[0], _tokens[1]); Call memory _wellFunction = Call( - address(new StableSwap2()), + address(new CurveStableSwap2()), wellFunctionData ); tokens = _tokens; wellFunction = _wellFunction; + vm.label(address(wellFunction.target), "CurveStableSwap2 WF"); for (uint i = 0; i < _pumps.length; i++) { pumps.push(_pumps[i]); } @@ -156,7 +171,15 @@ abstract contract TestHelper is Test, WellDeployer { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); + well = encodeAndBoreWell( + address(aquifer), + wellImplementation, + tokens, + _wellFunction, + _pumps, + bytes32(0) + ); + vm.label(address(well), "CurveStableSwap2Well"); // Mint mock tokens to user mintTokens(user, initialLiquidity); @@ -175,7 +198,9 @@ abstract contract TestHelper is Test, WellDeployer { //////////// Test Tokens //////////// /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokens(uint256 n) internal returns (IERC20[] memory _tokens) { + function deployMockTokens( + uint256 n + ) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockToken(i); @@ -183,17 +208,34 @@ abstract contract TestHelper is Test, WellDeployer { } function deployMockToken(uint256 i) internal returns (IERC20) { - return IERC20( - new MockToken( - string.concat("Token ", i.toString()), // name - string.concat("TOKEN", i.toString()), // symbol - 18 // decimals - ) - ); + return + IERC20( + new MockToken( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + 18 // decimals + ) + ); + } + + function deployMockTokenWithDecimals( + uint256 i, + uint8 decimals + ) internal returns (IERC20) { + return + IERC20( + new MockToken( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + decimals // decimals + ) + ); } /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokensFeeOnTransfer(uint256 n) internal returns (IERC20[] memory _tokens) { + function deployMockTokensFeeOnTransfer( + uint256 n + ) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockTokenFeeOnTransfer(i); @@ -201,13 +243,14 @@ abstract contract TestHelper is Test, WellDeployer { } function deployMockTokenFeeOnTransfer(uint256 i) internal returns (IERC20) { - return IERC20( - new MockTokenFeeOnTransfer( - string.concat("Token ", i.toString()), // name - string.concat("TOKEN", i.toString()), // symbol - 18 // decimals - ) - ); + return + IERC20( + new MockTokenFeeOnTransfer( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + 18 // decimals + ) + ); } /// @dev mint mock tokens to each recipient @@ -225,14 +268,19 @@ abstract contract TestHelper is Test, WellDeployer { } /// @dev approve `spender` to use `owner` tokens - function approveMaxTokens(address owner, address spender) internal prank(owner) { + function approveMaxTokens( + address owner, + address spender + ) internal prank(owner) { for (uint256 i; i < tokens.length; i++) { tokens[i].approve(spender, type(uint256).max); } } /// @dev gets the first `n` mock tokens - function getTokens(uint256 n) internal view returns (IERC20[] memory _tokens) { + function getTokens( + uint256 n + ) internal view returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; ++i) { _tokens[i] = tokens[i]; @@ -246,12 +294,17 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = new bytes(0); } - function deployWellFunction(address _target) internal pure returns (Call memory _wellFunction) { + function deployWellFunction( + address _target + ) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = new bytes(0); } - function deployWellFunction(address _target, bytes memory _data) internal pure returns (Call memory _wellFunction) { + function deployWellFunction( + address _target, + bytes memory _data + ) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = _data; } @@ -269,13 +322,19 @@ abstract contract TestHelper is Test, WellDeployer { return address(new Well()); } - function mintAndAddLiquidity(address to, uint256[] memory amounts) internal { + function mintAndAddLiquidity( + address to, + uint256[] memory amounts + ) internal { mintTokens(user, amounts); well.addLiquidity(amounts, 0, to, type(uint256).max); } /// @dev add the same `amount` of liquidity for all underlying tokens - function addLiquidityEqualAmount(address from, uint256 amount) internal prank(from) { + function addLiquidityEqualAmount( + address from, + uint256 amount + ) internal prank(from) { uint256[] memory amounts = new uint256[](tokens.length); for (uint256 i; i < tokens.length; i++) { amounts[i] = amount; @@ -287,7 +346,10 @@ abstract contract TestHelper is Test, WellDeployer { /// @dev get `account` balance of each token, lp token, total lp token supply /// @dev uses global tokens but not global well - function getBalances(address account, Well _well) internal view returns (Balances memory balances) { + function getBalances( + address account, + Well _well + ) internal view returns (Balances memory balances) { uint256[] memory tokenBalances = new uint256[](tokens.length); for (uint256 i; i < tokenBalances.length; ++i) { tokenBalances[i] = tokens[i].balanceOf(account); @@ -323,7 +385,11 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "IERC20[] mismatch"); } - function assertEq(IERC20[] memory a, IERC20[] memory b, string memory err) internal { + function assertEq( + IERC20[] memory a, + IERC20[] memory b, + string memory err + ) internal { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload @@ -334,7 +400,11 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "Call mismatch"); } - function assertEq(Call memory a, Call memory b, string memory err) internal { + function assertEq( + Call memory a, + Call memory b, + string memory err + ) internal { assertEq(a.target, b.target, err); assertEq(a.data, b.data, err); } @@ -343,18 +413,31 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "Call[] mismatch"); } - function assertEq(Call[] memory a, Call[] memory b, string memory err) internal { + function assertEq( + Call[] memory a, + Call[] memory b, + string memory err + ) internal { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload } } - function assertApproxEqRelN(uint256 a, uint256 b, uint256 precision) internal virtual { + function assertApproxEqRelN( + uint256 a, + uint256 b, + uint256 precision + ) internal virtual { assertApproxEqRelN(a, b, 1, precision); } - function assertApproxLeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { + function assertApproxLeRelN( + uint256 a, + uint256 b, + uint256 precision, + uint256 absoluteError + ) internal { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -375,7 +458,12 @@ abstract contract TestHelper is Test, WellDeployer { } } - function assertApproxGeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { + function assertApproxGeRelN( + uint256 a, + uint256 b, + uint256 precision, + uint256 absoluteError + ) internal { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -410,16 +498,28 @@ abstract contract TestHelper is Test, WellDeployer { emit log("Error: a ~= b not satisfied [uint]"); emit log_named_uint(" Expected", b); emit log_named_uint(" Actual", a); - emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, precision); - emit log_named_decimal_uint(" % Delta", percentDelta, precision); + emit log_named_decimal_uint( + " Max % Delta", + maxPercentDelta, + precision + ); + emit log_named_decimal_uint( + " % Delta", + percentDelta, + precision + ); fail(); } } - function percentDeltaN(uint256 a, uint256 b, uint256 precision) internal pure returns (uint256) { + function percentDeltaN( + uint256 a, + uint256 b, + uint256 precision + ) internal pure returns (uint256) { uint256 absDelta = stdMath.delta(a, b); - return absDelta * (10 ** precision) / b; + return (absDelta * (10 ** precision)) / b; } function _newSnapshot() internal view returns (Snapshot memory snapshot) { @@ -433,8 +533,11 @@ abstract contract TestHelper is Test, WellDeployer { Call memory _wellFunction = IWell(_well).wellFunction(); assertLe( IERC20(_well).totalSupply(), - IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), - 'totalSupply() is greater than calcLpTokenSupply()' + IWellFunction(_wellFunction.target).calcLpTokenSupply( + _reserves, + _wellFunction.data + ), + "totalSupply() is greater than calcLpTokenSupply()" ); } @@ -443,10 +546,17 @@ abstract contract TestHelper is Test, WellDeployer { Call memory _wellFunction = IWell(_well).wellFunction(); assertApproxEqAbs( IERC20(_well).totalSupply(), - IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), - 2); + IWellFunction(_wellFunction.target).calcLpTokenSupply( + _reserves, + _wellFunction.data + ), + 2 + ); } - function getPrecisionForReserves(uint256[] memory reserves) internal pure returns (uint256 precision) { + + function getPrecisionForReserves( + uint256[] memory reserves + ) internal pure returns (uint256 precision) { precision = type(uint256).max; for (uint256 i; i < reserves.length; ++i) { uint256 logReserve = reserves[i].log10(); @@ -454,7 +564,9 @@ abstract contract TestHelper is Test, WellDeployer { } } - function uint2ToUintN(uint256[2] memory input) internal pure returns (uint256[] memory out) { + function uint2ToUintN( + uint256[2] memory input + ) internal pure returns (uint256[] memory out) { out = new uint256[](input.length); for (uint256 i; i < input.length; i++) { out[i] = input[i]; diff --git a/test/Well.Shift.t.sol b/test/Well.Shift.t.sol index ad7d9c8f..083cfd0a 100644 --- a/test/Well.Shift.t.sol +++ b/test/Well.Shift.t.sol @@ -6,7 +6,12 @@ import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellShiftTest is TestHelper { - event Shift(uint256[] reserves, IERC20 toToken, uint256 amountOut, address recipient); + event Shift( + uint256[] reserves, + IERC20 toToken, + uint256 amountOut, + address recipient + ); ConstantProduct2 cp; @@ -21,22 +26,43 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received token0" + ); + assertEq( + wellBalanceBeforeShift.tokens[1], + 1000e18, + "Well should have NOT have received token1" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that `_user` has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); uint256 minAmountOut = well.getShiftOut(tokens[1]); uint256[] memory calcReservesAfter = new uint256[](2); calcReservesAfter[0] = tokens[0].balanceOf(address(well)); - calcReservesAfter[1] = tokens[1].balanceOf(address(well)) - minAmountOut; + calcReservesAfter[1] = + tokens[1].balanceOf(address(well)) - + minAmountOut; vm.expectEmit(true, true, true, true); emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); @@ -44,16 +70,38 @@ contract WellShiftTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained token1 - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); - assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); + assertEq( + userBalanceAfterShift.tokens[0], + 0, + "User should NOT have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + amtOut, + "User should have gained token1" + ); + assertTrue( + userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], + "User should have more token1" + ); // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); // The difference has been sent to _user. assertEq( @@ -75,20 +123,37 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received tokens" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); uint256 minAmountOut = well.getShiftOut(tokens[0]); uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = tokens[0].balanceOf(address(well)) - minAmountOut; + calcReservesAfter[0] = + tokens[0].balanceOf(address(well)) - + minAmountOut; calcReservesAfter[1] = tokens[1].balanceOf(address(well)); vm.expectEmit(true, true, true, true); @@ -98,17 +163,34 @@ contract WellShiftTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained token0 - assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); assertEq( - userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" + userBalanceAfterShift.tokens[0], + amount, + "User should have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + userBalanceBeforeShift.tokens[1], + "User should NOT have gained token1" ); // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); assertEq( userBalanceAfterShift.tokens[0], @@ -120,30 +202,64 @@ contract WellShiftTest is TestHelper { /// @dev Calling shift() on a balanced Well should do nothing. function test_shift_balanced_pool() public prank(user) { - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + wellBalanceBeforeShift.tokens[1], + "Well should should be balanced" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); well.shift(tokens[1], 0, _user); uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained neither token - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); + assertEq( + userBalanceAfterShift.tokens[0], + 0, + "User should NOT have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + 0, + "User should NOT have gained token1" + ); // Reserves should equal balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); checkInvariant(address(well)); } @@ -152,12 +268,29 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received token0" + ); + assertEq( + wellBalanceBeforeShift.tokens[1], + 1000e18, + "Well should have NOT have received token1" + ); uint256 amountOut = well.getShiftOut(tokens[1]); - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageOut.selector, + amountOut, + type(uint256).max + ) + ); well.shift(tokens[1], type(uint256).max, user); } } diff --git a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol deleted file mode 100644 index 498c9eb8..00000000 --- a/test/beanstalk/BeanstalkStableSwap2.calcReserveAtRatioSwap.t.sol +++ /dev/null @@ -1,120 +0,0 @@ -// // SPDX-License-Identifier: MIT -// pragma solidity ^0.8.17; - -// import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -// import {BeanstalkStableSwap} from "src/functions/BeanstalkStableSwap.sol"; -// import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; - - -// /// @dev Tests the {ConstantProduct2} Well function directly. -// contract BeanstalkStableSwapSwapTest is TestHelper { -// IBeanstalkWellFunction _f; -// bytes data; - -// //////////// SETUP //////////// - -// function setUp() public { -// _f = new BeanstalkStableSwap(); -// IERC20[] memory _token = deployMockTokens(2); -// data = abi.encode( -// address(_token[0]), -// address(_token[1]) -// ); -// } - -// function test_calcReserveAtRatioSwap_equal_equal() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 100; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 1; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 100); -// assertEq(reserve1, 100); -// } - -// function test_calcReserveAtRatioSwap_equal_diff() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 50; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 1; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 74); -// assertEq(reserve1, 74); -// } - -// function test_calcReserveAtRatioSwap_diff_equal() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 100; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 2; -// ratios[1] = 1; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 141); -// assertEq(reserve1, 70); -// } - -// function test_calcReserveAtRatioSwap_diff_diff() public { -// uint256[] memory reserves = new uint256[](2); -// reserves[0] = 50; -// reserves[1] = 100; -// uint256[] memory ratios = new uint256[](2); -// ratios[0] = 12_984_712_098_520; -// ratios[1] = 12_984_712_098; - -// uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); -// uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - -// assertEq(reserve0, 2236); -// assertEq(reserve1, 2); -// } - -// function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { -// // Upper bound is limited by stableSwap, -// // due to the stableswap reserves being extremely far apart. -// reserves[0] = bound(reserves[0], 1e18, 1e31); -// reserves[1] = bound(reserves[1], 1e18, 1e31); -// ratios[0] = 1e18; -// ratios[1] = 2e18; - - -// uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); -// console.log("lpTokenSupply:", lpTokenSupply); - -// uint256[] memory reservesOut = new uint256[](2); -// for (uint256 i; i < 2; ++i) { -// reservesOut[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); -// } -// console.log("reservesOut 0:", reservesOut[0]); -// console.log("reservesOut 1:", reservesOut[1]); - - -// // Get LP token supply with bound reserves. -// uint256 lpTokenSupplyOut = _f.calcLpTokenSupply(reservesOut, data); -// console.log("lpTokenSupplyOut:", lpTokenSupplyOut); -// // Precision is set to the minimum number of digits of the reserves out. -// uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) -// ? numDigits(reservesOut[1]) -// : numDigits(reservesOut[0]); - -// // Check LP Token Supply after = lp token supply before. -// // assertApproxEq(lpTokenSupplyOut, lpTokenSupply, 2, precision); -// assertApproxEqRel(lpTokenSupplyOut,lpTokenSupply, 0.01*1e18); - -// // Check ratio of `reservesOut` = ratio of `ratios`. -// // assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reservesOut[1], 2, precision); -// } -// } diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol new file mode 100644 index 00000000..9fbae568 --- /dev/null +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol @@ -0,0 +1,198 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + +/// @dev Tests the {ConstantProduct2} Well function directly. +contract CurveStableSwap2LiquidityTest is TestHelper { + IBeanstalkWellFunction _f; + bytes data; + + //////////// SETUP //////////// + + function setUp() public { + _f = new CurveStableSwap2(); + IERC20[] memory _token = deployMockTokens(2); + data = abi.encode(10, address(_token[0]), address(_token[1])); + } + + function test_calcReserveAtRatioLiquidity_equal_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity( + reserves, + 0, + ratios, + data + ); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity( + reserves, + 1, + ratios, + data + ); + + assertEq(reserve0, 100); + assertEq(reserve1, 100); + } + + function test_calcReserveAtRatioLiquidity_equal_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity( + reserves, + 0, + ratios, + data + ); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity( + reserves, + 1, + ratios, + data + ); + + assertEq(reserve0, 100); + assertEq(reserve1, 50); + } + + function test_calcReserveAtRatioLiquidity_diff_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 2; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity( + reserves, + 0, + ratios, + data + ); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity( + reserves, + 1, + ratios, + data + ); + + assertEq(reserve0, 200); + assertEq(reserve1, 50); + } + + function test_calcReserveAtRatioLiquidity_diff_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50; + reserves[1] = 100; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 2; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity( + reserves, + 0, + ratios, + data + ); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity( + reserves, + 1, + ratios, + data + ); + + assertEq(reserve0, 200); + assertEq(reserve1, 25); + } + + function test_calcReserveAtRatioLiquidity_fuzz( + uint256[2] memory reserves, + uint256[2] memory ratios + ) public { + for (uint256 i; i < 2; ++i) { + // Upper bound is limited by stableSwap, + // due to the stableswap reserves being extremely far apart. + reserves[i] = bound(reserves[i], 1e18, 1e31); + ratios[i] = bound(ratios[i], 1e6, 1e18); + } + + uint256 lpTokenSupply = _f.calcLpTokenSupply( + uint2ToUintN(reserves), + data + ); + console.log(lpTokenSupply); + + uint256[] memory reservesOut = new uint256[](2); + for (uint256 i; i < 2; ++i) { + reservesOut[i] = _f.calcReserveAtRatioLiquidity( + uint2ToUintN(reserves), + i, + uint2ToUintN(ratios), + data + ); + } + + // Precision is set to the minimum number of digits of the reserves out. + uint256 precision = numDigits(reservesOut[0]) > + numDigits(reservesOut[1]) + ? numDigits(reservesOut[1]) + : numDigits(reservesOut[0]); + + // Check ratio of each `reserveOut` to `reserve` with the ratio of `ratios`. + // If inequality doesn't hold, then reserves[1] will be zero + if (ratios[0] * reserves[1] >= ratios[1]) { + assertApproxEqRelN( + reservesOut[0] * ratios[1], + ratios[0] * reserves[1], + 1, + precision + ); + } else { + // Because `roundedDiv` is used. It could round up to 1. + assertApproxEqAbs( + reservesOut[0], + 0, + 1, + "reservesOut[0] should be zero" + ); + } + + // If inequality doesn't hold, then reserves[1] will be zero + if (reserves[0] * ratios[1] >= ratios[0]) { + assertApproxEqRelN( + reserves[0] * ratios[1], + ratios[0] * reservesOut[1], + 1, + precision + ); + } else { + // Because `roundedDiv` is used. It could round up to 1. + assertApproxEqAbs( + reservesOut[1], + 0, + 1, + "reservesOut[1] should be zero" + ); + } + } + + function test_calcReserveAtRatioLiquidity_invalidJ() public { + uint256[] memory reserves = new uint256[](2); + uint256[] memory ratios = new uint256[](2); + vm.expectRevert(); + _f.calcReserveAtRatioLiquidity(reserves, 2, ratios, ""); + } +} diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol new file mode 100644 index 00000000..16f54d0c --- /dev/null +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol @@ -0,0 +1,87 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {console, TestHelper, IERC20} from "test/TestHelper.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; + +/// @dev Tests the {ConstantProduct2} Well function directly. +contract BeanstalkStableSwapSwapTest is TestHelper { + IBeanstalkWellFunction _f; + bytes data; + + //////////// SETUP //////////// + + function setUp() public { + _f = new CurveStableSwap2(); + IERC20[] memory _token = deployMockTokens(2); + data = abi.encode(10, address(_token[0]), address(_token[1])); + } + + function test_calcReserveAtRatioSwap_equal_equal() public { + uint256[] memory reserves = new uint256[](2); + // calcReserveAtRatioSwap requires a minimum value of 10 ** token decimals. + reserves[0] = 100e18; + reserves[1] = 100e18; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + assertEq(reserve0, 100e18); + assertEq(reserve1, 100e18); + } + + function test_calcReserveAtRatioSwap_equal_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 50e18; + reserves[1] = 100e18; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 1; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + // assertEq(reserve0, 74); // 50 + // assertEq(reserve1, 74); // 100 + console.log("reserve0", reserve0); + console.log("reserve1", reserve1); + } + + function test_calcReserveAtRatioSwap_diff_equal() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100e18; + reserves[1] = 100e18; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 2; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + // assertEq(reserve0, 149); + // assertEq(reserve1, 74); + console.log("reserve0", reserve0); + console.log("reserve1", reserve1); + } + + function test_calcReserveAtRatioSwap_diff_diff() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 90; // bean + reserves[1] = 110; // usdc + uint256[] memory ratios = new uint256[](2); + ratios[0] = 110; + ratios[1] = 90; + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + // assertEq(reserve0, 110); + // assertEq(reserve1, 91); + console.log("reserve0", reserve0); + console.log("reserve1", reserve1); + } +} diff --git a/test/functions/StableSwap.t.sol b/test/functions/StableSwap.t.sol index d1c2b623..3e9474eb 100644 --- a/test/functions/StableSwap.t.sol +++ b/test/functions/StableSwap.t.sol @@ -2,35 +2,34 @@ pragma solidity ^0.8.17; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -import {WellFunctionHelper} from "./WellFunctionHelper.sol"; -import {StableSwap2} from "src/functions/StableSwap2.sol"; +import {WellFunctionHelper, IMultiFlowPumpWellFunction} from "./WellFunctionHelper.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; -/// @dev Tests the {StableSwap} Well function directly. -contract StableSwapTest is WellFunctionHelper { +/// @dev Tests the {CurveStableSwap2} Well function directly. +contract CurveStableSwapTest is WellFunctionHelper { /** * State A: Same decimals - * D (lpTokenSupply) should be the summation of + * D (lpTokenSupply) should be the summation of * the reserves, assuming they are equal. - */ + */ uint STATE_A_B0 = 10 * 1e18; uint STATE_A_B1 = 10 * 1e18; uint STATE_A_LP = 20 * 1e18; /** * State B: Different decimals - * @notice the stableswap implmentation + * @notice the stableswap implmentation * uses precision-adjusted to 18 decimals. - * In other words, a token with 6 decimals + * In other words, a token with 6 decimals * will be scaled up such that it uses 18 decimals. - * - * @dev D is the summation of the reserves, - * assuming they are equal. - * - */ + * + * @dev D is the summation of the reserves, + * assuming they are equal. + * + */ uint STATE_B_B0 = 10 * 1e18; uint STATE_B_B1 = 20 * 1e18; uint STATE_B_LP = 29_911_483_643_966_454_823; // ~29e18 - /// State C: Similar decimals uint STATE_C_B0 = 20 * 1e18; @@ -39,17 +38,19 @@ contract StableSwapTest is WellFunctionHelper { /// @dev See {calcLpTokenSupply}. uint MAX_RESERVE = 1e32; - //////////// SETUP //////////// function setUp() public { IERC20[] memory _tokens = deployMockTokens(2); tokens = _tokens; - _function = new StableSwap2(); - + _function = IMultiFlowPumpWellFunction(new CurveStableSwap2()); + + // encode well data with: + // A parameter of 10, + // address of token 0 and 1. _data = abi.encode( - StableSwap2.WellFunctionData( + CurveStableSwap2.WellFunctionData( 10, address(_tokens[0]), address(_tokens[1]) @@ -85,10 +86,7 @@ contract StableSwapTest is WellFunctionHelper { uint[] memory reserves = new uint[](2); reserves[0] = STATE_B_B0; // ex. 1 WETH reserves[1] = STATE_B_B1; // ex. 1250 BEAN - assertEq( - _function.calcLpTokenSupply(reserves, _data), - STATE_B_LP - ); + assertEq(_function.calcLpTokenSupply(reserves, _data), STATE_B_LP); } //////////// RESERVES //////////// @@ -104,14 +102,14 @@ contract StableSwapTest is WellFunctionHelper { reserves[1] = STATE_A_B1; assertEq( _function.calcReserve(reserves, 0, STATE_A_LP, _data), - STATE_A_B0 + STATE_A_B0 ); // find reserves[1] reserves[0] = STATE_A_B0; reserves[1] = 0; assertEq( - _function.calcReserve(reserves, 1, STATE_A_LP, _data), + _function.calcReserve(reserves, 1, STATE_A_LP, _data), STATE_A_B1 ); @@ -155,9 +153,14 @@ contract StableSwapTest is WellFunctionHelper { uint[] memory reserves = new uint[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); reserves[1] = bound(_reserves[1], 10e18, MAX_RESERVE); - + uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); - uint[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, _data); + uint[] memory underlying = _function.calcLPTokenUnderlying( + lpTokenSupply, + reserves, + lpTokenSupply, + _data + ); for (uint i = 0; i < reserves.length; ++i) { assertEq(reserves[i], underlying[i], "reserves mismatch"); } @@ -171,9 +174,8 @@ contract StableSwapTest is WellFunctionHelper { reserves[1] = bound(y, 10e18, MAX_RESERVE); a = bound(a, 1, 1000000); - _data = abi.encode( - StableSwap2.WellFunctionData( + CurveStableSwap2.WellFunctionData( a, address(tokens[0]), address(tokens[1]) @@ -181,8 +183,18 @@ contract StableSwapTest is WellFunctionHelper { ); uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); - uint reserve0 = _function.calcReserve(reserves, 0, lpTokenSupply, _data); - uint reserve1 = _function.calcReserve(reserves, 1, lpTokenSupply, _data); + uint reserve0 = _function.calcReserve( + reserves, + 0, + lpTokenSupply, + _data + ); + uint reserve1 = _function.calcReserve( + reserves, + 1, + lpTokenSupply, + _data + ); if (reserves[0] < 1e12) { assertApproxEqAbs(reserve0, reserves[0], 1); @@ -195,4 +207,32 @@ contract StableSwapTest is WellFunctionHelper { assertApproxEqRel(reserve1, reserves[1], 3e6); } } + + ///////// CALC RATE /////// + + function test_calcRateStable() public { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 1e18; + reserves[1] = 1e18; + assertEq(_function.calcRate(reserves, 0, 1, _data), 0.99767728e18); + assertEq(_function.calcRate(reserves, 1, 0, _data), 1.002328128e18); + } + + function test_calcRateStable6Decimals() public { + _data = abi.encode( + CurveStableSwap2.WellFunctionData( + 10, + address(tokens[0]), + address(deployMockTokenWithDecimals(1, 6)) + ) + ); + uint256[] memory reserves = new uint256[](2); + reserves[0] = 100e18; + reserves[1] = 100e6; + assertEq(_function.calcRate(reserves, 1, 0, _data), 1e6); + assertEq( + _function.calcRate(reserves, 0, 1, _data), + 0.9999952381178706e30 + ); + } } diff --git a/test/integration/IntegrationTestHelper.sol b/test/integration/IntegrationTestHelper.sol index 230c1e1b..7f72d44e 100644 --- a/test/integration/IntegrationTestHelper.sol +++ b/test/integration/IntegrationTestHelper.sol @@ -8,48 +8,26 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {MultiFlowPump} from "src/pumps/MultiFlowPump.sol"; import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; import {Users} from "test/helpers/Users.sol"; -import {TestHelper, Balances, ConstantProduct2, StableSwap2} from "test/TestHelper.sol"; +import {TestHelper, Balances, ConstantProduct2} from "test/TestHelper.sol"; import {from18, to18} from "test/pumps/PumpHelpers.sol"; abstract contract IntegrationTestHelper is TestHelper { using LibContractInfo for address; - function setupWell(IERC20[] memory _tokens, Well _well) internal returns (Well) { - Call[] memory _pumps = new Call[](1); - _pumps[0] = Call(address(new MultiFlowPump()), new bytes(0)); - - return setupWell(_tokens, Call(address(new ConstantProduct2()), new bytes(0)), _pumps, _well); - } - - function setupStableSwapWell( - uint256 a, - IERC20[] memory _tokens, + function setupWell( + IERC20[] memory _tokens, Well _well ) internal returns (Well) { Call[] memory _pumps = new Call[](1); - _pumps[0] = Call( - address(new GeoEmaAndCumSmaPump( - from18(0.5e18), - from18(0.333333333333333333e18), - 12, - from18(0.9e18) - )), - new bytes(0) - ); - - bytes memory data = abi.encode( - StableSwap2.WellFunctionData( - a, - address(_tokens[0]), - address(_tokens[1]) - ) - ); - return setupWell( - _tokens, - Call(address(new StableSwap2()), data), - _pumps, - _well - ); + _pumps[0] = Call(address(new MultiFlowPump()), new bytes(0)); + + return + setupWell( + _tokens, + Call(address(new ConstantProduct2()), new bytes(0)), + _pumps, + _well + ); } function setupWell( @@ -64,7 +42,14 @@ abstract contract IntegrationTestHelper is TestHelper { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - _well = encodeAndBoreWell(address(aquifer), wellImplementation, _tokens, wellFunction, _pumps, bytes32(0)); + _well = encodeAndBoreWell( + address(aquifer), + wellImplementation, + _tokens, + wellFunction, + _pumps, + bytes32(0) + ); // Mint mock tokens to user mintTokens(_tokens, user, initialLiquidity); @@ -78,20 +63,33 @@ abstract contract IntegrationTestHelper is TestHelper { approveMaxTokens(_tokens, address(this), address(_well)); // Add initial liquidity from TestHelper - addLiquidityEqualAmount(_tokens, address(this), initialLiquidity, Well(_well)); + addLiquidityEqualAmount( + _tokens, + address(this), + initialLiquidity, + Well(_well) + ); return _well; } /// @dev mint mock tokens to each recipient - function mintTokens(IERC20[] memory _tokens, address recipient, uint256 amount) internal { + function mintTokens( + IERC20[] memory _tokens, + address recipient, + uint256 amount + ) internal { for (uint256 i; i < _tokens.length; i++) { deal(address(_tokens[i]), recipient, amount); } } /// @dev approve `spender` to use `owner` tokens - function approveMaxTokens(IERC20[] memory _tokens, address owner, address spender) internal prank(owner) { + function approveMaxTokens( + IERC20[] memory _tokens, + address owner, + address spender + ) internal prank(owner) { for (uint256 i; i < _tokens.length; i++) { _tokens[i].approve(spender, type(uint256).max); } @@ -136,13 +134,17 @@ abstract contract IntegrationTestHelper is TestHelper { uint256 pasteIndex ) internal pure returns (bytes memory stuff) { uint256 clipboardData; - clipboardData = clipboardData | uint256(_type) << 248; + clipboardData = clipboardData | (uint256(_type) << 248); - clipboardData = clipboardData | returnDataIndex << 160 | (copyIndex * 32) + 32 << 80 | (pasteIndex * 32) + 36; + clipboardData = + clipboardData | + (returnDataIndex << 160) | + (((copyIndex * 32) + 32) << 80) | + ((pasteIndex * 32) + 36); if (useEther) { // put 0x1 in second byte // shift left 30 bytes - clipboardData = clipboardData | 1 << 240; + clipboardData = clipboardData | (1 << 240); return abi.encodePacked(clipboardData, amount); } else { return abi.encodePacked(clipboardData); diff --git a/test/integration/interfaces/ICurve.sol b/test/integration/interfaces/ICurve.sol index e46dae32..6c46ee5c 100644 --- a/test/integration/interfaces/ICurve.sol +++ b/test/integration/interfaces/ICurve.sol @@ -1,51 +1,160 @@ // SPDX-License-Identifier: MIT pragma experimental ABIEncoderV2; -pragma solidity 0.8.17; +pragma solidity 0.8.20; interface ICurvePool { function A_precise() external view returns (uint256); + function get_balances() external view returns (uint256[2] memory); + function totalSupply() external view returns (uint256); - function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external returns (uint256); - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external returns (uint256); + + function add_liquidity( + uint256[2] memory amounts, + uint256 min_mint_amount + ) external returns (uint256); + + function remove_liquidity_one_coin( + uint256 _token_amount, + int128 i, + uint256 min_amount + ) external returns (uint256); + function balances(int128 i) external view returns (uint256); + function fee() external view returns (uint256); + function coins(uint256 i) external view returns (address); + function get_virtual_price() external view returns (uint256); - function calc_token_amount(uint256[2] calldata amounts, bool deposit) external view returns (uint256); - function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256); - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); - function transfer(address recipient, uint256 amount) external returns (bool); + + function calc_token_amount( + uint256[2] calldata amounts, + bool deposit + ) external view returns (uint256); + + function calc_withdraw_one_coin( + uint256 _token_amount, + int128 i + ) external view returns (uint256); + + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + function exchange_underlying( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); + + function transfer( + address recipient, + uint256 amount + ) external returns (bool); } interface ICurveZap { - function add_liquidity(address _pool, uint256[4] memory _deposit_amounts, uint256 _min_mint_amount) external returns (uint256); - function calc_token_amount(address _pool, uint256[4] memory _amounts, bool _is_deposit) external returns (uint256); + function add_liquidity( + address _pool, + uint256[4] memory _deposit_amounts, + uint256 _min_mint_amount + ) external returns (uint256); + + function calc_token_amount( + address _pool, + uint256[4] memory _amounts, + bool _is_deposit + ) external returns (uint256); } interface ICurvePoolR { - function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver) external returns (uint256); - function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver) external returns (uint256); - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount, address receiver) external returns (uint256); + function exchange( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external returns (uint256); + + function exchange_underlying( + int128 i, + int128 j, + uint256 dx, + uint256 min_dy, + address receiver + ) external returns (uint256); + + function remove_liquidity_one_coin( + uint256 _token_amount, + int128 i, + uint256 min_amount, + address receiver + ) external returns (uint256); } interface ICurvePool2R { - function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); - function remove_liquidity(uint256 _burn_amount, uint256[2] memory _min_amounts, address reciever) external returns (uint256[2] calldata); - function remove_liquidity_imbalance(uint256[2] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); + function add_liquidity( + uint256[2] memory amounts, + uint256 min_mint_amount, + address reciever + ) external returns (uint256); + + function remove_liquidity( + uint256 _burn_amount, + uint256[2] memory _min_amounts, + address reciever + ) external returns (uint256[2] calldata); + + function remove_liquidity_imbalance( + uint256[2] memory _amounts, + uint256 _max_burn_amount, + address reciever + ) external returns (uint256); } interface ICurvePool3R { - function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); - function remove_liquidity(uint256 _burn_amount, uint256[3] memory _min_amounts, address reciever) external returns (uint256[3] calldata); - function remove_liquidity_imbalance(uint256[3] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); + function add_liquidity( + uint256[3] memory amounts, + uint256 min_mint_amount, + address reciever + ) external returns (uint256); + + function remove_liquidity( + uint256 _burn_amount, + uint256[3] memory _min_amounts, + address reciever + ) external returns (uint256[3] calldata); + + function remove_liquidity_imbalance( + uint256[3] memory _amounts, + uint256 _max_burn_amount, + address reciever + ) external returns (uint256); } interface ICurvePool4R { - function add_liquidity(uint256[4] memory amounts, uint256 min_mint_amount, address reciever) external returns (uint256); - function remove_liquidity(uint256 _burn_amount, uint256[4] memory _min_amounts, address reciever) external returns (uint256[4] calldata); - function remove_liquidity_imbalance(uint256[4] memory _amounts, uint256 _max_burn_amount, address reciever) external returns (uint256); + function add_liquidity( + uint256[4] memory amounts, + uint256 min_mint_amount, + address reciever + ) external returns (uint256); + + function remove_liquidity( + uint256 _burn_amount, + uint256[4] memory _min_amounts, + address reciever + ) external returns (uint256[4] calldata); + + function remove_liquidity_imbalance( + uint256[4] memory _amounts, + uint256 _max_burn_amount, + address reciever + ) external returns (uint256); } interface I3Curve { @@ -53,27 +162,66 @@ interface I3Curve { } interface ICurveFactory { - function get_coins(address _pool) external view returns (address[4] calldata); - function get_underlying_coins(address _pool) external view returns (address[8] calldata); + function get_coins( + address _pool + ) external view returns (address[4] calldata); + + function get_underlying_coins( + address _pool + ) external view returns (address[8] calldata); } interface ICurveCryptoFactory { - function get_coins(address _pool) external view returns (address[8] calldata); + function get_coins( + address _pool + ) external view returns (address[8] calldata); } interface ICurvePoolC { - function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256); + function exchange( + uint256 i, + uint256 j, + uint256 dx, + uint256 min_dy + ) external returns (uint256); } interface ICurvePoolNoReturn { - function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external; - function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; - function remove_liquidity(uint256 _burn_amount, uint256[3] memory _min_amounts) external; - function remove_liquidity_imbalance(uint256[3] memory _amounts, uint256 _max_burn_amount) external; - function remove_liquidity_one_coin(uint256 _token_amount, uint256 i, uint256 min_amount) external; + function exchange( + uint256 i, + uint256 j, + uint256 dx, + uint256 min_dy + ) external; + + function add_liquidity( + uint256[3] memory amounts, + uint256 min_mint_amount + ) external; + + function remove_liquidity( + uint256 _burn_amount, + uint256[3] memory _min_amounts + ) external; + + function remove_liquidity_imbalance( + uint256[3] memory _amounts, + uint256 _max_burn_amount + ) external; + + function remove_liquidity_one_coin( + uint256 _token_amount, + uint256 i, + uint256 min_amount + ) external; } interface ICurvePoolNoReturn128 { function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; - function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; + + function remove_liquidity_one_coin( + uint256 _token_amount, + int128 i, + uint256 min_amount + ) external; } diff --git a/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol b/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol deleted file mode 100644 index 6437e679..00000000 --- a/test/stableSwap/IntegrationTestGasComparisonsStableSwap.sol +++ /dev/null @@ -1,263 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.17; - -import {IntegrationTestHelper, IERC20, console, Balances} from "test/integration/IntegrationTestHelper.sol"; -import {ICurvePool, ICurveZap} from "test/integration/interfaces/ICurve.sol"; -import {StableSwap2} from "test/TestHelper.sol"; -import {IPipeline, PipeCall, AdvancedPipeCall, IDepot, From, To} from "test/integration/interfaces/IPipeline.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; -import {Well} from "src/Well.sol"; - -/// @dev Tests gas usage of similar functions across Curve & Wells -contract IntegrationTestGasComparisonsStableSwap is IntegrationTestHelper { - using LibMath for uint256; - - uint256 mainnetFork; - - Well daiBeanWell; - Well daiUsdcWell; - StableSwap2 ss; - bytes data = ""; - - ICurvePool bean3Crv = ICurvePool(0xc9C32cd16Bf7eFB85Ff14e0c8603cc90F6F2eE49); - ICurveZap zap = ICurveZap(0xA79828DF1850E8a3A3064576f380D90aECDD3359); - - IERC20 constant DAI = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - IERC20 constant BEAN = IERC20(0xBEA0000029AD1c77D3d5D23Ba2D8893dB9d1Efab); - IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); - IERC20 constant THREE3CRV = IERC20(0x6c3F90f043a72FA612cbac8115EE7e52BDe6E490); - - - IPipeline pipeline = IPipeline(0xb1bE0000bFdcDDc92A8290202830C4Ef689dCeaa); - IDepot depot = IDepot(0xDEb0f000082fD56C10f449d4f8497682494da84D); - - IERC20[] daiBeanTokens = [DAI, BEAN]; - IERC20[] daiUsdcTokens = [DAI, USDC]; - - - /// @dev pausing gas metrer - function setUp() public { - ss = new StableSwap2(); - - mainnetFork = vm.createSelectFork("mainnet"); - assertEq(vm.activeFork(), mainnetFork); - - // Test contract has 5 * {TestHelper.initialLiquidity}, with an A parameter of 10. - daiBeanWell = Well(setupStableSwapWell(10, daiBeanTokens, daiBeanWell)); - daiUsdcWell = Well(setupStableSwapWell(10, daiUsdcTokens, daiUsdcWell)); - - _wellsInitializedHelper(); - } - - /// @dev Notes on fair comparison: - /// - /// 1. Gas will be dependent on input/output amount if the user's balance or the - /// pool's balance move from zero to non-zero during execution. For example, - /// if the user has no DAI and swaps from BEAN->DAI, extra gas cost is incurred - /// to set their DAI balance from 0 to non-zero. - /// - /// 2. Believe that some tokens don't decrement allowances if infinity is approved. - /// Make sure that approval amounts are the same for each test. - /// - /// 3. The first few swaps in a new Well with a Pump attached will be more expensive, - /// as the Pump will need to be initialized. Perform several swaps before testing - /// to ensure we're at steady-state gas cost. - - //////////////////// COMPARE: BEAN/DAI //////////////////// - - ////////// Wells - - function testFuzz_wells_BeanDai_Swap(uint256 amountIn) public { - vm.pauseGasMetering(); - amountIn = bound(amountIn, 1e18, daiBeanTokens[1].balanceOf(address(this))); - vm.resumeGasMetering(); - - daiBeanWell.swapFrom(daiBeanTokens[1], daiBeanTokens[0], amountIn, 0, address(this), type(uint256).max); - } - - function testFuzz_wells_BeanDaiUsdc_Swap(uint256 amountIn) public { - vm.pauseGasMetering(); - amountIn = bound(amountIn, 1e18, 1000e18); - - BEAN.approve(address(depot), type(uint256).max); - - // any user can approve pipeline for an arbritary set of assets. - // this means that most users do not need to approve pipeline, - // unless this is the first instance of the token being used. - // the increased risk in max approving all assets within pipeline is small, - // as any user can approve any contract to use the asset within pipeline. - PipeCall[] memory _prePipeCall = new PipeCall[](2); - - // Approval transactions are done prior as pipeline is likley to have apporvals for popular - // tokens done already, and this will save gas. However, if the user has not approved pipeline - // they can check this off-chain, and decide to do the approval themselves. - - // Approve DAI:BEAN Well to use pipeline's BEAN - _prePipeCall[0].target = address(BEAN); - _prePipeCall[0].data = abi.encodeWithSelector(BEAN.approve.selector, address(daiBeanWell), type(uint256).max); - - // Approve DAI:USDC Well to use pipeline's DAI - _prePipeCall[1].target = address(DAI); - _prePipeCall[1].data = abi.encodeWithSelector(DAI.approve.selector, address(daiUsdcWell), type(uint256).max); - - pipeline.multiPipe(_prePipeCall); - - AdvancedPipeCall[] memory _pipeCall = new AdvancedPipeCall[](2); - - // Swap BEAN for DAI - _pipeCall[0].target = address(daiBeanWell); - _pipeCall[0].callData = abi.encodeWithSelector( - Well.swapFrom.selector, daiBeanTokens[1], daiBeanTokens[0], amountIn, 0, address(pipeline) - ); - _pipeCall[0].clipboard = abi.encodePacked(uint256(0)); - - // Swap DAI for USDC - _pipeCall[1].target = address(daiUsdcWell); - _pipeCall[1].callData = - abi.encodeWithSelector(Well.swapFrom.selector, daiUsdcTokens[0], daiUsdcTokens[1], 0, 0, address(this)); - _pipeCall[1].clipboard = clipboardHelper(false, 0, ClipboardType.singlePaste, 1, 0, 2); - - bytes[] memory _farmCalls = new bytes[](2); - _farmCalls[0] = abi.encodeWithSelector( - depot.transferToken.selector, BEAN, address(pipeline), amountIn, From.EXTERNAL, To.EXTERNAL - ); - _farmCalls[1] = abi.encodeWithSelector(depot.advancedPipe.selector, _pipeCall, 0); - - vm.resumeGasMetering(); - depot.farm(_farmCalls); - } - - function testFuzz_wells_BeanDaiUsdc_Shift(uint256 amountIn) public { - vm.pauseGasMetering(); - amountIn = bound(amountIn, 1e18, 1000e18); - - BEAN.approve(address(depot), type(uint256).max); - - // unlike swap test (previous test), no tokens are sent back to pipeline. - // this means that pipeline does not prior approvals. - - AdvancedPipeCall[] memory _pipeCall = new AdvancedPipeCall[](2); - - // Shift excess tokens into DAI; deliver to the DAI:USDC Well - _pipeCall[0].target = address(daiBeanWell); - _pipeCall[0].callData = abi.encodeWithSelector(Well.shift.selector, DAI, 0, address(daiUsdcWell)); - _pipeCall[0].clipboard = abi.encodePacked(uint256(0)); - - // Shift excess tokens into USDC; deliver to the user - _pipeCall[1].target = address(daiUsdcWell); - _pipeCall[1].callData = abi.encodeWithSelector(Well.shift.selector, daiUsdcTokens[1], 0, address(this)); - _pipeCall[1].clipboard = abi.encodePacked(uint256(0)); - - // Send BEAN directly to the DAI:BEAN Well, then perform the Pipe calls above. - bytes[] memory _farmCalls = new bytes[](2); - _farmCalls[0] = abi.encodeWithSelector( - depot.transferToken.selector, BEAN, address(daiBeanWell), amountIn, From.EXTERNAL, To.EXTERNAL - ); - _farmCalls[1] = abi.encodeWithSelector(depot.advancedPipe.selector, _pipeCall, 0); - - vm.resumeGasMetering(); - depot.farm(_farmCalls); - } - - function testFuzz_wells_BeanDai_AddLiquidity(uint256 amount) public { - vm.pauseGasMetering(); - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(amount, 1e18, 1000e18); - amounts[1] = bound(amount, 1e18, 1000e18); - vm.resumeGasMetering(); - - daiBeanWell.addLiquidity(amounts, 0, address(this), type(uint256).max); - } - - function testFuzz_wells_BeanDai_RemoveLiquidity(uint256 amount) public { - vm.pauseGasMetering(); - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(amount, 1e18, 1000e18); - amounts[1] = amounts[0]; - uint256 lp = daiBeanWell.addLiquidity(amounts, 0, address(this), type(uint256).max); - uint256[] memory minAmountsOut = new uint256[](2); - vm.resumeGasMetering(); - - daiBeanWell.removeLiquidity(lp, minAmountsOut, address(this), type(uint256).max); - } - - ////////// Curve - - function testFuzz_curve_BeanDai_Swap(uint256 amount) public { - vm.pauseGasMetering(); - vm.assume(amount > 0); - amount = bound(amount, 1e18, 1000 * 1e18); - _curveSetupHelper(amount); - - int128 i = 0; // from bean - int128 j = 1; // to dai - - vm.resumeGasMetering(); - - bean3Crv.exchange_underlying(i, j, amount, 0); - } - - - function testFuzz_curve_AddLiquidity(uint256 amount) public { - vm.pauseGasMetering(); - uint256[2] memory amounts; - amount = bound(amount, 1e18, 1000 * 1e18); - amounts[0] = 0; - amounts[1] = amount; - - _curveSetupHelper(amount); - vm.resumeGasMetering(); - - bean3Crv.add_liquidity(amounts, 0); - } - - function testFuzz_curve_BeanDai_RemoveLiquidity(uint256 amount) public { - vm.pauseGasMetering(); - uint256[2] memory amounts; - amount = bound(amount, 1e18, 1000 * 1e18); - amounts[0] = 0; - amounts[1] = amount; - - _curveSetupHelper(amount); - uint256 liquidity = bean3Crv.add_liquidity(amounts, 0); - vm.resumeGasMetering(); - - bean3Crv.remove_liquidity_one_coin(liquidity, 0, 0); - } - - //////////////////// SETUP HELPERS //////////////////// - - /// @dev Approve the `router` to swap Test contract's tokens. - function _curveSetupHelper(uint256 amount) private { - deal(address(BEAN), address(this), amount * 2); - deal(address(DAI), address(this), amount * 2); - deal(address(THREE3CRV), address(this), amount * 2); - - BEAN.approve(address(bean3Crv), type(uint256).max); - DAI.approve(address(bean3Crv), type(uint256).max); - THREE3CRV.approve(address(bean3Crv), type(uint256).max); - - BEAN.approve(address(zap), type(uint256).max); - DAI.approve(address(zap), type(uint256).max); - THREE3CRV.approve(address(zap), type(uint256).max); - } - - /// @dev Perform a few swaps on the provided Well to proper initialization. - function _wellsInitializedHelper() private { - // DAI -> BEAN - daiBeanWell.swapFrom( - daiBeanTokens[0], daiBeanTokens[1], 1000 * 1e18, 500 * 1e18, address(this), type(uint256).max - ); - - // BEAN -> DAI - vm.warp(block.timestamp + 1); - daiBeanWell.swapFrom( - daiBeanTokens[1], daiBeanTokens[0], 500 * 1e18, 500 * 1e18, address(this), type(uint256).max - ); - } -} - -interface IBEAN is IERC20 { - function deposit() external payable; - function withdraw(uint256 amount) external; -} diff --git a/test/stableSwap/Well.BoreStableSwap.t.sol b/test/stableSwap/Well.BoreStableSwap.t.sol index daa017a7..ff229209 100644 --- a/test/stableSwap/Well.BoreStableSwap.t.sol +++ b/test/stableSwap/Well.BoreStableSwap.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.17; import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; import {MockPump} from "mocks/pumps/MockPump.sol"; -import {StableSwap2} from "src/functions/StableSwap2.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; contract WellBoreStableSwapTest is TestHelper { - /// @dev Bore a 2-token Well with StableSwap2 & several pumps. + /// @dev Bore a 2-token Well with CurveStableSwap2 & several pumps. function setUp() public { // setup a StableSwap Well with an A parameter of 10. setupStableSwapWell(10); @@ -91,13 +91,13 @@ contract WellBoreStableSwapTest is TestHelper { // Get the first `nTokens` mock tokens IERC20[] memory wellTokens = getTokens(nTokens); bytes memory wellFunctionBytes = abi.encode( - a, + a, address(wellTokens[0]), address(wellTokens[1]) ); // Deploy a Well Function - wellFunction = Call(address(new StableSwap2()), wellFunctionBytes); + wellFunction = Call(address(new CurveStableSwap2()), wellFunctionBytes); // Etch the MockPump at each `target` Call[] memory pumps = new Call[](numberOfPumps); @@ -107,11 +107,21 @@ contract WellBoreStableSwapTest is TestHelper { } // Deploy the Well - Well _well = - encodeAndBoreWell(address(aquifer), wellImplementation, wellTokens, wellFunction, pumps, bytes32(0)); + Well _well = encodeAndBoreWell( + address(aquifer), + wellImplementation, + wellTokens, + wellFunction, + pumps, + bytes32(0) + ); // Check Pumps - assertEq(_well.numberOfPumps(), numberOfPumps, "number of pumps mismatch"); + assertEq( + _well.numberOfPumps(), + numberOfPumps, + "number of pumps mismatch" + ); Call[] memory _pumps = _well.pumps(); if (numberOfPumps > 0) { @@ -130,6 +140,9 @@ contract WellBoreStableSwapTest is TestHelper { assertEq(_well.wellFunctionAddress(), wellFunction.target); // Check that Aquifer recorded the deployment - assertEq(aquifer.wellImplementation(address(_well)), wellImplementation); + assertEq( + aquifer.wellImplementation(address(_well)), + wellImplementation + ); } } diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol index 1ec92b55..8aacd0e1 100644 --- a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -1,29 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, StableSwap2, Balances} from "test/TestHelper.sol"; +import {TestHelper, CurveStableSwap2, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { - event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); + event RemoveLiquidity( + uint256 lpAmountIn, + uint256[] tokenAmountsOut, + address recipient + ); uint256[] tokenAmountsOut; uint256 requiredLpAmountIn; bytes _data; - // Setup - StableSwap2 ss; + CurveStableSwap2 ss; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new StableSwap2(); + ss = new CurveStableSwap2(); setupStableSwapWell(10); _data = abi.encode( - StableSwap2.WellFunctionData( + CurveStableSwap2.WellFunctionData( 10, address(tokens[0]), address(tokens[1]) @@ -40,20 +43,41 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { /// @dev Assumes use of ConstantProduct2 function test_getRemoveLiquidityImbalancedIn() public { - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn( + tokenAmountsOut + ); assertEq(lpAmountIn, requiredLpAmountIn); } /// @dev not enough LP to receive `tokenAmountsOut` - function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { + function test_removeLiquidityImbalanced_revertIf_notEnoughLP() + public + prank(user) + { uint256 maxLpAmountIn = 5 * 1e18; - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); - well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageIn.selector, + requiredLpAmountIn, + maxLpAmountIn + ) + ); + well.removeLiquidityImbalanced( + maxLpAmountIn, + tokenAmountsOut, + user, + type(uint256).max + ); } function test_removeLiquidityImbalanced_revertIf_expired() public { vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityImbalanced(0, new uint256[](2), user, block.timestamp - 1); + well.removeLiquidityImbalanced( + 0, + new uint256[](2), + user, + block.timestamp - 1 + ); } /// @dev Base case @@ -65,7 +89,12 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { vm.expectEmit(true, true, true, true); emit RemoveLiquidity(maxLpAmountIn, tokenAmountsOut, user); - well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + well.removeLiquidityImbalanced( + maxLpAmountIn, + tokenAmountsOut, + user, + type(uint256).max + ); Balances memory userBalanceAfter = getBalances(user, well); Balances memory wellBalanceAfter = getBalances(address(well), well); @@ -75,26 +104,51 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // `user` balance of underlying tokens increases // assumes initial balance of zero - assertEq(userBalanceAfter.tokens[0], tokenAmountsOut[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfter.tokens[1], tokenAmountsOut[1], "Incorrect token1 user balance"); + assertEq( + userBalanceAfter.tokens[0], + tokenAmountsOut[0], + "Incorrect token0 user balance" + ); + assertEq( + userBalanceAfter.tokens[1], + tokenAmountsOut[1], + "Incorrect token1 user balance" + ); // Well's reserve of underlying tokens decreases - assertEq(wellBalanceAfter.tokens[0], 1500 * 1e18, "Incorrect token0 well reserve"); - assertEq(wellBalanceAfter.tokens[1], 19_494 * 1e17, "Incorrect token1 well reserve"); + assertEq( + wellBalanceAfter.tokens[0], + 1500 * 1e18, + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfter.tokens[1], + 19_494 * 1e17, + "Incorrect token1 well reserve" + ); checkInvariant(address(well)); } /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityImbalanced(uint256 a0, uint256 a1) public prank(user) { + function testFuzz_removeLiquidityImbalanced( + uint256 a0, + uint256 a1 + ) public prank(user) { // Setup amounts of liquidity to remove // NOTE: amounts may or may not be equal uint256[] memory amounts = new uint256[](2); amounts[0] = bound(a0, 0, 750e18); amounts[1] = bound(a1, 0, 750e18); - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances( + address(well), + well + ); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances( + user, + well + ); // Calculate change in Well reserves after removing liquidity uint256[] memory reserves = new uint256[](2); reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; @@ -113,17 +167,40 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // Remove all of `user`'s liquidity and deliver them the tokens vm.expectEmit(true, true, true, true); emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + well.removeLiquidityImbalanced( + maxLpAmountIn, + amounts, + user, + type(uint256).max + ); - Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + Balances memory userBalanceAfterRemoveLiquidity = getBalances( + user, + well + ); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances( + address(well), + well + ); // `user` balance of LP tokens decreases - assertEq(userBalanceAfterRemoveLiquidity.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + assertEq( + userBalanceAfterRemoveLiquidity.lp, + maxLpAmountIn - lpAmountIn, + "Incorrect lp output" + ); // `user` balance of underlying tokens increases - assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); + assertEq( + userBalanceAfterRemoveLiquidity.tokens[0], + amounts[0], + "Incorrect token0 user balance" + ); + assertEq( + userBalanceAfterRemoveLiquidity.tokens[1], + amounts[1], + "Incorrect token1 user balance" + ); // Well's reserve of underlying tokens decreases // Equal amount of liquidity of 1000e18 were added in the setup function hence the @@ -145,7 +222,10 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` /// before liquidity is removed by `user`. - function testFuzz_removeLiquidityImbalanced_withSwap(uint256 a0, uint256 imbalanceBias) public { + function testFuzz_removeLiquidityImbalanced_withSwap( + uint256 a0, + uint256 imbalanceBias + ) public { // Setup amounts of liquidity to remove // NOTE: amounts[0] is bounded at 1 to prevent slippage overflow // failure, bug fix in progress @@ -156,7 +236,14 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // `user2` performs a swap to imbalance the pool by `imbalanceBias` vm.prank(user2); - well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + well.swapFrom( + tokens[0], + tokens[1], + imbalanceBias, + 0, + user2, + type(uint256).max + ); // `user` has LP tokens and will perform a `removeLiquidityImbalanced` call vm.startPrank(user); @@ -182,21 +269,46 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { uint256 maxLpAmountIn = userBalanceBefore.lp; vm.expectEmit(true, true, true, true); emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + well.removeLiquidityImbalanced( + maxLpAmountIn, + amounts, + user, + type(uint256).max + ); Balances memory wellBalanceAfter = getBalances(address(well), well); Balances memory userBalanceAfter = getBalances(user, well); // `user` balance of LP tokens decreases - assertEq(userBalanceAfter.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + assertEq( + userBalanceAfter.lp, + maxLpAmountIn - lpAmountIn, + "Incorrect lp output" + ); // `user` balance of underlying tokens increases - assertEq(userBalanceAfter.tokens[0], userBalanceBefore.tokens[0] + amounts[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfter.tokens[1], userBalanceBefore.tokens[1] + amounts[1], "Incorrect token1 user balance"); + assertEq( + userBalanceAfter.tokens[0], + userBalanceBefore.tokens[0] + amounts[0], + "Incorrect token0 user balance" + ); + assertEq( + userBalanceAfter.tokens[1], + userBalanceBefore.tokens[1] + amounts[1], + "Incorrect token1 user balance" + ); // Well's reserve of underlying tokens decreases - assertEq(wellBalanceAfter.tokens[0], wellBalanceBefore.tokens[0] - amounts[0], "Incorrect token0 well reserve"); - assertEq(wellBalanceAfter.tokens[1], wellBalanceBefore.tokens[1] - amounts[1], "Incorrect token1 well reserve"); + assertEq( + wellBalanceAfter.tokens[0], + wellBalanceBefore.tokens[0] - amounts[0], + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfter.tokens[1], + wellBalanceBefore.tokens[1] - amounts[1], + "Incorrect token1 well reserve" + ); checkInvariant(address(well)); } } diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index 25f45deb..ebd20ef9 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -1,26 +1,30 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { - event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); - - StableSwap2 ss; + event RemoveLiquidityOneToken( + uint256 lpAmountIn, + IERC20 tokenOut, + uint256 tokenAmountOut, + address recipient + ); + + CurveStableSwap2 ss; uint256 constant addedLiquidity = 1000 * 1e18; bytes _data; - function setUp() public { - ss = new StableSwap2(); + ss = new CurveStableSwap2(); setupStableSwapWell(10); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); _data = abi.encode( - StableSwap2.WellFunctionData( + CurveStableSwap2.WellFunctionData( 10, address(tokens[0]), address(tokens[1]) @@ -28,25 +32,52 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { ); } - /// @dev Assumes use of StableSwap2 + /// @dev Assumes use of CurveStableSwap2 function test_getRemoveLiquidityOneTokenOut() public { - uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); + uint256 amountOut = well.getRemoveLiquidityOneTokenOut( + 500 * 1e18, + tokens[0] + ); assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); } /// @dev not enough tokens received for `lpAmountIn`. - function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { + function test_removeLiquidityOneToken_revertIf_amountOutTooLow() + public + prank(user) + { uint256 lpAmountIn = 500 * 1e18; uint256 minTokenAmountOut = 500 * 1e18; - uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); + uint256 amountOut = well.getRemoveLiquidityOneTokenOut( + lpAmountIn, + tokens[0] + ); - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); - well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageOut.selector, + amountOut, + minTokenAmountOut + ) + ); + well.removeLiquidityOneToken( + lpAmountIn, + tokens[0], + minTokenAmountOut, + user, + type(uint256).max + ); } function test_removeLiquidityOneToken_revertIf_expired() public { vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityOneToken(0, tokens[0], 0, user, block.timestamp - 1); + well.removeLiquidityOneToken( + 0, + tokens[0], + 0, + user, + block.timestamp - 1 + ); } /// @dev Base case @@ -56,17 +87,35 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { Balances memory prevUserBalance = getBalances(user, well); vm.expectEmit(true, true, true, true); - emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); + emit RemoveLiquidityOneToken( + lpAmountIn, + tokens[0], + minTokenAmountOut, + user + ); - uint256 amountOut = - well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + uint256 amountOut = well.removeLiquidityOneToken( + lpAmountIn, + tokens[0], + minTokenAmountOut, + user, + type(uint256).max + ); Balances memory userBalance = getBalances(user, well); Balances memory wellBalance = getBalances(address(well), well); - assertEq(userBalance.lp, prevUserBalance.lp - lpAmountIn, "Incorrect lpAmountIn"); + assertEq( + userBalance.lp, + prevUserBalance.lp - lpAmountIn, + "Incorrect lpAmountIn" + ); - assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); + assertEq( + userBalance.tokens[0], + amountOut, + "Incorrect token0 user balance" + ); assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); // Equal amount of liquidity of 1000e18 were added in the setup function hence the @@ -77,7 +126,11 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { (initialLiquidity + addedLiquidity) - minTokenAmountOut, "Incorrect token0 well reserve" ); - assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); + assertEq( + wellBalance.tokens[1], + (initialLiquidity + addedLiquidity), + "Incorrect token1 well reserve" + ); checkStableSwapInvariant(address(well)); } @@ -89,14 +142,20 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { amounts[0] = bound(a0, 1e18, 750e18); amounts[1] = 0; - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances( + user, + well + ); uint256 userLpBalance = userBalanceBeforeRemoveLiquidity.lp; // Find the LP amount that should be burned given the fuzzed // amounts. Works even though only amounts[0] is set. uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances( + address(well), + well + ); // Calculate change in Well reserves after removing liquidity uint256[] memory reserves = new uint256[](2); @@ -109,16 +168,52 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; vm.expectEmit(true, true, true, false); - emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); - uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out - assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); + emit RemoveLiquidityOneToken( + lpAmountBurned, + tokens[0], + amounts[0], + user + ); + uint256 amountOut = well.removeLiquidityOneToken( + lpAmountIn, + tokens[0], + 0, + user, + type(uint256).max + ); // no minimum out + assertApproxEqAbs( + amountOut, + amounts[0], + 1, + "amounts[0] > userLpBalance" + ); - Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + Balances memory userBalanceAfterRemoveLiquidity = getBalances( + user, + well + ); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances( + address(well), + well + ); - assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same + assertEq( + userBalanceAfterRemoveLiquidity.lp, + userLpBalance - lpAmountIn, + "Incorrect lp output" + ); + assertApproxEqAbs( + userBalanceAfterRemoveLiquidity.tokens[0], + amounts[0], + 1, + "Incorrect token0 user balance" + ); + assertApproxEqAbs( + userBalanceAfterRemoveLiquidity.tokens[1], + amounts[1], + 1, + "Incorrect token1 user balance" + ); // should stay the same assertApproxEqAbs( wellBalanceAfterRemoveLiquidity.tokens[0], (initialLiquidity + addedLiquidity) - amounts[0], diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol index a085ae36..3e0c87e8 100644 --- a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, StableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { - StableSwap2 ss; + CurveStableSwap2 ss; bytes constant data = ""; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new StableSwap2(); + ss = new CurveStableSwap2(); setupStableSwapWell(10); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens @@ -24,7 +24,11 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { function test_liquidityInitialized() public { IERC20[] memory tokens = well.tokens(); for (uint256 i; i < tokens.length; i++) { - assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); + assertEq( + tokens[i].balanceOf(address(well)), + initialLiquidity + addedLiquidity, + "incorrect token reserve" + ); } assertEq(well.totalSupply(), 4000 * 1e18, "incorrect totalSupply"); } @@ -34,20 +38,38 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { function test_getRemoveLiquidityOut() public { uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); for (uint256 i; i < tokens.length; i++) { - assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); + assertEq( + amountsOut[i], + 500 * 1e18, + "incorrect getRemoveLiquidityOut" + ); } } /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token - function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { + function test_removeLiquidity_revertIf_minAmountOutTooHigh() + public + prank(user) + { uint256 lpAmountIn = 1000 * 1e18; uint256[] memory minTokenAmountsOut = new uint256[](2); minTokenAmountsOut[0] = 501 * 1e18; // too high minTokenAmountsOut[1] = 500 * 1e18; - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 500 * 1e18, minTokenAmountsOut[0])); - well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageOut.selector, + 500 * 1e18, + minTokenAmountsOut[0] + ) + ); + well.removeLiquidity( + lpAmountIn, + minTokenAmountsOut, + user, + type(uint256).max + ); } function test_removeLiquidity_revertIf_expired() public { @@ -101,7 +123,10 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { assertLe( well.totalSupply(), - StableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) + CurveStableSwap2(wellFunction.target).calcLpTokenSupply( + well.getReserves(), + wellFunction.data + ) ); checkInvariant(address(well)); } @@ -109,8 +134,14 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` /// before liquidity is removed by `user`. - function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + function test_removeLiquidity_fuzzSwapBias( + uint256 lpAmountBurned, + uint256 imbalanceBias + ) public { + Balances memory userBalanceBeforeRemoveLiquidity = getBalances( + user, + well + ); uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); @@ -118,7 +149,14 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { // `user2` performs a swap to imbalance the pool by `imbalanceBias` vm.prank(user2); - well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + well.swapFrom( + tokens[0], + tokens[1], + imbalanceBias, + 0, + user2, + type(uint256).max + ); // `user` has LP tokens and will perform a `removeLiquidity` call vm.startPrank(user); @@ -135,7 +173,12 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { action.fees = new uint256[](2); (before, action) = beforeRemoveLiquidity(action); - well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); + well.removeLiquidity( + lpAmountBurned, + tokenAmountsOut, + user, + type(uint256).max + ); afterRemoveLiquidity(before, action); checkStableSwapInvariant(address(well)); } diff --git a/test/stableSwap/Well.ShiftStable.t.sol b/test/stableSwap/Well.ShiftStable.t.sol index ea0d5e76..741fff76 100644 --- a/test/stableSwap/Well.ShiftStable.t.sol +++ b/test/stableSwap/Well.ShiftStable.t.sol @@ -1,12 +1,17 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, Balances, ConstantProduct2, IERC20, StableSwap2} from "test/TestHelper.sol"; +import {TestHelper, Balances, ConstantProduct2, IERC20, CurveStableSwap2} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellShiftStableTest is TestHelper { - event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); + event Shift( + uint256[] reserves, + IERC20 toToken, + uint256 minAmountOut, + address recipient + ); function setUp() public { setupStableSwapWell(10); @@ -18,23 +23,43 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received token0" + ); + assertEq( + wellBalanceBeforeShift.tokens[1], + 1000e18, + "Well should have NOT have received token1" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that `_user` has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); - well.sync(); uint256 minAmountOut = well.getShiftOut(tokens[1]); uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = well.getReserves()[0]; - calcReservesAfter[1] = well.getReserves()[1] - minAmountOut; + calcReservesAfter[0] = tokens[0].balanceOf(address(well)); + calcReservesAfter[1] = + tokens[1].balanceOf(address(well)) - + minAmountOut; vm.expectEmit(true, true, true, true); emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); @@ -42,16 +67,38 @@ contract WellShiftStableTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained token1 - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); - assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); + assertEq( + userBalanceAfterShift.tokens[0], + 0, + "User should NOT have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + amtOut, + "User should have gained token1" + ); + assertTrue( + userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], + "User should have more token1" + ); // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); // The difference has been sent to _user. assertEq( @@ -73,22 +120,38 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received tokens" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); - well.sync(); uint256 minAmountOut = well.getShiftOut(tokens[0]); uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = well.getReserves()[0] - minAmountOut; - calcReservesAfter[1] = well.getReserves()[1]; + calcReservesAfter[0] = + tokens[0].balanceOf(address(well)) - + minAmountOut; + calcReservesAfter[1] = tokens[1].balanceOf(address(well)); vm.expectEmit(true, true, true, true); emit Shift(calcReservesAfter, tokens[0], minAmountOut, _user); @@ -97,17 +160,34 @@ contract WellShiftStableTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained token0 - assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); assertEq( - userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" + userBalanceAfterShift.tokens[0], + amount, + "User should have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + userBalanceBeforeShift.tokens[1], + "User should NOT have gained token1" ); // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); assertEq( userBalanceAfterShift.tokens[0], @@ -119,30 +199,64 @@ contract WellShiftStableTest is TestHelper { /// @dev Calling shift() on a balanced Well should do nothing. function test_shift_balanced_pool() public prank(user) { - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + wellBalanceBeforeShift.tokens[1], + "Well should should be balanced" + ); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + assertEq( + userBalanceBeforeShift.tokens[0], + 0, + "User should start with 0 of token0" + ); + assertEq( + userBalanceBeforeShift.tokens[1], + 0, + "User should start with 0 of token1" + ); well.shift(tokens[1], 0, _user); uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); + Balances memory wellBalanceAfterShift = getBalances( + address(well), + well + ); // User should have gained neither token - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); + assertEq( + userBalanceAfterShift.tokens[0], + 0, + "User should NOT have gained token0" + ); + assertEq( + userBalanceAfterShift.tokens[1], + 0, + "User should NOT have gained token1" + ); // Reserves should equal balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + assertEq( + wellBalanceAfterShift.tokens[0], + reserves[0], + "Well should have correct token0 balance" + ); + assertEq( + wellBalanceAfterShift.tokens[1], + reserves[1], + "Well should have correct token1 balance" + ); checkInvariant(address(well)); } @@ -151,12 +265,29 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + Balances memory wellBalanceBeforeShift = getBalances( + address(well), + well + ); + assertEq( + wellBalanceBeforeShift.tokens[0], + 1000e18 + amount, + "Well should have received token0" + ); + assertEq( + wellBalanceBeforeShift.tokens[1], + 1000e18, + "Well should have NOT have received token1" + ); uint256 amountOut = well.getShiftOut(tokens[1]); - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageOut.selector, + amountOut, + type(uint256).max + ) + ); well.shift(tokens[1], type(uint256).max, user); } } diff --git a/test/stableSwap/Well.SkimStableSwap.t.sol b/test/stableSwap/Well.SkimStableSwap.t.sol index a8bcf8ff..b04be2be 100644 --- a/test/stableSwap/Well.SkimStableSwap.t.sol +++ b/test/stableSwap/Well.SkimStableSwap.t.sol @@ -8,7 +8,7 @@ contract WellSkimTest is TestHelper { setupStableSwapWell(10); } - function test_initialized() public { + function test_initialized() public view { // Well should have liquidity Balances memory wellBalance = getBalances(address(well), well); assertEq(wellBalance.tokens[0], 1000e18); @@ -23,7 +23,10 @@ contract WellSkimTest is TestHelper { tokens[0].transfer(address(well), amounts[0]); tokens[1].transfer(address(well), amounts[1]); - Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); + Balances memory wellBalanceBeforeSkim = getBalances( + address(well), + well + ); // Verify that the Well has received the tokens assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); diff --git a/test/stableSwap/Well.SwapFromStableSwap.t.sol b/test/stableSwap/Well.SwapFromStableSwap.t.sol index d04687b1..0c3b1cc0 100644 --- a/test/stableSwap/Well.SwapFromStableSwap.t.sol +++ b/test/stableSwap/Well.SwapFromStableSwap.t.sol @@ -8,20 +8,22 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - contract WellSwapFromStableSwapTest is SwapHelper { function setUp() public { setupStableSwapWell(10); } - function test_getSwapOut() public { + function test_getSwapOut() public view { uint amountIn = 10 * 1e18; uint amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); assertEq(amountOut, 9_995_239_930_393_036_263); // ~0.05% slippage } - function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint amountIn, uint i) public prank(user) { + function testFuzz_getSwapOut_revertIf_insufficientWellBalance( + uint amountIn, + uint i + ) public prank(user) { // Swap token `i` -> all other tokens vm.assume(i < tokens.length); @@ -33,13 +35,26 @@ contract WellSwapFromStableSwapTest is SwapHelper { // Its `getBalance` function can return an amount greater than the Well holds. IWellFunction badFunction = new MockFunctionBad(); Well badWell = encodeAndBoreWell( - address(aquifer), wellImplementation, tokens, Call(address(badFunction), ""), pumps, bytes32(0) + address(aquifer), + wellImplementation, + tokens, + Call(address(badFunction), ""), + pumps, + bytes32(0) ); // Check assumption that reserves are empty Balances memory wellBalances = getBalances(address(badWell), badWell); - assertEq(wellBalances.tokens[0], 0, "bad assumption: wellBalances.tokens[0] != 0"); - assertEq(wellBalances.tokens[1], 0, "bad assumption: wellBalances.tokens[1] != 0"); + assertEq( + wellBalances.tokens[0], + 0, + "bad assumption: wellBalances.tokens[0] != 0" + ); + assertEq( + wellBalances.tokens[1], + 0, + "bad assumption: wellBalances.tokens[1] != 0" + ); for (uint j = 0; j < tokens.length; ++j) { if (j != i) { @@ -52,7 +67,14 @@ contract WellSwapFromStableSwapTest is SwapHelper { /// @dev Swaps should always revert if `fromToken` = `toToken`. function test_swapFrom_revertIf_sameToken() public prank(user) { vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint).max); + well.swapFrom( + tokens[0], + tokens[0], + 100 * 1e18, + 0, + user, + type(uint).max + ); } /// @dev Slippage revert if minAmountOut is too high @@ -61,8 +83,21 @@ contract WellSwapFromStableSwapTest is SwapHelper { uint amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); uint minAmountOut = amountOut + 1e18; - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); - well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint).max); + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageOut.selector, + amountOut, + minAmountOut + ) + ); + well.swapFrom( + tokens[0], + tokens[1], + amountIn, + minAmountOut, + user, + type(uint).max + ); } function test_swapFrom_revertIf_expired() public { @@ -73,15 +108,26 @@ contract WellSwapFromStableSwapTest is SwapHelper { function testFuzz_swapFrom(uint amountIn) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); - (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); - act.wellSends = well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint).max); + (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom( + 0, + 1, + amountIn + ); + act.wellSends = well.swapFrom( + tokens[0], + tokens[1], + amountIn, + 0, + user, + type(uint).max + ); afterSwapFrom(bef, act); checkStableSwapInvariant(address(well)); } function testFuzz_swapAndRemoveAllLiq(uint amountIn) public { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); - vm.prank(user); + vm.prank(user); well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint).max); vm.prank(address(this)); @@ -91,14 +137,28 @@ contract WellSwapFromStableSwapTest is SwapHelper { address(this), type(uint).max ); - assertEq(IERC20(address(well)).totalSupply(),0); + assertEq(IERC20(address(well)).totalSupply(), 0); } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result function testFuzz_swapFrom_equalSwap(uint token0AmtIn) public prank(user) { vm.assume(token0AmtIn < tokens[0].balanceOf(user)); - uint token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint).max); - uint token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint).max); + uint token1Out = well.swapFrom( + tokens[0], + tokens[1], + token0AmtIn, + 0, + user, + type(uint).max + ); + uint token0Out = well.swapFrom( + tokens[1], + tokens[0], + token1Out, + 0, + user, + type(uint).max + ); assertEq(token0Out, token0AmtIn); checkInvariant(address(well)); } diff --git a/test/stableSwap/Well.SwapToStableSwap.t.sol b/test/stableSwap/Well.SwapToStableSwap.t.sol index 7e165f23..c4548999 100644 --- a/test/stableSwap/Well.SwapToStableSwap.t.sol +++ b/test/stableSwap/Well.SwapToStableSwap.t.sol @@ -13,13 +13,15 @@ contract WellSwapToStableSwapTest is SwapHelper { setupStableSwapWell(10); } - function test_getSwapIn() public { + function test_getSwapIn() public view { uint256 amountOut = 100 * 1e18; uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage } - function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { + function testFuzz_getSwapIn_revertIf_insufficientWellBalance( + uint256 i + ) public prank(user) { IERC20[] memory _tokens = well.tokens(); Balances memory wellBalances = getBalances(address(well), well); vm.assume(i < _tokens.length); @@ -39,17 +41,37 @@ contract WellSwapToStableSwapTest is SwapHelper { /// @dev Swaps should always revert if `fromToken` = `toToken`. function test_swapTo_revertIf_sameToken() public prank(user) { vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapTo(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); + well.swapTo( + tokens[0], + tokens[0], + 100 * 1e18, + 0, + user, + type(uint256).max + ); } /// @dev Slippage revert occurs if maxAmountIn is too low function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { - uint256 amountOut = 100 * 1e18; + uint256 amountOut = 100 * 1e18; uint256 amountIn = 100_482_889_020_651_556_292; - uint256 maxAmountIn = amountIn * 99 / 100; - - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); - well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + uint256 maxAmountIn = (amountIn * 99) / 100; + + vm.expectRevert( + abi.encodeWithSelector( + IWellErrors.SlippageIn.selector, + amountIn, + maxAmountIn + ) + ); + well.swapTo( + tokens[0], + tokens[1], + maxAmountIn, + amountOut, + user, + type(uint256).max + ); } /// @dev Note: this covers the case where there is a fee as well @@ -69,7 +91,9 @@ contract WellSwapToStableSwapTest is SwapHelper { Balances memory wellBalancesBefore = getBalances(address(well), well); // Decrease reserve of token 1 by `amountOut` which is paid to user - uint256[] memory calcBalances = new uint256[](wellBalancesBefore.tokens.length); + uint256[] memory calcBalances = new uint256[]( + wellBalancesBefore.tokens.length + ); calcBalances[0] = wellBalancesBefore.tokens[0]; calcBalances[1] = wellBalancesBefore.tokens[1] - amountOut; @@ -82,28 +106,63 @@ contract WellSwapToStableSwapTest is SwapHelper { vm.expectEmit(true, true, true, true); emit Swap(tokens[0], tokens[1], calcAmountIn, amountOut, user); - well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + well.swapTo( + tokens[0], + tokens[1], + maxAmountIn, + amountOut, + user, + type(uint256).max + ); Balances memory userBalancesAfter = getBalances(user, well); Balances memory wellBalancesAfter = getBalances(address(well), well); assertEq( - userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], calcAmountIn, "Incorrect token0 user balance" + userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], + calcAmountIn, + "Incorrect token0 user balance" ); - assertEq(userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], amountOut, "Incorrect token1 user balance"); assertEq( - wellBalancesAfter.tokens[0], wellBalancesBefore.tokens[0] + calcAmountIn, "Incorrect token0 well reserve" + userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], + amountOut, + "Incorrect token1 user balance" + ); + assertEq( + wellBalancesAfter.tokens[0], + wellBalancesBefore.tokens[0] + calcAmountIn, + "Incorrect token0 well reserve" + ); + assertEq( + wellBalancesAfter.tokens[1], + wellBalancesBefore.tokens[1] - amountOut, + "Incorrect token1 well reserve" ); - assertEq(wellBalancesAfter.tokens[1], wellBalancesBefore.tokens[1] - amountOut, "Incorrect token1 well reserve"); checkStableSwapInvariant(address(well)); } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { + function testFuzz_swapTo_equalSwap( + uint256 token0AmtOut + ) public prank(user) { // assume amtOut is lower due to slippage vm.assume(token0AmtOut < 500e18); - uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); - uint256 token0In = well.swapTo(tokens[1], tokens[0], 1000e18, token1In, user, type(uint256).max); + uint256 token1In = well.swapTo( + tokens[0], + tokens[1], + 1000e18, + token0AmtOut, + user, + type(uint256).max + ); + uint256 token0In = well.swapTo( + tokens[1], + tokens[0], + 1000e18, + token1In, + user, + type(uint256).max + ); assertEq(token0In, token0AmtOut); checkInvariant(address(well)); } From 6a6da6b1f38c25361cd39e1d5eba0406c83ea669 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Mon, 8 Jul 2024 21:45:33 +0200 Subject: [PATCH 22/69] WIP --- src/functions/CurveStableSwap2.sol | 630 +++++++++--------- src/functions/Stable2.sol | 241 +++++++ src/libraries/LibMath.sol | 1 - test/Stable2/Well.BoreStableSwap.t.sol | 126 ++++ ....RemoveLiquidityImbalancedStableSwap.t.sol | 195 ++++++ ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 130 ++++ .../Well.RemoveLiquidityStableSwap.t.sol | 142 ++++ test/Stable2/Well.ShiftStable.t.sol | 160 +++++ test/Stable2/Well.SkimStableSwap.t.sol | 56 ++ test/Stable2/Well.Stable2.AddLiquidity.t.sol | 176 +++++ test/Stable2/Well.SwapFromStableSwap.t.sol | 101 +++ test/Stable2/Well.SwapToStableSwap.t.sol | 110 +++ test/TestHelper.sol | 223 ++----- ...bleSwap2.calcReserveAtRatioLiquidity.t.sol | 118 +--- ...veStableSwap2.calcReserveAtRatioSwap.t.sol | 2 +- test/functions/StableSwap.t.sol | 124 ++-- .../Well.AddLiquidityStableSwap.t.sol | 2 +- test/stableSwap/Well.BoreStableSwap.t.sol | 44 +- ....RemoveLiquidityImbalancedStableSwap.t.sol | 183 +---- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 154 +---- .../Well.RemoveLiquidityStableSwap.t.sol | 67 +- test/stableSwap/Well.ShiftStable.t.sol | 207 +----- test/stableSwap/Well.SkimStableSwap.t.sol | 7 +- test/stableSwap/Well.SwapFromStableSwap.t.sol | 112 +--- test/stableSwap/Well.SwapToStableSwap.t.sol | 87 +-- 25 files changed, 2034 insertions(+), 1364 deletions(-) create mode 100644 src/functions/Stable2.sol create mode 100644 test/Stable2/Well.BoreStableSwap.t.sol create mode 100644 test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol create mode 100644 test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol create mode 100644 test/Stable2/Well.RemoveLiquidityStableSwap.t.sol create mode 100644 test/Stable2/Well.ShiftStable.t.sol create mode 100644 test/Stable2/Well.SkimStableSwap.t.sol create mode 100644 test/Stable2/Well.Stable2.AddLiquidity.t.sol create mode 100644 test/Stable2/Well.SwapFromStableSwap.t.sol create mode 100644 test/Stable2/Well.SwapToStableSwap.t.sol diff --git a/src/functions/CurveStableSwap2.sol b/src/functions/CurveStableSwap2.sol index 11001155..d81c73fe 100644 --- a/src/functions/CurveStableSwap2.sol +++ b/src/functions/CurveStableSwap2.sol @@ -3,11 +3,37 @@ pragma solidity ^0.8.17; import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {LibMath} from "src/libraries/LibMath.sol"; import {SafeMath} from "oz/utils/math/SafeMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; import "forge-std/console.sol"; +interface ILookupTable { + /** + * @notice the lookup table returns a series of data, given a price point: + * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. + * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. + * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. + * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. + * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param precision precision of reserve. + */ + struct PriceData { + uint256 highPrice; + uint256 highPriceI; + uint256 highPriceJ; + uint256 lowPrice; + uint256 lowPriceI; + uint256 lowPriceJ; + uint256 precision; + } + + function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); + function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); + function getAParameter() external view returns (uint256); +} /** * @author Brean * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. @@ -23,54 +49,54 @@ import "forge-std/console.sol"; * * @dev Limited to tokens with a maximum of 18 decimals. */ -contract CurveStableSwap2 is IBeanstalkWellFunction { - using LibMath for uint; - using SafeMath for uint; + +contract CurveStableSwap2 is ProportionalLPToken2, IBeanstalkWellFunction { + struct PriceData { + uint256 targetPrice; + uint256 currentPrice; + uint256 maxStepSize; + ILookupTable.PriceData lutData; + } + + using LibMath for uint256; + using SafeMath for uint256; // 2 token Pool. - uint constant N = 2; + uint256 constant N = 2; // A precision - uint constant A_PRECISION = 100; + uint256 constant A_PRECISION = 100; // Precision that all pools tokens will be converted to. - uint constant POOL_PRECISION_DECIMALS = 18; - - // Maximum A parameter. - uint constant MAX_A = 10000 * A_PRECISION; + uint256 constant POOL_PRECISION_DECIMALS = 18; // Calc Rate Precision. uint256 constant CALC_RATE_PRECISION = 1e24; - // - uint256 MIN_TOKEN_DECIMALS = 10; + // price Precision. + uint256 constant PRICE_PRECISION = 1e6; + + address immutable lookupTable; + uint256 immutable a; // Errors error InvalidAParameter(uint256); error InvalidTokens(); - error InvalidTokenDecimals(uint256); - - /** - * The CurveStableSwap Well Function fetches the following data from the well: - * 1: A parameter - * 2: token0 address - * 3: token1 address - */ - struct WellFunctionData { - uint256 a; - address token0; - address token1; - } - - struct DeltaB { - uint256 pegBeans; - int256 currentBeans; - int256 deltaBToPeg; - int256 deltaPriceToTarget; - int256 deltaPriceToPeg; - int256 estDeltaB; - uint256 targetPrice; - uint256 poolPrice; + error InvalidTokenDecimals(); + error InvalidLUT(); + + // Due to the complexity of `calcReserveAtRatioLiquidity` and `calcReserveAtRatioSwap`, + // a LUT table is used to reduce the complexity of the calculations on chain. + // the lookup table contract implements 3 functions: + // 1. getRatiosFromPriceLiquidity(uint256) -> PriceData memory + // 2. getRatiosFromPriceSwap(uint256) -> PriceData memory + // 3. getAParameter() -> uint256 + // Lookup tables are a function of the A parameter. + constructor(address lut) { + if (lut == address(0)) revert InvalidLUT(); + lookupTable = lut; + // a = ILookupTable(lut).getAParameter(); + a = 10; } /** @@ -82,35 +108,27 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) */ function calcLpTokenSupply( - uint[] memory reserves, - bytes calldata data - ) public view returns (uint lpTokenSupply) { - (uint256 Ann, uint256[] memory precisions) = decodeWFData(data); - console.log(precisions[0], precisions[1]); - reserves = getScaledReserves(reserves, precisions); + uint256[] memory reserves, + bytes memory data + ) public view returns (uint256 lpTokenSupply) { + uint256[] memory decimals = decodeWellData(data); + // scale reserves to 18 decimals. + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - uint256 sumReserves = reserves[0] + reserves[1]; + uint256 Ann = a * N * N * A_PRECISION; + + uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; if (sumReserves == 0) return 0; lpTokenSupply = sumReserves; - - for (uint i = 0; i < 255; i++) { + for (uint256 i = 0; i < 255; i++) { uint256 dP = lpTokenSupply; // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(reserves[0].mul(N)); - dP = dP.mul(lpTokenSupply).div(reserves[1].mul(N)); + dP = dP.mul(lpTokenSupply).div(scaledReserves[0].mul(N)); + dP = dP.mul(lpTokenSupply).div(scaledReserves[1].mul(N)); uint256 prevReserves = lpTokenSupply; - lpTokenSupply = Ann - .mul(sumReserves) - .div(A_PRECISION) - .add(dP.mul(N)) - .mul(lpTokenSupply) - .div( - Ann - .sub(A_PRECISION) - .mul(lpTokenSupply) - .div(A_PRECISION) - .add(N.add(1).mul(dP)) - ); + lpTokenSupply = Ann.mul(sumReserves).div(A_PRECISION).add(dP.mul(N)).mul(lpTokenSupply).div( + Ann.sub(A_PRECISION).mul(lpTokenSupply).div(A_PRECISION).add(N.add(1).mul(dP)) + ); // Equality with the precision of 1 if (lpTokenSupply > prevReserves) { if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; @@ -130,62 +148,42 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { * which may round in favor of the well or the user. */ function calcReserve( - uint[] memory reserves, - uint j, - uint lpTokenSupply, - bytes calldata data - ) public view returns (uint reserve) { - (uint256 Ann, uint256[] memory precisions) = decodeWFData(data); - reserves = getScaledReserves(reserves, precisions); + uint256[] memory reserves, + uint256 j, + uint256 lpTokenSupply, + bytes memory data + ) public view returns (uint256 reserve) { + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); // avoid stack too deep errors. - (uint256 c, uint256 b) = getBandC( - Ann, - lpTokenSupply, - j == 0 ? reserves[1] : reserves[0] - ); + (uint256 c, uint256 b) = + getBandC(a * N * N * A_PRECISION, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]); reserve = lpTokenSupply; uint256 prevReserve; - for (uint i; i < 255; ++i) { + for (uint256 i; i < 255; ++i) { prevReserve = reserve; reserve = _calcReserve(reserve, b, c, lpTokenSupply); // Equality with the precision of 1 // scale reserve down to original precision if (reserve > prevReserve) { - if (reserve - prevReserve <= 1) - return reserve.div(precisions[j]); + if (reserve - prevReserve <= 1) { + return reserve.div(10 ** (18 - decimals[j])); + } } else { - if (prevReserve - reserve <= 1) - return reserve.div(precisions[j]); + if (prevReserve - reserve <= 1) { + return reserve.div(10 ** (18 - decimals[j])); + } } } revert("did not find convergence"); } - /** - * @notice Defines a proportional relationship between the supply of LP tokens - * and the amount of each underlying token for a two-token Well. - * @dev When removing `s` LP tokens with a Well with `S` LP token supply, the user - * recieves `s * b_i / S` of each underlying token. - * reserves are scaled as needed based on tknXScalar - */ - function calcLPTokenUnderlying( - uint lpTokenAmount, - uint[] memory reserves, - uint lpTokenSupply, - bytes calldata - ) external view returns (uint[] memory underlyingAmounts) { - underlyingAmounts = new uint[](2); - underlyingAmounts[0] = (lpTokenAmount * reserves[0]) / lpTokenSupply; - underlyingAmounts[1] = (lpTokenAmount * reserves[1]) / lpTokenSupply; - } - /** * @inheritdoc IMultiFlowPumpWellFunction - * @dev when the reserves are equal, the summation of the reserves - * is equivalent to the token supply of the Well. The LP token supply is calculated from - * `reserves`, and is scaled based on `ratios`. + * @dev `calcReserveAtRatioSwap` fetches the closes approxeimate ratios from the target price, + * and performs neuwtons method in order to */ function calcReserveAtRatioSwap( uint256[] memory reserves, @@ -193,90 +191,67 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { uint256[] memory ratios, bytes calldata data ) external view returns (uint256 reserve) { - DeltaB memory db; - uint256 i = j == 1 ? 0 : 1; - // scale reserves to 18 decimals. - uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); - console.log("lpTokenSupply:", lpTokenSupply); - // inital guess - db.currentBeans = int256(reserves[j]); - console.log("db.currentBeans"); - console.logInt(db.currentBeans); - db.pegBeans = lpTokenSupply / 2; - console.log("db.pegBeans"); - console.log(db.pegBeans); - db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); - console.log("db.deltaBToPeg"); - console.logInt(db.deltaBToPeg); - - uint256 prevPrice; - uint256 x; - uint256 x2; - - // fetch target and pool prices. - // scale ratio by precision: - ratios[0] = ratios[0] * CALC_RATE_PRECISION; - ratios[1] = ratios[1] * CALC_RATE_PRECISION; - console.log("ratios[0]", ratios[0]); - console.log("ratios[1]", ratios[1]); - - db.targetPrice = calcRate(ratios, i, j, data); - console.log("db.targetPrice", db.targetPrice); - console.log("reserve0", reserves[0]); - console.log("reserve1", reserves[1]); - db.poolPrice = calcRate(reserves, i, j, data); - console.log("db.poolPrice", db.poolPrice); - - for (uint k; k < 2; k++) { - db.deltaPriceToTarget = - int256(db.targetPrice) - - int256(db.poolPrice); - console.log("deltaPriceToTarget"); - console.logInt(db.deltaPriceToTarget); - db.deltaPriceToPeg = 1e18 - int256(db.poolPrice); - console.log("deltaPriceToPeg"); - - console.logInt(db.deltaPriceToPeg); - console.log("reserve0----", reserves[j]); - console.log("pegBeans----", db.pegBeans); - db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); - console.log("deltaBToPeg"); - console.logInt(db.deltaBToPeg); - console.log("estDeltaB"); - console.logInt(db.estDeltaB); - - if (db.deltaPriceToPeg != 0) { - db.estDeltaB = - (db.deltaBToPeg * - int256( - (db.deltaPriceToTarget * 1e18) / db.deltaPriceToPeg - )) / - 1e18; + // scale reserves and ratios: + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + PriceData memory pd; + + { + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; + } + + // get ratios and price from the closest highest and lowest price from targetPrice: + pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceSwap(pd.targetPrice); + + // perform an initial update on the reserves, such that `calcRate(reserves, i, j, data) == pd.lutData.lowPrice. + + // calculate lp token supply: + uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + + // lpTokenSupply / 2 gives the reserves at parity: + uint256 parityReserve = lpTokenSupply / 2; + + // update `scaledReserves`. + scaledReserves[0] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; + scaledReserves[1] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; + + // calculate max step size: + pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; + + // initialize currentPrice: + pd.currentPrice = pd.lutData.lowPrice; + + for (uint256 k; k < 255; k++) { + // scale stepSize proporitional to distance from price: + uint256 stepSize = + pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + // increment reserve by stepSize: + scaledReserves[j] = reserves[j] + stepSize; + // calculate scaledReserve[i]: + scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + + // check if new price is within 1 of target price: + if (pd.currentPrice > pd.targetPrice) { + if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); } else { - db.estDeltaB = 0; + if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); } - console.log("estDeltaB"); - console.logInt(db.estDeltaB); - x = uint256(int256(reserves[j]) + db.estDeltaB); - console.log("-----reserve0----", reserves[0]); - console.log("-----reserve1----", reserves[1]); - console.log(i); - x2 = calcReserve(reserves, i, lpTokenSupply, data); - console.log("x", x, "x2", x2); - reserves[j] = x; - reserves[i] = x2; - prevPrice = db.poolPrice; - db.poolPrice = calcRate(reserves, i, j, data); - if (prevPrice > db.poolPrice) { - if (prevPrice - db.poolPrice <= 1) break; - } else if (db.poolPrice - prevPrice <= 1) break; + + // calc currentPrice: + pd.currentPrice = calcRate(reserves, i, j, data); } - return reserves[j]; } /** * @inheritdoc IMultiFlowPumpWellFunction + * @dev Returns a rate with 6 decimal precision. + * Requires a minimum scaled reserves of 1e12. + * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, + * which is prohibtive for large value tokens. */ function calcRate( uint256[] memory reserves, @@ -284,55 +259,75 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { uint256 j, bytes calldata data ) public view returns (uint256 rate) { - // console.log("reserves[j]", reserves[j]); - // console.log("reserves[i]", reserves[i]); - uint256[] memory _reserves = new uint256[](2); - _reserves[0] = reserves[0]; - _reserves[1] = reserves[1]; - uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); - // console.log("reserves[j]", reserves[j]); - // console.log("reserves[i]", reserves[i]); - // console.log("_reserves[j]", _reserves[j]); - // console.log("_reserves[i]", _reserves[i]); - // add the precision of opposite token to the reserve. - (uint256 padding, uint256 divPadding) = getPadding( - _reserves, - i, - j, - data - ); - // console.log("padding", padding); - // console.log("reserves[j]", _reserves[j]); - _reserves[j] = _reserves[j] + padding; - // console.log("reserves[j]", _reserves[j]); - // console.log("reserves[i]", _reserves[i]); - uint256 oldReserve = _reserves[i]; - uint256 newReserve = calcReserve(_reserves, i, lpTokenSupply, data); - // console.log("oldReserve", oldReserve); - // console.log("newReserve", newReserve); - // console.log("diff", oldReserve - newReserve); - uint256 tokenIDecimal = getTokenDecimal(i, data); - uint256 tokenJDecimal = getTokenDecimal(j, data); - // console.log("Check", (18 + tokenIDecimal - tokenJDecimal)); - // console.log("divPadding", divPadding); - - rate = - ((oldReserve - newReserve) * - (10 ** (18 + tokenIDecimal - tokenJDecimal))) / - divPadding; + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). + uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + + // reverse if i is not 0. + + // add 1e6 to reserves: + scaledReserves[j] += PRICE_PRECISION; + + // calculate new reserve 1: + uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + rate = (scaledReserves[0] - new_reserve1); } /** * @inheritdoc IBeanstalkWellFunction + * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. + * @dev `calcReserveAtRatioLiquidity` fetches the closest approximate ratios from the target price, and + * perform an neutonian-estimation to calculate the reserves. */ function calcReserveAtRatioLiquidity( uint256[] calldata reserves, uint256 j, uint256[] calldata ratios, - bytes calldata - ) external pure returns (uint256 reserve) { + bytes calldata data + ) external view returns (uint256 reserve) { uint256 i = j == 1 ? 0 : 1; - reserve = (reserves[i] * ratios[j]) / ratios[i]; + // scale reserves and ratios: + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + PriceData memory pd; + + { + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; + } + + // calc currentPrice: + pd.currentPrice = calcRate(reserves, i, j, data); + + // get ratios and price from the closest highest and lowest price from targetPrice: + pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); + + // update scaledReserve[j] based on lowPrice: + scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; + + // calculate max step size: + pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; + + for (uint256 k; k < 255; k++) { + // scale stepSize proporitional to distance from price: + uint256 stepSize = + pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + // increment reserve by stepSize: + scaledReserves[j] = reserves[j] + stepSize; + // calculate new price from reserves: + pd.currentPrice = calcRate(scaledReserves, i, j, data); + + // check if new price is within 1 of target price: + if (pd.currentPrice > pd.targetPrice) { + if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } else { + if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } + } } function name() external pure returns (string memory) { @@ -345,101 +340,23 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { /** * @notice decodes the data encoded in the well. - * @return Ann (A parameter * n^2) - * @return precisions the value used to scale each token such that - * each token has 18 decimals. + * @return decimals an array of the decimals of the tokens in the well. */ - function decodeWFData( - bytes memory data - ) public view virtual returns (uint256 Ann, uint256[] memory precisions) { - WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - if (wfd.a == 0) revert InvalidAParameter(wfd.a); - if (wfd.token0 == address(0) || wfd.token1 == address(0)) - revert InvalidTokens(); - - uint8 token0Decimals = IERC20(wfd.token0).decimals(); - uint8 token1Decimals = IERC20(wfd.token1).decimals(); - - if (token0Decimals > 18) revert InvalidTokenDecimals(token0Decimals); - if (token1Decimals > 18) revert InvalidTokenDecimals(token1Decimals); - - Ann = wfd.a * N * N * A_PRECISION; - - precisions = new uint256[](2); - precisions[0] = - 10 ** (POOL_PRECISION_DECIMALS - uint256(token0Decimals)); - precisions[1] = - 10 ** (POOL_PRECISION_DECIMALS - uint256(token1Decimals)); - } + function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) { + (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256)); - function getTokenDecimal( - uint256 i, - bytes memory data - ) internal view returns (uint256 decimals) { - WellFunctionData memory wfd = abi.decode(data, (WellFunctionData)); - return - i == 0 - ? IERC20(wfd.token0).decimals() - : IERC20(wfd.token1).decimals(); - } - - function getPadding( - uint256[] memory reserves, - uint256 i, - uint256 j, - bytes memory data - ) internal view returns (uint256 padding, uint256 divPadding) { - uint256 k = reserves[i] < reserves[j] ? i : j; - - uint256 numDigits = getNumDigits(reserves, k); - - // Set the slippage error equal to the precision. - padding = numDigits / 2; - - uint256 tokenIDecimal = getTokenDecimal(i, data); - uint256 tokenJDecimal = getTokenDecimal(j, data); - - // console.log("tokenIDecimalI", tokenIDecimal); - // console.log("tokenJDecimalJ", tokenJDecimal); - - // console.log("paddings", padding); - if (tokenJDecimal > tokenIDecimal) { - divPadding = 10 ** padding; - // console.log("paddings", padding); - padding = padding + tokenJDecimal - tokenIDecimal; - } else { - divPadding = 10 ** (padding + (tokenIDecimal - tokenJDecimal)); + // if well data returns 0, assume 18 decimals. + if (decimal0 == 0) { + decimal0 = 18; } - // console.log("paddings", padding); - - if (padding > tokenJDecimal) { - padding = tokenJDecimal; + if (decimal0 == 0) { + decimal1 = 18; } - // console.log("paddings", padding); - padding = 10 ** padding; - - // 10000001/10000000 + if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals(); - // 10000000 - - // 100001/99999 = 1.0000200002 -> 4 zeroes. - // 1000001/99999 = 10.0001100011 -> 4 zeroes. - // 10000001/99999 = 100.0010100101 -> 4 zeroes - // 100001/999999 = 0.1000011 -> 4 zeros. - // 100001/9999999 = 0.010000101 -> 4 zeroes - // 100001/99999999 = 0.00100001001 -> 4 zeroes - } - - function getNumDigits( - uint256[] memory reserves, - uint256 i - ) internal pure returns (uint256 numDigits) { - numDigits = 0; - uint256 reserve = reserves[i]; - while (reserve != 0) { - reserve /= 10; - numDigits++; - } + decimals = new uint256[](2); + decimals[0] = decimal0; + decimals[1] = decimal1; } /** @@ -447,12 +364,12 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { * @dev this sets both reserves to 18 decimals. */ function getScaledReserves( - uint[] memory reserves, - uint256[] memory precisions - ) internal pure returns (uint[] memory) { - reserves[0] = reserves[0].mul(precisions[0]); - reserves[1] = reserves[1].mul(precisions[1]); - return reserves; + uint256[] memory reserves, + uint256[] memory decimals + ) internal pure returns (uint256[] memory scaledReserves) { + scaledReserves = new uint256[](2); + scaledReserves[0] = reserves[0] * 10 ** (18 - decimals[0]); + scaledReserves[1] = reserves[1] * 10 ** (18 - decimals[1]); } function _calcReserve( @@ -461,10 +378,7 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { uint256 c, uint256 lpTokenSupply ) private pure returns (uint256) { - return - reserve.mul(reserve).add(c).div( - reserve.mul(2).add(b).sub(lpTokenSupply) - ); + return reserve.mul(reserve).add(c).div(reserve.mul(2).add(b).sub(lpTokenSupply)); } function getBandC( @@ -472,12 +386,96 @@ contract CurveStableSwap2 is IBeanstalkWellFunction { uint256 lpTokenSupply, uint256 reserves ) private pure returns (uint256 c, uint256 b) { - c = lpTokenSupply - .mul(lpTokenSupply) - .div(reserves.mul(N)) - .mul(lpTokenSupply) - .mul(A_PRECISION) - .div(Ann.mul(N)); + c = lpTokenSupply.mul(lpTokenSupply).div(reserves.mul(N)).mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); } } + +// /** +// * @inheritdoc IMultiFlowPumpWellFunction +// * @dev when the reserves are equal, the summation of the reserves +// * is equivalent to the token supply of the Well. The LP token supply is calculated from +// * `reserves`, and is scaled based on `ratios`. +// */ +// function calcReserveAtRatioSwap( +// uint256[] memory reserves, +// uint256 j, +// uint256[] memory ratios, +// bytes calldata data +// ) external view returns (uint256 reserve) { +// DeltaB memory db; + +// uint256 i = j == 1 ? 0 : 1; +// // scale reserves to 18 decimals. +// uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); +// console.log("lpTokenSupply:", lpTokenSupply); +// // inital guess +// db.currentBeans = int256(reserves[j]); +// console.log("db.currentBeans"); +// console.logInt(db.currentBeans); +// db.pegBeans = lpTokenSupply / 2; +// console.log("db.pegBeans"); +// console.log(db.pegBeans); +// db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); +// console.log("db.deltaBToPeg"); +// console.logInt(db.deltaBToPeg); + +// uint256 prevPrice; +// uint256 x; +// uint256 x2; + +// // fetch target and pool prices. +// // scale ratio by precision: +// ratios[0] = ratios[0] * CALC_RATE_PRECISION; +// ratios[1] = ratios[1] * CALC_RATE_PRECISION; +// console.log("ratios[0]", ratios[0]); +// console.log("ratios[1]", ratios[1]); + +// db.targetPrice = calcRate(ratios, i, j, data); +// console.log("db.targetPrice", db.targetPrice); +// console.log("reserve0", reserves[0]); +// console.log("reserve1", reserves[1]); +// db.poolPrice = calcRate(reserves, i, j, data); +// console.log("db.poolPrice", db.poolPrice); + +// for (uint256 k; k < 2; k++) { +// db.deltaPriceToTarget = int256(db.targetPrice) - int256(db.poolPrice); +// console.log("deltaPriceToTarget"); +// console.logInt(db.deltaPriceToTarget); +// db.deltaPriceToPeg = 1e18 - int256(db.poolPrice); +// console.log("deltaPriceToPeg"); + +// console.logInt(db.deltaPriceToPeg); +// console.log("reserve0----", reserves[j]); +// console.log("pegBeans----", db.pegBeans); +// db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); +// console.log("deltaBToPeg"); +// console.logInt(db.deltaBToPeg); +// console.log("estDeltaB"); +// console.logInt(db.estDeltaB); + +// if (db.deltaPriceToPeg != 0) { +// db.estDeltaB = (db.deltaBToPeg * int256((db.deltaPriceToTarget * 1e18) / db.deltaPriceToPeg)) / 1e18; +// } else { +// db.estDeltaB = 0; +// } +// console.log("estDeltaB"); +// console.logInt(db.estDeltaB); +// x = uint256(int256(reserves[j]) + db.estDeltaB); +// console.log("-----reserve0----", reserves[0]); +// console.log("-----reserve1----", reserves[1]); +// console.log(i); +// x2 = calcReserve(reserves, i, lpTokenSupply, data); +// console.log("x", x, "x2", x2); +// reserves[j] = x; +// reserves[i] = x2; +// prevPrice = db.poolPrice; +// db.poolPrice = calcRate(reserves, i, j, data); +// if (prevPrice > db.poolPrice) { +// if (prevPrice - db.poolPrice <= 1) break; +// } else if (db.poolPrice - prevPrice <= 1) { +// break; +// } +// } +// return reserves[j]; +// } diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol new file mode 100644 index 00000000..e0c20b0b --- /dev/null +++ b/src/functions/Stable2.sol @@ -0,0 +1,241 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import {IMultiFlowPumpWellFunction} from "src/interfaces/IMultiFlowPumpWellFunction.sol"; +import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; +import {LibMath} from "src/libraries/LibMath.sol"; +import {SafeMath} from "oz/utils/math/SafeMath.sol"; +import {IERC20} from "forge-std/interfaces/IERC20.sol"; +import {Math} from "oz/utils/math/Math.sol"; +import {console} from "forge-std/console.sol"; + +/** + * @author Brean + * @title Stable pricing function for Wells with 2 tokens. + * developed by Solidly/Aerodome: https://github.com/aerodrome-finance/contracts + * + * Stable2 Wells with 2 tokens use the formula: + * `d^2 = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` + * + * Where: + * `d` is the supply of LP tokens + * `b_i` is the reserve at index `i` + */ +contract Stable2 is ProportionalLPToken2, IMultiFlowPumpWellFunction { + using Math for uint256; + + uint256 constant PRECISION = 1e18; + + mapping(address well => uint256 liquidityIndex) public liquidityIndexForWell; + + /** + * @notice Calculates the `j`th reserve given a list of `reserves` and `lpTokenSupply`. + * @param reserves A list of token reserves. The jth reserve will be ignored, but a placeholder must be provided. + * @param j The index of the reserve to solve for + * @param data Extra Well function data provided on every call + * @return reserve The resulting reserve at the jth index + * @dev Should round up to ensure that Well reserves are marginally higher to enforce calcLpTokenSupply(...) >= totalSupply() + */ + function calcReserve( + uint256[] memory reserves, + uint256 j, + uint256, + bytes calldata data + ) external pure returns (uint256 reserve) { + uint256 i = j == 0 ? 1 : 0; + (uint256 decimals0, uint256 decimals1) = decodeWellData(data); + uint256 xy; + uint256 y = reserves[j]; + uint256 x0 = reserves[i]; + // if j is 0, swap decimals0 and decimals1 + if (j == 0) { + (decimals0, decimals1) = (decimals1, decimals0); + } + xy = _k(x0, y, decimals0, decimals1); + + for (uint256 l = 0; l < 255; l++) { + uint256 k = _f(reserves[i], j); + if (k < xy) { + // there are two cases where dy == 0 + // case 1: The y is converged and we find the correct answer + // case 2: _d(x0, y) is too large compare to (xy - k) and the rounding error + // screwed us. + // In this case, we need to increase y by 1 + uint256 dy = ((xy - k) * PRECISION) / _d(x0, y); + if (dy == 0) { + if (k == xy) { + // We found the correct answer. Return y + return y; + } + if (_k(x0, y + 1, decimals0, decimals1) > xy) { + // If _k(x0, y + 1) > xy, then we are close to the correct answer. + // There's no closer answer than y + 1 + return y + 1; + } + dy = 1; + } + y = y + dy; + } else { + uint256 dy = ((k - xy) * PRECISION) / _d(x0, y); + if (dy == 0) { + if (k == xy || _f(x0, y - 1) < xy) { + // Likewise, if k == xy, we found the correct answer. + // If _f(x0, y - 1) < xy, then we are close to the correct answer. + // There's no closer answer than "y" + // It's worth mentioning that we need to find y where f(x0, y) >= xy + // As a result, we can't return y - 1 even it's closer to the correct answer + return y; + } + dy = 1; + } + y = y - dy; + } + } + revert("!y"); + } + + /** + * @notice Gets the LP token supply given a list of reserves. + * @param reserves A list of token reserves + * @param data Extra Well function data provided on every call + * @return lpTokenSupply The resulting supply of LP tokens + * @dev Should round down to ensure so that the Well Token supply is marignally lower to enforce calcLpTokenSupply(...) >= totalSupply() + * @dev `s^2 = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` + * The further apart the reserve values, the greater the loss of precision in the `sqrt` function. + */ + function calcLpTokenSupply( + uint256[] memory reserves, + bytes calldata data + ) external pure returns (uint256 lpTokenSupply) { + console.log("calcLpTokenSupply"); + (uint256 decimals0, uint256 decimals1) = decodeWellData(data); + console.log(_k(reserves[0], reserves[1], decimals0, decimals1).sqrt()); + return _k(reserves[0], reserves[1], decimals0, decimals1).sqrt(); + } + + /** + * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. + * assumes that reserve_j is being swapped for other reserves in the Well. + * @dev used by Beanstalk to calculate the deltaB every Season + * @param reserves The reserves of the Well + * @param j The index of the reserve to solve for + * @param ratios The ratios of reserves to solve for + * @param data Well function data provided on every call + * @return reserve The resulting reserve at the jth index + */ + function calcReserveAtRatioSwap( + uint256[] calldata reserves, + uint256 j, + uint256[] calldata ratios, + bytes calldata data + ) external view returns (uint256 reserve) {} + + /** + * @inheritdoc IMultiFlowPumpWellFunction + * @notice Calculates the exchange rate of the Well. + * @dev used by Beanstalk to calculate the exchange rate of the Well. + * The maximum value of each reserves cannot exceed max(uint128)/ 2. + * Returns with 18 decimal precision. + */ + function calcRate( + uint256[] calldata reserves, + uint256 i, + uint256 j, + bytes calldata data + ) external pure returns (uint256 rate) { + (uint256 precision0, uint256 precision1) = decodeWellData(data); + if (i == 1) { + (precision0, precision1) = (precision1, precision0); + } + uint256 reservesI = uint256(reserves[i]) * precision0; + uint256 reservesJ = uint256(reserves[j]) * precision1; + rate = reservesJ.mulDiv(_g(reservesI, reservesJ), _g(reservesJ, reservesI)).mulDiv(PRECISION, reservesI); + } + + /** + * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. + * assumes that reserve_j is being added or removed in exchange for LP Tokens. + * @dev used by Beanstalk to calculate the max deltaB that can be converted in/out of a Well. + * @param reserves The reserves of the Well + * @param j The index of the reserve to solve for + * @param ratios The ratios of reserves to solve for + * @return reserve The resulting reserve at the jth index + */ + function calcReserveAtRatioLiquidity( + uint256[] calldata reserves, + uint256 j, + uint256[] calldata ratios, + bytes calldata + ) external pure returns (uint256 reserve) { + // TODO + // start at current reserves, increases reserves j until the ratio is correct + } + + /** + * @notice returns k, based on the reserves of x/y. + * @param x the reserves of `x` + * @param y the reserves of `y`. + * + * @dev Implmentation from: + * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L315 + */ + function _k(uint256 x, uint256 y, uint256 xDecimals, uint256 yDecimals) internal pure returns (uint256) { + console.log("x: %s, y: %s", x, y); + // scale x and y to 18 decimals + uint256 _x = (x * PRECISION) / xDecimals; + uint256 _y = (y * PRECISION) / yDecimals; + uint256 _a = (_x * _y) / PRECISION; + uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); + console.log("k: %s", (_a * _b) / PRECISION); + return (_a * _b) / PRECISION; // x3y+y3x >= k + } + + /** + * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L401C5-L405C6 + */ + function _f(uint256 x0, uint256 y) internal pure returns (uint256) { + uint256 _a = (x0 * y) / PRECISION; + uint256 _b = ((x0 * x0) / PRECISION + (y * y) / PRECISION); + return (_a * _b) / PRECISION; + } + + /** + * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L401C5-L405C6 + */ + function _d(uint256 x0, uint256 y) internal pure returns (uint256) { + return (3 * x0 * ((y * y) / PRECISION)) / PRECISION + ((((x0 * x0) / PRECISION) * x0) / PRECISION); + } + + /** + * @notice returns g(x, y) = 3x^2 + y^2 + * @dev used in `calcRate` to calculate the exchange rate of the well. + */ + function _g(uint256 x, uint256 y) internal pure returns (uint256) { + return (3 * x ** 2) + y ** 2; + } + + /** + * @notice The stableswap requires 2 parameters to be encoded in the well: + * Incorrect encoding may result in incorrect calculations. + * @dev tokens with more than 18 decimals are not supported. + * @return precision0 precision of token0 + * @return precision1 precision of token1 + * + */ + function decodeWellData(bytes calldata data) public pure returns (uint256 precision0, uint256 precision1) { + (precision0, precision1) = abi.decode(data, (uint256, uint256)); + console.log(precision0, precision1); + precision0 = 10 ** (precision0); + precision1 = 10 ** (precision1); + console.log(precision0, precision1); + } + + function name() external pure override returns (string memory) { + return "Stable2"; + } + + function symbol() external pure override returns (string memory) { + return "S2"; + } +} diff --git a/src/libraries/LibMath.sol b/src/libraries/LibMath.sol index 850adc88..c291c69f 100644 --- a/src/libraries/LibMath.sol +++ b/src/libraries/LibMath.sol @@ -68,7 +68,6 @@ library LibMath { * Implementation from: https://github.com/Gaussian-Process/solidity-sqrt/blob/main/src/FixedPointMathLib.sol * based on https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol */ - function sqrt(uint256 a) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { diff --git a/test/Stable2/Well.BoreStableSwap.t.sol b/test/Stable2/Well.BoreStableSwap.t.sol new file mode 100644 index 00000000..42b0179a --- /dev/null +++ b/test/Stable2/Well.BoreStableSwap.t.sol @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; +import {MockPump} from "mocks/pumps/MockPump.sol"; +import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; + +contract WellBoreStableSwapTest is TestHelper { + /// @dev Bore a 2-token Well with CurveStableSwap2 & several pumps. + function setUp() public { + // setup a StableSwap Well with an A parameter of 10. + setupStableSwapWell(); + // Well.sol doesn't use wellData, so it should always return empty bytes + wellData = new bytes(0); + } + + //////////// Well Definition //////////// + + function test_tokens() public { + assertEq(well.tokens(), tokens); + } + + function test_wellFunction() public { + assertEq(well.wellFunction(), wellFunction); + } + + function test_pumps() public { + assertEq(well.pumps(), pumps); + } + + function test_wellData() public { + assertEq(well.wellData(), wellData); + } + + function test_aquifer() public { + assertEq(well.aquifer(), address(aquifer)); + } + + function test_well() public { + ( + IERC20[] memory _wellTokens, + Call memory _wellFunction, + Call[] memory _wellPumps, + bytes memory _wellData, + address _aquifer + ) = well.well(); + + assertEq(_wellTokens, tokens); + assertEq(_wellFunction, wellFunction); + assertEq(_wellPumps, pumps); + assertEq(_wellData, wellData); + assertEq(_aquifer, address(aquifer)); + } + + function test_getReserves() public { + assertEq(well.getReserves(), getBalances(address(well), well).tokens); + } + + //////////// ERC20 LP Token //////////// + + function test_name() public { + assertEq(well.name(), "TOKEN0:TOKEN1 StableSwap Well"); + } + + function test_symbol() public { + assertEq(well.symbol(), "TOKEN0TOKEN1SS2w"); + } + + function test_decimals() public { + assertEq(well.decimals(), 18); + } + + //////////// Deployment //////////// + + /// @dev Fuzz different combinations of Well configuration data and check + /// that the Aquifer deploys everything correctly. + function testFuzz_bore(uint256 numberOfPumps, bytes[4] memory pumpData, uint256 nTokens, uint256 a) public { + // Constraints + numberOfPumps = bound(numberOfPumps, 0, 4); + for (uint256 i = 0; i < numberOfPumps; i++) { + vm.assume(pumpData[i].length <= 4 * 32); + } + nTokens = bound(nTokens, 2, tokens.length); + + vm.assume(a > 0); + // Get the first `nTokens` mock tokens + IERC20[] memory wellTokens = getTokens(nTokens); + bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); + + // Deploy a Well Function + wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionBytes); + + // Etch the MockPump at each `target` + Call[] memory pumps = new Call[](numberOfPumps); + for (uint256 i = 0; i < numberOfPumps; i++) { + pumps[i].target = address(new MockPump()); + pumps[i].data = pumpData[i]; + } + + // Deploy the Well + Well _well = + encodeAndBoreWell(address(aquifer), wellImplementation, wellTokens, wellFunction, pumps, bytes32(0)); + + // Check Pumps + assertEq(_well.numberOfPumps(), numberOfPumps, "number of pumps mismatch"); + Call[] memory _pumps = _well.pumps(); + + if (numberOfPumps > 0) { + assertEq(_well.firstPump(), pumps[0], "pump mismatch"); + } + + for (uint256 i = 0; i < numberOfPumps; i++) { + assertEq(_pumps[i], pumps[i], "pump mismatch"); + } + + // Check token addresses + assertEq(_well.tokens(), wellTokens); + + // Check Well Function + assertEq(_well.wellFunction(), wellFunction); + assertEq(_well.wellFunctionAddress(), wellFunction.target); + + // Check that Aquifer recorded the deployment + assertEq(aquifer.wellImplementation(address(_well)), wellImplementation); + } +} diff --git a/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol new file mode 100644 index 00000000..8bcbf35d --- /dev/null +++ b/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -0,0 +1,195 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, CurveStableSwap2, Balances} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { + event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); + + uint256[] tokenAmountsOut; + uint256 requiredLpAmountIn; + bytes _data; + + // Setup + CurveStableSwap2 ss; + + uint256 constant addedLiquidity = 1000 * 1e18; + + function setUp() public { + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); + + _data = abi.encode(18, 18); + + // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + // Shared removal amounts + tokenAmountsOut.push(500 * 1e18); // 500 token0 + tokenAmountsOut.push(506 * 1e17); // 50.6 token1 + requiredLpAmountIn = 552_016_399_701_327_563_972; // ~552e18 LP needed to remove `tokenAmountsOut` + } + + /// @dev Assumes use of ConstantProduct2 + function test_getRemoveLiquidityImbalancedIn() public { + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); + assertEq(lpAmountIn, requiredLpAmountIn); + } + + /// @dev not enough LP to receive `tokenAmountsOut` + function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { + uint256 maxLpAmountIn = 5 * 1e18; + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + } + + function test_removeLiquidityImbalanced_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidityImbalanced(0, new uint256[](2), user, block.timestamp - 1); + } + + /// @dev Base case + function test_removeLiquidityImbalanced() public prank(user) { + Balances memory userBalanceBefore = getBalances(user, well); + + uint256 initialLpAmount = userBalanceBefore.lp; + uint256 maxLpAmountIn = requiredLpAmountIn; + + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(maxLpAmountIn, tokenAmountsOut, user); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); + + Balances memory userBalanceAfter = getBalances(user, well); + Balances memory wellBalanceAfter = getBalances(address(well), well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfter.lp, initialLpAmount - maxLpAmountIn); + + // `user` balance of underlying tokens increases + // assumes initial balance of zero + assertEq(userBalanceAfter.tokens[0], tokenAmountsOut[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], tokenAmountsOut[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + assertEq(wellBalanceAfter.tokens[0], 1500 * 1e18, "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], 19_494 * 1e17, "Incorrect token1 well reserve"); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function testFuzz_removeLiquidityImbalanced(uint256 a0, uint256 a1) public prank(user) { + // Setup amounts of liquidity to remove + // NOTE: amounts may or may not be equal + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 0, 750e18); + amounts[1] = bound(a1, 0, 750e18); + + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; + + // lpAmountIn should be <= umaxLpAmountIn + uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + + // Remove all of `user`'s liquidity and deliver them the tokens + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(lpAmountBurned, amounts, user); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfterRemoveLiquidity.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + + // `user` balance of underlying tokens increases + assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + // Equal amount of liquidity of 1000e18 were added in the setup function hence the + // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity + // is 1000e18 of each token. + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[0], + (initialLiquidity + addedLiquidity) - amounts[0], + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[1], + (initialLiquidity + addedLiquidity) - amounts[1], + "Incorrect token1 well reserve" + ); + checkStableSwapInvariant(address(well)); + } + + /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal + /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` + /// before liquidity is removed by `user`. + function testFuzz_removeLiquidityImbalanced_withSwap(uint256 a0, uint256 imbalanceBias) public { + // Setup amounts of liquidity to remove + // NOTE: amounts[0] is bounded at 1 to prevent slippage overflow + // failure, bug fix in progress + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 1, 950e18); + amounts[1] = amounts[0]; + imbalanceBias = bound(imbalanceBias, 0, 40e18); + + // `user2` performs a swap to imbalance the pool by `imbalanceBias` + vm.prank(user2); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + + // `user` has LP tokens and will perform a `removeLiquidityImbalanced` call + vm.startPrank(user); + + Balances memory wellBalanceBefore = getBalances(address(well), well); + Balances memory userBalanceBefore = getBalances(user, well); + + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBefore.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBefore.tokens[1] - amounts[1]; + + // lpAmountIn should be <= user's LP balance + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + + // Remove some of `user`'s liquidity and deliver them the tokens + uint256 maxLpAmountIn = userBalanceBefore.lp; + vm.expectEmit(true, true, true, true); + emit RemoveLiquidity(lpAmountBurned, amounts, user); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); + + Balances memory wellBalanceAfter = getBalances(address(well), well); + Balances memory userBalanceAfter = getBalances(user, well); + + // `user` balance of LP tokens decreases + assertEq(userBalanceAfter.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); + + // `user` balance of underlying tokens increases + assertEq(userBalanceAfter.tokens[0], userBalanceBefore.tokens[0] + amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], userBalanceBefore.tokens[1] + amounts[1], "Incorrect token1 user balance"); + + // Well's reserve of underlying tokens decreases + assertEq(wellBalanceAfter.tokens[0], wellBalanceBefore.tokens[0] - amounts[0], "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], wellBalanceBefore.tokens[1] - amounts[1], "Incorrect token1 well reserve"); + checkInvariant(address(well)); + } +} diff --git a/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol new file mode 100644 index 00000000..ab5032c7 --- /dev/null +++ b/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { + event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); + + CurveStableSwap2 ss; + uint256 constant addedLiquidity = 1000 * 1e18; + bytes _data; + + function setUp() public { + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); + + // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + _data = abi.encode(18, 18); + } + + /// @dev Assumes use of CurveStableSwap2 + function test_getRemoveLiquidityOneTokenOut() public { + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); + assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); + } + + /// @dev not enough tokens received for `lpAmountIn`. + function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { + uint256 lpAmountIn = 500 * 1e18; + uint256 minTokenAmountOut = 500 * 1e18; + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + } + + function test_removeLiquidityOneToken_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidityOneToken(0, tokens[0], 0, user, block.timestamp - 1); + } + + /// @dev Base case + function test_removeLiquidityOneToken() public prank(user) { + uint256 lpAmountIn = 500 * 1e18; + uint256 minTokenAmountOut = 498_279_423_862_830_737_827; + Balances memory prevUserBalance = getBalances(user, well); + + vm.expectEmit(true, true, true, true); + emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); + + uint256 amountOut = + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); + + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + + assertEq(userBalance.lp, prevUserBalance.lp - lpAmountIn, "Incorrect lpAmountIn"); + + assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); + assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); + + // Equal amount of liquidity of 1000e18 were added in the setup function hence the + // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity + // is 1000e18 of each token. + assertEq( + wellBalance.tokens[0], + (initialLiquidity + addedLiquidity) - minTokenAmountOut, + "Incorrect token0 well reserve" + ); + assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); + checkStableSwapInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { + // Assume we're removing tokens[0] + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 1e18, 750e18); + amounts[1] = 0; + + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + uint256 userLpBalance = userBalanceBeforeRemoveLiquidity.lp; + + // Find the LP amount that should be burned given the fuzzed + // amounts. Works even though only amounts[0] is set. + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + + // Calculate change in Well reserves after removing liquidity + uint256[] memory reserves = new uint256[](2); + reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; + reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; // should stay the same + + // Calculate the new LP token supply after the Well's reserves are changed. + // The delta `lpAmountBurned` is the amount of LP that should be burned + // when this liquidity is removed. + uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); + uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; + vm.expectEmit(true, true, true, false); + emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); + uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out + assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); + + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); + + assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same + assertApproxEqAbs( + wellBalanceAfterRemoveLiquidity.tokens[0], + (initialLiquidity + addedLiquidity) - amounts[0], + 1, + "Incorrect token0 well reserve" + ); + assertEq( + wellBalanceAfterRemoveLiquidity.tokens[1], + (initialLiquidity + addedLiquidity) - amounts[1], + "Incorrect token1 well reserve" + ); // should stay the same + checkStableSwapInvariant(address(well)); + } + + // TODO: fuzz test: imbalanced ratio of tokens +} diff --git a/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol new file mode 100644 index 00000000..4f34da94 --- /dev/null +++ b/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { + CurveStableSwap2 ss; + bytes constant data = ""; + uint256 constant addedLiquidity = 1000 * 1e18; + + function setUp() public { + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); + + // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens + addLiquidityEqualAmount(user, addedLiquidity); + } + + /// @dev ensure that Well liq was initialized correctly in {setUp} + /// currently, liquidity is added in {TestHelper} and above + function test_liquidityInitialized() public { + IERC20[] memory tokens = well.tokens(); + for (uint256 i; i < tokens.length; i++) { + assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); + } + assertEq(well.totalSupply(), 4000 * 1e18, "incorrect totalSupply"); + } + + /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying + /// since the tokens in the Well are balanced, user receives equal amounts + function test_getRemoveLiquidityOut() public { + uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); + for (uint256 i; i < tokens.length; i++) { + assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); + } + } + + /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token + function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { + uint256 lpAmountIn = 1000 * 1e18; + + uint256[] memory minTokenAmountsOut = new uint256[](2); + minTokenAmountsOut[0] = 501 * 1e18; // too high + minTokenAmountsOut[1] = 500 * 1e18; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 500 * 1e18, minTokenAmountsOut[0])); + well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); + } + + function test_removeLiquidity_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.removeLiquidity(0, new uint256[](2), user, block.timestamp - 1); + } + + /// @dev removeLiquidity: remove to equal amounts of underlying + function test_removeLiquidity() public prank(user) { + uint256 lpAmountIn = 1000 * 1e18; + + uint256[] memory amountsOut = new uint256[](2); + amountsOut[0] = 500 * 1e18; + amountsOut[1] = 500 * 1e18; + + Snapshot memory before; + RemoveLiquidityAction memory action; + + action.amounts = amountsOut; + action.lpAmountIn = lpAmountIn; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountIn, amountsOut, user, type(uint256).max); + afterRemoveLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: EQUAL token reserves, BALANCED removal + /// The Well contains equal reserves of all underlying tokens before execution. + function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { + // Setup amounts of liquidity to remove + // NOTE: amounts may or may not match the maximum removable by `user`. + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(a0, 0, 1000e18); + amounts[1] = amounts[0]; + + Snapshot memory before; + RemoveLiquidityAction memory action; + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); + + action.amounts = amounts; + action.lpAmountIn = lpAmountIn; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountIn, amounts, user, type(uint256).max); + afterRemoveLiquidity(before, action); + + assertLe( + well.totalSupply(), + CurveStableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) + ); + checkInvariant(address(well)); + } + + /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal + /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` + /// before liquidity is removed by `user`. + function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); + + uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; + lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); + imbalanceBias = bound(imbalanceBias, 0, 10e18); + + // `user2` performs a swap to imbalance the pool by `imbalanceBias` + vm.prank(user2); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); + + // `user` has LP tokens and will perform a `removeLiquidity` call + vm.startPrank(user); + + uint256[] memory tokenAmountsOut = new uint256[](2); + tokenAmountsOut = well.getRemoveLiquidityOut(lpAmountBurned); + + Snapshot memory before; + RemoveLiquidityAction memory action; + + action.amounts = tokenAmountsOut; + action.lpAmountIn = lpAmountBurned; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeRemoveLiquidity(action); + well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); + afterRemoveLiquidity(before, action); + checkStableSwapInvariant(address(well)); + } +} diff --git a/test/Stable2/Well.ShiftStable.t.sol b/test/Stable2/Well.ShiftStable.t.sol new file mode 100644 index 00000000..0ea78807 --- /dev/null +++ b/test/Stable2/Well.ShiftStable.t.sol @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Balances, ConstantProduct2, IERC20, CurveStableSwap2} from "test/TestHelper.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellShiftStableTest is TestHelper { + event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); + + function setUp() public { + setupStableSwapWell(); + } + + /// @dev Shift excess token0 into token1. + function testFuzz_shift(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that `_user` has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + uint256 minAmountOut = well.getShiftOut(tokens[1]); + uint256[] memory calcReservesAfter = new uint256[](2); + calcReservesAfter[0] = tokens[0].balanceOf(address(well)); + calcReservesAfter[1] = tokens[1].balanceOf(address(well)) - minAmountOut; + + vm.expectEmit(true, true, true, true); + emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); + uint256 amtOut = well.shift(tokens[1], minAmountOut, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained token1 + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); + assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); + + // Reserves should now match balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + + // The difference has been sent to _user. + assertEq( + userBalanceAfterShift.tokens[1], + wellBalanceBeforeShift.tokens[1] - wellBalanceAfterShift.tokens[1], + "User should have correct token1 balance" + ); + assertEq( + userBalanceAfterShift.tokens[1], + userBalanceBeforeShift.tokens[1] + amtOut, + "User should have correct token1 balance" + ); + checkStableSwapInvariant(address(well)); + } + + /// @dev Shift excess token0 into token0 (just transfers the excess token0 to the user). + function testFuzz_shift_tokenOut(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that the user has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + uint256 minAmountOut = well.getShiftOut(tokens[0]); + uint256[] memory calcReservesAfter = new uint256[](2); + calcReservesAfter[0] = tokens[0].balanceOf(address(well)) - minAmountOut; + calcReservesAfter[1] = tokens[1].balanceOf(address(well)); + + vm.expectEmit(true, true, true, true); + emit Shift(calcReservesAfter, tokens[0], minAmountOut, _user); + // Shift the imbalanced token as the token out + well.shift(tokens[0], 0, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained token0 + assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); + assertEq( + userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" + ); + + // Reserves should now match balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + + assertEq( + userBalanceAfterShift.tokens[0], + userBalanceBeforeShift.tokens[0] + amount, + "User should have gained token 1" + ); + checkInvariant(address(well)); + } + + /// @dev Calling shift() on a balanced Well should do nothing. + function test_shift_balanced_pool() public prank(user) { + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + Balances memory userBalanceBeforeShift = getBalances(_user, well); + + // Verify that the user has no tokens + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); + + well.shift(tokens[1], 0, _user); + + uint256[] memory reserves = well.getReserves(); + Balances memory userBalanceAfterShift = getBalances(_user, well); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); + + // User should have gained neither token + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); + + // Reserves should equal balances + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); + checkInvariant(address(well)); + } + + function test_shift_fail_slippage(uint256 amount) public prank(user) { + amount = bound(amount, 1, 1000e18); + + // Transfer `amount` of token0 to the Well + tokens[0].transfer(address(well), amount); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); + + uint256 amountOut = well.getShiftOut(tokens[1]); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); + well.shift(tokens[1], type(uint256).max, user); + } +} diff --git a/test/Stable2/Well.SkimStableSwap.t.sol b/test/Stable2/Well.SkimStableSwap.t.sol new file mode 100644 index 00000000..f3965dd0 --- /dev/null +++ b/test/Stable2/Well.SkimStableSwap.t.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {TestHelper, Balances} from "test/TestHelper.sol"; + +contract WellSkimTest is TestHelper { + function setUp() public { + setupStableSwapWell(); + } + + function test_initialized() public { + // Well should have liquidity + Balances memory wellBalance = getBalances(address(well), well); + assertEq(wellBalance.tokens[0], 1000e18); + assertEq(wellBalance.tokens[1], 1000e18); + } + + function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { + vm.assume(amounts[0] <= 800e18); + vm.assume(amounts[1] <= 800e18); + + // Transfer from Test contract to Well + tokens[0].transfer(address(well), amounts[0]); + tokens[1].transfer(address(well), amounts[1]); + + Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); + // Verify that the Well has received the tokens + assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); + assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); + + // Get a user with a fresh address (no ERC20 tokens) + address _user = users.getNextUserAddress(); + uint256[] memory reserves = new uint256[](2); + + // Verify that the user has no tokens + Balances memory userBalanceBeforeSkim = getBalances(_user, well); + reserves[0] = userBalanceBeforeSkim.tokens[0]; + reserves[1] = userBalanceBeforeSkim.tokens[1]; + assertEq(reserves[0], 0); + assertEq(reserves[1], 0); + + well.skim(_user); + + Balances memory userBalanceAfterSkim = getBalances(_user, well); + Balances memory wellBalanceAfterSkim = getBalances(address(well), well); + + // Since only 1000e18 of each token was added as liquidity, the Well's reserve + // should be reset back to this. + assertEq(wellBalanceAfterSkim.tokens[0], 1000e18); + assertEq(wellBalanceAfterSkim.tokens[1], 1000e18); + + // The difference has been sent to _user. + assertEq(userBalanceAfterSkim.tokens[0], amounts[0]); + assertEq(userBalanceAfterSkim.tokens[1], amounts[1]); + } +} diff --git a/test/Stable2/Well.Stable2.AddLiquidity.t.sol b/test/Stable2/Well.Stable2.AddLiquidity.t.sol new file mode 100644 index 00000000..107405dc --- /dev/null +++ b/test/Stable2/Well.Stable2.AddLiquidity.t.sol @@ -0,0 +1,176 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {TestHelper, IERC20, Call, Balances} from "test/TestHelper.sol"; +import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; +import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; +import {Math} from "oz/utils/math/Math.sol"; + +contract WellStable2AddLiquidityTest is LiquidityHelper { + function setUp() public { + setupStableSwapWell(); + } + + /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent + /// tests will run correctly. + function test_liquidityInitialized() public view { + IERC20[] memory tokens = well.tokens(); + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + for (uint256 i; i < tokens.length; i++) { + assertEq(userBalance.tokens[i], initialLiquidity, "incorrect user token reserve"); + assertEq(wellBalance.tokens[i], initialLiquidity, "incorrect well token reserve"); + } + } + + /// @dev Adding liquidity in equal proportions should summate and be scaled + /// up by sqrt(ConstantProduct2.EXP_PRECISION) + function test_getAddLiquidityOut_equalAmounts() public view { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = well.getAddLiquidityOut(amounts); + assertEq(lpAmountOut, well.totalSupply(), "Incorrect AmountOut"); + } + + function test_getAddLiquidityOut_oneToken() public view { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10 * 1e18; + amounts[1] = 0; + + uint256 amountOut = well.getAddLiquidityOut(amounts); + assertEq(amountOut, 9_998_815_419_300_522_901, "incorrect amt out"); + } + + function test_addLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = well.getAddLiquidityOut(amounts); + + vm.expectRevert(abi.encodeWithSelector(SlippageOut.selector, lpAmountOut, lpAmountOut + 1)); + well.addLiquidity(amounts, lpAmountOut + 1, user, type(uint256).max); // lpAmountOut is 2000*1e27 + } + + function test_addLiquidity_revertIf_expired() public { + vm.expectRevert(Expired.selector); + well.addLiquidity(new uint256[](tokens.length), 0, user, block.timestamp - 1); + } + + function test_addLiquidity_balanced() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = 2000 * 1e18; + + vm.expectEmit(true, true, true, true); + emit AddLiquidity(amounts, lpAmountOut, user); + well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); + + Balances memory userBalance = getBalances(user, well); + Balances memory wellBalance = getBalances(address(well), well); + + assertEq(userBalance.lp, lpAmountOut); + + // Consumes all of user's tokens + assertEq(userBalance.tokens[0], 0, "incorrect token0 user amt"); + assertEq(userBalance.tokens[1], 0, "incorrect token1 user amt"); + + // Adds to the Well's reserves + assertEq(wellBalance.tokens[0], initialLiquidity + amounts[0], "incorrect token0 well amt"); + assertEq(wellBalance.tokens[1], initialLiquidity + amounts[1], "incorrect token1 well amt"); + checkInvariant(address(well)); + } + + function test_addLiquidity_oneSided() public prank(user) { + uint256[] memory amounts = new uint256[](2); + amounts[0] = 10 * 1e18; + amounts[1] = 0; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Adding and removing liquidity in sequence should return the Well to its previous state + function test_addAndRemoveLiquidity() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + for (uint256 i; i < tokens.length; i++) { + amounts[i] = 1000 * 1e18; + } + uint256 lpAmountOut = 2000 * 1e18; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); + afterAddLiquidity(before, action); + + Snapshot memory beforeRemove; + RemoveLiquidityAction memory actionRemove; + actionRemove.lpAmountIn = well.getAddLiquidityOut(amounts); + actionRemove.amounts = amounts; + actionRemove.recipient = user; + + (beforeRemove, actionRemove) = beforeRemoveLiquidity(actionRemove); + well.removeLiquidity(lpAmountOut, amounts, user, type(uint256).max); + afterRemoveLiquidity(beforeRemove, actionRemove); + checkInvariant(address(well)); + } + + /// @dev Adding zero liquidity emits empty event but doesn't change reserves + function test_addLiquidity_zeroChange() public prank(user) { + uint256[] memory amounts = new uint256[](tokens.length); + uint256 liquidity = 0; + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = liquidity; + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, liquidity, user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } + + /// @dev Two-token fuzz test adding liquidity in any ratio + function testFuzz_addLiquidity(uint256 x, uint256 y) public prank(user) { + // amounts to add as liquidity + uint256[] memory amounts = new uint256[](2); + amounts[0] = bound(x, 0, type(uint104).max); + amounts[1] = bound(y, 0, type(uint104).max); + mintTokens(user, amounts); + + Snapshot memory before; + AddLiquidityAction memory action; + action.amounts = amounts; + action.lpAmountOut = well.getAddLiquidityOut(amounts); + action.recipient = user; + action.fees = new uint256[](2); + + (before, action) = beforeAddLiquidity(action); + well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); + afterAddLiquidity(before, action); + checkInvariant(address(well)); + } +} diff --git a/test/Stable2/Well.SwapFromStableSwap.t.sol b/test/Stable2/Well.SwapFromStableSwap.t.sol new file mode 100644 index 00000000..fd7f814d --- /dev/null +++ b/test/Stable2/Well.SwapFromStableSwap.t.sol @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {IERC20, Balances, Call, MockToken, Well, console} from "test/TestHelper.sol"; +import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol"; +import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; +import {IWellFunction} from "src/interfaces/IWellFunction.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellSwapFromStableSwapTest is SwapHelper { + function setUp() public { + setupStableSwapWell(); + } + + function test_getSwapOut() public view { + uint256 amountIn = 10 * 1e18; + uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); + + assertEq(amountOut, 9_995_239_930_393_036_263); // ~0.05% slippage + } + + function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint256 amountIn, uint256 i) public prank(user) { + // Swap token `i` -> all other tokens + vm.assume(i < tokens.length); + + // Find an input amount that produces an output amount higher than what the Well has. + // When the Well is deployed it has zero reserves, so any nonzero value should revert. + amountIn = bound(amountIn, 1, type(uint128).max); + + // Deploy a new Well with a poorly engineered pricing function. + // Its `getBalance` function can return an amount greater than the Well holds. + IWellFunction badFunction = new MockFunctionBad(); + Well badWell = encodeAndBoreWell( + address(aquifer), wellImplementation, tokens, Call(address(badFunction), ""), pumps, bytes32(0) + ); + + // Check assumption that reserves are empty + Balances memory wellBalances = getBalances(address(badWell), badWell); + assertEq(wellBalances.tokens[0], 0, "bad assumption: wellBalances.tokens[0] != 0"); + assertEq(wellBalances.tokens[1], 0, "bad assumption: wellBalances.tokens[1] != 0"); + + for (uint256 j = 0; j < tokens.length; ++j) { + if (j != i) { + vm.expectRevert(); // underflow + badWell.getSwapOut(tokens[i], tokens[j], amountIn); + } + } + } + + /// @dev Swaps should always revert if `fromToken` = `toToken`. + function test_swapFrom_revertIf_sameToken() public prank(user) { + vm.expectRevert(IWellErrors.InvalidTokens.selector); + well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); + } + + /// @dev Slippage revert if minAmountOut is too high + function test_swapFrom_revertIf_minAmountOutTooHigh() public prank(user) { + uint256 amountIn = 10 * 1e18; + uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); + uint256 minAmountOut = amountOut + 1e18; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); + well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); + } + + function test_swapFrom_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); + } + + function testFuzz_swapFrom(uint256 amountIn) public prank(user) { + amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); + + (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); + act.wellSends = well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); + afterSwapFrom(bef, act); + checkStableSwapInvariant(address(well)); + } + + function testFuzz_swapAndRemoveAllLiq(uint256 amountIn) public { + amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); + vm.prank(user); + well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); + + vm.prank(address(this)); + well.removeLiquidityImbalanced( + type(uint256).max, IWell(address(well)).getReserves(), address(this), type(uint256).max + ); + assertEq(IERC20(address(well)).totalSupply(), 0); + } + + /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result + function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { + vm.assume(token0AmtIn < tokens[0].balanceOf(user)); + uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); + uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); + assertEq(token0Out, token0AmtIn); + checkInvariant(address(well)); + } +} diff --git a/test/Stable2/Well.SwapToStableSwap.t.sol b/test/Stable2/Well.SwapToStableSwap.t.sol new file mode 100644 index 00000000..89c0750f --- /dev/null +++ b/test/Stable2/Well.SwapToStableSwap.t.sol @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.17; + +import {IERC20, Balances, Call, MockToken, Well} from "test/TestHelper.sol"; +import {SwapHelper, SwapAction} from "test/SwapHelper.sol"; +import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; +import {IWellFunction} from "src/interfaces/IWellFunction.sol"; +import {IWell} from "src/interfaces/IWell.sol"; +import {IWellErrors} from "src/interfaces/IWellErrors.sol"; + +contract WellSwapToStableSwapTest is SwapHelper { + function setUp() public { + setupStableSwapWell(); + } + + function test_getSwapIn() public { + uint256 amountOut = 100 * 1e18; + uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); + assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage + } + + function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { + IERC20[] memory _tokens = well.tokens(); + Balances memory wellBalances = getBalances(address(well), well); + vm.assume(i < _tokens.length); + + // Swap token `i` -> all other tokens + for (uint256 j; j < _tokens.length; ++j) { + if (j != i) { + // Request to buy more of {_tokens[j]} than the Well has. + // There is no input amount that could complete this Swap. + uint256 amountOut = wellBalances.tokens[j] + 1; + vm.expectRevert(); // underflow + well.getSwapIn(_tokens[i], _tokens[j], amountOut); + } + } + } + + /// @dev Swaps should always revert if `fromToken` = `toToken`. + function test_swapTo_revertIf_sameToken() public prank(user) { + vm.expectRevert(IWellErrors.InvalidTokens.selector); + well.swapTo(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); + } + + /// @dev Slippage revert occurs if maxAmountIn is too low + function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { + uint256 amountOut = 100 * 1e18; + uint256 amountIn = 100_482_889_020_651_556_292; + uint256 maxAmountIn = (amountIn * 99) / 100; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + } + + /// @dev Note: this covers the case where there is a fee as well + function test_swapFromFeeOnTransferNoFee_revertIf_expired() public { + vm.expectRevert(IWellErrors.Expired.selector); + well.swapTo(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); + } + + /// @dev tests assume 2 tokens in future we can extend for multiple tokens + function testFuzz_swapTo(uint256 amountOut) public prank(user) { + // User has 1000 of each token + // Given current liquidity, swapping 1000 of one token gives 500 of the other + uint256 maxAmountIn = 1000 * 1e18; + amountOut = bound(amountOut, 0, 500 * 1e18); + + Balances memory userBalancesBefore = getBalances(user, well); + Balances memory wellBalancesBefore = getBalances(address(well), well); + + // Decrease reserve of token 1 by `amountOut` which is paid to user + uint256[] memory calcBalances = new uint256[](wellBalancesBefore.tokens.length); + calcBalances[0] = wellBalancesBefore.tokens[0]; + calcBalances[1] = wellBalancesBefore.tokens[1] - amountOut; + + uint256 calcAmountIn = IWellFunction(wellFunction.target).calcReserve( + calcBalances, + 0, // j + wellBalancesBefore.lpSupply, + wellFunction.data + ) - wellBalancesBefore.tokens[0]; + + vm.expectEmit(true, true, true, true); + emit Swap(tokens[0], tokens[1], calcAmountIn, amountOut, user); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); + + Balances memory userBalancesAfter = getBalances(user, well); + Balances memory wellBalancesAfter = getBalances(address(well), well); + + assertEq( + userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], calcAmountIn, "Incorrect token0 user balance" + ); + assertEq(userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], amountOut, "Incorrect token1 user balance"); + assertEq( + wellBalancesAfter.tokens[0], wellBalancesBefore.tokens[0] + calcAmountIn, "Incorrect token0 well reserve" + ); + assertEq(wellBalancesAfter.tokens[1], wellBalancesBefore.tokens[1] - amountOut, "Incorrect token1 well reserve"); + checkStableSwapInvariant(address(well)); + } + + /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result + function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { + // assume amtOut is lower due to slippage + vm.assume(token0AmtOut < 500e18); + uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); + uint256 token0In = well.swapTo(tokens[1], tokens[0], 1000e18, token1In, user, type(uint256).max); + assertEq(token0In, token0AmtOut); + checkInvariant(address(well)); + } +} diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 558a4053..282045d3 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -78,19 +78,11 @@ abstract contract TestHelper is Test, WellDeployer { setupWell(n, deployWellFunction(), _pumps); } - function setupWell( - uint256 n, - Call memory _wellFunction, - Call[] memory _pumps - ) internal { + function setupWell(uint256 n, Call memory _wellFunction, Call[] memory _pumps) internal { setupWell(_wellFunction, _pumps, deployMockTokens(n)); } - function setupWell( - Call memory _wellFunction, - Call[] memory _pumps, - IERC20[] memory _tokens - ) internal { + function setupWell(Call memory _wellFunction, Call[] memory _pumps, IERC20[] memory _tokens) internal { tokens = _tokens; wellFunction = _wellFunction; for (uint256 i; i < _pumps.length; i++) { @@ -101,14 +93,7 @@ abstract contract TestHelper is Test, WellDeployer { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - well = encodeAndBoreWell( - address(aquifer), - wellImplementation, - tokens, - _wellFunction, - _pumps, - bytes32(0) - ); + well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); // Mint mock tokens to user mintTokens(user, initialLiquidity); @@ -125,10 +110,7 @@ abstract contract TestHelper is Test, WellDeployer { } function setupWellWithFeeOnTransfer(uint256 n) internal { - Call memory _wellFunction = Call( - address(new ConstantProduct2()), - new bytes(0) - ); + Call memory _wellFunction = Call(address(new ConstantProduct2()), new bytes(0)); Call[] memory _pumps = new Call[](2); _pumps[0].target = address(new MockPump()); _pumps[0].data = new bytes(1); @@ -145,25 +127,19 @@ abstract contract TestHelper is Test, WellDeployer { user2 = _user[1]; } - function setupStableSwapWell(uint256 a) internal { - setupStableSwapWell(a, deployPumps(1), deployMockTokens(2)); + function setupStableSwapWell() internal { + setupStableSwapWell(deployPumps(1), deployMockTokens(2)); } - function setupStableSwapWell( - uint256 a, - Call[] memory _pumps, - IERC20[] memory _tokens - ) internal { + function setupStableSwapWell(Call[] memory _pumps, IERC20[] memory _tokens) internal { // encode wellFunction Data - bytes memory wellFunctionData = abi.encode(a, _tokens[0], _tokens[1]); - Call memory _wellFunction = Call( - address(new CurveStableSwap2()), - wellFunctionData - ); + bytes memory wellFunctionData = + abi.encode(MockToken(address(_tokens[0])).decimals(), MockToken(address(_tokens[1])).decimals()); + Call memory _wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionData); tokens = _tokens; wellFunction = _wellFunction; vm.label(address(wellFunction.target), "CurveStableSwap2 WF"); - for (uint i = 0; i < _pumps.length; i++) { + for (uint256 i = 0; i < _pumps.length; i++) { pumps.push(_pumps[i]); } @@ -171,14 +147,7 @@ abstract contract TestHelper is Test, WellDeployer { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - well = encodeAndBoreWell( - address(aquifer), - wellImplementation, - tokens, - _wellFunction, - _pumps, - bytes32(0) - ); + well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); vm.label(address(well), "CurveStableSwap2Well"); // Mint mock tokens to user @@ -198,9 +167,7 @@ abstract contract TestHelper is Test, WellDeployer { //////////// Test Tokens //////////// /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokens( - uint256 n - ) internal returns (IERC20[] memory _tokens) { + function deployMockTokens(uint256 n) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockToken(i); @@ -208,34 +175,27 @@ abstract contract TestHelper is Test, WellDeployer { } function deployMockToken(uint256 i) internal returns (IERC20) { - return - IERC20( - new MockToken( - string.concat("Token ", i.toString()), // name - string.concat("TOKEN", i.toString()), // symbol - 18 // decimals - ) - ); - } - - function deployMockTokenWithDecimals( - uint256 i, - uint8 decimals - ) internal returns (IERC20) { - return - IERC20( - new MockToken( - string.concat("Token ", i.toString()), // name - string.concat("TOKEN", i.toString()), // symbol - decimals // decimals - ) - ); + return IERC20( + new MockToken( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + 18 // decimals + ) + ); + } + + function deployMockTokenWithDecimals(uint256 i, uint8 decimals) internal returns (IERC20) { + return IERC20( + new MockToken( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + decimals // decimals + ) + ); } /// @dev deploy `n` mock ERC20 tokens and sort by address - function deployMockTokensFeeOnTransfer( - uint256 n - ) internal returns (IERC20[] memory _tokens) { + function deployMockTokensFeeOnTransfer(uint256 n) internal returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; i++) { _tokens[i] = deployMockTokenFeeOnTransfer(i); @@ -243,14 +203,13 @@ abstract contract TestHelper is Test, WellDeployer { } function deployMockTokenFeeOnTransfer(uint256 i) internal returns (IERC20) { - return - IERC20( - new MockTokenFeeOnTransfer( - string.concat("Token ", i.toString()), // name - string.concat("TOKEN", i.toString()), // symbol - 18 // decimals - ) - ); + return IERC20( + new MockTokenFeeOnTransfer( + string.concat("Token ", i.toString()), // name + string.concat("TOKEN", i.toString()), // symbol + 18 // decimals + ) + ); } /// @dev mint mock tokens to each recipient @@ -268,19 +227,14 @@ abstract contract TestHelper is Test, WellDeployer { } /// @dev approve `spender` to use `owner` tokens - function approveMaxTokens( - address owner, - address spender - ) internal prank(owner) { + function approveMaxTokens(address owner, address spender) internal prank(owner) { for (uint256 i; i < tokens.length; i++) { tokens[i].approve(spender, type(uint256).max); } } /// @dev gets the first `n` mock tokens - function getTokens( - uint256 n - ) internal view returns (IERC20[] memory _tokens) { + function getTokens(uint256 n) internal view returns (IERC20[] memory _tokens) { _tokens = new IERC20[](n); for (uint256 i; i < n; ++i) { _tokens[i] = tokens[i]; @@ -294,9 +248,7 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = new bytes(0); } - function deployWellFunction( - address _target - ) internal pure returns (Call memory _wellFunction) { + function deployWellFunction(address _target) internal pure returns (Call memory _wellFunction) { _wellFunction.target = _target; _wellFunction.data = new bytes(0); } @@ -309,7 +261,7 @@ abstract contract TestHelper is Test, WellDeployer { _wellFunction.data = _data; } - function deployPumps(uint n) internal returns (Call[] memory _pumps) { + function deployPumps(uint256 n) internal returns (Call[] memory _pumps) { _pumps = new Call[](n); for (uint256 i; i < n; i++) { _pumps[i].target = address(new MockPump()); @@ -322,19 +274,13 @@ abstract contract TestHelper is Test, WellDeployer { return address(new Well()); } - function mintAndAddLiquidity( - address to, - uint256[] memory amounts - ) internal { + function mintAndAddLiquidity(address to, uint256[] memory amounts) internal { mintTokens(user, amounts); well.addLiquidity(amounts, 0, to, type(uint256).max); } /// @dev add the same `amount` of liquidity for all underlying tokens - function addLiquidityEqualAmount( - address from, - uint256 amount - ) internal prank(from) { + function addLiquidityEqualAmount(address from, uint256 amount) internal prank(from) { uint256[] memory amounts = new uint256[](tokens.length); for (uint256 i; i < tokens.length; i++) { amounts[i] = amount; @@ -346,10 +292,7 @@ abstract contract TestHelper is Test, WellDeployer { /// @dev get `account` balance of each token, lp token, total lp token supply /// @dev uses global tokens but not global well - function getBalances( - address account, - Well _well - ) internal view returns (Balances memory balances) { + function getBalances(address account, Well _well) internal view returns (Balances memory balances) { uint256[] memory tokenBalances = new uint256[](tokens.length); for (uint256 i; i < tokenBalances.length; ++i) { tokenBalances[i] = tokens[i].balanceOf(account); @@ -385,11 +328,7 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "IERC20[] mismatch"); } - function assertEq( - IERC20[] memory a, - IERC20[] memory b, - string memory err - ) internal { + function assertEq(IERC20[] memory a, IERC20[] memory b, string memory err) internal { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload @@ -400,11 +339,7 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "Call mismatch"); } - function assertEq( - Call memory a, - Call memory b, - string memory err - ) internal { + function assertEq(Call memory a, Call memory b, string memory err) internal { assertEq(a.target, b.target, err); assertEq(a.data, b.data, err); } @@ -413,31 +348,18 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "Call[] mismatch"); } - function assertEq( - Call[] memory a, - Call[] memory b, - string memory err - ) internal { + function assertEq(Call[] memory a, Call[] memory b, string memory err) internal { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload } } - function assertApproxEqRelN( - uint256 a, - uint256 b, - uint256 precision - ) internal virtual { + function assertApproxEqRelN(uint256 a, uint256 b, uint256 precision) internal virtual { assertApproxEqRelN(a, b, 1, precision); } - function assertApproxLeRelN( - uint256 a, - uint256 b, - uint256 precision, - uint256 absoluteError - ) internal { + function assertApproxLeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -458,12 +380,7 @@ abstract contract TestHelper is Test, WellDeployer { } } - function assertApproxGeRelN( - uint256 a, - uint256 b, - uint256 precision, - uint256 absoluteError - ) internal { + function assertApproxGeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -498,25 +415,13 @@ abstract contract TestHelper is Test, WellDeployer { emit log("Error: a ~= b not satisfied [uint]"); emit log_named_uint(" Expected", b); emit log_named_uint(" Actual", a); - emit log_named_decimal_uint( - " Max % Delta", - maxPercentDelta, - precision - ); - emit log_named_decimal_uint( - " % Delta", - percentDelta, - precision - ); + emit log_named_decimal_uint(" Max % Delta", maxPercentDelta, precision); + emit log_named_decimal_uint(" % Delta", percentDelta, precision); fail(); } } - function percentDeltaN( - uint256 a, - uint256 b, - uint256 precision - ) internal pure returns (uint256) { + function percentDeltaN(uint256 a, uint256 b, uint256 precision) internal pure returns (uint256) { uint256 absDelta = stdMath.delta(a, b); return (absDelta * (10 ** precision)) / b; @@ -533,30 +438,22 @@ abstract contract TestHelper is Test, WellDeployer { Call memory _wellFunction = IWell(_well).wellFunction(); assertLe( IERC20(_well).totalSupply(), - IWellFunction(_wellFunction.target).calcLpTokenSupply( - _reserves, - _wellFunction.data - ), + IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), "totalSupply() is greater than calcLpTokenSupply()" ); } function checkStableSwapInvariant(address _well) internal { - uint[] memory _reserves = IWell(_well).getReserves(); + uint256[] memory _reserves = IWell(_well).getReserves(); Call memory _wellFunction = IWell(_well).wellFunction(); assertApproxEqAbs( IERC20(_well).totalSupply(), - IWellFunction(_wellFunction.target).calcLpTokenSupply( - _reserves, - _wellFunction.data - ), + IWellFunction(_wellFunction.target).calcLpTokenSupply(_reserves, _wellFunction.data), 2 ); } - function getPrecisionForReserves( - uint256[] memory reserves - ) internal pure returns (uint256 precision) { + function getPrecisionForReserves(uint256[] memory reserves) internal pure returns (uint256 precision) { precision = type(uint256).max; for (uint256 i; i < reserves.length; ++i) { uint256 logReserve = reserves[i].log10(); @@ -564,9 +461,7 @@ abstract contract TestHelper is Test, WellDeployer { } } - function uint2ToUintN( - uint256[2] memory input - ) internal pure returns (uint256[] memory out) { + function uint2ToUintN(uint256[2] memory input) internal pure returns (uint256[] memory out) { out = new uint256[](input.length); for (uint256 i; i < input.length; i++) { out[i] = input[i]; diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol index 9fbae568..44707976 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol @@ -13,12 +13,12 @@ contract CurveStableSwap2LiquidityTest is TestHelper { //////////// SETUP //////////// function setUp() public { - _f = new CurveStableSwap2(); - IERC20[] memory _token = deployMockTokens(2); - data = abi.encode(10, address(_token[0]), address(_token[1])); + _f = new CurveStableSwap2(address(1)); + deployMockTokens(2); + data = abi.encode(18, 18); } - function test_calcReserveAtRatioLiquidity_equal_equal() public { + function test_calcReserveAtRatioLiquidity_equal_equal() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 100; reserves[1] = 100; @@ -26,24 +26,14 @@ contract CurveStableSwap2LiquidityTest is TestHelper { ratios[0] = 1; ratios[1] = 1; - uint256 reserve0 = _f.calcReserveAtRatioLiquidity( - reserves, - 0, - ratios, - data - ); - uint256 reserve1 = _f.calcReserveAtRatioLiquidity( - reserves, - 1, - ratios, - data - ); + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); assertEq(reserve0, 100); assertEq(reserve1, 100); } - function test_calcReserveAtRatioLiquidity_equal_diff() public { + function test_calcReserveAtRatioLiquidity_equal_diff() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 50; reserves[1] = 100; @@ -51,24 +41,14 @@ contract CurveStableSwap2LiquidityTest is TestHelper { ratios[0] = 1; ratios[1] = 1; - uint256 reserve0 = _f.calcReserveAtRatioLiquidity( - reserves, - 0, - ratios, - data - ); - uint256 reserve1 = _f.calcReserveAtRatioLiquidity( - reserves, - 1, - ratios, - data - ); + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); assertEq(reserve0, 100); assertEq(reserve1, 50); } - function test_calcReserveAtRatioLiquidity_diff_equal() public { + function test_calcReserveAtRatioLiquidity_diff_equal() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 100; reserves[1] = 100; @@ -76,24 +56,14 @@ contract CurveStableSwap2LiquidityTest is TestHelper { ratios[0] = 2; ratios[1] = 1; - uint256 reserve0 = _f.calcReserveAtRatioLiquidity( - reserves, - 0, - ratios, - data - ); - uint256 reserve1 = _f.calcReserveAtRatioLiquidity( - reserves, - 1, - ratios, - data - ); + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); assertEq(reserve0, 200); assertEq(reserve1, 50); } - function test_calcReserveAtRatioLiquidity_diff_diff() public { + function test_calcReserveAtRatioLiquidity_diff_diff() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 50; reserves[1] = 100; @@ -101,27 +71,14 @@ contract CurveStableSwap2LiquidityTest is TestHelper { ratios[0] = 2; ratios[1] = 1; - uint256 reserve0 = _f.calcReserveAtRatioLiquidity( - reserves, - 0, - ratios, - data - ); - uint256 reserve1 = _f.calcReserveAtRatioLiquidity( - reserves, - 1, - ratios, - data - ); + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); assertEq(reserve0, 200); assertEq(reserve1, 25); } - function test_calcReserveAtRatioLiquidity_fuzz( - uint256[2] memory reserves, - uint256[2] memory ratios - ) public { + function test_calcReserveAtRatioLiquidity_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { for (uint256 i; i < 2; ++i) { // Upper bound is limited by stableSwap, // due to the stableswap reserves being extremely far apart. @@ -129,63 +86,34 @@ contract CurveStableSwap2LiquidityTest is TestHelper { ratios[i] = bound(ratios[i], 1e6, 1e18); } - uint256 lpTokenSupply = _f.calcLpTokenSupply( - uint2ToUintN(reserves), - data - ); + uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); console.log(lpTokenSupply); uint256[] memory reservesOut = new uint256[](2); for (uint256 i; i < 2; ++i) { - reservesOut[i] = _f.calcReserveAtRatioLiquidity( - uint2ToUintN(reserves), - i, - uint2ToUintN(ratios), - data - ); + reservesOut[i] = _f.calcReserveAtRatioLiquidity(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); } // Precision is set to the minimum number of digits of the reserves out. - uint256 precision = numDigits(reservesOut[0]) > - numDigits(reservesOut[1]) + uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) ? numDigits(reservesOut[1]) : numDigits(reservesOut[0]); // Check ratio of each `reserveOut` to `reserve` with the ratio of `ratios`. // If inequality doesn't hold, then reserves[1] will be zero if (ratios[0] * reserves[1] >= ratios[1]) { - assertApproxEqRelN( - reservesOut[0] * ratios[1], - ratios[0] * reserves[1], - 1, - precision - ); + assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reserves[1], 1, precision); } else { // Because `roundedDiv` is used. It could round up to 1. - assertApproxEqAbs( - reservesOut[0], - 0, - 1, - "reservesOut[0] should be zero" - ); + assertApproxEqAbs(reservesOut[0], 0, 1, "reservesOut[0] should be zero"); } // If inequality doesn't hold, then reserves[1] will be zero if (reserves[0] * ratios[1] >= ratios[0]) { - assertApproxEqRelN( - reserves[0] * ratios[1], - ratios[0] * reservesOut[1], - 1, - precision - ); + assertApproxEqRelN(reserves[0] * ratios[1], ratios[0] * reservesOut[1], 1, precision); } else { // Because `roundedDiv` is used. It could round up to 1. - assertApproxEqAbs( - reservesOut[1], - 0, - 1, - "reservesOut[1] should be zero" - ); + assertApproxEqAbs(reservesOut[1], 0, 1, "reservesOut[1] should be zero"); } } diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol index 16f54d0c..27ca82d0 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol @@ -13,7 +13,7 @@ contract BeanstalkStableSwapSwapTest is TestHelper { //////////// SETUP //////////// function setUp() public { - _f = new CurveStableSwap2(); + _f = new CurveStableSwap2(address(1)); IERC20[] memory _token = deployMockTokens(2); data = abi.encode(10, address(_token[0]), address(_token[1])); } diff --git a/test/functions/StableSwap.t.sol b/test/functions/StableSwap.t.sol index 3e9474eb..195dfde8 100644 --- a/test/functions/StableSwap.t.sol +++ b/test/functions/StableSwap.t.sol @@ -12,9 +12,9 @@ contract CurveStableSwapTest is WellFunctionHelper { * D (lpTokenSupply) should be the summation of * the reserves, assuming they are equal. */ - uint STATE_A_B0 = 10 * 1e18; - uint STATE_A_B1 = 10 * 1e18; - uint STATE_A_LP = 20 * 1e18; + uint256 STATE_A_B0 = 10 * 1e18; + uint256 STATE_A_B1 = 10 * 1e18; + uint256 STATE_A_LP = 20 * 1e18; /** * State B: Different decimals @@ -27,38 +27,32 @@ contract CurveStableSwapTest is WellFunctionHelper { * assuming they are equal. * */ - uint STATE_B_B0 = 10 * 1e18; - uint STATE_B_B1 = 20 * 1e18; - uint STATE_B_LP = 29_911_483_643_966_454_823; // ~29e18 + uint256 STATE_B_B0 = 10 * 1e18; + uint256 STATE_B_B1 = 20 * 1e18; + uint256 STATE_B_LP = 29_911_483_643_966_454_823; // ~29e18 /// State C: Similar decimals - uint STATE_C_B0 = 20 * 1e18; - uint STATE_C_LP = 25 * 1e24; - uint STATE_C_B1 = 2_221_929_790_566_403_172_822_276_028; // 2.221e19 + uint256 STATE_C_B0 = 20 * 1e18; + uint256 STATE_C_LP = 25 * 1e24; + uint256 STATE_C_B1 = 2_221_929_790_566_403_172_822_276_028; // 2.221e19 /// @dev See {calcLpTokenSupply}. - uint MAX_RESERVE = 1e32; + uint256 MAX_RESERVE = 1e32; //////////// SETUP //////////// function setUp() public { IERC20[] memory _tokens = deployMockTokens(2); tokens = _tokens; - _function = IMultiFlowPumpWellFunction(new CurveStableSwap2()); + _function = IMultiFlowPumpWellFunction(new CurveStableSwap2(address(1))); // encode well data with: // A parameter of 10, // address of token 0 and 1. - _data = abi.encode( - CurveStableSwap2.WellFunctionData( - 10, - address(_tokens[0]), - address(_tokens[1]) - ) - ); + _data = abi.encode(18, 18); } - function test_metadata() public { + function test_metadata() public view { assertEq(_function.name(), "StableSwap"); assertEq(_function.symbol(), "SS2"); } @@ -71,8 +65,8 @@ contract CurveStableSwapTest is WellFunctionHelper { } /// @dev calcLpTokenSupply: same decimals, manual calc for 2 equal reserves - function test_calcLpTokenSupply_sameDecimals() public { - uint[] memory reserves = new uint[](2); + function test_calcLpTokenSupply_sameDecimals() public view { + uint256[] memory reserves = new uint256[](2); reserves[0] = STATE_A_B0; reserves[1] = STATE_A_B1; assertEq( @@ -82,8 +76,8 @@ contract CurveStableSwapTest is WellFunctionHelper { } /// @dev calcLpTokenSupply: diff decimals - function test_calcLpTokenSupply_diffDecimals() public { - uint[] memory reserves = new uint[](2); + function test_calcLpTokenSupply_diffDecimals() public view { + uint256[] memory reserves = new uint256[](2); reserves[0] = STATE_B_B0; // ex. 1 WETH reserves[1] = STATE_B_B1; // ex. 1250 BEAN assertEq(_function.calcLpTokenSupply(reserves, _data), STATE_B_LP); @@ -93,25 +87,19 @@ contract CurveStableSwapTest is WellFunctionHelper { /// @dev calcReserve: same decimals, both positions /// Matches example in {testLpTokenSupplySameDecimals}. - function test_calcReserve_sameDecimals() public { - uint[] memory reserves = new uint[](2); + function test_calcReserve_sameDecimals() public view { + uint256[] memory reserves = new uint256[](2); /// STATE A // find reserves[0] reserves[0] = 0; reserves[1] = STATE_A_B1; - assertEq( - _function.calcReserve(reserves, 0, STATE_A_LP, _data), - STATE_A_B0 - ); + assertEq(_function.calcReserve(reserves, 0, STATE_A_LP, _data), STATE_A_B0); // find reserves[1] reserves[0] = STATE_A_B0; reserves[1] = 0; - assertEq( - _function.calcReserve(reserves, 1, STATE_A_LP, _data), - STATE_A_B1 - ); + assertEq(_function.calcReserve(reserves, 1, STATE_A_LP, _data), STATE_A_B1); /// STATE C // find reserves[1] @@ -125,8 +113,8 @@ contract CurveStableSwapTest is WellFunctionHelper { /// @dev calcReserve: diff decimals, both positions /// Matches example in {testLpTokenSupplyDiffDecimals}. - function test_calcReserve_diffDecimals() public { - uint[] memory reserves = new uint[](2); + function test_calcReserve_diffDecimals() public view { + uint256[] memory reserves = new uint256[](2); /// STATE B // find reserves[0] @@ -149,52 +137,31 @@ contract CurveStableSwapTest is WellFunctionHelper { //////////// LP TOKEN SUPPLY //////////// /// @dev invariant: reserves -> lpTokenSupply -> reserves should match - function testFuzz_calcLpTokenSupply(uint[2] memory _reserves) public { - uint[] memory reserves = new uint[](2); + function testFuzz_calcLpTokenSupply(uint256[2] memory _reserves) public view { + uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); reserves[1] = bound(_reserves[1], 10e18, MAX_RESERVE); - uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); - uint[] memory underlying = _function.calcLPTokenUnderlying( - lpTokenSupply, - reserves, - lpTokenSupply, - _data - ); - for (uint i = 0; i < reserves.length; ++i) { + uint256 lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); + uint256[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, _data); + for (uint256 i = 0; i < reserves.length; ++i) { assertEq(reserves[i], underlying[i], "reserves mismatch"); } } //////////// FUZZ //////////// - function testFuzz_stableSwap(uint x, uint y, uint a) public { - uint[] memory reserves = new uint[](2); + function testFuzz_stableSwap(uint256 x, uint256 y, uint256 a) public { + uint256[] memory reserves = new uint256[](2); reserves[0] = bound(x, 10e18, MAX_RESERVE); reserves[1] = bound(y, 10e18, MAX_RESERVE); - a = bound(a, 1, 1000000); - - _data = abi.encode( - CurveStableSwap2.WellFunctionData( - a, - address(tokens[0]), - address(tokens[1]) - ) - ); + a = bound(a, 1, 1_000_000); - uint lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); - uint reserve0 = _function.calcReserve( - reserves, - 0, - lpTokenSupply, - _data - ); - uint reserve1 = _function.calcReserve( - reserves, - 1, - lpTokenSupply, - _data - ); + _data = abi.encode(18, 18); + + uint256 lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); + uint256 reserve0 = _function.calcReserve(reserves, 0, lpTokenSupply, _data); + uint256 reserve1 = _function.calcReserve(reserves, 1, lpTokenSupply, _data); if (reserves[0] < 1e12) { assertApproxEqAbs(reserve0, reserves[0], 1); @@ -210,29 +177,20 @@ contract CurveStableSwapTest is WellFunctionHelper { ///////// CALC RATE /////// - function test_calcRateStable() public { + function test_calcRateStable() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 1e18; reserves[1] = 1e18; - assertEq(_function.calcRate(reserves, 0, 1, _data), 0.99767728e18); - assertEq(_function.calcRate(reserves, 1, 0, _data), 1.002328128e18); + assertEq(_function.calcRate(reserves, 0, 1, _data), 1e6); + assertEq(_function.calcRate(reserves, 1, 0, _data), 1e6); } function test_calcRateStable6Decimals() public { - _data = abi.encode( - CurveStableSwap2.WellFunctionData( - 10, - address(tokens[0]), - address(deployMockTokenWithDecimals(1, 6)) - ) - ); + _data = abi.encode(18, 6); uint256[] memory reserves = new uint256[](2); reserves[0] = 100e18; reserves[1] = 100e6; assertEq(_function.calcRate(reserves, 1, 0, _data), 1e6); - assertEq( - _function.calcRate(reserves, 0, 1, _data), - 0.9999952381178706e30 - ); + assertEq(_function.calcRate(reserves, 0, 1, _data), 1e6); } } diff --git a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol index 70a6fb59..4dda91bc 100644 --- a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol @@ -8,7 +8,7 @@ import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} fr contract WellAddLiquidityStableSwapTest is LiquidityHelper { function setUp() public { - setupStableSwapWell(10); + setupStableSwapWell(); } /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent diff --git a/test/stableSwap/Well.BoreStableSwap.t.sol b/test/stableSwap/Well.BoreStableSwap.t.sol index ff229209..42b0179a 100644 --- a/test/stableSwap/Well.BoreStableSwap.t.sol +++ b/test/stableSwap/Well.BoreStableSwap.t.sol @@ -9,7 +9,7 @@ contract WellBoreStableSwapTest is TestHelper { /// @dev Bore a 2-token Well with CurveStableSwap2 & several pumps. function setUp() public { // setup a StableSwap Well with an A parameter of 10. - setupStableSwapWell(10); + setupStableSwapWell(); // Well.sol doesn't use wellData, so it should always return empty bytes wellData = new bytes(0); } @@ -74,15 +74,10 @@ contract WellBoreStableSwapTest is TestHelper { /// @dev Fuzz different combinations of Well configuration data and check /// that the Aquifer deploys everything correctly. - function testFuzz_bore( - uint numberOfPumps, - bytes[4] memory pumpData, - uint nTokens, - uint a - ) public { + function testFuzz_bore(uint256 numberOfPumps, bytes[4] memory pumpData, uint256 nTokens, uint256 a) public { // Constraints numberOfPumps = bound(numberOfPumps, 0, 4); - for (uint i = 0; i < numberOfPumps; i++) { + for (uint256 i = 0; i < numberOfPumps; i++) { vm.assume(pumpData[i].length <= 4 * 32); } nTokens = bound(nTokens, 2, tokens.length); @@ -90,45 +85,31 @@ contract WellBoreStableSwapTest is TestHelper { vm.assume(a > 0); // Get the first `nTokens` mock tokens IERC20[] memory wellTokens = getTokens(nTokens); - bytes memory wellFunctionBytes = abi.encode( - a, - address(wellTokens[0]), - address(wellTokens[1]) - ); + bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); // Deploy a Well Function - wellFunction = Call(address(new CurveStableSwap2()), wellFunctionBytes); + wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionBytes); // Etch the MockPump at each `target` Call[] memory pumps = new Call[](numberOfPumps); - for (uint i = 0; i < numberOfPumps; i++) { + for (uint256 i = 0; i < numberOfPumps; i++) { pumps[i].target = address(new MockPump()); pumps[i].data = pumpData[i]; } // Deploy the Well - Well _well = encodeAndBoreWell( - address(aquifer), - wellImplementation, - wellTokens, - wellFunction, - pumps, - bytes32(0) - ); + Well _well = + encodeAndBoreWell(address(aquifer), wellImplementation, wellTokens, wellFunction, pumps, bytes32(0)); // Check Pumps - assertEq( - _well.numberOfPumps(), - numberOfPumps, - "number of pumps mismatch" - ); + assertEq(_well.numberOfPumps(), numberOfPumps, "number of pumps mismatch"); Call[] memory _pumps = _well.pumps(); if (numberOfPumps > 0) { assertEq(_well.firstPump(), pumps[0], "pump mismatch"); } - for (uint i = 0; i < numberOfPumps; i++) { + for (uint256 i = 0; i < numberOfPumps; i++) { assertEq(_pumps[i], pumps[i], "pump mismatch"); } @@ -140,9 +121,6 @@ contract WellBoreStableSwapTest is TestHelper { assertEq(_well.wellFunctionAddress(), wellFunction.target); // Check that Aquifer recorded the deployment - assertEq( - aquifer.wellImplementation(address(_well)), - wellImplementation - ); + assertEq(aquifer.wellImplementation(address(_well)), wellImplementation); } } diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol index 8aacd0e1..8bcbf35d 100644 --- a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -6,11 +6,7 @@ import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { - event RemoveLiquidity( - uint256 lpAmountIn, - uint256[] tokenAmountsOut, - address recipient - ); + event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); uint256[] tokenAmountsOut; uint256 requiredLpAmountIn; @@ -22,16 +18,10 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(); - setupStableSwapWell(10); - - _data = abi.encode( - CurveStableSwap2.WellFunctionData( - 10, - address(tokens[0]), - address(tokens[1]) - ) - ); + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); + + _data = abi.encode(18, 18); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); @@ -43,41 +33,20 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { /// @dev Assumes use of ConstantProduct2 function test_getRemoveLiquidityImbalancedIn() public { - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn( - tokenAmountsOut - ); + uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); assertEq(lpAmountIn, requiredLpAmountIn); } /// @dev not enough LP to receive `tokenAmountsOut` - function test_removeLiquidityImbalanced_revertIf_notEnoughLP() - public - prank(user) - { + function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { uint256 maxLpAmountIn = 5 * 1e18; - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageIn.selector, - requiredLpAmountIn, - maxLpAmountIn - ) - ); - well.removeLiquidityImbalanced( - maxLpAmountIn, - tokenAmountsOut, - user, - type(uint256).max - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); } function test_removeLiquidityImbalanced_revertIf_expired() public { vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityImbalanced( - 0, - new uint256[](2), - user, - block.timestamp - 1 - ); + well.removeLiquidityImbalanced(0, new uint256[](2), user, block.timestamp - 1); } /// @dev Base case @@ -89,12 +58,7 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { vm.expectEmit(true, true, true, true); emit RemoveLiquidity(maxLpAmountIn, tokenAmountsOut, user); - well.removeLiquidityImbalanced( - maxLpAmountIn, - tokenAmountsOut, - user, - type(uint256).max - ); + well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); Balances memory userBalanceAfter = getBalances(user, well); Balances memory wellBalanceAfter = getBalances(address(well), well); @@ -104,51 +68,26 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // `user` balance of underlying tokens increases // assumes initial balance of zero - assertEq( - userBalanceAfter.tokens[0], - tokenAmountsOut[0], - "Incorrect token0 user balance" - ); - assertEq( - userBalanceAfter.tokens[1], - tokenAmountsOut[1], - "Incorrect token1 user balance" - ); + assertEq(userBalanceAfter.tokens[0], tokenAmountsOut[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], tokenAmountsOut[1], "Incorrect token1 user balance"); // Well's reserve of underlying tokens decreases - assertEq( - wellBalanceAfter.tokens[0], - 1500 * 1e18, - "Incorrect token0 well reserve" - ); - assertEq( - wellBalanceAfter.tokens[1], - 19_494 * 1e17, - "Incorrect token1 well reserve" - ); + assertEq(wellBalanceAfter.tokens[0], 1500 * 1e18, "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], 19_494 * 1e17, "Incorrect token1 well reserve"); checkInvariant(address(well)); } /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityImbalanced( - uint256 a0, - uint256 a1 - ) public prank(user) { + function testFuzz_removeLiquidityImbalanced(uint256 a0, uint256 a1) public prank(user) { // Setup amounts of liquidity to remove // NOTE: amounts may or may not be equal uint256[] memory amounts = new uint256[](2); amounts[0] = bound(a0, 0, 750e18); amounts[1] = bound(a1, 0, 750e18); - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances( - address(well), - well - ); - Balances memory userBalanceBeforeRemoveLiquidity = getBalances( - user, - well - ); + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); // Calculate change in Well reserves after removing liquidity uint256[] memory reserves = new uint256[](2); reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; @@ -167,40 +106,17 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // Remove all of `user`'s liquidity and deliver them the tokens vm.expectEmit(true, true, true, true); emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced( - maxLpAmountIn, - amounts, - user, - type(uint256).max - ); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); - Balances memory userBalanceAfterRemoveLiquidity = getBalances( - user, - well - ); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances( - address(well), - well - ); + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); // `user` balance of LP tokens decreases - assertEq( - userBalanceAfterRemoveLiquidity.lp, - maxLpAmountIn - lpAmountIn, - "Incorrect lp output" - ); + assertEq(userBalanceAfterRemoveLiquidity.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); // `user` balance of underlying tokens increases - assertEq( - userBalanceAfterRemoveLiquidity.tokens[0], - amounts[0], - "Incorrect token0 user balance" - ); - assertEq( - userBalanceAfterRemoveLiquidity.tokens[1], - amounts[1], - "Incorrect token1 user balance" - ); + assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); // Well's reserve of underlying tokens decreases // Equal amount of liquidity of 1000e18 were added in the setup function hence the @@ -222,10 +138,7 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` /// before liquidity is removed by `user`. - function testFuzz_removeLiquidityImbalanced_withSwap( - uint256 a0, - uint256 imbalanceBias - ) public { + function testFuzz_removeLiquidityImbalanced_withSwap(uint256 a0, uint256 imbalanceBias) public { // Setup amounts of liquidity to remove // NOTE: amounts[0] is bounded at 1 to prevent slippage overflow // failure, bug fix in progress @@ -236,14 +149,7 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // `user2` performs a swap to imbalance the pool by `imbalanceBias` vm.prank(user2); - well.swapFrom( - tokens[0], - tokens[1], - imbalanceBias, - 0, - user2, - type(uint256).max - ); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); // `user` has LP tokens and will perform a `removeLiquidityImbalanced` call vm.startPrank(user); @@ -269,46 +175,21 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { uint256 maxLpAmountIn = userBalanceBefore.lp; vm.expectEmit(true, true, true, true); emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced( - maxLpAmountIn, - amounts, - user, - type(uint256).max - ); + well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); Balances memory wellBalanceAfter = getBalances(address(well), well); Balances memory userBalanceAfter = getBalances(user, well); // `user` balance of LP tokens decreases - assertEq( - userBalanceAfter.lp, - maxLpAmountIn - lpAmountIn, - "Incorrect lp output" - ); + assertEq(userBalanceAfter.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); // `user` balance of underlying tokens increases - assertEq( - userBalanceAfter.tokens[0], - userBalanceBefore.tokens[0] + amounts[0], - "Incorrect token0 user balance" - ); - assertEq( - userBalanceAfter.tokens[1], - userBalanceBefore.tokens[1] + amounts[1], - "Incorrect token1 user balance" - ); + assertEq(userBalanceAfter.tokens[0], userBalanceBefore.tokens[0] + amounts[0], "Incorrect token0 user balance"); + assertEq(userBalanceAfter.tokens[1], userBalanceBefore.tokens[1] + amounts[1], "Incorrect token1 user balance"); // Well's reserve of underlying tokens decreases - assertEq( - wellBalanceAfter.tokens[0], - wellBalanceBefore.tokens[0] - amounts[0], - "Incorrect token0 well reserve" - ); - assertEq( - wellBalanceAfter.tokens[1], - wellBalanceBefore.tokens[1] - amounts[1], - "Incorrect token1 well reserve" - ); + assertEq(wellBalanceAfter.tokens[0], wellBalanceBefore.tokens[0] - amounts[0], "Incorrect token0 well reserve"); + assertEq(wellBalanceAfter.tokens[1], wellBalanceBefore.tokens[1] - amounts[1], "Incorrect token1 well reserve"); checkInvariant(address(well)); } } diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index ebd20ef9..ab5032c7 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -6,78 +6,40 @@ import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { - event RemoveLiquidityOneToken( - uint256 lpAmountIn, - IERC20 tokenOut, - uint256 tokenAmountOut, - address recipient - ); + event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); CurveStableSwap2 ss; uint256 constant addedLiquidity = 1000 * 1e18; bytes _data; function setUp() public { - ss = new CurveStableSwap2(); - setupStableSwapWell(10); + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); - _data = abi.encode( - CurveStableSwap2.WellFunctionData( - 10, - address(tokens[0]), - address(tokens[1]) - ) - ); + _data = abi.encode(18, 18); } /// @dev Assumes use of CurveStableSwap2 function test_getRemoveLiquidityOneTokenOut() public { - uint256 amountOut = well.getRemoveLiquidityOneTokenOut( - 500 * 1e18, - tokens[0] - ); + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); } /// @dev not enough tokens received for `lpAmountIn`. - function test_removeLiquidityOneToken_revertIf_amountOutTooLow() - public - prank(user) - { + function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { uint256 lpAmountIn = 500 * 1e18; uint256 minTokenAmountOut = 500 * 1e18; - uint256 amountOut = well.getRemoveLiquidityOneTokenOut( - lpAmountIn, - tokens[0] - ); + uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageOut.selector, - amountOut, - minTokenAmountOut - ) - ); - well.removeLiquidityOneToken( - lpAmountIn, - tokens[0], - minTokenAmountOut, - user, - type(uint256).max - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); } function test_removeLiquidityOneToken_revertIf_expired() public { vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityOneToken( - 0, - tokens[0], - 0, - user, - block.timestamp - 1 - ); + well.removeLiquidityOneToken(0, tokens[0], 0, user, block.timestamp - 1); } /// @dev Base case @@ -87,35 +49,17 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { Balances memory prevUserBalance = getBalances(user, well); vm.expectEmit(true, true, true, true); - emit RemoveLiquidityOneToken( - lpAmountIn, - tokens[0], - minTokenAmountOut, - user - ); + emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); - uint256 amountOut = well.removeLiquidityOneToken( - lpAmountIn, - tokens[0], - minTokenAmountOut, - user, - type(uint256).max - ); + uint256 amountOut = + well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); Balances memory userBalance = getBalances(user, well); Balances memory wellBalance = getBalances(address(well), well); - assertEq( - userBalance.lp, - prevUserBalance.lp - lpAmountIn, - "Incorrect lpAmountIn" - ); + assertEq(userBalance.lp, prevUserBalance.lp - lpAmountIn, "Incorrect lpAmountIn"); - assertEq( - userBalance.tokens[0], - amountOut, - "Incorrect token0 user balance" - ); + assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); // Equal amount of liquidity of 1000e18 were added in the setup function hence the @@ -126,11 +70,7 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { (initialLiquidity + addedLiquidity) - minTokenAmountOut, "Incorrect token0 well reserve" ); - assertEq( - wellBalance.tokens[1], - (initialLiquidity + addedLiquidity), - "Incorrect token1 well reserve" - ); + assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); checkStableSwapInvariant(address(well)); } @@ -142,20 +82,14 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { amounts[0] = bound(a0, 1e18, 750e18); amounts[1] = 0; - Balances memory userBalanceBeforeRemoveLiquidity = getBalances( - user, - well - ); + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); uint256 userLpBalance = userBalanceBeforeRemoveLiquidity.lp; // Find the LP amount that should be burned given the fuzzed // amounts. Works even though only amounts[0] is set. uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances( - address(well), - well - ); + Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); // Calculate change in Well reserves after removing liquidity uint256[] memory reserves = new uint256[](2); @@ -168,52 +102,16 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; vm.expectEmit(true, true, true, false); - emit RemoveLiquidityOneToken( - lpAmountBurned, - tokens[0], - amounts[0], - user - ); - uint256 amountOut = well.removeLiquidityOneToken( - lpAmountIn, - tokens[0], - 0, - user, - type(uint256).max - ); // no minimum out - assertApproxEqAbs( - amountOut, - amounts[0], - 1, - "amounts[0] > userLpBalance" - ); + emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); + uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out + assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); - Balances memory userBalanceAfterRemoveLiquidity = getBalances( - user, - well - ); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances( - address(well), - well - ); + Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); + Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); - assertEq( - userBalanceAfterRemoveLiquidity.lp, - userLpBalance - lpAmountIn, - "Incorrect lp output" - ); - assertApproxEqAbs( - userBalanceAfterRemoveLiquidity.tokens[0], - amounts[0], - 1, - "Incorrect token0 user balance" - ); - assertApproxEqAbs( - userBalanceAfterRemoveLiquidity.tokens[1], - amounts[1], - 1, - "Incorrect token1 user balance" - ); // should stay the same + assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same assertApproxEqAbs( wellBalanceAfterRemoveLiquidity.tokens[0], (initialLiquidity + addedLiquidity) - amounts[0], diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol index 3e0c87e8..4f34da94 100644 --- a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol @@ -12,8 +12,8 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(); - setupStableSwapWell(10); + ss = new CurveStableSwap2(address(1)); + setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens addLiquidityEqualAmount(user, addedLiquidity); @@ -24,11 +24,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { function test_liquidityInitialized() public { IERC20[] memory tokens = well.tokens(); for (uint256 i; i < tokens.length; i++) { - assertEq( - tokens[i].balanceOf(address(well)), - initialLiquidity + addedLiquidity, - "incorrect token reserve" - ); + assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); } assertEq(well.totalSupply(), 4000 * 1e18, "incorrect totalSupply"); } @@ -38,38 +34,20 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { function test_getRemoveLiquidityOut() public { uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); for (uint256 i; i < tokens.length; i++) { - assertEq( - amountsOut[i], - 500 * 1e18, - "incorrect getRemoveLiquidityOut" - ); + assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); } } /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token - function test_removeLiquidity_revertIf_minAmountOutTooHigh() - public - prank(user) - { + function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { uint256 lpAmountIn = 1000 * 1e18; uint256[] memory minTokenAmountsOut = new uint256[](2); minTokenAmountsOut[0] = 501 * 1e18; // too high minTokenAmountsOut[1] = 500 * 1e18; - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageOut.selector, - 500 * 1e18, - minTokenAmountsOut[0] - ) - ); - well.removeLiquidity( - lpAmountIn, - minTokenAmountsOut, - user, - type(uint256).max - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 500 * 1e18, minTokenAmountsOut[0])); + well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); } function test_removeLiquidity_revertIf_expired() public { @@ -123,10 +101,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { assertLe( well.totalSupply(), - CurveStableSwap2(wellFunction.target).calcLpTokenSupply( - well.getReserves(), - wellFunction.data - ) + CurveStableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) ); checkInvariant(address(well)); } @@ -134,14 +109,8 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` /// before liquidity is removed by `user`. - function test_removeLiquidity_fuzzSwapBias( - uint256 lpAmountBurned, - uint256 imbalanceBias - ) public { - Balances memory userBalanceBeforeRemoveLiquidity = getBalances( - user, - well - ); + function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { + Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); @@ -149,14 +118,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { // `user2` performs a swap to imbalance the pool by `imbalanceBias` vm.prank(user2); - well.swapFrom( - tokens[0], - tokens[1], - imbalanceBias, - 0, - user2, - type(uint256).max - ); + well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); // `user` has LP tokens and will perform a `removeLiquidity` call vm.startPrank(user); @@ -173,12 +135,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { action.fees = new uint256[](2); (before, action) = beforeRemoveLiquidity(action); - well.removeLiquidity( - lpAmountBurned, - tokenAmountsOut, - user, - type(uint256).max - ); + well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); afterRemoveLiquidity(before, action); checkStableSwapInvariant(address(well)); } diff --git a/test/stableSwap/Well.ShiftStable.t.sol b/test/stableSwap/Well.ShiftStable.t.sol index 741fff76..0ea78807 100644 --- a/test/stableSwap/Well.ShiftStable.t.sol +++ b/test/stableSwap/Well.ShiftStable.t.sol @@ -6,15 +6,10 @@ import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellShiftStableTest is TestHelper { - event Shift( - uint256[] reserves, - IERC20 toToken, - uint256 minAmountOut, - address recipient - ); + event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); function setUp() public { - setupStableSwapWell(10); + setupStableSwapWell(); } /// @dev Shift excess token0 into token1. @@ -23,43 +18,22 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received token0" - ); - assertEq( - wellBalanceBeforeShift.tokens[1], - 1000e18, - "Well should have NOT have received token1" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that `_user` has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); uint256 minAmountOut = well.getShiftOut(tokens[1]); uint256[] memory calcReservesAfter = new uint256[](2); calcReservesAfter[0] = tokens[0].balanceOf(address(well)); - calcReservesAfter[1] = - tokens[1].balanceOf(address(well)) - - minAmountOut; + calcReservesAfter[1] = tokens[1].balanceOf(address(well)) - minAmountOut; vm.expectEmit(true, true, true, true); emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); @@ -67,38 +41,16 @@ contract WellShiftStableTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained token1 - assertEq( - userBalanceAfterShift.tokens[0], - 0, - "User should NOT have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - amtOut, - "User should have gained token1" - ); - assertTrue( - userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], - "User should have more token1" - ); + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); + assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); // Reserves should now match balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); // The difference has been sent to _user. assertEq( @@ -120,37 +72,20 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received tokens" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); uint256 minAmountOut = well.getShiftOut(tokens[0]); uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = - tokens[0].balanceOf(address(well)) - - minAmountOut; + calcReservesAfter[0] = tokens[0].balanceOf(address(well)) - minAmountOut; calcReservesAfter[1] = tokens[1].balanceOf(address(well)); vm.expectEmit(true, true, true, true); @@ -160,34 +95,17 @@ contract WellShiftStableTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained token0 + assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); assertEq( - userBalanceAfterShift.tokens[0], - amount, - "User should have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - userBalanceBeforeShift.tokens[1], - "User should NOT have gained token1" + userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" ); // Reserves should now match balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); assertEq( userBalanceAfterShift.tokens[0], @@ -199,64 +117,30 @@ contract WellShiftStableTest is TestHelper { /// @dev Calling shift() on a balanced Well should do nothing. function test_shift_balanced_pool() public prank(user) { - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - wellBalanceBeforeShift.tokens[1], - "Well should should be balanced" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); well.shift(tokens[1], 0, _user); uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained neither token - assertEq( - userBalanceAfterShift.tokens[0], - 0, - "User should NOT have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - 0, - "User should NOT have gained token1" - ); + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); // Reserves should equal balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); checkInvariant(address(well)); } @@ -265,29 +149,12 @@ contract WellShiftStableTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received token0" - ); - assertEq( - wellBalanceBeforeShift.tokens[1], - 1000e18, - "Well should have NOT have received token1" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); uint256 amountOut = well.getShiftOut(tokens[1]); - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageOut.selector, - amountOut, - type(uint256).max - ) - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); well.shift(tokens[1], type(uint256).max, user); } } diff --git a/test/stableSwap/Well.SkimStableSwap.t.sol b/test/stableSwap/Well.SkimStableSwap.t.sol index b04be2be..9a1d1160 100644 --- a/test/stableSwap/Well.SkimStableSwap.t.sol +++ b/test/stableSwap/Well.SkimStableSwap.t.sol @@ -5,7 +5,7 @@ import {TestHelper, Balances} from "test/TestHelper.sol"; contract WellSkimTest is TestHelper { function setUp() public { - setupStableSwapWell(10); + setupStableSwapWell(); } function test_initialized() public view { @@ -23,10 +23,7 @@ contract WellSkimTest is TestHelper { tokens[0].transfer(address(well), amounts[0]); tokens[1].transfer(address(well), amounts[1]); - Balances memory wellBalanceBeforeSkim = getBalances( - address(well), - well - ); + Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); // Verify that the Well has received the tokens assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); diff --git a/test/stableSwap/Well.SwapFromStableSwap.t.sol b/test/stableSwap/Well.SwapFromStableSwap.t.sol index 0c3b1cc0..fd7f814d 100644 --- a/test/stableSwap/Well.SwapFromStableSwap.t.sol +++ b/test/stableSwap/Well.SwapFromStableSwap.t.sol @@ -10,20 +10,17 @@ import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellSwapFromStableSwapTest is SwapHelper { function setUp() public { - setupStableSwapWell(10); + setupStableSwapWell(); } function test_getSwapOut() public view { - uint amountIn = 10 * 1e18; - uint amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); + uint256 amountIn = 10 * 1e18; + uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); assertEq(amountOut, 9_995_239_930_393_036_263); // ~0.05% slippage } - function testFuzz_getSwapOut_revertIf_insufficientWellBalance( - uint amountIn, - uint i - ) public prank(user) { + function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint256 amountIn, uint256 i) public prank(user) { // Swap token `i` -> all other tokens vm.assume(i < tokens.length); @@ -35,28 +32,15 @@ contract WellSwapFromStableSwapTest is SwapHelper { // Its `getBalance` function can return an amount greater than the Well holds. IWellFunction badFunction = new MockFunctionBad(); Well badWell = encodeAndBoreWell( - address(aquifer), - wellImplementation, - tokens, - Call(address(badFunction), ""), - pumps, - bytes32(0) + address(aquifer), wellImplementation, tokens, Call(address(badFunction), ""), pumps, bytes32(0) ); // Check assumption that reserves are empty Balances memory wellBalances = getBalances(address(badWell), badWell); - assertEq( - wellBalances.tokens[0], - 0, - "bad assumption: wellBalances.tokens[0] != 0" - ); - assertEq( - wellBalances.tokens[1], - 0, - "bad assumption: wellBalances.tokens[1] != 0" - ); + assertEq(wellBalances.tokens[0], 0, "bad assumption: wellBalances.tokens[0] != 0"); + assertEq(wellBalances.tokens[1], 0, "bad assumption: wellBalances.tokens[1] != 0"); - for (uint j = 0; j < tokens.length; ++j) { + for (uint256 j = 0; j < tokens.length; ++j) { if (j != i) { vm.expectRevert(); // underflow badWell.getSwapOut(tokens[i], tokens[j], amountIn); @@ -67,37 +51,17 @@ contract WellSwapFromStableSwapTest is SwapHelper { /// @dev Swaps should always revert if `fromToken` = `toToken`. function test_swapFrom_revertIf_sameToken() public prank(user) { vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapFrom( - tokens[0], - tokens[0], - 100 * 1e18, - 0, - user, - type(uint).max - ); + well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); } /// @dev Slippage revert if minAmountOut is too high function test_swapFrom_revertIf_minAmountOutTooHigh() public prank(user) { - uint amountIn = 10 * 1e18; - uint amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); - uint minAmountOut = amountOut + 1e18; - - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageOut.selector, - amountOut, - minAmountOut - ) - ); - well.swapFrom( - tokens[0], - tokens[1], - amountIn, - minAmountOut, - user, - type(uint).max - ); + uint256 amountIn = 10 * 1e18; + uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); + uint256 minAmountOut = amountOut + 1e18; + + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); + well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); } function test_swapFrom_revertIf_expired() public { @@ -105,60 +69,32 @@ contract WellSwapFromStableSwapTest is SwapHelper { well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); } - function testFuzz_swapFrom(uint amountIn) public prank(user) { + function testFuzz_swapFrom(uint256 amountIn) public prank(user) { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); - (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom( - 0, - 1, - amountIn - ); - act.wellSends = well.swapFrom( - tokens[0], - tokens[1], - amountIn, - 0, - user, - type(uint).max - ); + (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); + act.wellSends = well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); afterSwapFrom(bef, act); checkStableSwapInvariant(address(well)); } - function testFuzz_swapAndRemoveAllLiq(uint amountIn) public { + function testFuzz_swapAndRemoveAllLiq(uint256 amountIn) public { amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); vm.prank(user); - well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint).max); + well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); vm.prank(address(this)); well.removeLiquidityImbalanced( - type(uint).max, - IWell(address(well)).getReserves(), - address(this), - type(uint).max + type(uint256).max, IWell(address(well)).getReserves(), address(this), type(uint256).max ); assertEq(IERC20(address(well)).totalSupply(), 0); } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapFrom_equalSwap(uint token0AmtIn) public prank(user) { + function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { vm.assume(token0AmtIn < tokens[0].balanceOf(user)); - uint token1Out = well.swapFrom( - tokens[0], - tokens[1], - token0AmtIn, - 0, - user, - type(uint).max - ); - uint token0Out = well.swapFrom( - tokens[1], - tokens[0], - token1Out, - 0, - user, - type(uint).max - ); + uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); + uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); assertEq(token0Out, token0AmtIn); checkInvariant(address(well)); } diff --git a/test/stableSwap/Well.SwapToStableSwap.t.sol b/test/stableSwap/Well.SwapToStableSwap.t.sol index c4548999..b027ace3 100644 --- a/test/stableSwap/Well.SwapToStableSwap.t.sol +++ b/test/stableSwap/Well.SwapToStableSwap.t.sol @@ -10,7 +10,7 @@ import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellSwapToStableSwapTest is SwapHelper { function setUp() public { - setupStableSwapWell(10); + setupStableSwapWell(); } function test_getSwapIn() public view { @@ -19,9 +19,7 @@ contract WellSwapToStableSwapTest is SwapHelper { assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage } - function testFuzz_getSwapIn_revertIf_insufficientWellBalance( - uint256 i - ) public prank(user) { + function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { IERC20[] memory _tokens = well.tokens(); Balances memory wellBalances = getBalances(address(well), well); vm.assume(i < _tokens.length); @@ -41,14 +39,7 @@ contract WellSwapToStableSwapTest is SwapHelper { /// @dev Swaps should always revert if `fromToken` = `toToken`. function test_swapTo_revertIf_sameToken() public prank(user) { vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapTo( - tokens[0], - tokens[0], - 100 * 1e18, - 0, - user, - type(uint256).max - ); + well.swapTo(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); } /// @dev Slippage revert occurs if maxAmountIn is too low @@ -57,21 +48,8 @@ contract WellSwapToStableSwapTest is SwapHelper { uint256 amountIn = 100_482_889_020_651_556_292; uint256 maxAmountIn = (amountIn * 99) / 100; - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageIn.selector, - amountIn, - maxAmountIn - ) - ); - well.swapTo( - tokens[0], - tokens[1], - maxAmountIn, - amountOut, - user, - type(uint256).max - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); } /// @dev Note: this covers the case where there is a fee as well @@ -91,9 +69,7 @@ contract WellSwapToStableSwapTest is SwapHelper { Balances memory wellBalancesBefore = getBalances(address(well), well); // Decrease reserve of token 1 by `amountOut` which is paid to user - uint256[] memory calcBalances = new uint256[]( - wellBalancesBefore.tokens.length - ); + uint256[] memory calcBalances = new uint256[](wellBalancesBefore.tokens.length); calcBalances[0] = wellBalancesBefore.tokens[0]; calcBalances[1] = wellBalancesBefore.tokens[1] - amountOut; @@ -106,63 +82,28 @@ contract WellSwapToStableSwapTest is SwapHelper { vm.expectEmit(true, true, true, true); emit Swap(tokens[0], tokens[1], calcAmountIn, amountOut, user); - well.swapTo( - tokens[0], - tokens[1], - maxAmountIn, - amountOut, - user, - type(uint256).max - ); + well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); Balances memory userBalancesAfter = getBalances(user, well); Balances memory wellBalancesAfter = getBalances(address(well), well); assertEq( - userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], - calcAmountIn, - "Incorrect token0 user balance" + userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], calcAmountIn, "Incorrect token0 user balance" ); + assertEq(userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], amountOut, "Incorrect token1 user balance"); assertEq( - userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], - amountOut, - "Incorrect token1 user balance" - ); - assertEq( - wellBalancesAfter.tokens[0], - wellBalancesBefore.tokens[0] + calcAmountIn, - "Incorrect token0 well reserve" - ); - assertEq( - wellBalancesAfter.tokens[1], - wellBalancesBefore.tokens[1] - amountOut, - "Incorrect token1 well reserve" + wellBalancesAfter.tokens[0], wellBalancesBefore.tokens[0] + calcAmountIn, "Incorrect token0 well reserve" ); + assertEq(wellBalancesAfter.tokens[1], wellBalancesBefore.tokens[1] - amountOut, "Incorrect token1 well reserve"); checkStableSwapInvariant(address(well)); } /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapTo_equalSwap( - uint256 token0AmtOut - ) public prank(user) { + function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { // assume amtOut is lower due to slippage vm.assume(token0AmtOut < 500e18); - uint256 token1In = well.swapTo( - tokens[0], - tokens[1], - 1000e18, - token0AmtOut, - user, - type(uint256).max - ); - uint256 token0In = well.swapTo( - tokens[1], - tokens[0], - 1000e18, - token1In, - user, - type(uint256).max - ); + uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); + uint256 token0In = well.swapTo(tokens[1], tokens[0], 1000e18, token1In, user, type(uint256).max); assertEq(token0In, token0AmtOut); checkInvariant(address(well)); } From 7fbcc2f2eabf44a72d252b5bd925a12de1650244 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 9 Jul 2024 17:13:40 +0200 Subject: [PATCH 23/69] stable2 --- lib/forge-std | 2 +- src/functions/CurveStableSwap2.sol | 481 ----------------- src/functions/SolidlyStableSwap2.sol | 155 ------ src/functions/Stable2.sol | 492 ++++++++++++------ test/Stable2/Well.BoreStableSwap.t.sol | 9 +- ....RemoveLiquidityImbalancedStableSwap.t.sol | 6 +- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 8 +- .../Well.RemoveLiquidityStableSwap.t.sol | 9 +- test/Stable2/Well.ShiftStable.t.sol | 2 +- test/TestHelper.sol | 24 +- ...bleSwap2.calcReserveAtRatioLiquidity.t.sol | 6 +- ...veStableSwap2.calcReserveAtRatioSwap.t.sol | 4 +- test/functions/StableSwap.t.sol | 6 +- test/libraries/LibMath.t.sol | 16 +- test/stableSwap/Well.BoreStableSwap.t.sol | 6 +- ....RemoveLiquidityImbalancedStableSwap.t.sol | 6 +- ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 8 +- .../Well.RemoveLiquidityStableSwap.t.sol | 9 +- test/stableSwap/Well.ShiftStable.t.sol | 2 +- 19 files changed, 382 insertions(+), 869 deletions(-) delete mode 100644 src/functions/CurveStableSwap2.sol delete mode 100644 src/functions/SolidlyStableSwap2.sol diff --git a/lib/forge-std b/lib/forge-std index 978ac6fa..8948d45d 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 978ac6fadb62f5f0b723c996f64be52eddba6801 +Subproject commit 8948d45d3d9022c508b83eb5d26fd3a7a93f2f32 diff --git a/src/functions/CurveStableSwap2.sol b/src/functions/CurveStableSwap2.sol deleted file mode 100644 index d81c73fe..00000000 --- a/src/functions/CurveStableSwap2.sol +++ /dev/null @@ -1,481 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; -import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; -import {SafeMath} from "oz/utils/math/SafeMath.sol"; -import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import "forge-std/console.sol"; - -interface ILookupTable { - /** - * @notice the lookup table returns a series of data, given a price point: - * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. - * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. - * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. - * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. - * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. - * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. - * @param precision precision of reserve. - */ - struct PriceData { - uint256 highPrice; - uint256 highPriceI; - uint256 highPriceJ; - uint256 lowPrice; - uint256 lowPriceI; - uint256 lowPriceJ; - uint256 precision; - } - - function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); - function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); - function getAParameter() external view returns (uint256); -} -/** - * @author Brean - * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. - * developed by curve. - * - * Stableswap Wells with 2 tokens use the formula: - * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` - * - * Where: - * `A` is the Amplication parameter. - * `D` is the supply of LP tokens - * `b_i` is the reserve at index `i` - * - * @dev Limited to tokens with a maximum of 18 decimals. - */ - -contract CurveStableSwap2 is ProportionalLPToken2, IBeanstalkWellFunction { - struct PriceData { - uint256 targetPrice; - uint256 currentPrice; - uint256 maxStepSize; - ILookupTable.PriceData lutData; - } - - using LibMath for uint256; - using SafeMath for uint256; - - // 2 token Pool. - uint256 constant N = 2; - - // A precision - uint256 constant A_PRECISION = 100; - - // Precision that all pools tokens will be converted to. - uint256 constant POOL_PRECISION_DECIMALS = 18; - - // Calc Rate Precision. - uint256 constant CALC_RATE_PRECISION = 1e24; - - // price Precision. - uint256 constant PRICE_PRECISION = 1e6; - - address immutable lookupTable; - uint256 immutable a; - - // Errors - error InvalidAParameter(uint256); - error InvalidTokens(); - error InvalidTokenDecimals(); - error InvalidLUT(); - - // Due to the complexity of `calcReserveAtRatioLiquidity` and `calcReserveAtRatioSwap`, - // a LUT table is used to reduce the complexity of the calculations on chain. - // the lookup table contract implements 3 functions: - // 1. getRatiosFromPriceLiquidity(uint256) -> PriceData memory - // 2. getRatiosFromPriceSwap(uint256) -> PriceData memory - // 3. getAParameter() -> uint256 - // Lookup tables are a function of the A parameter. - constructor(address lut) { - if (lut == address(0)) revert InvalidLUT(); - lookupTable = lut; - // a = ILookupTable(lut).getAParameter(); - a = 10; - } - - /** - * @notice Calculate the amount of LP tokens minted when adding liquidity. - * D invariant calculation in non-overflowing integer operations iteratively - * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) - * - * Converging solution: - * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) - */ - function calcLpTokenSupply( - uint256[] memory reserves, - bytes memory data - ) public view returns (uint256 lpTokenSupply) { - uint256[] memory decimals = decodeWellData(data); - // scale reserves to 18 decimals. - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - uint256 Ann = a * N * N * A_PRECISION; - - uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; - if (sumReserves == 0) return 0; - lpTokenSupply = sumReserves; - for (uint256 i = 0; i < 255; i++) { - uint256 dP = lpTokenSupply; - // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(scaledReserves[0].mul(N)); - dP = dP.mul(lpTokenSupply).div(scaledReserves[1].mul(N)); - uint256 prevReserves = lpTokenSupply; - lpTokenSupply = Ann.mul(sumReserves).div(A_PRECISION).add(dP.mul(N)).mul(lpTokenSupply).div( - Ann.sub(A_PRECISION).mul(lpTokenSupply).div(A_PRECISION).add(N.add(1).mul(dP)) - ); - // Equality with the precision of 1 - if (lpTokenSupply > prevReserves) { - if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; - } else { - if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; - } - } - } - - /** - * @notice Calculate x[i] if one reduces D from being calculated for reserves to D - * Done by solving quadratic equation iteratively. - * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) - * x_1**2 + b*x_1 = c - * x_1 = (x_1**2 + c) / (2*x_1 + b) - * @dev This function has a precision of +/- 1, - * which may round in favor of the well or the user. - */ - function calcReserve( - uint256[] memory reserves, - uint256 j, - uint256 lpTokenSupply, - bytes memory data - ) public view returns (uint256 reserve) { - uint256[] memory decimals = decodeWellData(data); - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - // avoid stack too deep errors. - (uint256 c, uint256 b) = - getBandC(a * N * N * A_PRECISION, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]); - reserve = lpTokenSupply; - uint256 prevReserve; - - for (uint256 i; i < 255; ++i) { - prevReserve = reserve; - reserve = _calcReserve(reserve, b, c, lpTokenSupply); - // Equality with the precision of 1 - // scale reserve down to original precision - if (reserve > prevReserve) { - if (reserve - prevReserve <= 1) { - return reserve.div(10 ** (18 - decimals[j])); - } - } else { - if (prevReserve - reserve <= 1) { - return reserve.div(10 ** (18 - decimals[j])); - } - } - } - revert("did not find convergence"); - } - - /** - * @inheritdoc IMultiFlowPumpWellFunction - * @dev `calcReserveAtRatioSwap` fetches the closes approxeimate ratios from the target price, - * and performs neuwtons method in order to - */ - function calcReserveAtRatioSwap( - uint256[] memory reserves, - uint256 j, - uint256[] memory ratios, - bytes calldata data - ) external view returns (uint256 reserve) { - uint256 i = j == 1 ? 0 : 1; - // scale reserves and ratios: - uint256[] memory decimals = decodeWellData(data); - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - PriceData memory pd; - - { - uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); - // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; - } - - // get ratios and price from the closest highest and lowest price from targetPrice: - pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceSwap(pd.targetPrice); - - // perform an initial update on the reserves, such that `calcRate(reserves, i, j, data) == pd.lutData.lowPrice. - - // calculate lp token supply: - uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); - - // lpTokenSupply / 2 gives the reserves at parity: - uint256 parityReserve = lpTokenSupply / 2; - - // update `scaledReserves`. - scaledReserves[0] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; - scaledReserves[1] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; - - // calculate max step size: - pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; - - // initialize currentPrice: - pd.currentPrice = pd.lutData.lowPrice; - - for (uint256 k; k < 255; k++) { - // scale stepSize proporitional to distance from price: - uint256 stepSize = - pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); - // increment reserve by stepSize: - scaledReserves[j] = reserves[j] + stepSize; - // calculate scaledReserve[i]: - scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - - // check if new price is within 1 of target price: - if (pd.currentPrice > pd.targetPrice) { - if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); - } else { - if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); - } - - // calc currentPrice: - pd.currentPrice = calcRate(reserves, i, j, data); - } - } - - /** - * @inheritdoc IMultiFlowPumpWellFunction - * @dev Returns a rate with 6 decimal precision. - * Requires a minimum scaled reserves of 1e12. - * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, - * which is prohibtive for large value tokens. - */ - function calcRate( - uint256[] memory reserves, - uint256 i, - uint256 j, - bytes calldata data - ) public view returns (uint256 rate) { - uint256[] memory decimals = decodeWellData(data); - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). - uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); - - // reverse if i is not 0. - - // add 1e6 to reserves: - scaledReserves[j] += PRICE_PRECISION; - - // calculate new reserve 1: - uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - rate = (scaledReserves[0] - new_reserve1); - } - - /** - * @inheritdoc IBeanstalkWellFunction - * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. - * @dev `calcReserveAtRatioLiquidity` fetches the closest approximate ratios from the target price, and - * perform an neutonian-estimation to calculate the reserves. - */ - function calcReserveAtRatioLiquidity( - uint256[] calldata reserves, - uint256 j, - uint256[] calldata ratios, - bytes calldata data - ) external view returns (uint256 reserve) { - uint256 i = j == 1 ? 0 : 1; - // scale reserves and ratios: - uint256[] memory decimals = decodeWellData(data); - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - PriceData memory pd; - - { - uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); - // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; - } - - // calc currentPrice: - pd.currentPrice = calcRate(reserves, i, j, data); - - // get ratios and price from the closest highest and lowest price from targetPrice: - pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); - - // update scaledReserve[j] based on lowPrice: - scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; - - // calculate max step size: - pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; - - for (uint256 k; k < 255; k++) { - // scale stepSize proporitional to distance from price: - uint256 stepSize = - pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); - // increment reserve by stepSize: - scaledReserves[j] = reserves[j] + stepSize; - // calculate new price from reserves: - pd.currentPrice = calcRate(scaledReserves, i, j, data); - - // check if new price is within 1 of target price: - if (pd.currentPrice > pd.targetPrice) { - if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); - } else { - if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); - } - } - } - - function name() external pure returns (string memory) { - return "StableSwap"; - } - - function symbol() external pure returns (string memory) { - return "SS2"; - } - - /** - * @notice decodes the data encoded in the well. - * @return decimals an array of the decimals of the tokens in the well. - */ - function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) { - (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256)); - - // if well data returns 0, assume 18 decimals. - if (decimal0 == 0) { - decimal0 = 18; - } - if (decimal0 == 0) { - decimal1 = 18; - } - if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals(); - - decimals = new uint256[](2); - decimals[0] = decimal0; - decimals[1] = decimal1; - } - - /** - * @notice scale `reserves` by `precision`. - * @dev this sets both reserves to 18 decimals. - */ - function getScaledReserves( - uint256[] memory reserves, - uint256[] memory decimals - ) internal pure returns (uint256[] memory scaledReserves) { - scaledReserves = new uint256[](2); - scaledReserves[0] = reserves[0] * 10 ** (18 - decimals[0]); - scaledReserves[1] = reserves[1] * 10 ** (18 - decimals[1]); - } - - function _calcReserve( - uint256 reserve, - uint256 b, - uint256 c, - uint256 lpTokenSupply - ) private pure returns (uint256) { - return reserve.mul(reserve).add(c).div(reserve.mul(2).add(b).sub(lpTokenSupply)); - } - - function getBandC( - uint256 Ann, - uint256 lpTokenSupply, - uint256 reserves - ) private pure returns (uint256 c, uint256 b) { - c = lpTokenSupply.mul(lpTokenSupply).div(reserves.mul(N)).mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); - } -} - -// /** -// * @inheritdoc IMultiFlowPumpWellFunction -// * @dev when the reserves are equal, the summation of the reserves -// * is equivalent to the token supply of the Well. The LP token supply is calculated from -// * `reserves`, and is scaled based on `ratios`. -// */ -// function calcReserveAtRatioSwap( -// uint256[] memory reserves, -// uint256 j, -// uint256[] memory ratios, -// bytes calldata data -// ) external view returns (uint256 reserve) { -// DeltaB memory db; - -// uint256 i = j == 1 ? 0 : 1; -// // scale reserves to 18 decimals. -// uint256 lpTokenSupply = calcLpTokenSupply(reserves, data); -// console.log("lpTokenSupply:", lpTokenSupply); -// // inital guess -// db.currentBeans = int256(reserves[j]); -// console.log("db.currentBeans"); -// console.logInt(db.currentBeans); -// db.pegBeans = lpTokenSupply / 2; -// console.log("db.pegBeans"); -// console.log(db.pegBeans); -// db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); -// console.log("db.deltaBToPeg"); -// console.logInt(db.deltaBToPeg); - -// uint256 prevPrice; -// uint256 x; -// uint256 x2; - -// // fetch target and pool prices. -// // scale ratio by precision: -// ratios[0] = ratios[0] * CALC_RATE_PRECISION; -// ratios[1] = ratios[1] * CALC_RATE_PRECISION; -// console.log("ratios[0]", ratios[0]); -// console.log("ratios[1]", ratios[1]); - -// db.targetPrice = calcRate(ratios, i, j, data); -// console.log("db.targetPrice", db.targetPrice); -// console.log("reserve0", reserves[0]); -// console.log("reserve1", reserves[1]); -// db.poolPrice = calcRate(reserves, i, j, data); -// console.log("db.poolPrice", db.poolPrice); - -// for (uint256 k; k < 2; k++) { -// db.deltaPriceToTarget = int256(db.targetPrice) - int256(db.poolPrice); -// console.log("deltaPriceToTarget"); -// console.logInt(db.deltaPriceToTarget); -// db.deltaPriceToPeg = 1e18 - int256(db.poolPrice); -// console.log("deltaPriceToPeg"); - -// console.logInt(db.deltaPriceToPeg); -// console.log("reserve0----", reserves[j]); -// console.log("pegBeans----", db.pegBeans); -// db.deltaBToPeg = int256(db.pegBeans) - int256(reserves[j]); -// console.log("deltaBToPeg"); -// console.logInt(db.deltaBToPeg); -// console.log("estDeltaB"); -// console.logInt(db.estDeltaB); - -// if (db.deltaPriceToPeg != 0) { -// db.estDeltaB = (db.deltaBToPeg * int256((db.deltaPriceToTarget * 1e18) / db.deltaPriceToPeg)) / 1e18; -// } else { -// db.estDeltaB = 0; -// } -// console.log("estDeltaB"); -// console.logInt(db.estDeltaB); -// x = uint256(int256(reserves[j]) + db.estDeltaB); -// console.log("-----reserve0----", reserves[0]); -// console.log("-----reserve1----", reserves[1]); -// console.log(i); -// x2 = calcReserve(reserves, i, lpTokenSupply, data); -// console.log("x", x, "x2", x2); -// reserves[j] = x; -// reserves[i] = x2; -// prevPrice = db.poolPrice; -// db.poolPrice = calcRate(reserves, i, j, data); -// if (prevPrice > db.poolPrice) { -// if (prevPrice - db.poolPrice <= 1) break; -// } else if (db.poolPrice - prevPrice <= 1) { -// break; -// } -// } -// return reserves[j]; -// } diff --git a/src/functions/SolidlyStableSwap2.sol b/src/functions/SolidlyStableSwap2.sol deleted file mode 100644 index e92092ea..00000000 --- a/src/functions/SolidlyStableSwap2.sol +++ /dev/null @@ -1,155 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -// import {IWellFunction} from "src/interfaces/IWellFunction.sol"; -// import {LibMath} from "src/libraries/LibMath.sol"; -// import {SafeMath} from "oz/utils/math/SafeMath.sol"; -// import {IERC20} from "forge-std/interfaces/IERC20.sol"; - -// /** -// * @author Brean -// * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. -// * developed by Solidly/Aerodome: https://github.com/aerodrome-finance/contracts -// * -// * Stableswap Wells with 2 tokens use the formula: -// * `d = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` -// * -// * Where: -// * `d` is the supply of LP tokens -// * `b_i` is the reserve at index `i` -// */ -// contract CurveStableSwap2 is IWellFunction { -// using LibMath for uint; -// using SafeMath for uint; - -// uint256 constant PRECISION = 1e18; - -// /** -// * @notice Calculates the `j`th reserve given a list of `reserves` and `lpTokenSupply`. -// * @param reserves A list of token reserves. The jth reserve will be ignored, but a placeholder must be provided. -// * @param j The index of the reserve to solve for -// * @param lpTokenSupply The supply of LP tokens -// * @param data Extra Well function data provided on every call -// * @return reserve The resulting reserve at the jth index -// * @dev Should round up to ensure that Well reserves are marginally higher to enforce calcLpTokenSupply(...) >= totalSupply() -// */ -// function calcReserve( -// uint256[] memory reserves, -// uint256 j, -// uint256 lpTokenSupply, -// bytes calldata data -// ) external view returns (uint256 reserve); - -// /** -// * @notice Gets the LP token supply given a list of reserves. -// * @param reserves A list of token reserves -// * @param data Extra Well function data provided on every call -// * @return lpTokenSupply The resulting supply of LP tokens -// * @dev Should round down to ensure so that the Well Token supply is marignally lower to enforce calcLpTokenSupply(...) >= totalSupply() -// */ -// function calcLpTokenSupply( -// uint256[] memory reserves, -// bytes calldata data -// ) external view returns (uint256 lpTokenSupply); - -// /** -// * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. -// * @param lpTokenAmount An amount of LP tokens -// * @param reserves A list of token reserves -// * @param lpTokenSupply The current supply of LP tokens -// * @param data Extra Well function data provided on every call -// * @return underlyingAmounts The amount of each reserve token that underlies the LP tokens -// * @dev The constraint totalSupply() <= calcLPTokenSupply(...) must be held in the case where -// * `lpTokenAmount` LP tokens are burned in exchanged for `underlyingAmounts`. If the constraint -// * does not hold, then the Well Function is invalid. -// */ -// function calcLPTokenUnderlying( -// uint256 lpTokenAmount, -// uint256[] memory reserves, -// uint256 lpTokenSupply, -// bytes calldata data -// ) external view returns (uint256[] memory underlyingAmounts) { -// // overflow cannot occur as lpTokenAmount could not be calculated. -// underlyingAmounts[0] = (lpTokenAmount * reserves[0]) / lpTokenSupply; -// underlyingAmounts[1] = (lpTokenAmount * reserves[1]) / lpTokenSupply; -// }; - -// /** -// * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. -// * assumes that reserve_j is being swapped for other reserves in the Well. -// * @dev used by Beanstalk to calculate the deltaB every Season -// * @param reserves The reserves of the Well -// * @param j The index of the reserve to solve for -// * @param ratios The ratios of reserves to solve for -// * @param data Well function data provided on every call -// * @return reserve The resulting reserve at the jth index -// */ -// function calcReserveAtRatioSwap( -// uint256[] calldata reserves, -// uint256 j, -// uint256[] calldata ratios, -// bytes calldata data -// ) external view returns (uint256 reserve); - -// /** -// * @inheritdoc IMultiFlowPumpWellFunction -// * @dev Implmentation from: https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L460 -// */ -// function calcRate( -// uint256[] calldata reserves, -// uint256 i, -// uint256 j, -// bytes calldata data -// ) external view returns (uint256 rate) { -// uint256[] memory _reserves = reserves; -// uint256 xy = _k(_reserves[0], _reserves[1]); -// _reserves[0] = (_reserves[0] * PRECISION) / decimals0; -// _reserves[1] = (_reserves[1] * PRECISION) / decimals1; -// (uint256 reserveA, uint256 reserveB) = tokenIn == token0 ? (_reserve0, _reserve1) : (_reserve1, _reserve0); -// amountIn = tokenIn == token0 ? (amountIn * PRECISION) / decimals0 : (amountIn * PRECISION) / decimals1; -// uint256 y = reserveB - _get_y(amountIn + reserveA, xy, reserveB); -// return (y * (tokenIn == token0 ? decimals1 : decimals0)) / PRECISION; -// }; - -// /** -// * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. -// * assumes that reserve_j is being added or removed in exchange for LP Tokens. -// * @dev used by Beanstalk to calculate the max deltaB that can be converted in/out of a Well. -// * @param reserves The reserves of the Well -// * @param j The index of the reserve to solve for -// * @param ratios The ratios of reserves to solve for -// * @param data Well function data provided on every call -// * @return reserve The resulting reserve at the jth index -// */ -// function calcReserveAtRatioLiquidity( -// uint256[] calldata reserves, -// uint256 j, -// uint256[] calldata ratios, -// bytes calldata data -// ) external view returns (uint256 reserve); - -// /** -// * @notice returns k, based on the reserves of x/y. -// * @param x the reserves of `x` -// * @param y the reserves of `y`. -// * -// * @dev Implmentation from: -// * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L315 -// */ -// function _k(uint256 x, uint256 y) internal view returns (uint256) { -// uint256 _x = (x * PRECISION) / decimals0; -// uint256 _y = (y * PRECISION) / decimals1; -// uint256 _a = (_x * _y) / PRECISION; -// uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); -// return (_a * _b) / PRECISION; // x3y+y3x >= k -// } - -// function name() external pure override returns (string memory) { -// return "Solidly-StableSwap"; -// } - -// function symbol() external pure override returns (string memory) { -// return "SSS2"; -// } -// } diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index e0c20b0b..8a53f7e8 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -1,241 +1,393 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.20; +pragma solidity ^0.8.17; -import {IMultiFlowPumpWellFunction} from "src/interfaces/IMultiFlowPumpWellFunction.sol"; +import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {LibMath} from "src/libraries/LibMath.sol"; import {SafeMath} from "oz/utils/math/SafeMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {Math} from "oz/utils/math/Math.sol"; -import {console} from "forge-std/console.sol"; +import "forge-std/console.sol"; +interface ILookupTable { + /** + * @notice the lookup table returns a series of data, given a price point: + * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. + * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. + * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. + * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. + * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param precision precision of reserve. + */ + struct PriceData { + uint256 highPrice; + uint256 highPriceI; + uint256 highPriceJ; + uint256 lowPrice; + uint256 lowPriceI; + uint256 lowPriceJ; + uint256 precision; + } + + // for liquidity, x stays the same, y changes (D changes) + // for swap, x changes, y changes, (D stays the same) + function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); + function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); + function getAParameter() external view returns (uint256); +} /** * @author Brean - * @title Stable pricing function for Wells with 2 tokens. - * developed by Solidly/Aerodome: https://github.com/aerodrome-finance/contracts + * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. + * developed by curve. * - * Stable2 Wells with 2 tokens use the formula: - * `d^2 = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` + * Stableswap Wells with 2 tokens use the formula: + * `4 * A * (b_0+b_1) + D = 4 * A * D + D^3/(4 * b_0 * b_1)` * * Where: - * `d` is the supply of LP tokens + * `A` is the Amplication parameter. + * `D` is the supply of LP tokens * `b_i` is the reserve at index `i` + * + * @dev Limited to tokens with a maximum of 18 decimals. */ -contract Stable2 is ProportionalLPToken2, IMultiFlowPumpWellFunction { - using Math for uint256; - uint256 constant PRECISION = 1e18; +contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { + struct PriceData { + uint256 targetPrice; + uint256 currentPrice; + uint256 maxStepSize; + ILookupTable.PriceData lutData; + } + + using LibMath for uint256; + using SafeMath for uint256; + + // 2 token Pool. + uint256 constant N = 2; + + // A precision + uint256 constant A_PRECISION = 100; + + // Precision that all pools tokens will be converted to. + uint256 constant POOL_PRECISION_DECIMALS = 18; + + // Calc Rate Precision. + uint256 constant CALC_RATE_PRECISION = 1e24; - mapping(address well => uint256 liquidityIndex) public liquidityIndexForWell; + // price Precision. + uint256 constant PRICE_PRECISION = 1e6; + + address immutable lookupTable; + uint256 immutable a; + + // Errors + error InvalidAParameter(uint256); + error InvalidTokens(); + error InvalidTokenDecimals(); + error InvalidLUT(); + + // Due to the complexity of `calcReserveAtRatioLiquidity` and `calcReserveAtRatioSwap`, + // a LUT table is used to reduce the complexity of the calculations on chain. + // the lookup table contract implements 3 functions: + // 1. getRatiosFromPriceLiquidity(uint256) -> PriceData memory + // 2. getRatiosFromPriceSwap(uint256) -> PriceData memory + // 3. getAParameter() -> uint256 + // Lookup tables are a function of the A parameter. + constructor(address lut) { + if (lut == address(0)) revert InvalidLUT(); + lookupTable = lut; + // a = ILookupTable(lut).getAParameter(); + a = 10; + } /** - * @notice Calculates the `j`th reserve given a list of `reserves` and `lpTokenSupply`. - * @param reserves A list of token reserves. The jth reserve will be ignored, but a placeholder must be provided. - * @param j The index of the reserve to solve for - * @param data Extra Well function data provided on every call - * @return reserve The resulting reserve at the jth index - * @dev Should round up to ensure that Well reserves are marginally higher to enforce calcLpTokenSupply(...) >= totalSupply() + * @notice Calculate the amount of LP tokens minted when adding liquidity. + * D invariant calculation in non-overflowing integer operations iteratively + * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) + * + * Converging solution: + * D[j+1] = (4 * A * sum(b_i) - (D[j] ** 3) / (4 * prod(b_i))) / (4 * A - 1) */ - function calcReserve( + function calcLpTokenSupply( uint256[] memory reserves, - uint256 j, - uint256, - bytes calldata data - ) external pure returns (uint256 reserve) { - uint256 i = j == 0 ? 1 : 0; - (uint256 decimals0, uint256 decimals1) = decodeWellData(data); - uint256 xy; - uint256 y = reserves[j]; - uint256 x0 = reserves[i]; - // if j is 0, swap decimals0 and decimals1 - if (j == 0) { - (decimals0, decimals1) = (decimals1, decimals0); - } - xy = _k(x0, y, decimals0, decimals1); - - for (uint256 l = 0; l < 255; l++) { - uint256 k = _f(reserves[i], j); - if (k < xy) { - // there are two cases where dy == 0 - // case 1: The y is converged and we find the correct answer - // case 2: _d(x0, y) is too large compare to (xy - k) and the rounding error - // screwed us. - // In this case, we need to increase y by 1 - uint256 dy = ((xy - k) * PRECISION) / _d(x0, y); - if (dy == 0) { - if (k == xy) { - // We found the correct answer. Return y - return y; - } - if (_k(x0, y + 1, decimals0, decimals1) > xy) { - // If _k(x0, y + 1) > xy, then we are close to the correct answer. - // There's no closer answer than y + 1 - return y + 1; - } - dy = 1; - } - y = y + dy; + bytes memory data + ) public view returns (uint256 lpTokenSupply) { + uint256[] memory decimals = decodeWellData(data); + // scale reserves to 18 decimals. + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + uint256 Ann = a * N * N * A_PRECISION; + + uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; + if (sumReserves == 0) return 0; + lpTokenSupply = sumReserves; + for (uint256 i = 0; i < 255; i++) { + uint256 dP = lpTokenSupply; + // If division by 0, this will be borked: only withdrawal will work. And that is good + dP = dP.mul(lpTokenSupply).div(scaledReserves[0].mul(N)); + dP = dP.mul(lpTokenSupply).div(scaledReserves[1].mul(N)); + uint256 prevReserves = lpTokenSupply; + lpTokenSupply = Ann.mul(sumReserves).div(A_PRECISION).add(dP.mul(N)).mul(lpTokenSupply).div( + Ann.sub(A_PRECISION).mul(lpTokenSupply).div(A_PRECISION).add(N.add(1).mul(dP)) + ); + // Equality with the precision of 1 + if (lpTokenSupply > prevReserves) { + if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; } else { - uint256 dy = ((k - xy) * PRECISION) / _d(x0, y); - if (dy == 0) { - if (k == xy || _f(x0, y - 1) < xy) { - // Likewise, if k == xy, we found the correct answer. - // If _f(x0, y - 1) < xy, then we are close to the correct answer. - // There's no closer answer than "y" - // It's worth mentioning that we need to find y where f(x0, y) >= xy - // As a result, we can't return y - 1 even it's closer to the correct answer - return y; - } - dy = 1; - } - y = y - dy; + if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; } } - revert("!y"); } /** - * @notice Gets the LP token supply given a list of reserves. - * @param reserves A list of token reserves - * @param data Extra Well function data provided on every call - * @return lpTokenSupply The resulting supply of LP tokens - * @dev Should round down to ensure so that the Well Token supply is marignally lower to enforce calcLpTokenSupply(...) >= totalSupply() - * @dev `s^2 = (b_0^3 * b_1) + (b_0 ^ 3 + b_1)` - * The further apart the reserve values, the greater the loss of precision in the `sqrt` function. + * @notice Calculate x[i] if one reduces D from being calculated for reserves to D + * Done by solving quadratic equation iteratively. + * x_1**2 + x_1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A) + * x_1**2 + b*x_1 = c + * x_1 = (x_1**2 + c) / (2*x_1 + b) + * @dev This function has a precision of +/- 1, + * which may round in favor of the well or the user. */ - function calcLpTokenSupply( + function calcReserve( uint256[] memory reserves, - bytes calldata data - ) external pure returns (uint256 lpTokenSupply) { - console.log("calcLpTokenSupply"); - (uint256 decimals0, uint256 decimals1) = decodeWellData(data); - console.log(_k(reserves[0], reserves[1], decimals0, decimals1).sqrt()); - return _k(reserves[0], reserves[1], decimals0, decimals1).sqrt(); + uint256 j, + uint256 lpTokenSupply, + bytes memory data + ) public view returns (uint256 reserve) { + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + // avoid stack too deep errors. + (uint256 c, uint256 b) = + getBandC(a * N * N * A_PRECISION, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]); + reserve = lpTokenSupply; + uint256 prevReserve; + + for (uint256 i; i < 255; ++i) { + prevReserve = reserve; + reserve = _calcReserve(reserve, b, c, lpTokenSupply); + // Equality with the precision of 1 + // scale reserve down to original precision + if (reserve > prevReserve) { + if (reserve - prevReserve <= 1) { + return reserve.div(10 ** (18 - decimals[j])); + } + } else { + if (prevReserve - reserve <= 1) { + return reserve.div(10 ** (18 - decimals[j])); + } + } + } + revert("did not find convergence"); } /** - * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. - * assumes that reserve_j is being swapped for other reserves in the Well. - * @dev used by Beanstalk to calculate the deltaB every Season - * @param reserves The reserves of the Well - * @param j The index of the reserve to solve for - * @param ratios The ratios of reserves to solve for - * @param data Well function data provided on every call - * @return reserve The resulting reserve at the jth index + * @inheritdoc IMultiFlowPumpWellFunction + * @dev `calcReserveAtRatioSwap` fetches the closes approxeimate ratios from the target price, + * and performs neuwtons method in order to */ function calcReserveAtRatioSwap( - uint256[] calldata reserves, + uint256[] memory reserves, uint256 j, - uint256[] calldata ratios, + uint256[] memory ratios, bytes calldata data - ) external view returns (uint256 reserve) {} + ) external view returns (uint256 reserve) { + uint256 i = j == 1 ? 0 : 1; + // scale reserves and ratios: + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + PriceData memory pd; + + { + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; + } + + // get ratios and price from the closest highest and lowest price from targetPrice: + pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceSwap(pd.targetPrice); + + // perform an initial update on the reserves, such that `calcRate(reserves, i, j, data) == pd.lutData.lowPrice. + + // calculate lp token supply: + uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + + // lpTokenSupply / 2 gives the reserves at parity: + uint256 parityReserve = lpTokenSupply / 2; + + // update `scaledReserves`. + scaledReserves[0] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; + scaledReserves[1] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; + + // calculate max step size: + pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; + + // initialize currentPrice: + pd.currentPrice = pd.lutData.lowPrice; + + for (uint256 k; k < 255; k++) { + // scale stepSize proporitional to distance from price: + uint256 stepSize = + pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + // increment reserve by stepSize: + scaledReserves[j] = reserves[j] + stepSize; + // calculate scaledReserve[i]: + scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + + // check if new price is within 1 of target price: + if (pd.currentPrice > pd.targetPrice) { + if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } else { + if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } + + // calc currentPrice: + pd.currentPrice = calcRate(reserves, i, j, data); + } + } /** * @inheritdoc IMultiFlowPumpWellFunction - * @notice Calculates the exchange rate of the Well. - * @dev used by Beanstalk to calculate the exchange rate of the Well. - * The maximum value of each reserves cannot exceed max(uint128)/ 2. - * Returns with 18 decimal precision. + * @dev Returns a rate with 6 decimal precision. + * Requires a minimum scaled reserves of 1e12. + * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, + * which is prohibtive for large value tokens. */ function calcRate( - uint256[] calldata reserves, + uint256[] memory reserves, uint256 i, uint256 j, bytes calldata data - ) external pure returns (uint256 rate) { - (uint256 precision0, uint256 precision1) = decodeWellData(data); - if (i == 1) { - (precision0, precision1) = (precision1, precision0); - } - uint256 reservesI = uint256(reserves[i]) * precision0; - uint256 reservesJ = uint256(reserves[j]) * precision1; - rate = reservesJ.mulDiv(_g(reservesI, reservesJ), _g(reservesJ, reservesI)).mulDiv(PRECISION, reservesI); + ) public view returns (uint256 rate) { + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). + uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + + // reverse if i is not 0. + + // add 1e6 to reserves: + scaledReserves[j] += PRICE_PRECISION; + + // calculate new reserve 1: + uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + rate = (scaledReserves[0] - new_reserve1); } /** - * @notice Calculates the `j` reserve such that `π_{i | i != j} (d reserves_j / d reserves_i) = π_{i | i != j}(ratios_j / ratios_i)`. - * assumes that reserve_j is being added or removed in exchange for LP Tokens. - * @dev used by Beanstalk to calculate the max deltaB that can be converted in/out of a Well. - * @param reserves The reserves of the Well - * @param j The index of the reserve to solve for - * @param ratios The ratios of reserves to solve for - * @return reserve The resulting reserve at the jth index + * @inheritdoc IBeanstalkWellFunction + * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. + * @dev `calcReserveAtRatioLiquidity` fetches the closest approximate ratios from the target price, and + * perform an neutonian-estimation to calculate the reserves. */ function calcReserveAtRatioLiquidity( uint256[] calldata reserves, uint256 j, uint256[] calldata ratios, - bytes calldata - ) external pure returns (uint256 reserve) { - // TODO - // start at current reserves, increases reserves j until the ratio is correct - } + bytes calldata data + ) external view returns (uint256 reserve) { + uint256 i = j == 1 ? 0 : 1; + // scale reserves and ratios: + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - /** - * @notice returns k, based on the reserves of x/y. - * @param x the reserves of `x` - * @param y the reserves of `y`. - * - * @dev Implmentation from: - * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L315 - */ - function _k(uint256 x, uint256 y, uint256 xDecimals, uint256 yDecimals) internal pure returns (uint256) { - console.log("x: %s, y: %s", x, y); - // scale x and y to 18 decimals - uint256 _x = (x * PRECISION) / xDecimals; - uint256 _y = (y * PRECISION) / yDecimals; - uint256 _a = (_x * _y) / PRECISION; - uint256 _b = ((_x * _x) / PRECISION + (_y * _y) / PRECISION); - console.log("k: %s", (_a * _b) / PRECISION); - return (_a * _b) / PRECISION; // x3y+y3x >= k + PriceData memory pd; + { + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; + } + + // calc currentPrice: + pd.currentPrice = calcRate(reserves, i, j, data); + + // get ratios and price from the closest highest and lowest price from targetPrice: + pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); + + // update scaledReserve[j] based on lowPrice: + scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; + + // calculate max step size: + pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.lowPriceJ; + + for (uint256 k; k < 255; k++) { + // scale stepSize proporitional to distance from price: + uint256 stepSize = + pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + // increment reserve by stepSize: + scaledReserves[j] = scaledReserves[j] + stepSize; + // calculate new price from reserves: + pd.currentPrice = calcRate(scaledReserves, i, j, data); + + // check if new price is within 1 of target price: + if (pd.currentPrice > pd.targetPrice) { + if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } else { + if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + } + } } - /** - * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L401C5-L405C6 - */ - function _f(uint256 x0, uint256 y) internal pure returns (uint256) { - uint256 _a = (x0 * y) / PRECISION; - uint256 _b = ((x0 * x0) / PRECISION + (y * y) / PRECISION); - return (_a * _b) / PRECISION; + function name() external pure returns (string memory) { + return "StableSwap"; } - /** - * https://github.com/aerodrome-finance/contracts/blob/main/contracts/Pool.sol#L401C5-L405C6 - */ - function _d(uint256 x0, uint256 y) internal pure returns (uint256) { - return (3 * x0 * ((y * y) / PRECISION)) / PRECISION + ((((x0 * x0) / PRECISION) * x0) / PRECISION); + function symbol() external pure returns (string memory) { + return "SS2"; } /** - * @notice returns g(x, y) = 3x^2 + y^2 - * @dev used in `calcRate` to calculate the exchange rate of the well. + * @notice decodes the data encoded in the well. + * @return decimals an array of the decimals of the tokens in the well. */ - function _g(uint256 x, uint256 y) internal pure returns (uint256) { - return (3 * x ** 2) + y ** 2; + function decodeWellData(bytes memory data) public view virtual returns (uint256[] memory decimals) { + (uint256 decimal0, uint256 decimal1) = abi.decode(data, (uint256, uint256)); + + // if well data returns 0, assume 18 decimals. + if (decimal0 == 0) { + decimal0 = 18; + } + if (decimal0 == 0) { + decimal1 = 18; + } + if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals(); + + decimals = new uint256[](2); + decimals[0] = decimal0; + decimals[1] = decimal1; } /** - * @notice The stableswap requires 2 parameters to be encoded in the well: - * Incorrect encoding may result in incorrect calculations. - * @dev tokens with more than 18 decimals are not supported. - * @return precision0 precision of token0 - * @return precision1 precision of token1 - * + * @notice scale `reserves` by `precision`. + * @dev this sets both reserves to 18 decimals. */ - function decodeWellData(bytes calldata data) public pure returns (uint256 precision0, uint256 precision1) { - (precision0, precision1) = abi.decode(data, (uint256, uint256)); - console.log(precision0, precision1); - precision0 = 10 ** (precision0); - precision1 = 10 ** (precision1); - console.log(precision0, precision1); + function getScaledReserves( + uint256[] memory reserves, + uint256[] memory decimals + ) internal pure returns (uint256[] memory scaledReserves) { + scaledReserves = new uint256[](2); + scaledReserves[0] = reserves[0] * 10 ** (18 - decimals[0]); + scaledReserves[1] = reserves[1] * 10 ** (18 - decimals[1]); } - function name() external pure override returns (string memory) { - return "Stable2"; + function _calcReserve( + uint256 reserve, + uint256 b, + uint256 c, + uint256 lpTokenSupply + ) private pure returns (uint256) { + return reserve.mul(reserve).add(c).div(reserve.mul(2).add(b).sub(lpTokenSupply)); } - function symbol() external pure override returns (string memory) { - return "S2"; + function getBandC( + uint256 Ann, + uint256 lpTokenSupply, + uint256 reserves + ) private pure returns (uint256 c, uint256 b) { + c = lpTokenSupply.mul(lpTokenSupply).div(reserves.mul(N)).mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); + b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); } } diff --git a/test/Stable2/Well.BoreStableSwap.t.sol b/test/Stable2/Well.BoreStableSwap.t.sol index 42b0179a..8851b834 100644 --- a/test/Stable2/Well.BoreStableSwap.t.sol +++ b/test/Stable2/Well.BoreStableSwap.t.sol @@ -3,12 +3,11 @@ pragma solidity ^0.8.17; import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; import {MockPump} from "mocks/pumps/MockPump.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; contract WellBoreStableSwapTest is TestHelper { - /// @dev Bore a 2-token Well with CurveStableSwap2 & several pumps. + /// @dev Bore a 2-token Well with Stable2 & several pumps. function setUp() public { - // setup a StableSwap Well with an A parameter of 10. setupStableSwapWell(); // Well.sol doesn't use wellData, so it should always return empty bytes wellData = new bytes(0); @@ -28,7 +27,7 @@ contract WellBoreStableSwapTest is TestHelper { assertEq(well.pumps(), pumps); } - function test_wellData() public { + function test_wellData() public view { assertEq(well.wellData(), wellData); } @@ -88,7 +87,7 @@ contract WellBoreStableSwapTest is TestHelper { bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); // Deploy a Well Function - wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionBytes); + wellFunction = Call(address(new Stable2(address(1))), wellFunctionBytes); // Etch the MockPump at each `target` Call[] memory pumps = new Call[](numberOfPumps); diff --git a/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol index 8bcbf35d..4bd0ad2c 100644 --- a/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; @@ -13,12 +13,12 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { bytes _data; // Setup - CurveStableSwap2 ss; + Stable2 ss; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); _data = abi.encode(18, 18); diff --git a/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol index ab5032c7..d19b900b 100644 --- a/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); - CurveStableSwap2 ss; + Stable2 ss; uint256 constant addedLiquidity = 1000 * 1e18; bytes _data; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens @@ -21,7 +21,7 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { _data = abi.encode(18, 18); } - /// @dev Assumes use of CurveStableSwap2 + /// @dev Assumes use of Stable2 function test_getRemoveLiquidityOneTokenOut() public { uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); diff --git a/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol index 4f34da94..5c67aa6a 100644 --- a/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { - CurveStableSwap2 ss; + Stable2 ss; bytes constant data = ""; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens @@ -100,8 +100,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { afterRemoveLiquidity(before, action); assertLe( - well.totalSupply(), - CurveStableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) + well.totalSupply(), Stable2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) ); checkInvariant(address(well)); } diff --git a/test/Stable2/Well.ShiftStable.t.sol b/test/Stable2/Well.ShiftStable.t.sol index 0ea78807..e9a7adc3 100644 --- a/test/Stable2/Well.ShiftStable.t.sol +++ b/test/Stable2/Well.ShiftStable.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, Balances, ConstantProduct2, IERC20, CurveStableSwap2} from "test/TestHelper.sol"; +import {TestHelper, Balances, ConstantProduct2, IERC20, Stable2} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 282045d3..ae7f6ee7 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -13,7 +13,7 @@ import {Users} from "test/helpers/Users.sol"; import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; @@ -135,10 +135,10 @@ abstract contract TestHelper is Test, WellDeployer { // encode wellFunction Data bytes memory wellFunctionData = abi.encode(MockToken(address(_tokens[0])).decimals(), MockToken(address(_tokens[1])).decimals()); - Call memory _wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionData); + Call memory _wellFunction = Call(address(new Stable2(address(1))), wellFunctionData); tokens = _tokens; wellFunction = _wellFunction; - vm.label(address(wellFunction.target), "CurveStableSwap2 WF"); + vm.label(address(wellFunction.target), "Stable2 WF"); for (uint256 i = 0; i < _pumps.length; i++) { pumps.push(_pumps[i]); } @@ -148,7 +148,7 @@ abstract contract TestHelper is Test, WellDeployer { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); well = encodeAndBoreWell(address(aquifer), wellImplementation, tokens, _wellFunction, _pumps, bytes32(0)); - vm.label(address(well), "CurveStableSwap2Well"); + vm.label(address(well), "Stable2Well"); // Mint mock tokens to user mintTokens(user, initialLiquidity); @@ -316,11 +316,11 @@ abstract contract TestHelper is Test, WellDeployer { //////////// Assertions //////////// - function assertEq(IERC20 a, IERC20 b) internal { + function assertEq(IERC20 a, IERC20 b) internal pure { assertEq(a, b, "Address mismatch"); } - function assertEq(IERC20 a, IERC20 b, string memory err) internal { + function assertEq(IERC20 a, IERC20 b, string memory err) internal pure { assertEq(address(a), address(b), err); } @@ -328,7 +328,7 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "IERC20[] mismatch"); } - function assertEq(IERC20[] memory a, IERC20[] memory b, string memory err) internal { + function assertEq(IERC20[] memory a, IERC20[] memory b, string memory err) internal pure { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload @@ -339,7 +339,7 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a, b, "Call mismatch"); } - function assertEq(Call memory a, Call memory b, string memory err) internal { + function assertEq(Call memory a, Call memory b, string memory err) internal pure { assertEq(a.target, b.target, err); assertEq(a.data, b.data, err); } @@ -359,7 +359,7 @@ abstract contract TestHelper is Test, WellDeployer { assertApproxEqRelN(a, b, 1, precision); } - function assertApproxLeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { + function assertApproxLeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal pure { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -380,7 +380,7 @@ abstract contract TestHelper is Test, WellDeployer { } } - function assertApproxGeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal { + function assertApproxGeRelN(uint256 a, uint256 b, uint256 precision, uint256 absoluteError) internal pure { console.log("A: %s", a); console.log("B: %s", b); console.log(precision); @@ -433,7 +433,7 @@ abstract contract TestHelper is Test, WellDeployer { snapshot.reserves = well.getReserves(); } - function checkInvariant(address _well) internal { + function checkInvariant(address _well) internal view { uint256[] memory _reserves = IWell(_well).getReserves(); Call memory _wellFunction = IWell(_well).wellFunction(); assertLe( @@ -443,7 +443,7 @@ abstract contract TestHelper is Test, WellDeployer { ); } - function checkStableSwapInvariant(address _well) internal { + function checkStableSwapInvariant(address _well) internal view { uint256[] memory _reserves = IWell(_well).getReserves(); Call memory _wellFunction = IWell(_well).wellFunction(); assertApproxEqAbs( diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol index 44707976..76aa7d78 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.20; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; /// @dev Tests the {ConstantProduct2} Well function directly. -contract CurveStableSwap2LiquidityTest is TestHelper { +contract Stable2LiquidityTest is TestHelper { IBeanstalkWellFunction _f; bytes data; //////////// SETUP //////////// function setUp() public { - _f = new CurveStableSwap2(address(1)); + _f = new Stable2(address(1)); deployMockTokens(2); data = abi.encode(18, 18); } diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol index 27ca82d0..379dfdac 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.17; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; /// @dev Tests the {ConstantProduct2} Well function directly. @@ -13,7 +13,7 @@ contract BeanstalkStableSwapSwapTest is TestHelper { //////////// SETUP //////////// function setUp() public { - _f = new CurveStableSwap2(address(1)); + _f = new Stable2(address(1)); IERC20[] memory _token = deployMockTokens(2); data = abi.encode(10, address(_token[0]), address(_token[1])); } diff --git a/test/functions/StableSwap.t.sol b/test/functions/StableSwap.t.sol index 195dfde8..69794180 100644 --- a/test/functions/StableSwap.t.sol +++ b/test/functions/StableSwap.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; import {WellFunctionHelper, IMultiFlowPumpWellFunction} from "./WellFunctionHelper.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; -/// @dev Tests the {CurveStableSwap2} Well function directly. +/// @dev Tests the {Stable2} Well function directly. contract CurveStableSwapTest is WellFunctionHelper { /** * State A: Same decimals @@ -44,7 +44,7 @@ contract CurveStableSwapTest is WellFunctionHelper { function setUp() public { IERC20[] memory _tokens = deployMockTokens(2); tokens = _tokens; - _function = IMultiFlowPumpWellFunction(new CurveStableSwap2(address(1))); + _function = IMultiFlowPumpWellFunction(new Stable2(address(1))); // encode well data with: // A parameter of 10, diff --git a/test/libraries/LibMath.t.sol b/test/libraries/LibMath.t.sol index 30c1ca4f..2ef8086f 100644 --- a/test/libraries/LibMath.t.sol +++ b/test/libraries/LibMath.t.sol @@ -47,39 +47,39 @@ contract LibMathTest is TestHelper { //////////// SQRT //////////// /// @dev zero case - function testSqrt0() public { + function testSqrt0() public pure { assertEq(LibMath.sqrt(0), 0); } /// @dev perfect square case, small number - function testSqrtPerfectSmall() public { + function testSqrtPerfectSmall() public pure { assertEq(LibMath.sqrt(4), 2); } /// @dev perfect square case, large number /// 4e6 = sqrt(1.6e13) - function testSqrtPerfectLarge() public { + function testSqrtPerfectLarge() public pure { assertEq(LibMath.sqrt(16 * 1e12), 4 * 1e6); } /// @dev imperfect square case, small number with decimal < 0.5 - function testSqrtImperfectSmallLt() public { + function testSqrtImperfectSmallLt() public pure { assertEq(LibMath.sqrt(2), 1); // rounds down from 1.414... } /// @dev imperfect square case, large number with decimal < 0.5 - function testSqrtImperfectLargeLt() public { + function testSqrtImperfectLargeLt() public pure { assertEq(LibMath.sqrt(1250 * 1e6), 35_355); // rounds down from 35355.339... } /// @dev imperfect square case, small number with decimal >= 0.5 - function testSqrtImperfectSmallGte() public { + function testSqrtImperfectSmallGte() public pure { assertEq(LibMath.sqrt(3), 1); // rounds down from 1.732... } /// @dev imperfect square case, small number with decimal >= 0.5 /// 2828427124 = sqrt(8e18) - function testSqrtImperfectLargeGte() public { + function testSqrtImperfectLargeGte() public pure { assertEq(LibMath.sqrt(8 * 1e18), 2_828_427_124); // rounds down from 2.828...e9 } @@ -89,7 +89,7 @@ contract LibMathTest is TestHelper { LibMath.roundUpDiv(1, 0); } - function test_roundUpDiv() public { + function test_roundUpDiv() public pure { assertEq(LibMath.roundUpDiv(1, 3), 1); assertEq(LibMath.roundUpDiv(1, 2), 1); assertEq(LibMath.roundUpDiv(2, 3), 1); diff --git a/test/stableSwap/Well.BoreStableSwap.t.sol b/test/stableSwap/Well.BoreStableSwap.t.sol index 42b0179a..9c4f2659 100644 --- a/test/stableSwap/Well.BoreStableSwap.t.sol +++ b/test/stableSwap/Well.BoreStableSwap.t.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.17; import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; import {MockPump} from "mocks/pumps/MockPump.sol"; -import {CurveStableSwap2} from "src/functions/CurveStableSwap2.sol"; +import {Stable2} from "src/functions/Stable2.sol"; contract WellBoreStableSwapTest is TestHelper { - /// @dev Bore a 2-token Well with CurveStableSwap2 & several pumps. + /// @dev Bore a 2-token Well with Stable2 & several pumps. function setUp() public { // setup a StableSwap Well with an A parameter of 10. setupStableSwapWell(); @@ -88,7 +88,7 @@ contract WellBoreStableSwapTest is TestHelper { bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); // Deploy a Well Function - wellFunction = Call(address(new CurveStableSwap2(address(1))), wellFunctionBytes); + wellFunction = Call(address(new Stable2(address(1))), wellFunctionBytes); // Etch the MockPump at each `target` Call[] memory pumps = new Call[](numberOfPumps); diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol index 8bcbf35d..4bd0ad2c 100644 --- a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; @@ -13,12 +13,12 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { bytes _data; // Setup - CurveStableSwap2 ss; + Stable2 ss; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); _data = abi.encode(18, 18); diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol index ab5032c7..d19b900b 100644 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol @@ -1,19 +1,19 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); - CurveStableSwap2 ss; + Stable2 ss; uint256 constant addedLiquidity = 1000 * 1e18; bytes _data; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens @@ -21,7 +21,7 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { _data = abi.encode(18, 18); } - /// @dev Assumes use of CurveStableSwap2 + /// @dev Assumes use of Stable2 function test_getRemoveLiquidityOneTokenOut() public { uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol index 4f34da94..5c67aa6a 100644 --- a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol @@ -1,18 +1,18 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, CurveStableSwap2, IERC20, Balances} from "test/TestHelper.sol"; +import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { - CurveStableSwap2 ss; + Stable2 ss; bytes constant data = ""; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new CurveStableSwap2(address(1)); + ss = new Stable2(address(1)); setupStableSwapWell(); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens @@ -100,8 +100,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { afterRemoveLiquidity(before, action); assertLe( - well.totalSupply(), - CurveStableSwap2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) + well.totalSupply(), Stable2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) ); checkInvariant(address(well)); } diff --git a/test/stableSwap/Well.ShiftStable.t.sol b/test/stableSwap/Well.ShiftStable.t.sol index 0ea78807..e9a7adc3 100644 --- a/test/stableSwap/Well.ShiftStable.t.sol +++ b/test/stableSwap/Well.ShiftStable.t.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.17; -import {TestHelper, Balances, ConstantProduct2, IERC20, CurveStableSwap2} from "test/TestHelper.sol"; +import {TestHelper, Balances, ConstantProduct2, IERC20, Stable2} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; From e5cd49094ec7acb8bde1e9af7d106250a03a836d Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 9 Jul 2024 17:18:46 +0200 Subject: [PATCH 24/69] priceReserveMapping2 --- src/functions/PriceReserveMapping2.sol | 506 +++++++++++++++++++++++++ 1 file changed, 506 insertions(+) create mode 100644 src/functions/PriceReserveMapping2.sol diff --git a/src/functions/PriceReserveMapping2.sol b/src/functions/PriceReserveMapping2.sol new file mode 100644 index 00000000..a498b976 --- /dev/null +++ b/src/functions/PriceReserveMapping2.sol @@ -0,0 +1,506 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +/** + * @title PriceReserveMapping + * @author DeadmanWalking + * @notice The `Stable2` Well Function implementation of + * `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` requires + * multiple calculations of the price given a change in reserves. + * Performing the calculation on-chain is expensive, and thus a LUT is implemented + * to decrease the computation done on execution. + * + * @dev given `price`, returns the ratio of reserves needed for the pool to return + * `price` when calling `calcRate`, scaled to 100e18. + * Example: + * p = 1.011271030257560703e18, + * r = 698337_296093749599797248 + * reserve[0] = 1_000_000e6 + * reserve[1] = 698337_296093 + */ +contract PriceReserveMapping { + function getReserve(uint256 price) external pure returns (uint256) { + if (price < 1.000033716578442112e18) { + if (price < 830_357_766_063_866_624) { + if (price < 608_750_007_429_268_480) { + if (price < 497_147_688_282_685_696) { + if (price < 441_195_594_527_224_000) { + if (price < 421_290_035_178_414_208) { + if (price < 404_517_292_279_625_088) { + return 7_039_988_712_124_655_530_409_984; + } else { + return 7_039_988_712_124_655_530_409_984; + } + } else { + if (price < 422_561_897_881_033_792) { + return 6_704_751_154_404_433_787_355_136; + } else { + return 6_704_751_154_404_433_787_355_136; + } + } + } else { + if (price < 460_406_209_346_677_952) { + if (price < 458_149_633_111_917_824) { + return 6_115_909_044_841_460_461_993_984; + } else { + return 6_115_909_044_841_460_461_993_984; + } + } else { + if (price < 480_176_259_556_639_872) { + return 5_791_816_135_971_867_467_972_608; + } else { + return 5_791_816_135_971_867_467_972_608; + } + } + } + } else { + if (price < 542_576_156_450_906_944) { + if (price < 521_294_640_197_852_672) { + if (price < 500_482_361_488_375_424) { + return 5_516_015_367_592_254_935_924_736; + } else { + return 5_516_015_367_592_254_935_924_736; + } + } else { + if (price < 538_091_395_837_277_056) { + return 5_054_470_284_992_941_802_913_792; + } else { + return 5_054_470_284_992_941_802_913_792; + } + } + } else { + if (price < 580_686_644_101_094_656) { + if (price < 564_282_371_756_721_024) { + return 4_764_941_468_603_609_620_938_752; + } else { + return 4_764_941_468_603_609_620_938_752; + } + } else { + if (price < 586_360_676_154_978_944) { + return 4_538_039_493_908_200_048_033_792; + } else { + return 4_538_039_493_908_200_048_033_792; + } + } + } + } + } else { + if (price < 722_615_518_283_425_408) { + if (price < 669_067_822_553_920_512) { + if (price < 631_380_594_036_478_848) { + if (price < 624_523_709_284_514_688) { + return 4_177_248_169_415_654_040_928_256; + } else { + return 4_177_248_169_415_654_040_928_256; + } + } else { + if (price < 654_173_856_946_462_592) { + return 3_920_129_138_458_653_678_895_104; + } else { + return 3_920_129_138_458_653_678_895_104; + } + } + } else { + if (price < 699_890_877_274_508_032) { + if (price < 677_042_507_344_532_224) { + return 3_733_456_322_341_575_008_976_896; + } else { + return 3_733_456_322_341_575_008_976_896; + } + } else { + if (price < 713_658_133_485_124_864) { + return 3_452_271_214_393_102_144_897_024; + } else { + return 3_452_271_214_393_102_144_897_024; + } + } + } + } else { + if (price < 788_916_974_369_642_752) { + if (price < 757_518_945_604_179_584) { + if (price < 745_106_098_488_377_088) { + return 3_225_099_943_713_702_223_544_320; + } else { + return 3_225_099_943_713_702_223_544_320; + } + } else { + if (price < 767_246_620_828_971_264) { + return 3_071_523_755_917_812_229_472_256; + } else { + return 3_071_523_755_917_812_229_472_256; + } + } + } else { + if (price < 809_994_816_396_841_728) { + if (price < 799_786_725_808_037_248) { + return 2_853_116_706_110_001_328_947_200; + } else { + return 2_853_116_706_110_001_328_947_200; + } + } else { + return 2_785_962_590_401_643_029_725_184; + } + } + } + } + } else { + if (price < 972_989_115_515_289_216) { + if (price < 917_489_006_622_623_232) { + if (price < 875_936_112_087_689_344) { + if (price < 849_885_871_198_024_448) { + if (price < 839_554_943_435_977_728) { + return 2_593_742_460_100_001_012_908_032; + } else { + return 2_593_742_460_100_001_012_908_032; + } + } else { + if (price < 868_464_289_727_404_288) { + return 2_406_619_233_691_085_014_302_720; + } else { + return 2_406_619_233_691_085_014_302_720; + } + } + } else { + if (price < 902_355_196_812_224_000) { + if (price < 885_986_107_499_586_304) { + return 2_292_018_317_801_032_886_779_904; + } else { + return 2_292_018_317_801_032_886_779_904; + } + } else { + if (price < 908_136_713_928_969_216) { + return 2_143_588_810_000_000_484_376_576; + } else { + return 2_143_588_810_000_000_484_376_576; + } + } + } + } else { + if (price < 954_909_391_012_556_032) { + if (price < 935_536_737_788_984_832) { + if (price < 931_321_167_909_476_608) { + return 1_979_931_599_439_397_501_534_208; + } else { + return 1_979_931_599_439_397_501_534_208; + } + } else { + if (price < 943_803_797_433_811_072) { + return 1_885_649_142_323_235_971_399_680; + } else { + return 1_885_649_142_323_235_971_399_680; + } + } + } else { + if (price < 964_632_213_434_632_832) { + if (price < 957_762_568_730_056_576) { + return 1_771_561_000_000_000_284_950_528; + } else { + return 1_771_561_000_000_000_284_950_528; + } + } else { + return 1_710_339_358_116_313_758_171_136; + } + } + } + } else { + if (price < 996_430_298_345_225_728) { + if (price < 986_726_125_054_516_224) { + if (price < 980_019_737_867_874_432) { + if (price < 974_741_215_197_397_632) { + return 1_610_510_000_000_000_210_239_488; + } else { + return 1_610_510_000_000_000_210_239_488; + } + } else { + if (price < 985_786_093_464_194_432) { + return 1_477_455_443_789_062_678_249_472; + } else { + return 1_477_455_443_789_062_678_249_472; + } + } + } else { + if (price < 993_879_297_978_292_480) { + if (price < 990_371_554_230_599_424) { + return 1_407_100_422_656_250_003_587_072; + } else { + return 1_407_100_422_656_250_003_587_072; + } + } else { + if (price < 994_289_877_233_378_560) { + return 1_331_000_000_000_000_102_760_448; + } else { + return 1_331_000_000_000_000_102_760_448; + } + } + } + } else { + if (price < 999_768_290_809_212_032) { + if (price < 998_285_495_618_766_464) { + if (price < 998_160_960_147_888_768) { + return 1_215_506_250_000_000_018_808_832; + } else { + return 1_215_506_250_000_000_018_808_832; + } + } else { + if (price < 999_220_514_391_006_208) { + return 1_157_625_000_000_000_062_652_416; + } else { + return 1_157_625_000_000_000_062_652_416; + } + } + } else { + if (price < 999_970_981_689_446_016) { + if (price < 999_784_063_917_080_448) { + return 1_099_999_999_999_999_874_170_880; + } else { + return 1_099_999_999_999_999_874_170_880; + } + } else { + return 1_050_000_000_000_000_062_914_560; + } + } + } + } + } + } else { + if (price < 1_270_229_006_927_506_432) { + if (price < 1_042_410_779_328_363_904) { + if (price < 1_007_730_870_339_573_120) { + if (price < 1_002_138_979_681_074_944) { + if (price < 1_000_291_630_212_889_856) { + if (price < 1_000_269_233_009_443_840) { + return 902_500_000_000_000_015_728_640; + } else { + return 902_500_000_000_000_015_728_640; + } + } else { + if (price < 1_000_905_972_187_880_320) { + return 857_374_999_999_999_847_170_048; + } else { + return 857_374_999_999_999_847_170_048; + } + } + } else { + if (price < 1_004_157_377_550_525_696) { + if (price < 1_002_316_102_194_843_648) { + return 810_000_000_000_000_029_360_128; + } else { + return 810_000_000_000_000_029_360_128; + } + } else { + if (price < 1_007_143_263_844_912_000) { + return 735_091_890_624_999_614_054_400; + } else { + return 735_091_890_624_999_614_054_400; + } + } + } + } else { + if (price < 1_023_609_741_631_299_072) { + if (price < 1_016_707_057_698_784_896) { + if (price < 1_011_271_030_257_560_704) { + return 698_337_296_093_749_599_797_248; + } else { + return 698_337_296_093_749_599_797_248; + } + } else { + if (price < 1_018_070_626_816_667_904) { + return 656_100_000_000_000_049_283_072; + } else { + return 656_100_000_000_000_049_283_072; + } + } + } else { + if (price < 1_034_730_220_540_242_944) { + if (price < 1_032_129_795_364_543_616) { + return 598_736_939_238_378_581_262_336; + } else { + return 598_736_939_238_378_581_262_336; + } + } else { + return 590_490_000_000_000_030_932_992; + } + } + } + } else { + if (price < 1_134_730_371_284_194_048) { + if (price < 1_085_163_323_283_893_760) { + if (price < 1_058_971_528_414_115_200) { + if (price < 1_054_589_806_813_510_272) { + return 540_360_087_662_636_587_548_672; + } else { + return 540_360_087_662_636_587_548_672; + } + } else { + if (price < 1_068_798_380_970_533_888) { + return 513_342_083_279_504_818_569_216; + } else { + return 513_342_083_279_504_818_569_216; + } + } + } else { + if (price < 1_103_807_759_690_609_920) { + if (price < 1_091_946_638_369_647_232) { + return 478_296_900_000_000_063_307_776; + } else { + return 478_296_900_000_000_063_307_776; + } + } else { + if (price < 1_124_852_136_531_598_848) { + return 440_126_668_651_765_310_685_184; + } else { + return 440_126_668_651_765_310_685_184; + } + } + } + } else { + if (price < 1_203_570_602_035_602_432) { + if (price < 1_174_615_232_221_620_736) { + if (price < 1_148_415_244_275_700_096) { + return 418_120_335_219_177_014_951_936; + } else { + return 418_120_335_219_177_014_951_936; + } + } else { + if (price < 1_188_356_761_339_094_272) { + return 387_420_489_000_000_082_149_376; + } else { + return 387_420_489_000_000_082_149_376; + } + } + } else { + if (price < 1_253_855_901_740_080_128) { + if (price < 1_235_401_171_969_669_632) { + return 358_485_922_408_541_800_366_080; + } else { + return 358_485_922_408_541_800_366_080; + } + } else { + return 348_678_440_100_000_100_777_984; + } + } + } + } + } else { + if (price < 1_813_506_410_438_685_184) { + if (price < 1_532_552_739_400_572_928) { + if (price < 1_393_968_967_516_634_880) { + if (price < 1_332_289_125_721_440_768) { + if (price < 1_308_179_312_239_340_544) { + return 323_533_544_973_709_000_835_072; + } else { + return 323_533_544_973_709_000_835_072; + } + } else { + if (price < 1_349_381_291_133_012_224) { + return 307_356_867_725_023_544_082_432; + } else { + return 307_356_867_725_023_544_082_432; + } + } + } else { + if (price < 1_442_081_976_893_328_640) { + if (price < 1_424_781_668_356_408_832) { + return 282_429_536_481_000_091_025_408; + } else { + return 282_429_536_481_000_091_025_408; + } + } else { + if (price < 1_493_866_329_083_113_216) { + return 263_520_094_465_741_985_677_312; + } else { + return 263_520_094_465_741_985_677_312; + } + } + } + } else { + if (price < 1_672_818_539_081_097_216) { + if (price < 1_609_069_386_068_001_536) { + if (price < 1_549_475_146_998_422_528) { + return 250_344_089_742_454_896_459_776; + } else { + return 250_344_089_742_454_896_459_776; + } + } else { + if (price < 1_656_943_402_509_807_872) { + return 228_767_924_549_610_094_198_784; + } else { + return 228_767_924_549_610_094_198_784; + } + } + } else { + if (price < 1_799_442_892_480_155_648) { + if (price < 1_740_901_331_273_835_008) { + return 214_638_763_942_937_209_339_904; + } else { + return 214_638_763_942_937_209_339_904; + } + } else { + return 205_891_132_094_649_074_712_576; + } + } + } + } else { + if (price < 2_353_244_129_099_838_464) { + if (price < 2_060_505_206_109_642_752) { + if (price < 1_961_714_091_549_304_320) { + if (price < 1_890_833_036_735_312_384) { + return 193_711_484_458_500_800_643_072; + } else { + return 193_711_484_458_500_800_643_072; + } + } else { + if (price < 1_973_091_776_740_027_136) { + return 184_025_910_235_575_711_956_992; + } else { + return 184_025_910_235_575_711_956_992; + } + } + } else { + if (price < 2_153_308_625_067_387_136) { + if (price < 2_145_618_891_058_364_672) { + return 166_771_816_996_665_772_998_656; + } else { + return 166_771_816_996_665_772_998_656; + } + } else { + if (price < 2_251_750_790_753_840_896) { + return 157_779_214_788_226_736_717_824; + } else { + return 157_779_214_788_226_736_717_824; + } + } + } + } else { + if (price < 2_586_928_745_378_339_328) { + if (price < 2_466_618_218_597_512_192) { + if (price < 2_356_094_670_332_007_936) { + return 149_890_254_048_815_394_848_768; + } else { + return 149_890_254_048_815_394_848_768; + } + } else { + if (price < 2_583_615_183_726_152_192) { + return 135_275_954_279_055_877_996_544; + } else { + return 135_275_954_279_055_877_996_544; + } + } + } else { + if (price < 2_849_292_747_060_493_824) { + if (price < 2_707_395_944_693_278_720) { + return 128_512_156_565_103_074_869_248; + } else { + return 128_512_156_565_103_074_869_248; + } + } else { + return 121_576_654_590_569_365_897_216; + } + } + } + } + } + } + } +} From c897e7e99d0b85c7161f10e8ddc0787ca1769298 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 12 Jul 2024 10:28:32 +0200 Subject: [PATCH 25/69] remove unneeded tests, update format --- src/Well.sol | 263 +- src/functions/LookupTable.t.sol | 128 + src/functions/PriceReserveMapping2.sol | 506 --- src/functions/Stable2.sol | 68 +- src/functions/StableLUT/Stable2LUT1.sol | 2948 +++++++++++++++++ src/interfaces/ILookupTable.sol | 35 + src/interfaces/beanstalk/IBeanstalkA.sol | 16 - src/pumps/MultiFlowPump.sol | 4 +- ....RemoveLiquidityImbalancedStableSwap.t.sol | 195 -- .../Well.RemoveLiquidityStableSwap.t.sol | 141 - test/Stable2/Well.ShiftStable.t.sol | 160 - test/Stable2/Well.Stable2.AddLiquidity.t.sol | 4 +- ...ableSwap.t.sol => Well.Stable2.Bore.t.sol} | 22 +- .../Well.Stable2.RemoveLiquidity.t.sol} | 12 +- ...l.Stable2.RemoveLiquidityImbalanced.t.sol} | 12 +- ...ell.Stable2.RemoveLiquidityOneToken.t.sol} | 22 +- .../Well.Stable2.Shift.t.sol} | 4 +- ...ableSwap.t.sol => Well.Stable2.Skim.t.sol} | 4 +- .../Well.Stable2.SwapFrom.t.sol} | 6 +- .../Well.Stable2.SwapTo.t.sol} | 9 +- test/Stable2/Well.SwapFromStableSwap.t.sol | 101 - test/Stable2/Well.SwapToStableSwap.t.sol | 110 - test/TestHelper.sol | 19 +- test/Well.Shift.t.sol | 205 +- ...Stable2.calcReserveAtRatioLiquidity.t.sol} | 22 +- ...stalkStable2.calcReserveAtRatioSwap.t.sol} | 17 +- .../{StableSwap.t.sol => Stable2.t.sol} | 58 +- test/integration/IntegrationTestHelper.sol | 46 +- test/integration/interfaces/ICurve.sol | 95 +- test/libraries/TestABDK.t.sol | 5 +- test/pumps/Pump.CapReserves.t.sol | 6 +- test/pumps/Pump.Fuzz.t.sol | 2 - .../Well.AddLiquidityStableSwap.t.sol | 175 - test/stableSwap/Well.BoreStableSwap.t.sol | 126 - ...ll.RemoveLiquidityOneTokenStableSwap.t.sol | 130 - test/stableSwap/Well.SkimStableSwap.t.sol | 56 - 36 files changed, 3372 insertions(+), 2360 deletions(-) create mode 100644 src/functions/LookupTable.t.sol delete mode 100644 src/functions/PriceReserveMapping2.sol create mode 100644 src/functions/StableLUT/Stable2LUT1.sol create mode 100644 src/interfaces/ILookupTable.sol delete mode 100644 src/interfaces/beanstalk/IBeanstalkA.sol delete mode 100644 test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol delete mode 100644 test/Stable2/Well.RemoveLiquidityStableSwap.t.sol delete mode 100644 test/Stable2/Well.ShiftStable.t.sol rename test/Stable2/{Well.BoreStableSwap.t.sol => Well.Stable2.Bore.t.sol} (85%) rename test/{stableSwap/Well.RemoveLiquidityStableSwap.t.sol => Stable2/Well.Stable2.RemoveLiquidity.t.sol} (94%) rename test/{stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol => Stable2/Well.Stable2.RemoveLiquidityImbalanced.t.sol} (96%) rename test/Stable2/{Well.RemoveLiquidityOneTokenStableSwap.t.sol => Well.Stable2.RemoveLiquidityOneToken.t.sol} (90%) rename test/{stableSwap/Well.ShiftStable.t.sol => Stable2/Well.Stable2.Shift.t.sol} (99%) rename test/Stable2/{Well.SkimStableSwap.t.sol => Well.Stable2.Skim.t.sol} (96%) rename test/{stableSwap/Well.SwapFromStableSwap.t.sol => Stable2/Well.Stable2.SwapFrom.t.sol} (96%) rename test/{stableSwap/Well.SwapToStableSwap.t.sol => Stable2/Well.Stable2.SwapTo.t.sol} (95%) delete mode 100644 test/Stable2/Well.SwapFromStableSwap.t.sol delete mode 100644 test/Stable2/Well.SwapToStableSwap.t.sol rename test/beanstalk/{CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol => BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol} (91%) rename test/beanstalk/{CurveStableSwap2.calcReserveAtRatioSwap.t.sol => BeanstalkStable2.calcReserveAtRatioSwap.t.sol} (83%) rename test/functions/{StableSwap.t.sol => Stable2.t.sol} (80%) delete mode 100644 test/stableSwap/Well.AddLiquidityStableSwap.t.sol delete mode 100644 test/stableSwap/Well.BoreStableSwap.t.sol delete mode 100644 test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol delete mode 100644 test/stableSwap/Well.SkimStableSwap.t.sol diff --git a/src/Well.sol b/src/Well.sol index fc51a3e9..4207a0b2 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -32,29 +32,19 @@ import {ClonePlus} from "src/utils/ClonePlus.sol"; * - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity), * INCLUDE the fee that is taken on transfer when calculating amount out values. */ -contract Well is - ERC20PermitUpgradeable, - IWell, - IWellErrors, - ReentrancyGuardUpgradeable, - ClonePlus -{ +contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgradeable, ClonePlus { using SafeERC20 for IERC20; uint256 private constant PACKED_ADDRESS = 20; uint256 private constant ONE_WORD_PLUS_PACKED_ADDRESS = 52; // For gas efficiency purposes - bytes32 private constant RESERVES_STORAGE_SLOT = - 0x4bba01c388049b5ebd30398b65e8ad45b632802c5faf4964e58085ea8ab03715; // bytes32(uint256(keccak256("reserves.storage.slot")) - 1); + bytes32 private constant RESERVES_STORAGE_SLOT = 0x4bba01c388049b5ebd30398b65e8ad45b632802c5faf4964e58085ea8ab03715; // bytes32(uint256(keccak256("reserves.storage.slot")) - 1); constructor() { // Disable Initializers to prevent the init function from being callable on the implementation contract _disableInitializers(); } - function init( - string memory _name, - string memory _symbol - ) external initializer { + function init(string memory _name, string memory _symbol) external initializer { __ERC20Permit_init(_name); __ERC20_init(_name, _symbol); __ReentrancyGuard_init(); @@ -119,10 +109,7 @@ contract Well is function wellFunction() public pure returns (Call memory _wellFunction) { _wellFunction.target = wellFunctionAddress(); - _wellFunction.data = _getArgBytes( - LOC_VARIABLE + numberOfTokens() * ONE_WORD, - wellFunctionDataLength() - ); + _wellFunction.data = _getArgBytes(LOC_VARIABLE + numberOfTokens() * ONE_WORD, wellFunctionDataLength()); } function pumps() public pure returns (Call[] memory _pumps) { @@ -130,10 +117,7 @@ contract Well is if (_numberOfPumps == 0) return _pumps; _pumps = new Call[](_numberOfPumps); - uint256 dataLoc = LOC_VARIABLE + - numberOfTokens() * - ONE_WORD + - wellFunctionDataLength(); + uint256 dataLoc = LOC_VARIABLE + numberOfTokens() * ONE_WORD + wellFunctionDataLength(); uint256 pumpDataLength; for (uint256 i; i < _pumps.length; ++i) { @@ -209,15 +193,9 @@ contract Well is * @dev Provided as an optimization in the case where {numberOfPumps} returns 1. */ function firstPump() public pure returns (Call memory _pump) { - uint256 dataLoc = LOC_VARIABLE + - numberOfTokens() * - ONE_WORD + - wellFunctionDataLength(); + uint256 dataLoc = LOC_VARIABLE + numberOfTokens() * ONE_WORD + wellFunctionDataLength(); _pump.target = _getArgAddress(dataLoc); - _pump.data = _getArgBytes( - dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, - _getArgUint256(dataLoc + PACKED_ADDRESS) - ); + _pump.data = _getArgBytes(dataLoc + ONE_WORD_PLUS_PACKED_ADDRESS, _getArgUint256(dataLoc + PACKED_ADDRESS)); } //////////////////// SWAP: FROM //////////////////// @@ -235,13 +213,7 @@ contract Well is uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 amountOut) { fromToken.safeTransferFrom(msg.sender, address(this), amountIn); - amountOut = _swapFrom( - fromToken, - toToken, - amountIn, - minAmountOut, - recipient - ); + amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); } /** @@ -257,18 +229,8 @@ contract Well is address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 amountOut) { - amountIn = _safeTransferFromFeeOnTransfer( - fromToken, - msg.sender, - amountIn - ); - amountOut = _swapFrom( - fromToken, - toToken, - amountIn, - minAmountOut, - recipient - ); + amountIn = _safeTransferFromFeeOnTransfer(fromToken, msg.sender, amountIn); + amountOut = _swapFrom(fromToken, toToken, amountIn, minAmountOut, recipient); } function _swapFrom( @@ -313,9 +275,7 @@ contract Well is reserves[i] += amountIn; // underflow is desired; Well Function SHOULD NOT increase reserves of both `i` and `j` - amountOut = - reserves[j] - - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); } //////////////////// SWAP: TO //////////////////// @@ -384,9 +344,7 @@ contract Well is reserves[j] -= amountOut; - amountIn = - _calcReserve(wellFunction(), reserves, i, totalSupply()) - - reserves[i]; + amountIn = _calcReserve(wellFunction(), reserves, i, totalSupply()) - reserves[i]; } //////////////////// SHIFT //////////////////// @@ -438,9 +396,7 @@ contract Well is reserves[i] = _tokens[i].balanceOf(address(this)); } uint256 j = _getJ(_tokens, tokenOut); - amountOut = - reserves[j] - - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); if (amountOut >= minAmountOut) { tokenOut.safeTransfer(recipient, amountOut); @@ -452,9 +408,7 @@ contract Well is } } - function getShiftOut( - IERC20 tokenOut - ) external view readOnlyNonReentrant returns (uint256 amountOut) { + function getShiftOut(IERC20 tokenOut) external view readOnlyNonReentrant returns (uint256 amountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = new uint256[](tokensLength); @@ -463,9 +417,7 @@ contract Well is } uint256 j = _getJ(_tokens, tokenOut); - amountOut = - reserves[j] - - _calcReserve(wellFunction(), reserves, j, totalSupply()); + amountOut = reserves[j] - _calcReserve(wellFunction(), reserves, j, totalSupply()); } //////////////////// ADD LIQUIDITY //////////////////// @@ -476,12 +428,7 @@ contract Well is address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 lpAmountOut) { - lpAmountOut = _addLiquidity( - tokenAmountsIn, - minLpAmountOut, - recipient, - false - ); + lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, false); } function addLiquidityFeeOnTransfer( @@ -490,12 +437,7 @@ contract Well is address recipient, uint256 deadline ) external nonReentrant expire(deadline) returns (uint256 lpAmountOut) { - lpAmountOut = _addLiquidity( - tokenAmountsIn, - minLpAmountOut, - recipient, - true - ); + lpAmountOut = _addLiquidity(tokenAmountsIn, minLpAmountOut, recipient, true); } /** @@ -516,11 +458,7 @@ contract Well is for (uint256 i; i < tokensLength; ++i) { _tokenAmountIn = tokenAmountsIn[i]; if (_tokenAmountIn == 0) continue; - _tokenAmountIn = _safeTransferFromFeeOnTransfer( - _tokens[i], - msg.sender, - _tokenAmountIn - ); + _tokenAmountIn = _safeTransferFromFeeOnTransfer(_tokens[i], msg.sender, _tokenAmountIn); reserves[i] += _tokenAmountIn; tokenAmountsIn[i] = _tokenAmountIn; } @@ -528,18 +466,12 @@ contract Well is for (uint256 i; i < tokensLength; ++i) { _tokenAmountIn = tokenAmountsIn[i]; if (_tokenAmountIn == 0) continue; - _tokens[i].safeTransferFrom( - msg.sender, - address(this), - _tokenAmountIn - ); + _tokens[i].safeTransferFrom(msg.sender, address(this), _tokenAmountIn); reserves[i] += _tokenAmountIn; } } - lpAmountOut = - _calcLpTokenSupply(wellFunction(), reserves) - - totalSupply(); + lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply(); if (lpAmountOut < minLpAmountOut) { revert SlippageOut(lpAmountOut, minLpAmountOut); } @@ -552,18 +484,19 @@ contract Well is /** * @dev Assumes that no tokens involved incur a fee on transfer. */ - function getAddLiquidityOut( - uint256[] memory tokenAmountsIn - ) external view readOnlyNonReentrant returns (uint256 lpAmountOut) { + function getAddLiquidityOut(uint256[] memory tokenAmountsIn) + external + view + readOnlyNonReentrant + returns (uint256 lpAmountOut) + { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); for (uint256 i; i < tokensLength; ++i) { reserves[i] += tokenAmountsIn[i]; } - lpAmountOut = - _calcLpTokenSupply(wellFunction(), reserves) - - totalSupply(); + lpAmountOut = _calcLpTokenSupply(wellFunction(), reserves) - totalSupply(); } //////////////////// REMOVE LIQUIDITY: BALANCED //////////////////// @@ -573,22 +506,12 @@ contract Well is uint256[] calldata minTokenAmountsOut, address recipient, uint256 deadline - ) - external - nonReentrant - expire(deadline) - returns (uint256[] memory tokenAmountsOut) - { + ) external nonReentrant expire(deadline) returns (uint256[] memory tokenAmountsOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _updatePumps(tokensLength); - tokenAmountsOut = _calcLPTokenUnderlying( - wellFunction(), - lpAmountIn, - reserves, - totalSupply() - ); + tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, totalSupply()); _burn(msg.sender, lpAmountIn); uint256 _tokenAmountOut; for (uint256 i; i < tokensLength; ++i) { @@ -604,9 +527,7 @@ contract Well is emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityOut( - uint256 lpAmountIn - ) + function getRemoveLiquidityOut(uint256 lpAmountIn) external view readOnlyNonReentrant @@ -616,12 +537,7 @@ contract Well is uint256[] memory reserves = _getReserves(_tokens.length); uint256 lpTokenSupply = totalSupply(); - tokenAmountsOut = _calcLPTokenUnderlying( - wellFunction(), - lpAmountIn, - reserves, - lpTokenSupply - ); + tokenAmountsOut = _calcLPTokenUnderlying(wellFunction(), lpAmountIn, reserves, lpTokenSupply); } //////////////////// REMOVE LIQUIDITY: ONE TOKEN //////////////////// @@ -637,11 +553,7 @@ contract Well is uint256[] memory reserves = _updatePumps(_tokens.length); uint256 j = _getJ(_tokens, tokenOut); - tokenAmountOut = _getRemoveLiquidityOneTokenOut( - lpAmountIn, - j, - reserves - ); + tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, j, reserves); if (tokenAmountOut < minTokenAmountOut) { revert SlippageOut(tokenAmountOut, minTokenAmountOut); } @@ -651,12 +563,7 @@ contract Well is reserves[j] -= tokenAmountOut; _setReserves(_tokens, reserves); - emit RemoveLiquidityOneToken( - lpAmountIn, - tokenOut, - tokenAmountOut, - recipient - ); + emit RemoveLiquidityOneToken(lpAmountIn, tokenOut, tokenAmountOut, recipient); } function getRemoveLiquidityOneTokenOut( @@ -665,11 +572,7 @@ contract Well is ) external view readOnlyNonReentrant returns (uint256 tokenAmountOut) { IERC20[] memory _tokens = tokens(); uint256[] memory reserves = _getReserves(_tokens.length); - tokenAmountOut = _getRemoveLiquidityOneTokenOut( - lpAmountIn, - _getJ(_tokens, tokenOut), - reserves - ); + tokenAmountOut = _getRemoveLiquidityOneTokenOut(lpAmountIn, _getJ(_tokens, tokenOut), reserves); } /** @@ -684,12 +587,7 @@ contract Well is uint256 j, uint256[] memory reserves ) private view returns (uint256 tokenAmountOut) { - uint256 newReserveJ = _calcReserve( - wellFunction(), - reserves, - j, - totalSupply() - lpAmountIn - ); + uint256 newReserveJ = _calcReserve(wellFunction(), reserves, j, totalSupply() - lpAmountIn); tokenAmountOut = reserves[j] - newReserveJ; } @@ -712,9 +610,7 @@ contract Well is reserves[i] -= _tokenAmountOut; } - lpAmountIn = - totalSupply() - - _calcLpTokenSupply(wellFunction(), reserves); + lpAmountIn = totalSupply() - _calcLpTokenSupply(wellFunction(), reserves); if (lpAmountIn > maxLpAmountIn) { revert SlippageIn(lpAmountIn, maxLpAmountIn); } @@ -724,18 +620,19 @@ contract Well is emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityImbalancedIn( - uint256[] calldata tokenAmountsOut - ) external view readOnlyNonReentrant returns (uint256 lpAmountIn) { + function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) + external + view + readOnlyNonReentrant + returns (uint256 lpAmountIn) + { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); for (uint256 i; i < tokensLength; ++i) { reserves[i] -= tokenAmountsOut[i]; } - lpAmountIn = - totalSupply() - - _calcLpTokenSupply(wellFunction(), reserves); + lpAmountIn = totalSupply() - _calcLpTokenSupply(wellFunction(), reserves); } //////////////////// RESERVES //////////////////// @@ -744,10 +641,7 @@ contract Well is * @dev Can be used in a multicall to add liquidity similar to how `shift` can be used to swap. * See {shift} for examples of how to use in a multicall. */ - function sync( - address recipient, - uint256 minLpAmountOut - ) external nonReentrant returns (uint256 lpAmountOut) { + function sync(address recipient, uint256 minLpAmountOut) external nonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; _updatePumps(tokensLength); @@ -770,12 +664,7 @@ contract Well is emit Sync(reserves, lpAmountOut, recipient); } - function getSyncOut() - external - view - readOnlyNonReentrant - returns (uint256 lpAmountOut) - { + function getSyncOut() external view readOnlyNonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; @@ -794,9 +683,7 @@ contract Well is /** * @dev Transfer excess tokens held by the Well to `recipient`. */ - function skim( - address recipient - ) external nonReentrant returns (uint256[] memory skimAmounts) { + function skim(address recipient) external nonReentrant returns (uint256[] memory skimAmounts) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); @@ -809,21 +696,14 @@ contract Well is } } - function getReserves() - external - view - readOnlyNonReentrant - returns (uint256[] memory reserves) - { + function getReserves() external view readOnlyNonReentrant returns (uint256[] memory reserves) { reserves = _getReserves(numberOfTokens()); } /** * @dev Gets the Well's token reserves by reading from byte storage. */ - function _getReserves( - uint256 _numberOfTokens - ) internal view returns (uint256[] memory reserves) { + function _getReserves(uint256 _numberOfTokens) internal view returns (uint256[] memory reserves) { reserves = LibBytes.readUint128(RESERVES_STORAGE_SLOT, _numberOfTokens); } @@ -831,13 +711,11 @@ contract Well is * @dev Checks that the balance of each ERC-20 token is >= the reserves and * sets the Well's reserves of each token by writing to byte storage. */ - function _setReserves( - IERC20[] memory _tokens, - uint256[] memory reserves - ) internal { + function _setReserves(IERC20[] memory _tokens, uint256[] memory reserves) internal { for (uint256 i; i < reserves.length; ++i) { - if (reserves[i] > _tokens[i].balanceOf(address(this))) + if (reserves[i] > _tokens[i].balanceOf(address(this))) { revert InvalidReserves(); + } } LibBytes.storeUint128(RESERVES_STORAGE_SLOT, reserves); } @@ -848,9 +726,7 @@ contract Well is * @dev Fetches the current token reserves of the Well and updates the Pumps. * Typically called before an operation that modifies the Well's reserves. */ - function _updatePumps( - uint256 _numberOfTokens - ) internal returns (uint256[] memory reserves) { + function _updatePumps(uint256 _numberOfTokens) internal returns (uint256[] memory reserves) { reserves = _getReserves(_numberOfTokens); uint256 _numberOfPumps = numberOfPumps(); @@ -862,16 +738,16 @@ contract Well is if (_numberOfPumps == 1) { Call memory _pump = firstPump(); // Don't revert if the update call fails. - try IPump(_pump.target).update(reserves, _pump.data) {} catch { + try IPump(_pump.target).update(reserves, _pump.data) {} + catch { // ignore reversion. If an external shutoff mechanism is added to a Pump, it could be called here. } } else { Call[] memory _pumps = pumps(); for (uint256 i; i < _pumps.length; ++i) { // Don't revert if the update call fails. - try - IPump(_pumps[i].target).update(reserves, _pumps[i].data) - {} catch { + try IPump(_pumps[i].target).update(reserves, _pumps[i].data) {} + catch { // ignore reversion. If an external shutoff mechanism is added to a Pump, it could be called here. } } @@ -891,10 +767,7 @@ contract Well is Call memory _wellFunction, uint256[] memory reserves ) internal view returns (uint256 lpTokenSupply) { - lpTokenSupply = IWellFunction(_wellFunction.target).calcLpTokenSupply( - reserves, - _wellFunction.data - ); + lpTokenSupply = IWellFunction(_wellFunction.target).calcLpTokenSupply(reserves, _wellFunction.data); } /** @@ -910,12 +783,7 @@ contract Well is uint256 j, uint256 lpTokenSupply ) internal view returns (uint256 reserve) { - reserve = IWellFunction(_wellFunction.target).calcReserve( - reserves, - j, - lpTokenSupply, - _wellFunction.data - ); + reserve = IWellFunction(_wellFunction.target).calcReserve(reserves, j, lpTokenSupply, _wellFunction.data); } /** @@ -933,13 +801,9 @@ contract Well is uint256[] memory reserves, uint256 lpTokenSupply ) internal view returns (uint256[] memory tokenAmounts) { - tokenAmounts = IWellFunction(_wellFunction.target) - .calcLPTokenUnderlying( - lpTokenAmount, - reserves, - lpTokenSupply, - _wellFunction.data - ); + tokenAmounts = IWellFunction(_wellFunction.target).calcLPTokenUnderlying( + lpTokenAmount, reserves, lpTokenSupply, _wellFunction.data + ); } //////////////////// INTERNAL: WELL TOKEN INDEXING //////////////////// @@ -976,10 +840,7 @@ contract Well is * If `_tokens` contains multiple instances of `jToken`, this will return * the first one. A {Well} with duplicate tokens has been misconfigured. */ - function _getJ( - IERC20[] memory _tokens, - IERC20 jToken - ) internal pure returns (uint256 j) { + function _getJ(IERC20[] memory _tokens, IERC20 jToken) internal pure returns (uint256 j) { for (j; j < _tokens.length; ++j) { if (jToken == _tokens[j]) { return j; diff --git a/src/functions/LookupTable.t.sol b/src/functions/LookupTable.t.sol new file mode 100644 index 00000000..2146a693 --- /dev/null +++ b/src/functions/LookupTable.t.sol @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: MIT +// forgefmt: disable-start + +pragma solidity ^0.8.20; + +import {TestHelper, Well, IERC20, console} from "test/TestHelper.sol"; +import {Stable2LUT1} from "src/functions/StableLut/Stable2LUT1.sol"; + +contract LookupTableTest is TestHelper { + + Stable2LUT1 lookupTable; + Stable2LUT1.PriceData pd; + + + function setUp() public { + lookupTable = new Stable2LUT1(); + } + + function test_getAParameter() public { + uint256 a = lookupTable.getAParameter(); + assertEq(a , 1); + } + + //////////////// getRatiosFromPriceSwap //////////////// + + function test_getRatiosFromPriceSwapAroundDollarHigh() public { + uint256 currentPrice = 1e18; + // test 1.0 - 1.10 range + for (uint256 i; i<10 ; i++) { + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + uint256 diff = pd.highPrice - pd.lowPrice; + assertLt(diff, 0.1e18); + currentPrice += 0.01e18; + } + } + + function test_getRatiosFromPriceSwapAroundDollarLow() public { + uint256 currentPrice = 0.9e18; + // test 0.9 - 1.0 range + for (uint256 i; i<10 ; i++) { + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + uint256 diff = pd.highPrice - pd.lowPrice; + assertLt(diff, 0.1e18); + currentPrice += 0.01e18; + } + } + + function test_getRatiosFromPriceSwapExtremeLow() public { + // pick a value close to the min (P=0.01) + uint256 currentPrice = 0.015e18; + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + console.log("pd.lowPrice: %e", pd.lowPrice); + console.log("pd.highPrice: %e", pd.highPrice); + } + + function test_getRatiosFromPriceSwapExtremeHigh() public { + // pick a value close to the max (P=9.85) + uint256 currentPrice = 9.84e18; + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + console.log("pd.lowPrice: %e", pd.lowPrice); + console.log("pd.highPrice: %e", pd.highPrice); + } + + function testFail_getRatiosFromPriceSwapExtremeLow() public { + // pick an out of bounds value (P<0.01) + uint256 currentPrice = 0.001e18; + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + } + + function testFail_getRatiosFromPriceSwapExtremeHigh() public { + // pick an out of bounds value (P>9.85) + uint256 currentPrice = 10e18; + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + } + + //////////////// getRatiosFromPriceLiquidity //////////////// + + + function test_getRatiosFromPriceLiquidityAroundDollarHigh() public { + uint256 currentPrice = 1e18; + // test 1.0 - 1.10 range + for (uint256 i; i<10 ; i++) { + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + uint256 diff = pd.highPrice - pd.lowPrice; + assertLt(diff, 0.1e18); + currentPrice += 0.01e18; + } + } + + function test_getRatiosFromPriceLiquidityAroundDollarLow() public { + uint256 currentPrice = 0.9e18; + // test 0.9 - 1.0 range + for (uint256 i; i<10 ; i++) { + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + uint256 diff = pd.highPrice - pd.lowPrice; + assertLt(diff, 0.1e18); + currentPrice += 0.01e18; + } + } + + function test_getRatiosFromPriceLiquidityExtremeLow() public { + // pick a value close to the min (P=0.01) + uint256 currentPrice = 0.015e18; + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + console.log("pd.lowPrice: %e", pd.lowPrice); + console.log("pd.highPrice: %e", pd.highPrice); + } + + function test_getRatiosFromPriceLiquidityExtremeHigh() public { + // pick a value close to the max (P=9.92) + uint256 currentPrice = 9.91e18; + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + console.log("pd.lowPrice: %e", pd.lowPrice); + console.log("pd.highPrice: %e", pd.highPrice); + } + + function testFail_getRatiosFromPriceLiquidityExtremeLow() public { + // pick an out of bounds value (P<0.01) + uint256 currentPrice = 0.001e18; + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + } + + function testFail_getRatiosFromPriceLiquidityExtremeHigh() public { + // pick an out of bounds value (P>9.85) + uint256 currentPrice = 10e18; + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + } +} diff --git a/src/functions/PriceReserveMapping2.sol b/src/functions/PriceReserveMapping2.sol deleted file mode 100644 index a498b976..00000000 --- a/src/functions/PriceReserveMapping2.sol +++ /dev/null @@ -1,506 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.20; - -/** - * @title PriceReserveMapping - * @author DeadmanWalking - * @notice The `Stable2` Well Function implementation of - * `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` requires - * multiple calculations of the price given a change in reserves. - * Performing the calculation on-chain is expensive, and thus a LUT is implemented - * to decrease the computation done on execution. - * - * @dev given `price`, returns the ratio of reserves needed for the pool to return - * `price` when calling `calcRate`, scaled to 100e18. - * Example: - * p = 1.011271030257560703e18, - * r = 698337_296093749599797248 - * reserve[0] = 1_000_000e6 - * reserve[1] = 698337_296093 - */ -contract PriceReserveMapping { - function getReserve(uint256 price) external pure returns (uint256) { - if (price < 1.000033716578442112e18) { - if (price < 830_357_766_063_866_624) { - if (price < 608_750_007_429_268_480) { - if (price < 497_147_688_282_685_696) { - if (price < 441_195_594_527_224_000) { - if (price < 421_290_035_178_414_208) { - if (price < 404_517_292_279_625_088) { - return 7_039_988_712_124_655_530_409_984; - } else { - return 7_039_988_712_124_655_530_409_984; - } - } else { - if (price < 422_561_897_881_033_792) { - return 6_704_751_154_404_433_787_355_136; - } else { - return 6_704_751_154_404_433_787_355_136; - } - } - } else { - if (price < 460_406_209_346_677_952) { - if (price < 458_149_633_111_917_824) { - return 6_115_909_044_841_460_461_993_984; - } else { - return 6_115_909_044_841_460_461_993_984; - } - } else { - if (price < 480_176_259_556_639_872) { - return 5_791_816_135_971_867_467_972_608; - } else { - return 5_791_816_135_971_867_467_972_608; - } - } - } - } else { - if (price < 542_576_156_450_906_944) { - if (price < 521_294_640_197_852_672) { - if (price < 500_482_361_488_375_424) { - return 5_516_015_367_592_254_935_924_736; - } else { - return 5_516_015_367_592_254_935_924_736; - } - } else { - if (price < 538_091_395_837_277_056) { - return 5_054_470_284_992_941_802_913_792; - } else { - return 5_054_470_284_992_941_802_913_792; - } - } - } else { - if (price < 580_686_644_101_094_656) { - if (price < 564_282_371_756_721_024) { - return 4_764_941_468_603_609_620_938_752; - } else { - return 4_764_941_468_603_609_620_938_752; - } - } else { - if (price < 586_360_676_154_978_944) { - return 4_538_039_493_908_200_048_033_792; - } else { - return 4_538_039_493_908_200_048_033_792; - } - } - } - } - } else { - if (price < 722_615_518_283_425_408) { - if (price < 669_067_822_553_920_512) { - if (price < 631_380_594_036_478_848) { - if (price < 624_523_709_284_514_688) { - return 4_177_248_169_415_654_040_928_256; - } else { - return 4_177_248_169_415_654_040_928_256; - } - } else { - if (price < 654_173_856_946_462_592) { - return 3_920_129_138_458_653_678_895_104; - } else { - return 3_920_129_138_458_653_678_895_104; - } - } - } else { - if (price < 699_890_877_274_508_032) { - if (price < 677_042_507_344_532_224) { - return 3_733_456_322_341_575_008_976_896; - } else { - return 3_733_456_322_341_575_008_976_896; - } - } else { - if (price < 713_658_133_485_124_864) { - return 3_452_271_214_393_102_144_897_024; - } else { - return 3_452_271_214_393_102_144_897_024; - } - } - } - } else { - if (price < 788_916_974_369_642_752) { - if (price < 757_518_945_604_179_584) { - if (price < 745_106_098_488_377_088) { - return 3_225_099_943_713_702_223_544_320; - } else { - return 3_225_099_943_713_702_223_544_320; - } - } else { - if (price < 767_246_620_828_971_264) { - return 3_071_523_755_917_812_229_472_256; - } else { - return 3_071_523_755_917_812_229_472_256; - } - } - } else { - if (price < 809_994_816_396_841_728) { - if (price < 799_786_725_808_037_248) { - return 2_853_116_706_110_001_328_947_200; - } else { - return 2_853_116_706_110_001_328_947_200; - } - } else { - return 2_785_962_590_401_643_029_725_184; - } - } - } - } - } else { - if (price < 972_989_115_515_289_216) { - if (price < 917_489_006_622_623_232) { - if (price < 875_936_112_087_689_344) { - if (price < 849_885_871_198_024_448) { - if (price < 839_554_943_435_977_728) { - return 2_593_742_460_100_001_012_908_032; - } else { - return 2_593_742_460_100_001_012_908_032; - } - } else { - if (price < 868_464_289_727_404_288) { - return 2_406_619_233_691_085_014_302_720; - } else { - return 2_406_619_233_691_085_014_302_720; - } - } - } else { - if (price < 902_355_196_812_224_000) { - if (price < 885_986_107_499_586_304) { - return 2_292_018_317_801_032_886_779_904; - } else { - return 2_292_018_317_801_032_886_779_904; - } - } else { - if (price < 908_136_713_928_969_216) { - return 2_143_588_810_000_000_484_376_576; - } else { - return 2_143_588_810_000_000_484_376_576; - } - } - } - } else { - if (price < 954_909_391_012_556_032) { - if (price < 935_536_737_788_984_832) { - if (price < 931_321_167_909_476_608) { - return 1_979_931_599_439_397_501_534_208; - } else { - return 1_979_931_599_439_397_501_534_208; - } - } else { - if (price < 943_803_797_433_811_072) { - return 1_885_649_142_323_235_971_399_680; - } else { - return 1_885_649_142_323_235_971_399_680; - } - } - } else { - if (price < 964_632_213_434_632_832) { - if (price < 957_762_568_730_056_576) { - return 1_771_561_000_000_000_284_950_528; - } else { - return 1_771_561_000_000_000_284_950_528; - } - } else { - return 1_710_339_358_116_313_758_171_136; - } - } - } - } else { - if (price < 996_430_298_345_225_728) { - if (price < 986_726_125_054_516_224) { - if (price < 980_019_737_867_874_432) { - if (price < 974_741_215_197_397_632) { - return 1_610_510_000_000_000_210_239_488; - } else { - return 1_610_510_000_000_000_210_239_488; - } - } else { - if (price < 985_786_093_464_194_432) { - return 1_477_455_443_789_062_678_249_472; - } else { - return 1_477_455_443_789_062_678_249_472; - } - } - } else { - if (price < 993_879_297_978_292_480) { - if (price < 990_371_554_230_599_424) { - return 1_407_100_422_656_250_003_587_072; - } else { - return 1_407_100_422_656_250_003_587_072; - } - } else { - if (price < 994_289_877_233_378_560) { - return 1_331_000_000_000_000_102_760_448; - } else { - return 1_331_000_000_000_000_102_760_448; - } - } - } - } else { - if (price < 999_768_290_809_212_032) { - if (price < 998_285_495_618_766_464) { - if (price < 998_160_960_147_888_768) { - return 1_215_506_250_000_000_018_808_832; - } else { - return 1_215_506_250_000_000_018_808_832; - } - } else { - if (price < 999_220_514_391_006_208) { - return 1_157_625_000_000_000_062_652_416; - } else { - return 1_157_625_000_000_000_062_652_416; - } - } - } else { - if (price < 999_970_981_689_446_016) { - if (price < 999_784_063_917_080_448) { - return 1_099_999_999_999_999_874_170_880; - } else { - return 1_099_999_999_999_999_874_170_880; - } - } else { - return 1_050_000_000_000_000_062_914_560; - } - } - } - } - } - } else { - if (price < 1_270_229_006_927_506_432) { - if (price < 1_042_410_779_328_363_904) { - if (price < 1_007_730_870_339_573_120) { - if (price < 1_002_138_979_681_074_944) { - if (price < 1_000_291_630_212_889_856) { - if (price < 1_000_269_233_009_443_840) { - return 902_500_000_000_000_015_728_640; - } else { - return 902_500_000_000_000_015_728_640; - } - } else { - if (price < 1_000_905_972_187_880_320) { - return 857_374_999_999_999_847_170_048; - } else { - return 857_374_999_999_999_847_170_048; - } - } - } else { - if (price < 1_004_157_377_550_525_696) { - if (price < 1_002_316_102_194_843_648) { - return 810_000_000_000_000_029_360_128; - } else { - return 810_000_000_000_000_029_360_128; - } - } else { - if (price < 1_007_143_263_844_912_000) { - return 735_091_890_624_999_614_054_400; - } else { - return 735_091_890_624_999_614_054_400; - } - } - } - } else { - if (price < 1_023_609_741_631_299_072) { - if (price < 1_016_707_057_698_784_896) { - if (price < 1_011_271_030_257_560_704) { - return 698_337_296_093_749_599_797_248; - } else { - return 698_337_296_093_749_599_797_248; - } - } else { - if (price < 1_018_070_626_816_667_904) { - return 656_100_000_000_000_049_283_072; - } else { - return 656_100_000_000_000_049_283_072; - } - } - } else { - if (price < 1_034_730_220_540_242_944) { - if (price < 1_032_129_795_364_543_616) { - return 598_736_939_238_378_581_262_336; - } else { - return 598_736_939_238_378_581_262_336; - } - } else { - return 590_490_000_000_000_030_932_992; - } - } - } - } else { - if (price < 1_134_730_371_284_194_048) { - if (price < 1_085_163_323_283_893_760) { - if (price < 1_058_971_528_414_115_200) { - if (price < 1_054_589_806_813_510_272) { - return 540_360_087_662_636_587_548_672; - } else { - return 540_360_087_662_636_587_548_672; - } - } else { - if (price < 1_068_798_380_970_533_888) { - return 513_342_083_279_504_818_569_216; - } else { - return 513_342_083_279_504_818_569_216; - } - } - } else { - if (price < 1_103_807_759_690_609_920) { - if (price < 1_091_946_638_369_647_232) { - return 478_296_900_000_000_063_307_776; - } else { - return 478_296_900_000_000_063_307_776; - } - } else { - if (price < 1_124_852_136_531_598_848) { - return 440_126_668_651_765_310_685_184; - } else { - return 440_126_668_651_765_310_685_184; - } - } - } - } else { - if (price < 1_203_570_602_035_602_432) { - if (price < 1_174_615_232_221_620_736) { - if (price < 1_148_415_244_275_700_096) { - return 418_120_335_219_177_014_951_936; - } else { - return 418_120_335_219_177_014_951_936; - } - } else { - if (price < 1_188_356_761_339_094_272) { - return 387_420_489_000_000_082_149_376; - } else { - return 387_420_489_000_000_082_149_376; - } - } - } else { - if (price < 1_253_855_901_740_080_128) { - if (price < 1_235_401_171_969_669_632) { - return 358_485_922_408_541_800_366_080; - } else { - return 358_485_922_408_541_800_366_080; - } - } else { - return 348_678_440_100_000_100_777_984; - } - } - } - } - } else { - if (price < 1_813_506_410_438_685_184) { - if (price < 1_532_552_739_400_572_928) { - if (price < 1_393_968_967_516_634_880) { - if (price < 1_332_289_125_721_440_768) { - if (price < 1_308_179_312_239_340_544) { - return 323_533_544_973_709_000_835_072; - } else { - return 323_533_544_973_709_000_835_072; - } - } else { - if (price < 1_349_381_291_133_012_224) { - return 307_356_867_725_023_544_082_432; - } else { - return 307_356_867_725_023_544_082_432; - } - } - } else { - if (price < 1_442_081_976_893_328_640) { - if (price < 1_424_781_668_356_408_832) { - return 282_429_536_481_000_091_025_408; - } else { - return 282_429_536_481_000_091_025_408; - } - } else { - if (price < 1_493_866_329_083_113_216) { - return 263_520_094_465_741_985_677_312; - } else { - return 263_520_094_465_741_985_677_312; - } - } - } - } else { - if (price < 1_672_818_539_081_097_216) { - if (price < 1_609_069_386_068_001_536) { - if (price < 1_549_475_146_998_422_528) { - return 250_344_089_742_454_896_459_776; - } else { - return 250_344_089_742_454_896_459_776; - } - } else { - if (price < 1_656_943_402_509_807_872) { - return 228_767_924_549_610_094_198_784; - } else { - return 228_767_924_549_610_094_198_784; - } - } - } else { - if (price < 1_799_442_892_480_155_648) { - if (price < 1_740_901_331_273_835_008) { - return 214_638_763_942_937_209_339_904; - } else { - return 214_638_763_942_937_209_339_904; - } - } else { - return 205_891_132_094_649_074_712_576; - } - } - } - } else { - if (price < 2_353_244_129_099_838_464) { - if (price < 2_060_505_206_109_642_752) { - if (price < 1_961_714_091_549_304_320) { - if (price < 1_890_833_036_735_312_384) { - return 193_711_484_458_500_800_643_072; - } else { - return 193_711_484_458_500_800_643_072; - } - } else { - if (price < 1_973_091_776_740_027_136) { - return 184_025_910_235_575_711_956_992; - } else { - return 184_025_910_235_575_711_956_992; - } - } - } else { - if (price < 2_153_308_625_067_387_136) { - if (price < 2_145_618_891_058_364_672) { - return 166_771_816_996_665_772_998_656; - } else { - return 166_771_816_996_665_772_998_656; - } - } else { - if (price < 2_251_750_790_753_840_896) { - return 157_779_214_788_226_736_717_824; - } else { - return 157_779_214_788_226_736_717_824; - } - } - } - } else { - if (price < 2_586_928_745_378_339_328) { - if (price < 2_466_618_218_597_512_192) { - if (price < 2_356_094_670_332_007_936) { - return 149_890_254_048_815_394_848_768; - } else { - return 149_890_254_048_815_394_848_768; - } - } else { - if (price < 2_583_615_183_726_152_192) { - return 135_275_954_279_055_877_996_544; - } else { - return 135_275_954_279_055_877_996_544; - } - } - } else { - if (price < 2_849_292_747_060_493_824) { - if (price < 2_707_395_944_693_278_720) { - return 128_512_156_565_103_074_869_248; - } else { - return 128_512_156_565_103_074_869_248; - } - } else { - return 121_576_654_590_569_365_897_216; - } - } - } - } - } - } - } -} diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 8a53f7e8..40fa9f1e 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -3,39 +3,12 @@ pragma solidity ^0.8.17; import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {ILookupTable} from "src/interfaces/ILookupTable.sol"; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {LibMath} from "src/libraries/LibMath.sol"; import {SafeMath} from "oz/utils/math/SafeMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import "forge-std/console.sol"; - -interface ILookupTable { - /** - * @notice the lookup table returns a series of data, given a price point: - * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. - * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. - * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. - * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. - * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. - * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. - * @param precision precision of reserve. - */ - struct PriceData { - uint256 highPrice; - uint256 highPriceI; - uint256 highPriceJ; - uint256 lowPrice; - uint256 lowPriceI; - uint256 lowPriceJ; - uint256 precision; - } - - // for liquidity, x stays the same, y changes (D changes) - // for swap, x changes, y changes, (D stays the same) - function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); - function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); - function getAParameter() external view returns (uint256); -} +import {console} from "forge-std/console.sol"; /** * @author Brean * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. @@ -69,12 +42,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // A precision uint256 constant A_PRECISION = 100; - // Precision that all pools tokens will be converted to. - uint256 constant POOL_PRECISION_DECIMALS = 18; - - // Calc Rate Precision. - uint256 constant CALC_RATE_PRECISION = 1e24; - // price Precision. uint256 constant PRICE_PRECISION = 1e6; @@ -82,8 +49,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 immutable a; // Errors - error InvalidAParameter(uint256); - error InvalidTokens(); error InvalidTokenDecimals(); error InvalidLUT(); @@ -97,8 +62,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { constructor(address lut) { if (lut == address(0)) revert InvalidLUT(); lookupTable = lut; - // a = ILookupTable(lut).getAParameter(); - a = 10; + a = ILookupTable(lut).getAParameter(); } /** @@ -113,6 +77,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory reserves, bytes memory data ) public view returns (uint256 lpTokenSupply) { + if (reserves[0] == 0 && reserves[1] == 0) return 0; uint256[] memory decimals = decodeWellData(data); // scale reserves to 18 decimals. uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); @@ -250,7 +215,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { /** * @inheritdoc IMultiFlowPumpWellFunction - * @dev Returns a rate with 6 decimal precision. + * @dev Returns a rate with decimal precision. * Requires a minimum scaled reserves of 1e12. * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, * which is prohibtive for large value tokens. @@ -267,14 +232,12 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); - // reverse if i is not 0. - // add 1e6 to reserves: scaledReserves[j] += PRICE_PRECISION; // calculate new reserve 1: uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - rate = (scaledReserves[0] - new_reserve1); + rate = (scaledReserves[i] - new_reserve1); } /** @@ -307,18 +270,33 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); + // scale down lutData POC: + // TODO: remove + pd.lutData.precision = pd.lutData.precision * 1e6; + pd.lutData.highPriceJ = pd.lutData.highPriceJ / 1e18; + pd.lutData.lowPriceJ = pd.lutData.lowPriceJ / 1e18; + // update scaledReserve[j] based on lowPrice: + console.log("scaledReserve[j] b4", scaledReserves[j]); scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; + console.log("scaledReserve[j] afta", scaledReserves[j]); // calculate max step size: + console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); + console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); + pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.lowPriceJ; + console.log("pd.maxStepSize", pd.maxStepSize); for (uint256 k; k < 255; k++) { + console.log("i", i); // scale stepSize proporitional to distance from price: uint256 stepSize = pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + console.log("stepSize", stepSize); // increment reserve by stepSize: scaledReserves[j] = scaledReserves[j] + stepSize; + console.log("scaledReserves[j]", scaledReserves[j]); // calculate new price from reserves: pd.currentPrice = calcRate(scaledReserves, i, j, data); @@ -332,11 +310,11 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } function name() external pure returns (string memory) { - return "StableSwap"; + return "Stable2"; } function symbol() external pure returns (string memory) { - return "SS2"; + return "S2"; } /** diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol new file mode 100644 index 00000000..0d73f8a0 --- /dev/null +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -0,0 +1,2948 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +import {ILookupTable} from "src/interfaces/ILookupTable.sol"; + +/** + * @title Stable2LUT1 + * @author DeadmanWalking + * @notice The `Stable2` Well Function implementation of + * `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` requires + * multiple calculations of the price given a change in reserves. + * Performing the calculation on-chain is expensive, and thus a LUT is implemented + * to decrease the computation done on execution. + * + */ +contract Stable2LUT1 is ILookupTable { + /** + * @notice returns the effective A parameter that the LUT is used for. + */ + function getAParameter() external pure returns (uint256) { + return 1; + } + + function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { + // todo, remove once LUT is updated. + price = price * 1e12; + if (price < 1.0259848410192591e18) { + if (price < 0.5719086306678213e18) { + if (price < 0.40963548734729605e18) { + if (price < 0.33971565164228473e18) { + if (price < 0.3017802513050929e18) { + if (price < 0.28982321123204907e18) { + if (price < 0.010863721053450156e18) { + revert("Price too low, well is bricked"); + } else { + return PriceData( + 0.28982321123204907e18, + 0, + 7_039_988_712_124_657_677_893_632, + 0.010863721053450156e18, + 0, + 200_000_000_000_000_009_529_458_688, + 1e18 + ); + } + } else { + if (price < 0.30093898499677296e18) { + return PriceData( + 0.3017802513050929e18, + 0, + 6_704_751_154_404_434_861_096_960, + 0.30093898499677296e18, + 0, + 6_727_499_949_325_611_769_528_320, + 1e18 + ); + } else { + return PriceData( + 0.3017802513050929e18, + 0, + 6_704_751_154_404_434_861_096_960, + 0.30093898499677296e18, + 0, + 6_727_499_949_325_611_769_528_320, + 1e18 + ); + } + } + } else { + if (price < 0.3252445574032712e18) { + if (price < 0.31408250964722695e18) { + return PriceData( + 0.3252445574032712e18, + 0, + 6_115_909_044_841_464_756_961_280, + 0.31408250964722695e18, + 0, + 6_385_477_289_908_985_837_649_920, + 1e18 + ); + } else { + return PriceData( + 0.3252445574032712e18, + 0, + 6_115_909_044_841_464_756_961_280, + 0.31408250964722695e18, + 0, + 6_385_477_289_908_985_837_649_920, + 1e18 + ); + } + } else { + if (price < 0.3267284286293485e18) { + return PriceData( + 0.33971565164228473e18, + 0, + 5_791_816_135_971_868_541_714_432, + 0.3267284286293485e18, + 0, + 6_081_406_942_770_462_344_609_792, + 1e18 + ); + } else { + return PriceData( + 0.33971565164228473e18, + 0, + 5_791_816_135_971_868_541_714_432, + 0.3267284286293485e18, + 0, + 6_081_406_942_770_462_344_609_792, + 1e18 + ); + } + } + } + } else { + if (price < 0.37773904701946753e18) { + if (price < 0.35304103579667717e18) { + if (price < 0.35085305477232803e18) { + return PriceData( + 0.35304103579667717e18, + 0, + 5_516_015_367_592_256_009_666_560, + 0.35085305477232803e18, + 0, + 5_559_917_313_492_240_492_920_832, + 1e18 + ); + } else { + return PriceData( + 0.35304103579667717e18, + 0, + 5_516_015_367_592_256_009_666_560, + 0.35085305477232803e18, + 0, + 5_559_917_313_492_240_492_920_832, + 1e18 + ); + } + } else { + if (price < 0.36670067588887123e18) { + return PriceData( + 0.37773904701946753e18, + 0, + 5_054_470_284_992_945_024_139_264, + 0.36670067588887123e18, + 0, + 5_253_347_969_135_480_431_181_824, + 1e18 + ); + } else { + return PriceData( + 0.37773904701946753e18, + 0, + 5_054_470_284_992_945_024_139_264, + 0.36670067588887123e18, + 0, + 5_253_347_969_135_480_431_181_824, + 1e18 + ); + } + } + } else { + if (price < 0.3950035195454608e18) { + if (price < 0.3806899403242078e18) { + return PriceData( + 0.3950035195454608e18, + 0, + 4_764_941_468_603_611_231_551_488, + 0.3806899403242078e18, + 0, + 5_003_188_542_033_791_551_537_152, + 1e18 + ); + } else { + return PriceData( + 0.3950035195454608e18, + 0, + 4_764_941_468_603_611_231_551_488, + 0.3806899403242078e18, + 0, + 5_003_188_542_033_791_551_537_152, + 1e18 + ); + } + } else { + if (price < 0.40586651378772454e18) { + return PriceData( + 0.40963548734729605e18, + 0, + 4_538_039_493_908_200_584_904_704, + 0.40586651378772454e18, + 0, + 4_594_972_986_357_222_505_185_280, + 1e18 + ); + } else { + return PriceData( + 0.40963548734729605e18, + 0, + 4_538_039_493_908_200_584_904_704, + 0.40586651378772454e18, + 0, + 4_594_972_986_357_222_505_185_280, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.49721516194096665e18) { + if (price < 0.4553748640059295e18) { + if (price < 0.4351903417101215e18) { + if (price < 0.42457937527049394e18) { + return PriceData( + 0.4351903417101215e18, + 0, + 4_177_248_169_415_656_725_282_816, + 0.42457937527049394e18, + 0, + 4_321_942_375_150_666_456_760_320, + 1e18 + ); + } else { + return PriceData( + 0.4351903417101215e18, + 0, + 4_177_248_169_415_656_725_282_816, + 0.42457937527049394e18, + 0, + 4_321_942_375_150_666_456_760_320, + 1e18 + ); + } + } else { + if (price < 0.439828260066239e18) { + return PriceData( + 0.4553748640059295e18, + 0, + 3_920_129_138_458_653_678_895_104, + 0.439828260066239e18, + 0, + 4_116_135_595_381_586_846_023_680, + 1e18 + ); + } else { + return PriceData( + 0.4553748640059295e18, + 0, + 3_920_129_138_458_653_678_895_104, + 0.439828260066239e18, + 0, + 4_116_135_595_381_586_846_023_680, + 1e18 + ); + } + } + } else { + if (price < 0.4712116675914488e18) { + if (price < 0.465658539376915e18) { + return PriceData( + 0.4712116675914488e18, + 0, + 3_733_456_322_341_575_008_976_896, + 0.465658539376915e18, + 0, + 3_797_498_335_832_414_667_931_648, + 1e18 + ); + } else { + return PriceData( + 0.4712116675914488e18, + 0, + 3_733_456_322_341_575_008_976_896, + 0.465658539376915e18, + 0, + 3_797_498_335_832_414_667_931_648, + 1e18 + ); + } + } else { + if (price < 0.48733103400099287e18) { + return PriceData( + 0.49721516194096665e18, + 0, + 3_452_271_214_393_103_755_509_760, + 0.48733103400099287e18, + 0, + 3_555_672_687_944_357_023_580_160, + 1e18 + ); + } else { + return PriceData( + 0.49721516194096665e18, + 0, + 3_452_271_214_393_103_755_509_760, + 0.48733103400099287e18, + 0, + 3_555_672_687_944_357_023_580_160, + 1e18 + ); + } + } + } + } else { + if (price < 0.5373092907143455e18) { + if (price < 0.5203871429939636e18) { + if (price < 0.5037253443931629e18) { + return PriceData( + 0.5203871429939636e18, + 0, + 3_225_099_943_713_702_223_544_320, + 0.5037253443931629e18, + 0, + 3_386_354_940_899_387_334_721_536, + 1e18 + ); + } else { + return PriceData( + 0.5203871429939636e18, + 0, + 3_225_099_943_713_702_223_544_320, + 0.5037253443931629e18, + 0, + 3_386_354_940_899_387_334_721_536, + 1e18 + ); + } + } else { + if (price < 0.5298038919377664e18) { + return PriceData( + 0.5373092907143455e18, + 0, + 3_071_523_755_917_811_692_601_344, + 0.5298038919377664e18, + 0, + 3_138_428_376_721_003_609_325_568, + 1e18 + ); + } else { + return PriceData( + 0.5373092907143455e18, + 0, + 3_071_523_755_917_811_692_601_344, + 0.5298038919377664e18, + 0, + 3_138_428_376_721_003_609_325_568, + 1e18 + ); + } + } + } else { + if (price < 0.5633721785756237e18) { + if (price < 0.5544851258962078e18) { + return PriceData( + 0.5633721785756237e18, + 0, + 2_853_116_706_110_002_939_559_936, + 0.5544851258962078e18, + 0, + 2_925_260_719_921_725_395_959_808, + 1e18 + ); + } else { + return PriceData( + 0.5633721785756237e18, + 0, + 2_853_116_706_110_002_939_559_936, + 0.5544851258962078e18, + 0, + 2_925_260_719_921_725_395_959_808, + 1e18 + ); + } + } else { + return PriceData( + 0.5719086306678213e18, + 0, + 2_785_962_590_401_643_029_725_184, + 0.5633721785756237e18, + 0, + 2_853_116_706_110_002_939_559_936, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.7793501316201135e18) { + if (price < 0.6695828208753776e18) { + if (price < 0.6256182332555407e18) { + if (price < 0.5978757997638141e18) { + if (price < 0.5895746013098355e18) { + return PriceData( + 0.5978757997638141e18, + 0, + 2_593_742_460_100_002_623_520_768, + 0.5895746013098355e18, + 0, + 2_653_297_705_144_421_600_722_944, + 1e18 + ); + } else { + return PriceData( + 0.5978757997638141e18, + 0, + 2_593_742_460_100_002_623_520_768, + 0.5895746013098355e18, + 0, + 2_653_297_705_144_421_600_722_944, + 1e18 + ); + } + } else { + if (price < 0.6074788209935387e18) { + return PriceData( + 0.6256182332555407e18, + 0, + 2_406_619_233_691_085_014_302_720, + 0.6074788209935387e18, + 0, + 2_526_950_195_375_639_466_344_448, + 1e18 + ); + } else { + return PriceData( + 0.6256182332555407e18, + 0, + 2_406_619_233_691_085_014_302_720, + 0.6074788209935387e18, + 0, + 2_526_950_195_375_639_466_344_448, + 1e18 + ); + } + } + } else { + if (price < 0.6439911146177e18) { + if (price < 0.6332836844239108e18) { + return PriceData( + 0.6439911146177e18, + 0, + 2_292_018_317_801_033_155_215_360, + 0.6332836844239108e18, + 0, + 2_357_947_691_000_001_848_147_968, + 1e18 + ); + } else { + return PriceData( + 0.6439911146177e18, + 0, + 2_292_018_317_801_033_155_215_360, + 0.6332836844239108e18, + 0, + 2_357_947_691_000_001_848_147_968, + 1e18 + ); + } + } else { + if (price < 0.6625972448465314e18) { + return PriceData( + 0.6695828208753776e18, + 0, + 2_143_588_810_000_001_558_118_400, + 0.6625972448465314e18, + 0, + 2_182_874_588_381_936_223_256_576, + 1e18 + ); + } else { + return PriceData( + 0.6695828208753776e18, + 0, + 2_143_588_810_000_001_558_118_400, + 0.6625972448465314e18, + 0, + 2_182_874_588_381_936_223_256_576, + 1e18 + ); + } + } + } + } else { + if (price < 0.7198389360940738e18) { + if (price < 0.7005168813237215e18) { + if (price < 0.6814380734696386e18) { + return PriceData( + 0.7005168813237215e18, + 0, + 1_979_931_599_439_398_038_405_120, + 0.6814380734696386e18, + 0, + 2_078_928_179_411_368_074_543_104, + 1e18 + ); + } else { + return PriceData( + 0.7005168813237215e18, + 0, + 1_979_931_599_439_398_038_405_120, + 0.6814380734696386e18, + 0, + 2_078_928_179_411_368_074_543_104, + 1e18 + ); + } + } else { + if (price < 0.7067830820833219e18) { + return PriceData( + 0.7198389360940738e18, + 0, + 1_885_649_142_323_235_971_399_680, + 0.7067830820833219e18, + 0, + 1_948_717_100_000_000_904_003_584, + 1e18 + ); + } else { + return PriceData( + 0.7198389360940738e18, + 0, + 1_885_649_142_323_235_971_399_680, + 0.7067830820833219e18, + 0, + 1_948_717_100_000_000_904_003_584, + 1e18 + ); + } + } + } else { + if (price < 0.7449218198151675e18) { + if (price < 0.7394116410130436e18) { + return PriceData( + 0.7449218198151675e18, + 0, + 1_771_561_000_000_000_821_821_440, + 0.7394116410130436e18, + 0, + 1_795_856_326_022_129_432_657_920, + 1e18 + ); + } else { + return PriceData( + 0.7449218198151675e18, + 0, + 1_771_561_000_000_000_821_821_440, + 0.7394116410130436e18, + 0, + 1_795_856_326_022_129_432_657_920, + 1e18 + ); + } + } else { + if (price < 0.7592446761079147e18) { + return PriceData( + 0.7793501316201135e18, + 0, + 1_628_894_626_777_441_725_579_264, + 0.7592446761079147e18, + 0, + 1_710_339_358_116_313_758_171_136, + 1e18 + ); + } else { + return PriceData( + 0.7793501316201135e18, + 0, + 1_628_894_626_777_441_725_579_264, + 0.7592446761079147e18, + 0, + 1_710_339_358_116_313_758_171_136, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.8845707447410645e18) { + if (price < 0.8243266124067958e18) { + if (price < 0.7997426334506159e18) { + if (price < 0.7840681145956876e18) { + return PriceData( + 0.7997426334506159e18, + 0, + 1_551_328_215_978_515_852_427_264, + 0.7840681145956876e18, + 0, + 1_610_510_000_000_000_478_674_944, + 1e18 + ); + } else { + return PriceData( + 0.7997426334506159e18, + 0, + 1_551_328_215_978_515_852_427_264, + 0.7840681145956876e18, + 0, + 1_610_510_000_000_000_478_674_944, + 1e18 + ); + } + } else { + if (price < 0.8204394607143816e18) { + return PriceData( + 0.8243266124067958e18, + 0, + 1_464_100_000_000_000_166_723_584, + 0.8204394607143816e18, + 0, + 1_477_455_443_789_062_678_249_472, + 1e18 + ); + } else { + return PriceData( + 0.8243266124067958e18, + 0, + 1_464_100_000_000_000_166_723_584, + 0.8204394607143816e18, + 0, + 1_477_455_443_789_062_678_249_472, + 1e18 + ); + } + } + } else { + if (price < 0.8628291267605447e18) { + if (price < 0.8414606557036146e18) { + return PriceData( + 0.8628291267605447e18, + 0, + 1_340_095_640_624_999_952_285_696, + 0.8414606557036146e18, + 0, + 1_407_100_422_656_250_003_587_072, + 1e18 + ); + } else { + return PriceData( + 0.8628291267605447e18, + 0, + 1_340_095_640_624_999_952_285_696, + 0.8414606557036146e18, + 0, + 1_407_100_422_656_250_003_587_072, + 1e18 + ); + } + } else { + if (price < 0.8658409270814319e18) { + return PriceData( + 0.8845707447410645e18, + 0, + 1_276_281_562_499_999_992_905_728, + 0.8658409270814319e18, + 0, + 1_331_000_000_000_000_102_760_448, + 1e18 + ); + } else { + return PriceData( + 0.8845707447410645e18, + 0, + 1_276_281_562_499_999_992_905_728, + 0.8658409270814319e18, + 0, + 1_331_000_000_000_000_102_760_448, + 1e18 + ); + } + } + } + } else { + if (price < 0.9523395041216094e18) { + if (price < 0.9087966340219248e18) { + if (price < 0.9067144339078782e18) { + return PriceData( + 0.9087966340219248e18, + 0, + 1_209_999_999_999_999_995_805_696, + 0.9067144339078782e18, + 0, + 1_215_506_250_000_000_018_808_832, + 1e18 + ); + } else { + return PriceData( + 0.9087966340219248e18, + 0, + 1_209_999_999_999_999_995_805_696, + 0.9067144339078782e18, + 0, + 1_215_506_250_000_000_018_808_832, + 1e18 + ); + } + } else { + if (price < 0.9292922582238045e18) { + return PriceData( + 0.9523395041216094e18, + 0, + 1_102_500_000_000_000_066_060_288, + 0.9292922582238045e18, + 0, + 1_157_625_000_000_000_062_652_416, + 1e18 + ); + } else { + return PriceData( + 0.9523395041216094e18, + 0, + 1_102_500_000_000_000_066_060_288, + 0.9292922582238045e18, + 0, + 1_157_625_000_000_000_062_652_416, + 1e18 + ); + } + } + } else { + if (price < 0.9758947609062864e18) { + if (price < 0.9534239217722583e18) { + return PriceData( + 0.9758947609062864e18, + 0, + 1_050_000_000_000_000_062_914_560, + 0.9534239217722583e18, + 0, + 1_100_000_000_000_000_008_388_608, + 1e18 + ); + } else { + return PriceData( + 0.9758947609062864e18, + 0, + 1_050_000_000_000_000_062_914_560, + 0.9534239217722583e18, + 0, + 1_100_000_000_000_000_008_388_608, + 1e18 + ); + } + } else { + return PriceData( + 1.0259848410192591e18, + 0, + 950_000_000_000_000_037_748_736, + 0.9758947609062864e18, + 0, + 1_050_000_000_000_000_062_914_560, + 1e18 + ); + } + } + } + } + } + } else { + if (price < 1.8687492953303397e18) { + if (price < 1.3748886759657402e18) { + if (price < 1.172935442440526e18) { + if (price < 1.108480455013783e18) { + if (price < 1.054150295008403e18) { + if (price < 1.052684807341633e18) { + return PriceData( + 1.054150295008403e18, + 0, + 899_999_999_999_999_958_056_960, + 1.052684807341633e18, + 0, + 902_500_000_000_000_015_728_640, + 1e18 + ); + } else { + return PriceData( + 1.054150295008403e18, + 0, + 899_999_999_999_999_958_056_960, + 1.052684807341633e18, + 0, + 902_500_000_000_000_015_728_640, + 1e18 + ); + } + } else { + if (price < 1.0801613471931895e18) { + return PriceData( + 1.108480455013783e18, + 0, + 814_506_250_000_000_029_294_592, + 1.0801613471931895e18, + 0, + 857_374_999_999_999_981_387_776, + 1e18 + ); + } else { + return PriceData( + 1.108480455013783e18, + 0, + 814_506_250_000_000_029_294_592, + 1.0801613471931895e18, + 0, + 857_374_999_999_999_981_387_776, + 1e18 + ); + } + } + } else { + if (price < 1.1377127905684534e18) { + if (price < 1.1115968591227412e18) { + return PriceData( + 1.1377127905684534e18, + 0, + 773_780_937_499_999_954_010_112, + 1.1115968591227412e18, + 0, + 810_000_000_000_000_029_360_128, + 1e18 + ); + } else { + return PriceData( + 1.1377127905684534e18, + 0, + 773_780_937_499_999_954_010_112, + 1.1115968591227412e18, + 0, + 810_000_000_000_000_029_360_128, + 1e18 + ); + } + } else { + if (price < 1.1679338065313114e18) { + return PriceData( + 1.172935442440526e18, + 0, + 729_000_000_000_000_039_845_888, + 1.1679338065313114e18, + 0, + 735_091_890_625_000_016_707_584, + 1e18 + ); + } else { + return PriceData( + 1.172935442440526e18, + 0, + 729_000_000_000_000_039_845_888, + 1.1679338065313114e18, + 0, + 735_091_890_625_000_016_707_584, + 1e18 + ); + } + } + } + } else { + if (price < 1.2653583277434033e18) { + if (price < 1.2316684918193181e18) { + if (price < 1.19922388606303e18) { + return PriceData( + 1.2316684918193181e18, + 0, + 663_420_431_289_062_394_953_728, + 1.19922388606303e18, + 0, + 698_337_296_093_749_868_232_704, + 1e18 + ); + } else { + return PriceData( + 1.2316684918193181e18, + 0, + 663_420_431_289_062_394_953_728, + 1.19922388606303e18, + 0, + 698_337_296_093_749_868_232_704, + 1e18 + ); + } + } else { + if (price < 1.2388474712160897e18) { + return PriceData( + 1.2653583277434033e18, + 0, + 630_249_409_724_609_181_253_632, + 1.2388474712160897e18, + 0, + 656_100_000_000_000_049_283_072, + 1e18 + ); + } else { + return PriceData( + 1.2653583277434033e18, + 0, + 630_249_409_724_609_181_253_632, + 1.2388474712160897e18, + 0, + 656_100_000_000_000_049_283_072, + 1e18 + ); + } + } + } else { + if (price < 1.3101053721805707e18) { + if (price < 1.3003895149140976e18) { + return PriceData( + 1.3101053721805707e18, + 0, + 590_490_000_000_000_030_932_992, + 1.3003895149140976e18, + 0, + 598_736_939_238_378_782_588_928, + 1e18 + ); + } else { + return PriceData( + 1.3101053721805707e18, + 0, + 590_490_000_000_000_030_932_992, + 1.3003895149140976e18, + 0, + 598_736_939_238_378_782_588_928, + 1e18 + ); + } + } else { + if (price < 1.3368637826453649e18) { + return PriceData( + 1.3748886759657402e18, + 0, + 540_360_087_662_636_788_875_264, + 1.3368637826453649e18, + 0, + 568_800_092_276_459_816_615_936, + 1e18 + ); + } else { + return PriceData( + 1.3748886759657402e18, + 0, + 540_360_087_662_636_788_875_264, + 1.3368637826453649e18, + 0, + 568_800_092_276_459_816_615_936, + 1e18 + ); + } + } + } + } + } else { + if (price < 1.5924736308372711e18) { + if (price < 1.4722424340099083e18) { + if (price < 1.4145777805482114e18) { + if (price < 1.387578868152878e18) { + return PriceData( + 1.4145777805482114e18, + 0, + 513_342_083_279_504_885_678_080, + 1.387578868152878e18, + 0, + 531_440_999_999_999_980_863_488, + 1e18 + ); + } else { + return PriceData( + 1.4145777805482114e18, + 0, + 513_342_083_279_504_885_678_080, + 1.387578868152878e18, + 0, + 531_440_999_999_999_980_863_488, + 1e18 + ); + } + } else { + if (price < 1.4560509661144443e18) { + return PriceData( + 1.4722424340099083e18, + 0, + 478_296_899_999_999_996_198_912, + 1.4560509661144443e18, + 0, + 487_674_979_115_529_607_839_744, + 1e18 + ); + } else { + return PriceData( + 1.4722424340099083e18, + 0, + 478_296_899_999_999_996_198_912, + 1.4560509661144443e18, + 0, + 487_674_979_115_529_607_839_744, + 1e18 + ); + } + } + } else { + if (price < 1.544862076961942e18) { + if (price < 1.4994346493022137e18) { + return PriceData( + 1.544862076961942e18, + 0, + 440_126_668_651_765_444_902_912, + 1.4994346493022137e18, + 0, + 463_291_230_159_753_114_025_984, + 1e18 + ); + } else { + return PriceData( + 1.544862076961942e18, + 0, + 440_126_668_651_765_444_902_912, + 1.4994346493022137e18, + 0, + 463_291_230_159_753_114_025_984, + 1e18 + ); + } + } else { + if (price < 1.5651840818577563e18) { + return PriceData( + 1.5924736308372711e18, + 0, + 418_120_335_219_177_149_169_664, + 1.5651840818577563e18, + 0, + 430_467_210_000_000_016_711_680, + 1e18 + ); + } else { + return PriceData( + 1.5924736308372711e18, + 0, + 418_120_335_219_177_149_169_664, + 1.5651840818577563e18, + 0, + 430_467_210_000_000_016_711_680, + 1e18 + ); + } + } + } + } else { + if (price < 1.7499309221597035e18) { + if (price < 1.6676156306366652e18) { + if (price < 1.642417154585352e18) { + return PriceData( + 1.6676156306366652e18, + 0, + 387_420_489_000_000_015_040_512, + 1.642417154585352e18, + 0, + 397_214_318_458_218_211_180_544, + 1e18 + ); + } else { + return PriceData( + 1.6676156306366652e18, + 0, + 387_420_489_000_000_015_040_512, + 1.642417154585352e18, + 0, + 397_214_318_458_218_211_180_544, + 1e18 + ); + } + } else { + if (price < 1.6948483041045312e18) { + return PriceData( + 1.7499309221597035e18, + 0, + 358_485_922_408_541_867_474_944, + 1.6948483041045312e18, + 0, + 377_353_602_535_307_320_754_176, + 1e18 + ); + } else { + return PriceData( + 1.7499309221597035e18, + 0, + 358_485_922_408_541_867_474_944, + 1.6948483041045312e18, + 0, + 377_353_602_535_307_320_754_176, + 1e18 + ); + } + } + } else { + if (price < 1.8078374383281453e18) { + if (price < 1.7808846093213746e18) { + return PriceData( + 1.8078374383281453e18, + 0, + 340_561_626_288_114_794_233_856, + 1.7808846093213746e18, + 0, + 348_678_440_100_000_033_669_120, + 1e18 + ); + } else { + return PriceData( + 1.8078374383281453e18, + 0, + 340_561_626_288_114_794_233_856, + 1.7808846093213746e18, + 0, + 348_678_440_100_000_033_669_120, + 1e18 + ); + } + } else { + return PriceData( + 1.8687492953303397e18, + 0, + 323_533_544_973_709_067_943_936, + 1.8078374383281453e18, + 0, + 340_561_626_288_114_794_233_856, + 1e18 + ); + } + } + } + } + } else { + if (price < 2.690522555073485e18) { + if (price < 2.2254297444346713e18) { + if (price < 2.0460875899097033e18) { + if (price < 1.9328574028605179e18) { + if (price < 1.9064879443100322e18) { + return PriceData( + 1.9328574028605179e18, + 0, + 307_356_867_725_023_611_191_296, + 1.9064879443100322e18, + 0, + 313_810_596_089_999_996_747_776, + 1e18 + ); + } else { + return PriceData( + 1.9328574028605179e18, + 0, + 307_356_867_725_023_611_191_296, + 1.9064879443100322e18, + 0, + 313_810_596_089_999_996_747_776, + 1e18 + ); + } + } else { + if (price < 2.000362620089605e18) { + return PriceData( + 2.0460875899097033e18, + 0, + 282_429_536_481_000_023_916_544, + 2.000362620089605e18, + 0, + 291_989_024_338_772_393_721_856, + 1e18 + ); + } else { + return PriceData( + 2.0460875899097033e18, + 0, + 282_429_536_481_000_023_916_544, + 2.000362620089605e18, + 0, + 291_989_024_338_772_393_721_856, + 1e18 + ); + } + } + } else { + if (price < 2.146420673410915e18) { + if (price < 2.0714762680784395e18) { + return PriceData( + 2.146420673410915e18, + 0, + 263_520_094_465_742_052_786_176, + 2.0714762680784395e18, + 0, + 277_389_573_121_833_787_457_536, + 1e18 + ); + } else { + return PriceData( + 2.146420673410915e18, + 0, + 263_520_094_465_742_052_786_176, + 2.0714762680784395e18, + 0, + 277_389_573_121_833_787_457_536, + 1e18 + ); + } + } else { + if (price < 2.2015282751469254e18) { + return PriceData( + 2.2254297444346713e18, + 0, + 250_344_089_742_454_930_014_208, + 2.2015282751469254e18, + 0, + 254_186_582_832_900_011_458_560, + 1e18 + ); + } else { + return PriceData( + 2.2254297444346713e18, + 0, + 250_344_089_742_454_930_014_208, + 2.2015282751469254e18, + 0, + 254_186_582_832_900_011_458_560, + 1e18 + ); + } + } + } + } else { + if (price < 2.4893708294330623e18) { + if (price < 2.374857559565706e18) { + if (price < 2.3087495815807664e18) { + return PriceData( + 2.374857559565706e18, + 0, + 228_767_924_549_610_027_089_920, + 2.3087495815807664e18, + 0, + 237_826_885_255_332_165_058_560, + 1e18 + ); + } else { + return PriceData( + 2.374857559565706e18, + 0, + 228_767_924_549_610_027_089_920, + 2.3087495815807664e18, + 0, + 237_826_885_255_332_165_058_560, + 1e18 + ); + } + } else { + if (price < 2.3966391233230944e18) { + return PriceData( + 2.4893708294330623e18, + 0, + 214_638_763_942_937_242_894_336, + 2.3966391233230944e18, + 0, + 225_935_540_992_565_540_028_416, + 1e18 + ); + } else { + return PriceData( + 2.4893708294330623e18, + 0, + 214_638_763_942_937_242_894_336, + 2.3966391233230944e18, + 0, + 225_935_540_992_565_540_028_416, + 1e18 + ); + } + } + } else { + if (price < 2.5872314032850503e18) { + if (price < 2.5683484144734416e18) { + return PriceData( + 2.5872314032850503e18, + 0, + 203_906_825_745_790_394_171_392, + 2.5683484144734416e18, + 0, + 205_891_132_094_649_041_158_144, + 1e18 + ); + } else { + return PriceData( + 2.5872314032850503e18, + 0, + 203_906_825_745_790_394_171_392, + 2.5683484144734416e18, + 0, + 205_891_132_094_649_041_158_144, + 1e18 + ); + } + } else { + return PriceData( + 2.690522555073485e18, + 0, + 193_711_484_458_500_834_197_504, + 2.5872314032850503e18, + 0, + 203_906_825_745_790_394_171_392, + 1e18 + ); + } + } + } + } else { + if (price < 3.3001527441316973e18) { + if (price < 3.0261889293694004e18) { + if (price < 2.7995618079130224e18) { + if (price < 2.7845245737305033e18) { + return PriceData( + 2.7995618079130224e18, + 0, + 184_025_910_235_575_779_065_856, + 2.7845245737305033e18, + 0, + 185_302_018_885_184_143_753_216, + 1e18 + ); + } else { + return PriceData( + 2.7995618079130224e18, + 0, + 184_025_910_235_575_779_065_856, + 2.7845245737305033e18, + 0, + 185_302_018_885_184_143_753_216, + 1e18 + ); + } + } else { + if (price < 2.9146833489094695e18) { + return PriceData( + 3.0261889293694004e18, + 0, + 166_771_816_996_665_739_444_224, + 2.9146833489094695e18, + 0, + 174_824_614_723_796_969_979_904, + 1e18 + ); + } else { + return PriceData( + 3.0261889293694004e18, + 0, + 166_771_816_996_665_739_444_224, + 2.9146833489094695e18, + 0, + 174_824_614_723_796_969_979_904, + 1e18 + ); + } + } + } else { + if (price < 3.1645988027761267e18) { + if (price < 3.0362389274108987e18) { + return PriceData( + 3.1645988027761267e18, + 0, + 157_779_214_788_226_770_272_256, + 3.0362389274108987e18, + 0, + 166_083_383_987_607_124_836_352, + 1e18 + ); + } else { + return PriceData( + 3.1645988027761267e18, + 0, + 157_779_214_788_226_770_272_256, + 3.0362389274108987e18, + 0, + 166_083_383_987_607_124_836_352, + 1e18 + ); + } + } else { + if (price < 3.2964552820220048e18) { + return PriceData( + 3.3001527441316973e18, + 0, + 149_890_254_048_815_411_625_984, + 3.2964552820220048e18, + 0, + 150_094_635_296_999_155_433_472, + 1e18 + ); + } else { + return PriceData( + 3.3001527441316973e18, + 0, + 149_890_254_048_815_411_625_984, + 3.2964552820220048e18, + 0, + 150_094_635_296_999_155_433_472, + 1e18 + ); + } + } + } + } else { + if (price < 3.754191847846751e18) { + if (price < 3.5945058336600226e18) { + if (price < 3.4433110847289314e18) { + return PriceData( + 3.5945058336600226e18, + 0, + 135_275_954_279_055_877_996_544, + 3.4433110847289314e18, + 0, + 142_395_741_346_374_623_428_608, + 1e18 + ); + } else { + return PriceData( + 3.5945058336600226e18, + 0, + 135_275_954_279_055_877_996_544, + 3.4433110847289314e18, + 0, + 142_395_741_346_374_623_428_608, + 1e18 + ); + } + } else { + if (price < 3.5987837944717675e18) { + return PriceData( + 3.754191847846751e18, + 0, + 128_512_156_565_103_091_646_464, + 3.5987837944717675e18, + 0, + 135_085_171_767_299_243_245_568, + 1e18 + ); + } else { + return PriceData( + 3.754191847846751e18, + 0, + 128_512_156_565_103_091_646_464, + 3.5987837944717675e18, + 0, + 135_085_171_767_299_243_245_568, + 1e18 + ); + } + } + } else { + if (price < 9.922382968146263e18) { + if (price < 3.9370205389323547e18) { + return PriceData( + 9.922382968146263e18, + 0, + 44_999_999_999_999_997_902_848, + 3.9370205389323547e18, + 0, + 121_576_654_590_569_315_565_568, + 1e18 + ); + } else { + return PriceData( + 9.922382968146263e18, + 0, + 44_999_999_999_999_997_902_848, + 3.9370205389323547e18, + 0, + 121_576_654_590_569_315_565_568, + 1e18 + ); + } + } else { + revert("Price is too High, well is bricked"); + } + } + } + } + } + } + } + + function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) { + // todo, remove once LUT is updated. + price = price * 1e12; + if (price < 0.9837578538258441e18) { + if (price < 0.7657894226400184e18) { + if (price < 0.6853356086625426e18) { + if (price < 0.6456640806168077e18) { + if (price < 0.5892475765452702e18) { + if (price < 0.5536123757558428e18) { + if (price < 0.010442230679169563e18) { + revert("Price is too low, Well is bricked!"); + } else { + return PriceData( + 0.5536123757558428e18, + 1_599_999_999_999_999_865_782_272, + 545_614_743_365_999_923_298_304, + 0.010442230679169563e18, + 6_271_998_357_018_457_453_625_344, + 30_170_430_329_999_536_947_200, + 1e18 + ); + } + } else { + if (price < 0.5712351217876162e18) { + return PriceData( + 0.5892475765452702e18, + 1_540_000_000_000_000_092_274_688, + 579_889_286_297_999_932_129_280, + 0.5712351217876162e18, + 1_569_999_999_999_999_979_028_480, + 562_484_780_749_999_921_168_384, + 1e18 + ); + } else { + return PriceData( + 0.5892475765452702e18, + 1_540_000_000_000_000_092_274_688, + 579_889_286_297_999_932_129_280, + 0.5712351217876162e18, + 1_569_999_999_999_999_979_028_480, + 562_484_780_749_999_921_168_384, + 1e18 + ); + } + } + } else { + if (price < 0.6264561035067443e18) { + if (price < 0.6076529363710561e18) { + return PriceData( + 0.6264561035067443e18, + 1_480_000_000_000_000_050_331_648, + 616_348_759_373_999_889_186_816, + 0.6076529363710561e18, + 1_509_999_999_999_999_937_085_440, + 597_839_994_250_999_858_462_720, + 1e18 + ); + } else { + return PriceData( + 0.6264561035067443e18, + 1_480_000_000_000_000_050_331_648, + 616_348_759_373_999_889_186_816, + 0.6076529363710561e18, + 1_509_999_999_999_999_937_085_440, + 597_839_994_250_999_858_462_720, + 1e18 + ); + } + } else { + if (price < 0.6418277116264056e18) { + return PriceData( + 0.6456640806168077e18, + 1_449_999_999_999_999_895_142_400, + 635_427_613_953_999_981_510_656, + 0.6418277116264056e18, + 1_456_009_992_965_810_341_019_648, + 631_633_461_631_000_017_633_280, + 1e18 + ); + } else { + return PriceData( + 0.6456640806168077e18, + 1_449_999_999_999_999_895_142_400, + 635_427_613_953_999_981_510_656, + 0.6418277116264056e18, + 1_456_009_992_965_810_341_019_648, + 631_633_461_631_000_017_633_280, + 1e18 + ); + } + } + } + } else { + if (price < 0.6652864026252798e18) { + if (price < 0.6548068731524411e18) { + if (price < 0.6482942041630876e18) { + return PriceData( + 0.6548068731524411e18, + 1_436_009_992_965_810_416_517_120, + 644_598_199_151_000_104_730_624, + 0.6482942041630876e18, + 1_446_009_992_965_810_378_768_384, + 638_083_385_911_000_032_083_968, + 1e18 + ); + } else { + return PriceData( + 0.6548068731524411e18, + 1_436_009_992_965_810_416_517_120, + 644_598_199_151_000_104_730_624, + 0.6482942041630876e18, + 1_446_009_992_965_810_378_768_384, + 638_083_385_911_000_032_083_968, + 1e18 + ); + } + } else { + if (price < 0.6613661573609778e18) { + return PriceData( + 0.6652864026252798e18, + 1_420_000_000_000_000_008_388_608, + 655_088_837_188_999_989_690_368, + 0.6613661573609778e18, + 1_426_009_992_965_810_454_265_856, + 651_178_365_231_000_060_952_576, + 1e18 + ); + } else { + return PriceData( + 0.6652864026252798e18, + 1_420_000_000_000_000_008_388_608, + 655_088_837_188_999_989_690_368, + 0.6613661573609778e18, + 1_426_009_992_965_810_454_265_856, + 651_178_365_231_000_060_952_576, + 1e18 + ); + } + } + } else { + if (price < 0.6746265282007935e18) { + if (price < 0.667972535466219e18) { + return PriceData( + 0.6746265282007935e18, + 1_406_009_992_965_810_529_763_328, + 664_536_634_765_000_046_542_848, + 0.667972535466219e18, + 1_416_009_992_965_810_492_014_592, + 657_824_352_616_000_123_830_272, + 1e18 + ); + } else { + return PriceData( + 0.6746265282007935e18, + 1_406_009_992_965_810_529_763_328, + 664_536_634_765_000_046_542_848, + 0.667972535466219e18, + 1_416_009_992_965_810_492_014_592, + 657_824_352_616_000_123_830_272, + 1e18 + ); + } + } else { + if (price < 0.6813287005626545e18) { + return PriceData( + 0.6853356086625426e18, + 1_390_000_000_000_000_121_634_816, + 675_345_038_118_000_013_606_912, + 0.6813287005626545e18, + 1_396_009_992_965_810_567_512_064, + 671_315_690_563_000_076_337_152, + 1e18 + ); + } else { + return PriceData( + 0.6853356086625426e18, + 1_390_000_000_000_000_121_634_816, + 675_345_038_118_000_013_606_912, + 0.6813287005626545e18, + 1_396_009_992_965_810_567_512_064, + 671_315_690_563_000_076_337_152, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.7267829951477605e18) { + if (price < 0.7058277579915956e18) { + if (price < 0.6948800792282216e18) { + if (price < 0.6880796640937009e18) { + return PriceData( + 0.6948800792282216e18, + 1_376_009_992_965_810_374_574_080, + 685_076_068_526_999_994_368_000, + 0.6880796640937009e18, + 1_386_009_992_965_810_336_825_344, + 678_162_004_775_999_991_971_840, + 1e18 + ); + } else { + return PriceData( + 0.6948800792282216e18, + 1_376_009_992_965_810_374_574_080, + 685_076_068_526_999_994_368_000, + 0.6880796640937009e18, + 1_386_009_992_965_810_336_825_344, + 678_162_004_775_999_991_971_840, + 1e18 + ); + } + } else { + if (price < 0.7017306577163355e18) { + return PriceData( + 0.7058277579915956e18, + 1_359_999_999_999_999_966_445_568, + 696_209_253_362_999_968_661_504, + 0.7017306577163355e18, + 1_366_009_992_965_810_412_322_816, + 692_058_379_796_999_917_535_232, + 1e18 + ); + } else { + return PriceData( + 0.7058277579915956e18, + 1_359_999_999_999_999_966_445_568, + 696_209_253_362_999_968_661_504, + 0.7017306577163355e18, + 1_366_009_992_965_810_412_322_816, + 692_058_379_796_999_917_535_232, + 1e18 + ); + } + } + } else { + if (price < 0.7155854234239166e18) { + if (price < 0.7086321651255534e18) { + return PriceData( + 0.7155854234239166e18, + 1_346_009_992_965_810_487_820_288, + 706_229_774_288_999_996_719_104, + 0.7086321651255534e18, + 1_356_009_992_965_810_450_071_552, + 699_109_443_950_999_980_998_656, + 1e18 + ); + } else { + return PriceData( + 0.7155854234239166e18, + 1_346_009_992_965_810_487_820_288, + 706_229_774_288_999_996_719_104, + 0.7086321651255534e18, + 1_356_009_992_965_810_450_071_552, + 699_109_443_950_999_980_998_656, + 1e18 + ); + } + } else { + if (price < 0.7225913136485337e18) { + return PriceData( + 0.7267829951477605e18, + 1_330_000_000_000_000_079_691_776, + 717_695_061_066_999_981_408_256, + 0.7225913136485337e18, + 1_336_009_992_965_810_525_569_024, + 713_419_892_621_999_983_296_512, + 1e18 + ); + } else { + return PriceData( + 0.7267829951477605e18, + 1_330_000_000_000_000_079_691_776, + 717_695_061_066_999_981_408_256, + 0.7225913136485337e18, + 1_336_009_992_965_810_525_569_024, + 713_419_892_621_999_983_296_512, + 1e18 + ); + } + } + } + } else { + if (price < 0.7482261712801425e18) { + if (price < 0.7367648260299406e18) { + if (price < 0.7296507786665005e18) { + return PriceData( + 0.7367648260299406e18, + 1_316_009_992_965_810_332_631_040, + 728_011_626_732_999_961_214_976, + 0.7296507786665005e18, + 1_326_009_992_965_810_563_317_760, + 720_680_329_877_999_918_776_320, + 1e18 + ); + } else { + return PriceData( + 0.7367648260299406e18, + 1_316_009_992_965_810_332_631_040, + 728_011_626_732_999_961_214_976, + 0.7296507786665005e18, + 1_326_009_992_965_810_563_317_760, + 720_680_329_877_999_918_776_320, + 1e18 + ); + } + } else { + if (price < 0.7439345309340744e18) { + return PriceData( + 0.7482261712801425e18, + 1_299_999_999_999_999_924_502_528, + 739_816_712_606_999_965_073_408, + 0.7439345309340744e18, + 1_306_009_992_965_810_370_379_776, + 735_414_334_273_999_920_431_104, + 1e18 + ); + } else { + return PriceData( + 0.7482261712801425e18, + 1_299_999_999_999_999_924_502_528, + 739_816_712_606_999_965_073_408, + 0.7439345309340744e18, + 1_306_009_992_965_810_370_379_776, + 735_414_334_273_999_920_431_104, + 1e18 + ); + } + } + } else { + if (price < 0.7584455708586255e18) { + if (price < 0.7511610392809491e18) { + return PriceData( + 0.7584455708586255e18, + 1_286_009_992_965_810_445_877_248, + 750_436_241_990_999_850_614_784, + 0.7511610392809491e18, + 1_296_009_992_965_810_408_128_512, + 742_889_014_688_999_846_969_344, + 1e18 + ); + } else { + return PriceData( + 0.7584455708586255e18, + 1_286_009_992_965_810_445_877_248, + 750_436_241_990_999_850_614_784, + 0.7511610392809491e18, + 1_296_009_992_965_810_408_128_512, + 742_889_014_688_999_846_969_344, + 1e18 + ); + } + } else { + return PriceData( + 0.7657894226400184e18, + 1_276_009_992_965_810_483_625_984, + 758_056_602_771_999_862_816_768, + 0.7584455708586255e18, + 1_286_009_992_965_810_445_877_248, + 750_436_241_990_999_850_614_784, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.8591362583263777e18) { + if (price < 0.8111815071890757e18) { + if (price < 0.7881910996771402e18) { + if (price < 0.7731939722121913e18) { + if (price < 0.7701875308591448e18) { + return PriceData( + 0.7731939722121913e18, + 1_266_009_992_965_810_521_374_720, + 765_750_696_993_999_871_279_104, + 0.7701875308591448e18, + 1_270_000_000_000_000_037_748_736, + 762_589_283_900_000_049_299_456, + 1e18 + ); + } else { + return PriceData( + 0.7731939722121913e18, + 1_266_009_992_965_810_521_374_720, + 765_750_696_993_999_871_279_104, + 0.7701875308591448e18, + 1_270_000_000_000_000_037_748_736, + 762_589_283_900_000_049_299_456, + 1e18 + ); + } + } else { + if (price < 0.7806606813398431e18) { + return PriceData( + 0.7881910996771402e18, + 1_246_009_992_965_810_596_872_192, + 781_362_557_425_999_864_135_680, + 0.7806606813398431e18, + 1_256_009_992_965_810_559_123_456, + 773_519_138_809_999_964_241_920, + 1e18 + ); + } else { + return PriceData( + 0.7881910996771402e18, + 1_246_009_992_965_810_596_872_192, + 781_362_557_425_999_864_135_680, + 0.7806606813398431e18, + 1_256_009_992_965_810_559_123_456, + 773_519_138_809_999_964_241_920, + 1e18 + ); + } + } + } else { + if (price < 0.7957868686334806e18) { + if (price < 0.792703475602566e18) { + return PriceData( + 0.7957868686334806e18, + 1_236_009_992_965_810_366_185_472, + 789_281_597_997_999_894_560_768, + 0.792703475602566e18, + 1_239_999_999_999_999_882_559_488, + 786_028_848_437_000_042_184_704, + 1e18 + ); + } else { + return PriceData( + 0.7957868686334806e18, + 1_236_009_992_965_810_366_185_472, + 789_281_597_997_999_894_560_768, + 0.792703475602566e18, + 1_239_999_999_999_999_882_559_488, + 786_028_848_437_000_042_184_704, + 1e18 + ); + } + } else { + if (price < 0.8034497254059905e18) { + return PriceData( + 0.8111815071890757e18, + 1_216_009_992_965_810_441_682_944, + 805_349_211_052_999_895_416_832, + 0.8034497254059905e18, + 1_226_009_992_965_810_403_934_208, + 797_276_922_569_999_826_026_496, + 1e18 + ); + } else { + return PriceData( + 0.8111815071890757e18, + 1_216_009_992_965_810_441_682_944, + 805_349_211_052_999_895_416_832, + 0.8034497254059905e18, + 1_226_009_992_965_810_403_934_208, + 797_276_922_569_999_826_026_496, + 1e18 + ); + } + } + } + } else { + if (price < 0.8348103683034702e18) { + if (price < 0.8189841555727294e18) { + if (price < 0.8158174207616607e18) { + return PriceData( + 0.8189841555727294e18, + 1_206_009_992_965_810_479_431_680, + 813_499_162_245_999_760_506_880, + 0.8158174207616607e18, + 1_209_999_999_999_999_995_805_696, + 810_152_674_566_000_114_401_280, + 1e18 + ); + } else { + return PriceData( + 0.8189841555727294e18, + 1_206_009_992_965_810_479_431_680, + 813_499_162_245_999_760_506_880, + 0.8158174207616607e18, + 1_209_999_999_999_999_995_805_696, + 810_152_674_566_000_114_401_280, + 1e18 + ); + } + } else { + if (price < 0.8268597211437622e18) { + return PriceData( + 0.8348103683034702e18, + 1_186_009_992_965_810_420_711_424, + 830_034_948_846_999_811_653_632, + 0.8268597211437622e18, + 1_196_009_992_965_810_517_180_416, + 821_727_494_902_999_775_444_992, + 1e18 + ); + } else { + return PriceData( + 0.8348103683034702e18, + 1_186_009_992_965_810_420_711_424, + 830_034_948_846_999_811_653_632, + 0.8268597211437622e18, + 1_196_009_992_965_810_517_180_416, + 821_727_494_902_999_775_444_992, + 1e18 + ); + } + } + } else { + if (price < 0.842838380316301e18) { + if (price < 0.8395807629721064e18) { + return PriceData( + 0.842838380316301e18, + 1_176_009_992_965_810_458_460_160, + 838_422_286_131_999_841_189_888, + 0.8395807629721064e18, + 1_179_999_999_999_999_974_834_176, + 834_979_450_092_000_118_833_152, + 1e18 + ); + } else { + return PriceData( + 0.842838380316301e18, + 1_176_009_992_965_810_458_460_160, + 838_422_286_131_999_841_189_888, + 0.8395807629721064e18, + 1_179_999_999_999_999_974_834_176, + 834_979_450_092_000_118_833_152, + 1e18 + ); + } + } else { + if (price < 0.8509461646072527e18) { + return PriceData( + 0.8591362583263777e18, + 1_156_009_992_965_810_399_739_904, + 855_439_777_442_999_782_342_656, + 0.8509461646072527e18, + 1_166_009_992_965_810_496_208_896, + 846_890_292_257_999_745_449_984, + 1e18 + ); + } else { + return PriceData( + 0.8591362583263777e18, + 1_156_009_992_965_810_399_739_904, + 855_439_777_442_999_782_342_656, + 0.8509461646072527e18, + 1_166_009_992_965_810_496_208_896, + 846_890_292_257_999_745_449_984, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.9101652846401054e18) { + if (price < 0.8842278383622283e18) { + if (price < 0.8674113341943706e18) { + if (price < 0.8640539838258433e18) { + return PriceData( + 0.8674113341943706e18, + 1_146_009_992_965_810_437_488_640, + 864_071_577_944_999_841_497_088, + 0.8640539838258433e18, + 1_149_999_999_999_999_953_862_656, + 860_529_537_869_000_136_982_528, + 1e18 + ); + } else { + return PriceData( + 0.8674113341943706e18, + 1_146_009_992_965_810_437_488_640, + 864_071_577_944_999_841_497_088, + 0.8640539838258433e18, + 1_149_999_999_999_999_953_862_656, + 860_529_537_869_000_136_982_528, + 1e18 + ); + } + } else { + if (price < 0.8757742066564991e18) { + return PriceData( + 0.8842278383622283e18, + 1_126_009_992_965_810_512_986_112, + 881_585_608_519_999_884_886_016, + 0.8757742066564991e18, + 1_136_009_992_965_810_475_237_376, + 872_786_557_449_999_864_561_664, + 1e18 + ); + } else { + return PriceData( + 0.8842278383622283e18, + 1_126_009_992_965_810_512_986_112, + 881_585_608_519_999_884_886_016, + 0.8757742066564991e18, + 1_136_009_992_965_810_475_237_376, + 872_786_557_449_999_864_561_664, + 1e18 + ); + } + } + } else { + if (price < 0.8927753469982641e18) { + if (price < 0.8893079194806377e18) { + return PriceData( + 0.8927753469982641e18, + 1_116_009_992_965_810_416_517_120, + 890_469_654_111_999_903_137_792, + 0.8893079194806377e18, + 1_120_000_000_000_000_067_108_864, + 886_825_266_904_000_064_651_264, + 1e18 + ); + } else { + return PriceData( + 0.8927753469982641e18, + 1_116_009_992_965_810_416_517_120, + 890_469_654_111_999_903_137_792, + 0.8893079194806377e18, + 1_120_000_000_000_000_067_108_864, + 886_825_266_904_000_064_651_264, + 1e18 + ); + } + } else { + if (price < 0.9014200124952659e18) { + return PriceData( + 0.9101652846401054e18, + 1_096_009_992_965_810_492_014_592, + 908_496_582_238_999_955_898_368, + 0.9014200124952659e18, + 1_106_009_992_965_810_454_265_856, + 899_439_649_160_999_875_903_488, + 1e18 + ); + } else { + return PriceData( + 0.9101652846401054e18, + 1_096_009_992_965_810_492_014_592, + 908_496_582_238_999_955_898_368, + 0.9014200124952659e18, + 1_106_009_992_965_810_454_265_856, + 899_439_649_160_999_875_903_488, + 1e18 + ); + } + } + } + } else { + if (price < 0.93704195898873e18) { + if (price < 0.9190147911249779e18) { + if (price < 0.9154252342593594e18) { + return PriceData( + 0.9190147911249779e18, + 1_086_009_992_965_810_529_763_328, + 917_641_477_296_999_901_429_760, + 0.9154252342593594e18, + 1_090_000_000_000_000_046_137_344, + 913_891_264_499_999_987_204_096, + 1e18 + ); + } else { + return PriceData( + 0.9190147911249779e18, + 1_086_009_992_965_810_529_763_328, + 917_641_477_296_999_901_429_760, + 0.9154252342593594e18, + 1_090_000_000_000_000_046_137_344, + 913_891_264_499_999_987_204_096, + 1e18 + ); + } + } else { + if (price < 0.9279723460598399e18) { + return PriceData( + 0.93704195898873e18, + 1_066_009_992_965_810_471_043_072, + 936_199_437_051_999_863_963_648, + 0.9279723460598399e18, + 1_076_009_992_965_810_433_294_336, + 926_875_395_482_999_811_211_264, + 1e18 + ); + } else { + return PriceData( + 0.93704195898873e18, + 1_066_009_992_965_810_471_043_072, + 936_199_437_051_999_863_963_648, + 0.9279723460598399e18, + 1_076_009_992_965_810_433_294_336, + 926_875_395_482_999_811_211_264, + 1e18 + ); + } + } + } else { + if (price < 0.97065046122351e18) { + if (price < 0.9425021457735017e18) { + return PriceData( + 0.97065046122351e18, + 1_030_000_000_000_000_004_194_304, + 970_446_402_236_000_094_912_512, + 0.9425021457735017e18, + 1_060_000_000_000_000_025_165_824, + 941_754_836_243_000_007_327_744, + 1e18 + ); + } else { + return PriceData( + 0.97065046122351e18, + 1_030_000_000_000_000_004_194_304, + 970_446_402_236_000_094_912_512, + 0.9425021457735017e18, + 1_060_000_000_000_000_025_165_824, + 941_754_836_243_000_007_327_744, + 1e18 + ); + } + } else { + return PriceData( + 0.9837578538258441e18, + 1_016_574_698_021_103_146_631_168, + 983_823_407_046_999_249_256_448, + 0.97065046122351e18, + 1_030_000_000_000_000_004_194_304, + 970_446_402_236_000_094_912_512, + 1e18 + ); + } + } + } + } + } + } else { + if (price < 1.2781476871714348e18) { + if (price < 1.1479740676895633e18) { + if (price < 1.08346873509363e18) { + if (price < 1.052270281144944e18) { + if (price < 1.04200618543826e18) { + if (price < 1.0137361745263807e18) { + return PriceData( + 1.04200618543826e18, + 959_393_930_406_635_507_810_304, + 1_041_633_461_631_000_080_547_840, + 1.0137361745263807e18, + 986_536_714_103_430_341_197_824, + 1_013_823_407_046_999_136_010_240, + 1e18 + ); + } else { + return PriceData( + 1.04200618543826e18, + 959_393_930_406_635_507_810_304, + 1_041_633_461_631_000_080_547_840, + 1.0137361745263807e18, + 986_536_714_103_430_341_197_824, + 1_013_823_407_046_999_136_010_240, + 1e18 + ); + } + } else { + if (price < 1.0442014332455682e18) { + return PriceData( + 1.052270281144944e18, + 949_844_925_094_088_539_111_424, + 1_051_633_461_631_000_042_799_104, + 1.0442014332455682e18, + 957_380_872_867_950_333_263_872, + 1_043_823_407_046_999_156_981_760, + 1e18 + ); + } else { + return PriceData( + 1.052270281144944e18, + 949_844_925_094_088_539_111_424, + 1_051_633_461_631_000_042_799_104, + 1.0442014332455682e18, + 957_380_872_867_950_333_263_872, + 1_043_823_407_046_999_156_981_760, + 1e18 + ); + } + } + } else { + if (price < 1.0729984643915151e18) { + if (price < 1.0626000128180033e18) { + return PriceData( + 1.0729984643915151e18, + 931_024_660_370_625_864_400_896, + 1_071_633_461_631_000_101_519_360, + 1.0626000128180033e18, + 940_388_903_170_712_077_860_864, + 1_061_633_461_631_000_005_050_368, + 1e18 + ); + } else { + return PriceData( + 1.0729984643915151e18, + 931_024_660_370_625_864_400_896, + 1_071_633_461_631_000_101_519_360, + 1.0626000128180033e18, + 940_388_903_170_712_077_860_864, + 1_061_633_461_631_000_005_050_368, + 1e18 + ); + } + } else { + if (price < 1.075235747233168e18) { + return PriceData( + 1.08346873509363e18, + 921_751_036_591_489_341_718_528, + 1_081_633_461_631_000_063_770_624, + 1.075235747233168e18, + 929_070_940_571_911_522_877_440, + 1_073_823_407_046_999_312_171_008, + 1e18 + ); + } else { + return PriceData( + 1.08346873509363e18, + 921_751_036_591_489_341_718_528, + 1_081_633_461_631_000_063_770_624, + 1.075235747233168e18, + 929_070_940_571_911_522_877_440, + 1_073_823_407_046_999_312_171_008, + 1e18 + ); + } + } + } + } else { + if (price < 1.1153416855406852e18) { + if (price < 1.1046372077990465e18) { + if (price < 1.094013939534592e18) { + return PriceData( + 1.1046372077990465e18, + 903_471_213_736_046_924_660_736, + 1_101_633_461_630_999_988_273_152, + 1.094013939534592e18, + 912_566_913_749_610_422_861_824, + 1_091_633_461_631_000_026_021_888, + 1e18 + ); + } else { + return PriceData( + 1.1046372077990465e18, + 903_471_213_736_046_924_660_736, + 1_101_633_461_630_999_988_273_152, + 1.094013939534592e18, + 912_566_913_749_610_422_861_824, + 1_091_633_461_631_000_026_021_888, + 1e18 + ); + } + } else { + if (price < 1.106922462140042e18) { + return PriceData( + 1.1153416855406852e18, + 894_462_896_467_921_655_037_952, + 1_111_633_461_631_000_084_742_144, + 1.106922462140042e18, + 901_574_605_299_420_214_853_632, + 1_103_823_407_046_999_333_142_528, + 1e18 + ); + } else { + return PriceData( + 1.1153416855406852e18, + 894_462_896_467_921_655_037_952, + 1_111_633_461_631_000_084_742_144, + 1.106922462140042e18, + 901_574_605_299_420_214_853_632, + 1_103_823_407_046_999_333_142_528, + 1e18 + ); + } + } + } else { + if (price < 1.1370069304852344e18) { + if (price < 1.126130534077399e18) { + return PriceData( + 1.1370069304852344e18, + 876_704_428_898_610_466_258_944, + 1_131_633_461_631_000_009_244_672, + 1.126130534077399e18, + 885_540_958_029_582_579_007_488, + 1_121_633_461_631_000_046_993_408, + 1e18 + ); + } else { + return PriceData( + 1.1370069304852344e18, + 876_704_428_898_610_466_258_944, + 1_131_633_461_631_000_009_244_672, + 1.126130534077399e18, + 885_540_958_029_582_579_007_488, + 1_121_633_461_631_000_046_993_408, + 1e18 + ); + } + } else { + if (price < 1.1393461713403175e18) { + return PriceData( + 1.1479740676895633e18, + 867_952_372_252_030_623_809_536, + 1_141_633_461_631_000_105_713_664, + 1.1393461713403175e18, + 874_862_932_837_460_311_277_568, + 1_133_823_407_046_999_354_114_048, + 1e18 + ); + } else { + return PriceData( + 1.1479740676895633e18, + 867_952_372_252_030_623_809_536, + 1_141_633_461_631_000_105_713_664, + 1.1393461713403175e18, + 874_862_932_837_460_311_277_568, + 1_133_823_407_046_999_354_114_048, + 1e18 + ); + } + } + } + } + } else { + if (price < 1.2158632690425795e18) { + if (price < 1.1814520928592147e18) { + if (price < 1.1701934159515677e18) { + if (price < 1.1590351545519821e18) { + return PriceData( + 1.1701934159515677e18, + 850_698_082_981_724_339_306_496, + 1_161_633_461_631_000_030_216_192, + 1.1590351545519821e18, + 859_283_882_348_396_836_552_704, + 1_151_633_461_631_000_067_964_928, + 1e18 + ); + } else { + return PriceData( + 1.1701934159515677e18, + 850_698_082_981_724_339_306_496, + 1_161_633_461_631_000_030_216_192, + 1.1590351545519821e18, + 859_283_882_348_396_836_552_704, + 1_151_633_461_631_000_067_964_928, + 1e18 + ); + } + } else { + if (price < 1.1725927382568915e18) { + return PriceData( + 1.1814520928592147e18, + 842_194_126_003_515_871_461_376, + 1_171_633_461_630_999_992_467_456, + 1.1725927382568915e18, + 848_909_895_863_161_958_957_056, + 1_163_823_407_046_999_375_085_568, + 1e18 + ); + } else { + return PriceData( + 1.1814520928592147e18, + 842_194_126_003_515_871_461_376, + 1_171_633_461_630_999_992_467_456, + 1.1725927382568915e18, + 848_909_895_863_161_958_957_056, + 1_163_823_407_046_999_375_085_568, + 1e18 + ); + } + } + } else { + if (price < 1.204283737929635e18) { + if (price < 1.1928144424038778e18) { + return PriceData( + 1.204283737929635e18, + 825_428_478_487_026_483_593_216, + 1_191_633_461_631_000_051_187_712, + 1.1928144424038778e18, + 833_771_189_909_386_858_332_160, + 1_181_633_461_631_000_088_936_448, + 1e18 + ); + } else { + return PriceData( + 1.204283737929635e18, + 825_428_478_487_026_483_593_216, + 1_191_633_461_631_000_051_187_712, + 1.1928144424038778e18, + 833_771_189_909_386_858_332_160, + 1_181_633_461_631_000_088_936_448, + 1e18 + ); + } + } else { + if (price < 1.206749317725993e18) { + return PriceData( + 1.2158632690425795e18, + 817_165_219_522_460_124_184_576, + 1_201_633_461_631_000_013_438_976, + 1.206749317725993e18, + 823_691_964_726_974_573_707_264, + 1_193_823_407_046_999_261_839_360, + 1e18 + ); + } else { + return PriceData( + 1.2158632690425795e18, + 817_165_219_522_460_124_184_576, + 1_201_633_461_631_000_013_438_976, + 1.206749317725993e18, + 823_691_964_726_974_573_707_264, + 1_193_823_407_046_999_261_839_360, + 1e18 + ); + } + } + } + } else { + if (price < 1.2512964165637426e18) { + if (price < 1.239366277967463e18) { + if (price < 1.227556341646634e18) { + return PriceData( + 1.239366277967463e18, + 800_874_082_725_647_358_099_456, + 1_221_633_461_630_999_937_941_504, + 1.227556341646634e18, + 808_980_663_561_772_026_822_656, + 1_211_633_461_630_999_975_690_240, + 1e18 + ); + } else { + return PriceData( + 1.239366277967463e18, + 800_874_082_725_647_358_099_456, + 1_221_633_461_630_999_937_941_504, + 1.227556341646634e18, + 808_980_663_561_772_026_822_656, + 1_211_633_461_630_999_975_690_240, + 1e18 + ); + } + } else { + if (price < 1.2419043731900452e18) { + return PriceData( + 1.2512964165637426e18, + 792_844_769_574_258_384_830_464, + 1_231_633_461_631_000_168_628_224, + 1.2419043731900452e18, + 799_187_750_396_588_808_208_384, + 1_223_823_407_046_999_417_028_608, + 1e18 + ); + } else { + return PriceData( + 1.2512964165637426e18, + 792_844_769_574_258_384_830_464, + 1_231_633_461_631_000_168_628_224, + 1.2419043731900452e18, + 799_187_750_396_588_808_208_384, + 1_223_823_407_046_999_417_028_608, + 1e18 + ); + } + } + } else { + if (price < 1.275530736456304e18) { + if (price < 1.2633501123251245e18) { + return PriceData( + 1.275530736456304e18, + 777_015_212_287_252_263_600_128, + 1_251_633_461_631_000_093_130_752, + 1.2633501123251245e18, + 784_892_036_020_190_803_132_416, + 1_241_633_461_631_000_130_879_488, + 1e18 + ); + } else { + return PriceData( + 1.275530736456304e18, + 777_015_212_287_252_263_600_128, + 1_251_633_461_631_000_093_130_752, + 1.2633501123251245e18, + 784_892_036_020_190_803_132_416, + 1_241_633_461_631_000_130_879_488, + 1e18 + ); + } + } else { + return PriceData( + 1.2781476871714348e18, + 775_377_691_936_595_793_412_096, + 1_253_823_407_046_999_303_782_400, + 1.275530736456304e18, + 777_015_212_287_252_263_600_128, + 1_251_633_461_631_000_093_130_752, + 1e18 + ); + } + } + } + } + } else { + if (price < 1.4328477724460313e18) { + if (price < 1.3542648168520204e18) { + if (price < 1.3155703630695195e18) { + if (price < 1.3002863360251475e18) { + if (price < 1.287841676446678e18) { + return PriceData( + 1.3002863360251475e18, + 761_486_700_794_138_643_660_800, + 1_271_633_461_631_000_017_633_280, + 1.287841676446678e18, + 769_213_645_913_148_350_267_392, + 1_261_633_461_631_000_055_382_016, + 1e18 + ); + } else { + return PriceData( + 1.3002863360251475e18, + 761_486_700_794_138_643_660_800, + 1_271_633_461_631_000_017_633_280, + 1.287841676446678e18, + 769_213_645_913_148_350_267_392, + 1_261_633_461_631_000_055_382_016, + 1e18 + ); + } + } else { + if (price < 1.3128681350996887e18) { + return PriceData( + 1.3155703630695195e18, + 752_243_782_340_972_617_138_176, + 1_283_823_407_046_999_190_536_192, + 1.3128681350996887e18, + 753_833_756_269_910_991_831_040, + 1_281_633_461_630_999_979_884_544, + 1e18 + ); + } else { + return PriceData( + 1.3155703630695195e18, + 752_243_782_340_972_617_138_176, + 1_283_823_407_046_999_190_536_192, + 1.3128681350996887e18, + 753_833_756_269_910_991_831_040, + 1_281_633_461_630_999_979_884_544, + 1e18 + ); + } + } + } else { + if (price < 1.338456911792658e18) { + if (price < 1.325590509681383e18) { + return PriceData( + 1.338456911792658e18, + 738_747_458_359_333_922_799_616, + 1_301_633_461_631_000_172_822_528, + 1.325590509681383e18, + 746_254_206_247_018_816_339_968, + 1_291_633_461_630_999_942_135_808, + 1e18 + ); + } else { + return PriceData( + 1.338456911792658e18, + 738_747_458_359_333_922_799_616, + 1_301_633_461_631_000_172_822_528, + 1.325590509681383e18, + 746_254_206_247_018_816_339_968, + 1_291_633_461_630_999_942_135_808, + 1e18 + ); + } + } else { + if (price < 1.3514708093595578e18) { + return PriceData( + 1.3542648168520204e18, + 729_769_327_686_751_939_985_408, + 1_313_823_407_046_999_345_725_440, + 1.3514708093595578e18, + 731_312_933_164_060_298_444_800, + 1_311_633_461_631_000_135_073_792, + 1e18 + ); + } else { + return PriceData( + 1.3542648168520204e18, + 729_769_327_686_751_939_985_408, + 1_313_823_407_046_999_345_725_440, + 1.3514708093595578e18, + 731_312_933_164_060_298_444_800, + 1_311_633_461_631_000_135_073_792, + 1e18 + ); + } + } + } + } else { + if (price < 1.394324757689252e18) { + if (price < 1.3779550413233685e18) { + if (price < 1.3646356860879163e18) { + return PriceData( + 1.3779550413233685e18, + 716_658_293_110_424_452_726_784, + 1_331_633_461_631_000_059_576_320, + 1.3646356860879163e18, + 723_950_063_371_948_465_848_320, + 1_321_633_461_631_000_097_325_056, + 1e18 + ); + } else { + return PriceData( + 1.3779550413233685e18, + 716_658_293_110_424_452_726_784, + 1_331_633_461_631_000_059_576_320, + 1.3646356860879163e18, + 723_950_063_371_948_465_848_320, + 1_321_633_461_631_000_097_325_056, + 1e18 + ); + } + } else { + if (price < 1.391432389895189e18) { + return PriceData( + 1.394324757689252e18, + 707_938_735_496_683_067_539_456, + 1_343_823_407_046_999_232_479_232, + 1.391432389895189e18, + 709_437_077_218_432_531_824_640, + 1_341_633_461_631_000_021_827_584, + 1e18 + ); + } else { + return PriceData( + 1.394324757689252e18, + 707_938_735_496_683_067_539_456, + 1_343_823_407_046_999_232_479_232, + 1.391432389895189e18, + 709_437_077_218_432_531_824_640, + 1_341_633_461_631_000_021_827_584, + 1e18 + ); + } + } + } else { + if (price < 1.4188752027334606e18) { + if (price < 1.4050712619440027e18) { + return PriceData( + 1.4188752027334606e18, + 695_204_177_438_428_696_150_016, + 1_361_633_461_630_999_946_330_112, + 1.4050712619440027e18, + 702_285_880_571_853_430_325_248, + 1_351_633_461_630_999_984_078_848, + 1e18 + ); + } else { + return PriceData( + 1.4188752027334606e18, + 695_204_177_438_428_696_150_016, + 1_361_633_461_630_999_946_330_112, + 1.4050712619440027e18, + 702_285_880_571_853_430_325_248, + 1_351_633_461_630_999_984_078_848, + 1e18 + ); + } + } else { + return PriceData( + 1.4328477724460313e18, + 688_191_450_861_183_111_790_592, + 1_371_633_461_630_999_908_581_376, + 1.4188752027334606e18, + 695_204_177_438_428_696_150_016, + 1_361_633_461_630_999_946_330_112, + 1e18 + ); + } + } + } + } else { + if (price < 1.5204255894714784e18) { + if (price < 1.4758130760035226e18) { + if (price < 1.446992545963098e18) { + if (price < 1.4358451570118336e18) { + return PriceData( + 1.446992545963098e18, + 681_247_192_069_384_962_048_000, + 1_381_633_461_631_000_139_268_096, + 1.4358451570118336e18, + 686_737_328_931_840_165_150_720, + 1_373_823_407_046_999_387_668_480, + 1e18 + ); + } else { + return PriceData( + 1.446992545963098e18, + 681_247_192_069_384_962_048_000, + 1_381_633_461_631_000_139_268_096, + 1.4358451570118336e18, + 686_737_328_931_840_165_150_720, + 1_373_823_407_046_999_387_668_480, + 1e18 + ); + } + } else { + if (price < 1.4613131126296026e18) { + return PriceData( + 1.4758130760035226e18, + 667_562_080_341_789_698_424_832, + 1_401_633_461_631_000_063_770_624, + 1.4613131126296026e18, + 674_370_899_916_144_494_247_936, + 1_391_633_461_631_000_101_519_360, + 1e18 + ); + } else { + return PriceData( + 1.4758130760035226e18, + 667_562_080_341_789_698_424_832, + 1_401_633_461_631_000_063_770_624, + 1.4613131126296026e18, + 674_370_899_916_144_494_247_936, + 1_391_633_461_631_000_101_519_360, + 1e18 + ); + } + } + } else { + if (price < 1.4904960535905056e18) { + if (price < 1.478922205863501e18) { + return PriceData( + 1.4904960535905056e18, + 660_820_245_862_202_021_511_168, + 1_411_633_461_631_000_026_021_888, + 1.478922205863501e18, + 666_151_184_017_568_179_421_184, + 1_403_823_407_046_999_274_422_272, + 1e18 + ); + } else { + return PriceData( + 1.4904960535905056e18, + 660_820_245_862_202_021_511_168, + 1_411_633_461_631_000_026_021_888, + 1.478922205863501e18, + 666_151_184_017_568_179_421_184, + 1_403_823_407_046_999_274_422_272, + 1e18 + ); + } + } else { + if (price < 1.5053656765640269e18) { + return PriceData( + 1.5204255894714784e18, + 647_535_612_227_205_331_943_424, + 1_431_633_461_630_999_950_524_416, + 1.5053656765640269e18, + 654_144_915_081_340_263_596_032, + 1_421_633_461_630_999_988_273_152, + 1e18 + ); + } else { + return PriceData( + 1.5204255894714784e18, + 647_535_612_227_205_331_943_424, + 1_431_633_461_630_999_950_524_416, + 1.5053656765640269e18, + 654_144_915_081_340_263_596_032, + 1_421_633_461_630_999_988_273_152, + 1e18 + ); + } + } + } + } else { + if (price < 1.6687600728918373e18) { + if (price < 1.570136778675488e18) { + if (price < 1.5236532607722375e18) { + return PriceData( + 1.570136778675488e18, + 626_771_913_818_503_370_506_240, + 1_463_823_407_046_999_316_365_312, + 1.5236532607722375e18, + 646_166_987_566_021_192_187_904, + 1_433_823_407_046_999_429_611_520, + 1e18 + ); + } else { + return PriceData( + 1.570136778675488e18, + 626_771_913_818_503_370_506_240, + 1_463_823_407_046_999_316_365_312, + 1.5236532607722375e18, + 646_166_987_566_021_192_187_904, + 1_433_823_407_046_999_429_611_520, + 1e18 + ); + } + } else { + if (price < 1.6184722417079278e18) { + return PriceData( + 1.6687600728918373e18, + 589_699_646_066_015_911_018_496, + 1_523_823_407_046_999_358_308_352, + 1.6184722417079278e18, + 607_953_518_109_393_449_648_128, + 1_493_823_407_046_999_203_119_104, + 1e18 + ); + } else { + return PriceData( + 1.6687600728918373e18, + 589_699_646_066_015_911_018_496, + 1_523_823_407_046_999_358_308_352, + 1.6184722417079278e18, + 607_953_518_109_393_449_648_128, + 1_493_823_407_046_999_203_119_104, + 1e18 + ); + } + } + } else { + if (price < 9.859705175834625e18) { + if (price < 1.7211015439591308e18) { + return PriceData( + 9.859705175834625e18, + 141_771_511_686_624_031_277_056, + 3_130_170_430_329_999_549_530_112, + 1.7211015439591308e18, + 571_998_357_018_457_696_894_976, + 1_553_823_407_046_999_245_062_144, + 1e18 + ); + } else { + return PriceData( + 9.859705175834625e18, + 141_771_511_686_624_031_277_056, + 3_130_170_430_329_999_549_530_112, + 1.7211015439591308e18, + 571_998_357_018_457_696_894_976, + 1_553_823_407_046_999_245_062_144, + 1e18 + ); + } + } else { + revert("Price Too High, Well is Bricked!"); + } + } + } + } + } + } + } +} diff --git a/src/interfaces/ILookupTable.sol b/src/interfaces/ILookupTable.sol new file mode 100644 index 00000000..6d774852 --- /dev/null +++ b/src/interfaces/ILookupTable.sol @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.17; + +/** + * @title PriceReserveMapping + * @author DeadmanWalking + * @notice In order to reasonably use `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` on chain, + * a lookup table contract is used to decrease the amount of iterations needed to converge into an answer. + */ +interface ILookupTable { + /** + * @notice the lookup table returns a series of data, given a price point: + * @param highPrice the closest price to the targetPrice, where targetPrice < highPrice. + * @param highPriceI reserve i such that `calcRate(reserve, i, j, data)` == highPrice. + * @param highPriceJ reserve j such that `calcRate(reserve, i, j, data)` == highPrice. + * @param lowPrice the closest price to the targetPrice, where targetPrice > lowPrice. + * @param lowPriceI reserve i such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param lowPriceJ reserve j such that `calcRate(reserve, i, j, data)` == lowPrice. + * @param precision the initial reserve values. Assumes the inital reserve i == reserve j + */ + struct PriceData { + uint256 highPrice; + uint256 highPriceI; + uint256 highPriceJ; + uint256 lowPrice; + uint256 lowPriceI; + uint256 lowPriceJ; + uint256 precision; + } + + function getRatiosFromPriceLiquidity(uint256) external view returns (PriceData memory); + function getRatiosFromPriceSwap(uint256) external view returns (PriceData memory); + function getAParameter() external view returns (uint256); +} diff --git a/src/interfaces/beanstalk/IBeanstalkA.sol b/src/interfaces/beanstalk/IBeanstalkA.sol deleted file mode 100644 index a94c3c9e..00000000 --- a/src/interfaces/beanstalk/IBeanstalkA.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -/** - * @title IBeanstalkA - * @author Brean - * @notice Interface for the Beanstalk A, the A parameter that beanstalk sets. - */ -interface IBeanstalkA { - - /** - * @return a A parameter, precision of 2 (a of 1 == 100) - */ - function getBeanstalkA() external pure returns (uint256 a); -} diff --git a/src/pumps/MultiFlowPump.sol b/src/pumps/MultiFlowPump.sol index 0a9e774d..efb16c1d 100644 --- a/src/pumps/MultiFlowPump.sol +++ b/src/pumps/MultiFlowPump.sol @@ -29,7 +29,6 @@ import {LibMath} from "src/libraries/LibMath.sol"; * Note: If an `update` call is made with a reserve of 0, the Geometric mean oracles will be set to 0. * Each Well is responsible for ensuring that an `update` call cannot be made with a reserve of 0. */ - contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumulativePump { using LibLastReserveBytes for bytes32; using LibBytes16 for bytes32; @@ -275,7 +274,7 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu if (crv.r > crv.rLimit) { calcReservesAtRatioSwap(mfpWf, crv.rLimit, cappedReserves, i, j, data); } - // If the ratio decreased, check that it didn't overflow during calculation + // If the ratio decreased, check that it didn't overflow during calculation } else if (crv.r < crv.rLast) { bytes16 tempExp = ABDKMathQuad.ONE.div(ABDKMathQuad.ONE.add(crp.maxRateChanges[j][i])).powu(capExponent); // Check for overflow before converting to 128x128 @@ -290,7 +289,6 @@ contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumu } } - /** * @dev Cap the change in LP Token Supply of `reserves` to a maximum % change from `lastReserves`. */ diff --git a/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol deleted file mode 100644 index 4bd0ad2c..00000000 --- a/test/Stable2/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ /dev/null @@ -1,195 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Stable2, Balances} from "test/TestHelper.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { - event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); - - uint256[] tokenAmountsOut; - uint256 requiredLpAmountIn; - bytes _data; - - // Setup - Stable2 ss; - - uint256 constant addedLiquidity = 1000 * 1e18; - - function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); - - _data = abi.encode(18, 18); - - // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens - addLiquidityEqualAmount(user, addedLiquidity); - // Shared removal amounts - tokenAmountsOut.push(500 * 1e18); // 500 token0 - tokenAmountsOut.push(506 * 1e17); // 50.6 token1 - requiredLpAmountIn = 552_016_399_701_327_563_972; // ~552e18 LP needed to remove `tokenAmountsOut` - } - - /// @dev Assumes use of ConstantProduct2 - function test_getRemoveLiquidityImbalancedIn() public { - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); - assertEq(lpAmountIn, requiredLpAmountIn); - } - - /// @dev not enough LP to receive `tokenAmountsOut` - function test_removeLiquidityImbalanced_revertIf_notEnoughLP() public prank(user) { - uint256 maxLpAmountIn = 5 * 1e18; - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, requiredLpAmountIn, maxLpAmountIn)); - well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); - } - - function test_removeLiquidityImbalanced_revertIf_expired() public { - vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityImbalanced(0, new uint256[](2), user, block.timestamp - 1); - } - - /// @dev Base case - function test_removeLiquidityImbalanced() public prank(user) { - Balances memory userBalanceBefore = getBalances(user, well); - - uint256 initialLpAmount = userBalanceBefore.lp; - uint256 maxLpAmountIn = requiredLpAmountIn; - - vm.expectEmit(true, true, true, true); - emit RemoveLiquidity(maxLpAmountIn, tokenAmountsOut, user); - well.removeLiquidityImbalanced(maxLpAmountIn, tokenAmountsOut, user, type(uint256).max); - - Balances memory userBalanceAfter = getBalances(user, well); - Balances memory wellBalanceAfter = getBalances(address(well), well); - - // `user` balance of LP tokens decreases - assertEq(userBalanceAfter.lp, initialLpAmount - maxLpAmountIn); - - // `user` balance of underlying tokens increases - // assumes initial balance of zero - assertEq(userBalanceAfter.tokens[0], tokenAmountsOut[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfter.tokens[1], tokenAmountsOut[1], "Incorrect token1 user balance"); - - // Well's reserve of underlying tokens decreases - assertEq(wellBalanceAfter.tokens[0], 1500 * 1e18, "Incorrect token0 well reserve"); - assertEq(wellBalanceAfter.tokens[1], 19_494 * 1e17, "Incorrect token1 well reserve"); - checkInvariant(address(well)); - } - - /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal - /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityImbalanced(uint256 a0, uint256 a1) public prank(user) { - // Setup amounts of liquidity to remove - // NOTE: amounts may or may not be equal - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(a0, 0, 750e18); - amounts[1] = bound(a1, 0, 750e18); - - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); - // Calculate change in Well reserves after removing liquidity - uint256[] memory reserves = new uint256[](2); - reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; - reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; - - // lpAmountIn should be <= umaxLpAmountIn - uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - - // Calculate the new LP token supply after the Well's reserves are changed. - // The delta `lpAmountBurned` is the amount of LP that should be burned - // when this liquidity is removed. - uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); - uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; - - // Remove all of `user`'s liquidity and deliver them the tokens - vm.expectEmit(true, true, true, true); - emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); - - Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); - - // `user` balance of LP tokens decreases - assertEq(userBalanceAfterRemoveLiquidity.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); - - // `user` balance of underlying tokens increases - assertEq(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], "Incorrect token1 user balance"); - - // Well's reserve of underlying tokens decreases - // Equal amount of liquidity of 1000e18 were added in the setup function hence the - // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity - // is 1000e18 of each token. - assertEq( - wellBalanceAfterRemoveLiquidity.tokens[0], - (initialLiquidity + addedLiquidity) - amounts[0], - "Incorrect token0 well reserve" - ); - assertEq( - wellBalanceAfterRemoveLiquidity.tokens[1], - (initialLiquidity + addedLiquidity) - amounts[1], - "Incorrect token1 well reserve" - ); - checkStableSwapInvariant(address(well)); - } - - /// @dev Fuzz test: UNEQUAL token reserves, IMBALANCED removal - /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` - /// before liquidity is removed by `user`. - function testFuzz_removeLiquidityImbalanced_withSwap(uint256 a0, uint256 imbalanceBias) public { - // Setup amounts of liquidity to remove - // NOTE: amounts[0] is bounded at 1 to prevent slippage overflow - // failure, bug fix in progress - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(a0, 1, 950e18); - amounts[1] = amounts[0]; - imbalanceBias = bound(imbalanceBias, 0, 40e18); - - // `user2` performs a swap to imbalance the pool by `imbalanceBias` - vm.prank(user2); - well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); - - // `user` has LP tokens and will perform a `removeLiquidityImbalanced` call - vm.startPrank(user); - - Balances memory wellBalanceBefore = getBalances(address(well), well); - Balances memory userBalanceBefore = getBalances(user, well); - - // Calculate change in Well reserves after removing liquidity - uint256[] memory reserves = new uint256[](2); - reserves[0] = wellBalanceBefore.tokens[0] - amounts[0]; - reserves[1] = wellBalanceBefore.tokens[1] - amounts[1]; - - // lpAmountIn should be <= user's LP balance - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - - // Calculate the new LP token supply after the Well's reserves are changed. - // The delta `lpAmountBurned` is the amount of LP that should be burned - // when this liquidity is removed. - uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); - uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; - - // Remove some of `user`'s liquidity and deliver them the tokens - uint256 maxLpAmountIn = userBalanceBefore.lp; - vm.expectEmit(true, true, true, true); - emit RemoveLiquidity(lpAmountBurned, amounts, user); - well.removeLiquidityImbalanced(maxLpAmountIn, amounts, user, type(uint256).max); - - Balances memory wellBalanceAfter = getBalances(address(well), well); - Balances memory userBalanceAfter = getBalances(user, well); - - // `user` balance of LP tokens decreases - assertEq(userBalanceAfter.lp, maxLpAmountIn - lpAmountIn, "Incorrect lp output"); - - // `user` balance of underlying tokens increases - assertEq(userBalanceAfter.tokens[0], userBalanceBefore.tokens[0] + amounts[0], "Incorrect token0 user balance"); - assertEq(userBalanceAfter.tokens[1], userBalanceBefore.tokens[1] + amounts[1], "Incorrect token1 user balance"); - - // Well's reserve of underlying tokens decreases - assertEq(wellBalanceAfter.tokens[0], wellBalanceBefore.tokens[0] - amounts[0], "Incorrect token0 well reserve"); - assertEq(wellBalanceAfter.tokens[1], wellBalanceBefore.tokens[1] - amounts[1], "Incorrect token1 well reserve"); - checkInvariant(address(well)); - } -} diff --git a/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol b/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol deleted file mode 100644 index 5c67aa6a..00000000 --- a/test/Stable2/Well.RemoveLiquidityStableSwap.t.sol +++ /dev/null @@ -1,141 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; -import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { - Stable2 ss; - bytes constant data = ""; - uint256 constant addedLiquidity = 1000 * 1e18; - - function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); - - // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens - addLiquidityEqualAmount(user, addedLiquidity); - } - - /// @dev ensure that Well liq was initialized correctly in {setUp} - /// currently, liquidity is added in {TestHelper} and above - function test_liquidityInitialized() public { - IERC20[] memory tokens = well.tokens(); - for (uint256 i; i < tokens.length; i++) { - assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); - } - assertEq(well.totalSupply(), 4000 * 1e18, "incorrect totalSupply"); - } - - /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying - /// since the tokens in the Well are balanced, user receives equal amounts - function test_getRemoveLiquidityOut() public { - uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); - for (uint256 i; i < tokens.length; i++) { - assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); - } - } - - /// @dev removeLiquidity: reverts when user tries to remove too much of an underlying token - function test_removeLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { - uint256 lpAmountIn = 1000 * 1e18; - - uint256[] memory minTokenAmountsOut = new uint256[](2); - minTokenAmountsOut[0] = 501 * 1e18; // too high - minTokenAmountsOut[1] = 500 * 1e18; - - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, 500 * 1e18, minTokenAmountsOut[0])); - well.removeLiquidity(lpAmountIn, minTokenAmountsOut, user, type(uint256).max); - } - - function test_removeLiquidity_revertIf_expired() public { - vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidity(0, new uint256[](2), user, block.timestamp - 1); - } - - /// @dev removeLiquidity: remove to equal amounts of underlying - function test_removeLiquidity() public prank(user) { - uint256 lpAmountIn = 1000 * 1e18; - - uint256[] memory amountsOut = new uint256[](2); - amountsOut[0] = 500 * 1e18; - amountsOut[1] = 500 * 1e18; - - Snapshot memory before; - RemoveLiquidityAction memory action; - - action.amounts = amountsOut; - action.lpAmountIn = lpAmountIn; - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeRemoveLiquidity(action); - well.removeLiquidity(lpAmountIn, amountsOut, user, type(uint256).max); - afterRemoveLiquidity(before, action); - checkInvariant(address(well)); - } - - /// @dev Fuzz test: EQUAL token reserves, BALANCED removal - /// The Well contains equal reserves of all underlying tokens before execution. - function test_removeLiquidity_fuzz(uint256 a0) public prank(user) { - // Setup amounts of liquidity to remove - // NOTE: amounts may or may not match the maximum removable by `user`. - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(a0, 0, 1000e18); - amounts[1] = amounts[0]; - - Snapshot memory before; - RemoveLiquidityAction memory action; - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - - action.amounts = amounts; - action.lpAmountIn = lpAmountIn; - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeRemoveLiquidity(action); - well.removeLiquidity(lpAmountIn, amounts, user, type(uint256).max); - afterRemoveLiquidity(before, action); - - assertLe( - well.totalSupply(), Stable2(wellFunction.target).calcLpTokenSupply(well.getReserves(), wellFunction.data) - ); - checkInvariant(address(well)); - } - - /// @dev Fuzz test: UNEQUAL token reserves, BALANCED removal - /// A Swap is performed by `user2` that imbalances the pool by `imbalanceBias` - /// before liquidity is removed by `user`. - function test_removeLiquidity_fuzzSwapBias(uint256 lpAmountBurned, uint256 imbalanceBias) public { - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); - - uint256 maxLpAmountIn = userBalanceBeforeRemoveLiquidity.lp; - lpAmountBurned = bound(lpAmountBurned, 100, maxLpAmountIn); - imbalanceBias = bound(imbalanceBias, 0, 10e18); - - // `user2` performs a swap to imbalance the pool by `imbalanceBias` - vm.prank(user2); - well.swapFrom(tokens[0], tokens[1], imbalanceBias, 0, user2, type(uint256).max); - - // `user` has LP tokens and will perform a `removeLiquidity` call - vm.startPrank(user); - - uint256[] memory tokenAmountsOut = new uint256[](2); - tokenAmountsOut = well.getRemoveLiquidityOut(lpAmountBurned); - - Snapshot memory before; - RemoveLiquidityAction memory action; - - action.amounts = tokenAmountsOut; - action.lpAmountIn = lpAmountBurned; - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeRemoveLiquidity(action); - well.removeLiquidity(lpAmountBurned, tokenAmountsOut, user, type(uint256).max); - afterRemoveLiquidity(before, action); - checkStableSwapInvariant(address(well)); - } -} diff --git a/test/Stable2/Well.ShiftStable.t.sol b/test/Stable2/Well.ShiftStable.t.sol deleted file mode 100644 index e9a7adc3..00000000 --- a/test/Stable2/Well.ShiftStable.t.sol +++ /dev/null @@ -1,160 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Balances, ConstantProduct2, IERC20, Stable2} from "test/TestHelper.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellShiftStableTest is TestHelper { - event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); - - function setUp() public { - setupStableSwapWell(); - } - - /// @dev Shift excess token0 into token1. - function testFuzz_shift(uint256 amount) public prank(user) { - amount = bound(amount, 1, 1000e18); - - // Transfer `amount` of token0 to the Well - tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); - - // Get a user with a fresh address (no ERC20 tokens) - address _user = users.getNextUserAddress(); - Balances memory userBalanceBeforeShift = getBalances(_user, well); - - // Verify that `_user` has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); - - uint256 minAmountOut = well.getShiftOut(tokens[1]); - uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = tokens[0].balanceOf(address(well)); - calcReservesAfter[1] = tokens[1].balanceOf(address(well)) - minAmountOut; - - vm.expectEmit(true, true, true, true); - emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); - uint256 amtOut = well.shift(tokens[1], minAmountOut, _user); - - uint256[] memory reserves = well.getReserves(); - Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); - - // User should have gained token1 - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); - assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); - - // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); - - // The difference has been sent to _user. - assertEq( - userBalanceAfterShift.tokens[1], - wellBalanceBeforeShift.tokens[1] - wellBalanceAfterShift.tokens[1], - "User should have correct token1 balance" - ); - assertEq( - userBalanceAfterShift.tokens[1], - userBalanceBeforeShift.tokens[1] + amtOut, - "User should have correct token1 balance" - ); - checkStableSwapInvariant(address(well)); - } - - /// @dev Shift excess token0 into token0 (just transfers the excess token0 to the user). - function testFuzz_shift_tokenOut(uint256 amount) public prank(user) { - amount = bound(amount, 1, 1000e18); - - // Transfer `amount` of token0 to the Well - tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); - - // Get a user with a fresh address (no ERC20 tokens) - address _user = users.getNextUserAddress(); - Balances memory userBalanceBeforeShift = getBalances(_user, well); - - // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); - - uint256 minAmountOut = well.getShiftOut(tokens[0]); - uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = tokens[0].balanceOf(address(well)) - minAmountOut; - calcReservesAfter[1] = tokens[1].balanceOf(address(well)); - - vm.expectEmit(true, true, true, true); - emit Shift(calcReservesAfter, tokens[0], minAmountOut, _user); - // Shift the imbalanced token as the token out - well.shift(tokens[0], 0, _user); - - uint256[] memory reserves = well.getReserves(); - Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); - - // User should have gained token0 - assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); - assertEq( - userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" - ); - - // Reserves should now match balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); - - assertEq( - userBalanceAfterShift.tokens[0], - userBalanceBeforeShift.tokens[0] + amount, - "User should have gained token 1" - ); - checkInvariant(address(well)); - } - - /// @dev Calling shift() on a balanced Well should do nothing. - function test_shift_balanced_pool() public prank(user) { - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); - - // Get a user with a fresh address (no ERC20 tokens) - address _user = users.getNextUserAddress(); - Balances memory userBalanceBeforeShift = getBalances(_user, well); - - // Verify that the user has no tokens - assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); - assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); - - well.shift(tokens[1], 0, _user); - - uint256[] memory reserves = well.getReserves(); - Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances(address(well), well); - - // User should have gained neither token - assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); - assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); - - // Reserves should equal balances - assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); - assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); - checkInvariant(address(well)); - } - - function test_shift_fail_slippage(uint256 amount) public prank(user) { - amount = bound(amount, 1, 1000e18); - - // Transfer `amount` of token0 to the Well - tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances(address(well), well); - assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); - assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); - - uint256 amountOut = well.getShiftOut(tokens[1]); - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); - well.shift(tokens[1], type(uint256).max, user); - } -} diff --git a/test/Stable2/Well.Stable2.AddLiquidity.t.sol b/test/Stable2/Well.Stable2.AddLiquidity.t.sol index 107405dc..710e6e9f 100644 --- a/test/Stable2/Well.Stable2.AddLiquidity.t.sol +++ b/test/Stable2/Well.Stable2.AddLiquidity.t.sol @@ -9,7 +9,7 @@ import {Math} from "oz/utils/math/Math.sol"; contract WellStable2AddLiquidityTest is LiquidityHelper { function setUp() public { - setupStableSwapWell(); + setupStable2Well(); } /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent @@ -41,7 +41,7 @@ contract WellStable2AddLiquidityTest is LiquidityHelper { amounts[1] = 0; uint256 amountOut = well.getAddLiquidityOut(amounts); - assertEq(amountOut, 9_998_815_419_300_522_901, "incorrect amt out"); + assertEq(amountOut, 9_991_708_006_311_592_653, "incorrect amt out"); } function test_addLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { diff --git a/test/Stable2/Well.BoreStableSwap.t.sol b/test/Stable2/Well.Stable2.Bore.t.sol similarity index 85% rename from test/Stable2/Well.BoreStableSwap.t.sol rename to test/Stable2/Well.Stable2.Bore.t.sol index 8851b834..662e4da3 100644 --- a/test/Stable2/Well.BoreStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.Bore.t.sol @@ -4,11 +4,12 @@ pragma solidity ^0.8.17; import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; import {MockPump} from "mocks/pumps/MockPump.sol"; import {Stable2} from "src/functions/Stable2.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -contract WellBoreStableSwapTest is TestHelper { +contract WellStable2BoreTest is TestHelper { /// @dev Bore a 2-token Well with Stable2 & several pumps. function setUp() public { - setupStableSwapWell(); + setupStable2Well(); // Well.sol doesn't use wellData, so it should always return empty bytes wellData = new bytes(0); } @@ -31,7 +32,7 @@ contract WellBoreStableSwapTest is TestHelper { assertEq(well.wellData(), wellData); } - function test_aquifer() public { + function test_aquifer() public view { assertEq(well.aquifer(), address(aquifer)); } @@ -51,21 +52,21 @@ contract WellBoreStableSwapTest is TestHelper { assertEq(_aquifer, address(aquifer)); } - function test_getReserves() public { + function test_getReserves() public view { assertEq(well.getReserves(), getBalances(address(well), well).tokens); } //////////// ERC20 LP Token //////////// - function test_name() public { - assertEq(well.name(), "TOKEN0:TOKEN1 StableSwap Well"); + function test_name() public view { + assertEq(well.name(), "TOKEN0:TOKEN1 Stable2 Well"); } - function test_symbol() public { - assertEq(well.symbol(), "TOKEN0TOKEN1SS2w"); + function test_symbol() public view { + assertEq(well.symbol(), "TOKEN0TOKEN1S2w"); } - function test_decimals() public { + function test_decimals() public view { assertEq(well.decimals(), 18); } @@ -87,7 +88,8 @@ contract WellBoreStableSwapTest is TestHelper { bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); // Deploy a Well Function - wellFunction = Call(address(new Stable2(address(1))), wellFunctionBytes); + address lut = address(new Stable2LUT1()); + wellFunction = Call(address(new Stable2(lut)), wellFunctionBytes); // Etch the MockPump at each `target` Call[] memory pumps = new Call[](numberOfPumps); diff --git a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol b/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol similarity index 94% rename from test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol rename to test/Stable2/Well.Stable2.RemoveLiquidity.t.sol index 5c67aa6a..1e2dbfc0 100644 --- a/test/stableSwap/Well.RemoveLiquidityStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.RemoveLiquidity.t.sol @@ -5,15 +5,17 @@ import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { +contract WellStable2RemoveLiquidityTest is LiquidityHelper { Stable2 ss; bytes constant data = ""; uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); + address lut = address(new Stable2LUT1()); + ss = new Stable2(lut); + setupStable2Well(); // Add liquidity. `user` now has (2 * 1000 * 1e27) LP tokens addLiquidityEqualAmount(user, addedLiquidity); @@ -21,7 +23,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { /// @dev ensure that Well liq was initialized correctly in {setUp} /// currently, liquidity is added in {TestHelper} and above - function test_liquidityInitialized() public { + function test_liquidityInitialized() public view { IERC20[] memory tokens = well.tokens(); for (uint256 i; i < tokens.length; i++) { assertEq(tokens[i].balanceOf(address(well)), initialLiquidity + addedLiquidity, "incorrect token reserve"); @@ -31,7 +33,7 @@ contract WellRemoveLiquidityTestStableSwap is LiquidityHelper { /// @dev getRemoveLiquidityOut: remove to equal amounts of underlying /// since the tokens in the Well are balanced, user receives equal amounts - function test_getRemoveLiquidityOut() public { + function test_getRemoveLiquidityOut() public view { uint256[] memory amountsOut = well.getRemoveLiquidityOut(1000 * 1e18); for (uint256 i; i < tokens.length; i++) { assertEq(amountsOut[i], 500 * 1e18, "incorrect getRemoveLiquidityOut"); diff --git a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol b/test/Stable2/Well.Stable2.RemoveLiquidityImbalanced.t.sol similarity index 96% rename from test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol rename to test/Stable2/Well.Stable2.RemoveLiquidityImbalanced.t.sol index 4bd0ad2c..2913cce8 100644 --- a/test/stableSwap/Well.RemoveLiquidityImbalancedStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.RemoveLiquidityImbalanced.t.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.17; import {TestHelper, Stable2, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { +contract WellStable2RemoveLiquidityImbalancedTest is TestHelper { event RemoveLiquidity(uint256 lpAmountIn, uint256[] tokenAmountsOut, address recipient); uint256[] tokenAmountsOut; @@ -18,8 +19,9 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { uint256 constant addedLiquidity = 1000 * 1e18; function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); + address lut = address(new Stable2LUT1()); + ss = new Stable2(lut); + setupStable2Well(); _data = abi.encode(18, 18); @@ -28,11 +30,11 @@ contract WellRemoveLiquidityImbalancedTestStableSwap is TestHelper { // Shared removal amounts tokenAmountsOut.push(500 * 1e18); // 500 token0 tokenAmountsOut.push(506 * 1e17); // 50.6 token1 - requiredLpAmountIn = 552_016_399_701_327_563_972; // ~552e18 LP needed to remove `tokenAmountsOut` + requiredLpAmountIn = 560_455_949_926_809_426_750; // ~552e18 LP needed to remove `tokenAmountsOut` } /// @dev Assumes use of ConstantProduct2 - function test_getRemoveLiquidityImbalancedIn() public { + function test_getRemoveLiquidityImbalancedIn() public view { uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(tokenAmountsOut); assertEq(lpAmountIn, requiredLpAmountIn); } diff --git a/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol similarity index 90% rename from test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol rename to test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol index d19b900b..6ca26382 100644 --- a/test/Stable2/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.RemoveLiquidityOneToken.t.sol @@ -4,8 +4,9 @@ pragma solidity ^0.8.17; import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { +contract WellStable2RemoveLiquidityOneTokenTest is TestHelper { event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); Stable2 ss; @@ -13,8 +14,9 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { bytes _data; function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); + address lut = address(new Stable2LUT1()); + ss = new Stable2(lut); + setupStable2Well(); // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens addLiquidityEqualAmount(user, addedLiquidity); @@ -22,9 +24,9 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { } /// @dev Assumes use of Stable2 - function test_getRemoveLiquidityOneTokenOut() public { + function test_getRemoveLiquidityOneTokenOut() public view { uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); - assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); + assertEq(amountOut, 488_542_119_171_820_114_601, "incorrect tokenOut"); } /// @dev not enough tokens received for `lpAmountIn`. @@ -45,7 +47,7 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { /// @dev Base case function test_removeLiquidityOneToken() public prank(user) { uint256 lpAmountIn = 500 * 1e18; - uint256 minTokenAmountOut = 498_279_423_862_830_737_827; + uint256 minTokenAmountOut = 488_542_119_171_820_114_601; Balances memory prevUserBalance = getBalances(user, well); vm.expectEmit(true, true, true, true); @@ -104,18 +106,18 @@ contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { vm.expectEmit(true, true, true, false); emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out - assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); + assertApproxEqAbs(amountOut, amounts[0], 2, "amounts[0] > userLpBalance"); Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 2, "Incorrect token0 user balance"); + assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 2, "Incorrect token1 user balance"); // should stay the same assertApproxEqAbs( wellBalanceAfterRemoveLiquidity.tokens[0], (initialLiquidity + addedLiquidity) - amounts[0], - 1, + 2, "Incorrect token0 well reserve" ); assertEq( diff --git a/test/stableSwap/Well.ShiftStable.t.sol b/test/Stable2/Well.Stable2.Shift.t.sol similarity index 99% rename from test/stableSwap/Well.ShiftStable.t.sol rename to test/Stable2/Well.Stable2.Shift.t.sol index e9a7adc3..e2873e84 100644 --- a/test/stableSwap/Well.ShiftStable.t.sol +++ b/test/Stable2/Well.Stable2.Shift.t.sol @@ -5,11 +5,11 @@ import {TestHelper, Balances, ConstantProduct2, IERC20, Stable2} from "test/Test import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; -contract WellShiftStableTest is TestHelper { +contract WellStable2ShiftTest is TestHelper { event Shift(uint256[] reserves, IERC20 toToken, uint256 minAmountOut, address recipient); function setUp() public { - setupStableSwapWell(); + setupStable2Well(); } /// @dev Shift excess token0 into token1. diff --git a/test/Stable2/Well.SkimStableSwap.t.sol b/test/Stable2/Well.Stable2.Skim.t.sol similarity index 96% rename from test/Stable2/Well.SkimStableSwap.t.sol rename to test/Stable2/Well.Stable2.Skim.t.sol index f3965dd0..190f1cc0 100644 --- a/test/Stable2/Well.SkimStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.Skim.t.sol @@ -3,9 +3,9 @@ pragma solidity ^0.8.17; import {TestHelper, Balances} from "test/TestHelper.sol"; -contract WellSkimTest is TestHelper { +contract WellStable2SkimTest is TestHelper { function setUp() public { - setupStableSwapWell(); + setupStable2Well(); } function test_initialized() public { diff --git a/test/stableSwap/Well.SwapFromStableSwap.t.sol b/test/Stable2/Well.Stable2.SwapFrom.t.sol similarity index 96% rename from test/stableSwap/Well.SwapFromStableSwap.t.sol rename to test/Stable2/Well.Stable2.SwapFrom.t.sol index fd7f814d..436e421a 100644 --- a/test/stableSwap/Well.SwapFromStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.SwapFrom.t.sol @@ -8,16 +8,16 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; -contract WellSwapFromStableSwapTest is SwapHelper { +contract WellStable2SwapFromTest is SwapHelper { function setUp() public { - setupStableSwapWell(); + setupStable2Well(); } function test_getSwapOut() public view { uint256 amountIn = 10 * 1e18; uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); - assertEq(amountOut, 9_995_239_930_393_036_263); // ~0.05% slippage + assertEq(amountOut, 9_966_775_941_840_933_593); } function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint256 amountIn, uint256 i) public prank(user) { diff --git a/test/stableSwap/Well.SwapToStableSwap.t.sol b/test/Stable2/Well.Stable2.SwapTo.t.sol similarity index 95% rename from test/stableSwap/Well.SwapToStableSwap.t.sol rename to test/Stable2/Well.Stable2.SwapTo.t.sol index b027ace3..067b5d02 100644 --- a/test/stableSwap/Well.SwapToStableSwap.t.sol +++ b/test/Stable2/Well.Stable2.SwapTo.t.sol @@ -8,15 +8,16 @@ import {IWellFunction} from "src/interfaces/IWellFunction.sol"; import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; -contract WellSwapToStableSwapTest is SwapHelper { +contract WellStable2SwapToTest is SwapHelper { function setUp() public { - setupStableSwapWell(); + // init 1000e18, 1000e18 + setupStable2Well(); } function test_getSwapIn() public view { uint256 amountOut = 100 * 1e18; uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); - assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage + assertEq(amountIn, 103_464_719_546_263_310_322); // ~3% slippage } function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { @@ -45,7 +46,7 @@ contract WellSwapToStableSwapTest is SwapHelper { /// @dev Slippage revert occurs if maxAmountIn is too low function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { uint256 amountOut = 100 * 1e18; - uint256 amountIn = 100_482_889_020_651_556_292; + uint256 amountIn = 103_464_719_546_263_310_322; uint256 maxAmountIn = (amountIn * 99) / 100; vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); diff --git a/test/Stable2/Well.SwapFromStableSwap.t.sol b/test/Stable2/Well.SwapFromStableSwap.t.sol deleted file mode 100644 index fd7f814d..00000000 --- a/test/Stable2/Well.SwapFromStableSwap.t.sol +++ /dev/null @@ -1,101 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {IERC20, Balances, Call, MockToken, Well, console} from "test/TestHelper.sol"; -import {SwapHelper, SwapAction, Snapshot} from "test/SwapHelper.sol"; -import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; -import {IWellFunction} from "src/interfaces/IWellFunction.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellSwapFromStableSwapTest is SwapHelper { - function setUp() public { - setupStableSwapWell(); - } - - function test_getSwapOut() public view { - uint256 amountIn = 10 * 1e18; - uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); - - assertEq(amountOut, 9_995_239_930_393_036_263); // ~0.05% slippage - } - - function testFuzz_getSwapOut_revertIf_insufficientWellBalance(uint256 amountIn, uint256 i) public prank(user) { - // Swap token `i` -> all other tokens - vm.assume(i < tokens.length); - - // Find an input amount that produces an output amount higher than what the Well has. - // When the Well is deployed it has zero reserves, so any nonzero value should revert. - amountIn = bound(amountIn, 1, type(uint128).max); - - // Deploy a new Well with a poorly engineered pricing function. - // Its `getBalance` function can return an amount greater than the Well holds. - IWellFunction badFunction = new MockFunctionBad(); - Well badWell = encodeAndBoreWell( - address(aquifer), wellImplementation, tokens, Call(address(badFunction), ""), pumps, bytes32(0) - ); - - // Check assumption that reserves are empty - Balances memory wellBalances = getBalances(address(badWell), badWell); - assertEq(wellBalances.tokens[0], 0, "bad assumption: wellBalances.tokens[0] != 0"); - assertEq(wellBalances.tokens[1], 0, "bad assumption: wellBalances.tokens[1] != 0"); - - for (uint256 j = 0; j < tokens.length; ++j) { - if (j != i) { - vm.expectRevert(); // underflow - badWell.getSwapOut(tokens[i], tokens[j], amountIn); - } - } - } - - /// @dev Swaps should always revert if `fromToken` = `toToken`. - function test_swapFrom_revertIf_sameToken() public prank(user) { - vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapFrom(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); - } - - /// @dev Slippage revert if minAmountOut is too high - function test_swapFrom_revertIf_minAmountOutTooHigh() public prank(user) { - uint256 amountIn = 10 * 1e18; - uint256 amountOut = well.getSwapOut(tokens[0], tokens[1], amountIn); - uint256 minAmountOut = amountOut + 1e18; - - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minAmountOut)); - well.swapFrom(tokens[0], tokens[1], amountIn, minAmountOut, user, type(uint256).max); - } - - function test_swapFrom_revertIf_expired() public { - vm.expectRevert(IWellErrors.Expired.selector); - well.swapFrom(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); - } - - function testFuzz_swapFrom(uint256 amountIn) public prank(user) { - amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); - - (Snapshot memory bef, SwapAction memory act) = beforeSwapFrom(0, 1, amountIn); - act.wellSends = well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); - afterSwapFrom(bef, act); - checkStableSwapInvariant(address(well)); - } - - function testFuzz_swapAndRemoveAllLiq(uint256 amountIn) public { - amountIn = bound(amountIn, 0, tokens[0].balanceOf(user)); - vm.prank(user); - well.swapFrom(tokens[0], tokens[1], amountIn, 0, user, type(uint256).max); - - vm.prank(address(this)); - well.removeLiquidityImbalanced( - type(uint256).max, IWell(address(well)).getReserves(), address(this), type(uint256).max - ); - assertEq(IERC20(address(well)).totalSupply(), 0); - } - - /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapFrom_equalSwap(uint256 token0AmtIn) public prank(user) { - vm.assume(token0AmtIn < tokens[0].balanceOf(user)); - uint256 token1Out = well.swapFrom(tokens[0], tokens[1], token0AmtIn, 0, user, type(uint256).max); - uint256 token0Out = well.swapFrom(tokens[1], tokens[0], token1Out, 0, user, type(uint256).max); - assertEq(token0Out, token0AmtIn); - checkInvariant(address(well)); - } -} diff --git a/test/Stable2/Well.SwapToStableSwap.t.sol b/test/Stable2/Well.SwapToStableSwap.t.sol deleted file mode 100644 index 89c0750f..00000000 --- a/test/Stable2/Well.SwapToStableSwap.t.sol +++ /dev/null @@ -1,110 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {IERC20, Balances, Call, MockToken, Well} from "test/TestHelper.sol"; -import {SwapHelper, SwapAction} from "test/SwapHelper.sol"; -import {MockFunctionBad} from "mocks/functions/MockFunctionBad.sol"; -import {IWellFunction} from "src/interfaces/IWellFunction.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellSwapToStableSwapTest is SwapHelper { - function setUp() public { - setupStableSwapWell(); - } - - function test_getSwapIn() public { - uint256 amountOut = 100 * 1e18; - uint256 amountIn = well.getSwapIn(tokens[0], tokens[1], amountOut); - assertEq(amountIn, 100_482_889_020_651_556_292); // ~0.4% slippage - } - - function testFuzz_getSwapIn_revertIf_insufficientWellBalance(uint256 i) public prank(user) { - IERC20[] memory _tokens = well.tokens(); - Balances memory wellBalances = getBalances(address(well), well); - vm.assume(i < _tokens.length); - - // Swap token `i` -> all other tokens - for (uint256 j; j < _tokens.length; ++j) { - if (j != i) { - // Request to buy more of {_tokens[j]} than the Well has. - // There is no input amount that could complete this Swap. - uint256 amountOut = wellBalances.tokens[j] + 1; - vm.expectRevert(); // underflow - well.getSwapIn(_tokens[i], _tokens[j], amountOut); - } - } - } - - /// @dev Swaps should always revert if `fromToken` = `toToken`. - function test_swapTo_revertIf_sameToken() public prank(user) { - vm.expectRevert(IWellErrors.InvalidTokens.selector); - well.swapTo(tokens[0], tokens[0], 100 * 1e18, 0, user, type(uint256).max); - } - - /// @dev Slippage revert occurs if maxAmountIn is too low - function test_swapTo_revertIf_maxAmountInTooLow() public prank(user) { - uint256 amountOut = 100 * 1e18; - uint256 amountIn = 100_482_889_020_651_556_292; - uint256 maxAmountIn = (amountIn * 99) / 100; - - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageIn.selector, amountIn, maxAmountIn)); - well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); - } - - /// @dev Note: this covers the case where there is a fee as well - function test_swapFromFeeOnTransferNoFee_revertIf_expired() public { - vm.expectRevert(IWellErrors.Expired.selector); - well.swapTo(tokens[0], tokens[1], 0, 0, user, block.timestamp - 1); - } - - /// @dev tests assume 2 tokens in future we can extend for multiple tokens - function testFuzz_swapTo(uint256 amountOut) public prank(user) { - // User has 1000 of each token - // Given current liquidity, swapping 1000 of one token gives 500 of the other - uint256 maxAmountIn = 1000 * 1e18; - amountOut = bound(amountOut, 0, 500 * 1e18); - - Balances memory userBalancesBefore = getBalances(user, well); - Balances memory wellBalancesBefore = getBalances(address(well), well); - - // Decrease reserve of token 1 by `amountOut` which is paid to user - uint256[] memory calcBalances = new uint256[](wellBalancesBefore.tokens.length); - calcBalances[0] = wellBalancesBefore.tokens[0]; - calcBalances[1] = wellBalancesBefore.tokens[1] - amountOut; - - uint256 calcAmountIn = IWellFunction(wellFunction.target).calcReserve( - calcBalances, - 0, // j - wellBalancesBefore.lpSupply, - wellFunction.data - ) - wellBalancesBefore.tokens[0]; - - vm.expectEmit(true, true, true, true); - emit Swap(tokens[0], tokens[1], calcAmountIn, amountOut, user); - well.swapTo(tokens[0], tokens[1], maxAmountIn, amountOut, user, type(uint256).max); - - Balances memory userBalancesAfter = getBalances(user, well); - Balances memory wellBalancesAfter = getBalances(address(well), well); - - assertEq( - userBalancesBefore.tokens[0] - userBalancesAfter.tokens[0], calcAmountIn, "Incorrect token0 user balance" - ); - assertEq(userBalancesAfter.tokens[1] - userBalancesBefore.tokens[1], amountOut, "Incorrect token1 user balance"); - assertEq( - wellBalancesAfter.tokens[0], wellBalancesBefore.tokens[0] + calcAmountIn, "Incorrect token0 well reserve" - ); - assertEq(wellBalancesAfter.tokens[1], wellBalancesBefore.tokens[1] - amountOut, "Incorrect token1 well reserve"); - checkStableSwapInvariant(address(well)); - } - - /// @dev Zero hysteresis: token0 -> token1 -> token0 gives the same result - function testFuzz_swapTo_equalSwap(uint256 token0AmtOut) public prank(user) { - // assume amtOut is lower due to slippage - vm.assume(token0AmtOut < 500e18); - uint256 token1In = well.swapTo(tokens[0], tokens[1], 1000e18, token0AmtOut, user, type(uint256).max); - uint256 token0In = well.swapTo(tokens[1], tokens[0], 1000e18, token1In, user, type(uint256).max); - assertEq(token0In, token0AmtOut); - checkInvariant(address(well)); - } -} diff --git a/test/TestHelper.sol b/test/TestHelper.sol index ae7f6ee7..3289be13 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -14,6 +14,7 @@ import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; import {Stable2} from "src/functions/Stable2.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; @@ -127,15 +128,17 @@ abstract contract TestHelper is Test, WellDeployer { user2 = _user[1]; } - function setupStableSwapWell() internal { - setupStableSwapWell(deployPumps(1), deployMockTokens(2)); + function setupStable2Well() internal { + setupStable2Well(deployPumps(1), deployMockTokens(2)); } - function setupStableSwapWell(Call[] memory _pumps, IERC20[] memory _tokens) internal { + function setupStable2Well(Call[] memory _pumps, IERC20[] memory _tokens) internal { + // deploy new LUT: + address lut = address(new Stable2LUT1()); // encode wellFunction Data bytes memory wellFunctionData = abi.encode(MockToken(address(_tokens[0])).decimals(), MockToken(address(_tokens[1])).decimals()); - Call memory _wellFunction = Call(address(new Stable2(address(1))), wellFunctionData); + Call memory _wellFunction = Call(address(new Stable2(lut)), wellFunctionData); tokens = _tokens; wellFunction = _wellFunction; vm.label(address(wellFunction.target), "Stable2 WF"); @@ -324,7 +327,7 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(address(a), address(b), err); } - function assertEq(IERC20[] memory a, IERC20[] memory b) internal { + function assertEq(IERC20[] memory a, IERC20[] memory b) internal pure { assertEq(a, b, "IERC20[] mismatch"); } @@ -335,7 +338,7 @@ abstract contract TestHelper is Test, WellDeployer { } } - function assertEq(Call memory a, Call memory b) internal { + function assertEq(Call memory a, Call memory b) internal pure { assertEq(a, b, "Call mismatch"); } @@ -344,11 +347,11 @@ abstract contract TestHelper is Test, WellDeployer { assertEq(a.data, b.data, err); } - function assertEq(Call[] memory a, Call[] memory b) internal { + function assertEq(Call[] memory a, Call[] memory b) internal pure { assertEq(a, b, "Call[] mismatch"); } - function assertEq(Call[] memory a, Call[] memory b, string memory err) internal { + function assertEq(Call[] memory a, Call[] memory b, string memory err) internal pure { assertEq(a.length, b.length, err); for (uint256 i; i < a.length; i++) { assertEq(a[i], b[i], err); // uses the prev overload diff --git a/test/Well.Shift.t.sol b/test/Well.Shift.t.sol index 083cfd0a..ad7d9c8f 100644 --- a/test/Well.Shift.t.sol +++ b/test/Well.Shift.t.sol @@ -6,12 +6,7 @@ import {IWell} from "src/interfaces/IWell.sol"; import {IWellErrors} from "src/interfaces/IWellErrors.sol"; contract WellShiftTest is TestHelper { - event Shift( - uint256[] reserves, - IERC20 toToken, - uint256 amountOut, - address recipient - ); + event Shift(uint256[] reserves, IERC20 toToken, uint256 amountOut, address recipient); ConstantProduct2 cp; @@ -26,43 +21,22 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received token0" - ); - assertEq( - wellBalanceBeforeShift.tokens[1], - 1000e18, - "Well should have NOT have received token1" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that `_user` has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); uint256 minAmountOut = well.getShiftOut(tokens[1]); uint256[] memory calcReservesAfter = new uint256[](2); calcReservesAfter[0] = tokens[0].balanceOf(address(well)); - calcReservesAfter[1] = - tokens[1].balanceOf(address(well)) - - minAmountOut; + calcReservesAfter[1] = tokens[1].balanceOf(address(well)) - minAmountOut; vm.expectEmit(true, true, true, true); emit Shift(calcReservesAfter, tokens[1], minAmountOut, _user); @@ -70,38 +44,16 @@ contract WellShiftTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained token1 - assertEq( - userBalanceAfterShift.tokens[0], - 0, - "User should NOT have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - amtOut, - "User should have gained token1" - ); - assertTrue( - userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], - "User should have more token1" - ); + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], amtOut, "User should have gained token1"); + assertTrue(userBalanceAfterShift.tokens[1] >= userBalanceBeforeShift.tokens[1], "User should have more token1"); // Reserves should now match balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); // The difference has been sent to _user. assertEq( @@ -123,37 +75,20 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received tokens" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received tokens"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); uint256 minAmountOut = well.getShiftOut(tokens[0]); uint256[] memory calcReservesAfter = new uint256[](2); - calcReservesAfter[0] = - tokens[0].balanceOf(address(well)) - - minAmountOut; + calcReservesAfter[0] = tokens[0].balanceOf(address(well)) - minAmountOut; calcReservesAfter[1] = tokens[1].balanceOf(address(well)); vm.expectEmit(true, true, true, true); @@ -163,34 +98,17 @@ contract WellShiftTest is TestHelper { uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained token0 + assertEq(userBalanceAfterShift.tokens[0], amount, "User should have gained token0"); assertEq( - userBalanceAfterShift.tokens[0], - amount, - "User should have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - userBalanceBeforeShift.tokens[1], - "User should NOT have gained token1" + userBalanceAfterShift.tokens[1], userBalanceBeforeShift.tokens[1], "User should NOT have gained token1" ); // Reserves should now match balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); assertEq( userBalanceAfterShift.tokens[0], @@ -202,64 +120,30 @@ contract WellShiftTest is TestHelper { /// @dev Calling shift() on a balanced Well should do nothing. function test_shift_balanced_pool() public prank(user) { - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - wellBalanceBeforeShift.tokens[1], - "Well should should be balanced" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], wellBalanceBeforeShift.tokens[1], "Well should should be balanced"); // Get a user with a fresh address (no ERC20 tokens) address _user = users.getNextUserAddress(); Balances memory userBalanceBeforeShift = getBalances(_user, well); // Verify that the user has no tokens - assertEq( - userBalanceBeforeShift.tokens[0], - 0, - "User should start with 0 of token0" - ); - assertEq( - userBalanceBeforeShift.tokens[1], - 0, - "User should start with 0 of token1" - ); + assertEq(userBalanceBeforeShift.tokens[0], 0, "User should start with 0 of token0"); + assertEq(userBalanceBeforeShift.tokens[1], 0, "User should start with 0 of token1"); well.shift(tokens[1], 0, _user); uint256[] memory reserves = well.getReserves(); Balances memory userBalanceAfterShift = getBalances(_user, well); - Balances memory wellBalanceAfterShift = getBalances( - address(well), - well - ); + Balances memory wellBalanceAfterShift = getBalances(address(well), well); // User should have gained neither token - assertEq( - userBalanceAfterShift.tokens[0], - 0, - "User should NOT have gained token0" - ); - assertEq( - userBalanceAfterShift.tokens[1], - 0, - "User should NOT have gained token1" - ); + assertEq(userBalanceAfterShift.tokens[0], 0, "User should NOT have gained token0"); + assertEq(userBalanceAfterShift.tokens[1], 0, "User should NOT have gained token1"); // Reserves should equal balances - assertEq( - wellBalanceAfterShift.tokens[0], - reserves[0], - "Well should have correct token0 balance" - ); - assertEq( - wellBalanceAfterShift.tokens[1], - reserves[1], - "Well should have correct token1 balance" - ); + assertEq(wellBalanceAfterShift.tokens[0], reserves[0], "Well should have correct token0 balance"); + assertEq(wellBalanceAfterShift.tokens[1], reserves[1], "Well should have correct token1 balance"); checkInvariant(address(well)); } @@ -268,29 +152,12 @@ contract WellShiftTest is TestHelper { // Transfer `amount` of token0 to the Well tokens[0].transfer(address(well), amount); - Balances memory wellBalanceBeforeShift = getBalances( - address(well), - well - ); - assertEq( - wellBalanceBeforeShift.tokens[0], - 1000e18 + amount, - "Well should have received token0" - ); - assertEq( - wellBalanceBeforeShift.tokens[1], - 1000e18, - "Well should have NOT have received token1" - ); + Balances memory wellBalanceBeforeShift = getBalances(address(well), well); + assertEq(wellBalanceBeforeShift.tokens[0], 1000e18 + amount, "Well should have received token0"); + assertEq(wellBalanceBeforeShift.tokens[1], 1000e18, "Well should have NOT have received token1"); uint256 amountOut = well.getShiftOut(tokens[1]); - vm.expectRevert( - abi.encodeWithSelector( - IWellErrors.SlippageOut.selector, - amountOut, - type(uint256).max - ) - ); + vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, type(uint256).max)); well.shift(tokens[1], type(uint256).max, user); } } diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol similarity index 91% rename from test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol rename to test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index 76aa7d78..730f8aec 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -4,24 +4,26 @@ pragma solidity ^0.8.20; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; /// @dev Tests the {ConstantProduct2} Well function directly. -contract Stable2LiquidityTest is TestHelper { +contract BeanstalkStable2LiquidityTest is TestHelper { IBeanstalkWellFunction _f; bytes data; //////////// SETUP //////////// function setUp() public { - _f = new Stable2(address(1)); + address lut = address(new Stable2LUT1()); + _f = new Stable2(lut); deployMockTokens(2); data = abi.encode(18, 18); } function test_calcReserveAtRatioLiquidity_equal_equal() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; + reserves[0] = 100e12; + reserves[1] = 100e12; uint256[] memory ratios = new uint256[](2); ratios[0] = 1; ratios[1] = 1; @@ -35,8 +37,8 @@ contract Stable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_equal_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; + reserves[0] = 50e12; + reserves[1] = 100e12; uint256[] memory ratios = new uint256[](2); ratios[0] = 1; ratios[1] = 1; @@ -50,8 +52,8 @@ contract Stable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_diff_equal() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 100; - reserves[1] = 100; + reserves[0] = 100e12; + reserves[1] = 100e12; uint256[] memory ratios = new uint256[](2); ratios[0] = 2; ratios[1] = 1; @@ -65,8 +67,8 @@ contract Stable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 50; - reserves[1] = 100; + reserves[0] = 50e12; + reserves[1] = 100e12; uint256[] memory ratios = new uint256[](2); ratios[0] = 2; ratios[1] = 1; diff --git a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol similarity index 83% rename from test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol rename to test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 379dfdac..915796e8 100644 --- a/test/beanstalk/CurveStableSwap2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -4,21 +4,22 @@ pragma solidity ^0.8.17; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; /// @dev Tests the {ConstantProduct2} Well function directly. -contract BeanstalkStableSwapSwapTest is TestHelper { +contract BeanstalkStable2SwapTest is TestHelper { IBeanstalkWellFunction _f; bytes data; //////////// SETUP //////////// function setUp() public { - _f = new Stable2(address(1)); - IERC20[] memory _token = deployMockTokens(2); - data = abi.encode(10, address(_token[0]), address(_token[1])); + address lut = address(new Stable2LUT1()); + _f = new Stable2(lut); + data = abi.encode(18, 18); } - function test_calcReserveAtRatioSwap_equal_equal() public { + function test_calcReserveAtRatioSwap_equal_equal() public view { uint256[] memory reserves = new uint256[](2); // calcReserveAtRatioSwap requires a minimum value of 10 ** token decimals. reserves[0] = 100e18; @@ -34,7 +35,7 @@ contract BeanstalkStableSwapSwapTest is TestHelper { assertEq(reserve1, 100e18); } - function test_calcReserveAtRatioSwap_equal_diff() public { + function test_calcReserveAtRatioSwap_equal_diff() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 50e18; reserves[1] = 100e18; @@ -51,7 +52,7 @@ contract BeanstalkStableSwapSwapTest is TestHelper { console.log("reserve1", reserve1); } - function test_calcReserveAtRatioSwap_diff_equal() public { + function test_calcReserveAtRatioSwap_diff_equal() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 100e18; reserves[1] = 100e18; @@ -68,7 +69,7 @@ contract BeanstalkStableSwapSwapTest is TestHelper { console.log("reserve1", reserve1); } - function test_calcReserveAtRatioSwap_diff_diff() public { + function test_calcReserveAtRatioSwap_diff_diff() public view { uint256[] memory reserves = new uint256[](2); reserves[0] = 90; // bean reserves[1] = 110; // usdc diff --git a/test/functions/StableSwap.t.sol b/test/functions/Stable2.t.sol similarity index 80% rename from test/functions/StableSwap.t.sol rename to test/functions/Stable2.t.sol index 69794180..31e9ab19 100644 --- a/test/functions/StableSwap.t.sol +++ b/test/functions/Stable2.t.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.17; import {console, TestHelper, IERC20} from "test/TestHelper.sol"; import {WellFunctionHelper, IMultiFlowPumpWellFunction} from "./WellFunctionHelper.sol"; import {Stable2} from "src/functions/Stable2.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; /// @dev Tests the {Stable2} Well function directly. -contract CurveStableSwapTest is WellFunctionHelper { +contract Stable2Test is WellFunctionHelper { /** * State A: Same decimals * D (lpTokenSupply) should be the summation of @@ -28,13 +29,13 @@ contract CurveStableSwapTest is WellFunctionHelper { * */ uint256 STATE_B_B0 = 10 * 1e18; - uint256 STATE_B_B1 = 20 * 1e18; - uint256 STATE_B_LP = 29_911_483_643_966_454_823; // ~29e18 + uint256 STATE_B_B1 = 20 * 1e6; + uint256 STATE_B_LP = 29_405_570_361_996_060_057; // ~29.4e18 /// State C: Similar decimals - uint256 STATE_C_B0 = 20 * 1e18; - uint256 STATE_C_LP = 25 * 1e24; - uint256 STATE_C_B1 = 2_221_929_790_566_403_172_822_276_028; // 2.221e19 + uint256 STATE_C_B0 = 20 * 1e12; + uint256 STATE_C_B1 = 25 * 1e18; + uint256 STATE_C_LP = 44_906_735_116_816_626_495; // 44.9e18 /// @dev See {calcLpTokenSupply}. uint256 MAX_RESERVE = 1e32; @@ -44,28 +45,20 @@ contract CurveStableSwapTest is WellFunctionHelper { function setUp() public { IERC20[] memory _tokens = deployMockTokens(2); tokens = _tokens; - _function = IMultiFlowPumpWellFunction(new Stable2(address(1))); - - // encode well data with: - // A parameter of 10, - // address of token 0 and 1. - _data = abi.encode(18, 18); + address lut = address(new Stable2LUT1()); + _function = IMultiFlowPumpWellFunction(new Stable2(lut)); } function test_metadata() public view { - assertEq(_function.name(), "StableSwap"); - assertEq(_function.symbol(), "SS2"); + assertEq(_function.name(), "Stable2"); + assertEq(_function.symbol(), "S2"); } //////////// LP TOKEN SUPPLY //////////// - /// @dev reverts when trying to calculate lp token supply with < 2 reserves - function test_calcLpTokenSupply_minBalancesLength() public { - check_calcLpTokenSupply_minBalancesLength(2); - } - /// @dev calcLpTokenSupply: same decimals, manual calc for 2 equal reserves - function test_calcLpTokenSupply_sameDecimals() public view { + function test_calcLpTokenSupply_sameDecimals() public { + _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = STATE_A_B0; reserves[1] = STATE_A_B1; @@ -76,10 +69,11 @@ contract CurveStableSwapTest is WellFunctionHelper { } /// @dev calcLpTokenSupply: diff decimals - function test_calcLpTokenSupply_diffDecimals() public view { + function test_calcLpTokenSupply_diffDecimals() public { uint256[] memory reserves = new uint256[](2); - reserves[0] = STATE_B_B0; // ex. 1 WETH - reserves[1] = STATE_B_B1; // ex. 1250 BEAN + _data = abi.encode(18, 6); + reserves[0] = STATE_B_B0; // 10 USDT + reserves[1] = STATE_B_B1; // 20 BEAN assertEq(_function.calcLpTokenSupply(reserves, _data), STATE_B_LP); } @@ -87,11 +81,12 @@ contract CurveStableSwapTest is WellFunctionHelper { /// @dev calcReserve: same decimals, both positions /// Matches example in {testLpTokenSupplySameDecimals}. - function test_calcReserve_sameDecimals() public view { + function test_calcReserve_sameDecimals() public { uint256[] memory reserves = new uint256[](2); /// STATE A // find reserves[0] + _data = abi.encode(18, 18); reserves[0] = 0; reserves[1] = STATE_A_B1; assertEq(_function.calcReserve(reserves, 0, STATE_A_LP, _data), STATE_A_B0); @@ -103,17 +98,16 @@ contract CurveStableSwapTest is WellFunctionHelper { /// STATE C // find reserves[1] + _data = abi.encode(12, 18); reserves[0] = STATE_C_B0; reserves[1] = 0; - assertEq( - _function.calcReserve(reserves, 1, STATE_C_LP, _data), - STATE_C_B1 // (50e18/2) ^ 2 / 20e18 = 31.25e19 - ); + assertEq(_function.calcReserve(reserves, 1, STATE_C_LP, _data), STATE_C_B1); } /// @dev calcReserve: diff decimals, both positions /// Matches example in {testLpTokenSupplyDiffDecimals}. - function test_calcReserve_diffDecimals() public view { + function test_calcReserve_diffDecimals() public { + _data = abi.encode(18, 6); uint256[] memory reserves = new uint256[](2); /// STATE B @@ -137,7 +131,8 @@ contract CurveStableSwapTest is WellFunctionHelper { //////////// LP TOKEN SUPPLY //////////// /// @dev invariant: reserves -> lpTokenSupply -> reserves should match - function testFuzz_calcLpTokenSupply(uint256[2] memory _reserves) public view { + function testFuzz_calcLpTokenSupply(uint256[2] memory _reserves) public { + _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); reserves[1] = bound(_reserves[1], 10e18, MAX_RESERVE); @@ -177,7 +172,8 @@ contract CurveStableSwapTest is WellFunctionHelper { ///////// CALC RATE /////// - function test_calcRateStable() public view { + function test_calcRateStable() public { + _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = 1e18; reserves[1] = 1e18; diff --git a/test/integration/IntegrationTestHelper.sol b/test/integration/IntegrationTestHelper.sol index 7f72d44e..6abc0920 100644 --- a/test/integration/IntegrationTestHelper.sol +++ b/test/integration/IntegrationTestHelper.sol @@ -14,20 +14,11 @@ import {from18, to18} from "test/pumps/PumpHelpers.sol"; abstract contract IntegrationTestHelper is TestHelper { using LibContractInfo for address; - function setupWell( - IERC20[] memory _tokens, - Well _well - ) internal returns (Well) { + function setupWell(IERC20[] memory _tokens, Well _well) internal returns (Well) { Call[] memory _pumps = new Call[](1); _pumps[0] = Call(address(new MultiFlowPump()), new bytes(0)); - return - setupWell( - _tokens, - Call(address(new ConstantProduct2()), new bytes(0)), - _pumps, - _well - ); + return setupWell(_tokens, Call(address(new ConstantProduct2()), new bytes(0)), _pumps, _well); } function setupWell( @@ -42,14 +33,7 @@ abstract contract IntegrationTestHelper is TestHelper { wellImplementation = deployWellImplementation(); aquifer = new Aquifer(); - _well = encodeAndBoreWell( - address(aquifer), - wellImplementation, - _tokens, - wellFunction, - _pumps, - bytes32(0) - ); + _well = encodeAndBoreWell(address(aquifer), wellImplementation, _tokens, wellFunction, _pumps, bytes32(0)); // Mint mock tokens to user mintTokens(_tokens, user, initialLiquidity); @@ -63,33 +47,20 @@ abstract contract IntegrationTestHelper is TestHelper { approveMaxTokens(_tokens, address(this), address(_well)); // Add initial liquidity from TestHelper - addLiquidityEqualAmount( - _tokens, - address(this), - initialLiquidity, - Well(_well) - ); + addLiquidityEqualAmount(_tokens, address(this), initialLiquidity, Well(_well)); return _well; } /// @dev mint mock tokens to each recipient - function mintTokens( - IERC20[] memory _tokens, - address recipient, - uint256 amount - ) internal { + function mintTokens(IERC20[] memory _tokens, address recipient, uint256 amount) internal { for (uint256 i; i < _tokens.length; i++) { deal(address(_tokens[i]), recipient, amount); } } /// @dev approve `spender` to use `owner` tokens - function approveMaxTokens( - IERC20[] memory _tokens, - address owner, - address spender - ) internal prank(owner) { + function approveMaxTokens(IERC20[] memory _tokens, address owner, address spender) internal prank(owner) { for (uint256 i; i < _tokens.length; i++) { _tokens[i].approve(spender, type(uint256).max); } @@ -137,10 +108,7 @@ abstract contract IntegrationTestHelper is TestHelper { clipboardData = clipboardData | (uint256(_type) << 248); clipboardData = - clipboardData | - (returnDataIndex << 160) | - (((copyIndex * 32) + 32) << 80) | - ((pasteIndex * 32) + 36); + clipboardData | (returnDataIndex << 160) | (((copyIndex * 32) + 32) << 80) | ((pasteIndex * 32) + 36); if (useEther) { // put 0x1 in second byte // shift left 30 bytes diff --git a/test/integration/interfaces/ICurve.sol b/test/integration/interfaces/ICurve.sol index 6c46ee5c..3a0535f3 100644 --- a/test/integration/interfaces/ICurve.sol +++ b/test/integration/interfaces/ICurve.sol @@ -9,10 +9,7 @@ interface ICurvePool { function totalSupply() external view returns (uint256); - function add_liquidity( - uint256[2] memory amounts, - uint256 min_mint_amount - ) external returns (uint256); + function add_liquidity(uint256[2] memory amounts, uint256 min_mint_amount) external returns (uint256); function remove_liquidity_one_coin( uint256 _token_amount, @@ -28,34 +25,15 @@ interface ICurvePool { function get_virtual_price() external view returns (uint256); - function calc_token_amount( - uint256[2] calldata amounts, - bool deposit - ) external view returns (uint256); + function calc_token_amount(uint256[2] calldata amounts, bool deposit) external view returns (uint256); - function calc_withdraw_one_coin( - uint256 _token_amount, - int128 i - ) external view returns (uint256); + function calc_withdraw_one_coin(uint256 _token_amount, int128 i) external view returns (uint256); - function exchange( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy - ) external returns (uint256); + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); - function exchange_underlying( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy - ) external returns (uint256); + function exchange_underlying(int128 i, int128 j, uint256 dx, uint256 min_dy) external returns (uint256); - function transfer( - address recipient, - uint256 amount - ) external returns (bool); + function transfer(address recipient, uint256 amount) external returns (bool); } interface ICurveZap { @@ -73,13 +51,7 @@ interface ICurveZap { } interface ICurvePoolR { - function exchange( - int128 i, - int128 j, - uint256 dx, - uint256 min_dy, - address receiver - ) external returns (uint256); + function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy, address receiver) external returns (uint256); function exchange_underlying( int128 i, @@ -162,66 +134,33 @@ interface I3Curve { } interface ICurveFactory { - function get_coins( - address _pool - ) external view returns (address[4] calldata); + function get_coins(address _pool) external view returns (address[4] calldata); - function get_underlying_coins( - address _pool - ) external view returns (address[8] calldata); + function get_underlying_coins(address _pool) external view returns (address[8] calldata); } interface ICurveCryptoFactory { - function get_coins( - address _pool - ) external view returns (address[8] calldata); + function get_coins(address _pool) external view returns (address[8] calldata); } interface ICurvePoolC { - function exchange( - uint256 i, - uint256 j, - uint256 dx, - uint256 min_dy - ) external returns (uint256); + function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external returns (uint256); } interface ICurvePoolNoReturn { - function exchange( - uint256 i, - uint256 j, - uint256 dx, - uint256 min_dy - ) external; + function exchange(uint256 i, uint256 j, uint256 dx, uint256 min_dy) external; - function add_liquidity( - uint256[3] memory amounts, - uint256 min_mint_amount - ) external; + function add_liquidity(uint256[3] memory amounts, uint256 min_mint_amount) external; - function remove_liquidity( - uint256 _burn_amount, - uint256[3] memory _min_amounts - ) external; + function remove_liquidity(uint256 _burn_amount, uint256[3] memory _min_amounts) external; - function remove_liquidity_imbalance( - uint256[3] memory _amounts, - uint256 _max_burn_amount - ) external; + function remove_liquidity_imbalance(uint256[3] memory _amounts, uint256 _max_burn_amount) external; - function remove_liquidity_one_coin( - uint256 _token_amount, - uint256 i, - uint256 min_amount - ) external; + function remove_liquidity_one_coin(uint256 _token_amount, uint256 i, uint256 min_amount) external; } interface ICurvePoolNoReturn128 { function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external; - function remove_liquidity_one_coin( - uint256 _token_amount, - int128 i, - uint256 min_amount - ) external; + function remove_liquidity_one_coin(uint256 _token_amount, int128 i, uint256 min_amount) external; } diff --git a/test/libraries/TestABDK.t.sol b/test/libraries/TestABDK.t.sol index 4fd6b602..ea741c4d 100644 --- a/test/libraries/TestABDK.t.sol +++ b/test/libraries/TestABDK.t.sol @@ -1,3 +1,4 @@ +// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "test/TestHelper.sol"; @@ -67,12 +68,12 @@ contract ABDKTest is TestHelper { return a.fromUInt().div(b.fromUInt()).powu(c); } - function testFuzz_FromUIntToLog2(uint256 x) public { + function testFuzz_FromUIntToLog2(uint256 x) public pure { x = bound(x, 1, type(uint256).max); assertEq(ABDKMathQuad.fromUInt(x).log_2(), ABDKMathQuad.fromUIntToLog2(x)); } - function testFuzz_pow_2ToUInt(uint256 x) public { + function testFuzz_pow_2ToUInt(uint256 x) public pure { x = bound(x, 0, 255); // test the pow_2ToUInt function diff --git a/test/pumps/Pump.CapReserves.t.sol b/test/pumps/Pump.CapReserves.t.sol index e06d7aa4..d1586153 100644 --- a/test/pumps/Pump.CapReserves.t.sol +++ b/test/pumps/Pump.CapReserves.t.sol @@ -51,11 +51,7 @@ contract CapBalanceTest is TestHelper, MultiFlowPump { _well = address( new MockStaticWell( - deployMockTokens(2), - Call(address(wf), new bytes(0)), - deployPumps(1), - address(0), - new bytes(0) + deployMockTokens(2), Call(address(wf), new bytes(0)), deployPumps(1), address(0), new bytes(0) ) ); } diff --git a/test/pumps/Pump.Fuzz.t.sol b/test/pumps/Pump.Fuzz.t.sol index 67cd9954..cced9ba2 100644 --- a/test/pumps/Pump.Fuzz.t.sol +++ b/test/pumps/Pump.Fuzz.t.sol @@ -124,6 +124,4 @@ contract PumpFuzzTest is TestHelper, MultiFlowPump { } // assertTrue(false); } - - } diff --git a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol b/test/stableSwap/Well.AddLiquidityStableSwap.t.sol deleted file mode 100644 index 4dda91bc..00000000 --- a/test/stableSwap/Well.AddLiquidityStableSwap.t.sol +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.17; - -import {TestHelper, IERC20, Call, Balances} from "test/TestHelper.sol"; -import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; -import {Snapshot, AddLiquidityAction, RemoveLiquidityAction, LiquidityHelper} from "test/LiquidityHelper.sol"; - -contract WellAddLiquidityStableSwapTest is LiquidityHelper { - function setUp() public { - setupStableSwapWell(); - } - - /// @dev Liquidity is initially added in {TestHelper}; ensure that subsequent - /// tests will run correctly. - function test_liquidityInitialized() public { - IERC20[] memory tokens = well.tokens(); - Balances memory userBalance = getBalances(user, well); - Balances memory wellBalance = getBalances(address(well), well); - for (uint256 i; i < tokens.length; i++) { - assertEq(userBalance.tokens[i], initialLiquidity, "incorrect user token reserve"); - assertEq(wellBalance.tokens[i], initialLiquidity, "incorrect well token reserve"); - } - } - - /// @dev Adding liquidity in equal proportions should summate and be scaled - /// up by sqrt(ConstantProduct2.EXP_PRECISION) - function test_getAddLiquidityOut_equalAmounts() public { - uint256[] memory amounts = new uint256[](tokens.length); - for (uint256 i; i < tokens.length; i++) { - amounts[i] = 1000 * 1e18; - } - uint256 lpAmountOut = well.getAddLiquidityOut(amounts); - assertEq(lpAmountOut, 2000 * 1e18, "Incorrect AmountOut"); - } - - function test_getAddLiquidityOut_oneToken() public { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 10 * 1e18; - amounts[1] = 0; - - uint256 amountOut = well.getAddLiquidityOut(amounts); - assertEq(amountOut, 9_998_815_419_300_522_901, "incorrect amt out"); - } - - function test_addLiquidity_revertIf_minAmountOutTooHigh() public prank(user) { - uint256[] memory amounts = new uint256[](tokens.length); - for (uint256 i; i < tokens.length; i++) { - amounts[i] = 1000 * 1e18; - } - uint256 lpAmountOut = well.getAddLiquidityOut(amounts); - - vm.expectRevert(abi.encodeWithSelector(SlippageOut.selector, lpAmountOut, lpAmountOut + 1)); - well.addLiquidity(amounts, lpAmountOut + 1, user, type(uint256).max); // lpAmountOut is 2000*1e27 - } - - function test_addLiquidity_revertIf_expired() public { - vm.expectRevert(Expired.selector); - well.addLiquidity(new uint256[](tokens.length), 0, user, block.timestamp - 1); - } - - function test_addLiquidity_balanced() public prank(user) { - uint256[] memory amounts = new uint256[](tokens.length); - for (uint256 i; i < tokens.length; i++) { - amounts[i] = 1000 * 1e18; - } - uint256 lpAmountOut = 2000 * 1e18; - - vm.expectEmit(true, true, true, true); - emit AddLiquidity(amounts, lpAmountOut, user); - well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); - - Balances memory userBalance = getBalances(user, well); - Balances memory wellBalance = getBalances(address(well), well); - - assertEq(userBalance.lp, lpAmountOut); - - // Consumes all of user's tokens - assertEq(userBalance.tokens[0], 0, "incorrect token0 user amt"); - assertEq(userBalance.tokens[1], 0, "incorrect token1 user amt"); - - // Adds to the Well's reserves - assertEq(wellBalance.tokens[0], initialLiquidity + amounts[0], "incorrect token0 well amt"); - assertEq(wellBalance.tokens[1], initialLiquidity + amounts[1], "incorrect token1 well amt"); - checkInvariant(address(well)); - } - - function test_addLiquidity_oneSided() public prank(user) { - uint256[] memory amounts = new uint256[](2); - amounts[0] = 10 * 1e18; - amounts[1] = 0; - - Snapshot memory before; - AddLiquidityAction memory action; - action.amounts = amounts; - action.lpAmountOut = well.getAddLiquidityOut(amounts); - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeAddLiquidity(action); - well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); - afterAddLiquidity(before, action); - checkInvariant(address(well)); - } - - /// @dev Adding and removing liquidity in sequence should return the Well to its previous state - function test_addAndRemoveLiquidity() public prank(user) { - uint256[] memory amounts = new uint256[](tokens.length); - for (uint256 i; i < tokens.length; i++) { - amounts[i] = 1000 * 1e18; - } - uint256 lpAmountOut = 2000 * 1e18; - - Snapshot memory before; - AddLiquidityAction memory action; - action.amounts = amounts; - action.lpAmountOut = well.getAddLiquidityOut(amounts); - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeAddLiquidity(action); - well.addLiquidity(amounts, lpAmountOut, user, type(uint256).max); - afterAddLiquidity(before, action); - - Snapshot memory beforeRemove; - RemoveLiquidityAction memory actionRemove; - actionRemove.lpAmountIn = well.getAddLiquidityOut(amounts); - actionRemove.amounts = amounts; - actionRemove.recipient = user; - - (beforeRemove, actionRemove) = beforeRemoveLiquidity(actionRemove); - well.removeLiquidity(lpAmountOut, amounts, user, type(uint256).max); - afterRemoveLiquidity(beforeRemove, actionRemove); - checkInvariant(address(well)); - } - - /// @dev Adding zero liquidity emits empty event but doesn't change reserves - function test_addLiquidity_zeroChange() public prank(user) { - uint256[] memory amounts = new uint256[](tokens.length); - uint256 liquidity = 0; - - Snapshot memory before; - AddLiquidityAction memory action; - action.amounts = amounts; - action.lpAmountOut = liquidity; - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeAddLiquidity(action); - well.addLiquidity(amounts, liquidity, user, type(uint256).max); - afterAddLiquidity(before, action); - checkInvariant(address(well)); - } - - /// @dev Two-token fuzz test adding liquidity in any ratio - function testFuzz_addLiquidity(uint256 x, uint256 y) public prank(user) { - // amounts to add as liquidity - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(x, 0, type(uint104).max); - amounts[1] = bound(y, 0, type(uint104).max); - mintTokens(user, amounts); - - Snapshot memory before; - AddLiquidityAction memory action; - action.amounts = amounts; - action.lpAmountOut = well.getAddLiquidityOut(amounts); - action.recipient = user; - action.fees = new uint256[](2); - - (before, action) = beforeAddLiquidity(action); - well.addLiquidity(amounts, well.getAddLiquidityOut(amounts), user, type(uint256).max); - afterAddLiquidity(before, action); - checkInvariant(address(well)); - } -} diff --git a/test/stableSwap/Well.BoreStableSwap.t.sol b/test/stableSwap/Well.BoreStableSwap.t.sol deleted file mode 100644 index 9c4f2659..00000000 --- a/test/stableSwap/Well.BoreStableSwap.t.sol +++ /dev/null @@ -1,126 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Well, IERC20, Call, Balances} from "test/TestHelper.sol"; -import {MockPump} from "mocks/pumps/MockPump.sol"; -import {Stable2} from "src/functions/Stable2.sol"; - -contract WellBoreStableSwapTest is TestHelper { - /// @dev Bore a 2-token Well with Stable2 & several pumps. - function setUp() public { - // setup a StableSwap Well with an A parameter of 10. - setupStableSwapWell(); - // Well.sol doesn't use wellData, so it should always return empty bytes - wellData = new bytes(0); - } - - //////////// Well Definition //////////// - - function test_tokens() public { - assertEq(well.tokens(), tokens); - } - - function test_wellFunction() public { - assertEq(well.wellFunction(), wellFunction); - } - - function test_pumps() public { - assertEq(well.pumps(), pumps); - } - - function test_wellData() public { - assertEq(well.wellData(), wellData); - } - - function test_aquifer() public { - assertEq(well.aquifer(), address(aquifer)); - } - - function test_well() public { - ( - IERC20[] memory _wellTokens, - Call memory _wellFunction, - Call[] memory _wellPumps, - bytes memory _wellData, - address _aquifer - ) = well.well(); - - assertEq(_wellTokens, tokens); - assertEq(_wellFunction, wellFunction); - assertEq(_wellPumps, pumps); - assertEq(_wellData, wellData); - assertEq(_aquifer, address(aquifer)); - } - - function test_getReserves() public { - assertEq(well.getReserves(), getBalances(address(well), well).tokens); - } - - //////////// ERC20 LP Token //////////// - - function test_name() public { - assertEq(well.name(), "TOKEN0:TOKEN1 StableSwap Well"); - } - - function test_symbol() public { - assertEq(well.symbol(), "TOKEN0TOKEN1SS2w"); - } - - function test_decimals() public { - assertEq(well.decimals(), 18); - } - - //////////// Deployment //////////// - - /// @dev Fuzz different combinations of Well configuration data and check - /// that the Aquifer deploys everything correctly. - function testFuzz_bore(uint256 numberOfPumps, bytes[4] memory pumpData, uint256 nTokens, uint256 a) public { - // Constraints - numberOfPumps = bound(numberOfPumps, 0, 4); - for (uint256 i = 0; i < numberOfPumps; i++) { - vm.assume(pumpData[i].length <= 4 * 32); - } - nTokens = bound(nTokens, 2, tokens.length); - - vm.assume(a > 0); - // Get the first `nTokens` mock tokens - IERC20[] memory wellTokens = getTokens(nTokens); - bytes memory wellFunctionBytes = abi.encode(a, address(wellTokens[0]), address(wellTokens[1])); - - // Deploy a Well Function - wellFunction = Call(address(new Stable2(address(1))), wellFunctionBytes); - - // Etch the MockPump at each `target` - Call[] memory pumps = new Call[](numberOfPumps); - for (uint256 i = 0; i < numberOfPumps; i++) { - pumps[i].target = address(new MockPump()); - pumps[i].data = pumpData[i]; - } - - // Deploy the Well - Well _well = - encodeAndBoreWell(address(aquifer), wellImplementation, wellTokens, wellFunction, pumps, bytes32(0)); - - // Check Pumps - assertEq(_well.numberOfPumps(), numberOfPumps, "number of pumps mismatch"); - Call[] memory _pumps = _well.pumps(); - - if (numberOfPumps > 0) { - assertEq(_well.firstPump(), pumps[0], "pump mismatch"); - } - - for (uint256 i = 0; i < numberOfPumps; i++) { - assertEq(_pumps[i], pumps[i], "pump mismatch"); - } - - // Check token addresses - assertEq(_well.tokens(), wellTokens); - - // Check Well Function - assertEq(_well.wellFunction(), wellFunction); - assertEq(_well.wellFunctionAddress(), wellFunction.target); - - // Check that Aquifer recorded the deployment - assertEq(aquifer.wellImplementation(address(_well)), wellImplementation); - } -} diff --git a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol b/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol deleted file mode 100644 index d19b900b..00000000 --- a/test/stableSwap/Well.RemoveLiquidityOneTokenStableSwap.t.sol +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Stable2, IERC20, Balances} from "test/TestHelper.sol"; -import {IWell} from "src/interfaces/IWell.sol"; -import {IWellErrors} from "src/interfaces/IWellErrors.sol"; - -contract WellRemoveLiquidityOneTokenTestStableSwap is TestHelper { - event RemoveLiquidityOneToken(uint256 lpAmountIn, IERC20 tokenOut, uint256 tokenAmountOut, address recipient); - - Stable2 ss; - uint256 constant addedLiquidity = 1000 * 1e18; - bytes _data; - - function setUp() public { - ss = new Stable2(address(1)); - setupStableSwapWell(); - - // Add liquidity. `user` now has (2 * 1000 * 1e18) LP tokens - addLiquidityEqualAmount(user, addedLiquidity); - _data = abi.encode(18, 18); - } - - /// @dev Assumes use of Stable2 - function test_getRemoveLiquidityOneTokenOut() public { - uint256 amountOut = well.getRemoveLiquidityOneTokenOut(500 * 1e18, tokens[0]); - assertEq(amountOut, 498_279_423_862_830_737_827, "incorrect tokenOut"); - } - - /// @dev not enough tokens received for `lpAmountIn`. - function test_removeLiquidityOneToken_revertIf_amountOutTooLow() public prank(user) { - uint256 lpAmountIn = 500 * 1e18; - uint256 minTokenAmountOut = 500 * 1e18; - uint256 amountOut = well.getRemoveLiquidityOneTokenOut(lpAmountIn, tokens[0]); - - vm.expectRevert(abi.encodeWithSelector(IWellErrors.SlippageOut.selector, amountOut, minTokenAmountOut)); - well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); - } - - function test_removeLiquidityOneToken_revertIf_expired() public { - vm.expectRevert(IWellErrors.Expired.selector); - well.removeLiquidityOneToken(0, tokens[0], 0, user, block.timestamp - 1); - } - - /// @dev Base case - function test_removeLiquidityOneToken() public prank(user) { - uint256 lpAmountIn = 500 * 1e18; - uint256 minTokenAmountOut = 498_279_423_862_830_737_827; - Balances memory prevUserBalance = getBalances(user, well); - - vm.expectEmit(true, true, true, true); - emit RemoveLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user); - - uint256 amountOut = - well.removeLiquidityOneToken(lpAmountIn, tokens[0], minTokenAmountOut, user, type(uint256).max); - - Balances memory userBalance = getBalances(user, well); - Balances memory wellBalance = getBalances(address(well), well); - - assertEq(userBalance.lp, prevUserBalance.lp - lpAmountIn, "Incorrect lpAmountIn"); - - assertEq(userBalance.tokens[0], amountOut, "Incorrect token0 user balance"); - assertEq(userBalance.tokens[1], 0, "Incorrect token1 user balance"); - - // Equal amount of liquidity of 1000e18 were added in the setup function hence the - // well's reserves here are 2000e18 minus the amounts removed, as the initial liquidity - // is 1000e18 of each token. - assertEq( - wellBalance.tokens[0], - (initialLiquidity + addedLiquidity) - minTokenAmountOut, - "Incorrect token0 well reserve" - ); - assertEq(wellBalance.tokens[1], (initialLiquidity + addedLiquidity), "Incorrect token1 well reserve"); - checkStableSwapInvariant(address(well)); - } - - /// @dev Fuzz test: EQUAL token reserves, IMBALANCED removal - /// The Well contains equal reserves of all underlying tokens before execution. - function testFuzz_removeLiquidityOneToken(uint256 a0) public prank(user) { - // Assume we're removing tokens[0] - uint256[] memory amounts = new uint256[](2); - amounts[0] = bound(a0, 1e18, 750e18); - amounts[1] = 0; - - Balances memory userBalanceBeforeRemoveLiquidity = getBalances(user, well); - uint256 userLpBalance = userBalanceBeforeRemoveLiquidity.lp; - - // Find the LP amount that should be burned given the fuzzed - // amounts. Works even though only amounts[0] is set. - uint256 lpAmountIn = well.getRemoveLiquidityImbalancedIn(amounts); - - Balances memory wellBalanceBeforeRemoveLiquidity = getBalances(address(well), well); - - // Calculate change in Well reserves after removing liquidity - uint256[] memory reserves = new uint256[](2); - reserves[0] = wellBalanceBeforeRemoveLiquidity.tokens[0] - amounts[0]; - reserves[1] = wellBalanceBeforeRemoveLiquidity.tokens[1] - amounts[1]; // should stay the same - - // Calculate the new LP token supply after the Well's reserves are changed. - // The delta `lpAmountBurned` is the amount of LP that should be burned - // when this liquidity is removed. - uint256 newLpTokenSupply = ss.calcLpTokenSupply(reserves, _data); - uint256 lpAmountBurned = well.totalSupply() - newLpTokenSupply; - vm.expectEmit(true, true, true, false); - emit RemoveLiquidityOneToken(lpAmountBurned, tokens[0], amounts[0], user); - uint256 amountOut = well.removeLiquidityOneToken(lpAmountIn, tokens[0], 0, user, type(uint256).max); // no minimum out - assertApproxEqAbs(amountOut, amounts[0], 1, "amounts[0] > userLpBalance"); - - Balances memory userBalanceAfterRemoveLiquidity = getBalances(user, well); - Balances memory wellBalanceAfterRemoveLiquidity = getBalances(address(well), well); - - assertEq(userBalanceAfterRemoveLiquidity.lp, userLpBalance - lpAmountIn, "Incorrect lp output"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[0], amounts[0], 1, "Incorrect token0 user balance"); - assertApproxEqAbs(userBalanceAfterRemoveLiquidity.tokens[1], amounts[1], 1, "Incorrect token1 user balance"); // should stay the same - assertApproxEqAbs( - wellBalanceAfterRemoveLiquidity.tokens[0], - (initialLiquidity + addedLiquidity) - amounts[0], - 1, - "Incorrect token0 well reserve" - ); - assertEq( - wellBalanceAfterRemoveLiquidity.tokens[1], - (initialLiquidity + addedLiquidity) - amounts[1], - "Incorrect token1 well reserve" - ); // should stay the same - checkStableSwapInvariant(address(well)); - } - - // TODO: fuzz test: imbalanced ratio of tokens -} diff --git a/test/stableSwap/Well.SkimStableSwap.t.sol b/test/stableSwap/Well.SkimStableSwap.t.sol deleted file mode 100644 index 9a1d1160..00000000 --- a/test/stableSwap/Well.SkimStableSwap.t.sol +++ /dev/null @@ -1,56 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; - -import {TestHelper, Balances} from "test/TestHelper.sol"; - -contract WellSkimTest is TestHelper { - function setUp() public { - setupStableSwapWell(); - } - - function test_initialized() public view { - // Well should have liquidity - Balances memory wellBalance = getBalances(address(well), well); - assertEq(wellBalance.tokens[0], 1000e18); - assertEq(wellBalance.tokens[1], 1000e18); - } - - function testFuzz_skim(uint256[2] calldata amounts) public prank(user) { - vm.assume(amounts[0] <= 800e18); - vm.assume(amounts[1] <= 800e18); - - // Transfer from Test contract to Well - tokens[0].transfer(address(well), amounts[0]); - tokens[1].transfer(address(well), amounts[1]); - - Balances memory wellBalanceBeforeSkim = getBalances(address(well), well); - // Verify that the Well has received the tokens - assertEq(wellBalanceBeforeSkim.tokens[0], 1000e18 + amounts[0]); - assertEq(wellBalanceBeforeSkim.tokens[1], 1000e18 + amounts[1]); - - // Get a user with a fresh address (no ERC20 tokens) - address _user = users.getNextUserAddress(); - uint256[] memory reserves = new uint256[](2); - - // Verify that the user has no tokens - Balances memory userBalanceBeforeSkim = getBalances(_user, well); - reserves[0] = userBalanceBeforeSkim.tokens[0]; - reserves[1] = userBalanceBeforeSkim.tokens[1]; - assertEq(reserves[0], 0); - assertEq(reserves[1], 0); - - well.skim(_user); - - Balances memory userBalanceAfterSkim = getBalances(_user, well); - Balances memory wellBalanceAfterSkim = getBalances(address(well), well); - - // Since only 1000e18 of each token was added as liquidity, the Well's reserve - // should be reset back to this. - assertEq(wellBalanceAfterSkim.tokens[0], 1000e18); - assertEq(wellBalanceAfterSkim.tokens[1], 1000e18); - - // The difference has been sent to _user. - assertEq(userBalanceAfterSkim.tokens[0], amounts[0]); - assertEq(userBalanceAfterSkim.tokens[1], amounts[1]); - } -} From a776b78165a18cc0ca0042cfafb4126516da8520 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 12 Jul 2024 16:41:55 +0200 Subject: [PATCH 26/69] update LUT --- src/functions/LookupTable.t.sol | 79 +- src/functions/Stable2.sol | 37 +- src/functions/StableLUT/Stable2LUT1.sol | 3434 +++++++++-------- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 4 +- 4 files changed, 1792 insertions(+), 1762 deletions(-) diff --git a/src/functions/LookupTable.t.sol b/src/functions/LookupTable.t.sol index 2146a693..776a1c47 100644 --- a/src/functions/LookupTable.t.sol +++ b/src/functions/LookupTable.t.sol @@ -1,53 +1,49 @@ // SPDX-License-Identifier: MIT -// forgefmt: disable-start - pragma solidity ^0.8.20; import {TestHelper, Well, IERC20, console} from "test/TestHelper.sol"; import {Stable2LUT1} from "src/functions/StableLut/Stable2LUT1.sol"; contract LookupTableTest is TestHelper { - Stable2LUT1 lookupTable; Stable2LUT1.PriceData pd; - function setUp() public { lookupTable = new Stable2LUT1(); } function test_getAParameter() public { uint256 a = lookupTable.getAParameter(); - assertEq(a , 1); + assertEq(a, 1); } //////////////// getRatiosFromPriceSwap //////////////// function test_getRatiosFromPriceSwapAroundDollarHigh() public { - uint256 currentPrice = 1e18; + uint256 currentPrice = 1e6; // test 1.0 - 1.10 range - for (uint256 i; i<10 ; i++) { + for (uint256 i; i < 10; i++) { pd = lookupTable.getRatiosFromPriceSwap(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.1e18); - currentPrice += 0.01e18; + assertLt(diff, 0.08e6); + currentPrice += 0.01e6; } } function test_getRatiosFromPriceSwapAroundDollarLow() public { - uint256 currentPrice = 0.9e18; + uint256 currentPrice = 0.9e6; // test 0.9 - 1.0 range - for (uint256 i; i<10 ; i++) { + for (uint256 i; i < 10; i++) { pd = lookupTable.getRatiosFromPriceSwap(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.1e18); - currentPrice += 0.01e18; + assertLt(diff, 0.08e18); + currentPrice += 0.01e6; } } function test_getRatiosFromPriceSwapExtremeLow() public { // pick a value close to the min (P=0.01) - uint256 currentPrice = 0.015e18; + uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); console.log("pd.lowPrice: %e", pd.lowPrice); console.log("pd.highPrice: %e", pd.highPrice); @@ -55,7 +51,7 @@ contract LookupTableTest is TestHelper { function test_getRatiosFromPriceSwapExtremeHigh() public { // pick a value close to the max (P=9.85) - uint256 currentPrice = 9.84e18; + uint256 currentPrice = 9.84e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); console.log("pd.lowPrice: %e", pd.lowPrice); console.log("pd.highPrice: %e", pd.highPrice); @@ -63,44 +59,43 @@ contract LookupTableTest is TestHelper { function testFail_getRatiosFromPriceSwapExtremeLow() public { // pick an out of bounds value (P<0.01) - uint256 currentPrice = 0.001e18; + uint256 currentPrice = 0.001e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); } function testFail_getRatiosFromPriceSwapExtremeHigh() public { // pick an out of bounds value (P>9.85) - uint256 currentPrice = 10e18; + uint256 currentPrice = 10e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); } //////////////// getRatiosFromPriceLiquidity //////////////// - function test_getRatiosFromPriceLiquidityAroundDollarHigh() public { - uint256 currentPrice = 1e18; + uint256 currentPrice = 1e6; // test 1.0 - 1.10 range - for (uint256 i; i<10 ; i++) { + for (uint256 i; i < 10; i++) { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.1e18); - currentPrice += 0.01e18; + assertLt(diff, 0.08e6); + currentPrice += 0.01e6; } } function test_getRatiosFromPriceLiquidityAroundDollarLow() public { - uint256 currentPrice = 0.9e18; + uint256 currentPrice = 0.9e6; // test 0.9 - 1.0 range - for (uint256 i; i<10 ; i++) { + for (uint256 i; i < 10; i++) { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.1e18); - currentPrice += 0.01e18; + assertLt(diff, 0.1e6); + currentPrice += 0.01e6; } } function test_getRatiosFromPriceLiquidityExtremeLow() public { // pick a value close to the min (P=0.01) - uint256 currentPrice = 0.015e18; + uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); console.log("pd.lowPrice: %e", pd.lowPrice); console.log("pd.highPrice: %e", pd.highPrice); @@ -108,7 +103,7 @@ contract LookupTableTest is TestHelper { function test_getRatiosFromPriceLiquidityExtremeHigh() public { // pick a value close to the max (P=9.92) - uint256 currentPrice = 9.91e18; + uint256 currentPrice = 9.91e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); console.log("pd.lowPrice: %e", pd.lowPrice); console.log("pd.highPrice: %e", pd.highPrice); @@ -116,13 +111,37 @@ contract LookupTableTest is TestHelper { function testFail_getRatiosFromPriceLiquidityExtremeLow() public { // pick an out of bounds value (P<0.01) - uint256 currentPrice = 0.001e18; + uint256 currentPrice = 0.001e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); } function testFail_getRatiosFromPriceLiquidityExtremeHigh() public { // pick an out of bounds value (P>9.85) - uint256 currentPrice = 10e18; + uint256 currentPrice = 10e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); } + + ////////////////// Price Range Tests ////////////////// + + function test_PriceRangeSwap() public { + // test range 0.5 - 2.5 + uint256 currentPrice = 0.5e6; + for (uint256 i; i < 200; i++) { + pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + assertGe(pd.highPrice, currentPrice); + assertLt(pd.lowPrice, currentPrice); + currentPrice += 0.01e6; + } + } + + function test_PriceRangeLiq() public { + // test range 0.5 - 2.5 + uint256 currentPrice = 0.5e6; + for (uint256 i; i < 200; i++) { + pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + assertGe(pd.highPrice, currentPrice); + assertLt(pd.lowPrice, currentPrice); + currentPrice += 0.01e6; + } + } } diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 40fa9f1e..96559970 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -53,7 +53,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { error InvalidLUT(); // Due to the complexity of `calcReserveAtRatioLiquidity` and `calcReserveAtRatioSwap`, - // a LUT table is used to reduce the complexity of the calculations on chain. + // a LUT is used to reduce the complexity of the calculations on chain. // the lookup table contract implements 3 functions: // 1. getRatiosFromPriceLiquidity(uint256) -> PriceData memory // 2. getRatiosFromPriceSwap(uint256) -> PriceData memory @@ -66,7 +66,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } /** - * @notice Calculate the amount of LP tokens minted when adding liquidity. + * @notice Calculate the amount of lp tokens given reserves. * D invariant calculation in non-overflowing integer operations iteratively * A * sum(x_i) * n**n + D = A * D * n**n + D**(n+1) / (n**n * prod(x_i)) * @@ -224,7 +224,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory reserves, uint256 i, uint256 j, - bytes calldata data + bytes memory data ) public view returns (uint256 rate) { uint256[] memory decimals = decodeWellData(data); uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); @@ -264,18 +264,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; } - // calc currentPrice: - pd.currentPrice = calcRate(reserves, i, j, data); - // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); - // scale down lutData POC: - // TODO: remove - pd.lutData.precision = pd.lutData.precision * 1e6; - pd.lutData.highPriceJ = pd.lutData.highPriceJ / 1e18; - pd.lutData.lowPriceJ = pd.lutData.lowPriceJ / 1e18; - // update scaledReserve[j] based on lowPrice: console.log("scaledReserve[j] b4", scaledReserves[j]); scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; @@ -285,22 +276,34 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); - pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.lowPriceJ; + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.highPriceJ; console.log("pd.maxStepSize", pd.maxStepSize); - for (uint256 k; k < 255; k++) { - console.log("i", i); + // calc currentPrice: + console.log("scaledReserve[j]", scaledReserves[j]); + console.log("scaledReserve[i]", scaledReserves[i]); + pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); + console.log("initial currentPrice", pd.currentPrice); + + for (uint256 k; k < 10; k++) { + console.log("k", k); // scale stepSize proporitional to distance from price: + console.log("pd.targetPrice", pd.targetPrice); + console.log("pd.currentPrice before stepping", pd.currentPrice); uint256 stepSize = pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); console.log("stepSize", stepSize); // increment reserve by stepSize: + console.log("scaledReserves[j] b4", scaledReserves[j]); scaledReserves[j] = scaledReserves[j] + stepSize; - console.log("scaledReserves[j]", scaledReserves[j]); + console.log("scaledReserves[i] af", scaledReserves[i]); + console.log("scaledReserves[j] af", scaledReserves[j]); // calculate new price from reserves: - pd.currentPrice = calcRate(scaledReserves, i, j, data); + pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); // check if new price is within 1 of target price: + console.log("pd.currentPrice after step size", pd.currentPrice); + console.log("pd.targetPrice", pd.targetPrice); if (pd.currentPrice > pd.targetPrice) { if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); } else { diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index 0d73f8a0..a0907abb 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -5,204 +5,208 @@ pragma solidity ^0.8.17; import {ILookupTable} from "src/interfaces/ILookupTable.sol"; /** - * @title Stable2LUT1 - * @author DeadmanWalking - * @notice The `Stable2` Well Function implementation of - * `calcReserveAtRatioSwap` and `calcReserveAtRatioLiquidity` requires - * multiple calculations of the price given a change in reserves. - * Performing the calculation on-chain is expensive, and thus a LUT is implemented - * to decrease the computation done on execution. - * + * @title BasinStableswapLookupTable + * @dev This contract implements a lookup table of estimations used in the stableswap well function + * to calculate the token ratios in a stableswap pool to return to peg. + * It uses an if ladder structured as a binary tree to store and retrieve + * price-to-token ratio estimates needed for liquidity and swap operations + * within Beanstalk in O(1) time complexity. + * A lookup table was used to avoid the need for expensive calculations on-chain. */ contract Stable2LUT1 is ILookupTable { /** - * @notice returns the effective A parameter that the LUT is used for. + * @notice Returns the amplification coefficient (A parameter) used to calculate the estimates. + * @return The amplification coefficient. */ function getAParameter() external pure returns (uint256) { return 1; } + /** + * @notice Returns the estimated range of reserve ratios for a given price, + * assuming one token reserve remains constant. + * Needed to calculate the liquidity of a well for Beanstalk to return to peg. + * Used in `calcReserveAtRatioLiquidity` function in the stableswap well function. + */ function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { - // todo, remove once LUT is updated. - price = price * 1e12; - if (price < 1.0259848410192591e18) { - if (price < 0.5719086306678213e18) { - if (price < 0.40963548734729605e18) { - if (price < 0.33971565164228473e18) { - if (price < 0.3017802513050929e18) { - if (price < 0.28982321123204907e18) { - if (price < 0.010863721053450156e18) { - revert("Price too low, well is bricked"); + if (price < 1.0259e6) { + if (price < 0.5719e6) { + if (price < 0.4096e6) { + if (price < 0.3397e6) { + if (price < 0.3017e6) { + if (price < 0.2898e6) { + if (price < 0.0108e6) { + revert("LUT: Invalid price"); } else { return PriceData( - 0.28982321123204907e18, - 0, - 7_039_988_712_124_657_677_893_632, - 0.010863721053450156e18, - 0, - 200_000_000_000_000_009_529_458_688, + 0.2898e6, + 0e18, + 703_998_871_212_465_767e18, + 0.0108e6, + 0e18, + 200_000_000_000_000_009e18, 1e18 ); } } else { - if (price < 0.30093898499677296e18) { + if (price < 0.3009e6) { return PriceData( - 0.3017802513050929e18, - 0, - 6_704_751_154_404_434_861_096_960, - 0.30093898499677296e18, - 0, - 6_727_499_949_325_611_769_528_320, + 0.3017e6, + 0e18, + 670_475_115_440_443_486e18, + 0.2898e6, + 0e18, + 703_998_871_212_465_767e18, 1e18 ); } else { return PriceData( - 0.3017802513050929e18, - 0, - 6_704_751_154_404_434_861_096_960, - 0.30093898499677296e18, - 0, - 6_727_499_949_325_611_769_528_320, + 0.3017e6, + 0e18, + 670_475_115_440_443_486e18, + 0.3009e6, + 0e18, + 672_749_994_932_561_176e18, 1e18 ); } } } else { - if (price < 0.3252445574032712e18) { - if (price < 0.31408250964722695e18) { + if (price < 0.3252e6) { + if (price < 0.314e6) { return PriceData( - 0.3252445574032712e18, - 0, - 6_115_909_044_841_464_756_961_280, - 0.31408250964722695e18, - 0, - 6_385_477_289_908_985_837_649_920, + 0.3252e6, + 0e18, + 611_590_904_484_146_475e18, + 0.3017e6, + 0e18, + 670_475_115_440_443_486e18, 1e18 ); } else { return PriceData( - 0.3252445574032712e18, - 0, - 6_115_909_044_841_464_756_961_280, - 0.31408250964722695e18, - 0, - 6_385_477_289_908_985_837_649_920, + 0.3252e6, + 0e18, + 611_590_904_484_146_475e18, + 0.314e6, + 0e18, + 638_547_728_990_898_583e18, 1e18 ); } } else { - if (price < 0.3267284286293485e18) { + if (price < 0.3267e6) { return PriceData( - 0.33971565164228473e18, - 0, - 5_791_816_135_971_868_541_714_432, - 0.3267284286293485e18, - 0, - 6_081_406_942_770_462_344_609_792, + 0.3397e6, + 0e18, + 579_181_613_597_186_854e18, + 0.3252e6, + 0e18, + 611_590_904_484_146_475e18, 1e18 ); } else { return PriceData( - 0.33971565164228473e18, - 0, - 5_791_816_135_971_868_541_714_432, - 0.3267284286293485e18, - 0, - 6_081_406_942_770_462_344_609_792, + 0.3397e6, + 0e18, + 579_181_613_597_186_854e18, + 0.3267e6, + 0e18, + 608_140_694_277_046_234e18, 1e18 ); } } } } else { - if (price < 0.37773904701946753e18) { - if (price < 0.35304103579667717e18) { - if (price < 0.35085305477232803e18) { + if (price < 0.3777e6) { + if (price < 0.353e6) { + if (price < 0.3508e6) { return PriceData( - 0.35304103579667717e18, - 0, - 5_516_015_367_592_256_009_666_560, - 0.35085305477232803e18, - 0, - 5_559_917_313_492_240_492_920_832, + 0.353e6, + 0e18, + 551_601_536_759_225_600e18, + 0.3397e6, + 0e18, + 579_181_613_597_186_854e18, 1e18 ); } else { return PriceData( - 0.35304103579667717e18, - 0, - 5_516_015_367_592_256_009_666_560, - 0.35085305477232803e18, - 0, - 5_559_917_313_492_240_492_920_832, + 0.353e6, + 0e18, + 551_601_536_759_225_600e18, + 0.3508e6, + 0e18, + 555_991_731_349_224_049e18, 1e18 ); } } else { - if (price < 0.36670067588887123e18) { + if (price < 0.3667e6) { return PriceData( - 0.37773904701946753e18, - 0, - 5_054_470_284_992_945_024_139_264, - 0.36670067588887123e18, - 0, - 5_253_347_969_135_480_431_181_824, + 0.3777e6, + 0e18, + 505_447_028_499_294_502e18, + 0.353e6, + 0e18, + 551_601_536_759_225_600e18, 1e18 ); } else { return PriceData( - 0.37773904701946753e18, - 0, - 5_054_470_284_992_945_024_139_264, - 0.36670067588887123e18, - 0, - 5_253_347_969_135_480_431_181_824, + 0.3777e6, + 0e18, + 505_447_028_499_294_502e18, + 0.3667e6, + 0e18, + 525_334_796_913_548_043e18, 1e18 ); } } } else { - if (price < 0.3950035195454608e18) { - if (price < 0.3806899403242078e18) { + if (price < 0.395e6) { + if (price < 0.3806e6) { return PriceData( - 0.3950035195454608e18, - 0, - 4_764_941_468_603_611_231_551_488, - 0.3806899403242078e18, - 0, - 5_003_188_542_033_791_551_537_152, + 0.395e6, + 0e18, + 476_494_146_860_361_123e18, + 0.3777e6, + 0e18, + 505_447_028_499_294_502e18, 1e18 ); } else { return PriceData( - 0.3950035195454608e18, - 0, - 4_764_941_468_603_611_231_551_488, - 0.3806899403242078e18, - 0, - 5_003_188_542_033_791_551_537_152, + 0.395e6, + 0e18, + 476_494_146_860_361_123e18, + 0.3806e6, + 0e18, + 500_318_854_203_379_155e18, 1e18 ); } } else { - if (price < 0.40586651378772454e18) { + if (price < 0.4058e6) { return PriceData( - 0.40963548734729605e18, - 0, - 4_538_039_493_908_200_584_904_704, - 0.40586651378772454e18, - 0, - 4_594_972_986_357_222_505_185_280, + 0.4096e6, + 0e18, + 453_803_949_390_820_058e18, + 0.395e6, + 0e18, + 476_494_146_860_361_123e18, 1e18 ); } else { return PriceData( - 0.40963548734729605e18, - 0, - 4_538_039_493_908_200_584_904_704, - 0.40586651378772454e18, - 0, - 4_594_972_986_357_222_505_185_280, + 0.4096e6, + 0e18, + 453_803_949_390_820_058e18, + 0.4058e6, + 0e18, + 459_497_298_635_722_250e18, 1e18 ); } @@ -210,178 +214,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.49721516194096665e18) { - if (price < 0.4553748640059295e18) { - if (price < 0.4351903417101215e18) { - if (price < 0.42457937527049394e18) { + if (price < 0.4972e6) { + if (price < 0.4553e6) { + if (price < 0.4351e6) { + if (price < 0.4245e6) { return PriceData( - 0.4351903417101215e18, - 0, - 4_177_248_169_415_656_725_282_816, - 0.42457937527049394e18, - 0, - 4_321_942_375_150_666_456_760_320, + 0.4351e6, + 0e18, + 417_724_816_941_565_672e18, + 0.4096e6, + 0e18, + 453_803_949_390_820_058e18, 1e18 ); } else { return PriceData( - 0.4351903417101215e18, - 0, - 4_177_248_169_415_656_725_282_816, - 0.42457937527049394e18, - 0, - 4_321_942_375_150_666_456_760_320, + 0.4351e6, + 0e18, + 417_724_816_941_565_672e18, + 0.4245e6, + 0e18, + 432_194_237_515_066_645e18, 1e18 ); } } else { - if (price < 0.439828260066239e18) { + if (price < 0.4398e6) { return PriceData( - 0.4553748640059295e18, - 0, - 3_920_129_138_458_653_678_895_104, - 0.439828260066239e18, - 0, - 4_116_135_595_381_586_846_023_680, + 0.4553e6, + 0e18, + 392_012_913_845_865_367e18, + 0.4351e6, + 0e18, + 417_724_816_941_565_672e18, 1e18 ); } else { return PriceData( - 0.4553748640059295e18, - 0, - 3_920_129_138_458_653_678_895_104, - 0.439828260066239e18, - 0, - 4_116_135_595_381_586_846_023_680, + 0.4553e6, + 0e18, + 392_012_913_845_865_367e18, + 0.4398e6, + 0e18, + 411_613_559_538_158_684e18, 1e18 ); } } } else { - if (price < 0.4712116675914488e18) { - if (price < 0.465658539376915e18) { + if (price < 0.4712e6) { + if (price < 0.4656e6) { return PriceData( - 0.4712116675914488e18, - 0, - 3_733_456_322_341_575_008_976_896, - 0.465658539376915e18, - 0, - 3_797_498_335_832_414_667_931_648, + 0.4712e6, + 0e18, + 373_345_632_234_157_500e18, + 0.4553e6, + 0e18, + 392_012_913_845_865_367e18, 1e18 ); } else { return PriceData( - 0.4712116675914488e18, - 0, - 3_733_456_322_341_575_008_976_896, - 0.465658539376915e18, - 0, - 3_797_498_335_832_414_667_931_648, + 0.4712e6, + 0e18, + 373_345_632_234_157_500e18, + 0.4656e6, + 0e18, + 379_749_833_583_241_466e18, 1e18 ); } } else { - if (price < 0.48733103400099287e18) { + if (price < 0.4873e6) { return PriceData( - 0.49721516194096665e18, - 0, - 3_452_271_214_393_103_755_509_760, - 0.48733103400099287e18, - 0, - 3_555_672_687_944_357_023_580_160, + 0.4972e6, + 0e18, + 345_227_121_439_310_375e18, + 0.4712e6, + 0e18, + 373_345_632_234_157_500e18, 1e18 ); } else { return PriceData( - 0.49721516194096665e18, - 0, - 3_452_271_214_393_103_755_509_760, - 0.48733103400099287e18, - 0, - 3_555_672_687_944_357_023_580_160, + 0.4972e6, + 0e18, + 345_227_121_439_310_375e18, + 0.4873e6, + 0e18, + 355_567_268_794_435_702e18, 1e18 ); } } } } else { - if (price < 0.5373092907143455e18) { - if (price < 0.5203871429939636e18) { - if (price < 0.5037253443931629e18) { + if (price < 0.5373e6) { + if (price < 0.5203e6) { + if (price < 0.5037e6) { return PriceData( - 0.5203871429939636e18, - 0, - 3_225_099_943_713_702_223_544_320, - 0.5037253443931629e18, - 0, - 3_386_354_940_899_387_334_721_536, + 0.5203e6, + 0e18, + 322_509_994_371_370_222e18, + 0.4972e6, + 0e18, + 345_227_121_439_310_375e18, 1e18 ); } else { return PriceData( - 0.5203871429939636e18, - 0, - 3_225_099_943_713_702_223_544_320, - 0.5037253443931629e18, - 0, - 3_386_354_940_899_387_334_721_536, + 0.5203e6, + 0e18, + 322_509_994_371_370_222e18, + 0.5037e6, + 0e18, + 338_635_494_089_938_733e18, 1e18 ); } } else { - if (price < 0.5298038919377664e18) { + if (price < 0.5298e6) { return PriceData( - 0.5373092907143455e18, - 0, - 3_071_523_755_917_811_692_601_344, - 0.5298038919377664e18, - 0, - 3_138_428_376_721_003_609_325_568, + 0.5373e6, + 0e18, + 307_152_375_591_781_169e18, + 0.5203e6, + 0e18, + 322_509_994_371_370_222e18, 1e18 ); } else { return PriceData( - 0.5373092907143455e18, - 0, - 3_071_523_755_917_811_692_601_344, - 0.5298038919377664e18, - 0, - 3_138_428_376_721_003_609_325_568, + 0.5373e6, + 0e18, + 307_152_375_591_781_169e18, + 0.5298e6, + 0e18, + 313_842_837_672_100_360e18, 1e18 ); } } } else { - if (price < 0.5633721785756237e18) { - if (price < 0.5544851258962078e18) { + if (price < 0.5633e6) { + if (price < 0.5544e6) { return PriceData( - 0.5633721785756237e18, - 0, - 2_853_116_706_110_002_939_559_936, - 0.5544851258962078e18, - 0, - 2_925_260_719_921_725_395_959_808, + 0.5633e6, + 0e18, + 285_311_670_611_000_293e18, + 0.5373e6, + 0e18, + 307_152_375_591_781_169e18, 1e18 ); } else { return PriceData( - 0.5633721785756237e18, - 0, - 2_853_116_706_110_002_939_559_936, - 0.5544851258962078e18, - 0, - 2_925_260_719_921_725_395_959_808, + 0.5633e6, + 0e18, + 285_311_670_611_000_293e18, + 0.5544e6, + 0e18, + 292_526_071_992_172_539e18, 1e18 ); } } else { return PriceData( - 0.5719086306678213e18, - 0, - 2_785_962_590_401_643_029_725_184, - 0.5633721785756237e18, - 0, - 2_853_116_706_110_002_939_559_936, + 0.5719e6, + 0e18, + 278_596_259_040_164_302e18, + 0.5633e6, + 0e18, + 285_311_670_611_000_293e18, 1e18 ); } @@ -389,190 +393,190 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.7793501316201135e18) { - if (price < 0.6695828208753776e18) { - if (price < 0.6256182332555407e18) { - if (price < 0.5978757997638141e18) { - if (price < 0.5895746013098355e18) { + if (price < 0.7793e6) { + if (price < 0.6695e6) { + if (price < 0.6256e6) { + if (price < 0.5978e6) { + if (price < 0.5895e6) { return PriceData( - 0.5978757997638141e18, - 0, - 2_593_742_460_100_002_623_520_768, - 0.5895746013098355e18, - 0, - 2_653_297_705_144_421_600_722_944, + 0.5978e6, + 0e18, + 259_374_246_010_000_262e18, + 0.5719e6, + 0e18, + 278_596_259_040_164_302e18, 1e18 ); } else { return PriceData( - 0.5978757997638141e18, - 0, - 2_593_742_460_100_002_623_520_768, - 0.5895746013098355e18, - 0, - 2_653_297_705_144_421_600_722_944, + 0.5978e6, + 0e18, + 259_374_246_010_000_262e18, + 0.5895e6, + 0e18, + 265_329_770_514_442_160e18, 1e18 ); } } else { - if (price < 0.6074788209935387e18) { + if (price < 0.6074e6) { return PriceData( - 0.6256182332555407e18, - 0, - 2_406_619_233_691_085_014_302_720, - 0.6074788209935387e18, - 0, - 2_526_950_195_375_639_466_344_448, + 0.6256e6, + 0e18, + 240_661_923_369_108_501e18, + 0.5978e6, + 0e18, + 259_374_246_010_000_262e18, 1e18 ); } else { return PriceData( - 0.6256182332555407e18, - 0, - 2_406_619_233_691_085_014_302_720, - 0.6074788209935387e18, - 0, - 2_526_950_195_375_639_466_344_448, + 0.6256e6, + 0e18, + 240_661_923_369_108_501e18, + 0.6074e6, + 0e18, + 252_695_019_537_563_946e18, 1e18 ); } } } else { - if (price < 0.6439911146177e18) { - if (price < 0.6332836844239108e18) { + if (price < 0.6439e6) { + if (price < 0.6332e6) { return PriceData( - 0.6439911146177e18, - 0, - 2_292_018_317_801_033_155_215_360, - 0.6332836844239108e18, - 0, - 2_357_947_691_000_001_848_147_968, + 0.6439e6, + 0e18, + 229_201_831_780_103_315e18, + 0.6256e6, + 0e18, + 240_661_923_369_108_501e18, 1e18 ); } else { return PriceData( - 0.6439911146177e18, - 0, - 2_292_018_317_801_033_155_215_360, - 0.6332836844239108e18, - 0, - 2_357_947_691_000_001_848_147_968, + 0.6439e6, + 0e18, + 229_201_831_780_103_315e18, + 0.6332e6, + 0e18, + 235_794_769_100_000_184e18, 1e18 ); } } else { - if (price < 0.6625972448465314e18) { + if (price < 0.6625e6) { return PriceData( - 0.6695828208753776e18, - 0, - 2_143_588_810_000_001_558_118_400, - 0.6625972448465314e18, - 0, - 2_182_874_588_381_936_223_256_576, + 0.6695e6, + 0e18, + 214_358_881_000_000_155e18, + 0.6439e6, + 0e18, + 229_201_831_780_103_315e18, 1e18 ); } else { return PriceData( - 0.6695828208753776e18, - 0, - 2_143_588_810_000_001_558_118_400, - 0.6625972448465314e18, - 0, - 2_182_874_588_381_936_223_256_576, + 0.6695e6, + 0e18, + 214_358_881_000_000_155e18, + 0.6625e6, + 0e18, + 218_287_458_838_193_622e18, 1e18 ); } } } } else { - if (price < 0.7198389360940738e18) { - if (price < 0.7005168813237215e18) { - if (price < 0.6814380734696386e18) { + if (price < 0.7198e6) { + if (price < 0.7005e6) { + if (price < 0.6814e6) { return PriceData( - 0.7005168813237215e18, - 0, - 1_979_931_599_439_398_038_405_120, - 0.6814380734696386e18, - 0, - 2_078_928_179_411_368_074_543_104, + 0.7005e6, + 0e18, + 197_993_159_943_939_803e18, + 0.6695e6, + 0e18, + 214_358_881_000_000_155e18, 1e18 ); } else { return PriceData( - 0.7005168813237215e18, - 0, - 1_979_931_599_439_398_038_405_120, - 0.6814380734696386e18, - 0, - 2_078_928_179_411_368_074_543_104, + 0.7005e6, + 0e18, + 197_993_159_943_939_803e18, + 0.6814e6, + 0e18, + 207_892_817_941_136_807e18, 1e18 ); } } else { - if (price < 0.7067830820833219e18) { + if (price < 0.7067e6) { return PriceData( - 0.7198389360940738e18, - 0, - 1_885_649_142_323_235_971_399_680, - 0.7067830820833219e18, - 0, - 1_948_717_100_000_000_904_003_584, + 0.7198e6, + 0e18, + 188_564_914_232_323_597e18, + 0.7005e6, + 0e18, + 197_993_159_943_939_803e18, 1e18 ); } else { return PriceData( - 0.7198389360940738e18, - 0, - 1_885_649_142_323_235_971_399_680, - 0.7067830820833219e18, - 0, - 1_948_717_100_000_000_904_003_584, + 0.7198e6, + 0e18, + 188_564_914_232_323_597e18, + 0.7067e6, + 0e18, + 194_871_710_000_000_090e18, 1e18 ); } } } else { - if (price < 0.7449218198151675e18) { - if (price < 0.7394116410130436e18) { + if (price < 0.7449e6) { + if (price < 0.7394e6) { return PriceData( - 0.7449218198151675e18, - 0, - 1_771_561_000_000_000_821_821_440, - 0.7394116410130436e18, - 0, - 1_795_856_326_022_129_432_657_920, + 0.7449e6, + 0e18, + 177_156_100_000_000_082e18, + 0.7198e6, + 0e18, + 188_564_914_232_323_597e18, 1e18 ); } else { return PriceData( - 0.7449218198151675e18, - 0, - 1_771_561_000_000_000_821_821_440, - 0.7394116410130436e18, - 0, - 1_795_856_326_022_129_432_657_920, + 0.7449e6, + 0e18, + 177_156_100_000_000_082e18, + 0.7394e6, + 0e18, + 179_585_632_602_212_943e18, 1e18 ); } } else { - if (price < 0.7592446761079147e18) { + if (price < 0.7592e6) { return PriceData( - 0.7793501316201135e18, - 0, - 1_628_894_626_777_441_725_579_264, - 0.7592446761079147e18, - 0, - 1_710_339_358_116_313_758_171_136, + 0.7793e6, + 0e18, + 162_889_462_677_744_172e18, + 0.7449e6, + 0e18, + 177_156_100_000_000_082e18, 1e18 ); } else { return PriceData( - 0.7793501316201135e18, - 0, - 1_628_894_626_777_441_725_579_264, - 0.7592446761079147e18, - 0, - 1_710_339_358_116_313_758_171_136, + 0.7793e6, + 0e18, + 162_889_462_677_744_172e18, + 0.7592e6, + 0e18, + 171_033_935_811_631_375e18, 1e18 ); } @@ -580,178 +584,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.8845707447410645e18) { - if (price < 0.8243266124067958e18) { - if (price < 0.7997426334506159e18) { - if (price < 0.7840681145956876e18) { + if (price < 0.8845e6) { + if (price < 0.8243e6) { + if (price < 0.7997e6) { + if (price < 0.784e6) { return PriceData( - 0.7997426334506159e18, - 0, - 1_551_328_215_978_515_852_427_264, - 0.7840681145956876e18, - 0, - 1_610_510_000_000_000_478_674_944, + 0.7997e6, + 0e18, + 155_132_821_597_851_585e18, + 0.7793e6, + 0e18, + 162_889_462_677_744_172e18, 1e18 ); } else { return PriceData( - 0.7997426334506159e18, - 0, - 1_551_328_215_978_515_852_427_264, - 0.7840681145956876e18, - 0, - 1_610_510_000_000_000_478_674_944, + 0.7997e6, + 0e18, + 155_132_821_597_851_585e18, + 0.784e6, + 0e18, + 161_051_000_000_000_047e18, 1e18 ); } } else { - if (price < 0.8204394607143816e18) { + if (price < 0.8204e6) { return PriceData( - 0.8243266124067958e18, - 0, - 1_464_100_000_000_000_166_723_584, - 0.8204394607143816e18, - 0, - 1_477_455_443_789_062_678_249_472, + 0.8243e6, + 0e18, + 146_410_000_000_000_016e18, + 0.7997e6, + 0e18, + 155_132_821_597_851_585e18, 1e18 ); } else { return PriceData( - 0.8243266124067958e18, - 0, - 1_464_100_000_000_000_166_723_584, - 0.8204394607143816e18, - 0, - 1_477_455_443_789_062_678_249_472, + 0.8243e6, + 0e18, + 146_410_000_000_000_016e18, + 0.8204e6, + 0e18, + 147_745_544_378_906_267e18, 1e18 ); } } } else { - if (price < 0.8628291267605447e18) { - if (price < 0.8414606557036146e18) { + if (price < 0.8628e6) { + if (price < 0.8414e6) { return PriceData( - 0.8628291267605447e18, - 0, - 1_340_095_640_624_999_952_285_696, - 0.8414606557036146e18, - 0, - 1_407_100_422_656_250_003_587_072, + 0.8628e6, + 0e18, + 134_009_564_062_499_995e18, + 0.8243e6, + 0e18, + 146_410_000_000_000_016e18, 1e18 ); } else { return PriceData( - 0.8628291267605447e18, - 0, - 1_340_095_640_624_999_952_285_696, - 0.8414606557036146e18, - 0, - 1_407_100_422_656_250_003_587_072, + 0.8628e6, + 0e18, + 134_009_564_062_499_995e18, + 0.8414e6, + 0e18, + 140_710_042_265_625_000e18, 1e18 ); } } else { - if (price < 0.8658409270814319e18) { + if (price < 0.8658e6) { return PriceData( - 0.8845707447410645e18, - 0, - 1_276_281_562_499_999_992_905_728, - 0.8658409270814319e18, - 0, - 1_331_000_000_000_000_102_760_448, + 0.8845e6, + 0e18, + 127_628_156_249_999_999e18, + 0.8628e6, + 0e18, + 134_009_564_062_499_995e18, 1e18 ); } else { return PriceData( - 0.8845707447410645e18, - 0, - 1_276_281_562_499_999_992_905_728, - 0.8658409270814319e18, - 0, - 1_331_000_000_000_000_102_760_448, + 0.8845e6, + 0e18, + 127_628_156_249_999_999e18, + 0.8658e6, + 0e18, + 133_100_000_000_000_010e18, 1e18 ); } } } } else { - if (price < 0.9523395041216094e18) { - if (price < 0.9087966340219248e18) { - if (price < 0.9067144339078782e18) { + if (price < 0.9523e6) { + if (price < 0.9087e6) { + if (price < 0.9067e6) { return PriceData( - 0.9087966340219248e18, - 0, - 1_209_999_999_999_999_995_805_696, - 0.9067144339078782e18, - 0, - 1_215_506_250_000_000_018_808_832, + 0.9087e6, + 0e18, + 120_999_999_999_999_999e18, + 0.8845e6, + 0e18, + 127_628_156_249_999_999e18, 1e18 ); } else { return PriceData( - 0.9087966340219248e18, - 0, - 1_209_999_999_999_999_995_805_696, - 0.9067144339078782e18, - 0, - 1_215_506_250_000_000_018_808_832, + 0.9087e6, + 0e18, + 120_999_999_999_999_999e18, + 0.9067e6, + 0e18, + 121_550_625_000_000_001e18, 1e18 ); } } else { - if (price < 0.9292922582238045e18) { + if (price < 0.9292e6) { return PriceData( - 0.9523395041216094e18, - 0, - 1_102_500_000_000_000_066_060_288, - 0.9292922582238045e18, - 0, - 1_157_625_000_000_000_062_652_416, + 0.9523e6, + 0e18, + 110_250_000_000_000_006e18, + 0.9087e6, + 0e18, + 120_999_999_999_999_999e18, 1e18 ); } else { return PriceData( - 0.9523395041216094e18, - 0, - 1_102_500_000_000_000_066_060_288, - 0.9292922582238045e18, - 0, - 1_157_625_000_000_000_062_652_416, + 0.9523e6, + 0e18, + 110_250_000_000_000_006e18, + 0.9292e6, + 0e18, + 115_762_500_000_000_006e18, 1e18 ); } } } else { - if (price < 0.9758947609062864e18) { - if (price < 0.9534239217722583e18) { + if (price < 0.9758e6) { + if (price < 0.9534e6) { return PriceData( - 0.9758947609062864e18, - 0, - 1_050_000_000_000_000_062_914_560, - 0.9534239217722583e18, - 0, - 1_100_000_000_000_000_008_388_608, + 0.9758e6, + 0e18, + 105_000_000_000_000_006e18, + 0.9523e6, + 0e18, + 110_250_000_000_000_006e18, 1e18 ); } else { return PriceData( - 0.9758947609062864e18, - 0, - 1_050_000_000_000_000_062_914_560, - 0.9534239217722583e18, - 0, - 1_100_000_000_000_000_008_388_608, + 0.9758e6, + 0e18, + 105_000_000_000_000_006e18, + 0.9534e6, + 0e18, + 110_000_000_000_000_000e18, 1e18 ); } } else { return PriceData( - 1.0259848410192591e18, - 0, - 950_000_000_000_000_037_748_736, - 0.9758947609062864e18, - 0, - 1_050_000_000_000_000_062_914_560, + 1.0259e6, + 0e18, + 950_000_000_000_000_037e18, + 0.9758e6, + 0e18, + 105_000_000_000_000_006e18, 1e18 ); } @@ -760,191 +764,191 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.8687492953303397e18) { - if (price < 1.3748886759657402e18) { - if (price < 1.172935442440526e18) { - if (price < 1.108480455013783e18) { - if (price < 1.054150295008403e18) { - if (price < 1.052684807341633e18) { + if (price < 1.8687e6) { + if (price < 1.3748e6) { + if (price < 1.1729e6) { + if (price < 1.1084e6) { + if (price < 1.0541e6) { + if (price < 1.0526e6) { return PriceData( - 1.054150295008403e18, - 0, - 899_999_999_999_999_958_056_960, - 1.052684807341633e18, - 0, - 902_500_000_000_000_015_728_640, + 1.0541e6, + 0e18, + 899_999_999_999_999_958e18, + 1.0259e6, + 0e18, + 950_000_000_000_000_037e18, 1e18 ); } else { return PriceData( - 1.054150295008403e18, - 0, - 899_999_999_999_999_958_056_960, - 1.052684807341633e18, - 0, - 902_500_000_000_000_015_728_640, + 1.0541e6, + 0e18, + 899_999_999_999_999_958e18, + 1.0526e6, + 0e18, + 902_500_000_000_000_015e18, 1e18 ); } } else { - if (price < 1.0801613471931895e18) { + if (price < 1.0801e6) { return PriceData( - 1.108480455013783e18, - 0, - 814_506_250_000_000_029_294_592, - 1.0801613471931895e18, - 0, - 857_374_999_999_999_981_387_776, + 1.1084e6, + 0e18, + 814_506_250_000_000_029e18, + 1.0541e6, + 0e18, + 899_999_999_999_999_958e18, 1e18 ); } else { return PriceData( - 1.108480455013783e18, - 0, - 814_506_250_000_000_029_294_592, - 1.0801613471931895e18, - 0, - 857_374_999_999_999_981_387_776, + 1.1084e6, + 0e18, + 814_506_250_000_000_029e18, + 1.0801e6, + 0e18, + 857_374_999_999_999_981e18, 1e18 ); } } } else { - if (price < 1.1377127905684534e18) { - if (price < 1.1115968591227412e18) { + if (price < 1.1377e6) { + if (price < 1.1115e6) { return PriceData( - 1.1377127905684534e18, - 0, - 773_780_937_499_999_954_010_112, - 1.1115968591227412e18, - 0, - 810_000_000_000_000_029_360_128, + 1.1377e6, + 0e18, + 773_780_937_499_999_954e18, + 1.1084e6, + 0e18, + 814_506_250_000_000_029e18, 1e18 ); } else { return PriceData( - 1.1377127905684534e18, - 0, - 773_780_937_499_999_954_010_112, - 1.1115968591227412e18, - 0, - 810_000_000_000_000_029_360_128, + 1.1377e6, + 0e18, + 773_780_937_499_999_954e18, + 1.1115e6, + 0e18, + 810_000_000_000_000_029e18, 1e18 ); } } else { - if (price < 1.1679338065313114e18) { + if (price < 1.1679e6) { return PriceData( - 1.172935442440526e18, - 0, - 729_000_000_000_000_039_845_888, - 1.1679338065313114e18, - 0, - 735_091_890_625_000_016_707_584, + 1.1729e6, + 0e18, + 729_000_000_000_000_039e18, + 1.1377e6, + 0e18, + 773_780_937_499_999_954e18, 1e18 ); } else { return PriceData( - 1.172935442440526e18, - 0, - 729_000_000_000_000_039_845_888, - 1.1679338065313114e18, - 0, - 735_091_890_625_000_016_707_584, + 1.1729e6, + 0e18, + 729_000_000_000_000_039e18, + 1.1679e6, + 0e18, + 735_091_890_625_000_016e18, 1e18 ); } } } } else { - if (price < 1.2653583277434033e18) { - if (price < 1.2316684918193181e18) { - if (price < 1.19922388606303e18) { + if (price < 1.2653e6) { + if (price < 1.2316e6) { + if (price < 1.1992e6) { return PriceData( - 1.2316684918193181e18, - 0, - 663_420_431_289_062_394_953_728, - 1.19922388606303e18, - 0, - 698_337_296_093_749_868_232_704, + 1.2316e6, + 0e18, + 663_420_431_289_062_394e18, + 1.1729e6, + 0e18, + 729_000_000_000_000_039e18, 1e18 ); } else { return PriceData( - 1.2316684918193181e18, - 0, - 663_420_431_289_062_394_953_728, - 1.19922388606303e18, - 0, - 698_337_296_093_749_868_232_704, + 1.2316e6, + 0e18, + 663_420_431_289_062_394e18, + 1.1992e6, + 0e18, + 698_337_296_093_749_868e18, 1e18 ); } } else { - if (price < 1.2388474712160897e18) { + if (price < 1.2388e6) { return PriceData( - 1.2653583277434033e18, - 0, - 630_249_409_724_609_181_253_632, - 1.2388474712160897e18, - 0, - 656_100_000_000_000_049_283_072, + 1.2653e6, + 0e18, + 630_249_409_724_609_181e18, + 1.2316e6, + 0e18, + 663_420_431_289_062_394e18, 1e18 ); } else { return PriceData( - 1.2653583277434033e18, - 0, - 630_249_409_724_609_181_253_632, - 1.2388474712160897e18, - 0, - 656_100_000_000_000_049_283_072, + 1.2653e6, + 0e18, + 630_249_409_724_609_181e18, + 1.2388e6, + 0e18, + 656_100_000_000_000_049e18, 1e18 ); } } } else { - if (price < 1.3101053721805707e18) { - if (price < 1.3003895149140976e18) { + if (price < 1.3101e6) { + if (price < 1.3003e6) { return PriceData( - 1.3101053721805707e18, - 0, - 590_490_000_000_000_030_932_992, - 1.3003895149140976e18, - 0, - 598_736_939_238_378_782_588_928, + 1.3101e6, + 0e18, + 590_490_000_000_000_030e18, + 1.2653e6, + 0e18, + 630_249_409_724_609_181e18, 1e18 ); } else { return PriceData( - 1.3101053721805707e18, - 0, - 590_490_000_000_000_030_932_992, - 1.3003895149140976e18, - 0, - 598_736_939_238_378_782_588_928, + 1.3101e6, + 0e18, + 590_490_000_000_000_030e18, + 1.3003e6, + 0e18, + 598_736_939_238_378_782e18, 1e18 ); } } else { - if (price < 1.3368637826453649e18) { + if (price < 1.3368e6) { return PriceData( - 1.3748886759657402e18, - 0, - 540_360_087_662_636_788_875_264, - 1.3368637826453649e18, - 0, - 568_800_092_276_459_816_615_936, + 1.3748e6, + 0e18, + 540_360_087_662_636_788e18, + 1.3101e6, + 0e18, + 590_490_000_000_000_030e18, 1e18 ); } else { return PriceData( - 1.3748886759657402e18, - 0, - 540_360_087_662_636_788_875_264, - 1.3368637826453649e18, - 0, - 568_800_092_276_459_816_615_936, + 1.3748e6, + 0e18, + 540_360_087_662_636_788e18, + 1.3368e6, + 0e18, + 568_800_092_276_459_816e18, 1e18 ); } @@ -952,178 +956,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.5924736308372711e18) { - if (price < 1.4722424340099083e18) { - if (price < 1.4145777805482114e18) { - if (price < 1.387578868152878e18) { + if (price < 1.5924e6) { + if (price < 1.4722e6) { + if (price < 1.4145e6) { + if (price < 1.3875e6) { return PriceData( - 1.4145777805482114e18, - 0, - 513_342_083_279_504_885_678_080, - 1.387578868152878e18, - 0, - 531_440_999_999_999_980_863_488, + 1.4145e6, + 0e18, + 513_342_083_279_504_885e18, + 1.3748e6, + 0e18, + 540_360_087_662_636_788e18, 1e18 ); } else { return PriceData( - 1.4145777805482114e18, - 0, - 513_342_083_279_504_885_678_080, - 1.387578868152878e18, - 0, - 531_440_999_999_999_980_863_488, + 1.4145e6, + 0e18, + 513_342_083_279_504_885e18, + 1.3875e6, + 0e18, + 531_440_999_999_999_980e18, 1e18 ); } } else { - if (price < 1.4560509661144443e18) { + if (price < 1.456e6) { return PriceData( - 1.4722424340099083e18, - 0, - 478_296_899_999_999_996_198_912, - 1.4560509661144443e18, - 0, - 487_674_979_115_529_607_839_744, + 1.4722e6, + 0e18, + 478_296_899_999_999_996e18, + 1.4145e6, + 0e18, + 513_342_083_279_504_885e18, 1e18 ); } else { return PriceData( - 1.4722424340099083e18, - 0, - 478_296_899_999_999_996_198_912, - 1.4560509661144443e18, - 0, - 487_674_979_115_529_607_839_744, + 1.4722e6, + 0e18, + 478_296_899_999_999_996e18, + 1.456e6, + 0e18, + 487_674_979_115_529_607e18, 1e18 ); } } } else { - if (price < 1.544862076961942e18) { - if (price < 1.4994346493022137e18) { + if (price < 1.5448e6) { + if (price < 1.4994e6) { return PriceData( - 1.544862076961942e18, - 0, - 440_126_668_651_765_444_902_912, - 1.4994346493022137e18, - 0, - 463_291_230_159_753_114_025_984, + 1.5448e6, + 0e18, + 440_126_668_651_765_444e18, + 1.4722e6, + 0e18, + 478_296_899_999_999_996e18, 1e18 ); } else { return PriceData( - 1.544862076961942e18, - 0, - 440_126_668_651_765_444_902_912, - 1.4994346493022137e18, - 0, - 463_291_230_159_753_114_025_984, + 1.5448e6, + 0e18, + 440_126_668_651_765_444e18, + 1.4994e6, + 0e18, + 463_291_230_159_753_114e18, 1e18 ); } } else { - if (price < 1.5651840818577563e18) { + if (price < 1.5651e6) { return PriceData( - 1.5924736308372711e18, - 0, - 418_120_335_219_177_149_169_664, - 1.5651840818577563e18, - 0, - 430_467_210_000_000_016_711_680, + 1.5924e6, + 0e18, + 418_120_335_219_177_149e18, + 1.5448e6, + 0e18, + 440_126_668_651_765_444e18, 1e18 ); } else { return PriceData( - 1.5924736308372711e18, - 0, - 418_120_335_219_177_149_169_664, - 1.5651840818577563e18, - 0, - 430_467_210_000_000_016_711_680, + 1.5924e6, + 0e18, + 418_120_335_219_177_149e18, + 1.5651e6, + 0e18, + 430_467_210_000_000_016e18, 1e18 ); } } } } else { - if (price < 1.7499309221597035e18) { - if (price < 1.6676156306366652e18) { - if (price < 1.642417154585352e18) { + if (price < 1.7499e6) { + if (price < 1.6676e6) { + if (price < 1.6424e6) { return PriceData( - 1.6676156306366652e18, - 0, - 387_420_489_000_000_015_040_512, - 1.642417154585352e18, - 0, - 397_214_318_458_218_211_180_544, + 1.6676e6, + 0e18, + 387_420_489_000_000_015e18, + 1.5924e6, + 0e18, + 418_120_335_219_177_149e18, 1e18 ); } else { return PriceData( - 1.6676156306366652e18, - 0, - 387_420_489_000_000_015_040_512, - 1.642417154585352e18, - 0, - 397_214_318_458_218_211_180_544, + 1.6676e6, + 0e18, + 387_420_489_000_000_015e18, + 1.6424e6, + 0e18, + 397_214_318_458_218_211e18, 1e18 ); } } else { - if (price < 1.6948483041045312e18) { + if (price < 1.6948e6) { return PriceData( - 1.7499309221597035e18, - 0, - 358_485_922_408_541_867_474_944, - 1.6948483041045312e18, - 0, - 377_353_602_535_307_320_754_176, + 1.7499e6, + 0e18, + 358_485_922_408_541_867e18, + 1.6676e6, + 0e18, + 387_420_489_000_000_015e18, 1e18 ); } else { return PriceData( - 1.7499309221597035e18, - 0, - 358_485_922_408_541_867_474_944, - 1.6948483041045312e18, - 0, - 377_353_602_535_307_320_754_176, + 1.7499e6, + 0e18, + 358_485_922_408_541_867e18, + 1.6948e6, + 0e18, + 377_353_602_535_307_320e18, 1e18 ); } } } else { - if (price < 1.8078374383281453e18) { - if (price < 1.7808846093213746e18) { + if (price < 1.8078e6) { + if (price < 1.7808e6) { return PriceData( - 1.8078374383281453e18, - 0, - 340_561_626_288_114_794_233_856, - 1.7808846093213746e18, - 0, - 348_678_440_100_000_033_669_120, + 1.8078e6, + 0e18, + 340_561_626_288_114_794e18, + 1.7499e6, + 0e18, + 358_485_922_408_541_867e18, 1e18 ); } else { return PriceData( - 1.8078374383281453e18, - 0, - 340_561_626_288_114_794_233_856, - 1.7808846093213746e18, - 0, - 348_678_440_100_000_033_669_120, + 1.8078e6, + 0e18, + 340_561_626_288_114_794e18, + 1.7808e6, + 0e18, + 348_678_440_100_000_033e18, 1e18 ); } } else { return PriceData( - 1.8687492953303397e18, - 0, - 323_533_544_973_709_067_943_936, - 1.8078374383281453e18, - 0, - 340_561_626_288_114_794_233_856, + 1.8687e6, + 0e18, + 323_533_544_973_709_067e18, + 1.8078e6, + 0e18, + 340_561_626_288_114_794e18, 1e18 ); } @@ -1131,351 +1135,351 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 2.690522555073485e18) { - if (price < 2.2254297444346713e18) { - if (price < 2.0460875899097033e18) { - if (price < 1.9328574028605179e18) { - if (price < 1.9064879443100322e18) { + if (price < 2.6905e6) { + if (price < 2.2254e6) { + if (price < 2.046e6) { + if (price < 1.9328e6) { + if (price < 1.9064e6) { return PriceData( - 1.9328574028605179e18, - 0, - 307_356_867_725_023_611_191_296, - 1.9064879443100322e18, - 0, - 313_810_596_089_999_996_747_776, + 1.9328e6, + 0e18, + 307_356_867_725_023_611e18, + 1.8687e6, + 0e18, + 323_533_544_973_709_067e18, 1e18 ); } else { return PriceData( - 1.9328574028605179e18, - 0, - 307_356_867_725_023_611_191_296, - 1.9064879443100322e18, - 0, - 313_810_596_089_999_996_747_776, + 1.9328e6, + 0e18, + 307_356_867_725_023_611e18, + 1.9064e6, + 0e18, + 313_810_596_089_999_996e18, 1e18 ); } } else { - if (price < 2.000362620089605e18) { + if (price < 2.0003e6) { return PriceData( - 2.0460875899097033e18, - 0, - 282_429_536_481_000_023_916_544, - 2.000362620089605e18, - 0, - 291_989_024_338_772_393_721_856, + 2.046e6, + 0e18, + 0.282429536481000023e18, + 1.9328e6, + 0e18, + 0.307356867725023611e18, 1e18 ); } else { return PriceData( - 2.0460875899097033e18, - 0, - 282_429_536_481_000_023_916_544, - 2.000362620089605e18, - 0, - 291_989_024_338_772_393_721_856, + 2.046e6, + 0e18, + 282_429_536_481_000_023e18, + 2.0003e6, + 0e18, + 291_989_024_338_772_393e18, 1e18 ); } } } else { - if (price < 2.146420673410915e18) { - if (price < 2.0714762680784395e18) { + if (price < 2.1464e6) { + if (price < 2.0714e6) { return PriceData( - 2.146420673410915e18, - 0, - 263_520_094_465_742_052_786_176, - 2.0714762680784395e18, - 0, - 277_389_573_121_833_787_457_536, + 2.1464e6, + 0e18, + 263_520_094_465_742_052e18, + 2.046e6, + 0e18, + 282_429_536_481_000_023e18, 1e18 ); } else { return PriceData( - 2.146420673410915e18, - 0, - 263_520_094_465_742_052_786_176, - 2.0714762680784395e18, - 0, - 277_389_573_121_833_787_457_536, + 2.1464e6, + 0e18, + 263_520_094_465_742_052e18, + 2.0714e6, + 0e18, + 277_389_573_121_833_787e18, 1e18 ); } } else { - if (price < 2.2015282751469254e18) { + if (price < 2.2015e6) { return PriceData( - 2.2254297444346713e18, - 0, - 250_344_089_742_454_930_014_208, - 2.2015282751469254e18, - 0, - 254_186_582_832_900_011_458_560, + 2.2254e6, + 0e18, + 250_344_089_742_454_930e18, + 2.1464e6, + 0e18, + 263_520_094_465_742_052e18, 1e18 ); } else { return PriceData( - 2.2254297444346713e18, - 0, - 250_344_089_742_454_930_014_208, - 2.2015282751469254e18, - 0, - 254_186_582_832_900_011_458_560, + 2.2254e6, + 0e18, + 250_344_089_742_454_930e18, + 2.2015e6, + 0e18, + 254_186_582_832_900_011e18, 1e18 ); } } } } else { - if (price < 2.4893708294330623e18) { - if (price < 2.374857559565706e18) { - if (price < 2.3087495815807664e18) { + if (price < 2.4893e6) { + if (price < 2.3748e6) { + if (price < 2.3087e6) { return PriceData( - 2.374857559565706e18, - 0, - 228_767_924_549_610_027_089_920, - 2.3087495815807664e18, - 0, - 237_826_885_255_332_165_058_560, + 2.3748e6, + 0e18, + 228_767_924_549_610_027e18, + 2.2254e6, + 0e18, + 250_344_089_742_454_930e18, 1e18 ); } else { return PriceData( - 2.374857559565706e18, - 0, - 228_767_924_549_610_027_089_920, - 2.3087495815807664e18, - 0, - 237_826_885_255_332_165_058_560, + 2.3748e6, + 0e18, + 228_767_924_549_610_027e18, + 2.3087e6, + 0e18, + 237_826_885_255_332_165e18, 1e18 ); } } else { - if (price < 2.3966391233230944e18) { + if (price < 2.3966e6) { return PriceData( - 2.4893708294330623e18, - 0, - 214_638_763_942_937_242_894_336, - 2.3966391233230944e18, - 0, - 225_935_540_992_565_540_028_416, + 2.4893e6, + 0e18, + 214_638_763_942_937_242e18, + 2.3748e6, + 0e18, + 228_767_924_549_610_027e18, 1e18 ); } else { return PriceData( - 2.4893708294330623e18, - 0, - 214_638_763_942_937_242_894_336, - 2.3966391233230944e18, - 0, - 225_935_540_992_565_540_028_416, + 2.4893e6, + 0e18, + 214_638_763_942_937_242e18, + 2.3966e6, + 0e18, + 225_935_540_992_565_540e18, 1e18 ); } } } else { - if (price < 2.5872314032850503e18) { - if (price < 2.5683484144734416e18) { + if (price < 2.5872e6) { + if (price < 2.5683e6) { return PriceData( - 2.5872314032850503e18, - 0, - 203_906_825_745_790_394_171_392, - 2.5683484144734416e18, - 0, - 205_891_132_094_649_041_158_144, + 2.5872e6, + 0e18, + 203_906_825_745_790_394e18, + 2.4893e6, + 0e18, + 214_638_763_942_937_242e18, 1e18 ); } else { return PriceData( - 2.5872314032850503e18, - 0, - 203_906_825_745_790_394_171_392, - 2.5683484144734416e18, - 0, - 205_891_132_094_649_041_158_144, + 2.5872e6, + 0e18, + 203_906_825_745_790_394e18, + 2.5683e6, + 0e18, + 205_891_132_094_649_041e18, 1e18 ); } } else { return PriceData( - 2.690522555073485e18, - 0, - 193_711_484_458_500_834_197_504, - 2.5872314032850503e18, - 0, - 203_906_825_745_790_394_171_392, + 2.6905e6, + 0e18, + 193_711_484_458_500_834e18, + 2.5872e6, + 0e18, + 203_906_825_745_790_394e18, 1e18 ); } } } } else { - if (price < 3.3001527441316973e18) { - if (price < 3.0261889293694004e18) { - if (price < 2.7995618079130224e18) { - if (price < 2.7845245737305033e18) { + if (price < 3.3001e6) { + if (price < 3.0261e6) { + if (price < 2.7995e6) { + if (price < 2.7845e6) { return PriceData( - 2.7995618079130224e18, - 0, - 184_025_910_235_575_779_065_856, - 2.7845245737305033e18, - 0, - 185_302_018_885_184_143_753_216, + 2.7995e6, + 0e18, + 184_025_910_235_575_779e18, + 2.6905e6, + 0e18, + 193_711_484_458_500_834e18, 1e18 ); } else { return PriceData( - 2.7995618079130224e18, - 0, - 184_025_910_235_575_779_065_856, - 2.7845245737305033e18, - 0, - 185_302_018_885_184_143_753_216, + 2.7995e6, + 0e18, + 184_025_910_235_575_779e18, + 2.7845e6, + 0e18, + 185_302_018_885_184_143e18, 1e18 ); } } else { - if (price < 2.9146833489094695e18) { + if (price < 2.9146e6) { return PriceData( - 3.0261889293694004e18, - 0, - 166_771_816_996_665_739_444_224, - 2.9146833489094695e18, - 0, - 174_824_614_723_796_969_979_904, + 3.0261e6, + 0e18, + 166_771_816_996_665_739e18, + 2.7995e6, + 0e18, + 184_025_910_235_575_779e18, 1e18 ); } else { return PriceData( - 3.0261889293694004e18, - 0, - 166_771_816_996_665_739_444_224, - 2.9146833489094695e18, - 0, - 174_824_614_723_796_969_979_904, + 3.0261e6, + 0e18, + 166_771_816_996_665_739e18, + 2.9146e6, + 0e18, + 174_824_614_723_796_969e18, 1e18 ); } } } else { - if (price < 3.1645988027761267e18) { - if (price < 3.0362389274108987e18) { + if (price < 3.1645e6) { + if (price < 3.0362e6) { return PriceData( - 3.1645988027761267e18, - 0, - 157_779_214_788_226_770_272_256, - 3.0362389274108987e18, - 0, - 166_083_383_987_607_124_836_352, + 3.1645e6, + 0e18, + 157_779_214_788_226_770e18, + 3.0261e6, + 0e18, + 166_771_816_996_665_739e18, 1e18 ); } else { return PriceData( - 3.1645988027761267e18, - 0, - 157_779_214_788_226_770_272_256, - 3.0362389274108987e18, - 0, - 166_083_383_987_607_124_836_352, + 3.1645e6, + 0e18, + 157_779_214_788_226_770e18, + 3.0362e6, + 0e18, + 166_083_383_987_607_124e18, 1e18 ); } } else { - if (price < 3.2964552820220048e18) { + if (price < 3.2964e6) { return PriceData( - 3.3001527441316973e18, - 0, - 149_890_254_048_815_411_625_984, - 3.2964552820220048e18, - 0, - 150_094_635_296_999_155_433_472, + 3.3001e6, + 0e18, + 149_890_254_048_815_411e18, + 3.1645e6, + 0e18, + 157_779_214_788_226_770e18, 1e18 ); } else { return PriceData( - 3.3001527441316973e18, - 0, - 149_890_254_048_815_411_625_984, - 3.2964552820220048e18, - 0, - 150_094_635_296_999_155_433_472, + 3.3001e6, + 0e18, + 149_890_254_048_815_411e18, + 3.2964e6, + 0e18, + 150_094_635_296_999_155e18, 1e18 ); } } } } else { - if (price < 3.754191847846751e18) { - if (price < 3.5945058336600226e18) { - if (price < 3.4433110847289314e18) { + if (price < 3.7541e6) { + if (price < 3.5945e6) { + if (price < 3.4433e6) { return PriceData( - 3.5945058336600226e18, - 0, - 135_275_954_279_055_877_996_544, - 3.4433110847289314e18, - 0, - 142_395_741_346_374_623_428_608, + 3.5945e6, + 0e18, + 135_275_954_279_055_877e18, + 3.3001e6, + 0e18, + 149_890_254_048_815_411e18, 1e18 ); } else { return PriceData( - 3.5945058336600226e18, - 0, - 135_275_954_279_055_877_996_544, - 3.4433110847289314e18, - 0, - 142_395_741_346_374_623_428_608, + 3.5945e6, + 0e18, + 135_275_954_279_055_877e18, + 3.4433e6, + 0e18, + 142_395_741_346_374_623e18, 1e18 ); } } else { - if (price < 3.5987837944717675e18) { + if (price < 3.5987e6) { return PriceData( - 3.754191847846751e18, - 0, - 128_512_156_565_103_091_646_464, - 3.5987837944717675e18, - 0, - 135_085_171_767_299_243_245_568, + 3.7541e6, + 0e18, + 128_512_156_565_103_091e18, + 3.5945e6, + 0e18, + 135_275_954_279_055_877e18, 1e18 ); } else { return PriceData( - 3.754191847846751e18, - 0, - 128_512_156_565_103_091_646_464, - 3.5987837944717675e18, - 0, - 135_085_171_767_299_243_245_568, + 3.7541e6, + 0e18, + 128_512_156_565_103_091e18, + 3.5987e6, + 0e18, + 135_085_171_767_299_243e18, 1e18 ); } } } else { - if (price < 9.922382968146263e18) { - if (price < 3.9370205389323547e18) { + if (price < 9.9223e6) { + if (price < 3.937e6) { return PriceData( - 9.922382968146263e18, - 0, - 44_999_999_999_999_997_902_848, - 3.9370205389323547e18, - 0, - 121_576_654_590_569_315_565_568, + 9.9223e6, + 0e18, + 449_999_999_999_999_979e18, + 3.7541e6, + 0e18, + 128_512_156_565_103_091e18, 1e18 ); } else { return PriceData( - 9.922382968146263e18, - 0, - 44_999_999_999_999_997_902_848, - 3.9370205389323547e18, - 0, - 121_576_654_590_569_315_565_568, + 9.9223e6, + 0e18, + 449_999_999_999_999_979e18, + 3.937e6, + 0e18, + 121_576_654_590_569_315e18, 1e18 ); } } else { - revert("Price is too High, well is bricked"); + revert("LUT: Invalid price"); } } } @@ -1484,187 +1488,191 @@ contract Stable2LUT1 is ILookupTable { } } + /** + * @notice Returns the estimated range of reserve ratios for a given price, + * assuming the pool liquidity remains constant. + * Needed to calculate the amounts of assets to swap in a well for Beanstalk to return to peg. + * Used in `calcReserveAtRatioSwap` function in the stableswap well function. + */ function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) { - // todo, remove once LUT is updated. - price = price * 1e12; - if (price < 0.9837578538258441e18) { - if (price < 0.7657894226400184e18) { - if (price < 0.6853356086625426e18) { - if (price < 0.6456640806168077e18) { - if (price < 0.5892475765452702e18) { - if (price < 0.5536123757558428e18) { - if (price < 0.010442230679169563e18) { - revert("Price is too low, Well is bricked!"); + if (price < 0.9837e6) { + if (price < 0.7657e6) { + if (price < 0.6853e6) { + if (price < 0.6456e6) { + if (price < 0.5892e6) { + if (price < 0.5536e6) { + if (price < 0.0104e6) { + revert("LUT: Invalid price"); } else { return PriceData( - 0.5536123757558428e18, - 1_599_999_999_999_999_865_782_272, - 545_614_743_365_999_923_298_304, - 0.010442230679169563e18, - 6_271_998_357_018_457_453_625_344, - 30_170_430_329_999_536_947_200, + 0.5536e6, + 159_999_999_999_999_986e18, + 545_614_743_365_999_923e18, + 0.0104e6, + 627_199_835_701_845_745e18, + 301_704_303_299_995_369e18, 1e18 ); } } else { - if (price < 0.5712351217876162e18) { + if (price < 0.5712e6) { return PriceData( - 0.5892475765452702e18, - 1_540_000_000_000_000_092_274_688, - 579_889_286_297_999_932_129_280, - 0.5712351217876162e18, - 1_569_999_999_999_999_979_028_480, - 562_484_780_749_999_921_168_384, + 0.5892e6, + 154_000_000_000_000_009e18, + 579_889_286_297_999_932e18, + 0.5536e6, + 159_999_999_999_999_986e18, + 545_614_743_365_999_923e18, 1e18 ); } else { return PriceData( - 0.5892475765452702e18, - 1_540_000_000_000_000_092_274_688, - 579_889_286_297_999_932_129_280, - 0.5712351217876162e18, - 1_569_999_999_999_999_979_028_480, - 562_484_780_749_999_921_168_384, + 0.5892e6, + 154_000_000_000_000_009e18, + 579_889_286_297_999_932e18, + 0.5712e6, + 156_999_999_999_999_997e18, + 562_484_780_749_999_921e18, 1e18 ); } } } else { - if (price < 0.6264561035067443e18) { - if (price < 0.6076529363710561e18) { + if (price < 0.6264e6) { + if (price < 0.6076e6) { return PriceData( - 0.6264561035067443e18, - 1_480_000_000_000_000_050_331_648, - 616_348_759_373_999_889_186_816, - 0.6076529363710561e18, - 1_509_999_999_999_999_937_085_440, - 597_839_994_250_999_858_462_720, + 0.6264e6, + 148_000_000_000_000_005e18, + 616_348_759_373_999_889e18, + 0.5892e6, + 154_000_000_000_000_009e18, + 579_889_286_297_999_932e18, 1e18 ); } else { return PriceData( - 0.6264561035067443e18, - 1_480_000_000_000_000_050_331_648, - 616_348_759_373_999_889_186_816, - 0.6076529363710561e18, - 1_509_999_999_999_999_937_085_440, - 597_839_994_250_999_858_462_720, + 0.6264e6, + 148_000_000_000_000_005e18, + 616_348_759_373_999_889e18, + 0.6076e6, + 150_999_999_999_999_993e18, + 597_839_994_250_999_858e18, 1e18 ); } } else { - if (price < 0.6418277116264056e18) { + if (price < 0.6418e6) { return PriceData( - 0.6456640806168077e18, - 1_449_999_999_999_999_895_142_400, - 635_427_613_953_999_981_510_656, - 0.6418277116264056e18, - 1_456_009_992_965_810_341_019_648, - 631_633_461_631_000_017_633_280, + 0.6456e6, + 144_999_999_999_999_989e18, + 635_427_613_953_999_981e18, + 0.6264e6, + 148_000_000_000_000_005e18, + 616_348_759_373_999_889e18, 1e18 ); } else { return PriceData( - 0.6456640806168077e18, - 1_449_999_999_999_999_895_142_400, - 635_427_613_953_999_981_510_656, - 0.6418277116264056e18, - 1_456_009_992_965_810_341_019_648, - 631_633_461_631_000_017_633_280, + 0.6456e6, + 144_999_999_999_999_989e18, + 635_427_613_953_999_981e18, + 0.6418e6, + 145_600_999_296_581_034e18, + 631_633_461_631_000_017e18, 1e18 ); } } } } else { - if (price < 0.6652864026252798e18) { - if (price < 0.6548068731524411e18) { - if (price < 0.6482942041630876e18) { + if (price < 0.6652e6) { + if (price < 0.6548e6) { + if (price < 0.6482e6) { return PriceData( - 0.6548068731524411e18, - 1_436_009_992_965_810_416_517_120, - 644_598_199_151_000_104_730_624, - 0.6482942041630876e18, - 1_446_009_992_965_810_378_768_384, - 638_083_385_911_000_032_083_968, + 0.6548e6, + 143_600_999_296_581_041e18, + 644_598_199_151_000_104e18, + 0.6456e6, + 144_999_999_999_999_989e18, + 635_427_613_953_999_981e18, 1e18 ); } else { return PriceData( - 0.6548068731524411e18, - 1_436_009_992_965_810_416_517_120, - 644_598_199_151_000_104_730_624, - 0.6482942041630876e18, - 1_446_009_992_965_810_378_768_384, - 638_083_385_911_000_032_083_968, + 0.6548e6, + 143_600_999_296_581_041e18, + 644_598_199_151_000_104e18, + 0.6482e6, + 144_600_999_296_581_037e18, + 638_083_385_911_000_032e18, 1e18 ); } } else { - if (price < 0.6613661573609778e18) { + if (price < 0.6613e6) { return PriceData( - 0.6652864026252798e18, - 1_420_000_000_000_000_008_388_608, - 655_088_837_188_999_989_690_368, - 0.6613661573609778e18, - 1_426_009_992_965_810_454_265_856, - 651_178_365_231_000_060_952_576, + 0.6652e6, + 142_000_000_000_000_000e18, + 655_088_837_188_999_989e18, + 0.6548e6, + 143_600_999_296_581_041e18, + 644_598_199_151_000_104e18, 1e18 ); } else { return PriceData( - 0.6652864026252798e18, - 1_420_000_000_000_000_008_388_608, - 655_088_837_188_999_989_690_368, - 0.6613661573609778e18, - 1_426_009_992_965_810_454_265_856, - 651_178_365_231_000_060_952_576, + 0.6652e6, + 142_000_000_000_000_000e18, + 655_088_837_188_999_989e18, + 0.6613e6, + 142_600_999_296_581_045e18, + 651_178_365_231_000_060e18, 1e18 ); } } } else { - if (price < 0.6746265282007935e18) { - if (price < 0.667972535466219e18) { + if (price < 0.6746e6) { + if (price < 0.6679e6) { return PriceData( - 0.6746265282007935e18, - 1_406_009_992_965_810_529_763_328, - 664_536_634_765_000_046_542_848, - 0.667972535466219e18, - 1_416_009_992_965_810_492_014_592, - 657_824_352_616_000_123_830_272, + 0.6746e6, + 140_600_999_296_581_052e18, + 664_536_634_765_000_046e18, + 0.6652e6, + 142_000_000_000_000_000e18, + 655_088_837_188_999_989e18, 1e18 ); } else { return PriceData( - 0.6746265282007935e18, - 1_406_009_992_965_810_529_763_328, - 664_536_634_765_000_046_542_848, - 0.667972535466219e18, - 1_416_009_992_965_810_492_014_592, - 657_824_352_616_000_123_830_272, + 0.6746e6, + 140_600_999_296_581_052e18, + 664_536_634_765_000_046e18, + 0.6679e6, + 141_600_999_296_581_049e18, + 657_824_352_616_000_123e18, 1e18 ); } } else { - if (price < 0.6813287005626545e18) { + if (price < 0.6813e6) { return PriceData( - 0.6853356086625426e18, - 1_390_000_000_000_000_121_634_816, - 675_345_038_118_000_013_606_912, - 0.6813287005626545e18, - 1_396_009_992_965_810_567_512_064, - 671_315_690_563_000_076_337_152, + 0.6853e6, + 139_000_000_000_000_012e18, + 675_345_038_118_000_013e18, + 0.6746e6, + 140_600_999_296_581_052e18, + 664_536_634_765_000_046e18, 1e18 ); } else { return PriceData( - 0.6853356086625426e18, - 1_390_000_000_000_000_121_634_816, - 675_345_038_118_000_013_606_912, - 0.6813287005626545e18, - 1_396_009_992_965_810_567_512_064, - 671_315_690_563_000_076_337_152, + 0.6853e6, + 139_000_000_000_000_012e18, + 675_345_038_118_000_013e18, + 0.6813e6, + 139_600_999_296_581_056e18, + 671_315_690_563_000_076e18, 1e18 ); } @@ -1672,178 +1680,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.7267829951477605e18) { - if (price < 0.7058277579915956e18) { - if (price < 0.6948800792282216e18) { - if (price < 0.6880796640937009e18) { + if (price < 0.7267e6) { + if (price < 0.7058e6) { + if (price < 0.6948e6) { + if (price < 0.688e6) { return PriceData( - 0.6948800792282216e18, - 1_376_009_992_965_810_374_574_080, - 685_076_068_526_999_994_368_000, - 0.6880796640937009e18, - 1_386_009_992_965_810_336_825_344, - 678_162_004_775_999_991_971_840, + 0.6948e6, + 137_600_999_296_581_037e18, + 685_076_068_526_999_994e18, + 0.6853e6, + 139_000_000_000_000_012e18, + 675_345_038_118_000_013e18, 1e18 ); } else { return PriceData( - 0.6948800792282216e18, - 1_376_009_992_965_810_374_574_080, - 685_076_068_526_999_994_368_000, - 0.6880796640937009e18, - 1_386_009_992_965_810_336_825_344, - 678_162_004_775_999_991_971_840, + 0.6948e6, + 137_600_999_296_581_037e18, + 685_076_068_526_999_994e18, + 0.688e6, + 138_600_999_296_581_033e18, + 678_162_004_775_999_991e18, 1e18 ); } } else { - if (price < 0.7017306577163355e18) { + if (price < 0.7017e6) { return PriceData( - 0.7058277579915956e18, - 1_359_999_999_999_999_966_445_568, - 696_209_253_362_999_968_661_504, - 0.7017306577163355e18, - 1_366_009_992_965_810_412_322_816, - 692_058_379_796_999_917_535_232, + 0.7058e6, + 135_999_999_999_999_996e18, + 696_209_253_362_999_968e18, + 0.6948e6, + 137_600_999_296_581_037e18, + 685_076_068_526_999_994e18, 1e18 ); } else { return PriceData( - 0.7058277579915956e18, - 1_359_999_999_999_999_966_445_568, - 696_209_253_362_999_968_661_504, - 0.7017306577163355e18, - 1_366_009_992_965_810_412_322_816, - 692_058_379_796_999_917_535_232, + 0.7058e6, + 135_999_999_999_999_996e18, + 696_209_253_362_999_968e18, + 0.7017e6, + 136_600_999_296_581_041e18, + 692_058_379_796_999_917e18, 1e18 ); } } } else { - if (price < 0.7155854234239166e18) { - if (price < 0.7086321651255534e18) { + if (price < 0.7155e6) { + if (price < 0.7086e6) { return PriceData( - 0.7155854234239166e18, - 1_346_009_992_965_810_487_820_288, - 706_229_774_288_999_996_719_104, - 0.7086321651255534e18, - 1_356_009_992_965_810_450_071_552, - 699_109_443_950_999_980_998_656, + 0.7155e6, + 134_600_999_296_581_048e18, + 706_229_774_288_999_996e18, + 0.7058e6, + 135_999_999_999_999_996e18, + 696_209_253_362_999_968e18, 1e18 ); } else { return PriceData( - 0.7155854234239166e18, - 1_346_009_992_965_810_487_820_288, - 706_229_774_288_999_996_719_104, - 0.7086321651255534e18, - 1_356_009_992_965_810_450_071_552, - 699_109_443_950_999_980_998_656, + 0.7155e6, + 134_600_999_296_581_048e18, + 706_229_774_288_999_996e18, + 0.7086e6, + 135_600_999_296_581_045e18, + 699_109_443_950_999_980e18, 1e18 ); } } else { - if (price < 0.7225913136485337e18) { + if (price < 0.7225e6) { return PriceData( - 0.7267829951477605e18, - 1_330_000_000_000_000_079_691_776, - 717_695_061_066_999_981_408_256, - 0.7225913136485337e18, - 1_336_009_992_965_810_525_569_024, - 713_419_892_621_999_983_296_512, + 0.7267e6, + 133_000_000_000_000_007e18, + 717_695_061_066_999_981e18, + 0.7155e6, + 134_600_999_296_581_048e18, + 706_229_774_288_999_996e18, 1e18 ); } else { return PriceData( - 0.7267829951477605e18, - 1_330_000_000_000_000_079_691_776, - 717_695_061_066_999_981_408_256, - 0.7225913136485337e18, - 1_336_009_992_965_810_525_569_024, - 713_419_892_621_999_983_296_512, + 0.7267e6, + 133_000_000_000_000_007e18, + 717_695_061_066_999_981e18, + 0.7225e6, + 133_600_999_296_581_052e18, + 713_419_892_621_999_983e18, 1e18 ); } } } } else { - if (price < 0.7482261712801425e18) { - if (price < 0.7367648260299406e18) { - if (price < 0.7296507786665005e18) { + if (price < 0.7482e6) { + if (price < 0.7367e6) { + if (price < 0.7296e6) { return PriceData( - 0.7367648260299406e18, - 1_316_009_992_965_810_332_631_040, - 728_011_626_732_999_961_214_976, - 0.7296507786665005e18, - 1_326_009_992_965_810_563_317_760, - 720_680_329_877_999_918_776_320, + 0.7367e6, + 131_600_999_296_581_033e18, + 728_011_626_732_999_961e18, + 0.7267e6, + 133_000_000_000_000_007e18, + 717_695_061_066_999_981e18, 1e18 ); } else { return PriceData( - 0.7367648260299406e18, - 1_316_009_992_965_810_332_631_040, - 728_011_626_732_999_961_214_976, - 0.7296507786665005e18, - 1_326_009_992_965_810_563_317_760, - 720_680_329_877_999_918_776_320, + 0.7367e6, + 131_600_999_296_581_033e18, + 728_011_626_732_999_961e18, + 0.7296e6, + 132_600_999_296_581_056e18, + 720_680_329_877_999_918e18, 1e18 ); } } else { - if (price < 0.7439345309340744e18) { + if (price < 0.7439e6) { return PriceData( - 0.7482261712801425e18, - 1_299_999_999_999_999_924_502_528, - 739_816_712_606_999_965_073_408, - 0.7439345309340744e18, - 1_306_009_992_965_810_370_379_776, - 735_414_334_273_999_920_431_104, + 0.7482e6, + 129_999_999_999_999_992e18, + 739_816_712_606_999_965e18, + 0.7367e6, + 131_600_999_296_581_033e18, + 728_011_626_732_999_961e18, 1e18 ); } else { return PriceData( - 0.7482261712801425e18, - 1_299_999_999_999_999_924_502_528, - 739_816_712_606_999_965_073_408, - 0.7439345309340744e18, - 1_306_009_992_965_810_370_379_776, - 735_414_334_273_999_920_431_104, + 0.7482e6, + 129_999_999_999_999_992e18, + 739_816_712_606_999_965e18, + 0.7439e6, + 130_600_999_296_581_037e18, + 735_414_334_273_999_920e18, 1e18 ); } } } else { - if (price < 0.7584455708586255e18) { - if (price < 0.7511610392809491e18) { + if (price < 0.7584e6) { + if (price < 0.7511e6) { return PriceData( - 0.7584455708586255e18, - 1_286_009_992_965_810_445_877_248, - 750_436_241_990_999_850_614_784, - 0.7511610392809491e18, - 1_296_009_992_965_810_408_128_512, - 742_889_014_688_999_846_969_344, + 0.7584e6, + 128_600_999_296_581_044e18, + 750_436_241_990_999_850e18, + 0.7482e6, + 129_999_999_999_999_992e18, + 739_816_712_606_999_965e18, 1e18 ); } else { return PriceData( - 0.7584455708586255e18, - 1_286_009_992_965_810_445_877_248, - 750_436_241_990_999_850_614_784, - 0.7511610392809491e18, - 1_296_009_992_965_810_408_128_512, - 742_889_014_688_999_846_969_344, + 0.7584e6, + 128_600_999_296_581_044e18, + 750_436_241_990_999_850e18, + 0.7511e6, + 129_600_999_296_581_040e18, + 742_889_014_688_999_846e18, 1e18 ); } } else { return PriceData( - 0.7657894226400184e18, - 1_276_009_992_965_810_483_625_984, - 758_056_602_771_999_862_816_768, - 0.7584455708586255e18, - 1_286_009_992_965_810_445_877_248, - 750_436_241_990_999_850_614_784, + 0.7657e6, + 127_600_999_296_581_048e18, + 758_056_602_771_999_862e18, + 0.7584e6, + 128_600_999_296_581_044e18, + 750_436_241_990_999_850e18, 1e18 ); } @@ -1851,190 +1859,190 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.8591362583263777e18) { - if (price < 0.8111815071890757e18) { - if (price < 0.7881910996771402e18) { - if (price < 0.7731939722121913e18) { - if (price < 0.7701875308591448e18) { + if (price < 0.8591e6) { + if (price < 0.8111e6) { + if (price < 0.7881e6) { + if (price < 0.7731e6) { + if (price < 0.7701e6) { return PriceData( - 0.7731939722121913e18, - 1_266_009_992_965_810_521_374_720, - 765_750_696_993_999_871_279_104, - 0.7701875308591448e18, - 1_270_000_000_000_000_037_748_736, - 762_589_283_900_000_049_299_456, + 0.7731e6, + 126_600_999_296_581_052e18, + 765_750_696_993_999_871e18, + 0.7657e6, + 127_600_999_296_581_048e18, + 758_056_602_771_999_862e18, 1e18 ); } else { return PriceData( - 0.7731939722121913e18, - 1_266_009_992_965_810_521_374_720, - 765_750_696_993_999_871_279_104, - 0.7701875308591448e18, - 1_270_000_000_000_000_037_748_736, - 762_589_283_900_000_049_299_456, + 0.7731e6, + 126_600_999_296_581_052e18, + 765_750_696_993_999_871e18, + 0.7701e6, + 127_000_000_000_000_003e18, + 762_589_283_900_000_049e18, 1e18 ); } } else { - if (price < 0.7806606813398431e18) { + if (price < 0.7806e6) { return PriceData( - 0.7881910996771402e18, - 1_246_009_992_965_810_596_872_192, - 781_362_557_425_999_864_135_680, - 0.7806606813398431e18, - 1_256_009_992_965_810_559_123_456, - 773_519_138_809_999_964_241_920, + 0.7881e6, + 124_600_999_296_581_059e18, + 781_362_557_425_999_864e18, + 0.7731e6, + 126_600_999_296_581_052e18, + 765_750_696_993_999_871e18, 1e18 ); } else { return PriceData( - 0.7881910996771402e18, - 1_246_009_992_965_810_596_872_192, - 781_362_557_425_999_864_135_680, - 0.7806606813398431e18, - 1_256_009_992_965_810_559_123_456, - 773_519_138_809_999_964_241_920, + 0.7881e6, + 124_600_999_296_581_059e18, + 781_362_557_425_999_864e18, + 0.7806e6, + 125_600_999_296_581_055e18, + 773_519_138_809_999_964e18, 1e18 ); } } } else { - if (price < 0.7957868686334806e18) { - if (price < 0.792703475602566e18) { + if (price < 0.7957e6) { + if (price < 0.7927e6) { return PriceData( - 0.7957868686334806e18, - 1_236_009_992_965_810_366_185_472, - 789_281_597_997_999_894_560_768, - 0.792703475602566e18, - 1_239_999_999_999_999_882_559_488, - 786_028_848_437_000_042_184_704, + 0.7957e6, + 123_600_999_296_581_036e18, + 789_281_597_997_999_894e18, + 0.7881e6, + 124_600_999_296_581_059e18, + 781_362_557_425_999_864e18, 1e18 ); } else { return PriceData( - 0.7957868686334806e18, - 1_236_009_992_965_810_366_185_472, - 789_281_597_997_999_894_560_768, - 0.792703475602566e18, - 1_239_999_999_999_999_882_559_488, - 786_028_848_437_000_042_184_704, + 0.7957e6, + 123_600_999_296_581_036e18, + 789_281_597_997_999_894e18, + 0.7927e6, + 123_999_999_999_999_988e18, + 786_028_848_437_000_042e18, 1e18 ); } } else { - if (price < 0.8034497254059905e18) { + if (price < 0.8034e6) { return PriceData( - 0.8111815071890757e18, - 1_216_009_992_965_810_441_682_944, - 805_349_211_052_999_895_416_832, - 0.8034497254059905e18, - 1_226_009_992_965_810_403_934_208, - 797_276_922_569_999_826_026_496, + 0.8111e6, + 121_600_999_296_581_044e18, + 805_349_211_052_999_895e18, + 0.7957e6, + 123_600_999_296_581_036e18, + 789_281_597_997_999_894e18, 1e18 ); } else { return PriceData( - 0.8111815071890757e18, - 1_216_009_992_965_810_441_682_944, - 805_349_211_052_999_895_416_832, - 0.8034497254059905e18, - 1_226_009_992_965_810_403_934_208, - 797_276_922_569_999_826_026_496, + 0.8111e6, + 121_600_999_296_581_044e18, + 805_349_211_052_999_895e18, + 0.8034e6, + 122_600_999_296_581_040e18, + 797_276_922_569_999_826e18, 1e18 ); } } } } else { - if (price < 0.8348103683034702e18) { - if (price < 0.8189841555727294e18) { - if (price < 0.8158174207616607e18) { + if (price < 0.8348e6) { + if (price < 0.8189e6) { + if (price < 0.8158e6) { return PriceData( - 0.8189841555727294e18, - 1_206_009_992_965_810_479_431_680, - 813_499_162_245_999_760_506_880, - 0.8158174207616607e18, - 1_209_999_999_999_999_995_805_696, - 810_152_674_566_000_114_401_280, + 0.8189e6, + 120_600_999_296_581_047e18, + 813_499_162_245_999_760e18, + 0.8111e6, + 121_600_999_296_581_044e18, + 805_349_211_052_999_895e18, 1e18 ); } else { return PriceData( - 0.8189841555727294e18, - 1_206_009_992_965_810_479_431_680, - 813_499_162_245_999_760_506_880, - 0.8158174207616607e18, - 1_209_999_999_999_999_995_805_696, - 810_152_674_566_000_114_401_280, + 0.8189e6, + 120_600_999_296_581_047e18, + 813_499_162_245_999_760e18, + 0.8158e6, + 120_999_999_999_999_999e18, + 810_152_674_566_000_114e18, 1e18 ); } } else { - if (price < 0.8268597211437622e18) { + if (price < 0.8268e6) { return PriceData( - 0.8348103683034702e18, - 1_186_009_992_965_810_420_711_424, - 830_034_948_846_999_811_653_632, - 0.8268597211437622e18, - 1_196_009_992_965_810_517_180_416, - 821_727_494_902_999_775_444_992, + 0.8348e6, + 118_600_999_296_581_042e18, + 830_034_948_846_999_811e18, + 0.8189e6, + 120_600_999_296_581_047e18, + 813_499_162_245_999_760e18, 1e18 ); } else { return PriceData( - 0.8348103683034702e18, - 1_186_009_992_965_810_420_711_424, - 830_034_948_846_999_811_653_632, - 0.8268597211437622e18, - 1_196_009_992_965_810_517_180_416, - 821_727_494_902_999_775_444_992, + 0.8348e6, + 118_600_999_296_581_042e18, + 830_034_948_846_999_811e18, + 0.8268e6, + 119_600_999_296_581_051e18, + 821_727_494_902_999_775e18, 1e18 ); } } } else { - if (price < 0.842838380316301e18) { - if (price < 0.8395807629721064e18) { + if (price < 0.8428e6) { + if (price < 0.8395e6) { return PriceData( - 0.842838380316301e18, - 1_176_009_992_965_810_458_460_160, - 838_422_286_131_999_841_189_888, - 0.8395807629721064e18, - 1_179_999_999_999_999_974_834_176, - 834_979_450_092_000_118_833_152, + 0.8428e6, + 117_600_999_296_581_045e18, + 838_422_286_131_999_841e18, + 0.8348e6, + 118_600_999_296_581_042e18, + 830_034_948_846_999_811e18, 1e18 ); } else { return PriceData( - 0.842838380316301e18, - 1_176_009_992_965_810_458_460_160, - 838_422_286_131_999_841_189_888, - 0.8395807629721064e18, - 1_179_999_999_999_999_974_834_176, - 834_979_450_092_000_118_833_152, + 0.8428e6, + 117_600_999_296_581_045e18, + 838_422_286_131_999_841e18, + 0.8395e6, + 117_999_999_999_999_997e18, + 834_979_450_092_000_118e18, 1e18 ); } } else { - if (price < 0.8509461646072527e18) { + if (price < 0.8509e6) { return PriceData( - 0.8591362583263777e18, - 1_156_009_992_965_810_399_739_904, - 855_439_777_442_999_782_342_656, - 0.8509461646072527e18, - 1_166_009_992_965_810_496_208_896, - 846_890_292_257_999_745_449_984, + 0.8591e6, + 115_600_999_296_581_039e18, + 855_439_777_442_999_782e18, + 0.8428e6, + 117_600_999_296_581_045e18, + 838_422_286_131_999_841e18, 1e18 ); } else { return PriceData( - 0.8591362583263777e18, - 1_156_009_992_965_810_399_739_904, - 855_439_777_442_999_782_342_656, - 0.8509461646072527e18, - 1_166_009_992_965_810_496_208_896, - 846_890_292_257_999_745_449_984, + 0.8591e6, + 115_600_999_296_581_039e18, + 855_439_777_442_999_782e18, + 0.8509e6, + 116_600_999_296_581_049e18, + 846_890_292_257_999_745e18, 1e18 ); } @@ -2042,178 +2050,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.9101652846401054e18) { - if (price < 0.8842278383622283e18) { - if (price < 0.8674113341943706e18) { - if (price < 0.8640539838258433e18) { + if (price < 0.9101e6) { + if (price < 0.8842e6) { + if (price < 0.8674e6) { + if (price < 0.864e6) { return PriceData( - 0.8674113341943706e18, - 1_146_009_992_965_810_437_488_640, - 864_071_577_944_999_841_497_088, - 0.8640539838258433e18, - 1_149_999_999_999_999_953_862_656, - 860_529_537_869_000_136_982_528, + 0.8674e6, + 114_600_999_296_581_043e18, + 864_071_577_944_999_841e18, + 0.8591e6, + 115_600_999_296_581_039e18, + 855_439_777_442_999_782e18, 1e18 ); } else { return PriceData( - 0.8674113341943706e18, - 1_146_009_992_965_810_437_488_640, - 864_071_577_944_999_841_497_088, - 0.8640539838258433e18, - 1_149_999_999_999_999_953_862_656, - 860_529_537_869_000_136_982_528, + 0.8674e6, + 114_600_999_296_581_043e18, + 864_071_577_944_999_841e18, + 0.864e6, + 114_999_999_999_999_995e18, + 860_529_537_869_000_136e18, 1e18 ); } } else { - if (price < 0.8757742066564991e18) { + if (price < 0.8757e6) { return PriceData( - 0.8842278383622283e18, - 1_126_009_992_965_810_512_986_112, - 881_585_608_519_999_884_886_016, - 0.8757742066564991e18, - 1_136_009_992_965_810_475_237_376, - 872_786_557_449_999_864_561_664, + 0.8842e6, + 112_600_999_296_581_051e18, + 881_585_608_519_999_884e18, + 0.8674e6, + 114_600_999_296_581_043e18, + 864_071_577_944_999_841e18, 1e18 ); } else { return PriceData( - 0.8842278383622283e18, - 1_126_009_992_965_810_512_986_112, - 881_585_608_519_999_884_886_016, - 0.8757742066564991e18, - 1_136_009_992_965_810_475_237_376, - 872_786_557_449_999_864_561_664, + 0.8842e6, + 112_600_999_296_581_051e18, + 881_585_608_519_999_884e18, + 0.8757e6, + 113_600_999_296_581_047e18, + 872_786_557_449_999_864e18, 1e18 ); } } } else { - if (price < 0.8927753469982641e18) { - if (price < 0.8893079194806377e18) { + if (price < 0.8927e6) { + if (price < 0.8893e6) { return PriceData( - 0.8927753469982641e18, - 1_116_009_992_965_810_416_517_120, - 890_469_654_111_999_903_137_792, - 0.8893079194806377e18, - 1_120_000_000_000_000_067_108_864, - 886_825_266_904_000_064_651_264, + 0.8927e6, + 111_600_999_296_581_041e18, + 890_469_654_111_999_903e18, + 0.8842e6, + 112_600_999_296_581_051e18, + 881_585_608_519_999_884e18, 1e18 ); } else { return PriceData( - 0.8927753469982641e18, - 1_116_009_992_965_810_416_517_120, - 890_469_654_111_999_903_137_792, - 0.8893079194806377e18, - 1_120_000_000_000_000_067_108_864, - 886_825_266_904_000_064_651_264, + 0.8927e6, + 111_600_999_296_581_041e18, + 890_469_654_111_999_903e18, + 0.8893e6, + 112_000_000_000_000_006e18, + 886_825_266_904_000_064e18, 1e18 ); } } else { - if (price < 0.9014200124952659e18) { + if (price < 0.9014e6) { return PriceData( - 0.9101652846401054e18, - 1_096_009_992_965_810_492_014_592, - 908_496_582_238_999_955_898_368, - 0.9014200124952659e18, - 1_106_009_992_965_810_454_265_856, - 899_439_649_160_999_875_903_488, + 0.9101e6, + 109_600_999_296_581_049e18, + 908_496_582_238_999_955e18, + 0.8927e6, + 111_600_999_296_581_041e18, + 890_469_654_111_999_903e18, 1e18 ); } else { return PriceData( - 0.9101652846401054e18, - 1_096_009_992_965_810_492_014_592, - 908_496_582_238_999_955_898_368, - 0.9014200124952659e18, - 1_106_009_992_965_810_454_265_856, - 899_439_649_160_999_875_903_488, + 0.9101e6, + 109_600_999_296_581_049e18, + 908_496_582_238_999_955e18, + 0.9014e6, + 110_600_999_296_581_045e18, + 899_439_649_160_999_875e18, 1e18 ); } } } } else { - if (price < 0.93704195898873e18) { - if (price < 0.9190147911249779e18) { - if (price < 0.9154252342593594e18) { + if (price < 0.937e6) { + if (price < 0.919e6) { + if (price < 0.9154e6) { return PriceData( - 0.9190147911249779e18, - 1_086_009_992_965_810_529_763_328, - 917_641_477_296_999_901_429_760, - 0.9154252342593594e18, - 1_090_000_000_000_000_046_137_344, - 913_891_264_499_999_987_204_096, + 0.919e6, + 108_600_999_296_581_052e18, + 917_641_477_296_999_901e18, + 0.9101e6, + 109_600_999_296_581_049e18, + 908_496_582_238_999_955e18, 1e18 ); } else { return PriceData( - 0.9190147911249779e18, - 1_086_009_992_965_810_529_763_328, - 917_641_477_296_999_901_429_760, - 0.9154252342593594e18, - 1_090_000_000_000_000_046_137_344, - 913_891_264_499_999_987_204_096, + 0.919e6, + 108_600_999_296_581_052e18, + 917_641_477_296_999_901e18, + 0.9154e6, + 109_000_000_000_000_004e18, + 913_891_264_499_999_987e18, 1e18 ); } } else { - if (price < 0.9279723460598399e18) { + if (price < 0.9279e6) { return PriceData( - 0.93704195898873e18, - 1_066_009_992_965_810_471_043_072, - 936_199_437_051_999_863_963_648, - 0.9279723460598399e18, - 1_076_009_992_965_810_433_294_336, - 926_875_395_482_999_811_211_264, + 0.937e6, + 106_600_999_296_581_047e18, + 936_199_437_051_999_863e18, + 0.919e6, + 108_600_999_296_581_052e18, + 917_641_477_296_999_901e18, 1e18 ); } else { return PriceData( - 0.93704195898873e18, - 1_066_009_992_965_810_471_043_072, - 936_199_437_051_999_863_963_648, - 0.9279723460598399e18, - 1_076_009_992_965_810_433_294_336, - 926_875_395_482_999_811_211_264, + 0.937e6, + 106_600_999_296_581_047e18, + 936_199_437_051_999_863e18, + 0.9279e6, + 107_600_999_296_581_043e18, + 926_875_395_482_999_811e18, 1e18 ); } } } else { - if (price < 0.97065046122351e18) { - if (price < 0.9425021457735017e18) { + if (price < 0.9706e6) { + if (price < 0.9425e6) { return PriceData( - 0.97065046122351e18, - 1_030_000_000_000_000_004_194_304, - 970_446_402_236_000_094_912_512, - 0.9425021457735017e18, - 1_060_000_000_000_000_025_165_824, - 941_754_836_243_000_007_327_744, + 0.9706e6, + 103_000_000_000_000_000e18, + 970_446_402_236_000_094e18, + 0.937e6, + 106_600_999_296_581_047e18, + 936_199_437_051_999_863e18, 1e18 ); } else { return PriceData( - 0.97065046122351e18, - 1_030_000_000_000_000_004_194_304, - 970_446_402_236_000_094_912_512, - 0.9425021457735017e18, - 1_060_000_000_000_000_025_165_824, - 941_754_836_243_000_007_327_744, + 0.9706e6, + 103_000_000_000_000_000e18, + 970_446_402_236_000_094e18, + 0.9425e6, + 106_000_000_000_000_002e18, + 941_754_836_243_000_007e18, 1e18 ); } } else { return PriceData( - 0.9837578538258441e18, - 1_016_574_698_021_103_146_631_168, - 983_823_407_046_999_249_256_448, - 0.97065046122351e18, - 1_030_000_000_000_000_004_194_304, - 970_446_402_236_000_094_912_512, + 0.9837e6, + 101_657_469_802_110_314e18, + 983_823_407_046_999_249e18, + 0.9706e6, + 103_000_000_000_000_000e18, + 970_446_402_236_000_094e18, 1e18 ); } @@ -2222,191 +2230,191 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.2781476871714348e18) { - if (price < 1.1479740676895633e18) { - if (price < 1.08346873509363e18) { - if (price < 1.052270281144944e18) { - if (price < 1.04200618543826e18) { - if (price < 1.0137361745263807e18) { + if (price < 1.2781e6) { + if (price < 1.1479e6) { + if (price < 1.0834e6) { + if (price < 1.0522e6) { + if (price < 1.042e6) { + if (price < 1.0137e6) { return PriceData( - 1.04200618543826e18, - 959_393_930_406_635_507_810_304, - 1_041_633_461_631_000_080_547_840, - 1.0137361745263807e18, - 986_536_714_103_430_341_197_824, - 1_013_823_407_046_999_136_010_240, + 1.042e6, + 959_393_930_406_635_507e18, + 104_163_346_163_100_008e18, + 0.9837e6, + 101_657_469_802_110_314e18, + 983_823_407_046_999_249e18, 1e18 ); } else { return PriceData( - 1.04200618543826e18, - 959_393_930_406_635_507_810_304, - 1_041_633_461_631_000_080_547_840, - 1.0137361745263807e18, - 986_536_714_103_430_341_197_824, - 1_013_823_407_046_999_136_010_240, + 1.042e6, + 959_393_930_406_635_507e18, + 104_163_346_163_100_008e18, + 1.0137e6, + 986_536_714_103_430_341e18, + 101_382_340_704_699_913e18, 1e18 ); } } else { - if (price < 1.0442014332455682e18) { + if (price < 1.0442e6) { return PriceData( - 1.052270281144944e18, - 949_844_925_094_088_539_111_424, - 1_051_633_461_631_000_042_799_104, - 1.0442014332455682e18, - 957_380_872_867_950_333_263_872, - 1_043_823_407_046_999_156_981_760, + 1.0522e6, + 949_844_925_094_088_539e18, + 105_163_346_163_100_004e18, + 1.042e6, + 959_393_930_406_635_507e18, + 104_163_346_163_100_008e18, 1e18 ); } else { return PriceData( - 1.052270281144944e18, - 949_844_925_094_088_539_111_424, - 1_051_633_461_631_000_042_799_104, - 1.0442014332455682e18, - 957_380_872_867_950_333_263_872, - 1_043_823_407_046_999_156_981_760, + 1.0522e6, + 949_844_925_094_088_539e18, + 105_163_346_163_100_004e18, + 1.0442e6, + 957_380_872_867_950_333e18, + 104_382_340_704_699_915e18, 1e18 ); } } } else { - if (price < 1.0729984643915151e18) { - if (price < 1.0626000128180033e18) { + if (price < 1.0729e6) { + if (price < 1.0626e6) { return PriceData( - 1.0729984643915151e18, - 931_024_660_370_625_864_400_896, - 1_071_633_461_631_000_101_519_360, - 1.0626000128180033e18, - 940_388_903_170_712_077_860_864, - 1_061_633_461_631_000_005_050_368, + 1.0729e6, + 931_024_660_370_625_864e18, + 107_163_346_163_100_010e18, + 1.0522e6, + 949_844_925_094_088_539e18, + 105_163_346_163_100_004e18, 1e18 ); } else { return PriceData( - 1.0729984643915151e18, - 931_024_660_370_625_864_400_896, - 1_071_633_461_631_000_101_519_360, - 1.0626000128180033e18, - 940_388_903_170_712_077_860_864, - 1_061_633_461_631_000_005_050_368, + 1.0729e6, + 931_024_660_370_625_864e18, + 107_163_346_163_100_010e18, + 1.0626e6, + 940_388_903_170_712_077e18, + 106_163_346_163_100_000e18, 1e18 ); } } else { - if (price < 1.075235747233168e18) { + if (price < 1.0752e6) { return PriceData( - 1.08346873509363e18, - 921_751_036_591_489_341_718_528, - 1_081_633_461_631_000_063_770_624, - 1.075235747233168e18, - 929_070_940_571_911_522_877_440, - 1_073_823_407_046_999_312_171_008, + 1.0834e6, + 921_751_036_591_489_341e18, + 108_163_346_163_100_006e18, + 1.0729e6, + 931_024_660_370_625_864e18, + 107_163_346_163_100_010e18, 1e18 ); } else { return PriceData( - 1.08346873509363e18, - 921_751_036_591_489_341_718_528, - 1_081_633_461_631_000_063_770_624, - 1.075235747233168e18, - 929_070_940_571_911_522_877_440, - 1_073_823_407_046_999_312_171_008, + 1.0834e6, + 921_751_036_591_489_341e18, + 108_163_346_163_100_006e18, + 1.0752e6, + 929_070_940_571_911_522e18, + 107_382_340_704_699_931e18, 1e18 ); } } } } else { - if (price < 1.1153416855406852e18) { - if (price < 1.1046372077990465e18) { - if (price < 1.094013939534592e18) { + if (price < 1.1153e6) { + if (price < 1.1046e6) { + if (price < 1.094e6) { return PriceData( - 1.1046372077990465e18, - 903_471_213_736_046_924_660_736, - 1_101_633_461_630_999_988_273_152, - 1.094013939534592e18, - 912_566_913_749_610_422_861_824, - 1_091_633_461_631_000_026_021_888, + 1.1046e6, + 903_471_213_736_046_924e18, + 110_163_346_163_099_998e18, + 1.0834e6, + 921_751_036_591_489_341e18, + 108_163_346_163_100_006e18, 1e18 ); } else { return PriceData( - 1.1046372077990465e18, - 903_471_213_736_046_924_660_736, - 1_101_633_461_630_999_988_273_152, - 1.094013939534592e18, - 912_566_913_749_610_422_861_824, - 1_091_633_461_631_000_026_021_888, + 1.1046e6, + 903_471_213_736_046_924e18, + 110_163_346_163_099_998e18, + 1.094e6, + 912_566_913_749_610_422e18, + 109_163_346_163_100_002e18, 1e18 ); } } else { - if (price < 1.106922462140042e18) { + if (price < 1.1069e6) { return PriceData( - 1.1153416855406852e18, - 894_462_896_467_921_655_037_952, - 1_111_633_461_631_000_084_742_144, - 1.106922462140042e18, - 901_574_605_299_420_214_853_632, - 1_103_823_407_046_999_333_142_528, + 1.1153e6, + 894_462_896_467_921_655e18, + 111_163_346_163_100_008e18, + 1.1046e6, + 903_471_213_736_046_924e18, + 110_163_346_163_099_998e18, 1e18 ); } else { return PriceData( - 1.1153416855406852e18, - 894_462_896_467_921_655_037_952, - 1_111_633_461_631_000_084_742_144, - 1.106922462140042e18, - 901_574_605_299_420_214_853_632, - 1_103_823_407_046_999_333_142_528, + 1.1153e6, + 894_462_896_467_921_655e18, + 111_163_346_163_100_008e18, + 1.1069e6, + 901_574_605_299_420_214e18, + 110_382_340_704_699_933e18, 1e18 ); } } } else { - if (price < 1.1370069304852344e18) { - if (price < 1.126130534077399e18) { + if (price < 1.137e6) { + if (price < 1.1261e6) { return PriceData( - 1.1370069304852344e18, - 876_704_428_898_610_466_258_944, - 1_131_633_461_631_000_009_244_672, - 1.126130534077399e18, - 885_540_958_029_582_579_007_488, - 1_121_633_461_631_000_046_993_408, + 1.137e6, + 876_704_428_898_610_466e18, + 113_163_346_163_100_000e18, + 1.1153e6, + 894_462_896_467_921_655e18, + 111_163_346_163_100_008e18, 1e18 ); } else { return PriceData( - 1.1370069304852344e18, - 876_704_428_898_610_466_258_944, - 1_131_633_461_631_000_009_244_672, - 1.126130534077399e18, - 885_540_958_029_582_579_007_488, - 1_121_633_461_631_000_046_993_408, + 1.137e6, + 876_704_428_898_610_466e18, + 113_163_346_163_100_000e18, + 1.1261e6, + 885_540_958_029_582_579e18, + 112_163_346_163_100_004e18, 1e18 ); } } else { - if (price < 1.1393461713403175e18) { + if (price < 1.1393e6) { return PriceData( - 1.1479740676895633e18, - 867_952_372_252_030_623_809_536, - 1_141_633_461_631_000_105_713_664, - 1.1393461713403175e18, - 874_862_932_837_460_311_277_568, - 1_133_823_407_046_999_354_114_048, + 1.1479e6, + 867_952_372_252_030_623e18, + 114_163_346_163_100_010e18, + 1.137e6, + 876_704_428_898_610_466e18, + 113_163_346_163_100_000e18, 1e18 ); } else { return PriceData( - 1.1479740676895633e18, - 867_952_372_252_030_623_809_536, - 1_141_633_461_631_000_105_713_664, - 1.1393461713403175e18, - 874_862_932_837_460_311_277_568, - 1_133_823_407_046_999_354_114_048, + 1.1479e6, + 867_952_372_252_030_623e18, + 114_163_346_163_100_010e18, + 1.1393e6, + 874_862_932_837_460_311e18, + 113_382_340_704_699_935e18, 1e18 ); } @@ -2414,178 +2422,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.2158632690425795e18) { - if (price < 1.1814520928592147e18) { - if (price < 1.1701934159515677e18) { - if (price < 1.1590351545519821e18) { + if (price < 1.2158e6) { + if (price < 1.1814e6) { + if (price < 1.1701e6) { + if (price < 1.159e6) { return PriceData( - 1.1701934159515677e18, - 850_698_082_981_724_339_306_496, - 1_161_633_461_631_000_030_216_192, - 1.1590351545519821e18, - 859_283_882_348_396_836_552_704, - 1_151_633_461_631_000_067_964_928, + 1.1701e6, + 850_698_082_981_724_339e18, + 116_163_346_163_100_003e18, + 1.1479e6, + 867_952_372_252_030_623e18, + 114_163_346_163_100_010e18, 1e18 ); } else { return PriceData( - 1.1701934159515677e18, - 850_698_082_981_724_339_306_496, - 1_161_633_461_631_000_030_216_192, - 1.1590351545519821e18, - 859_283_882_348_396_836_552_704, - 1_151_633_461_631_000_067_964_928, + 1.1701e6, + 850_698_082_981_724_339e18, + 116_163_346_163_100_003e18, + 1.159e6, + 859_283_882_348_396_836e18, + 115_163_346_163_100_006e18, 1e18 ); } } else { - if (price < 1.1725927382568915e18) { + if (price < 1.1725e6) { return PriceData( - 1.1814520928592147e18, - 842_194_126_003_515_871_461_376, - 1_171_633_461_630_999_992_467_456, - 1.1725927382568915e18, - 848_909_895_863_161_958_957_056, - 1_163_823_407_046_999_375_085_568, + 1.1814e6, + 842_194_126_003_515_871e18, + 117_163_346_163_099_999e18, + 1.1701e6, + 850_698_082_981_724_339e18, + 116_163_346_163_100_003e18, 1e18 ); } else { return PriceData( - 1.1814520928592147e18, - 842_194_126_003_515_871_461_376, - 1_171_633_461_630_999_992_467_456, - 1.1725927382568915e18, - 848_909_895_863_161_958_957_056, - 1_163_823_407_046_999_375_085_568, + 1.1814e6, + 842_194_126_003_515_871e18, + 117_163_346_163_099_999e18, + 1.1725e6, + 848_909_895_863_161_958e18, + 116_382_340_704_699_937e18, 1e18 ); } } } else { - if (price < 1.204283737929635e18) { - if (price < 1.1928144424038778e18) { + if (price < 1.2042e6) { + if (price < 1.1928e6) { return PriceData( - 1.204283737929635e18, - 825_428_478_487_026_483_593_216, - 1_191_633_461_631_000_051_187_712, - 1.1928144424038778e18, - 833_771_189_909_386_858_332_160, - 1_181_633_461_631_000_088_936_448, + 1.2042e6, + 825_428_478_487_026_483e18, + 119_163_346_163_100_005e18, + 1.1814e6, + 842_194_126_003_515_871e18, + 117_163_346_163_099_999e18, 1e18 ); } else { return PriceData( - 1.204283737929635e18, - 825_428_478_487_026_483_593_216, - 1_191_633_461_631_000_051_187_712, - 1.1928144424038778e18, - 833_771_189_909_386_858_332_160, - 1_181_633_461_631_000_088_936_448, + 1.2042e6, + 825_428_478_487_026_483e18, + 119_163_346_163_100_005e18, + 1.1928e6, + 833_771_189_909_386_858e18, + 118_163_346_163_100_008e18, 1e18 ); } } else { - if (price < 1.206749317725993e18) { + if (price < 1.2067e6) { return PriceData( - 1.2158632690425795e18, - 817_165_219_522_460_124_184_576, - 1_201_633_461_631_000_013_438_976, - 1.206749317725993e18, - 823_691_964_726_974_573_707_264, - 1_193_823_407_046_999_261_839_360, + 1.2158e6, + 817_165_219_522_460_124e18, + 120_163_346_163_100_001e18, + 1.2042e6, + 825_428_478_487_026_483e18, + 119_163_346_163_100_005e18, 1e18 ); } else { return PriceData( - 1.2158632690425795e18, - 817_165_219_522_460_124_184_576, - 1_201_633_461_631_000_013_438_976, - 1.206749317725993e18, - 823_691_964_726_974_573_707_264, - 1_193_823_407_046_999_261_839_360, + 1.2158e6, + 817_165_219_522_460_124e18, + 120_163_346_163_100_001e18, + 1.2067e6, + 823_691_964_726_974_573e18, + 119_382_340_704_699_926e18, 1e18 ); } } } } else { - if (price < 1.2512964165637426e18) { - if (price < 1.239366277967463e18) { - if (price < 1.227556341646634e18) { + if (price < 1.2512e6) { + if (price < 1.2393e6) { + if (price < 1.2275e6) { return PriceData( - 1.239366277967463e18, - 800_874_082_725_647_358_099_456, - 1_221_633_461_630_999_937_941_504, - 1.227556341646634e18, - 808_980_663_561_772_026_822_656, - 1_211_633_461_630_999_975_690_240, + 1.2393e6, + 800_874_082_725_647_358e18, + 122_163_346_163_099_993e18, + 1.2158e6, + 817_165_219_522_460_124e18, + 120_163_346_163_100_001e18, 1e18 ); } else { return PriceData( - 1.239366277967463e18, - 800_874_082_725_647_358_099_456, - 1_221_633_461_630_999_937_941_504, - 1.227556341646634e18, - 808_980_663_561_772_026_822_656, - 1_211_633_461_630_999_975_690_240, + 1.2393e6, + 800_874_082_725_647_358e18, + 122_163_346_163_099_993e18, + 1.2275e6, + 808_980_663_561_772_026e18, + 121_163_346_163_099_997e18, 1e18 ); } } else { - if (price < 1.2419043731900452e18) { + if (price < 1.2419e6) { return PriceData( - 1.2512964165637426e18, - 792_844_769_574_258_384_830_464, - 1_231_633_461_631_000_168_628_224, - 1.2419043731900452e18, - 799_187_750_396_588_808_208_384, - 1_223_823_407_046_999_417_028_608, + 1.2512e6, + 792_844_769_574_258_384e18, + 123_163_346_163_100_016e18, + 1.2393e6, + 800_874_082_725_647_358e18, + 122_163_346_163_099_993e18, 1e18 ); } else { return PriceData( - 1.2512964165637426e18, - 792_844_769_574_258_384_830_464, - 1_231_633_461_631_000_168_628_224, - 1.2419043731900452e18, - 799_187_750_396_588_808_208_384, - 1_223_823_407_046_999_417_028_608, + 1.2512e6, + 792_844_769_574_258_384e18, + 123_163_346_163_100_016e18, + 1.2419e6, + 799_187_750_396_588_808e18, + 122_382_340_704_699_941e18, 1e18 ); } } } else { - if (price < 1.275530736456304e18) { - if (price < 1.2633501123251245e18) { + if (price < 1.2755e6) { + if (price < 1.2633e6) { return PriceData( - 1.275530736456304e18, - 777_015_212_287_252_263_600_128, - 1_251_633_461_631_000_093_130_752, - 1.2633501123251245e18, - 784_892_036_020_190_803_132_416, - 1_241_633_461_631_000_130_879_488, + 1.2755e6, + 777_015_212_287_252_263e18, + 125_163_346_163_100_009e18, + 1.2512e6, + 792_844_769_574_258_384e18, + 123_163_346_163_100_016e18, 1e18 ); } else { return PriceData( - 1.275530736456304e18, - 777_015_212_287_252_263_600_128, - 1_251_633_461_631_000_093_130_752, - 1.2633501123251245e18, - 784_892_036_020_190_803_132_416, - 1_241_633_461_631_000_130_879_488, + 1.2755e6, + 777_015_212_287_252_263e18, + 125_163_346_163_100_009e18, + 1.2633e6, + 784_892_036_020_190_803e18, + 124_163_346_163_100_013e18, 1e18 ); } } else { return PriceData( - 1.2781476871714348e18, - 775_377_691_936_595_793_412_096, - 1_253_823_407_046_999_303_782_400, - 1.275530736456304e18, - 777_015_212_287_252_263_600_128, - 1_251_633_461_631_000_093_130_752, + 1.2781e6, + 775_377_691_936_595_793e18, + 125_382_340_704_699_930e18, + 1.2755e6, + 777_015_212_287_252_263e18, + 125_163_346_163_100_009e18, 1e18 ); } @@ -2593,351 +2601,351 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.4328477724460313e18) { - if (price < 1.3542648168520204e18) { - if (price < 1.3155703630695195e18) { - if (price < 1.3002863360251475e18) { - if (price < 1.287841676446678e18) { + if (price < 1.4328e6) { + if (price < 1.3542e6) { + if (price < 1.3155e6) { + if (price < 1.3002e6) { + if (price < 1.2878e6) { return PriceData( - 1.3002863360251475e18, - 761_486_700_794_138_643_660_800, - 1_271_633_461_631_000_017_633_280, - 1.287841676446678e18, - 769_213_645_913_148_350_267_392, - 1_261_633_461_631_000_055_382_016, + 1.3002e6, + 761_486_700_794_138_643e18, + 127_163_346_163_100_001e18, + 1.2781e6, + 775_377_691_936_595_793e18, + 125_382_340_704_699_930e18, 1e18 ); } else { return PriceData( - 1.3002863360251475e18, - 761_486_700_794_138_643_660_800, - 1_271_633_461_631_000_017_633_280, - 1.287841676446678e18, - 769_213_645_913_148_350_267_392, - 1_261_633_461_631_000_055_382_016, + 1.3002e6, + 761_486_700_794_138_643e18, + 127_163_346_163_100_001e18, + 1.2878e6, + 769_213_645_913_148_350e18, + 126_163_346_163_100_005e18, 1e18 ); } } else { - if (price < 1.3128681350996887e18) { + if (price < 1.3128e6) { return PriceData( - 1.3155703630695195e18, - 752_243_782_340_972_617_138_176, - 1_283_823_407_046_999_190_536_192, - 1.3128681350996887e18, - 753_833_756_269_910_991_831_040, - 1_281_633_461_630_999_979_884_544, + 1.3155e6, + 752_243_782_340_972_617e18, + 128_382_340_704_699_919e18, + 1.3002e6, + 761_486_700_794_138_643e18, + 127_163_346_163_100_001e18, 1e18 ); } else { return PriceData( - 1.3155703630695195e18, - 752_243_782_340_972_617_138_176, - 1_283_823_407_046_999_190_536_192, - 1.3128681350996887e18, - 753_833_756_269_910_991_831_040, - 1_281_633_461_630_999_979_884_544, + 1.3155e6, + 752_243_782_340_972_617e18, + 128_382_340_704_699_919e18, + 1.3128e6, + 753_833_756_269_910_991e18, + 128_163_346_163_099_997e18, 1e18 ); } } } else { - if (price < 1.338456911792658e18) { - if (price < 1.325590509681383e18) { + if (price < 1.3384e6) { + if (price < 1.3255e6) { return PriceData( - 1.338456911792658e18, - 738_747_458_359_333_922_799_616, - 1_301_633_461_631_000_172_822_528, - 1.325590509681383e18, - 746_254_206_247_018_816_339_968, - 1_291_633_461_630_999_942_135_808, + 1.3384e6, + 738_747_458_359_333_922e18, + 130_163_346_163_100_017e18, + 1.3155e6, + 752_243_782_340_972_617e18, + 128_382_340_704_699_919e18, 1e18 ); } else { return PriceData( - 1.338456911792658e18, - 738_747_458_359_333_922_799_616, - 1_301_633_461_631_000_172_822_528, - 1.325590509681383e18, - 746_254_206_247_018_816_339_968, - 1_291_633_461_630_999_942_135_808, + 1.3384e6, + 738_747_458_359_333_922e18, + 130_163_346_163_100_017e18, + 1.3255e6, + 746_254_206_247_018_816e18, + 129_163_346_163_099_994e18, 1e18 ); } } else { - if (price < 1.3514708093595578e18) { + if (price < 1.3514e6) { return PriceData( - 1.3542648168520204e18, - 729_769_327_686_751_939_985_408, - 1_313_823_407_046_999_345_725_440, - 1.3514708093595578e18, - 731_312_933_164_060_298_444_800, - 1_311_633_461_631_000_135_073_792, + 1.3542e6, + 729_769_327_686_751_939e18, + 131_382_340_704_699_934e18, + 1.3384e6, + 738_747_458_359_333_922e18, + 130_163_346_163_100_017e18, 1e18 ); } else { return PriceData( - 1.3542648168520204e18, - 729_769_327_686_751_939_985_408, - 1_313_823_407_046_999_345_725_440, - 1.3514708093595578e18, - 731_312_933_164_060_298_444_800, - 1_311_633_461_631_000_135_073_792, + 1.3542e6, + 729_769_327_686_751_939e18, + 131_382_340_704_699_934e18, + 1.3514e6, + 731_312_933_164_060_298e18, + 131_163_346_163_100_013e18, 1e18 ); } } } } else { - if (price < 1.394324757689252e18) { - if (price < 1.3779550413233685e18) { - if (price < 1.3646356860879163e18) { + if (price < 1.3943e6) { + if (price < 1.3779e6) { + if (price < 1.3646e6) { return PriceData( - 1.3779550413233685e18, - 716_658_293_110_424_452_726_784, - 1_331_633_461_631_000_059_576_320, - 1.3646356860879163e18, - 723_950_063_371_948_465_848_320, - 1_321_633_461_631_000_097_325_056, + 1.3779e6, + 716_658_293_110_424_452e18, + 133_163_346_163_100_005e18, + 1.3542e6, + 729_769_327_686_751_939e18, + 131_382_340_704_699_934e18, 1e18 ); } else { return PriceData( - 1.3779550413233685e18, - 716_658_293_110_424_452_726_784, - 1_331_633_461_631_000_059_576_320, - 1.3646356860879163e18, - 723_950_063_371_948_465_848_320, - 1_321_633_461_631_000_097_325_056, + 1.3779e6, + 716_658_293_110_424_452e18, + 133_163_346_163_100_005e18, + 1.3646e6, + 723_950_063_371_948_465e18, + 132_163_346_163_100_009e18, 1e18 ); } } else { - if (price < 1.391432389895189e18) { + if (price < 1.3914e6) { return PriceData( - 1.394324757689252e18, - 707_938_735_496_683_067_539_456, - 1_343_823_407_046_999_232_479_232, - 1.391432389895189e18, - 709_437_077_218_432_531_824_640, - 1_341_633_461_631_000_021_827_584, + 1.3943e6, + 707_938_735_496_683_067e18, + 134_382_340_704_699_923e18, + 1.3779e6, + 716_658_293_110_424_452e18, + 133_163_346_163_100_005e18, 1e18 ); } else { return PriceData( - 1.394324757689252e18, - 707_938_735_496_683_067_539_456, - 1_343_823_407_046_999_232_479_232, - 1.391432389895189e18, - 709_437_077_218_432_531_824_640, - 1_341_633_461_631_000_021_827_584, + 1.3943e6, + 707_938_735_496_683_067e18, + 134_382_340_704_699_923e18, + 1.3914e6, + 709_437_077_218_432_531e18, + 134_163_346_163_100_002e18, 1e18 ); } } } else { - if (price < 1.4188752027334606e18) { - if (price < 1.4050712619440027e18) { + if (price < 1.4188e6) { + if (price < 1.405e6) { return PriceData( - 1.4188752027334606e18, - 695_204_177_438_428_696_150_016, - 1_361_633_461_630_999_946_330_112, - 1.4050712619440027e18, - 702_285_880_571_853_430_325_248, - 1_351_633_461_630_999_984_078_848, + 1.4188e6, + 695_204_177_438_428_696e18, + 136_163_346_163_099_994e18, + 1.3943e6, + 707_938_735_496_683_067e18, + 134_382_340_704_699_923e18, 1e18 ); } else { return PriceData( - 1.4188752027334606e18, - 695_204_177_438_428_696_150_016, - 1_361_633_461_630_999_946_330_112, - 1.4050712619440027e18, - 702_285_880_571_853_430_325_248, - 1_351_633_461_630_999_984_078_848, + 1.4188e6, + 695_204_177_438_428_696e18, + 136_163_346_163_099_994e18, + 1.405e6, + 702_285_880_571_853_430e18, + 135_163_346_163_099_998e18, 1e18 ); } } else { return PriceData( - 1.4328477724460313e18, - 688_191_450_861_183_111_790_592, - 1_371_633_461_630_999_908_581_376, - 1.4188752027334606e18, - 695_204_177_438_428_696_150_016, - 1_361_633_461_630_999_946_330_112, + 1.4328e6, + 688_191_450_861_183_111e18, + 137_163_346_163_099_990e18, + 1.4188e6, + 695_204_177_438_428_696e18, + 136_163_346_163_099_994e18, 1e18 ); } } } } else { - if (price < 1.5204255894714784e18) { - if (price < 1.4758130760035226e18) { - if (price < 1.446992545963098e18) { - if (price < 1.4358451570118336e18) { + if (price < 1.5204e6) { + if (price < 1.4758e6) { + if (price < 1.4469e6) { + if (price < 1.4358e6) { return PriceData( - 1.446992545963098e18, - 681_247_192_069_384_962_048_000, - 1_381_633_461_631_000_139_268_096, - 1.4358451570118336e18, - 686_737_328_931_840_165_150_720, - 1_373_823_407_046_999_387_668_480, + 1.4469e6, + 681_247_192_069_384_962e18, + 138_163_346_163_100_013e18, + 1.4328e6, + 688_191_450_861_183_111e18, + 137_163_346_163_099_990e18, 1e18 ); } else { return PriceData( - 1.446992545963098e18, - 681_247_192_069_384_962_048_000, - 1_381_633_461_631_000_139_268_096, - 1.4358451570118336e18, - 686_737_328_931_840_165_150_720, - 1_373_823_407_046_999_387_668_480, + 1.4469e6, + 681_247_192_069_384_962e18, + 138_163_346_163_100_013e18, + 1.4358e6, + 686_737_328_931_840_165e18, + 137_382_340_704_699_938e18, 1e18 ); } } else { - if (price < 1.4613131126296026e18) { + if (price < 1.4613e6) { return PriceData( - 1.4758130760035226e18, - 667_562_080_341_789_698_424_832, - 1_401_633_461_631_000_063_770_624, - 1.4613131126296026e18, - 674_370_899_916_144_494_247_936, - 1_391_633_461_631_000_101_519_360, + 1.4758e6, + 667_562_080_341_789_698e18, + 140_163_346_163_100_006e18, + 1.4469e6, + 681_247_192_069_384_962e18, + 138_163_346_163_100_013e18, 1e18 ); } else { return PriceData( - 1.4758130760035226e18, - 667_562_080_341_789_698_424_832, - 1_401_633_461_631_000_063_770_624, - 1.4613131126296026e18, - 674_370_899_916_144_494_247_936, - 1_391_633_461_631_000_101_519_360, + 1.4758e6, + 667_562_080_341_789_698e18, + 140_163_346_163_100_006e18, + 1.4613e6, + 674_370_899_916_144_494e18, + 139_163_346_163_100_010e18, 1e18 ); } } } else { - if (price < 1.4904960535905056e18) { - if (price < 1.478922205863501e18) { + if (price < 1.4904e6) { + if (price < 1.4789e6) { return PriceData( - 1.4904960535905056e18, - 660_820_245_862_202_021_511_168, - 1_411_633_461_631_000_026_021_888, - 1.478922205863501e18, - 666_151_184_017_568_179_421_184, - 1_403_823_407_046_999_274_422_272, + 1.4904e6, + 660_820_245_862_202_021e18, + 141_163_346_163_100_002e18, + 1.4758e6, + 667_562_080_341_789_698e18, + 140_163_346_163_100_006e18, 1e18 ); } else { return PriceData( - 1.4904960535905056e18, - 660_820_245_862_202_021_511_168, - 1_411_633_461_631_000_026_021_888, - 1.478922205863501e18, - 666_151_184_017_568_179_421_184, - 1_403_823_407_046_999_274_422_272, + 1.4904e6, + 660_820_245_862_202_021e18, + 141_163_346_163_100_002e18, + 1.4789e6, + 666_151_184_017_568_179e18, + 140_382_340_704_699_927e18, 1e18 ); } } else { - if (price < 1.5053656765640269e18) { + if (price < 1.5053e6) { return PriceData( - 1.5204255894714784e18, - 647_535_612_227_205_331_943_424, - 1_431_633_461_630_999_950_524_416, - 1.5053656765640269e18, - 654_144_915_081_340_263_596_032, - 1_421_633_461_630_999_988_273_152, + 1.5204e6, + 647_535_612_227_205_331e18, + 143_163_346_163_099_995e18, + 1.4904e6, + 660_820_245_862_202_021e18, + 141_163_346_163_100_002e18, 1e18 ); } else { return PriceData( - 1.5204255894714784e18, - 647_535_612_227_205_331_943_424, - 1_431_633_461_630_999_950_524_416, - 1.5053656765640269e18, - 654_144_915_081_340_263_596_032, - 1_421_633_461_630_999_988_273_152, + 1.5204e6, + 647_535_612_227_205_331e18, + 143_163_346_163_099_995e18, + 1.5053e6, + 654_144_915_081_340_263e18, + 142_163_346_163_099_998e18, 1e18 ); } } } } else { - if (price < 1.6687600728918373e18) { - if (price < 1.570136778675488e18) { - if (price < 1.5236532607722375e18) { + if (price < 1.6687e6) { + if (price < 1.5701e6) { + if (price < 1.5236e6) { return PriceData( - 1.570136778675488e18, - 626_771_913_818_503_370_506_240, - 1_463_823_407_046_999_316_365_312, - 1.5236532607722375e18, - 646_166_987_566_021_192_187_904, - 1_433_823_407_046_999_429_611_520, + 1.5701e6, + 626_771_913_818_503_370e18, + 146_382_340_704_699_931e18, + 1.5204e6, + 647_535_612_227_205_331e18, + 143_163_346_163_099_995e18, 1e18 ); } else { return PriceData( - 1.570136778675488e18, - 626_771_913_818_503_370_506_240, - 1_463_823_407_046_999_316_365_312, - 1.5236532607722375e18, - 646_166_987_566_021_192_187_904, - 1_433_823_407_046_999_429_611_520, + 1.5701e6, + 626_771_913_818_503_370e18, + 146_382_340_704_699_931e18, + 1.5236e6, + 646_166_987_566_021_192e18, + 143_382_340_704_699_942e18, 1e18 ); } } else { - if (price < 1.6184722417079278e18) { + if (price < 1.6184e6) { return PriceData( - 1.6687600728918373e18, - 589_699_646_066_015_911_018_496, - 1_523_823_407_046_999_358_308_352, - 1.6184722417079278e18, - 607_953_518_109_393_449_648_128, - 1_493_823_407_046_999_203_119_104, + 1.6687e6, + 589_699_646_066_015_911e18, + 152_382_340_704_699_935e18, + 1.5701e6, + 626_771_913_818_503_370e18, + 146_382_340_704_699_931e18, 1e18 ); } else { return PriceData( - 1.6687600728918373e18, - 589_699_646_066_015_911_018_496, - 1_523_823_407_046_999_358_308_352, - 1.6184722417079278e18, - 607_953_518_109_393_449_648_128, - 1_493_823_407_046_999_203_119_104, + 1.6687e6, + 589_699_646_066_015_911e18, + 152_382_340_704_699_935e18, + 1.6184e6, + 607_953_518_109_393_449e18, + 149_382_340_704_699_920e18, 1e18 ); } } } else { - if (price < 9.859705175834625e18) { - if (price < 1.7211015439591308e18) { + if (price < 9.8597e6) { + if (price < 1.7211e6) { return PriceData( - 9.859705175834625e18, - 141_771_511_686_624_031_277_056, - 3_130_170_430_329_999_549_530_112, - 1.7211015439591308e18, - 571_998_357_018_457_696_894_976, - 1_553_823_407_046_999_245_062_144, + 9.8597e6, + 141_771_511_686_624_031e18, + 313_017_043_032_999_954e18, + 1.6687e6, + 589_699_646_066_015_911e18, + 152_382_340_704_699_935e18, 1e18 ); } else { return PriceData( - 9.859705175834625e18, - 141_771_511_686_624_031_277_056, - 3_130_170_430_329_999_549_530_112, - 1.7211015439591308e18, - 571_998_357_018_457_696_894_976, - 1_553_823_407_046_999_245_062_144, + 9.8597e6, + 141_771_511_686_624_031e18, + 313_017_043_032_999_954e18, + 1.7211e6, + 571_998_357_018_457_696e18, + 155_382_340_704_699_924e18, 1e18 ); } } else { - revert("Price Too High, Well is Bricked!"); + revert("LUT: Invalid price"); } } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index 730f8aec..74468d22 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -67,8 +67,8 @@ contract BeanstalkStable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 50e12; - reserves[1] = 100e12; + reserves[0] = 50e18; + reserves[1] = 100e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 2; ratios[1] = 1; From 10c74e1b78a6e0052967ff3c3a7c671207fccce9 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sat, 13 Jul 2024 23:40:51 +0200 Subject: [PATCH 27/69] update LUT table, add liquidity test. --- src/functions/Stable2.sol | 69 +- src/functions/StableLUT/Stable2LUT1.sol | 3400 ++++++++--------- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 87 +- 3 files changed, 1769 insertions(+), 1787 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 96559970..d3f3d251 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -42,9 +42,13 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // A precision uint256 constant A_PRECISION = 100; - // price Precision. + // price precision. uint256 constant PRICE_PRECISION = 1e6; + // price threshold. more accurate pricing requires a lower threshold, + // at the cost of higher execution costs. + uint256 constant PRICE_THRESHOLD = 100; // 0.001% + address immutable lookupTable; uint256 immutable a; @@ -149,8 +153,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { /** * @inheritdoc IMultiFlowPumpWellFunction - * @dev `calcReserveAtRatioSwap` fetches the closes approxeimate ratios from the target price, - * and performs neuwtons method in order to + * @dev `calcReserveAtRatioSwap` fetches the closes approximate ratios from the target price, + * and performs newtons method in order to converge into a reserve. */ function calcReserveAtRatioSwap( uint256[] memory reserves, @@ -203,9 +207,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // check if new price is within 1 of target price: if (pd.currentPrice > pd.targetPrice) { - if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) return scaledReserves[j] / (10 ** decimals[j]); } else { - if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.targetPrice - pd.currentPrice <= PRICE_THRESHOLD) return scaledReserves[j] / (10 ** decimals[j]); } // calc currentPrice: @@ -268,46 +272,51 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); // update scaledReserve[j] based on lowPrice: - console.log("scaledReserve[j] b4", scaledReserves[j]); + // console.log("scaledReserve[j] b4", scaledReserves[j]); scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; - console.log("scaledReserve[j] afta", scaledReserves[j]); + // console.log("scaledReserve[j] afta", scaledReserves[j]); - // calculate max step size: - console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); - console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); + // calculatde max step size: + // console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); + // console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); - pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.highPriceJ; - console.log("pd.maxStepSize", pd.maxStepSize); + pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; + // console.log("pd.maxStepSize", pd.maxStepSize); // calc currentPrice: - console.log("scaledReserve[j]", scaledReserves[j]); - console.log("scaledReserve[i]", scaledReserves[i]); - pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); - console.log("initial currentPrice", pd.currentPrice); + // console.log("scaledReserve[j]", scaledReserves[j]); + // console.log("scaledReserve[i]", scaledReserves[i]); + pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); + // console.log("initial currentPrice", pd.currentPrice); - for (uint256 k; k < 10; k++) { - console.log("k", k); + for (uint256 k; k < 255; k++) { + // console.log("----------------", k); // scale stepSize proporitional to distance from price: - console.log("pd.targetPrice", pd.targetPrice); - console.log("pd.currentPrice before stepping", pd.currentPrice); + // console.log("pd.targetPrice", pd.targetPrice); + // console.log("pd.currentPrice before stepping", pd.currentPrice); uint256 stepSize = pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); - console.log("stepSize", stepSize); + // console.log("stepSize", stepSize); // increment reserve by stepSize: - console.log("scaledReserves[j] b4", scaledReserves[j]); + // console.log("scaledReserves[j] b4", scaledReserves[j]); scaledReserves[j] = scaledReserves[j] + stepSize; - console.log("scaledReserves[i] af", scaledReserves[i]); - console.log("scaledReserves[j] af", scaledReserves[j]); + // console.log("scaledReserves[i] af", scaledReserves[i]); + // console.log("scaledReserves[j] af", scaledReserves[j]); // calculate new price from reserves: - pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); + // todo: j and i needs to be switched. Current LUT has it inverted + pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); - // check if new price is within 1 of target price: - console.log("pd.currentPrice after step size", pd.currentPrice); - console.log("pd.targetPrice", pd.targetPrice); + // check if new price is within PRICE_THRESHOLD: + // console.log("pd.currentPrice after stepping", pd.currentPrice); + // console.log("target price:", pd.targetPrice); if (pd.currentPrice > pd.targetPrice) { - if (pd.currentPrice - pd.targetPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) { + return scaledReserves[j] / (10 ** (18 - decimals[j])); + } } else { - if (pd.targetPrice - pd.currentPrice <= 1) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.targetPrice - pd.currentPrice <= PRICE_THRESHOLD) { + return scaledReserves[j] / (10 ** (18 - decimals[j])); + } } } } diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index a0907abb..7b713f59 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -29,184 +29,184 @@ contract Stable2LUT1 is ILookupTable { * Used in `calcReserveAtRatioLiquidity` function in the stableswap well function. */ function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { - if (price < 1.0259e6) { - if (price < 0.5719e6) { - if (price < 0.4096e6) { - if (price < 0.3397e6) { - if (price < 0.3017e6) { - if (price < 0.2898e6) { - if (price < 0.0108e6) { + if (price < 1.006623e6) { + if (price < 0.825934e6) { + if (price < 0.741141e6) { + if (price < 0.412329e6) { + if (price < 0.268539e6) { + if (price < 0.211448e6) { + if (price < 0.001832e6) { revert("LUT: Invalid price"); } else { return PriceData( - 0.2898e6, - 0e18, - 703_998_871_212_465_767e18, - 0.0108e6, - 0e18, - 200_000_000_000_000_009e18, + 0.211448e6, + 0.0e18, + 0.077562793638189589e18, + 0.001832e6, + 0.0e18, + 0.000833333333333333e18, 1e18 ); } } else { - if (price < 0.3009e6) { + if (price < 0.238695e6) { return PriceData( - 0.3017e6, - 0e18, - 670_475_115_440_443_486e18, - 0.2898e6, - 0e18, - 703_998_871_212_465_767e18, + 0.268539e6, + 0.0e18, + 0.100158566165017532e18, + 0.211448e6, + 0.0e18, + 0.077562793638189589e18, 1e18 ); } else { return PriceData( - 0.3017e6, - 0e18, - 670_475_115_440_443_486e18, - 0.3009e6, - 0e18, - 672_749_994_932_561_176e18, + 0.268539e6, + 0.0e18, + 0.100158566165017532e18, + 0.238695e6, + 0.0e18, + 0.088139538225215433e18, 1e18 ); } } } else { - if (price < 0.3252e6) { - if (price < 0.314e6) { + if (price < 0.335863e6) { + if (price < 0.300961e6) { return PriceData( - 0.3252e6, - 0e18, - 611_590_904_484_146_475e18, - 0.3017e6, - 0e18, - 670_475_115_440_443_486e18, + 0.335863e6, + 0.0e18, + 0.129336991432099091e18, + 0.268539e6, + 0.0e18, + 0.100158566165017532e18, 1e18 ); } else { return PriceData( - 0.3252e6, - 0e18, - 611_590_904_484_146_475e18, - 0.314e6, - 0e18, - 638_547_728_990_898_583e18, + 0.335863e6, + 0.0e18, + 0.129336991432099091e18, + 0.300961e6, + 0.0e18, + 0.113816552460247203e18, 1e18 ); } } else { - if (price < 0.3267e6) { + if (price < 0.373071e6) { return PriceData( - 0.3397e6, - 0e18, - 579_181_613_597_186_854e18, - 0.3252e6, - 0e18, - 611_590_904_484_146_475e18, + 0.412329e6, + 0.0e18, + 0.167015743068309769e18, + 0.335863e6, + 0.0e18, + 0.129336991432099091e18, 1e18 ); } else { return PriceData( - 0.3397e6, - 0e18, - 579_181_613_597_186_854e18, - 0.3267e6, - 0e18, - 608_140_694_277_046_234e18, + 0.412329e6, + 0.0e18, + 0.167015743068309769e18, + 0.373071e6, + 0.0e18, + 0.146973853900112583e18, 1e18 ); } } } } else { - if (price < 0.3777e6) { - if (price < 0.353e6) { - if (price < 0.3508e6) { + if (price < 0.582463e6) { + if (price < 0.495602e6) { + if (price < 0.453302e6) { return PriceData( - 0.353e6, - 0e18, - 551_601_536_759_225_600e18, - 0.3397e6, - 0e18, - 579_181_613_597_186_854e18, + 0.495602e6, + 0.0e18, + 0.215671155821681004e18, + 0.412329e6, + 0.0e18, + 0.167015743068309769e18, 1e18 ); } else { return PriceData( - 0.353e6, - 0e18, - 551_601_536_759_225_600e18, - 0.3508e6, - 0e18, - 555_991_731_349_224_049e18, + 0.495602e6, + 0.0e18, + 0.215671155821681004e18, + 0.453302e6, + 0.0e18, + 0.189790617123079292e18, 1e18 ); } } else { - if (price < 0.3667e6) { + if (price < 0.538801e6) { return PriceData( - 0.3777e6, - 0e18, - 505_447_028_499_294_502e18, - 0.353e6, - 0e18, - 551_601_536_759_225_600e18, + 0.582463e6, + 0.0e18, + 0.278500976009402101e18, + 0.495602e6, + 0.0e18, + 0.215671155821681004e18, 1e18 ); } else { return PriceData( - 0.3777e6, - 0e18, - 505_447_028_499_294_502e18, - 0.3667e6, - 0e18, - 525_334_796_913_548_043e18, + 0.582463e6, + 0.0e18, + 0.278500976009402101e18, + 0.538801e6, + 0.0e18, + 0.245080858888273884e18, 1e18 ); } } } else { - if (price < 0.395e6) { - if (price < 0.3806e6) { + if (price < 0.669604e6) { + if (price < 0.626181e6) { return PriceData( - 0.395e6, - 0e18, - 476_494_146_860_361_123e18, - 0.3777e6, - 0e18, - 505_447_028_499_294_502e18, + 0.669604e6, + 0.0e18, + 0.359634524805529598e18, + 0.582463e6, + 0.0e18, + 0.278500976009402101e18, 1e18 ); } else { return PriceData( - 0.395e6, - 0e18, - 476_494_146_860_361_123e18, - 0.3806e6, - 0e18, - 500_318_854_203_379_155e18, + 0.669604e6, + 0.0e18, + 0.359634524805529598e18, + 0.626181e6, + 0.0e18, + 0.316478381828866062e18, 1e18 ); } } else { - if (price < 0.4058e6) { + if (price < 0.712465e6) { return PriceData( - 0.4096e6, - 0e18, - 453_803_949_390_820_058e18, - 0.395e6, - 0e18, - 476_494_146_860_361_123e18, + 0.741141e6, + 0.0e18, + 0.445700403950951007e18, + 0.669604e6, + 0.0e18, + 0.359634524805529598e18, 1e18 ); } else { return PriceData( - 0.4096e6, - 0e18, - 453_803_949_390_820_058e18, - 0.4058e6, - 0e18, - 459_497_298_635_722_250e18, + 0.741141e6, + 0.0e18, + 0.445700403950951007e18, + 0.712465e6, + 0.0e18, + 0.408675596369920013e18, 1e18 ); } @@ -214,178 +214,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.4972e6) { - if (price < 0.4553e6) { - if (price < 0.4351e6) { - if (price < 0.4245e6) { + if (price < 0.787158e6) { + if (price < 0.760974e6) { + if (price < 0.754382e6) { + if (price < 0.747771e6) { return PriceData( - 0.4351e6, - 0e18, - 417_724_816_941_565_672e18, - 0.4096e6, - 0e18, - 453_803_949_390_820_058e18, + 0.754382e6, + 0.0e18, + 0.464077888328770338e18, + 0.741141e6, + 0.0e18, + 0.445700403950951007e18, 1e18 ); } else { return PriceData( - 0.4351e6, - 0e18, - 417_724_816_941_565_672e18, - 0.4245e6, - 0e18, - 432_194_237_515_066_645e18, + 0.754382e6, + 0.0e18, + 0.464077888328770338e18, + 0.747771e6, + 0.0e18, + 0.454796330562194928e18, 1e18 ); } } else { - if (price < 0.4398e6) { + if (price < 0.754612e6) { return PriceData( - 0.4553e6, - 0e18, - 392_012_913_845_865_367e18, - 0.4351e6, - 0e18, - 417_724_816_941_565_672e18, + 0.760974e6, + 0.0e18, + 0.473548865641602368e18, + 0.754382e6, + 0.0e18, + 0.464077888328770338e18, 1e18 ); } else { return PriceData( - 0.4553e6, - 0e18, - 392_012_913_845_865_367e18, - 0.4398e6, - 0e18, - 411_613_559_538_158_684e18, + 0.760974e6, + 0.0e18, + 0.473548865641602368e18, + 0.754612e6, + 0.0e18, + 0.464404086784000025e18, 1e18 ); } } } else { - if (price < 0.4712e6) { - if (price < 0.4656e6) { + if (price < 0.774102e6) { + if (price < 0.767548e6) { return PriceData( - 0.4712e6, - 0e18, - 373_345_632_234_157_500e18, - 0.4553e6, - 0e18, - 392_012_913_845_865_367e18, + 0.774102e6, + 0.0e18, + 0.49307462061807833e18, + 0.760974e6, + 0.0e18, + 0.473548865641602368e18, 1e18 ); } else { return PriceData( - 0.4712e6, - 0e18, - 373_345_632_234_157_500e18, - 0.4656e6, - 0e18, - 379_749_833_583_241_466e18, + 0.774102e6, + 0.0e18, + 0.49307462061807833e18, + 0.767548e6, + 0.0e18, + 0.483213128205716769e18, 1e18 ); } } else { - if (price < 0.4873e6) { + if (price < 0.780639e6) { return PriceData( - 0.4972e6, - 0e18, - 345_227_121_439_310_375e18, - 0.4712e6, - 0e18, - 373_345_632_234_157_500e18, + 0.787158e6, + 0.0e18, + 0.513405477528194876e18, + 0.774102e6, + 0.0e18, + 0.49307462061807833e18, 1e18 ); } else { return PriceData( - 0.4972e6, - 0e18, - 345_227_121_439_310_375e18, - 0.4873e6, - 0e18, - 355_567_268_794_435_702e18, + 0.787158e6, + 0.0e18, + 0.513405477528194876e18, + 0.780639e6, + 0.0e18, + 0.503137367977630867e18, 1e18 ); } } } } else { - if (price < 0.5373e6) { - if (price < 0.5203e6) { - if (price < 0.5037e6) { + if (price < 0.806614e6) { + if (price < 0.796011e6) { + if (price < 0.79366e6) { return PriceData( - 0.5203e6, - 0e18, - 322_509_994_371_370_222e18, - 0.4972e6, - 0e18, - 345_227_121_439_310_375e18, + 0.796011e6, + 0.0e18, + 0.527731916799999978e18, + 0.787158e6, + 0.0e18, + 0.513405477528194876e18, 1e18 ); } else { return PriceData( - 0.5203e6, - 0e18, - 322_509_994_371_370_222e18, - 0.5037e6, - 0e18, - 338_635_494_089_938_733e18, + 0.796011e6, + 0.0e18, + 0.527731916799999978e18, + 0.79366e6, + 0.0e18, + 0.523883140334892694e18, 1e18 ); } } else { - if (price < 0.5298e6) { + if (price < 0.800145e6) { return PriceData( - 0.5373e6, - 0e18, - 307_152_375_591_781_169e18, - 0.5203e6, - 0e18, - 322_509_994_371_370_222e18, + 0.806614e6, + 0.0e18, + 0.545484319382437244e18, + 0.796011e6, + 0.0e18, + 0.527731916799999978e18, 1e18 ); } else { return PriceData( - 0.5373e6, - 0e18, - 307_152_375_591_781_169e18, - 0.5298e6, - 0e18, - 313_842_837_672_100_360e18, + 0.806614e6, + 0.0e18, + 0.545484319382437244e18, + 0.800145e6, + 0.0e18, + 0.534574632994788468e18, 1e18 ); } } } else { - if (price < 0.5633e6) { - if (price < 0.5544e6) { + if (price < 0.819507e6) { + if (price < 0.813068e6) { return PriceData( - 0.5633e6, - 0e18, - 285_311_670_611_000_293e18, - 0.5373e6, - 0e18, - 307_152_375_591_781_169e18, + 0.819507e6, + 0.0e18, + 0.567976175950059559e18, + 0.806614e6, + 0.0e18, + 0.545484319382437244e18, 1e18 ); } else { return PriceData( - 0.5633e6, - 0e18, - 285_311_670_611_000_293e18, - 0.5544e6, - 0e18, - 292_526_071_992_172_539e18, + 0.819507e6, + 0.0e18, + 0.567976175950059559e18, + 0.813068e6, + 0.0e18, + 0.55661665243105829e18, 1e18 ); } } else { return PriceData( - 0.5719e6, - 0e18, - 278_596_259_040_164_302e18, - 0.5633e6, - 0e18, - 285_311_670_611_000_293e18, + 0.825934e6, + 0.0e18, + 0.579567526479652595e18, + 0.819507e6, + 0.0e18, + 0.567976175950059559e18, 1e18 ); } @@ -393,190 +393,190 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.7793e6) { - if (price < 0.6695e6) { - if (price < 0.6256e6) { - if (price < 0.5978e6) { - if (price < 0.5895e6) { + if (price < 0.915208e6) { + if (price < 0.870637e6) { + if (price < 0.845143e6) { + if (price < 0.836765e6) { + if (price < 0.832347e6) { return PriceData( - 0.5978e6, - 0e18, - 259_374_246_010_000_262e18, - 0.5719e6, - 0e18, - 278_596_259_040_164_302e18, + 0.836765e6, + 0.0e18, + 0.599695360000000011e18, + 0.825934e6, + 0.0e18, + 0.579567526479652595e18, 1e18 ); } else { return PriceData( - 0.5978e6, - 0e18, - 259_374_246_010_000_262e18, - 0.5895e6, - 0e18, - 265_329_770_514_442_160e18, + 0.836765e6, + 0.0e18, + 0.599695360000000011e18, + 0.832347e6, + 0.0e18, + 0.591395435183319051e18, 1e18 ); } } else { - if (price < 0.6074e6) { + if (price < 0.838751e6) { return PriceData( - 0.6256e6, - 0e18, - 240_661_923_369_108_501e18, - 0.5978e6, - 0e18, - 259_374_246_010_000_262e18, + 0.845143e6, + 0.0e18, + 0.615780336509078485e18, + 0.836765e6, + 0.0e18, + 0.599695360000000011e18, 1e18 ); } else { return PriceData( - 0.6256e6, - 0e18, - 240_661_923_369_108_501e18, - 0.6074e6, - 0e18, - 252_695_019_537_563_946e18, + 0.845143e6, + 0.0e18, + 0.615780336509078485e18, + 0.838751e6, + 0.0e18, + 0.60346472977889698e18, 1e18 ); } } } else { - if (price < 0.6439e6) { - if (price < 0.6332e6) { + if (price < 0.857902e6) { + if (price < 0.851526e6) { return PriceData( - 0.6439e6, - 0e18, - 229_201_831_780_103_315e18, - 0.6256e6, - 0e18, - 240_661_923_369_108_501e18, + 0.857902e6, + 0.0e18, + 0.641170696073592783e18, + 0.845143e6, + 0.0e18, + 0.615780336509078485e18, 1e18 ); } else { return PriceData( - 0.6439e6, - 0e18, - 229_201_831_780_103_315e18, - 0.6332e6, - 0e18, - 235_794_769_100_000_184e18, + 0.857902e6, + 0.0e18, + 0.641170696073592783e18, + 0.851526e6, + 0.0e18, + 0.628347282152120878e18, 1e18 ); } } else { - if (price < 0.6625e6) { + if (price < 0.864272e6) { return PriceData( - 0.6695e6, - 0e18, - 214_358_881_000_000_155e18, - 0.6439e6, - 0e18, - 229_201_831_780_103_315e18, + 0.870637e6, + 0.0e18, + 0.667607971755094454e18, + 0.857902e6, + 0.0e18, + 0.641170696073592783e18, 1e18 ); } else { return PriceData( - 0.6695e6, - 0e18, - 214_358_881_000_000_155e18, - 0.6625e6, - 0e18, - 218_287_458_838_193_622e18, + 0.870637e6, + 0.0e18, + 0.667607971755094454e18, + 0.864272e6, + 0.0e18, + 0.654255812319992636e18, 1e18 ); } } } } else { - if (price < 0.7198e6) { - if (price < 0.7005e6) { - if (price < 0.6814e6) { + if (price < 0.889721e6) { + if (price < 0.87711e6) { + if (price < 0.877e6) { return PriceData( - 0.7005e6, - 0e18, - 197_993_159_943_939_803e18, - 0.6695e6, - 0e18, - 214_358_881_000_000_155e18, + 0.87711e6, + 0.0e18, + 0.681471999999999967e18, + 0.870637e6, + 0.0e18, + 0.667607971755094454e18, 1e18 ); } else { return PriceData( - 0.7005e6, - 0e18, - 197_993_159_943_939_803e18, - 0.6814e6, - 0e18, - 207_892_817_941_136_807e18, + 0.87711e6, + 0.0e18, + 0.681471999999999967e18, + 0.877e6, + 0.0e18, + 0.681232624239892393e18, 1e18 ); } } else { - if (price < 0.7067e6) { + if (price < 0.88336e6) { return PriceData( - 0.7198e6, - 0e18, - 188_564_914_232_323_597e18, - 0.7005e6, - 0e18, - 197_993_159_943_939_803e18, + 0.889721e6, + 0.0e18, + 0.709321766180645907e18, + 0.87711e6, + 0.0e18, + 0.681471999999999967e18, 1e18 ); } else { return PriceData( - 0.7198e6, - 0e18, - 188_564_914_232_323_597e18, - 0.7067e6, - 0e18, - 194_871_710_000_000_090e18, + 0.889721e6, + 0.0e18, + 0.709321766180645907e18, + 0.88336e6, + 0.0e18, + 0.695135330857033051e18, 1e18 ); } } } else { - if (price < 0.7449e6) { - if (price < 0.7394e6) { + if (price < 0.902453e6) { + if (price < 0.896084e6) { return PriceData( - 0.7449e6, - 0e18, - 177_156_100_000_000_082e18, - 0.7198e6, - 0e18, - 188_564_914_232_323_597e18, + 0.902453e6, + 0.0e18, + 0.738569102645403985e18, + 0.889721e6, + 0.0e18, + 0.709321766180645907e18, 1e18 ); } else { return PriceData( - 0.7449e6, - 0e18, - 177_156_100_000_000_082e18, - 0.7394e6, - 0e18, - 179_585_632_602_212_943e18, + 0.902453e6, + 0.0e18, + 0.738569102645403985e18, + 0.896084e6, + 0.0e18, + 0.723797720592495919e18, 1e18 ); } } else { - if (price < 0.7592e6) { + if (price < 0.908826e6) { return PriceData( - 0.7793e6, - 0e18, - 162_889_462_677_744_172e18, - 0.7449e6, - 0e18, - 177_156_100_000_000_082e18, + 0.915208e6, + 0.0e18, + 0.769022389260104022e18, + 0.902453e6, + 0.0e18, + 0.738569102645403985e18, 1e18 ); } else { return PriceData( - 0.7793e6, - 0e18, - 162_889_462_677_744_172e18, - 0.7592e6, - 0e18, - 171_033_935_811_631_375e18, + 0.915208e6, + 0.0e18, + 0.769022389260104022e18, + 0.908826e6, + 0.0e18, + 0.753641941474902044e18, 1e18 ); } @@ -584,178 +584,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.8845e6) { - if (price < 0.8243e6) { - if (price < 0.7997e6) { - if (price < 0.784e6) { + if (price < 0.958167e6) { + if (price < 0.934425e6) { + if (price < 0.9216e6) { + if (price < 0.917411e6) { return PriceData( - 0.7997e6, - 0e18, - 155_132_821_597_851_585e18, - 0.7793e6, - 0e18, - 162_889_462_677_744_172e18, + 0.9216e6, + 0.0e18, + 0.784716723734800059e18, + 0.915208e6, + 0.0e18, + 0.769022389260104022e18, 1e18 ); } else { return PriceData( - 0.7997e6, - 0e18, - 155_132_821_597_851_585e18, - 0.784e6, - 0e18, - 161_051_000_000_000_047e18, + 0.9216e6, + 0.0e18, + 0.784716723734800059e18, + 0.917411e6, + 0.0e18, + 0.774399999999999977e18, 1e18 ); } } else { - if (price < 0.8204e6) { + if (price < 0.928005e6) { return PriceData( - 0.8243e6, - 0e18, - 146_410_000_000_000_016e18, - 0.7997e6, - 0e18, - 155_132_821_597_851_585e18, + 0.934425e6, + 0.0e18, + 0.81707280688754691e18, + 0.9216e6, + 0.0e18, + 0.784716723734800059e18, 1e18 ); } else { return PriceData( - 0.8243e6, - 0e18, - 146_410_000_000_000_016e18, - 0.8204e6, - 0e18, - 147_745_544_378_906_267e18, + 0.934425e6, + 0.0e18, + 0.81707280688754691e18, + 0.928005e6, + 0.0e18, + 0.800731350749795956e18, 1e18 ); } } } else { - if (price < 0.8628e6) { - if (price < 0.8414e6) { + if (price < 0.947318e6) { + if (price < 0.940861e6) { return PriceData( - 0.8628e6, - 0e18, - 134_009_564_062_499_995e18, - 0.8243e6, - 0e18, - 146_410_000_000_000_016e18, + 0.947318e6, + 0.0e18, + 0.8507630225817856e18, + 0.934425e6, + 0.0e18, + 0.81707280688754691e18, 1e18 ); } else { return PriceData( - 0.8628e6, - 0e18, - 134_009_564_062_499_995e18, - 0.8414e6, - 0e18, - 140_710_042_265_625_000e18, + 0.947318e6, + 0.0e18, + 0.8507630225817856e18, + 0.940861e6, + 0.0e18, + 0.833747762130149894e18, 1e18 ); } } else { - if (price < 0.8658e6) { + if (price < 0.953797e6) { return PriceData( - 0.8845e6, - 0e18, - 127_628_156_249_999_999e18, - 0.8628e6, - 0e18, - 134_009_564_062_499_995e18, + 0.958167e6, + 0.0e18, + 0.880000000000000004e18, + 0.947318e6, + 0.0e18, + 0.8507630225817856e18, 1e18 ); } else { return PriceData( - 0.8845e6, - 0e18, - 127_628_156_249_999_999e18, - 0.8658e6, - 0e18, - 133_100_000_000_000_010e18, + 0.958167e6, + 0.0e18, + 0.880000000000000004e18, + 0.953797e6, + 0.0e18, + 0.868125533246720038e18, 1e18 ); } } } } else { - if (price < 0.9523e6) { - if (price < 0.9087e6) { - if (price < 0.9067e6) { + if (price < 0.979988e6) { + if (price < 0.966831e6) { + if (price < 0.960301e6) { return PriceData( - 0.9087e6, - 0e18, - 120_999_999_999_999_999e18, - 0.8845e6, - 0e18, - 127_628_156_249_999_999e18, + 0.966831e6, + 0.0e18, + 0.903920796799999926e18, + 0.958167e6, + 0.0e18, + 0.880000000000000004e18, 1e18 ); } else { return PriceData( - 0.9087e6, - 0e18, - 120_999_999_999_999_999e18, - 0.9067e6, - 0e18, - 121_550_625_000_000_001e18, + 0.966831e6, + 0.0e18, + 0.903920796799999926e18, + 0.960301e6, + 0.0e18, + 0.885842380864000023e18, 1e18 ); } } else { - if (price < 0.9292e6) { + if (price < 0.973393e6) { return PriceData( - 0.9523e6, - 0e18, - 110_250_000_000_000_006e18, - 0.9087e6, - 0e18, - 120_999_999_999_999_999e18, + 0.979988e6, + 0.0e18, + 0.941192000000000029e18, + 0.966831e6, + 0.0e18, + 0.903920796799999926e18, 1e18 ); } else { return PriceData( - 0.9523e6, - 0e18, - 110_250_000_000_000_006e18, - 0.9292e6, - 0e18, - 115_762_500_000_000_006e18, + 0.979988e6, + 0.0e18, + 0.941192000000000029e18, + 0.973393e6, + 0.0e18, + 0.922368159999999992e18, 1e18 ); } } } else { - if (price < 0.9758e6) { - if (price < 0.9534e6) { + if (price < 0.993288e6) { + if (price < 0.986618e6) { return PriceData( - 0.9758e6, - 0e18, - 105_000_000_000_000_006e18, - 0.9523e6, - 0e18, - 110_250_000_000_000_006e18, + 0.993288e6, + 0.0e18, + 0.980000000000000093e18, + 0.979988e6, + 0.0e18, + 0.941192000000000029e18, 1e18 ); } else { return PriceData( - 0.9758e6, - 0e18, - 105_000_000_000_000_006e18, - 0.9534e6, - 0e18, - 110_000_000_000_000_000e18, + 0.993288e6, + 0.0e18, + 0.980000000000000093e18, + 0.986618e6, + 0.0e18, + 0.960400000000000031e18, 1e18 ); } } else { return PriceData( - 1.0259e6, - 0e18, - 950_000_000_000_000_037e18, - 0.9758e6, - 0e18, - 105_000_000_000_000_006e18, + 1.006623e6, + 0.0e18, + 1.020000000000000018e18, + 0.993288e6, + 0.0e18, + 0.980000000000000093e18, 1e18 ); } @@ -764,191 +764,191 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.8687e6) { - if (price < 1.3748e6) { - if (price < 1.1729e6) { - if (price < 1.1084e6) { - if (price < 1.0541e6) { - if (price < 1.0526e6) { + if (price < 1.214961e6) { + if (price < 1.105774e6) { + if (price < 1.054473e6) { + if (price < 1.033615e6) { + if (price < 1.020013e6) { + if (price < 1.013294e6) { return PriceData( - 1.0541e6, - 0e18, - 899_999_999_999_999_958e18, - 1.0259e6, - 0e18, - 950_000_000_000_000_037e18, + 1.020013e6, + 0.0e18, + 1.061208000000000151e18, + 1.006623e6, + 0.0e18, + 1.020000000000000018e18, 1e18 ); } else { return PriceData( - 1.0541e6, - 0e18, - 899_999_999_999_999_958e18, - 1.0526e6, - 0e18, - 902_500_000_000_000_015e18, + 1.020013e6, + 0.0e18, + 1.061208000000000151e18, + 1.013294e6, + 0.0e18, + 1.040399999999999991e18, 1e18 ); } } else { - if (price < 1.0801e6) { + if (price < 1.026785e6) { return PriceData( - 1.1084e6, - 0e18, - 814_506_250_000_000_029e18, - 1.0541e6, - 0e18, - 899_999_999_999_999_958e18, + 1.033615e6, + 0.0e18, + 1.104080803200000016e18, + 1.020013e6, + 0.0e18, + 1.061208000000000151e18, 1e18 ); } else { return PriceData( - 1.1084e6, - 0e18, - 814_506_250_000_000_029e18, - 1.0801e6, - 0e18, - 857_374_999_999_999_981e18, + 1.033615e6, + 0.0e18, + 1.104080803200000016e18, + 1.026785e6, + 0.0e18, + 1.082432159999999977e18, 1e18 ); } } } else { - if (price < 1.1377e6) { - if (price < 1.1115e6) { + if (price < 1.040503e6) { + if (price < 1.038588e6) { return PriceData( - 1.1377e6, - 0e18, - 773_780_937_499_999_954e18, - 1.1084e6, - 0e18, - 814_506_250_000_000_029e18, + 1.040503e6, + 0.0e18, + 1.12616241926400007e18, + 1.033615e6, + 0.0e18, + 1.104080803200000016e18, 1e18 ); } else { return PriceData( - 1.1377e6, - 0e18, - 773_780_937_499_999_954e18, - 1.1115e6, - 0e18, - 810_000_000_000_000_029e18, + 1.040503e6, + 0.0e18, + 1.12616241926400007e18, + 1.038588e6, + 0.0e18, + 1.120000000000000107e18, 1e18 ); } } else { - if (price < 1.1679e6) { + if (price < 1.047454e6) { return PriceData( - 1.1729e6, - 0e18, - 729_000_000_000_000_039e18, - 1.1377e6, - 0e18, - 773_780_937_499_999_954e18, + 1.054473e6, + 0.0e18, + 1.171659381002265521e18, + 1.040503e6, + 0.0e18, + 1.12616241926400007e18, 1e18 ); } else { return PriceData( - 1.1729e6, - 0e18, - 729_000_000_000_000_039e18, - 1.1679e6, - 0e18, - 735_091_890_625_000_016e18, + 1.054473e6, + 0.0e18, + 1.171659381002265521e18, + 1.047454e6, + 0.0e18, + 1.14868566764928004e18, 1e18 ); } } } } else { - if (price < 1.2653e6) { - if (price < 1.2316e6) { - if (price < 1.1992e6) { + if (price < 1.079216e6) { + if (price < 1.068722e6) { + if (price < 1.06156e6) { return PriceData( - 1.2316e6, - 0e18, - 663_420_431_289_062_394e18, - 1.1729e6, - 0e18, - 729_000_000_000_000_039e18, + 1.068722e6, + 0.0e18, + 1.218994419994757328e18, + 1.054473e6, + 0.0e18, + 1.171659381002265521e18, 1e18 ); } else { return PriceData( - 1.2316e6, - 0e18, - 663_420_431_289_062_394e18, - 1.1992e6, - 0e18, - 698_337_296_093_749_868e18, + 1.068722e6, + 0.0e18, + 1.218994419994757328e18, + 1.06156e6, + 0.0e18, + 1.195092568622310836e18, 1e18 ); } } else { - if (price < 1.2388e6) { + if (price < 1.075962e6) { return PriceData( - 1.2653e6, - 0e18, - 630_249_409_724_609_181e18, - 1.2316e6, - 0e18, - 663_420_431_289_062_394e18, + 1.079216e6, + 0.0e18, + 1.254399999999999959e18, + 1.068722e6, + 0.0e18, + 1.218994419994757328e18, 1e18 ); } else { return PriceData( - 1.2653e6, - 0e18, - 630_249_409_724_609_181e18, - 1.2388e6, - 0e18, - 656_100_000_000_000_049e18, + 1.079216e6, + 0.0e18, + 1.254399999999999959e18, + 1.075962e6, + 0.0e18, + 1.243374308394652239e18, 1e18 ); } } } else { - if (price < 1.3101e6) { - if (price < 1.3003e6) { + if (price < 1.09069e6) { + if (price < 1.083283e6) { return PriceData( - 1.3101e6, - 0e18, - 590_490_000_000_000_030e18, - 1.2653e6, - 0e18, - 630_249_409_724_609_181e18, + 1.09069e6, + 0.0e18, + 1.293606630453796313e18, + 1.079216e6, + 0.0e18, + 1.254399999999999959e18, 1e18 ); } else { return PriceData( - 1.3101e6, - 0e18, - 590_490_000_000_000_030e18, - 1.3003e6, - 0e18, - 598_736_939_238_378_782e18, + 1.09069e6, + 0.0e18, + 1.293606630453796313e18, + 1.083283e6, + 0.0e18, + 1.268241794562545266e18, 1e18 ); } } else { - if (price < 1.3368e6) { + if (price < 1.098185e6) { return PriceData( - 1.3748e6, - 0e18, - 540_360_087_662_636_788e18, - 1.3101e6, - 0e18, - 590_490_000_000_000_030e18, + 1.105774e6, + 0.0e18, + 1.345868338324129665e18, + 1.09069e6, + 0.0e18, + 1.293606630453796313e18, 1e18 ); } else { return PriceData( - 1.3748e6, - 0e18, - 540_360_087_662_636_788e18, - 1.3368e6, - 0e18, - 568_800_092_276_459_816e18, + 1.105774e6, + 0.0e18, + 1.345868338324129665e18, + 1.098185e6, + 0.0e18, + 1.319478763062872151e18, 1e18 ); } @@ -956,178 +956,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.5924e6) { - if (price < 1.4722e6) { - if (price < 1.4145e6) { - if (price < 1.3875e6) { + if (price < 1.161877e6) { + if (price < 1.129145e6) { + if (price < 1.12125e6) { + if (price < 1.113461e6) { return PriceData( - 1.4145e6, - 0e18, - 513_342_083_279_504_885e18, - 1.3748e6, - 0e18, - 540_360_087_662_636_788e18, + 1.12125e6, + 0.0e18, + 1.400241419192424397e18, + 1.105774e6, + 0.0e18, + 1.345868338324129665e18, 1e18 ); } else { return PriceData( - 1.4145e6, - 0e18, - 513_342_083_279_504_885e18, - 1.3875e6, - 0e18, - 531_440_999_999_999_980e18, + 1.12125e6, + 0.0e18, + 1.400241419192424397e18, + 1.113461e6, + 0.0e18, + 1.372785705090612263e18, 1e18 ); } } else { - if (price < 1.456e6) { + if (price < 1.122574e6) { return PriceData( - 1.4722e6, - 0e18, - 478_296_899_999_999_996e18, - 1.4145e6, - 0e18, - 513_342_083_279_504_885e18, + 1.129145e6, + 0.0e18, + 1.428246247576273165e18, + 1.12125e6, + 0.0e18, + 1.400241419192424397e18, 1e18 ); } else { return PriceData( - 1.4722e6, - 0e18, - 478_296_899_999_999_996e18, - 1.456e6, - 0e18, - 487_674_979_115_529_607e18, + 1.129145e6, + 0.0e18, + 1.428246247576273165e18, + 1.122574e6, + 0.0e18, + 1.404927999999999955e18, 1e18 ); } } } else { - if (price < 1.5448e6) { - if (price < 1.4994e6) { + if (price < 1.145271e6) { + if (price < 1.137151e6) { return PriceData( - 1.5448e6, - 0e18, - 440_126_668_651_765_444e18, - 1.4722e6, - 0e18, - 478_296_899_999_999_996e18, + 1.145271e6, + 0.0e18, + 1.485947395978354457e18, + 1.129145e6, + 0.0e18, + 1.428246247576273165e18, 1e18 ); } else { return PriceData( - 1.5448e6, - 0e18, - 440_126_668_651_765_444e18, - 1.4994e6, - 0e18, - 463_291_230_159_753_114e18, + 1.145271e6, + 0.0e18, + 1.485947395978354457e18, + 1.137151e6, + 0.0e18, + 1.456811172527798348e18, 1e18 ); } } else { - if (price < 1.5651e6) { + if (price < 1.153512e6) { return PriceData( - 1.5924e6, - 0e18, - 418_120_335_219_177_149e18, - 1.5448e6, - 0e18, - 440_126_668_651_765_444e18, + 1.161877e6, + 0.0e18, + 1.545979670775879944e18, + 1.145271e6, + 0.0e18, + 1.485947395978354457e18, 1e18 ); } else { return PriceData( - 1.5924e6, - 0e18, - 418_120_335_219_177_149e18, - 1.5651e6, - 0e18, - 430_467_210_000_000_016e18, + 1.161877e6, + 0.0e18, + 1.545979670775879944e18, + 1.153512e6, + 0.0e18, + 1.515666343897921431e18, 1e18 ); } } } } else { - if (price < 1.7499e6) { - if (price < 1.6676e6) { - if (price < 1.6424e6) { + if (price < 1.187769e6) { + if (price < 1.170371e6) { + if (price < 1.169444e6) { return PriceData( - 1.6676e6, - 0e18, - 387_420_489_000_000_015e18, - 1.5924e6, - 0e18, - 418_120_335_219_177_149e18, + 1.170371e6, + 0.0e18, + 1.576899264191397476e18, + 1.161877e6, + 0.0e18, + 1.545979670775879944e18, 1e18 ); } else { return PriceData( - 1.6676e6, - 0e18, - 387_420_489_000_000_015e18, - 1.6424e6, - 0e18, - 397_214_318_458_218_211e18, + 1.170371e6, + 0.0e18, + 1.576899264191397476e18, + 1.169444e6, + 0.0e18, + 1.573519359999999923e18, 1e18 ); } } else { - if (price < 1.6948e6) { + if (price < 1.179e6) { return PriceData( - 1.7499e6, - 0e18, - 358_485_922_408_541_867e18, - 1.6676e6, - 0e18, - 387_420_489_000_000_015e18, + 1.187769e6, + 0.0e18, + 1.640605994464729989e18, + 1.170371e6, + 0.0e18, + 1.576899264191397476e18, 1e18 ); } else { return PriceData( - 1.7499e6, - 0e18, - 358_485_922_408_541_867e18, - 1.6948e6, - 0e18, - 377_353_602_535_307_320e18, + 1.187769e6, + 0.0e18, + 1.640605994464729989e18, + 1.179e6, + 0.0e18, + 1.608437249475225483e18, 1e18 ); } } } else { - if (price < 1.8078e6) { - if (price < 1.7808e6) { + if (price < 1.205744e6) { + if (price < 1.196681e6) { return PriceData( - 1.8078e6, - 0e18, - 340_561_626_288_114_794e18, - 1.7499e6, - 0e18, - 358_485_922_408_541_867e18, + 1.205744e6, + 0.0e18, + 1.706886476641104933e18, + 1.187769e6, + 0.0e18, + 1.640605994464729989e18, 1e18 ); } else { return PriceData( - 1.8078e6, - 0e18, - 340_561_626_288_114_794e18, - 1.7808e6, - 0e18, - 348_678_440_100_000_033e18, + 1.205744e6, + 0.0e18, + 1.706886476641104933e18, + 1.196681e6, + 0.0e18, + 1.673418114354024322e18, 1e18 ); } } else { return PriceData( - 1.8687e6, - 0e18, - 323_533_544_973_709_067e18, - 1.8078e6, - 0e18, - 340_561_626_288_114_794e18, + 1.214961e6, + 0.0e18, + 1.741024206173927169e18, + 1.205744e6, + 0.0e18, + 1.706886476641104933e18, 1e18 ); } @@ -1135,346 +1135,346 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 2.6905e6) { - if (price < 2.2254e6) { - if (price < 2.046e6) { - if (price < 1.9328e6) { - if (price < 1.9064e6) { + if (price < 1.34048e6) { + if (price < 1.277347e6) { + if (price < 1.2436e6) { + if (price < 1.224339e6) { + if (price < 1.220705e6) { return PriceData( - 1.9328e6, - 0e18, - 307_356_867_725_023_611e18, - 1.8687e6, - 0e18, - 323_533_544_973_709_067e18, + 1.224339e6, + 0.0e18, + 1.775844690297405881e18, + 1.214961e6, + 0.0e18, + 1.741024206173927169e18, 1e18 ); } else { return PriceData( - 1.9328e6, - 0e18, - 307_356_867_725_023_611e18, - 1.9064e6, - 0e18, - 313_810_596_089_999_996e18, + 1.224339e6, + 0.0e18, + 1.775844690297405881e18, + 1.220705e6, + 0.0e18, + 1.762341683200000064e18, 1e18 ); } } else { - if (price < 2.0003e6) { + if (price < 1.233883e6) { return PriceData( - 2.046e6, - 0e18, - 0.282429536481000023e18, - 1.9328e6, - 0e18, - 0.307356867725023611e18, + 1.2436e6, + 0.0e18, + 1.847588815785421001e18, + 1.224339e6, + 0.0e18, + 1.775844690297405881e18, 1e18 ); } else { return PriceData( - 2.046e6, - 0e18, - 282_429_536_481_000_023e18, - 2.0003e6, - 0e18, - 291_989_024_338_772_393e18, + 1.2436e6, + 0.0e18, + 1.847588815785421001e18, + 1.233883e6, + 0.0e18, + 1.811361584103353684e18, 1e18 ); } } } else { - if (price < 2.1464e6) { - if (price < 2.0714e6) { + if (price < 1.263572e6) { + if (price < 1.253494e6) { return PriceData( - 2.1464e6, - 0e18, - 263_520_094_465_742_052e18, - 2.046e6, - 0e18, - 282_429_536_481_000_023e18, + 1.263572e6, + 0.0e18, + 1.92223140394315184e18, + 1.2436e6, + 0.0e18, + 1.847588815785421001e18, 1e18 ); } else { return PriceData( - 2.1464e6, - 0e18, - 263_520_094_465_742_052e18, - 2.0714e6, - 0e18, - 277_389_573_121_833_787e18, + 1.263572e6, + 0.0e18, + 1.92223140394315184e18, + 1.253494e6, + 0.0e18, + 1.884540592101129342e18, 1e18 ); } } else { - if (price < 2.2015e6) { + if (price < 1.273838e6) { return PriceData( - 2.2254e6, - 0e18, - 250_344_089_742_454_930e18, - 2.1464e6, - 0e18, - 263_520_094_465_742_052e18, + 1.277347e6, + 0.0e18, + 1.973822685183999948e18, + 1.263572e6, + 0.0e18, + 1.92223140394315184e18, 1e18 ); } else { return PriceData( - 2.2254e6, - 0e18, - 250_344_089_742_454_930e18, - 2.2015e6, - 0e18, - 254_186_582_832_900_011e18, + 1.277347e6, + 0.0e18, + 1.973822685183999948e18, + 1.273838e6, + 0.0e18, + 1.960676032022014903e18, 1e18 ); } } } } else { - if (price < 2.4893e6) { - if (price < 2.3748e6) { - if (price < 2.3087e6) { + if (price < 1.316927e6) { + if (price < 1.294966e6) { + if (price < 1.284301e6) { return PriceData( - 2.3748e6, - 0e18, - 228_767_924_549_610_027e18, - 2.2254e6, - 0e18, - 250_344_089_742_454_930e18, + 1.294966e6, + 0.0e18, + 2.039887343715704127e18, + 1.277347e6, + 0.0e18, + 1.973822685183999948e18, 1e18 ); } else { return PriceData( - 2.3748e6, - 0e18, - 228_767_924_549_610_027e18, - 2.3087e6, - 0e18, - 237_826_885_255_332_165e18, + 1.294966e6, + 0.0e18, + 2.039887343715704127e18, + 1.284301e6, + 0.0e18, + 1.999889552662455161e18, 1e18 ); } } else { - if (price < 2.3966e6) { + if (price < 1.305839e6) { return PriceData( - 2.4893e6, - 0e18, - 214_638_763_942_937_242e18, - 2.3748e6, - 0e18, - 228_767_924_549_610_027e18, + 1.316927e6, + 0.0e18, + 2.122298792401818623e18, + 1.294966e6, + 0.0e18, + 2.039887343715704127e18, 1e18 ); } else { return PriceData( - 2.4893e6, - 0e18, - 214_638_763_942_937_242e18, - 2.3966e6, - 0e18, - 225_935_540_992_565_540e18, + 1.316927e6, + 0.0e18, + 2.122298792401818623e18, + 1.305839e6, + 0.0e18, + 2.080685090590018493e18, 1e18 ); } } } else { - if (price < 2.5872e6) { - if (price < 2.5683e6) { + if (price < 1.339776e6) { + if (price < 1.328238e6) { return PriceData( - 2.5872e6, - 0e18, - 203_906_825_745_790_394e18, - 2.4893e6, - 0e18, - 214_638_763_942_937_242e18, + 1.339776e6, + 0.0e18, + 2.208039663614852266e18, + 1.316927e6, + 0.0e18, + 2.122298792401818623e18, 1e18 ); } else { return PriceData( - 2.5872e6, - 0e18, - 203_906_825_745_790_394e18, - 2.5683e6, - 0e18, - 205_891_132_094_649_041e18, + 1.339776e6, + 0.0e18, + 2.208039663614852266e18, + 1.328238e6, + 0.0e18, + 2.164744768249855067e18, 1e18 ); } } else { return PriceData( - 2.6905e6, - 0e18, - 193_711_484_458_500_834e18, - 2.5872e6, - 0e18, - 203_906_825_745_790_394e18, + 1.34048e6, + 0.0e18, + 2.210681407406080101e18, + 1.339776e6, + 0.0e18, + 2.208039663614852266e18, 1e18 ); } } } } else { - if (price < 3.3001e6) { - if (price < 3.0261e6) { - if (price < 2.7995e6) { - if (price < 2.7845e6) { + if (price < 2.267916e6) { + if (price < 1.685433e6) { + if (price < 1.491386e6) { + if (price < 1.411358e6) { return PriceData( - 2.7995e6, - 0e18, - 184_025_910_235_575_779e18, - 2.6905e6, - 0e18, - 193_711_484_458_500_834e18, + 1.491386e6, + 0.0e18, + 2.773078757450186949e18, + 1.34048e6, + 0.0e18, + 2.210681407406080101e18, 1e18 ); } else { return PriceData( - 2.7995e6, - 0e18, - 184_025_910_235_575_779e18, - 2.7845e6, - 0e18, - 185_302_018_885_184_143e18, + 1.491386e6, + 0.0e18, + 2.773078757450186949e18, + 1.411358e6, + 0.0e18, + 2.475963176294809553e18, 1e18 ); } } else { - if (price < 2.9146e6) { + if (price < 1.58215e6) { return PriceData( - 3.0261e6, - 0e18, - 166_771_816_996_665_739e18, - 2.7995e6, - 0e18, - 184_025_910_235_575_779e18, + 1.685433e6, + 0.0e18, + 3.478549993345514402e18, + 1.491386e6, + 0.0e18, + 2.773078757450186949e18, 1e18 ); } else { return PriceData( - 3.0261e6, - 0e18, - 166_771_816_996_665_739e18, - 2.9146e6, - 0e18, - 174_824_614_723_796_969e18, + 1.685433e6, + 0.0e18, + 3.478549993345514402e18, + 1.58215e6, + 0.0e18, + 3.105848208344209382e18, 1e18 ); } } } else { - if (price < 3.1645e6) { - if (price < 3.0362e6) { + if (price < 1.937842e6) { + if (price < 1.803243e6) { return PriceData( - 3.1645e6, - 0e18, - 157_779_214_788_226_770e18, - 3.0261e6, - 0e18, - 166_771_816_996_665_739e18, + 1.937842e6, + 0.0e18, + 4.363493111652613443e18, + 1.685433e6, + 0.0e18, + 3.478549993345514402e18, 1e18 ); } else { return PriceData( - 3.1645e6, - 0e18, - 157_779_214_788_226_770e18, - 3.0362e6, - 0e18, - 166_083_383_987_607_124e18, + 1.937842e6, + 0.0e18, + 4.363493111652613443e18, + 1.803243e6, + 0.0e18, + 3.89597599254697613e18, 1e18 ); } } else { - if (price < 3.2964e6) { + if (price < 2.091777e6) { return PriceData( - 3.3001e6, - 0e18, - 149_890_254_048_815_411e18, - 3.1645e6, - 0e18, - 157_779_214_788_226_770e18, + 2.267916e6, + 0.0e18, + 5.473565759257038366e18, + 1.937842e6, + 0.0e18, + 4.363493111652613443e18, 1e18 ); } else { return PriceData( - 3.3001e6, - 0e18, - 149_890_254_048_815_411e18, - 3.2964e6, - 0e18, - 150_094_635_296_999_155e18, + 2.267916e6, + 0.0e18, + 5.473565759257038366e18, + 2.091777e6, + 0.0e18, + 4.887112285050926097e18, 1e18 ); } } } } else { - if (price < 3.7541e6) { - if (price < 3.5945e6) { - if (price < 3.4433e6) { + if (price < 3.265419e6) { + if (price < 2.700115e6) { + if (price < 2.469485e6) { return PriceData( - 3.5945e6, - 0e18, - 135_275_954_279_055_877e18, - 3.3001e6, - 0e18, - 149_890_254_048_815_411e18, + 2.700115e6, + 0.0e18, + 6.866040888412029197e18, + 2.267916e6, + 0.0e18, + 5.473565759257038366e18, 1e18 ); } else { return PriceData( - 3.5945e6, - 0e18, - 135_275_954_279_055_877e18, - 3.4433e6, - 0e18, - 142_395_741_346_374_623e18, + 2.700115e6, + 0.0e18, + 6.866040888412029197e18, + 2.469485e6, + 0.0e18, + 6.130393650367882863e18, 1e18 ); } } else { - if (price < 3.5987e6) { + if (price < 2.963895e6) { return PriceData( - 3.7541e6, - 0e18, - 128_512_156_565_103_091e18, - 3.5945e6, - 0e18, - 135_275_954_279_055_877e18, + 3.265419e6, + 0.0e18, + 8.612761690424049377e18, + 2.700115e6, + 0.0e18, + 6.866040888412029197e18, 1e18 ); } else { return PriceData( - 3.7541e6, - 0e18, - 128_512_156_565_103_091e18, - 3.5987e6, - 0e18, - 135_085_171_767_299_243e18, + 3.265419e6, + 0.0e18, + 8.612761690424049377e18, + 2.963895e6, + 0.0e18, + 7.689965795021471706e18, 1e18 ); } } } else { - if (price < 9.9223e6) { - if (price < 3.937e6) { + if (price < 10.370891e6) { + if (price < 3.60986e6) { return PriceData( - 9.9223e6, - 0e18, - 449_999_999_999_999_979e18, - 3.7541e6, - 0e18, - 128_512_156_565_103_091e18, + 10.370891e6, + 0.0e18, + 28.000000000000003553e18, + 3.265419e6, + 0.0e18, + 8.612761690424049377e18, 1e18 ); } else { return PriceData( - 9.9223e6, - 0e18, - 449_999_999_999_999_979e18, - 3.937e6, - 0e18, - 121_576_654_590_569_315e18, + 10.370891e6, + 0.0e18, + 28.000000000000003553e18, + 3.60986e6, + 0.0e18, + 9.646293093274934449e18, 1e18 ); } @@ -1495,184 +1495,184 @@ contract Stable2LUT1 is ILookupTable { * Used in `calcReserveAtRatioSwap` function in the stableswap well function. */ function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) { - if (price < 0.9837e6) { - if (price < 0.7657e6) { - if (price < 0.6853e6) { - if (price < 0.6456e6) { - if (price < 0.5892e6) { - if (price < 0.5536e6) { - if (price < 0.0104e6) { + if (price < 0.993344e6) { + if (price < 0.834426e6) { + if (price < 0.718073e6) { + if (price < 0.391201e6) { + if (price < 0.264147e6) { + if (price < 0.213318e6) { + if (price < 0.001083e6) { revert("LUT: Invalid price"); } else { return PriceData( - 0.5536e6, - 159_999_999_999_999_986e18, - 545_614_743_365_999_923e18, - 0.0104e6, - 627_199_835_701_845_745e18, - 301_704_303_299_995_369e18, + 0.213318e6, + 0.188693329162796575e18, + 2.410556040105746423e18, + 0.001083e6, + 0.005263157894736842e18, + 10.522774272309483479e18, 1e18 ); } } else { - if (price < 0.5712e6) { + if (price < 0.237671e6) { return PriceData( - 0.5892e6, - 154_000_000_000_000_009e18, - 579_889_286_297_999_932e18, - 0.5536e6, - 159_999_999_999_999_986e18, - 545_614_743_365_999_923e18, + 0.264147e6, + 0.222936352980619729e18, + 2.26657220303422724e18, + 0.213318e6, + 0.188693329162796575e18, + 2.410556040105746423e18, 1e18 ); } else { return PriceData( - 0.5892e6, - 154_000_000_000_000_009e18, - 579_889_286_297_999_932e18, - 0.5712e6, - 156_999_999_999_999_997e18, - 562_484_780_749_999_921e18, + 0.264147e6, + 0.222936352980619729e18, + 2.26657220303422724e18, + 0.237671e6, + 0.20510144474217018e18, + 2.337718072004858261e18, 1e18 ); } } } else { - if (price < 0.6264e6) { - if (price < 0.6076e6) { + if (price < 0.323531e6) { + if (price < 0.292771e6) { return PriceData( - 0.6264e6, - 148_000_000_000_000_005e18, - 616_348_759_373_999_889e18, - 0.5892e6, - 154_000_000_000_000_009e18, - 579_889_286_297_999_932e18, + 0.323531e6, + 0.263393611744588529e18, + 2.128468246736633152e18, + 0.264147e6, + 0.222936352980619729e18, + 2.26657220303422724e18, 1e18 ); } else { return PriceData( - 0.6264e6, - 148_000_000_000_000_005e18, - 616_348_759_373_999_889e18, - 0.6076e6, - 150_999_999_999_999_993e18, - 597_839_994_250_999_858e18, + 0.323531e6, + 0.263393611744588529e18, + 2.128468246736633152e18, + 0.292771e6, + 0.242322122805021467e18, + 2.196897480682568293e18, 1e18 ); } } else { - if (price < 0.6418e6) { + if (price < 0.356373e6) { return PriceData( - 0.6456e6, - 144_999_999_999_999_989e18, - 635_427_613_953_999_981e18, - 0.6264e6, - 148_000_000_000_000_005e18, - 616_348_759_373_999_889e18, + 0.391201e6, + 0.311192830511092366e18, + 1.994416599735895801e18, + 0.323531e6, + 0.263393611744588529e18, + 2.128468246736633152e18, 1e18 ); } else { return PriceData( - 0.6456e6, - 144_999_999_999_999_989e18, - 635_427_613_953_999_981e18, - 0.6418e6, - 145_600_999_296_581_034e18, - 631_633_461_631_000_017e18, + 0.391201e6, + 0.311192830511092366e18, + 1.994416599735895801e18, + 0.356373e6, + 0.286297404070204931e18, + 2.061053544007124483e18, 1e18 ); } } } } else { - if (price < 0.6652e6) { - if (price < 0.6548e6) { - if (price < 0.6482e6) { + if (price < 0.546918e6) { + if (price < 0.466197e6) { + if (price < 0.427871e6) { return PriceData( - 0.6548e6, - 143_600_999_296_581_041e18, - 644_598_199_151_000_104e18, - 0.6456e6, - 144_999_999_999_999_989e18, - 635_427_613_953_999_981e18, + 0.466197e6, + 0.367666387654882243e18, + 1.86249753363281334e18, + 0.391201e6, + 0.311192830511092366e18, + 1.994416599735895801e18, 1e18 ); } else { return PriceData( - 0.6548e6, - 143_600_999_296_581_041e18, - 644_598_199_151_000_104e18, - 0.6482e6, - 144_600_999_296_581_037e18, - 638_083_385_911_000_032e18, + 0.466197e6, + 0.367666387654882243e18, + 1.86249753363281334e18, + 0.427871e6, + 0.338253076642491657e18, + 1.92831441898410505e18, 1e18 ); } } else { - if (price < 0.6613e6) { + if (price < 0.50596e6) { return PriceData( - 0.6652e6, - 142_000_000_000_000_000e18, - 655_088_837_188_999_989e18, - 0.6548e6, - 143_600_999_296_581_041e18, - 644_598_199_151_000_104e18, + 0.546918e6, + 0.434388454223632148e18, + 1.73068952191306602e18, + 0.466197e6, + 0.367666387654882243e18, + 1.86249753363281334e18, 1e18 ); } else { return PriceData( - 0.6652e6, - 142_000_000_000_000_000e18, - 655_088_837_188_999_989e18, - 0.6613e6, - 142_600_999_296_581_045e18, - 651_178_365_231_000_060e18, + 0.546918e6, + 0.434388454223632148e18, + 1.73068952191306602e18, + 0.50596e6, + 0.399637377885741607e18, + 1.796709969924970451e18, 1e18 ); } } } else { - if (price < 0.6746e6) { - if (price < 0.6679e6) { + if (price < 0.631434e6) { + if (price < 0.588821e6) { return PriceData( - 0.6746e6, - 140_600_999_296_581_052e18, - 664_536_634_765_000_046e18, - 0.6652e6, - 142_000_000_000_000_000e18, - 655_088_837_188_999_989e18, + 0.631434e6, + 0.513218873137561538e18, + 1.596874796852916001e18, + 0.546918e6, + 0.434388454223632148e18, + 1.73068952191306602e18, 1e18 ); } else { return PriceData( - 0.6746e6, - 140_600_999_296_581_052e18, - 664_536_634_765_000_046e18, - 0.6679e6, - 141_600_999_296_581_049e18, - 657_824_352_616_000_123e18, + 0.631434e6, + 0.513218873137561538e18, + 1.596874796852916001e18, + 0.588821e6, + 0.472161363286556723e18, + 1.664168452923131536e18, 1e18 ); } } else { - if (price < 0.6813e6) { + if (price < 0.67456e6) { return PriceData( - 0.6853e6, - 139_000_000_000_000_012e18, - 675_345_038_118_000_013e18, - 0.6746e6, - 140_600_999_296_581_052e18, - 664_536_634_765_000_046e18, + 0.718073e6, + 0.606355001344e18, + 1.458874768183093584e18, + 0.631434e6, + 0.513218873137561538e18, + 1.596874796852916001e18, 1e18 ); } else { return PriceData( - 0.6853e6, - 139_000_000_000_000_012e18, - 675_345_038_118_000_013e18, - 0.6813e6, - 139_600_999_296_581_056e18, - 671_315_690_563_000_076e18, + 0.718073e6, + 0.606355001344e18, + 1.458874768183093584e18, + 0.67456e6, + 0.55784660123648e18, + 1.52853450260679824e18, 1e18 ); } @@ -1680,178 +1680,178 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.7267e6) { - if (price < 0.7058e6) { - if (price < 0.6948e6) { - if (price < 0.688e6) { + if (price < 0.801931e6) { + if (price < 0.780497e6) { + if (price < 0.769833e6) { + if (price < 0.76195e6) { return PriceData( - 0.6948e6, - 137_600_999_296_581_037e18, - 685_076_068_526_999_994e18, - 0.6853e6, - 139_000_000_000_000_012e18, - 675_345_038_118_000_013e18, + 0.769833e6, + 0.668971758569680497e18, + 1.37471571145172633e18, + 0.718073e6, + 0.606355001344e18, + 1.458874768183093584e18, 1e18 ); } else { return PriceData( - 0.6948e6, - 137_600_999_296_581_037e18, - 685_076_068_526_999_994e18, - 0.688e6, - 138_600_999_296_581_033e18, - 678_162_004_775_999_991e18, + 0.769833e6, + 0.668971758569680497e18, + 1.37471571145172633e18, + 0.76195e6, + 0.659081523200000019e18, + 1.387629060213009469e18, 1e18 ); } } else { - if (price < 0.7017e6) { + if (price < 0.775161e6) { return PriceData( - 0.7058e6, - 135_999_999_999_999_996e18, - 696_209_253_362_999_968e18, - 0.6948e6, - 137_600_999_296_581_037e18, - 685_076_068_526_999_994e18, + 0.780497e6, + 0.682554595010387288e18, + 1.357193251389227306e18, + 0.769833e6, + 0.668971758569680497e18, + 1.37471571145172633e18, 1e18 ); } else { return PriceData( - 0.7058e6, - 135_999_999_999_999_996e18, - 696_209_253_362_999_968e18, - 0.7017e6, - 136_600_999_296_581_041e18, - 692_058_379_796_999_917e18, + 0.780497e6, + 0.682554595010387288e18, + 1.357193251389227306e18, + 0.775161e6, + 0.675729049060283415e18, + 1.365968375000512491e18, 1e18 ); } } } else { - if (price < 0.7155e6) { - if (price < 0.7086e6) { + if (price < 0.791195e6) { + if (price < 0.785842e6) { return PriceData( - 0.7155e6, - 134_600_999_296_581_048e18, - 706_229_774_288_999_996e18, - 0.7058e6, - 135_999_999_999_999_996e18, - 696_209_253_362_999_968e18, + 0.791195e6, + 0.696413218049573679e18, + 1.339558007037547016e18, + 0.780497e6, + 0.682554595010387288e18, + 1.357193251389227306e18, 1e18 ); } else { return PriceData( - 0.7155e6, - 134_600_999_296_581_048e18, - 706_229_774_288_999_996e18, - 0.7086e6, - 135_600_999_296_581_045e18, - 699_109_443_950_999_980e18, + 0.791195e6, + 0.696413218049573679e18, + 1.339558007037547016e18, + 0.785842e6, + 0.689449085869078049e18, + 1.34838993014876074e18, 1e18 ); } } else { - if (price < 0.7225e6) { + if (price < 0.796558e6) { return PriceData( - 0.7267e6, - 133_000_000_000_000_007e18, - 717_695_061_066_999_981e18, - 0.7155e6, - 134_600_999_296_581_048e18, - 706_229_774_288_999_996e18, + 0.801931e6, + 0.710553227272292309e18, + 1.321806771708554873e18, + 0.791195e6, + 0.696413218049573679e18, + 1.339558007037547016e18, 1e18 ); } else { return PriceData( - 0.7267e6, - 133_000_000_000_000_007e18, - 717_695_061_066_999_981e18, - 0.7225e6, - 133_600_999_296_581_052e18, - 713_419_892_621_999_983e18, + 0.801931e6, + 0.710553227272292309e18, + 1.321806771708554873e18, + 0.796558e6, + 0.703447694999569495e18, + 1.330697084427678423e18, 1e18 ); } } } } else { - if (price < 0.7482e6) { - if (price < 0.7367e6) { - if (price < 0.7296e6) { + if (price < 0.818119e6) { + if (price < 0.807315e6) { + if (price < 0.806314e6) { return PriceData( - 0.7367e6, - 131_600_999_296_581_033e18, - 728_011_626_732_999_961e18, - 0.7267e6, - 133_000_000_000_000_007e18, - 717_695_061_066_999_981e18, + 0.807315e6, + 0.717730532598275128e18, + 1.312886685708826162e18, + 0.801931e6, + 0.710553227272292309e18, + 1.321806771708554873e18, 1e18 ); } else { return PriceData( - 0.7367e6, - 131_600_999_296_581_033e18, - 728_011_626_732_999_961e18, - 0.7296e6, - 132_600_999_296_581_056e18, - 720_680_329_877_999_918e18, + 0.807315e6, + 0.717730532598275128e18, + 1.312886685708826162e18, + 0.806314e6, + 0.716392959999999968e18, + 1.314544530202049311e18, 1e18 ); } } else { - if (price < 0.7439e6) { + if (price < 0.812711e6) { return PriceData( - 0.7482e6, - 129_999_999_999_999_992e18, - 739_816_712_606_999_965e18, - 0.7367e6, - 131_600_999_296_581_033e18, - 728_011_626_732_999_961e18, + 0.818119e6, + 0.732303369654397684e18, + 1.294955701044462559e18, + 0.807315e6, + 0.717730532598275128e18, + 1.312886685708826162e18, 1e18 ); } else { return PriceData( - 0.7482e6, - 129_999_999_999_999_992e18, - 739_816_712_606_999_965e18, - 0.7439e6, - 130_600_999_296_581_037e18, - 735_414_334_273_999_920e18, + 0.818119e6, + 0.732303369654397684e18, + 1.294955701044462559e18, + 0.812711e6, + 0.724980335957853717e18, + 1.303936451137418295e18, 1e18 ); } } } else { - if (price < 0.7584e6) { - if (price < 0.7511e6) { + if (price < 0.828976e6) { + if (price < 0.82354e6) { return PriceData( - 0.7584e6, - 128_600_999_296_581_044e18, - 750_436_241_990_999_850e18, - 0.7482e6, - 129_999_999_999_999_992e18, - 739_816_712_606_999_965e18, + 0.828976e6, + 0.74717209433159637e18, + 1.276901231112211654e18, + 0.818119e6, + 0.732303369654397684e18, + 1.294955701044462559e18, 1e18 ); } else { return PriceData( - 0.7584e6, - 128_600_999_296_581_044e18, - 750_436_241_990_999_850e18, - 0.7511e6, - 129_600_999_296_581_040e18, - 742_889_014_688_999_846e18, + 0.828976e6, + 0.74717209433159637e18, + 1.276901231112211654e18, + 0.82354e6, + 0.73970037338828043e18, + 1.285944077302980215e18, 1e18 ); } } else { return PriceData( - 0.7657e6, - 127_600_999_296_581_048e18, - 758_056_602_771_999_862e18, - 0.7584e6, - 128_600_999_296_581_044e18, - 750_436_241_990_999_850e18, + 0.834426e6, + 0.754719287203632794e18, + 1.267826823523503732e18, + 0.828976e6, + 0.74717209433159637e18, + 1.276901231112211654e18, 1e18 ); } @@ -1859,369 +1859,357 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 0.8591e6) { - if (price < 0.8111e6) { - if (price < 0.7881e6) { - if (price < 0.7731e6) { - if (price < 0.7701e6) { + if (price < 0.907266e6) { + if (price < 0.873109e6) { + if (price < 0.851493e6) { + if (price < 0.845379e6) { + if (price < 0.839894e6) { return PriceData( - 0.7731e6, - 126_600_999_296_581_052e18, - 765_750_696_993_999_871e18, - 0.7657e6, - 127_600_999_296_581_048e18, - 758_056_602_771_999_862e18, + 0.845379e6, + 0.770043145805155316e18, + 1.249582020939133509e18, + 0.834426e6, + 0.754719287203632794e18, + 1.267826823523503732e18, 1e18 ); } else { return PriceData( - 0.7731e6, - 126_600_999_296_581_052e18, - 765_750_696_993_999_871e18, - 0.7701e6, - 127_000_000_000_000_003e18, - 762_589_283_900_000_049e18, + 0.845379e6, + 0.770043145805155316e18, + 1.249582020939133509e18, + 0.839894e6, + 0.762342714347103767e18, + 1.258720525989716954e18, 1e18 ); } } else { - if (price < 0.7806e6) { + if (price < 0.850882e6) { return PriceData( - 0.7881e6, - 124_600_999_296_581_059e18, - 781_362_557_425_999_864e18, - 0.7731e6, - 126_600_999_296_581_052e18, - 765_750_696_993_999_871e18, + 0.851493e6, + 0.778688000000000047e18, + 1.239392846883276889e18, + 0.845379e6, + 0.770043145805155316e18, + 1.249582020939133509e18, 1e18 ); } else { return PriceData( - 0.7881e6, - 124_600_999_296_581_059e18, - 781_362_557_425_999_864e18, - 0.7806e6, - 125_600_999_296_581_055e18, - 773_519_138_809_999_964e18, + 0.851493e6, + 0.778688000000000047e18, + 1.239392846883276889e18, + 0.850882e6, + 0.777821359399146761e18, + 1.240411002374896432e18, 1e18 ); } } } else { - if (price < 0.7957e6) { - if (price < 0.7927e6) { + if (price < 0.86195e6) { + if (price < 0.856405e6) { return PriceData( - 0.7957e6, - 123_600_999_296_581_036e18, - 789_281_597_997_999_894e18, - 0.7881e6, - 124_600_999_296_581_059e18, - 781_362_557_425_999_864e18, + 0.86195e6, + 0.793614283643655494e18, + 1.221970262376178118e18, + 0.851493e6, + 0.778688000000000047e18, + 1.239392846883276889e18, 1e18 ); } else { return PriceData( - 0.7957e6, - 123_600_999_296_581_036e18, - 789_281_597_997_999_894e18, - 0.7927e6, - 123_999_999_999_999_988e18, - 786_028_848_437_000_042e18, + 0.86195e6, + 0.793614283643655494e18, + 1.221970262376178118e18, + 0.856405e6, + 0.785678140807218983e18, + 1.231207176501035727e18, 1e18 ); } } else { - if (price < 0.8034e6) { + if (price < 0.867517e6) { return PriceData( - 0.8111e6, - 121_600_999_296_581_044e18, - 805_349_211_052_999_895e18, - 0.7957e6, - 123_600_999_296_581_036e18, - 789_281_597_997_999_894e18, + 0.873109e6, + 0.809727868221258529e18, + 1.203396114006087814e18, + 0.86195e6, + 0.793614283643655494e18, + 1.221970262376178118e18, 1e18 ); } else { return PriceData( - 0.8111e6, - 121_600_999_296_581_044e18, - 805_349_211_052_999_895e18, - 0.8034e6, - 122_600_999_296_581_040e18, - 797_276_922_569_999_826e18, + 0.873109e6, + 0.809727868221258529e18, + 1.203396114006087814e18, + 0.867517e6, + 0.801630589539045979e18, + 1.212699992596070864e18, 1e18 ); } } } } else { - if (price < 0.8348e6) { - if (price < 0.8189e6) { - if (price < 0.8158e6) { + if (price < 0.895753e6) { + if (price < 0.884372e6) { + if (price < 0.878727e6) { return PriceData( - 0.8189e6, - 120_600_999_296_581_047e18, - 813_499_162_245_999_760e18, - 0.8111e6, - 121_600_999_296_581_044e18, - 805_349_211_052_999_895e18, + 0.884372e6, + 0.826168623835586646e18, + 1.18468659352065786e18, + 0.873109e6, + 0.809727868221258529e18, + 1.203396114006087814e18, 1e18 ); } else { return PriceData( - 0.8189e6, - 120_600_999_296_581_047e18, - 813_499_162_245_999_760e18, - 0.8158e6, - 120_999_999_999_999_999e18, - 810_152_674_566_000_114e18, + 0.884372e6, + 0.826168623835586646e18, + 1.18468659352065786e18, + 0.878727e6, + 0.817906937597230987e18, + 1.194058388444914964e18, 1e18 ); } } else { - if (price < 0.8268e6) { + if (price < 0.890047e6) { return PriceData( - 0.8348e6, - 118_600_999_296_581_042e18, - 830_034_948_846_999_811e18, - 0.8189e6, - 120_600_999_296_581_047e18, - 813_499_162_245_999_760e18, + 0.895753e6, + 0.84294319338392687e18, + 1.16583998975613734e18, + 0.884372e6, + 0.826168623835586646e18, + 1.18468659352065786e18, 1e18 ); } else { return PriceData( - 0.8348e6, - 118_600_999_296_581_042e18, - 830_034_948_846_999_811e18, - 0.8268e6, - 119_600_999_296_581_051e18, - 821_727_494_902_999_775e18, + 0.895753e6, + 0.84294319338392687e18, + 1.16583998975613734e18, + 0.890047e6, + 0.834513761450087599e18, + 1.17528052342063094e18, 1e18 ); } } } else { - if (price < 0.8428e6) { - if (price < 0.8395e6) { + if (price < 0.901491e6) { + if (price < 0.898085e6) { return PriceData( - 0.8428e6, - 117_600_999_296_581_045e18, - 838_422_286_131_999_841e18, - 0.8348e6, - 118_600_999_296_581_042e18, - 830_034_948_846_999_811e18, + 0.901491e6, + 0.851457771094875637e18, + 1.156364822443562979e18, + 0.895753e6, + 0.84294319338392687e18, + 1.16583998975613734e18, 1e18 ); } else { return PriceData( - 0.8428e6, - 117_600_999_296_581_045e18, - 838_422_286_131_999_841e18, - 0.8395e6, - 117_999_999_999_999_997e18, - 834_979_450_092_000_118e18, + 0.901491e6, + 0.851457771094875637e18, + 1.156364822443562979e18, + 0.898085e6, + 0.846400000000000041e18, + 1.161985895520041945e18, 1e18 ); } } else { - if (price < 0.8509e6) { - return PriceData( - 0.8591e6, - 115_600_999_296_581_039e18, - 855_439_777_442_999_782e18, - 0.8428e6, - 117_600_999_296_581_045e18, - 838_422_286_131_999_841e18, - 1e18 - ); - } else { - return PriceData( - 0.8591e6, - 115_600_999_296_581_039e18, - 855_439_777_442_999_782e18, - 0.8509e6, - 116_600_999_296_581_049e18, - 846_890_292_257_999_745e18, - 1e18 - ); - } + return PriceData( + 0.907266e6, + 0.860058354641288547e18, + 1.146854870623147615e18, + 0.901491e6, + 0.851457771094875637e18, + 1.156364822443562979e18, + 1e18 + ); } } } } else { - if (price < 0.9101e6) { - if (price < 0.8842e6) { - if (price < 0.8674e6) { - if (price < 0.864e6) { + if (price < 0.948888e6) { + if (price < 0.930767e6) { + if (price < 0.918932e6) { + if (price < 0.913079e6) { return PriceData( - 0.8674e6, - 114_600_999_296_581_043e18, - 864_071_577_944_999_841e18, - 0.8591e6, - 115_600_999_296_581_039e18, - 855_439_777_442_999_782e18, + 0.918932e6, + 0.877521022998967948e18, + 1.127730111926438461e18, + 0.907266e6, + 0.860058354641288547e18, + 1.146854870623147615e18, 1e18 ); } else { return PriceData( - 0.8674e6, - 114_600_999_296_581_043e18, - 864_071_577_944_999_841e18, - 0.864e6, - 114_999_999_999_999_995e18, - 860_529_537_869_000_136e18, + 0.918932e6, + 0.877521022998967948e18, + 1.127730111926438461e18, + 0.913079e6, + 0.868745812768978332e18, + 1.137310003616810228e18, 1e18 ); } } else { - if (price < 0.8757e6) { + if (price < 0.924827e6) { return PriceData( - 0.8842e6, - 112_600_999_296_581_051e18, - 881_585_608_519_999_884e18, - 0.8674e6, - 114_600_999_296_581_043e18, - 864_071_577_944_999_841e18, + 0.930767e6, + 0.895338254258716493e18, + 1.10846492868530544e18, + 0.918932e6, + 0.877521022998967948e18, + 1.127730111926438461e18, 1e18 ); } else { return PriceData( - 0.8842e6, - 112_600_999_296_581_051e18, - 881_585_608_519_999_884e18, - 0.8757e6, - 113_600_999_296_581_047e18, - 872_786_557_449_999_864e18, + 0.930767e6, + 0.895338254258716493e18, + 1.10846492868530544e18, + 0.924827e6, + 0.88638487171612923e18, + 1.118115108274055913e18, 1e18 ); } } } else { - if (price < 0.8927e6) { - if (price < 0.8893e6) { + if (price < 0.942795e6) { + if (price < 0.936756e6) { return PriceData( - 0.8927e6, - 111_600_999_296_581_041e18, - 890_469_654_111_999_903e18, - 0.8842e6, - 112_600_999_296_581_051e18, - 881_585_608_519_999_884e18, + 0.942795e6, + 0.913517247483640937e18, + 1.089058909134983155e18, + 0.930767e6, + 0.895338254258716493e18, + 1.10846492868530544e18, 1e18 ); } else { return PriceData( - 0.8927e6, - 111_600_999_296_581_041e18, - 890_469_654_111_999_903e18, - 0.8893e6, - 112_000_000_000_000_006e18, - 886_825_266_904_000_064e18, + 0.942795e6, + 0.913517247483640937e18, + 1.089058909134983155e18, + 0.936756e6, + 0.90438207500880452e18, + 1.09877953361768621e18, 1e18 ); } } else { - if (price < 0.9014e6) { + if (price < 0.947076e6) { return PriceData( - 0.9101e6, - 109_600_999_296_581_049e18, - 908_496_582_238_999_955e18, - 0.8927e6, - 111_600_999_296_581_041e18, - 890_469_654_111_999_903e18, + 0.948888e6, + 0.922744694427920065e18, + 1.079303068129318754e18, + 0.942795e6, + 0.913517247483640937e18, + 1.089058909134983155e18, 1e18 ); } else { return PriceData( - 0.9101e6, - 109_600_999_296_581_049e18, - 908_496_582_238_999_955e18, - 0.9014e6, - 110_600_999_296_581_045e18, - 899_439_649_160_999_875e18, + 0.948888e6, + 0.922744694427920065e18, + 1.079303068129318754e18, + 0.947076e6, + 0.92000000000000004e18, + 1.082198372170484424e18, 1e18 ); } } } } else { - if (price < 0.937e6) { - if (price < 0.919e6) { - if (price < 0.9154e6) { + if (price < 0.973868e6) { + if (price < 0.961249e6) { + if (price < 0.955039e6) { return PriceData( - 0.919e6, - 108_600_999_296_581_052e18, - 917_641_477_296_999_901e18, - 0.9101e6, - 109_600_999_296_581_049e18, - 908_496_582_238_999_955e18, + 0.961249e6, + 0.941480149400999999e18, + 1.059685929936267312e18, + 0.948888e6, + 0.922744694427920065e18, + 1.079303068129318754e18, 1e18 ); } else { return PriceData( - 0.919e6, - 108_600_999_296_581_052e18, - 917_641_477_296_999_901e18, - 0.9154e6, - 109_000_000_000_000_004e18, - 913_891_264_499_999_987e18, + 0.961249e6, + 0.941480149400999999e18, + 1.059685929936267312e18, + 0.955039e6, + 0.932065347906990027e18, + 1.069512051592246715e18, 1e18 ); } } else { - if (price < 0.9279e6) { + if (price < 0.967525e6) { return PriceData( - 0.937e6, - 106_600_999_296_581_047e18, - 936_199_437_051_999_863e18, - 0.919e6, - 108_600_999_296_581_052e18, - 917_641_477_296_999_901e18, + 0.973868e6, + 0.960596010000000056e18, + 1.039928808315135234e18, + 0.961249e6, + 0.941480149400999999e18, + 1.059685929936267312e18, 1e18 ); } else { return PriceData( - 0.937e6, - 106_600_999_296_581_047e18, - 936_199_437_051_999_863e18, - 0.9279e6, - 107_600_999_296_581_043e18, - 926_875_395_482_999_811e18, + 0.973868e6, + 0.960596010000000056e18, + 1.039928808315135234e18, + 0.967525e6, + 0.950990049900000023e18, + 1.049824804368118425e18, 1e18 ); } } } else { - if (price < 0.9706e6) { - if (price < 0.9425e6) { + if (price < 0.986773e6) { + if (price < 0.980283e6) { return PriceData( - 0.9706e6, - 103_000_000_000_000_000e18, - 970_446_402_236_000_094e18, - 0.937e6, - 106_600_999_296_581_047e18, - 936_199_437_051_999_863e18, + 0.986773e6, + 0.980099999999999971e18, + 1.020032908506394831e18, + 0.973868e6, + 0.960596010000000056e18, + 1.039928808315135234e18, 1e18 ); } else { return PriceData( - 0.9706e6, - 103_000_000_000_000_000e18, - 970_446_402_236_000_094e18, - 0.9425e6, - 106_000_000_000_000_002e18, - 941_754_836_243_000_007e18, + 0.986773e6, + 0.980099999999999971e18, + 1.020032908506394831e18, + 0.980283e6, + 0.970299000000000134e18, + 1.029998108905910481e18, 1e18 ); } } else { return PriceData( - 0.9837e6, - 101_657_469_802_110_314e18, - 983_823_407_046_999_249e18, - 0.9706e6, - 103_000_000_000_000_000e18, - 970_446_402_236_000_094e18, + 0.993344e6, + 0.989999999999999991e18, + 1.01003344631248293e18, + 0.986773e6, + 0.980099999999999971e18, + 1.020032908506394831e18, 1e18 ); } @@ -2230,370 +2218,358 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.2781e6) { - if (price < 1.1479e6) { - if (price < 1.0834e6) { - if (price < 1.0522e6) { - if (price < 1.042e6) { - if (price < 1.0137e6) { + if (price < 1.211166e6) { + if (price < 1.09577e6) { + if (price < 1.048893e6) { + if (price < 1.027293e6) { + if (price < 1.01345e6) { + if (price < 1.006679e6) { return PriceData( - 1.042e6, - 959_393_930_406_635_507e18, - 104_163_346_163_100_008e18, - 0.9837e6, - 101_657_469_802_110_314e18, - 983_823_407_046_999_249e18, + 1.01345e6, + 1.020100000000000007e18, + 0.980033797419900599e18, + 0.993344e6, + 0.989999999999999991e18, + 1.01003344631248293e18, 1e18 ); } else { return PriceData( - 1.042e6, - 959_393_930_406_635_507e18, - 104_163_346_163_100_008e18, - 1.0137e6, - 986_536_714_103_430_341e18, - 101_382_340_704_699_913e18, + 1.01345e6, + 1.020100000000000007e18, + 0.980033797419900599e18, + 1.006679e6, + 1.010000000000000009e18, + 0.990033224058159078e18, 1e18 ); } } else { - if (price < 1.0442e6) { + if (price < 1.020319e6) { return PriceData( - 1.0522e6, - 949_844_925_094_088_539e18, - 105_163_346_163_100_004e18, - 1.042e6, - 959_393_930_406_635_507e18, - 104_163_346_163_100_008e18, + 1.027293e6, + 1.040604010000000024e18, + 0.959938599971011053e18, + 1.01345e6, + 1.020100000000000007e18, + 0.980033797419900599e18, 1e18 ); } else { return PriceData( - 1.0522e6, - 949_844_925_094_088_539e18, - 105_163_346_163_100_004e18, - 1.0442e6, - 957_380_872_867_950_333e18, - 104_382_340_704_699_915e18, + 1.027293e6, + 1.040604010000000024e18, + 0.959938599971011053e18, + 1.020319e6, + 1.030300999999999911e18, + 0.970002111104709575e18, 1e18 ); } } } else { - if (price < 1.0729e6) { - if (price < 1.0626e6) { + if (price < 1.034375e6) { + if (price < 1.033686e6) { return PriceData( - 1.0729e6, - 931_024_660_370_625_864e18, - 107_163_346_163_100_010e18, - 1.0522e6, - 949_844_925_094_088_539e18, - 105_163_346_163_100_004e18, + 1.034375e6, + 1.051010050100000148e18, + 0.949843744564435544e18, + 1.027293e6, + 1.040604010000000024e18, + 0.959938599971011053e18, 1e18 ); } else { return PriceData( - 1.0729e6, - 931_024_660_370_625_864e18, - 107_163_346_163_100_010e18, - 1.0626e6, - 940_388_903_170_712_077e18, - 106_163_346_163_100_000e18, + 1.034375e6, + 1.051010050100000148e18, + 0.949843744564435544e18, + 1.033686e6, + 1.050000000000000044e18, + 0.950820553711780869e18, 1e18 ); } } else { - if (price < 1.0752e6) { + if (price < 1.041574e6) { return PriceData( - 1.0834e6, - 921_751_036_591_489_341e18, - 108_163_346_163_100_006e18, - 1.0729e6, - 931_024_660_370_625_864e18, - 107_163_346_163_100_010e18, + 1.048893e6, + 1.072135352107010053e18, + 0.929562163027227939e18, + 1.034375e6, + 1.051010050100000148e18, + 0.949843744564435544e18, 1e18 ); } else { return PriceData( - 1.0834e6, - 921_751_036_591_489_341e18, - 108_163_346_163_100_006e18, - 1.0752e6, - 929_070_940_571_911_522e18, - 107_382_340_704_699_931e18, + 1.048893e6, + 1.072135352107010053e18, + 0.929562163027227939e18, + 1.041574e6, + 1.061520150601000134e18, + 0.93971807302139454e18, 1e18 ); } } } } else { - if (price < 1.1153e6) { - if (price < 1.1046e6) { - if (price < 1.094e6) { + if (price < 1.071652e6) { + if (price < 1.063925e6) { + if (price < 1.056342e6) { return PriceData( - 1.1046e6, - 903_471_213_736_046_924e18, - 110_163_346_163_099_998e18, - 1.0834e6, - 921_751_036_591_489_341e18, - 108_163_346_163_100_006e18, + 1.063925e6, + 1.093685272684360887e18, + 0.90916219829307332e18, + 1.048893e6, + 1.072135352107010053e18, + 0.929562163027227939e18, 1e18 ); } else { return PriceData( - 1.1046e6, - 903_471_213_736_046_924e18, - 110_163_346_163_099_998e18, - 1.094e6, - 912_566_913_749_610_422e18, - 109_163_346_163_100_002e18, + 1.063925e6, + 1.093685272684360887e18, + 0.90916219829307332e18, + 1.056342e6, + 1.082856705628080007e18, + 0.919376643827810258e18, 1e18 ); } } else { - if (price < 1.1069e6) { + if (price < 1.070147e6) { return PriceData( - 1.1153e6, - 894_462_896_467_921_655e18, - 111_163_346_163_100_008e18, - 1.1046e6, - 903_471_213_736_046_924e18, - 110_163_346_163_099_998e18, + 1.071652e6, + 1.104622125411204525e18, + 0.89891956503043724e18, + 1.063925e6, + 1.093685272684360887e18, + 0.90916219829307332e18, 1e18 ); } else { return PriceData( - 1.1153e6, - 894_462_896_467_921_655e18, - 111_163_346_163_100_008e18, - 1.1069e6, - 901_574_605_299_420_214e18, - 110_382_340_704_699_933e18, + 1.071652e6, + 1.104622125411204525e18, + 0.89891956503043724e18, + 1.070147e6, + 1.102500000000000036e18, + 0.900901195775543062e18, 1e18 ); } } } else { - if (price < 1.137e6) { - if (price < 1.1261e6) { + if (price < 1.087566e6) { + if (price < 1.079529e6) { return PriceData( - 1.137e6, - 876_704_428_898_610_466e18, - 113_163_346_163_100_000e18, - 1.1153e6, - 894_462_896_467_921_655e18, - 111_163_346_163_100_008e18, + 1.087566e6, + 1.126825030131969774e18, + 0.878352981447521719e18, + 1.071652e6, + 1.104622125411204525e18, + 0.89891956503043724e18, 1e18 ); } else { return PriceData( - 1.137e6, - 876_704_428_898_610_466e18, - 113_163_346_163_100_000e18, - 1.1261e6, - 885_540_958_029_582_579e18, - 112_163_346_163_100_004e18, + 1.087566e6, + 1.126825030131969774e18, + 0.878352981447521719e18, + 1.079529e6, + 1.115668346665316557e18, + 0.888649540545595529e18, 1e18 ); } } else { - if (price < 1.1393e6) { - return PriceData( - 1.1479e6, - 867_952_372_252_030_623e18, - 114_163_346_163_100_010e18, - 1.137e6, - 876_704_428_898_610_466e18, - 113_163_346_163_100_000e18, - 1e18 - ); - } else { - return PriceData( - 1.1479e6, - 867_952_372_252_030_623e18, - 114_163_346_163_100_010e18, - 1.1393e6, - 874_862_932_837_460_311e18, - 113_382_340_704_699_935e18, - 1e18 - ); - } + return PriceData( + 1.09577e6, + 1.1380932804332895e18, + 0.868030806693890433e18, + 1.087566e6, + 1.126825030131969774e18, + 0.878352981447521719e18, + 1e18 + ); } } } } else { - if (price < 1.2158e6) { - if (price < 1.1814e6) { - if (price < 1.1701e6) { - if (price < 1.159e6) { + if (price < 1.15496e6) { + if (price < 1.121482e6) { + if (price < 1.110215e6) { + if (price < 1.104151e6) { return PriceData( - 1.1701e6, - 850_698_082_981_724_339e18, - 116_163_346_163_100_003e18, - 1.1479e6, - 867_952_372_252_030_623e18, - 114_163_346_163_100_010e18, + 1.110215e6, + 1.157625000000000126e18, + 0.850322213751246947e18, + 1.09577e6, + 1.1380932804332895e18, + 0.868030806693890433e18, 1e18 ); } else { return PriceData( - 1.1701e6, - 850_698_082_981_724_339e18, - 116_163_346_163_100_003e18, - 1.159e6, - 859_283_882_348_396_836e18, - 115_163_346_163_100_006e18, + 1.110215e6, + 1.157625000000000126e18, + 0.850322213751246947e18, + 1.104151e6, + 1.149474213237622333e18, + 0.857683999872391523e18, 1e18 ); } } else { - if (price < 1.1725e6) { + if (price < 1.112718e6) { return PriceData( - 1.1814e6, - 842_194_126_003_515_871e18, - 117_163_346_163_099_999e18, - 1.1701e6, - 850_698_082_981_724_339e18, - 116_163_346_163_100_003e18, + 1.121482e6, + 1.172578644923698565e18, + 0.836920761422192294e18, + 1.110215e6, + 1.157625000000000126e18, + 0.850322213751246947e18, 1e18 ); } else { return PriceData( - 1.1814e6, - 842_194_126_003_515_871e18, - 117_163_346_163_099_999e18, - 1.1725e6, - 848_909_895_863_161_958e18, - 116_382_340_704_699_937e18, + 1.121482e6, + 1.172578644923698565e18, + 0.836920761422192294e18, + 1.112718e6, + 1.160968955369998667e18, + 0.847313611512600207e18, 1e18 ); } } } else { - if (price < 1.2042e6) { - if (price < 1.1928e6) { + if (price < 1.139642e6) { + if (price < 1.130452e6) { return PriceData( - 1.2042e6, - 825_428_478_487_026_483e18, - 119_163_346_163_100_005e18, - 1.1814e6, - 842_194_126_003_515_871e18, - 117_163_346_163_099_999e18, + 1.139642e6, + 1.196147475686665018e18, + 0.8160725157999702e18, + 1.121482e6, + 1.172578644923698565e18, + 0.836920761422192294e18, 1e18 ); } else { return PriceData( - 1.2042e6, - 825_428_478_487_026_483e18, - 119_163_346_163_100_005e18, - 1.1928e6, - 833_771_189_909_386_858e18, - 118_163_346_163_100_008e18, + 1.139642e6, + 1.196147475686665018e18, + 0.8160725157999702e18, + 1.130452e6, + 1.184304431372935618e18, + 0.826506641040327228e18, 1e18 ); } } else { - if (price < 1.2067e6) { + if (price < 1.149062e6) { return PriceData( - 1.2158e6, - 817_165_219_522_460_124e18, - 120_163_346_163_100_001e18, - 1.2042e6, - 825_428_478_487_026_483e18, - 119_163_346_163_100_005e18, + 1.15496e6, + 1.21550625000000001e18, + 0.799198479643147719e18, + 1.139642e6, + 1.196147475686665018e18, + 0.8160725157999702e18, 1e18 ); } else { return PriceData( - 1.2158e6, - 817_165_219_522_460_124e18, - 120_163_346_163_100_001e18, - 1.2067e6, - 823_691_964_726_974_573e18, - 119_382_340_704_699_926e18, + 1.15496e6, + 1.21550625000000001e18, + 0.799198479643147719e18, + 1.149062e6, + 1.208108950443531393e18, + 0.805619727489791271e18, 1e18 ); } } } } else { - if (price < 1.2512e6) { - if (price < 1.2393e6) { - if (price < 1.2275e6) { + if (price < 1.189304e6) { + if (price < 1.168643e6) { + if (price < 1.158725e6) { return PriceData( - 1.2393e6, - 800_874_082_725_647_358e18, - 122_163_346_163_099_993e18, - 1.2158e6, - 817_165_219_522_460_124e18, - 120_163_346_163_100_001e18, + 1.168643e6, + 1.232391940347446369e18, + 0.784663924675502389e18, + 1.15496e6, + 1.21550625000000001e18, + 0.799198479643147719e18, 1e18 ); } else { return PriceData( - 1.2393e6, - 800_874_082_725_647_358e18, - 122_163_346_163_099_993e18, - 1.2275e6, - 808_980_663_561_772_026e18, - 121_163_346_163_099_997e18, + 1.168643e6, + 1.232391940347446369e18, + 0.784663924675502389e18, + 1.158725e6, + 1.22019003994796682e18, + 0.795149696605042422e18, 1e18 ); } } else { - if (price < 1.2419e6) { + if (price < 1.178832e6) { return PriceData( - 1.2512e6, - 792_844_769_574_258_384e18, - 123_163_346_163_100_016e18, - 1.2393e6, - 800_874_082_725_647_358e18, - 122_163_346_163_099_993e18, + 1.189304e6, + 1.257163018348430139e18, + 0.763651582672810969e18, + 1.168643e6, + 1.232391940347446369e18, + 0.784663924675502389e18, 1e18 ); } else { return PriceData( - 1.2512e6, - 792_844_769_574_258_384e18, - 123_163_346_163_100_016e18, - 1.2419e6, - 799_187_750_396_588_808e18, - 122_382_340_704_699_941e18, + 1.189304e6, + 1.257163018348430139e18, + 0.763651582672810969e18, + 1.178832e6, + 1.244715859750920917e18, + 0.774163996557160172e18, 1e18 ); } } } else { - if (price < 1.2755e6) { - if (price < 1.2633e6) { + if (price < 1.205768e6) { + if (price < 1.200076e6) { return PriceData( - 1.2755e6, - 777_015_212_287_252_263e18, - 125_163_346_163_100_009e18, - 1.2512e6, - 792_844_769_574_258_384e18, - 123_163_346_163_100_016e18, + 1.205768e6, + 1.276281562499999911e18, + 0.747685899578659385e18, + 1.189304e6, + 1.257163018348430139e18, + 0.763651582672810969e18, 1e18 ); } else { return PriceData( - 1.2755e6, - 777_015_212_287_252_263e18, - 125_163_346_163_100_009e18, - 1.2633e6, - 784_892_036_020_190_803e18, - 124_163_346_163_100_013e18, + 1.205768e6, + 1.276281562499999911e18, + 0.747685899578659385e18, + 1.200076e6, + 1.269734648531914534e18, + 0.753128441185147435e18, 1e18 ); } } else { return PriceData( - 1.2781e6, - 775_377_691_936_595_793e18, - 125_382_340_704_699_930e18, - 1.2755e6, - 777_015_212_287_252_263e18, - 125_163_346_163_100_009e18, + 1.211166e6, + 1.282431995017233595e18, + 0.74259642008426785e18, + 1.205768e6, + 1.276281562499999911e18, + 0.747685899578659385e18, 1e18 ); } @@ -2601,346 +2577,346 @@ contract Stable2LUT1 is ILookupTable { } } } else { - if (price < 1.4328e6) { - if (price < 1.3542e6) { - if (price < 1.3155e6) { - if (price < 1.3002e6) { - if (price < 1.2878e6) { + if (price < 1.393403e6) { + if (price < 1.299217e6) { + if (price < 1.259043e6) { + if (price < 1.234362e6) { + if (price < 1.222589e6) { return PriceData( - 1.3002e6, - 761_486_700_794_138_643e18, - 127_163_346_163_100_001e18, - 1.2781e6, - 775_377_691_936_595_793e18, - 125_382_340_704_699_930e18, + 1.234362e6, + 1.308208878117080198e18, + 0.721513591905860174e18, + 1.211166e6, + 1.282431995017233595e18, + 0.74259642008426785e18, 1e18 ); } else { return PriceData( - 1.3002e6, - 761_486_700_794_138_643e18, - 127_163_346_163_100_001e18, - 1.2878e6, - 769_213_645_913_148_350e18, - 126_163_346_163_100_005e18, + 1.234362e6, + 1.308208878117080198e18, + 0.721513591905860174e18, + 1.222589e6, + 1.295256314967406119e18, + 0.732057459169776381e18, 1e18 ); } } else { - if (price < 1.3128e6) { + if (price < 1.246507e6) { return PriceData( - 1.3155e6, - 752_243_782_340_972_617e18, - 128_382_340_704_699_919e18, - 1.3002e6, - 761_486_700_794_138_643e18, - 127_163_346_163_100_001e18, + 1.259043e6, + 1.33450387656723346e18, + 0.700419750561125598e18, + 1.234362e6, + 1.308208878117080198e18, + 0.721513591905860174e18, 1e18 ); } else { return PriceData( - 1.3155e6, - 752_243_782_340_972_617e18, - 128_382_340_704_699_919e18, - 1.3128e6, - 753_833_756_269_910_991e18, - 128_163_346_163_099_997e18, + 1.259043e6, + 1.33450387656723346e18, + 0.700419750561125598e18, + 1.246507e6, + 1.321290966898250874e18, + 0.710966947125877935e18, 1e18 ); } } } else { - if (price < 1.3384e6) { - if (price < 1.3255e6) { + if (price < 1.271991e6) { + if (price < 1.264433e6) { return PriceData( - 1.3384e6, - 738_747_458_359_333_922e18, - 130_163_346_163_100_017e18, - 1.3155e6, - 752_243_782_340_972_617e18, - 128_382_340_704_699_919e18, + 1.271991e6, + 1.347848915332905628e18, + 0.689874326166576179e18, + 1.259043e6, + 1.33450387656723346e18, + 0.700419750561125598e18, 1e18 ); } else { return PriceData( - 1.3384e6, - 738_747_458_359_333_922e18, - 130_163_346_163_100_017e18, - 1.3255e6, - 746_254_206_247_018_816e18, - 129_163_346_163_099_994e18, + 1.271991e6, + 1.347848915332905628e18, + 0.689874326166576179e18, + 1.264433e6, + 1.340095640624999973e18, + 0.695987932996588454e18, 1e18 ); } } else { - if (price < 1.3514e6) { + if (price < 1.285375e6) { return PriceData( - 1.3542e6, - 729_769_327_686_751_939e18, - 131_382_340_704_699_934e18, - 1.3384e6, - 738_747_458_359_333_922e18, - 130_163_346_163_100_017e18, + 1.299217e6, + 1.374940678531097138e18, + 0.668798587125333244e18, + 1.271991e6, + 1.347848915332905628e18, + 0.689874326166576179e18, 1e18 ); } else { return PriceData( - 1.3542e6, - 729_769_327_686_751_939e18, - 131_382_340_704_699_934e18, - 1.3514e6, - 731_312_933_164_060_298e18, - 131_163_346_163_100_013e18, + 1.299217e6, + 1.374940678531097138e18, + 0.668798587125333244e18, + 1.285375e6, + 1.361327404486234682e18, + 0.67933309721453039e18, 1e18 ); } } } } else { - if (price < 1.3943e6) { - if (price < 1.3779e6) { - if (price < 1.3646e6) { + if (price < 1.343751e6) { + if (price < 1.328377e6) { + if (price < 1.313542e6) { return PriceData( - 1.3779e6, - 716_658_293_110_424_452e18, - 133_163_346_163_100_005e18, - 1.3542e6, - 729_769_327_686_751_939e18, - 131_382_340_704_699_934e18, + 1.328377e6, + 1.402576986169572049e18, + 0.647760320838866033e18, + 1.299217e6, + 1.374940678531097138e18, + 0.668798587125333244e18, 1e18 ); } else { return PriceData( - 1.3779e6, - 716_658_293_110_424_452e18, - 133_163_346_163_100_005e18, - 1.3646e6, - 723_950_063_371_948_465e18, - 132_163_346_163_100_009e18, + 1.328377e6, + 1.402576986169572049e18, + 0.647760320838866033e18, + 1.313542e6, + 1.38869008531640814e18, + 0.658273420002602916e18, 1e18 ); } } else { - if (price < 1.3914e6) { + if (price < 1.333292e6) { return PriceData( - 1.3943e6, - 707_938_735_496_683_067e18, - 134_382_340_704_699_923e18, - 1.3779e6, - 716_658_293_110_424_452e18, - 133_163_346_163_100_005e18, + 1.343751e6, + 1.416602756031267951e18, + 0.637262115356114656e18, + 1.328377e6, + 1.402576986169572049e18, + 0.647760320838866033e18, 1e18 ); } else { return PriceData( - 1.3943e6, - 707_938_735_496_683_067e18, - 134_382_340_704_699_923e18, - 1.3914e6, - 709_437_077_218_432_531e18, - 134_163_346_163_100_002e18, + 1.343751e6, + 1.416602756031267951e18, + 0.637262115356114656e18, + 1.333292e6, + 1.407100422656250016e18, + 0.644361360672887962e18, 1e18 ); } } } else { - if (price < 1.4188e6) { - if (price < 1.405e6) { + if (price < 1.376232e6) { + if (price < 1.359692e6) { return PriceData( - 1.4188e6, - 695_204_177_438_428_696e18, - 136_163_346_163_099_994e18, - 1.3943e6, - 707_938_735_496_683_067e18, - 134_382_340_704_699_923e18, + 1.376232e6, + 1.445076471427496179e18, + 0.616322188162944262e18, + 1.343751e6, + 1.416602756031267951e18, + 0.637262115356114656e18, 1e18 ); } else { return PriceData( - 1.4188e6, - 695_204_177_438_428_696e18, - 136_163_346_163_099_994e18, - 1.405e6, - 702_285_880_571_853_430e18, - 135_163_346_163_099_998e18, + 1.376232e6, + 1.445076471427496179e18, + 0.616322188162944262e18, + 1.359692e6, + 1.430768783591580551e18, + 0.626781729444674585e18, 1e18 ); } } else { return PriceData( - 1.4328e6, - 688_191_450_861_183_111e18, - 137_163_346_163_099_990e18, - 1.4188e6, - 695_204_177_438_428_696e18, - 136_163_346_163_099_994e18, + 1.393403e6, + 1.459527236141771489e18, + 0.605886614260108591e18, + 1.376232e6, + 1.445076471427496179e18, + 0.616322188162944262e18, 1e18 ); } } } } else { - if (price < 1.5204e6) { - if (price < 1.4758e6) { - if (price < 1.4469e6) { - if (price < 1.4358e6) { + if (price < 2.209802e6) { + if (price < 1.514667e6) { + if (price < 1.415386e6) { + if (price < 1.41124e6) { return PriceData( - 1.4469e6, - 681_247_192_069_384_962e18, - 138_163_346_163_100_013e18, - 1.4328e6, - 688_191_450_861_183_111e18, - 137_163_346_163_099_990e18, + 1.415386e6, + 1.47745544378906235e18, + 0.593119977480511928e18, + 1.393403e6, + 1.459527236141771489e18, + 0.605886614260108591e18, 1e18 ); } else { return PriceData( - 1.4469e6, - 681_247_192_069_384_962e18, - 138_163_346_163_100_013e18, - 1.4358e6, - 686_737_328_931_840_165e18, - 137_382_340_704_699_938e18, + 1.415386e6, + 1.47745544378906235e18, + 0.593119977480511928e18, + 1.41124e6, + 1.474122508503188822e18, + 0.595478226183906334e18, 1e18 ); } } else { - if (price < 1.4613e6) { + if (price < 1.42978e6) { return PriceData( - 1.4758e6, - 667_562_080_341_789_698e18, - 140_163_346_163_100_006e18, - 1.4469e6, - 681_247_192_069_384_962e18, - 138_163_346_163_100_013e18, + 1.514667e6, + 1.551328215978515557e18, + 0.54263432113736132e18, + 1.415386e6, + 1.47745544378906235e18, + 0.593119977480511928e18, 1e18 ); } else { return PriceData( - 1.4758e6, - 667_562_080_341_789_698e18, - 140_163_346_163_100_006e18, - 1.4613e6, - 674_370_899_916_144_494e18, - 139_163_346_163_100_010e18, + 1.514667e6, + 1.551328215978515557e18, + 0.54263432113736132e18, + 1.42978e6, + 1.488863733588220883e18, + 0.585100335536025584e18, 1e18 ); } } } else { - if (price < 1.4904e6) { - if (price < 1.4789e6) { + if (price < 1.786708e6) { + if (price < 1.636249e6) { return PriceData( - 1.4904e6, - 660_820_245_862_202_021e18, - 141_163_346_163_100_002e18, - 1.4758e6, - 667_562_080_341_789_698e18, - 140_163_346_163_100_006e18, + 1.786708e6, + 1.710339358116313546e18, + 0.445648172809785581e18, + 1.514667e6, + 1.551328215978515557e18, + 0.54263432113736132e18, 1e18 ); } else { return PriceData( - 1.4904e6, - 660_820_245_862_202_021e18, - 141_163_346_163_100_002e18, - 1.4789e6, - 666_151_184_017_568_179e18, - 140_382_340_704_699_927e18, + 1.786708e6, + 1.710339358116313546e18, + 0.445648172809785581e18, + 1.636249e6, + 1.628894626777441568e18, + 0.493325115988533236e18, 1e18 ); } } else { - if (price < 1.5053e6) { + if (price < 1.974398e6) { return PriceData( - 1.5204e6, - 647_535_612_227_205_331e18, - 143_163_346_163_099_995e18, - 1.4904e6, - 660_820_245_862_202_021e18, - 141_163_346_163_100_002e18, + 2.209802e6, + 1.885649142323235772e18, + 0.357031765135700119e18, + 1.786708e6, + 1.710339358116313546e18, + 0.445648172809785581e18, 1e18 ); } else { return PriceData( - 1.5204e6, - 647_535_612_227_205_331e18, - 143_163_346_163_099_995e18, - 1.5053e6, - 654_144_915_081_340_263e18, - 142_163_346_163_099_998e18, + 2.209802e6, + 1.885649142323235772e18, + 0.357031765135700119e18, + 1.974398e6, + 1.79585632602212919e18, + 0.400069510798421513e18, 1e18 ); } } } } else { - if (price < 1.6687e6) { - if (price < 1.5701e6) { - if (price < 1.5236e6) { + if (price < 3.931396e6) { + if (price < 2.878327e6) { + if (price < 2.505865e6) { return PriceData( - 1.5701e6, - 626_771_913_818_503_370e18, - 146_382_340_704_699_931e18, - 1.5204e6, - 647_535_612_227_205_331e18, - 143_163_346_163_099_995e18, + 2.878327e6, + 2.078928179411367427e18, + 0.28000760254479623e18, + 2.209802e6, + 1.885649142323235772e18, + 0.357031765135700119e18, 1e18 ); } else { return PriceData( - 1.5701e6, - 626_771_913_818_503_370e18, - 146_382_340_704_699_931e18, - 1.5236e6, - 646_166_987_566_021_192e18, - 143_382_340_704_699_942e18, + 2.878327e6, + 2.078928179411367427e18, + 0.28000760254479623e18, + 2.505865e6, + 1.97993159943939756e18, + 0.316916199929126341e18, 1e18 ); } } else { - if (price < 1.6184e6) { + if (price < 3.346057e6) { return PriceData( - 1.6687e6, - 589_699_646_066_015_911e18, - 152_382_340_704_699_935e18, - 1.5701e6, - 626_771_913_818_503_370e18, - 146_382_340_704_699_931e18, + 3.931396e6, + 2.292018317801032268e18, + 0.216340086006769544e18, + 2.878327e6, + 2.078928179411367427e18, + 0.28000760254479623e18, 1e18 ); } else { return PriceData( - 1.6687e6, - 589_699_646_066_015_911e18, - 152_382_340_704_699_935e18, - 1.6184e6, - 607_953_518_109_393_449e18, - 149_382_340_704_699_920e18, + 3.931396e6, + 2.292018317801032268e18, + 0.216340086006769544e18, + 3.346057e6, + 2.182874588381935599e18, + 0.246470170347584949e18, 1e18 ); } } } else { - if (price < 9.8597e6) { - if (price < 1.7211e6) { + if (price < 10.709509e6) { + if (price < 4.660591e6) { return PriceData( - 9.8597e6, - 141_771_511_686_624_031e18, - 313_017_043_032_999_954e18, - 1.6687e6, - 589_699_646_066_015_911e18, - 152_382_340_704_699_935e18, + 10.709509e6, + 3.0e18, + 0.103912563829966526e18, + 3.931396e6, + 2.292018317801032268e18, + 0.216340086006769544e18, 1e18 ); } else { return PriceData( - 9.8597e6, - 141_771_511_686_624_031e18, - 313_017_043_032_999_954e18, - 1.7211e6, - 571_998_357_018_457_696e18, - 155_382_340_704_699_924e18, + 10.709509e6, + 3.0e18, + 0.103912563829966526e18, + 4.660591e6, + 2.406619233691083881e18, + 0.189535571483960663e18, 1e18 ); } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index 74468d22..d2d26b2d 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -6,7 +6,7 @@ import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -/// @dev Tests the {ConstantProduct2} Well function directly. +/// @dev Tests the {Stable2.CalcReserveAtRatioLiquidity} Well function directly. contract BeanstalkStable2LiquidityTest is TestHelper { IBeanstalkWellFunction _f; bytes data; @@ -22,8 +22,8 @@ contract BeanstalkStable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_equal_equal() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 100e12; - reserves[1] = 100e12; + reserves[0] = 100e18; + reserves[1] = 100e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 1; ratios[1] = 1; @@ -31,14 +31,14 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 100); - assertEq(reserve1, 100); + assertEq(reserve0, 99.997220935618347533e18); + assertEq(reserve1, 99.997220935618347533e18); } function test_calcReserveAtRatioLiquidity_equal_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 50e12; - reserves[1] = 100e12; + reserves[0] = 50e18; + reserves[1] = 100e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 1; ratios[1] = 1; @@ -46,14 +46,14 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 100); - assertEq(reserve1, 50); + assertEq(reserve0, 99.997220935618347533e18); + assertEq(reserve1, 49.998610467809173766e18); } function test_calcReserveAtRatioLiquidity_diff_equal() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 100e12; - reserves[1] = 100e12; + reserves[0] = 1e18; + reserves[1] = 1e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 2; ratios[1] = 1; @@ -61,61 +61,58 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 200); - assertEq(reserve1, 50); + assertEq(reserve0, 4.576172337359416271e18); + assertEq(reserve1, 0.218464636709548541e18); } function test_calcReserveAtRatioLiquidity_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 50e18; - reserves[1] = 100e18; + reserves[0] = 1e18; + reserves[1] = 1e18; uint256[] memory ratios = new uint256[](2); - ratios[0] = 2; - ratios[1] = 1; + ratios[0] = 12; + ratios[1] = 10; + // p = 1.2 uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 200); - assertEq(reserve1, 25); + assertEq(reserve0, 1.685434381143450467e18); + assertEq(reserve1, 0.593220305288953143e18); } - function test_calcReserveAtRatioLiquidity_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public { + function test_calcReserveAtRatioLiquidity_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public view { for (uint256 i; i < 2; ++i) { // Upper bound is limited by stableSwap, // due to the stableswap reserves being extremely far apart. reserves[i] = bound(reserves[i], 1e18, 1e31); - ratios[i] = bound(ratios[i], 1e6, 1e18); + ratios[i] = bound(ratios[i], 1e18, 4e18); } - uint256 lpTokenSupply = _f.calcLpTokenSupply(uint2ToUintN(reserves), data); - console.log(lpTokenSupply); - - uint256[] memory reservesOut = new uint256[](2); + // create 2 new reserves, one where reserve[0] is updated, and one where reserve[1] is updated. + uint256[] memory r0Updated = new uint256[](2); + r0Updated[1] = reserves[1]; + uint256[] memory r1Updated = new uint256[](2); + r1Updated[0] = reserves[0]; for (uint256 i; i < 2; ++i) { - reservesOut[i] = _f.calcReserveAtRatioLiquidity(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); + uint256 reserve = _f.calcReserveAtRatioLiquidity(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); + // update reserves. + if (i == 0) { + r0Updated[0] = reserve; + } else { + r1Updated[1] = reserve; + } } - // Precision is set to the minimum number of digits of the reserves out. - uint256 precision = numDigits(reservesOut[0]) > numDigits(reservesOut[1]) - ? numDigits(reservesOut[1]) - : numDigits(reservesOut[0]); - - // Check ratio of each `reserveOut` to `reserve` with the ratio of `ratios`. - // If inequality doesn't hold, then reserves[1] will be zero - if (ratios[0] * reserves[1] >= ratios[1]) { - assertApproxEqRelN(reservesOut[0] * ratios[1], ratios[0] * reserves[1], 1, precision); - } else { - // Because `roundedDiv` is used. It could round up to 1. - assertApproxEqAbs(reservesOut[0], 0, 1, "reservesOut[0] should be zero"); - } + { + uint256 targetPrice = ratios[0] * 1e6 / ratios[1]; + uint256 reservePrice0 = _f.calcRate(r0Updated, 0, 1, data); + uint256 reservePrice1 = _f.calcRate(r1Updated, 0, 1, data); - // If inequality doesn't hold, then reserves[1] will be zero - if (reserves[0] * ratios[1] >= ratios[0]) { - assertApproxEqRelN(reserves[0] * ratios[1], ratios[0] * reservesOut[1], 1, precision); - } else { - // Because `roundedDiv` is used. It could round up to 1. - assertApproxEqAbs(reservesOut[1], 0, 1, "reservesOut[1] should be zero"); + // estimated price and actual price are within 0.04% in the worst case. + assertApproxEqRel(targetPrice, reservePrice0, 0.0004e18, "targetPrice <> reservePrice0"); + assertApproxEqRel(targetPrice, reservePrice1, 0.0004e18, "targetPrice <> reservePrice1"); + assertApproxEqRel(reservePrice0, reservePrice1, 0.0004e18, "reservePrice0 <> reservePrice1"); } } From e84145f09ec9afe6ca71b670e9ab2648656de47c Mon Sep 17 00:00:00 2001 From: Brean0 Date: Sun, 14 Jul 2024 20:00:36 +0200 Subject: [PATCH 28/69] WIP --- src/functions/Stable2.sol | 67 ++++++++++++++----- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 6 +- ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 2 +- 3 files changed, 58 insertions(+), 17 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index d3f3d251..6938371a 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -47,7 +47,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // price threshold. more accurate pricing requires a lower threshold, // at the cost of higher execution costs. - uint256 constant PRICE_THRESHOLD = 100; // 0.001% + uint256 constant PRICE_THRESHOLD = 100; // 0.01% address immutable lookupTable; uint256 immutable a; @@ -172,7 +172,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { { uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[1] * PRICE_PRECISION / scaledRatios[0]; + pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; } // get ratios and price from the closest highest and lowest price from targetPrice: @@ -181,39 +181,73 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // perform an initial update on the reserves, such that `calcRate(reserves, i, j, data) == pd.lutData.lowPrice. // calculate lp token supply: + console.log("scaledReserves[i]", scaledReserves[i]); + console.log("scaledReserves[j]", scaledReserves[j]); uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + console.log("lpTokenSupply", lpTokenSupply); // lpTokenSupply / 2 gives the reserves at parity: uint256 parityReserve = lpTokenSupply / 2; // update `scaledReserves`. - scaledReserves[0] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; - scaledReserves[1] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; + scaledReserves[i] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; + scaledReserves[j] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; + console.log("scaledReserves[i]", scaledReserves[i]); + console.log("scaledReserves[j]", scaledReserves[j]); + console.log("pd.lutData.lowPriceI", pd.lutData.lowPriceI); + console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); + console.log("pd.lutData.highPriceI", pd.lutData.highPriceI); + console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); // calculate max step size: - pd.maxStepSize = (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ * reserves[j]; + if (pd.lutData.lowPriceJ > pd.lutData.highPriceJ) { + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; + } else { + pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; + } + console.log("pd.maxStepSize", pd.maxStepSize); // initialize currentPrice: pd.currentPrice = pd.lutData.lowPrice; + console.log(pd.currentPrice); + console.log(calcRate(scaledReserves, j, i, abi.encode(18, 18))); // does not give lowPrice + console.log(calcRate(scaledReserves, i, j, abi.encode(18, 18))); // gives low price for (uint256 k; k < 255; k++) { + console.log("k", k); + console.log("pd.currentPrice", pd.currentPrice); + console.log("pd.targetPrice", pd.targetPrice); // scale stepSize proporitional to distance from price: + uint256 stepSize = pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + scaledReserves[j] = scaledReserves[j] + stepSize; + console.log("stepSize", stepSize); // increment reserve by stepSize: - scaledReserves[j] = reserves[j] + stepSize; + scaledReserves[j] = scaledReserves[j] + stepSize; + console.log("scaledReserves[i]", scaledReserves[i]); + console.log("scaledReserves[j]", scaledReserves[j]); + console.log("here"); // calculate scaledReserve[i]: scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + console.log("scaledReserves[i]", scaledReserves[i]); + console.log("scaledReserves[j]", scaledReserves[j]); // check if new price is within 1 of target price: if (pd.currentPrice > pd.targetPrice) { - if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) { + return scaledReserves[j] / (10 ** (18 - decimals[j])); + } } else { - if (pd.targetPrice - pd.currentPrice <= PRICE_THRESHOLD) return scaledReserves[j] / (10 ** decimals[j]); + if (pd.targetPrice - pd.currentPrice <= PRICE_THRESHOLD) { + return scaledReserves[j] / (10 ** (18 - decimals[j])); + } } // calc currentPrice: - pd.currentPrice = calcRate(reserves, i, j, data); + pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); + console.log("pd.currentPrice", pd.currentPrice); + console.log("here"); } } @@ -266,6 +300,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); // calc target price with 6 decimal precision: pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; + console.log("pd.targetPrice", pd.targetPrice); } // get ratios and price from the closest highest and lowest price from targetPrice: @@ -284,10 +319,12 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // console.log("pd.maxStepSize", pd.maxStepSize); // calc currentPrice: - // console.log("scaledReserve[j]", scaledReserves[j]); - // console.log("scaledReserve[i]", scaledReserves[i]); - pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); - // console.log("initial currentPrice", pd.currentPrice); + console.log("scaledReserve[j]", scaledReserves[j]); + console.log("scaledReserve[i]", scaledReserves[i]); + // BEAN/USDC -> BEAN is i, USDC is j + // pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); + pd.currentPrice = pd.lutData.lowPrice; + console.log("initial currentPrice", pd.currentPrice); for (uint256 k; k < 255; k++) { // console.log("----------------", k); @@ -307,8 +344,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); // check if new price is within PRICE_THRESHOLD: - // console.log("pd.currentPrice after stepping", pd.currentPrice); - // console.log("target price:", pd.targetPrice); + console.log("pd.currentPrice after stepping", pd.currentPrice); + console.log("target price:", pd.targetPrice); if (pd.currentPrice > pd.targetPrice) { if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) { return scaledReserves[j] / (10 ** (18 - decimals[j])); diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index d2d26b2d..f44c7676 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -67,7 +67,7 @@ contract BeanstalkStable2LiquidityTest is TestHelper { function test_calcReserveAtRatioLiquidity_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 1e18; + reserves[0] = 2e18; reserves[1] = 1e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 12; @@ -109,6 +109,10 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reservePrice0 = _f.calcRate(r0Updated, 0, 1, data); uint256 reservePrice1 = _f.calcRate(r1Updated, 0, 1, data); + uint256 targetPriceInverted = ratios[1] * 1e6 / ratios[0]; + uint256 reservePrice0Inverted = _f.calcRate(r0Updated, 1, 0, data); + uint256 reservePrice1Inverted = _f.calcRate(r1Updated, 1, 0, data); + // estimated price and actual price are within 0.04% in the worst case. assertApproxEqRel(targetPrice, reservePrice0, 0.0004e18, "targetPrice <> reservePrice0"); assertApproxEqRel(targetPrice, reservePrice1, 0.0004e18, "targetPrice <> reservePrice1"); diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 915796e8..43c38141 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -6,7 +6,7 @@ import {Stable2} from "src/functions/Stable2.sol"; import {IBeanstalkWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; -/// @dev Tests the {ConstantProduct2} Well function directly. +/// @dev Tests the {Stable2} Well function directly. contract BeanstalkStable2SwapTest is TestHelper { IBeanstalkWellFunction _f; bytes data; From 4e637c189bde4cbf0f1d2c412e03daf7572744fb Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 16 Jul 2024 00:36:01 +0200 Subject: [PATCH 29/69] add swap tests. --- src/functions/Stable2.sol | 187 +- src/functions/StableLUT/Stable2LUT1.sol | 2232 +++++++---------- .../Stable2}/LookupTable.t.sol | 19 +- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 25 +- ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 49 +- 5 files changed, 1010 insertions(+), 1502 deletions(-) rename {src/functions => test/Stable2}/LookupTable.t.sol (87%) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 6938371a..c35d674e 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -151,6 +151,33 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { revert("did not find convergence"); } + /** + * @inheritdoc IMultiFlowPumpWellFunction + * @dev Returns a rate with decimal precision. + * Requires a minimum scaled reserves of 1e12. + * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, + * which is prohibtive for large value tokens. + */ + function calcRate( + uint256[] memory reserves, + uint256 i, + uint256 j, + bytes memory data + ) public view returns (uint256 rate) { + uint256[] memory decimals = decodeWellData(data); + uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); + + // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). + uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + + // add 1e6 to reserves: + scaledReserves[j] += PRICE_PRECISION; + + // calculate new reserve 1: + uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); + rate = (scaledReserves[i] - new_reserve1); + } + /** * @inheritdoc IMultiFlowPumpWellFunction * @dev `calcReserveAtRatioSwap` fetches the closes approximate ratios from the target price, @@ -172,32 +199,32 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { { uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; + pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; } // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceSwap(pd.targetPrice); - // perform an initial update on the reserves, such that `calcRate(reserves, i, j, data) == pd.lutData.lowPrice. - // calculate lp token supply: - console.log("scaledReserves[i]", scaledReserves[i]); - console.log("scaledReserves[j]", scaledReserves[j]); uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); - console.log("lpTokenSupply", lpTokenSupply); // lpTokenSupply / 2 gives the reserves at parity: uint256 parityReserve = lpTokenSupply / 2; - // update `scaledReserves`. - scaledReserves[i] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; - scaledReserves[j] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; - console.log("scaledReserves[i]", scaledReserves[i]); - console.log("scaledReserves[j]", scaledReserves[j]); - console.log("pd.lutData.lowPriceI", pd.lutData.lowPriceI); - console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); - console.log("pd.lutData.highPriceI", pd.lutData.highPriceI); - console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); + // update `scaledReserves` based on whether targetPrice is closer to low or high price: + if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + // targetPrice is closer to lowPrice. + scaledReserves[i] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; + scaledReserves[j] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; + // initialize currentPrice: + pd.currentPrice = pd.lutData.lowPrice; + } else { + // targetPrice is closer to highPrice. + scaledReserves[i] = parityReserve * pd.lutData.highPriceI / pd.lutData.precision; + scaledReserves[j] = parityReserve * pd.lutData.highPriceJ / pd.lutData.precision; + // initialize currentPrice: + pd.currentPrice = pd.lutData.highPrice; + } // calculate max step size: if (pd.lutData.lowPriceJ > pd.lutData.highPriceJ) { @@ -205,33 +232,16 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } else { pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; } - console.log("pd.maxStepSize", pd.maxStepSize); - - // initialize currentPrice: - pd.currentPrice = pd.lutData.lowPrice; - console.log(pd.currentPrice); - console.log(calcRate(scaledReserves, j, i, abi.encode(18, 18))); // does not give lowPrice - console.log(calcRate(scaledReserves, i, j, abi.encode(18, 18))); // gives low price for (uint256 k; k < 255; k++) { - console.log("k", k); - console.log("pd.currentPrice", pd.currentPrice); - console.log("pd.targetPrice", pd.targetPrice); // scale stepSize proporitional to distance from price: - uint256 stepSize = - pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); - scaledReserves[j] = scaledReserves[j] + stepSize; - console.log("stepSize", stepSize); - // increment reserve by stepSize: - scaledReserves[j] = scaledReserves[j] + stepSize; - console.log("scaledReserves[i]", scaledReserves[i]); - console.log("scaledReserves[j]", scaledReserves[j]); - console.log("here"); + scaledReserves[j] = updateReserve(pd, scaledReserves[j]); // calculate scaledReserve[i]: scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - console.log("scaledReserves[i]", scaledReserves[i]); - console.log("scaledReserves[j]", scaledReserves[j]); + + // calc currentPrice: + pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); // check if new price is within 1 of target price: if (pd.currentPrice > pd.targetPrice) { @@ -243,41 +253,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { return scaledReserves[j] / (10 ** (18 - decimals[j])); } } - - // calc currentPrice: - pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); - console.log("pd.currentPrice", pd.currentPrice); - console.log("here"); } } - /** - * @inheritdoc IMultiFlowPumpWellFunction - * @dev Returns a rate with decimal precision. - * Requires a minimum scaled reserves of 1e12. - * 6 decimals was chosen as higher decimals would require a higher minimum scaled reserve, - * which is prohibtive for large value tokens. - */ - function calcRate( - uint256[] memory reserves, - uint256 i, - uint256 j, - bytes memory data - ) public view returns (uint256 rate) { - uint256[] memory decimals = decodeWellData(data); - uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - - // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). - uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); - - // add 1e6 to reserves: - scaledReserves[j] += PRICE_PRECISION; - - // calculate new reserve 1: - uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - rate = (scaledReserves[i] - new_reserve1); - } - /** * @inheritdoc IBeanstalkWellFunction * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. @@ -299,53 +277,41 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { { uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[j] * PRICE_PRECISION / scaledRatios[i]; - console.log("pd.targetPrice", pd.targetPrice); + pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; } // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); - // update scaledReserve[j] based on lowPrice: - // console.log("scaledReserve[j] b4", scaledReserves[j]); - scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; - // console.log("scaledReserve[j] afta", scaledReserves[j]); + // update scaledReserve[j] such that calcRate(scaledReserves, i, j) = low/high Price, + // depending on which is closer to targetPrice. + if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + // targetPrice is closer to lowPrice. + scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; - // calculatde max step size: - // console.log("pd.lutData.highPriceJ", pd.lutData.highPriceJ); - // console.log("pd.lutData.lowPriceJ", pd.lutData.lowPriceJ); + // set current price to lowPrice. + pd.currentPrice = pd.lutData.lowPrice; + } else { + // targetPrice is closer to highPrice. + scaledReserves[j] = scaledReserves[i] * pd.lutData.highPriceJ / pd.lutData.precision; - pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; - // console.log("pd.maxStepSize", pd.maxStepSize); + // set current price to highPrice. + pd.currentPrice = pd.lutData.highPrice; + } - // calc currentPrice: - console.log("scaledReserve[j]", scaledReserves[j]); - console.log("scaledReserve[i]", scaledReserves[i]); - // BEAN/USDC -> BEAN is i, USDC is j - // pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); - pd.currentPrice = pd.lutData.lowPrice; - console.log("initial currentPrice", pd.currentPrice); + // calculate max step size: + if (pd.lutData.lowPriceJ > pd.lutData.highPriceJ) { + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; + } else { + pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; + } for (uint256 k; k < 255; k++) { - // console.log("----------------", k); - // scale stepSize proporitional to distance from price: - // console.log("pd.targetPrice", pd.targetPrice); - // console.log("pd.currentPrice before stepping", pd.currentPrice); - uint256 stepSize = - pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); - // console.log("stepSize", stepSize); - // increment reserve by stepSize: - // console.log("scaledReserves[j] b4", scaledReserves[j]); - scaledReserves[j] = scaledReserves[j] + stepSize; - // console.log("scaledReserves[i] af", scaledReserves[i]); - // console.log("scaledReserves[j] af", scaledReserves[j]); + scaledReserves[j] = updateReserve(pd, scaledReserves[j]); // calculate new price from reserves: - // todo: j and i needs to be switched. Current LUT has it inverted - pd.currentPrice = calcRate(scaledReserves, j, i, abi.encode(18, 18)); + pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); // check if new price is within PRICE_THRESHOLD: - console.log("pd.currentPrice after stepping", pd.currentPrice); - console.log("target price:", pd.targetPrice); if (pd.currentPrice > pd.targetPrice) { if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) { return scaledReserves[j] / (10 ** (18 - decimals[j])); @@ -417,4 +383,21 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { c = lpTokenSupply.mul(lpTokenSupply).div(reserves.mul(N)).mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); } + + /** + * @notice calculates the step size, and returns the updated reserve. + */ + function updateReserve(PriceData memory pd, uint256 reserve) internal pure returns (uint256) { + if (pd.targetPrice > pd.currentPrice) { + // if the targetPrice is greater than the currentPrice, + // the reserve needs to be decremented to increase currentPrice. + return reserve + - pd.maxStepSize * (pd.targetPrice - pd.currentPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + } else { + // if the targetPrice is less than the currentPrice, + // the reserve needs to be incremented to decrease currentPrice. + return reserve + + pd.maxStepSize * (pd.currentPrice - pd.targetPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); + } + } } diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index 7b713f59..e40f41c7 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.20; import {ILookupTable} from "src/interfaces/ILookupTable.sol"; @@ -10,8 +10,8 @@ import {ILookupTable} from "src/interfaces/ILookupTable.sol"; * to calculate the token ratios in a stableswap pool to return to peg. * It uses an if ladder structured as a binary tree to store and retrieve * price-to-token ratio estimates needed for liquidity and swap operations - * within Beanstalk in O(1) time complexity. - * A lookup table was used to avoid the need for expensive calculations on-chain. + * within Beanstalk in O(log2(n)) time complexity. + * A lookup table was used to avoid the need for expensive price calculations on-chain. */ contract Stable2LUT1 is ILookupTable { /** @@ -29,1455 +29,975 @@ contract Stable2LUT1 is ILookupTable { * Used in `calcReserveAtRatioLiquidity` function in the stableswap well function. */ function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { - if (price < 1.006623e6) { - if (price < 0.825934e6) { - if (price < 0.741141e6) { - if (price < 0.412329e6) { - if (price < 0.268539e6) { - if (price < 0.211448e6) { - if (price < 0.001832e6) { - revert("LUT: Invalid price"); - } else { - return PriceData( - 0.211448e6, - 0.0e18, - 0.077562793638189589e18, - 0.001832e6, - 0.0e18, - 0.000833333333333333e18, - 1e18 - ); - } - } else { - if (price < 0.238695e6) { - return PriceData( - 0.268539e6, - 0.0e18, - 0.100158566165017532e18, - 0.211448e6, - 0.0e18, - 0.077562793638189589e18, - 1e18 - ); - } else { - return PriceData( - 0.268539e6, - 0.0e18, - 0.100158566165017532e18, - 0.238695e6, - 0.0e18, - 0.088139538225215433e18, - 1e18 - ); - } - } - } else { - if (price < 0.335863e6) { - if (price < 0.300961e6) { - return PriceData( - 0.335863e6, - 0.0e18, - 0.129336991432099091e18, - 0.268539e6, - 0.0e18, - 0.100158566165017532e18, - 1e18 - ); - } else { - return PriceData( - 0.335863e6, - 0.0e18, - 0.129336991432099091e18, - 0.300961e6, - 0.0e18, - 0.113816552460247203e18, - 1e18 - ); - } - } else { - if (price < 0.373071e6) { - return PriceData( - 0.412329e6, - 0.0e18, - 0.167015743068309769e18, - 0.335863e6, - 0.0e18, - 0.129336991432099091e18, - 1e18 - ); - } else { - return PriceData( - 0.412329e6, - 0.0e18, - 0.167015743068309769e18, - 0.373071e6, - 0.0e18, - 0.146973853900112583e18, - 1e18 - ); - } - } - } - } else { - if (price < 0.582463e6) { - if (price < 0.495602e6) { - if (price < 0.453302e6) { - return PriceData( - 0.495602e6, - 0.0e18, - 0.215671155821681004e18, - 0.412329e6, - 0.0e18, - 0.167015743068309769e18, - 1e18 - ); - } else { - return PriceData( - 0.495602e6, - 0.0e18, - 0.215671155821681004e18, - 0.453302e6, - 0.0e18, - 0.189790617123079292e18, - 1e18 - ); - } - } else { - if (price < 0.538801e6) { - return PriceData( - 0.582463e6, - 0.0e18, - 0.278500976009402101e18, - 0.495602e6, - 0.0e18, - 0.215671155821681004e18, - 1e18 - ); - } else { - return PriceData( - 0.582463e6, - 0.0e18, - 0.278500976009402101e18, - 0.538801e6, - 0.0e18, - 0.245080858888273884e18, - 1e18 - ); - } - } - } else { - if (price < 0.669604e6) { - if (price < 0.626181e6) { - return PriceData( - 0.669604e6, - 0.0e18, - 0.359634524805529598e18, - 0.582463e6, - 0.0e18, - 0.278500976009402101e18, - 1e18 - ); - } else { - return PriceData( - 0.669604e6, - 0.0e18, - 0.359634524805529598e18, - 0.626181e6, - 0.0e18, - 0.316478381828866062e18, - 1e18 - ); - } - } else { - if (price < 0.712465e6) { - return PriceData( - 0.741141e6, - 0.0e18, - 0.445700403950951007e18, - 0.669604e6, - 0.0e18, - 0.359634524805529598e18, - 1e18 - ); - } else { - return PriceData( - 0.741141e6, - 0.0e18, - 0.445700403950951007e18, - 0.712465e6, - 0.0e18, - 0.408675596369920013e18, - 1e18 - ); - } - } - } - } - } else { - if (price < 0.787158e6) { - if (price < 0.760974e6) { - if (price < 0.754382e6) { - if (price < 0.747771e6) { - return PriceData( - 0.754382e6, - 0.0e18, - 0.464077888328770338e18, - 0.741141e6, - 0.0e18, - 0.445700403950951007e18, - 1e18 - ); - } else { - return PriceData( - 0.754382e6, - 0.0e18, - 0.464077888328770338e18, - 0.747771e6, - 0.0e18, - 0.454796330562194928e18, - 1e18 - ); - } - } else { - if (price < 0.754612e6) { - return PriceData( - 0.760974e6, - 0.0e18, - 0.473548865641602368e18, - 0.754382e6, - 0.0e18, - 0.464077888328770338e18, - 1e18 - ); - } else { - return PriceData( - 0.760974e6, - 0.0e18, - 0.473548865641602368e18, - 0.754612e6, - 0.0e18, - 0.464404086784000025e18, - 1e18 - ); - } - } - } else { - if (price < 0.774102e6) { - if (price < 0.767548e6) { - return PriceData( - 0.774102e6, - 0.0e18, - 0.49307462061807833e18, - 0.760974e6, - 0.0e18, - 0.473548865641602368e18, - 1e18 - ); - } else { - return PriceData( - 0.774102e6, - 0.0e18, - 0.49307462061807833e18, - 0.767548e6, - 0.0e18, - 0.483213128205716769e18, - 1e18 - ); - } - } else { - if (price < 0.780639e6) { - return PriceData( - 0.787158e6, - 0.0e18, - 0.513405477528194876e18, - 0.774102e6, - 0.0e18, - 0.49307462061807833e18, - 1e18 - ); - } else { - return PriceData( - 0.787158e6, - 0.0e18, - 0.513405477528194876e18, - 0.780639e6, - 0.0e18, - 0.503137367977630867e18, - 1e18 - ); - } - } - } - } else { - if (price < 0.806614e6) { - if (price < 0.796011e6) { - if (price < 0.79366e6) { - return PriceData( - 0.796011e6, - 0.0e18, - 0.527731916799999978e18, - 0.787158e6, - 0.0e18, - 0.513405477528194876e18, - 1e18 - ); - } else { - return PriceData( - 0.796011e6, - 0.0e18, - 0.527731916799999978e18, - 0.79366e6, - 0.0e18, - 0.523883140334892694e18, - 1e18 - ); - } - } else { - if (price < 0.800145e6) { - return PriceData( - 0.806614e6, - 0.0e18, - 0.545484319382437244e18, - 0.796011e6, - 0.0e18, - 0.527731916799999978e18, - 1e18 - ); - } else { - return PriceData( - 0.806614e6, - 0.0e18, - 0.545484319382437244e18, - 0.800145e6, - 0.0e18, - 0.534574632994788468e18, - 1e18 - ); - } - } - } else { - if (price < 0.819507e6) { - if (price < 0.813068e6) { - return PriceData( - 0.819507e6, - 0.0e18, - 0.567976175950059559e18, - 0.806614e6, - 0.0e18, - 0.545484319382437244e18, - 1e18 - ); - } else { - return PriceData( - 0.819507e6, - 0.0e18, - 0.567976175950059559e18, - 0.813068e6, - 0.0e18, - 0.55661665243105829e18, - 1e18 - ); - } - } else { - return PriceData( - 0.825934e6, - 0.0e18, - 0.579567526479652595e18, - 0.819507e6, - 0.0e18, - 0.567976175950059559e18, - 1e18 - ); - } - } - } - } - } else { - if (price < 0.915208e6) { - if (price < 0.870637e6) { - if (price < 0.845143e6) { - if (price < 0.836765e6) { - if (price < 0.832347e6) { - return PriceData( - 0.836765e6, - 0.0e18, - 0.599695360000000011e18, - 0.825934e6, - 0.0e18, - 0.579567526479652595e18, - 1e18 - ); - } else { - return PriceData( - 0.836765e6, - 0.0e18, - 0.599695360000000011e18, - 0.832347e6, - 0.0e18, - 0.591395435183319051e18, - 1e18 - ); - } - } else { - if (price < 0.838751e6) { - return PriceData( - 0.845143e6, - 0.0e18, - 0.615780336509078485e18, - 0.836765e6, - 0.0e18, - 0.599695360000000011e18, - 1e18 - ); - } else { - return PriceData( - 0.845143e6, - 0.0e18, - 0.615780336509078485e18, - 0.838751e6, - 0.0e18, - 0.60346472977889698e18, - 1e18 - ); - } - } - } else { - if (price < 0.857902e6) { - if (price < 0.851526e6) { - return PriceData( - 0.857902e6, - 0.0e18, - 0.641170696073592783e18, - 0.845143e6, - 0.0e18, - 0.615780336509078485e18, - 1e18 - ); - } else { - return PriceData( - 0.857902e6, - 0.0e18, - 0.641170696073592783e18, - 0.851526e6, - 0.0e18, - 0.628347282152120878e18, - 1e18 - ); - } - } else { - if (price < 0.864272e6) { - return PriceData( - 0.870637e6, - 0.0e18, - 0.667607971755094454e18, - 0.857902e6, - 0.0e18, - 0.641170696073592783e18, - 1e18 - ); - } else { - return PriceData( - 0.870637e6, - 0.0e18, - 0.667607971755094454e18, - 0.864272e6, - 0.0e18, - 0.654255812319992636e18, - 1e18 - ); - } - } - } - } else { - if (price < 0.889721e6) { - if (price < 0.87711e6) { - if (price < 0.877e6) { - return PriceData( - 0.87711e6, - 0.0e18, - 0.681471999999999967e18, - 0.870637e6, - 0.0e18, - 0.667607971755094454e18, - 1e18 - ); - } else { - return PriceData( - 0.87711e6, - 0.0e18, - 0.681471999999999967e18, - 0.877e6, - 0.0e18, - 0.681232624239892393e18, - 1e18 - ); - } - } else { - if (price < 0.88336e6) { - return PriceData( - 0.889721e6, - 0.0e18, - 0.709321766180645907e18, - 0.87711e6, - 0.0e18, - 0.681471999999999967e18, - 1e18 - ); - } else { - return PriceData( - 0.889721e6, - 0.0e18, - 0.709321766180645907e18, - 0.88336e6, - 0.0e18, - 0.695135330857033051e18, - 1e18 - ); - } - } - } else { - if (price < 0.902453e6) { - if (price < 0.896084e6) { - return PriceData( - 0.902453e6, - 0.0e18, - 0.738569102645403985e18, - 0.889721e6, - 0.0e18, - 0.709321766180645907e18, - 1e18 - ); - } else { - return PriceData( - 0.902453e6, - 0.0e18, - 0.738569102645403985e18, - 0.896084e6, - 0.0e18, - 0.723797720592495919e18, - 1e18 - ); - } - } else { - if (price < 0.908826e6) { - return PriceData( - 0.915208e6, - 0.0e18, - 0.769022389260104022e18, - 0.902453e6, - 0.0e18, - 0.738569102645403985e18, - 1e18 - ); - } else { - return PriceData( - 0.915208e6, - 0.0e18, - 0.769022389260104022e18, - 0.908826e6, - 0.0e18, - 0.753641941474902044e18, - 1e18 - ); - } - } - } - } - } else { - if (price < 0.958167e6) { - if (price < 0.934425e6) { - if (price < 0.9216e6) { - if (price < 0.917411e6) { - return PriceData( - 0.9216e6, - 0.0e18, - 0.784716723734800059e18, - 0.915208e6, - 0.0e18, - 0.769022389260104022e18, - 1e18 - ); - } else { - return PriceData( - 0.9216e6, - 0.0e18, - 0.784716723734800059e18, - 0.917411e6, - 0.0e18, - 0.774399999999999977e18, - 1e18 - ); - } - } else { - if (price < 0.928005e6) { - return PriceData( - 0.934425e6, - 0.0e18, - 0.81707280688754691e18, - 0.9216e6, - 0.0e18, - 0.784716723734800059e18, - 1e18 - ); - } else { - return PriceData( - 0.934425e6, - 0.0e18, - 0.81707280688754691e18, - 0.928005e6, - 0.0e18, - 0.800731350749795956e18, - 1e18 - ); - } - } - } else { - if (price < 0.947318e6) { - if (price < 0.940861e6) { - return PriceData( - 0.947318e6, - 0.0e18, - 0.8507630225817856e18, - 0.934425e6, - 0.0e18, - 0.81707280688754691e18, - 1e18 - ); - } else { - return PriceData( - 0.947318e6, - 0.0e18, - 0.8507630225817856e18, - 0.940861e6, - 0.0e18, - 0.833747762130149894e18, - 1e18 - ); - } - } else { - if (price < 0.953797e6) { - return PriceData( - 0.958167e6, - 0.0e18, - 0.880000000000000004e18, - 0.947318e6, - 0.0e18, - 0.8507630225817856e18, - 1e18 - ); - } else { - return PriceData( - 0.958167e6, - 0.0e18, - 0.880000000000000004e18, - 0.953797e6, - 0.0e18, - 0.868125533246720038e18, - 1e18 - ); - } - } - } - } else { - if (price < 0.979988e6) { - if (price < 0.966831e6) { - if (price < 0.960301e6) { - return PriceData( - 0.966831e6, - 0.0e18, - 0.903920796799999926e18, - 0.958167e6, - 0.0e18, - 0.880000000000000004e18, - 1e18 - ); - } else { - return PriceData( - 0.966831e6, - 0.0e18, - 0.903920796799999926e18, - 0.960301e6, - 0.0e18, - 0.885842380864000023e18, - 1e18 - ); - } - } else { - if (price < 0.973393e6) { - return PriceData( - 0.979988e6, - 0.0e18, - 0.941192000000000029e18, - 0.966831e6, - 0.0e18, - 0.903920796799999926e18, - 1e18 - ); - } else { - return PriceData( - 0.979988e6, - 0.0e18, - 0.941192000000000029e18, - 0.973393e6, - 0.0e18, - 0.922368159999999992e18, - 1e18 - ); - } - } - } else { - if (price < 0.993288e6) { - if (price < 0.986618e6) { - return PriceData( - 0.993288e6, - 0.0e18, - 0.980000000000000093e18, - 0.979988e6, - 0.0e18, - 0.941192000000000029e18, - 1e18 - ); - } else { - return PriceData( - 0.993288e6, - 0.0e18, - 0.980000000000000093e18, - 0.986618e6, - 0.0e18, - 0.960400000000000031e18, - 1e18 - ); - } - } else { - return PriceData( - 1.006623e6, - 0.0e18, - 1.020000000000000018e18, - 0.993288e6, - 0.0e18, - 0.980000000000000093e18, - 1e18 - ); - } - } - } - } - } - } else { - if (price < 1.214961e6) { - if (price < 1.105774e6) { - if (price < 1.054473e6) { - if (price < 1.033615e6) { - if (price < 1.020013e6) { - if (price < 1.013294e6) { - return PriceData( - 1.020013e6, - 0.0e18, - 1.061208000000000151e18, - 1.006623e6, - 0.0e18, - 1.020000000000000018e18, - 1e18 - ); - } else { - return PriceData( - 1.020013e6, - 0.0e18, - 1.061208000000000151e18, - 1.013294e6, - 0.0e18, - 1.040399999999999991e18, - 1e18 - ); - } - } else { - if (price < 1.026785e6) { - return PriceData( - 1.033615e6, - 0.0e18, - 1.104080803200000016e18, - 1.020013e6, - 0.0e18, - 1.061208000000000151e18, - 1e18 - ); - } else { - return PriceData( - 1.033615e6, - 0.0e18, - 1.104080803200000016e18, - 1.026785e6, - 0.0e18, - 1.082432159999999977e18, - 1e18 - ); - } - } - } else { - if (price < 1.040503e6) { - if (price < 1.038588e6) { - return PriceData( - 1.040503e6, - 0.0e18, - 1.12616241926400007e18, - 1.033615e6, - 0.0e18, - 1.104080803200000016e18, - 1e18 - ); - } else { - return PriceData( - 1.040503e6, - 0.0e18, - 1.12616241926400007e18, - 1.038588e6, - 0.0e18, - 1.120000000000000107e18, - 1e18 - ); - } - } else { - if (price < 1.047454e6) { - return PriceData( - 1.054473e6, - 0.0e18, - 1.171659381002265521e18, - 1.040503e6, - 0.0e18, - 1.12616241926400007e18, - 1e18 - ); - } else { - return PriceData( - 1.054473e6, - 0.0e18, - 1.171659381002265521e18, - 1.047454e6, - 0.0e18, - 1.14868566764928004e18, - 1e18 - ); - } - } - } - } else { - if (price < 1.079216e6) { - if (price < 1.068722e6) { - if (price < 1.06156e6) { - return PriceData( - 1.068722e6, - 0.0e18, - 1.218994419994757328e18, - 1.054473e6, - 0.0e18, - 1.171659381002265521e18, - 1e18 - ); - } else { - return PriceData( - 1.068722e6, - 0.0e18, - 1.218994419994757328e18, - 1.06156e6, - 0.0e18, - 1.195092568622310836e18, - 1e18 - ); - } - } else { - if (price < 1.075962e6) { - return PriceData( - 1.079216e6, - 0.0e18, - 1.254399999999999959e18, - 1.068722e6, - 0.0e18, - 1.218994419994757328e18, - 1e18 - ); - } else { - return PriceData( - 1.079216e6, - 0.0e18, - 1.254399999999999959e18, - 1.075962e6, - 0.0e18, - 1.243374308394652239e18, - 1e18 - ); - } - } - } else { - if (price < 1.09069e6) { - if (price < 1.083283e6) { - return PriceData( - 1.09069e6, - 0.0e18, - 1.293606630453796313e18, - 1.079216e6, - 0.0e18, - 1.254399999999999959e18, - 1e18 - ); - } else { - return PriceData( - 1.09069e6, - 0.0e18, - 1.293606630453796313e18, - 1.083283e6, - 0.0e18, - 1.268241794562545266e18, - 1e18 - ); - } - } else { - if (price < 1.098185e6) { - return PriceData( - 1.105774e6, - 0.0e18, - 1.345868338324129665e18, - 1.09069e6, - 0.0e18, - 1.293606630453796313e18, - 1e18 - ); - } else { - return PriceData( - 1.105774e6, - 0.0e18, - 1.345868338324129665e18, - 1.098185e6, - 0.0e18, - 1.319478763062872151e18, - 1e18 - ); - } - } - } - } - } else { - if (price < 1.161877e6) { - if (price < 1.129145e6) { - if (price < 1.12125e6) { - if (price < 1.113461e6) { - return PriceData( - 1.12125e6, - 0.0e18, - 1.400241419192424397e18, - 1.105774e6, - 0.0e18, - 1.345868338324129665e18, - 1e18 - ); - } else { - return PriceData( - 1.12125e6, - 0.0e18, - 1.400241419192424397e18, - 1.113461e6, - 0.0e18, - 1.372785705090612263e18, - 1e18 - ); - } - } else { - if (price < 1.122574e6) { - return PriceData( - 1.129145e6, - 0.0e18, - 1.428246247576273165e18, - 1.12125e6, - 0.0e18, - 1.400241419192424397e18, - 1e18 - ); + if (price < 1.006758e6) { + if (price < 0.885627e6) { + if (price < 0.59332e6) { + if (price < 0.404944e6) { + if (price < 0.30624e6) { + if (price < 0.27702e6) { + if (price < 0.001083e6) { + revert("LUT: Invalid price"); } else { return PriceData( - 1.129145e6, - 0.0e18, - 1.428246247576273165e18, - 1.122574e6, + 0.27702e6, + 0.0e18, // x (should stay the same now) + 9.646293093274934449e18, // y + 0.001083e6, 0.0e18, - 1.404927999999999955e18, + 2000.0e18, 1e18 ); } + } else { + return PriceData( + 0.30624e6, + 0.0e18, + 8.612761690424049377e18, + 0.27702e6, + 0.0e18, + 9.646293093274934449e18, + 1e18 + ); } } else { - if (price < 1.145271e6) { - if (price < 1.137151e6) { + if (price < 0.370355e6) { + if (price < 0.337394e6) { return PriceData( - 1.145271e6, + 0.337394e6, 0.0e18, - 1.485947395978354457e18, - 1.129145e6, + 7.689965795021471706e18, + 0.30624e6, 0.0e18, - 1.428246247576273165e18, + 8.612761690424049377e18, 1e18 ); } else { return PriceData( - 1.145271e6, + 0.370355e6, 0.0e18, - 1.485947395978354457e18, - 1.137151e6, + 6.866040888412029197e18, + 0.337394e6, 0.0e18, - 1.456811172527798348e18, + 7.689965795021471706e18, 1e18 ); } } else { - if (price < 1.153512e6) { + return PriceData( + 0.404944e6, + 0.0e18, + 6.130393650367882863e18, + 0.370355e6, + 0.0e18, + 6.866040888412029197e18, + 1e18 + ); + } + } + } else { + if (price < 0.516039e6) { + if (price < 0.478063e6) { + if (price < 0.440934e6) { return PriceData( - 1.161877e6, + 0.440934e6, 0.0e18, - 1.545979670775879944e18, - 1.145271e6, + 5.473565759257038366e18, + 0.404944e6, 0.0e18, - 1.485947395978354457e18, + 6.130393650367882863e18, 1e18 ); } else { return PriceData( - 1.161877e6, + 0.478063e6, 0.0e18, - 1.545979670775879944e18, - 1.153512e6, + 4.887112285050926097e18, + 0.440934e6, 0.0e18, - 1.515666343897921431e18, + 5.473565759257038366e18, 1e18 ); } + } else { + return PriceData( + 0.516039e6, + 0.0e18, + 4.363493111652613443e18, + 0.478063e6, + 0.0e18, + 4.887112285050926097e18, + 1e18 + ); + } + } else { + if (price < 0.554558e6) { + return PriceData( + 0.554558e6, + 0.0e18, + 3.89597599254697613e18, + 0.516039e6, + 0.0e18, + 4.363493111652613443e18, + 1e18 + ); + } else { + return PriceData( + 0.59332e6, + 0.0e18, + 3.478549993345514402e18, + 0.554558e6, + 0.0e18, + 3.89597599254697613e18, + 1e18 + ); } } - } else { - if (price < 1.187769e6) { - if (price < 1.170371e6) { - if (price < 1.169444e6) { + } + } else { + if (price < 0.782874e6) { + if (price < 0.708539e6) { + if (price < 0.670518e6) { + if (price < 0.632052e6) { return PriceData( - 1.170371e6, + 0.632052e6, 0.0e18, - 1.576899264191397476e18, - 1.161877e6, + 3.105848208344209382e18, + 0.59332e6, 0.0e18, - 1.545979670775879944e18, + 3.478549993345514402e18, 1e18 ); } else { return PriceData( - 1.170371e6, + 0.670518e6, 0.0e18, - 1.576899264191397476e18, - 1.169444e6, + 2.773078757450186949e18, + 0.632052e6, 0.0e18, - 1.573519359999999923e18, + 3.105848208344209382e18, 1e18 ); } } else { - if (price < 1.179e6) { + return PriceData( + 0.708539e6, + 0.0e18, + 2.475963176294809553e18, + 0.670518e6, + 0.0e18, + 2.773078757450186949e18, + 1e18 + ); + } + } else { + if (price < 0.746003e6) { + return PriceData( + 0.746003e6, + 0.0e18, + 2.210681407406080101e18, + 0.708539e6, + 0.0e18, + 2.475963176294809553e18, + 1e18 + ); + } else { + return PriceData( + 0.782874e6, + 0.0e18, + 1.973822685183999948e18, + 0.746003e6, + 0.0e18, + 2.210681407406080101e18, + 1e18 + ); + } + } + } else { + if (price < 0.873157e6) { + if (price < 0.855108e6) { + if (price < 0.819199e6) { return PriceData( - 1.187769e6, + 0.819199e6, 0.0e18, - 1.640605994464729989e18, - 1.170371e6, + 1.762341683200000064e18, + 0.782874e6, 0.0e18, - 1.576899264191397476e18, + 1.973822685183999948e18, 1e18 ); } else { return PriceData( - 1.187769e6, + 0.855108e6, 0.0e18, - 1.640605994464729989e18, - 1.179e6, + 1.573519359999999923e18, + 0.819199e6, 0.0e18, - 1.608437249475225483e18, + 1.762341683200000064e18, 1e18 ); } + } else { + return PriceData( + 0.873157e6, + 0.0e18, + 1.485947395978354457e18, + 0.855108e6, + 0.0e18, + 1.573519359999999923e18, + 1e18 + ); } } else { - if (price < 1.205744e6) { - if (price < 1.196681e6) { + if (price < 0.879393e6) { + return PriceData( + 0.879393e6, + 0.0e18, + 1.456811172527798348e18, + 0.873157e6, + 0.0e18, + 1.485947395978354457e18, + 1e18 + ); + } else { + return PriceData( + 0.885627e6, + 0.0e18, + 1.428246247576273165e18, + 0.879393e6, + 0.0e18, + 1.456811172527798348e18, + 1e18 + ); + } + } + } + } + } else { + if (price < 0.94201e6) { + if (price < 0.916852e6) { + if (price < 0.898101e6) { + if (price < 0.891863e6) { + if (price < 0.89081e6) { return PriceData( - 1.205744e6, + 0.89081e6, 0.0e18, - 1.706886476641104933e18, - 1.187769e6, + 1.404927999999999955e18, + 0.885627e6, 0.0e18, - 1.640605994464729989e18, + 1.428246247576273165e18, 1e18 ); } else { return PriceData( - 1.205744e6, + 0.891863e6, 0.0e18, - 1.706886476641104933e18, - 1.196681e6, + 1.400241419192424397e18, + 0.89081e6, 0.0e18, - 1.673418114354024322e18, + 1.404927999999999955e18, 1e18 ); } } else { return PriceData( - 1.214961e6, + 0.898101e6, 0.0e18, - 1.741024206173927169e18, - 1.205744e6, + 1.372785705090612263e18, + 0.891863e6, 0.0e18, - 1.706886476641104933e18, + 1.400241419192424397e18, 1e18 ); } - } - } - } - } else { - if (price < 1.34048e6) { - if (price < 1.277347e6) { - if (price < 1.2436e6) { - if (price < 1.224339e6) { - if (price < 1.220705e6) { + } else { + if (price < 0.910594e6) { + if (price < 0.904344e6) { return PriceData( - 1.224339e6, + 0.904344e6, 0.0e18, - 1.775844690297405881e18, - 1.214961e6, + 1.345868338324129665e18, + 0.898101e6, 0.0e18, - 1.741024206173927169e18, + 1.372785705090612263e18, 1e18 ); } else { return PriceData( - 1.224339e6, + 0.910594e6, 0.0e18, - 1.775844690297405881e18, - 1.220705e6, + 1.319478763062872151e18, + 0.904344e6, 0.0e18, - 1.762341683200000064e18, + 1.345868338324129665e18, 1e18 ); } } else { - if (price < 1.233883e6) { + return PriceData( + 0.916852e6, + 0.0e18, + 1.293606630453796313e18, + 0.910594e6, + 0.0e18, + 1.319478763062872151e18, + 1e18 + ); + } + } + } else { + if (price < 0.929402e6) { + if (price < 0.9266e6) { + if (price < 0.92312e6) { return PriceData( - 1.2436e6, + 0.92312e6, 0.0e18, - 1.847588815785421001e18, - 1.224339e6, + 1.268241794562545266e18, + 0.916852e6, 0.0e18, - 1.775844690297405881e18, + 1.293606630453796313e18, 1e18 ); } else { return PriceData( - 1.2436e6, + 0.9266e6, 0.0e18, - 1.847588815785421001e18, - 1.233883e6, + 1.254399999999999959e18, + 0.92312e6, 0.0e18, - 1.811361584103353684e18, + 1.268241794562545266e18, 1e18 ); } + } else { + return PriceData( + 0.929402e6, + 0.0e18, + 1.243374308394652239e18, + 0.9266e6, + 0.0e18, + 1.254399999999999959e18, + 1e18 + ); } } else { - if (price < 1.263572e6) { - if (price < 1.253494e6) { + if (price < 0.935697e6) { + return PriceData( + 0.935697e6, + 0.0e18, + 1.218994419994757328e18, + 0.929402e6, + 0.0e18, + 1.243374308394652239e18, + 1e18 + ); + } else { + return PriceData( + 0.94201e6, + 0.0e18, + 1.195092568622310836e18, + 0.935697e6, + 0.0e18, + 1.218994419994757328e18, + 1e18 + ); + } + } + } + } else { + if (price < 0.96748e6) { + if (price < 0.961075e6) { + if (price < 0.954697e6) { + if (price < 0.948343e6) { return PriceData( - 1.263572e6, + 0.948343e6, 0.0e18, - 1.92223140394315184e18, - 1.2436e6, + 1.171659381002265521e18, + 0.94201e6, 0.0e18, - 1.847588815785421001e18, + 1.195092568622310836e18, 1e18 ); } else { return PriceData( - 1.263572e6, + 0.954697e6, 0.0e18, - 1.92223140394315184e18, - 1.253494e6, + 1.14868566764928004e18, + 0.948343e6, 0.0e18, - 1.884540592101129342e18, + 1.171659381002265521e18, 1e18 ); } } else { - if (price < 1.273838e6) { + return PriceData( + 0.961075e6, + 0.0e18, + 1.12616241926400007e18, + 0.954697e6, + 0.0e18, + 1.14868566764928004e18, + 1e18 + ); + } + } else { + if (price < 0.962847e6) { + return PriceData( + 0.962847e6, + 0.0e18, + 1.120000000000000107e18, + 0.961075e6, + 0.0e18, + 1.12616241926400007e18, + 1e18 + ); + } else { + return PriceData( + 0.96748e6, + 0.0e18, + 1.104080803200000016e18, + 0.962847e6, + 0.0e18, + 1.120000000000000107e18, + 1e18 + ); + } + } + } else { + if (price < 0.986882e6) { + if (price < 0.98038e6) { + if (price < 0.973914e6) { return PriceData( - 1.277347e6, + 0.973914e6, 0.0e18, - 1.973822685183999948e18, - 1.263572e6, + 1.082432159999999977e18, + 0.96748e6, 0.0e18, - 1.92223140394315184e18, + 1.104080803200000016e18, 1e18 ); } else { return PriceData( - 1.277347e6, + 0.98038e6, 0.0e18, - 1.973822685183999948e18, - 1.273838e6, + 1.061208000000000151e18, + 0.973914e6, 0.0e18, - 1.960676032022014903e18, + 1.082432159999999977e18, 1e18 ); } + } else { + return PriceData( + 0.986882e6, + 0.0e18, + 1.040399999999999991e18, + 0.98038e6, + 0.0e18, + 1.061208000000000151e18, + 1e18 + ); + } + } else { + if (price < 0.993421e6) { + return PriceData( + 0.993421e6, + 0.0e18, + 1.020000000000000018e18, + 0.986882e6, + 0.0e18, + 1.040399999999999991e18, + 1e18 + ); + } else { + return PriceData( + 1.006758e6, + 0.0e18, + 0.980000000000000093e18, + 0.993421e6, + 0.0e18, + 1.020000000000000018e18, + 1e18 + ); } } - } else { - if (price < 1.316927e6) { - if (price < 1.294966e6) { - if (price < 1.284301e6) { + } + } + } + } else { + if (price < 1.140253e6) { + if (price < 1.077582e6) { + if (price < 1.04366e6) { + if (price < 1.027335e6) { + if (price < 1.020422e6) { + if (price < 1.013564e6) { return PriceData( - 1.294966e6, + 1.013564e6, 0.0e18, - 2.039887343715704127e18, - 1.277347e6, + 0.960400000000000031e18, + 1.006758e6, 0.0e18, - 1.973822685183999948e18, + 0.980000000000000093e18, 1e18 ); } else { return PriceData( - 1.294966e6, + 1.020422e6, 0.0e18, - 2.039887343715704127e18, - 1.284301e6, + 0.941192000000000029e18, + 1.013564e6, 0.0e18, - 1.999889552662455161e18, + 0.960400000000000031e18, 1e18 ); } } else { - if (price < 1.305839e6) { + return PriceData( + 1.027335e6, + 0.0e18, + 0.922368159999999992e18, + 1.020422e6, + 0.0e18, + 0.941192000000000029e18, + 1e18 + ); + } + } else { + if (price < 1.041342e6) { + if (price < 1.034307e6) { return PriceData( - 1.316927e6, + 1.034307e6, 0.0e18, - 2.122298792401818623e18, - 1.294966e6, + 0.903920796799999926e18, + 1.027335e6, 0.0e18, - 2.039887343715704127e18, + 0.922368159999999992e18, 1e18 ); } else { return PriceData( - 1.316927e6, + 1.041342e6, 0.0e18, - 2.122298792401818623e18, - 1.305839e6, + 0.885842380864000023e18, + 1.034307e6, 0.0e18, - 2.080685090590018493e18, + 0.903920796799999926e18, 1e18 ); } + } else { + return PriceData( + 1.04366e6, + 0.0e18, + 0.880000000000000004e18, + 1.041342e6, + 0.0e18, + 0.885842380864000023e18, + 1e18 + ); } - } else { - if (price < 1.339776e6) { - if (price < 1.328238e6) { + } + } else { + if (price < 1.062857e6) { + if (price < 1.055613e6) { + if (price < 1.048443e6) { return PriceData( - 1.339776e6, + 1.048443e6, 0.0e18, - 2.208039663614852266e18, - 1.316927e6, + 0.868125533246720038e18, + 1.04366e6, 0.0e18, - 2.122298792401818623e18, + 0.880000000000000004e18, 1e18 ); } else { return PriceData( - 1.339776e6, + 1.055613e6, 0.0e18, - 2.208039663614852266e18, - 1.328238e6, + 0.8507630225817856e18, + 1.048443e6, 0.0e18, - 2.164744768249855067e18, + 0.868125533246720038e18, 1e18 ); } } else { return PriceData( - 1.34048e6, + 1.062857e6, + 0.0e18, + 0.833747762130149894e18, + 1.055613e6, + 0.0e18, + 0.8507630225817856e18, + 1e18 + ); + } + } else { + if (price < 1.070179e6) { + return PriceData( + 1.070179e6, + 0.0e18, + 0.81707280688754691e18, + 1.062857e6, + 0.0e18, + 0.833747762130149894e18, + 1e18 + ); + } else { + return PriceData( + 1.077582e6, 0.0e18, - 2.210681407406080101e18, - 1.339776e6, + 0.800731350749795956e18, + 1.070179e6, 0.0e18, - 2.208039663614852266e18, + 0.81707280688754691e18, 1e18 ); } } } } else { - if (price < 2.267916e6) { - if (price < 1.685433e6) { - if (price < 1.491386e6) { - if (price < 1.411358e6) { + if (price < 1.108094e6) { + if (price < 1.09265e6) { + if (price < 1.090025e6) { + if (price < 1.085071e6) { return PriceData( - 1.491386e6, + 1.085071e6, 0.0e18, - 2.773078757450186949e18, - 1.34048e6, + 0.784716723734800059e18, + 1.077582e6, 0.0e18, - 2.210681407406080101e18, + 0.800731350749795956e18, 1e18 ); } else { return PriceData( - 1.491386e6, + 1.090025e6, 0.0e18, - 2.773078757450186949e18, - 1.411358e6, + 0.774399999999999977e18, + 1.085071e6, 0.0e18, - 2.475963176294809553e18, + 0.784716723734800059e18, 1e18 ); } } else { - if (price < 1.58215e6) { - return PriceData( - 1.685433e6, - 0.0e18, - 3.478549993345514402e18, - 1.491386e6, - 0.0e18, - 2.773078757450186949e18, - 1e18 - ); - } else { - return PriceData( - 1.685433e6, - 0.0e18, - 3.478549993345514402e18, - 1.58215e6, - 0.0e18, - 3.105848208344209382e18, - 1e18 - ); - } + return PriceData( + 1.09265e6, + 0.0e18, + 0.769022389260104022e18, + 1.090025e6, + 0.0e18, + 0.774399999999999977e18, + 1e18 + ); } } else { - if (price < 1.937842e6) { - if (price < 1.803243e6) { + if (price < 1.100323e6) { + return PriceData( + 1.100323e6, + 0.0e18, + 0.753641941474902044e18, + 1.09265e6, + 0.0e18, + 0.769022389260104022e18, + 1e18 + ); + } else { + return PriceData( + 1.108094e6, + 0.0e18, + 0.738569102645403985e18, + 1.100323e6, + 0.0e18, + 0.753641941474902044e18, + 1e18 + ); + } + } + } else { + if (price < 1.132044e6) { + if (price < 1.123949e6) { + if (price < 1.115967e6) { return PriceData( - 1.937842e6, + 1.115967e6, 0.0e18, - 4.363493111652613443e18, - 1.685433e6, + 0.723797720592495919e18, + 1.108094e6, 0.0e18, - 3.478549993345514402e18, + 0.738569102645403985e18, 1e18 ); } else { return PriceData( - 1.937842e6, + 1.123949e6, 0.0e18, - 4.363493111652613443e18, - 1.803243e6, + 0.709321766180645907e18, + 1.115967e6, 0.0e18, - 3.89597599254697613e18, + 0.723797720592495919e18, 1e18 ); } } else { - if (price < 2.091777e6) { + return PriceData( + 1.132044e6, + 0.0e18, + 0.695135330857033051e18, + 1.123949e6, + 0.0e18, + 0.709321766180645907e18, + 1e18 + ); + } + } else { + if (price < 1.14011e6) { + return PriceData( + 1.14011e6, + 0.0e18, + 0.681471999999999967e18, + 1.132044e6, + 0.0e18, + 0.695135330857033051e18, + 1e18 + ); + } else { + return PriceData( + 1.140253e6, + 0.0e18, + 0.681232624239892393e18, + 1.14011e6, + 0.0e18, + 0.681471999999999967e18, + 1e18 + ); + } + } + } + } + } else { + if (price < 2.01775e6) { + if (price < 1.403579e6) { + if (price < 1.256266e6) { + if (price < 1.195079e6) { + if (price < 1.148586e6) { return PriceData( - 2.267916e6, + 1.148586e6, 0.0e18, - 5.473565759257038366e18, - 1.937842e6, + 0.667607971755094454e18, + 1.140253e6, 0.0e18, - 4.363493111652613443e18, + 0.681232624239892393e18, 1e18 ); } else { return PriceData( - 2.267916e6, + 1.195079e6, 0.0e18, - 5.473565759257038366e18, - 2.091777e6, + 0.599695360000000011e18, + 1.148586e6, 0.0e18, - 4.887112285050926097e18, + 0.667607971755094454e18, 1e18 ); } + } else { + return PriceData( + 1.256266e6, + 0.0e18, + 0.527731916799999978e18, + 1.195079e6, + 0.0e18, + 0.599695360000000011e18, + 1e18 + ); + } + } else { + if (price < 1.325188e6) { + return PriceData( + 1.325188e6, + 0.0e18, + 0.464404086784000025e18, + 1.256266e6, + 0.0e18, + 0.527731916799999978e18, + 1e18 + ); + } else { + return PriceData( + 1.403579e6, + 0.0e18, + 0.408675596369920013e18, + 1.325188e6, + 0.0e18, + 0.464404086784000025e18, + 1e18 + ); } } } else { - if (price < 3.265419e6) { - if (price < 2.700115e6) { - if (price < 2.469485e6) { + if (price < 1.716848e6) { + if (price < 1.596984e6) { + if (price < 1.493424e6) { return PriceData( - 2.700115e6, + 1.493424e6, 0.0e18, - 6.866040888412029197e18, - 2.267916e6, + 0.359634524805529598e18, + 1.403579e6, 0.0e18, - 5.473565759257038366e18, + 0.408675596369920013e18, 1e18 ); } else { return PriceData( - 2.700115e6, + 1.596984e6, 0.0e18, - 6.866040888412029197e18, - 2.469485e6, + 0.316478381828866062e18, + 1.493424e6, 0.0e18, - 6.130393650367882863e18, + 0.359634524805529598e18, 1e18 ); } } else { - if (price < 2.963895e6) { + return PriceData( + 1.716848e6, + 0.0e18, + 0.278500976009402101e18, + 1.596984e6, + 0.0e18, + 0.316478381828866062e18, + 1e18 + ); + } + } else { + if (price < 1.855977e6) { + return PriceData( + 1.855977e6, + 0.0e18, + 0.245080858888273884e18, + 1.716848e6, + 0.0e18, + 0.278500976009402101e18, + 1e18 + ); + } else { + return PriceData( + 2.01775e6, + 0.0e18, + 0.215671155821681004e18, + 1.855977e6, + 0.0e18, + 0.245080858888273884e18, + 1e18 + ); + } + } + } + } else { + if (price < 3.322705e6) { + if (price < 2.680458e6) { + if (price < 2.425256e6) { + if (price < 2.206036e6) { return PriceData( - 3.265419e6, + 2.206036e6, 0.0e18, - 8.612761690424049377e18, - 2.700115e6, + 0.189790617123079292e18, + 2.01775e6, 0.0e18, - 6.866040888412029197e18, + 0.215671155821681004e18, 1e18 ); } else { return PriceData( - 3.265419e6, + 2.425256e6, 0.0e18, - 8.612761690424049377e18, - 2.963895e6, + 0.167015743068309769e18, + 2.206036e6, 0.0e18, - 7.689965795021471706e18, + 0.189790617123079292e18, 1e18 ); } + } else { + return PriceData( + 2.680458e6, + 0.0e18, + 0.146973853900112583e18, + 2.425256e6, + 0.0e18, + 0.167015743068309769e18, + 1e18 + ); } } else { - if (price < 10.370891e6) { - if (price < 3.60986e6) { + if (price < 2.977411e6) { + return PriceData( + 2.977411e6, + 0.0e18, + 0.129336991432099091e18, + 2.680458e6, + 0.0e18, + 0.146973853900112583e18, + 1e18 + ); + } else { + return PriceData( + 3.322705e6, + 0.0e18, + 0.113816552460247203e18, + 2.977411e6, + 0.0e18, + 0.129336991432099091e18, + 1e18 + ); + } + } + } else { + if (price < 4.729321e6) { + if (price < 4.189464e6) { + if (price < 3.723858e6) { return PriceData( - 10.370891e6, + 3.723858e6, 0.0e18, - 28.000000000000003553e18, - 3.265419e6, + 0.100158566165017532e18, + 3.322705e6, 0.0e18, - 8.612761690424049377e18, + 0.113816552460247203e18, 1e18 ); } else { return PriceData( - 10.370891e6, + 4.189464e6, 0.0e18, - 28.000000000000003553e18, - 3.60986e6, + 0.088139538225215433e18, + 3.723858e6, 0.0e18, - 9.646293093274934449e18, + 0.100158566165017532e18, 1e18 ); } + } else { + return PriceData( + 4.729321e6, + 0.0e18, + 0.077562793638189589e18, + 4.189464e6, + 0.0e18, + 0.088139538225215433e18, + 1e18 + ); + } + } else { + if (price < 10.37089e6) { + return PriceData( + 10.37089e6, + 0.0e18, + 0.035714285714285712e18, + 4.729321e6, + 0.0e18, + 0.077562793638189589e18, + 1e18 + ); } else { revert("LUT: Invalid price"); } @@ -1517,9 +1037,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.237671e6) { return PriceData( - 0.264147e6, - 0.222936352980619729e18, - 2.26657220303422724e18, + 0.237671e6, + 0.20510144474217018e18, + 2.337718072004858261e18, 0.213318e6, 0.188693329162796575e18, 2.410556040105746423e18, @@ -1541,9 +1061,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.323531e6) { if (price < 0.292771e6) { return PriceData( - 0.323531e6, - 0.263393611744588529e18, - 2.128468246736633152e18, + 0.292771e6, + 0.242322122805021467e18, + 2.196897480682568293e18, 0.264147e6, 0.222936352980619729e18, 2.26657220303422724e18, @@ -1563,9 +1083,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.356373e6) { return PriceData( - 0.391201e6, - 0.311192830511092366e18, - 1.994416599735895801e18, + 0.356373e6, + 0.286297404070204931e18, + 2.061053544007124483e18, 0.323531e6, 0.263393611744588529e18, 2.128468246736633152e18, @@ -1589,9 +1109,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.466197e6) { if (price < 0.427871e6) { return PriceData( - 0.466197e6, - 0.367666387654882243e18, - 1.86249753363281334e18, + 0.427871e6, + 0.338253076642491657e18, + 1.92831441898410505e18, 0.391201e6, 0.311192830511092366e18, 1.994416599735895801e18, @@ -1611,9 +1131,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.50596e6) { return PriceData( - 0.546918e6, - 0.434388454223632148e18, - 1.73068952191306602e18, + 0.50596e6, + 0.399637377885741607e18, + 1.796709969924970451e18, 0.466197e6, 0.367666387654882243e18, 1.86249753363281334e18, @@ -1635,9 +1155,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.631434e6) { if (price < 0.588821e6) { return PriceData( - 0.631434e6, - 0.513218873137561538e18, - 1.596874796852916001e18, + 0.588821e6, + 0.472161363286556723e18, + 1.664168452923131536e18, 0.546918e6, 0.434388454223632148e18, 1.73068952191306602e18, @@ -1657,9 +1177,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.67456e6) { return PriceData( - 0.718073e6, - 0.606355001344e18, - 1.458874768183093584e18, + 0.67456e6, + 0.55784660123648e18, + 1.52853450260679824e18, 0.631434e6, 0.513218873137561538e18, 1.596874796852916001e18, @@ -1685,9 +1205,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.769833e6) { if (price < 0.76195e6) { return PriceData( - 0.769833e6, - 0.668971758569680497e18, - 1.37471571145172633e18, + 0.76195e6, + 0.659081523200000019e18, + 1.387629060213009469e18, 0.718073e6, 0.606355001344e18, 1.458874768183093584e18, @@ -1707,9 +1227,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.775161e6) { return PriceData( - 0.780497e6, - 0.682554595010387288e18, - 1.357193251389227306e18, + 0.775161e6, + 0.675729049060283415e18, + 1.365968375000512491e18, 0.769833e6, 0.668971758569680497e18, 1.37471571145172633e18, @@ -1731,9 +1251,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.791195e6) { if (price < 0.785842e6) { return PriceData( - 0.791195e6, - 0.696413218049573679e18, - 1.339558007037547016e18, + 0.785842e6, + 0.689449085869078049e18, + 1.34838993014876074e18, 0.780497e6, 0.682554595010387288e18, 1.357193251389227306e18, @@ -1753,9 +1273,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.796558e6) { return PriceData( - 0.801931e6, - 0.710553227272292309e18, - 1.321806771708554873e18, + 0.796558e6, + 0.703447694999569495e18, + 1.330697084427678423e18, 0.791195e6, 0.696413218049573679e18, 1.339558007037547016e18, @@ -1779,9 +1299,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.807315e6) { if (price < 0.806314e6) { return PriceData( - 0.807315e6, - 0.717730532598275128e18, - 1.312886685708826162e18, + 0.806314e6, + 0.716392959999999968e18, + 1.314544530202049311e18, 0.801931e6, 0.710553227272292309e18, 1.321806771708554873e18, @@ -1801,9 +1321,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.812711e6) { return PriceData( - 0.818119e6, - 0.732303369654397684e18, - 1.294955701044462559e18, + 0.812711e6, + 0.724980335957853717e18, + 1.303936451137418295e18, 0.807315e6, 0.717730532598275128e18, 1.312886685708826162e18, @@ -1824,10 +1344,10 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.828976e6) { if (price < 0.82354e6) { - return PriceData( - 0.828976e6, - 0.74717209433159637e18, - 1.276901231112211654e18, + return PriceData( + 0.82354e6, + 0.73970037338828043e18, + 1.285944077302980215e18, 0.818119e6, 0.732303369654397684e18, 1.294955701044462559e18, @@ -1865,9 +1385,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.845379e6) { if (price < 0.839894e6) { return PriceData( - 0.845379e6, - 0.770043145805155316e18, - 1.249582020939133509e18, + 0.839894e6, + 0.762342714347103767e18, + 1.258720525989716954e18, 0.834426e6, 0.754719287203632794e18, 1.267826823523503732e18, @@ -1887,9 +1407,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.850882e6) { return PriceData( - 0.851493e6, - 0.778688000000000047e18, - 1.239392846883276889e18, + 0.850882e6, + 0.777821359399146761e18, + 1.240411002374896432e18, 0.845379e6, 0.770043145805155316e18, 1.249582020939133509e18, @@ -1911,9 +1431,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.86195e6) { if (price < 0.856405e6) { return PriceData( - 0.86195e6, - 0.793614283643655494e18, - 1.221970262376178118e18, + 0.856405e6, + 0.785678140807218983e18, + 1.231207176501035727e18, 0.851493e6, 0.778688000000000047e18, 1.239392846883276889e18, @@ -1933,9 +1453,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.867517e6) { return PriceData( - 0.873109e6, - 0.809727868221258529e18, - 1.203396114006087814e18, + 0.867517e6, + 0.801630589539045979e18, + 1.212699992596070864e18, 0.86195e6, 0.793614283643655494e18, 1.221970262376178118e18, @@ -1959,9 +1479,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.884372e6) { if (price < 0.878727e6) { return PriceData( - 0.884372e6, - 0.826168623835586646e18, - 1.18468659352065786e18, + 0.878727e6, + 0.817906937597230987e18, + 1.194058388444914964e18, 0.873109e6, 0.809727868221258529e18, 1.203396114006087814e18, @@ -1981,9 +1501,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.890047e6) { return PriceData( - 0.895753e6, - 0.84294319338392687e18, - 1.16583998975613734e18, + 0.890047e6, + 0.834513761450087599e18, + 1.17528052342063094e18, 0.884372e6, 0.826168623835586646e18, 1.18468659352065786e18, @@ -2005,9 +1525,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.901491e6) { if (price < 0.898085e6) { return PriceData( - 0.901491e6, - 0.851457771094875637e18, - 1.156364822443562979e18, + 0.898085e6, + 0.846400000000000041e18, + 1.161985895520041945e18, 0.895753e6, 0.84294319338392687e18, 1.16583998975613734e18, @@ -2043,9 +1563,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.918932e6) { if (price < 0.913079e6) { return PriceData( - 0.918932e6, - 0.877521022998967948e18, - 1.127730111926438461e18, + 0.913079e6, + 0.868745812768978332e18, + 1.137310003616810228e18, 0.907266e6, 0.860058354641288547e18, 1.146854870623147615e18, @@ -2065,9 +1585,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.924827e6) { return PriceData( - 0.930767e6, - 0.895338254258716493e18, - 1.10846492868530544e18, + 0.924827e6, + 0.88638487171612923e18, + 1.118115108274055913e18, 0.918932e6, 0.877521022998967948e18, 1.127730111926438461e18, @@ -2089,9 +1609,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.942795e6) { if (price < 0.936756e6) { return PriceData( - 0.942795e6, - 0.913517247483640937e18, - 1.089058909134983155e18, + 0.936756e6, + 0.90438207500880452e18, + 1.09877953361768621e18, 0.930767e6, 0.895338254258716493e18, 1.10846492868530544e18, @@ -2111,9 +1631,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.947076e6) { return PriceData( - 0.948888e6, - 0.922744694427920065e18, - 1.079303068129318754e18, + 0.947076e6, + 0.92000000000000004e18, + 1.082198372170484424e18, 0.942795e6, 0.913517247483640937e18, 1.089058909134983155e18, @@ -2137,9 +1657,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.961249e6) { if (price < 0.955039e6) { return PriceData( - 0.961249e6, - 0.941480149400999999e18, - 1.059685929936267312e18, + 0.955039e6, + 0.932065347906990027e18, + 1.069512051592246715e18, 0.948888e6, 0.922744694427920065e18, 1.079303068129318754e18, @@ -2159,9 +1679,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 0.967525e6) { return PriceData( - 0.973868e6, - 0.960596010000000056e18, - 1.039928808315135234e18, + 0.967525e6, + 0.950990049900000023e18, + 1.049824804368118425e18, 0.961249e6, 0.941480149400999999e18, 1.059685929936267312e18, @@ -2183,9 +1703,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.986773e6) { if (price < 0.980283e6) { return PriceData( - 0.986773e6, - 0.980099999999999971e18, - 1.020032908506394831e18, + 0.980283e6, + 0.970299000000000134e18, + 1.029998108905910481e18, 0.973868e6, 0.960596010000000056e18, 1.039928808315135234e18, @@ -2225,9 +1745,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.01345e6) { if (price < 1.006679e6) { return PriceData( - 1.01345e6, - 1.020100000000000007e18, - 0.980033797419900599e18, + 1.006679e6, + 1.010000000000000009e18, + 0.990033224058159078e18, 0.993344e6, 0.989999999999999991e18, 1.01003344631248293e18, @@ -2247,9 +1767,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.020319e6) { return PriceData( - 1.027293e6, - 1.040604010000000024e18, - 0.959938599971011053e18, + 1.020319e6, + 1.030300999999999911e18, + 0.970002111104709575e18, 1.01345e6, 1.020100000000000007e18, 0.980033797419900599e18, @@ -2271,9 +1791,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.034375e6) { if (price < 1.033686e6) { return PriceData( - 1.034375e6, - 1.051010050100000148e18, - 0.949843744564435544e18, + 1.033686e6, + 1.050000000000000044e18, + 0.950820553711780869e18, 1.027293e6, 1.040604010000000024e18, 0.959938599971011053e18, @@ -2293,9 +1813,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.041574e6) { return PriceData( - 1.048893e6, - 1.072135352107010053e18, - 0.929562163027227939e18, + 1.041574e6, + 1.061520150601000134e18, + 0.93971807302139454e18, 1.034375e6, 1.051010050100000148e18, 0.949843744564435544e18, @@ -2319,9 +1839,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.063925e6) { if (price < 1.056342e6) { return PriceData( - 1.063925e6, - 1.093685272684360887e18, - 0.90916219829307332e18, + 1.056342e6, + 1.082856705628080007e18, + 0.919376643827810258e18, 1.048893e6, 1.072135352107010053e18, 0.929562163027227939e18, @@ -2341,9 +1861,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.070147e6) { return PriceData( - 1.071652e6, - 1.104622125411204525e18, - 0.89891956503043724e18, + 1.070147e6, + 1.102500000000000036e18, + 0.900901195775543062e18, 1.063925e6, 1.093685272684360887e18, 0.90916219829307332e18, @@ -2365,9 +1885,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.087566e6) { if (price < 1.079529e6) { return PriceData( - 1.087566e6, - 1.126825030131969774e18, - 0.878352981447521719e18, + 1.079529e6, + 1.115668346665316557e18, + 0.888649540545595529e18, 1.071652e6, 1.104622125411204525e18, 0.89891956503043724e18, @@ -2403,9 +1923,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.110215e6) { if (price < 1.104151e6) { return PriceData( - 1.110215e6, - 1.157625000000000126e18, - 0.850322213751246947e18, + 1.104151e6, + 1.149474213237622333e18, + 0.857683999872391523e18, 1.09577e6, 1.1380932804332895e18, 0.868030806693890433e18, @@ -2425,9 +1945,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.112718e6) { return PriceData( - 1.121482e6, - 1.172578644923698565e18, - 0.836920761422192294e18, + 1.112718e6, + 1.160968955369998667e18, + 0.847313611512600207e18, 1.110215e6, 1.157625000000000126e18, 0.850322213751246947e18, @@ -2449,9 +1969,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.139642e6) { if (price < 1.130452e6) { return PriceData( - 1.139642e6, - 1.196147475686665018e18, - 0.8160725157999702e18, + 1.130452e6, + 1.184304431372935618e18, + 0.826506641040327228e18, 1.121482e6, 1.172578644923698565e18, 0.836920761422192294e18, @@ -2471,9 +1991,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.149062e6) { return PriceData( - 1.15496e6, - 1.21550625000000001e18, - 0.799198479643147719e18, + 1.149062e6, + 1.208108950443531393e18, + 0.805619727489791271e18, 1.139642e6, 1.196147475686665018e18, 0.8160725157999702e18, @@ -2497,9 +2017,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.168643e6) { if (price < 1.158725e6) { return PriceData( - 1.168643e6, - 1.232391940347446369e18, - 0.784663924675502389e18, + 1.158725e6, + 1.22019003994796682e18, + 0.795149696605042422e18, 1.15496e6, 1.21550625000000001e18, 0.799198479643147719e18, @@ -2519,9 +2039,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.178832e6) { return PriceData( - 1.189304e6, - 1.257163018348430139e18, - 0.763651582672810969e18, + 1.178832e6, + 1.244715859750920917e18, + 0.774163996557160172e18, 1.168643e6, 1.232391940347446369e18, 0.784663924675502389e18, @@ -2543,9 +2063,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.205768e6) { if (price < 1.200076e6) { return PriceData( - 1.205768e6, - 1.276281562499999911e18, - 0.747685899578659385e18, + 1.200076e6, + 1.269734648531914534e18, + 0.753128441185147435e18, 1.189304e6, 1.257163018348430139e18, 0.763651582672810969e18, @@ -2583,9 +2103,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.234362e6) { if (price < 1.222589e6) { return PriceData( - 1.234362e6, - 1.308208878117080198e18, - 0.721513591905860174e18, + 1.222589e6, + 1.295256314967406119e18, + 0.732057459169776381e18, 1.211166e6, 1.282431995017233595e18, 0.74259642008426785e18, @@ -2605,9 +2125,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.246507e6) { return PriceData( - 1.259043e6, - 1.33450387656723346e18, - 0.700419750561125598e18, + 1.246507e6, + 1.321290966898250874e18, + 0.710966947125877935e18, 1.234362e6, 1.308208878117080198e18, 0.721513591905860174e18, @@ -2629,9 +2149,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.271991e6) { if (price < 1.264433e6) { return PriceData( - 1.271991e6, - 1.347848915332905628e18, - 0.689874326166576179e18, + 1.264433e6, + 1.340095640624999973e18, + 0.695987932996588454e18, 1.259043e6, 1.33450387656723346e18, 0.700419750561125598e18, @@ -2651,9 +2171,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.285375e6) { return PriceData( - 1.299217e6, - 1.374940678531097138e18, - 0.668798587125333244e18, + 1.285375e6, + 1.361327404486234682e18, + 0.67933309721453039e18, 1.271991e6, 1.347848915332905628e18, 0.689874326166576179e18, @@ -2677,9 +2197,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.328377e6) { if (price < 1.313542e6) { return PriceData( - 1.328377e6, - 1.402576986169572049e18, - 0.647760320838866033e18, + 1.313542e6, + 1.38869008531640814e18, + 0.658273420002602916e18, 1.299217e6, 1.374940678531097138e18, 0.668798587125333244e18, @@ -2699,9 +2219,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.333292e6) { return PriceData( - 1.343751e6, - 1.416602756031267951e18, - 0.637262115356114656e18, + 1.333292e6, + 1.407100422656250016e18, + 0.644361360672887962e18, 1.328377e6, 1.402576986169572049e18, 0.647760320838866033e18, @@ -2723,9 +2243,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.376232e6) { if (price < 1.359692e6) { return PriceData( - 1.376232e6, - 1.445076471427496179e18, - 0.616322188162944262e18, + 1.359692e6, + 1.430768783591580551e18, + 0.626781729444674585e18, 1.343751e6, 1.416602756031267951e18, 0.637262115356114656e18, @@ -2761,9 +2281,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.415386e6) { if (price < 1.41124e6) { return PriceData( - 1.415386e6, - 1.47745544378906235e18, - 0.593119977480511928e18, + 1.41124e6, + 1.474122508503188822e18, + 0.595478226183906334e18, 1.393403e6, 1.459527236141771489e18, 0.605886614260108591e18, @@ -2783,9 +2303,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.42978e6) { return PriceData( - 1.514667e6, - 1.551328215978515557e18, - 0.54263432113736132e18, + 1.42978e6, + 1.488863733588220883e18, + 0.585100335536025584e18, 1.415386e6, 1.47745544378906235e18, 0.593119977480511928e18, @@ -2807,9 +2327,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.786708e6) { if (price < 1.636249e6) { return PriceData( - 1.786708e6, - 1.710339358116313546e18, - 0.445648172809785581e18, + 1.636249e6, + 1.628894626777441568e18, + 0.493325115988533236e18, 1.514667e6, 1.551328215978515557e18, 0.54263432113736132e18, @@ -2829,9 +2349,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 1.974398e6) { return PriceData( - 2.209802e6, - 1.885649142323235772e18, - 0.357031765135700119e18, + 1.974398e6, + 1.79585632602212919e18, + 0.400069510798421513e18, 1.786708e6, 1.710339358116313546e18, 0.445648172809785581e18, @@ -2855,9 +2375,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 2.878327e6) { if (price < 2.505865e6) { return PriceData( - 2.878327e6, - 2.078928179411367427e18, - 0.28000760254479623e18, + 2.505865e6, + 1.97993159943939756e18, + 0.316916199929126341e18, 2.209802e6, 1.885649142323235772e18, 0.357031765135700119e18, @@ -2877,9 +2397,9 @@ contract Stable2LUT1 is ILookupTable { } else { if (price < 3.346057e6) { return PriceData( - 3.931396e6, - 2.292018317801032268e18, - 0.216340086006769544e18, + 3.346057e6, + 2.182874588381935599e18, + 0.246470170347584949e18, 2.878327e6, 2.078928179411367427e18, 0.28000760254479623e18, @@ -2901,9 +2421,9 @@ contract Stable2LUT1 is ILookupTable { if (price < 10.709509e6) { if (price < 4.660591e6) { return PriceData( - 10.709509e6, - 3.0e18, - 0.103912563829966526e18, + 4.660591e6, + 2.406619233691083881e18, + 0.189535571483960663e18, 3.931396e6, 2.292018317801032268e18, 0.216340086006769544e18, diff --git a/src/functions/LookupTable.t.sol b/test/Stable2/LookupTable.t.sol similarity index 87% rename from src/functions/LookupTable.t.sol rename to test/Stable2/LookupTable.t.sol index 776a1c47..84de3cd4 100644 --- a/src/functions/LookupTable.t.sol +++ b/test/Stable2/LookupTable.t.sol @@ -12,7 +12,7 @@ contract LookupTableTest is TestHelper { lookupTable = new Stable2LUT1(); } - function test_getAParameter() public { + function test_getAParameter() public view { uint256 a = lookupTable.getAParameter(); assertEq(a, 1); } @@ -45,16 +45,12 @@ contract LookupTableTest is TestHelper { // pick a value close to the min (P=0.01) uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); - console.log("pd.lowPrice: %e", pd.lowPrice); - console.log("pd.highPrice: %e", pd.highPrice); } function test_getRatiosFromPriceSwapExtremeHigh() public { // pick a value close to the max (P=9.85) uint256 currentPrice = 9.84e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); - console.log("pd.lowPrice: %e", pd.lowPrice); - console.log("pd.highPrice: %e", pd.highPrice); } function testFail_getRatiosFromPriceSwapExtremeLow() public { @@ -64,8 +60,8 @@ contract LookupTableTest is TestHelper { } function testFail_getRatiosFromPriceSwapExtremeHigh() public { - // pick an out of bounds value (P>9.85) - uint256 currentPrice = 10e6; + // pick an out of bounds value (P>10.37) + uint256 currentPrice = 11e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); } @@ -97,16 +93,12 @@ contract LookupTableTest is TestHelper { // pick a value close to the min (P=0.01) uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); - console.log("pd.lowPrice: %e", pd.lowPrice); - console.log("pd.highPrice: %e", pd.highPrice); } function test_getRatiosFromPriceLiquidityExtremeHigh() public { // pick a value close to the max (P=9.92) uint256 currentPrice = 9.91e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); - console.log("pd.lowPrice: %e", pd.lowPrice); - console.log("pd.highPrice: %e", pd.highPrice); } function testFail_getRatiosFromPriceLiquidityExtremeLow() public { @@ -116,8 +108,8 @@ contract LookupTableTest is TestHelper { } function testFail_getRatiosFromPriceLiquidityExtremeHigh() public { - // pick an out of bounds value (P>9.85) - uint256 currentPrice = 10e6; + // pick an out of bounds value (P>10.37) + uint256 currentPrice = 11e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); } @@ -141,6 +133,7 @@ contract LookupTableTest is TestHelper { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); assertGe(pd.highPrice, currentPrice); assertLt(pd.lowPrice, currentPrice); + assertLt(pd.highPriceJ / pd.precision, 1e18); currentPrice += 0.01e6; } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index f44c7676..a229a992 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -31,8 +31,8 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 99.997220935618347533e18); - assertEq(reserve1, 99.997220935618347533e18); + assertApproxEqRel(reserve0, 100.002494212050875384e18, 0.0003e18); + assertApproxEqRel(reserve1, 100.002494212050875384e18, 0.0003e18); } function test_calcReserveAtRatioLiquidity_equal_diff() public view { @@ -46,8 +46,8 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 99.997220935618347533e18); - assertEq(reserve1, 49.998610467809173766e18); + assertApproxEqRel(reserve0, 100.002494212050875384e18, 0.0003e18); + assertApproxEqRel(reserve1, 50.001091026498328056e18, 0.0003e18); } function test_calcReserveAtRatioLiquidity_diff_equal() public view { @@ -61,8 +61,8 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 4.576172337359416271e18); - assertEq(reserve1, 0.218464636709548541e18); + assertApproxEqRel(reserve0, 4.575771214546676444e18, 0.0001e18); + assertApproxEqRel(reserve1, 0.21852354514449462e18, 0.0001e18); } function test_calcReserveAtRatioLiquidity_diff_diff() public view { @@ -72,13 +72,12 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256[] memory ratios = new uint256[](2); ratios[0] = 12; ratios[1] = 10; - // p = 1.2 uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertEq(reserve0, 1.685434381143450467e18); - assertEq(reserve1, 0.593220305288953143e18); + assertApproxEqRel(reserve0, 1.685591553208758586e18, 0.0004e18); + assertApproxEqRel(reserve1, 1.18623685249742594e18, 0.0004e18); } function test_calcReserveAtRatioLiquidity_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public view { @@ -109,13 +108,9 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reservePrice0 = _f.calcRate(r0Updated, 0, 1, data); uint256 reservePrice1 = _f.calcRate(r1Updated, 0, 1, data); - uint256 targetPriceInverted = ratios[1] * 1e6 / ratios[0]; - uint256 reservePrice0Inverted = _f.calcRate(r0Updated, 1, 0, data); - uint256 reservePrice1Inverted = _f.calcRate(r1Updated, 1, 0, data); - // estimated price and actual price are within 0.04% in the worst case. - assertApproxEqRel(targetPrice, reservePrice0, 0.0004e18, "targetPrice <> reservePrice0"); - assertApproxEqRel(targetPrice, reservePrice1, 0.0004e18, "targetPrice <> reservePrice1"); + assertApproxEqRel(reservePrice0, targetPrice, 0.0004e18, "reservePrice0 <> targetPrice"); + assertApproxEqRel(reservePrice1, targetPrice, 0.0004e18, "reservePrice1 <> targetPrice"); assertApproxEqRel(reservePrice0, reservePrice1, 0.0004e18, "reservePrice0 <> reservePrice1"); } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 43c38141..2adc8884 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -31,8 +31,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 100e18); - assertEq(reserve1, 100e18); + assertEq(reserve0, 100.005058322101089709e18); + assertEq(reserve1, 100.005058322101089709e18); } function test_calcReserveAtRatioSwap_equal_diff() public view { @@ -46,10 +46,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - // assertEq(reserve0, 74); // 50 - // assertEq(reserve1, 74); // 100 - console.log("reserve0", reserve0); - console.log("reserve1", reserve1); + assertEq(reserve0, 73.517644476151580971e18); + assertEq(reserve1, 73.517644476151580971e18); } function test_calcReserveAtRatioSwap_diff_equal() public view { @@ -63,16 +61,14 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - // assertEq(reserve0, 149); - // assertEq(reserve1, 74); - console.log("reserve0", reserve0); - console.log("reserve1", reserve1); + assertEq(reserve0, 180.643950056605911775e18); // 100e18, 180.64235400499155996e18 + assertEq(reserve1, 39.474893649094790166e18); // 39.474893649094790166e18, 100e18 } function test_calcReserveAtRatioSwap_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 90; // bean - reserves[1] = 110; // usdc + reserves[0] = 90e18; // bean + reserves[1] = 110e18; // usdc uint256[] memory ratios = new uint256[](2); ratios[0] = 110; ratios[1] = 90; @@ -80,9 +76,30 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - // assertEq(reserve0, 110); - // assertEq(reserve1, 91); - console.log("reserve0", reserve0); - console.log("reserve1", reserve1); + assertEq(reserve0, 129.268187496764805614e18); // 90e18, 129.268187496764805614e18 + assertEq(reserve1, 73.11634314279891828e18); // 73.116252343760233529e18, 110e18 + } + + function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public view { + for (uint256 i; i < 2; ++i) { + // Upper bound is limited by stableSwap, + // due to the stableswap reserves being extremely far apart. + reserves[i] = bound(reserves[i], 1e18, 1e31); + ratios[i] = bound(ratios[i], 1e18, 4e18); + } + + // create 2 new reserves, one where reserve[0] is updated, and one where reserve[1] is updated. + uint256[] memory updatedReserves = new uint256[](2); + for (uint256 i; i < 2; ++i) { + updatedReserves[i] = _f.calcReserveAtRatioSwap(uint2ToUintN(reserves), i, uint2ToUintN(ratios), data); + } + + { + uint256 targetPrice = ratios[0] * 1e6 / ratios[1]; + uint256 reservePrice0 = _f.calcRate(updatedReserves, 0, 1, data); + + // estimated price and actual price are within 0.04% in the worst case. + assertApproxEqRel(reservePrice0, targetPrice, 0.0004e18, "reservePrice0 <> targetPrice"); + } } } From 81ac70adb0a0a84890471f5c40fa394434b8f693 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 16 Jul 2024 00:52:43 +0200 Subject: [PATCH 30/69] gas optimization I --- src/functions/Stable2.sol | 27 +++++++++++++------ ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 12 ++++----- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index c35d674e..e570f145 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -170,12 +170,25 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // calc lp token supply (note: `scaledReserves` is scaled up, and does not require bytes). uint256 lpTokenSupply = calcLpTokenSupply(scaledReserves, abi.encode(18, 18)); + rate = _calcRate(scaledReserves, i, j, lpTokenSupply); + } + + /** + * @notice internal calcRate function. + */ + function _calcRate( + uint256[] memory reserves, + uint256 i, + uint256 j, + uint256 lpTokenSupply + ) internal view returns (uint256 rate) { // add 1e6 to reserves: - scaledReserves[j] += PRICE_PRECISION; + uint256[] memory _reserves = new uint256[](2); + _reserves[i] = reserves[i]; + _reserves[j] = reserves[j] + PRICE_PRECISION; - // calculate new reserve 1: - uint256 new_reserve1 = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - rate = (scaledReserves[i] - new_reserve1); + // calculate rate: + rate = _reserves[i] - calcReserve(_reserves, i, lpTokenSupply, abi.encode(18, 18)); } /** @@ -234,14 +247,12 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } for (uint256 k; k < 255; k++) { - // scale stepSize proporitional to distance from price: - scaledReserves[j] = updateReserve(pd, scaledReserves[j]); + // calculate scaledReserve[i]: scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - // calc currentPrice: - pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); + pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply); // check if new price is within 1 of target price: if (pd.currentPrice > pd.targetPrice) { diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 2adc8884..dc837197 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -61,14 +61,14 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 180.643950056605911775e18); // 100e18, 180.64235400499155996e18 - assertEq(reserve1, 39.474893649094790166e18); // 39.474893649094790166e18, 100e18 + assertEq(reserve0, 180.643950056605911775e18); // 180.64235400499155996e18, 100e18 + assertEq(reserve1, 39.474875366590812867e18); // 100e18, 39.474875366590812867e18 } function test_calcReserveAtRatioSwap_diff_diff() public view { uint256[] memory reserves = new uint256[](2); - reserves[0] = 90e18; // bean - reserves[1] = 110e18; // usdc + reserves[0] = 90e18; + reserves[1] = 110e18; uint256[] memory ratios = new uint256[](2); ratios[0] = 110; ratios[1] = 90; @@ -76,8 +76,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 129.268187496764805614e18); // 90e18, 129.268187496764805614e18 - assertEq(reserve1, 73.11634314279891828e18); // 73.116252343760233529e18, 110e18 + assertEq(reserve0, 129.268187496764805614e18); // 129.268187496764805614e18, 90e18 + assertEq(reserve1, 73.11634314279891828e18); // 110e18, 73.116252343760233529e18 } function test_calcReserveAtRatioSwap_fuzz(uint256[2] memory reserves, uint256[2] memory ratios) public view { From 3e79463d70205a0360d0fb74e9b5391e3dfac713 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 16 Jul 2024 01:21:24 +0200 Subject: [PATCH 31/69] remove safeMath --- src/functions/Stable2.sol | 39 +++++++++++++++------------------------ 1 file changed, 15 insertions(+), 24 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index e570f145..c65cff55 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -6,9 +6,7 @@ import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces import {ILookupTable} from "src/interfaces/ILookupTable.sol"; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {LibMath} from "src/libraries/LibMath.sol"; -import {SafeMath} from "oz/utils/math/SafeMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; -import {console} from "forge-std/console.sol"; /** * @author Brean * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. @@ -34,7 +32,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } using LibMath for uint256; - using SafeMath for uint256; // 2 token Pool. uint256 constant N = 2; @@ -94,12 +91,11 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { for (uint256 i = 0; i < 255; i++) { uint256 dP = lpTokenSupply; // If division by 0, this will be borked: only withdrawal will work. And that is good - dP = dP.mul(lpTokenSupply).div(scaledReserves[0].mul(N)); - dP = dP.mul(lpTokenSupply).div(scaledReserves[1].mul(N)); + dP = dP * lpTokenSupply / (scaledReserves[0] * N); + dP = dP * lpTokenSupply / (scaledReserves[1] * N); uint256 prevReserves = lpTokenSupply; - lpTokenSupply = Ann.mul(sumReserves).div(A_PRECISION).add(dP.mul(N)).mul(lpTokenSupply).div( - Ann.sub(A_PRECISION).mul(lpTokenSupply).div(A_PRECISION).add(N.add(1).mul(dP)) - ); + lpTokenSupply = (Ann * sumReserves / A_PRECISION + (dP * N)) * lpTokenSupply + / (((Ann - A_PRECISION) * lpTokenSupply / A_PRECISION) + ((N + 1) * dP)); // Equality with the precision of 1 if (lpTokenSupply > prevReserves) { if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; @@ -140,11 +136,11 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // scale reserve down to original precision if (reserve > prevReserve) { if (reserve - prevReserve <= 1) { - return reserve.div(10 ** (18 - decimals[j])); + return reserve / (10 ** (18 - decimals[j])); } } else { if (prevReserve - reserve <= 1) { - return reserve.div(10 ** (18 - decimals[j])); + return reserve / (10 ** (18 - decimals[j])); } } } @@ -208,12 +204,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); PriceData memory pd; - - { - uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); - // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; - } + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceSwap(pd.targetPrice); @@ -285,11 +278,9 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); PriceData memory pd; - { - uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); - // calc target price with 6 decimal precision: - pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; - } + uint256[] memory scaledRatios = getScaledReserves(ratios, decimals); + // calc target price with 6 decimal precision: + pd.targetPrice = scaledRatios[i] * PRICE_PRECISION / scaledRatios[j]; // get ratios and price from the closest highest and lowest price from targetPrice: pd.lutData = ILookupTable(lookupTable).getRatiosFromPriceLiquidity(pd.targetPrice); @@ -383,7 +374,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 c, uint256 lpTokenSupply ) private pure returns (uint256) { - return reserve.mul(reserve).add(c).div(reserve.mul(2).add(b).sub(lpTokenSupply)); + return (reserve * reserve + c) / (reserve * 2 + b - lpTokenSupply); } function getBandC( @@ -391,8 +382,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 lpTokenSupply, uint256 reserves ) private pure returns (uint256 c, uint256 b) { - c = lpTokenSupply.mul(lpTokenSupply).div(reserves.mul(N)).mul(lpTokenSupply).mul(A_PRECISION).div(Ann.mul(N)); - b = reserves.add(lpTokenSupply.mul(A_PRECISION).div(Ann)); + c = lpTokenSupply * lpTokenSupply / (reserves * N) * lpTokenSupply * A_PRECISION / (Ann * N); + b = reserves + (lpTokenSupply * A_PRECISION / Ann); } /** From 40fd126a65ed7a6850f0c17ef1301bb5dc1b9130 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 16 Jul 2024 01:35:19 +0200 Subject: [PATCH 32/69] cleanup I --- test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index dc837197..534e08fb 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -21,7 +21,6 @@ contract BeanstalkStable2SwapTest is TestHelper { function test_calcReserveAtRatioSwap_equal_equal() public view { uint256[] memory reserves = new uint256[](2); - // calcReserveAtRatioSwap requires a minimum value of 10 ** token decimals. reserves[0] = 100e18; reserves[1] = 100e18; uint256[] memory ratios = new uint256[](2); @@ -98,8 +97,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 targetPrice = ratios[0] * 1e6 / ratios[1]; uint256 reservePrice0 = _f.calcRate(updatedReserves, 0, 1, data); - // estimated price and actual price are within 0.04% in the worst case. - assertApproxEqRel(reservePrice0, targetPrice, 0.0004e18, "reservePrice0 <> targetPrice"); + // estimated price and actual price are within 0.01% in the worst case. + assertApproxEqRel(reservePrice0, targetPrice, 0.0001e18, "reservePrice0 <> targetPrice"); } } } From 427ad85752587e6860fc536b78f58f09b5718833 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 16 Jul 2024 12:29:59 +0300 Subject: [PATCH 33/69] Add stable2 calcSwap and calcLiquidity simulation scripts --- .../StableswapCalcRatiosLiqSim.s.sol | 100 +++++++++++++++ .../StableswapCalcRatiosSwapSim.s.sol | 119 ++++++++++++++++++ 2 files changed, 219 insertions(+) create mode 100644 script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol create mode 100644 script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol diff --git a/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol b/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol new file mode 100644 index 00000000..7e2bf4e9 --- /dev/null +++ b/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Stable2} from "src/functions/Stable2.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; + +/** + * Stable2 well function simulation and precalculations used + * to produce the token ratios for the lookup table needed for the initial + * `calcReserveAtRatioLiquidity` estimates. +*/ +contract StableswapCalcRatiosLiqSim is Script { + function run() external { + Stable2LUT1 stable2LUT1 = new Stable2LUT1(); + Stable2 stable2 = new Stable2(address(stable2LUT1)); + console.log("stable2.getAParameter(): %d", stable2LUT1.getAParameter()); + // initial reserves + uint256 init_reserve_x = 1_000_000e18; + uint256 init_reserve_y = 1_000_000e18; + uint256[] memory reserves = new uint256[](2); + reserves[0] = init_reserve_x; + reserves[1] = init_reserve_y; + uint256 reserve_y = init_reserve_y; + bytes memory data = abi.encode(18, 18); + uint256 price; + + // for n times (1...n) : + // 1) modify reserve x_n-1 by some percentage (this changes the pool liquidity) + // 3) calc price_n using calcRate(...) + + // csv header + console.log("Price (P),Reserve (x),Reserve (y)"); + + // calcReserveAtRatioLiquidity + for (uint256 i; i < 20 ; i++) { + // update reserves + reserve_y = reserve_y * 88 / 100; + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + } + + // reset reserves + reserve_y = init_reserve_y; + + // calcReserveAtRatioLiquidity + for (uint256 i; i < 20 ; i++) { + // update reserves + reserve_y = reserve_y * 98 / 100; + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + } + + // reset reserves + reserve_y = init_reserve_y; + + // calcReserveAtRatioLiquidity + for (uint256 i; i < 20 ; i++) { + // update reserves + reserve_y = reserve_y * 102 / 100; + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + } + + // reset reserves + reserve_y = init_reserve_y; + + + // calcReserveAtRatioLiquidity + for (uint256 i; i < 20 ; i++) { + // update reserves + reserve_y = reserve_y * 112 / 100; + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + } + + // Extreme prices + + // extreme low + reserve_y = init_reserve_y * 1 / 28; + reserves[1] = reserve_y; + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + + // extreme high + reserve_y = init_reserve_y * 2000; + reserves[1] = reserve_y; + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, init_reserve_x, reserve_y); + } +} diff --git a/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol b/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol new file mode 100644 index 00000000..7789abd0 --- /dev/null +++ b/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.20; + +import "forge-std/Script.sol"; +import {console} from "forge-std/console.sol"; +import {Stable2} from "src/functions/Stable2.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; + +/** + * Stable2 well function simulation and precalculations used + * to produce the token ratios for the lookup table needed for the initial + * `calcReserveAtRatioSwap` estimates. +*/ +contract StableswapCalcRatiosSwapSim is Script { + function run() external { + Stable2LUT1 stable2LUT1 = new Stable2LUT1(); + Stable2 stable2 = new Stable2(address(stable2LUT1)); + console.log("stable2.getAParameter(): %d", stable2LUT1.getAParameter()); + // initial reserves + uint256 init_reserve_x = 1_000_000e18; + uint256 init_reserve_y = 1_000_000e18; + uint256[] memory reserves = new uint256[](2); + reserves[0] = init_reserve_x; + reserves[1] = init_reserve_y; + bytes memory data = abi.encode(18, 18); + // calculateLP token supply (this remains unchanged) + uint256 lpTokenSupply = stable2.calcLpTokenSupply(reserves, data); + console.log("lp_token_supply: %d", lpTokenSupply); + uint256 reserve_x = init_reserve_x; + uint256 price; + + // for n times (1...n) : + // 1) increment x_n-1 by some amount to get x_n + // 2) calc y_n using calcReserves(...) + // 3) calc price_n using calcRate(...) + + // csv header + console.log("Price (P),Reserve (x),Reserve (y)"); + + for (uint256 i; i < 20 ; i++) { + // update reserve x + reserve_x = reserve_x * 92 / 100; + reserves[0] = reserve_x; + // get y_n --> corresponding reserve y for a given liquidity level + uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + // update reserve y + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + } + + // reset reserves + reserve_x = init_reserve_x; + + for (uint256 i; i < 40 ; i++) { + // update reserve x + reserve_x = reserve_x * 99 / 100; + reserves[0] = reserve_x; + // get y_n --> corresponding reserve y for a given liquidity level + uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + // update reserve y + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + } + + // reset reserves + reserve_x = init_reserve_x; + + for (uint256 i; i < 40 ; i++) { + // update reserve x + reserve_x = reserve_x * 101 / 100; + reserves[0] = reserve_x; + // get y_n --> corresponding reserve y for a given liquidity level + uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + // update reserve y + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + } + + // reset reserves + reserve_x = init_reserve_x; + + for (uint256 i; i < 18 ; i++) { + // update reserve x + reserve_x = reserve_x * 105 / 100; + reserves[0] = reserve_x; + // get y_n --> corresponding reserve y for a given liquidity level + uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + // update reserve y + reserves[1] = reserve_y; + // mark price + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + } + + // Extreme prices + + // extreme low + reserve_x = init_reserve_x * 3; + reserves[0] = reserve_x; + uint256 reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + reserves[1] = reserve_y; + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + + // extreme high + reserve_x = init_reserve_x * 1/ 190; + reserves[0] = reserve_x; + reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); + reserves[1] = reserve_y; + price = stable2.calcRate(reserves, 0, 1, data); + console.log("%d,%d,%d", price, reserve_x, reserve_y); + } +} From fb2e36c6641618d95a92097f18e32926014bd889 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Tue, 16 Jul 2024 12:48:06 +0300 Subject: [PATCH 34/69] Update stable2 LUT tests --- test/Stable2/LookupTable.t.sol | 65 +++++++++++++++++++++------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/test/Stable2/LookupTable.t.sol b/test/Stable2/LookupTable.t.sol index 84de3cd4..73bebdcf 100644 --- a/test/Stable2/LookupTable.t.sol +++ b/test/Stable2/LookupTable.t.sol @@ -1,10 +1,13 @@ // SPDX-License-Identifier: MIT +// forgefmt: disable-start + pragma solidity ^0.8.20; import {TestHelper, Well, IERC20, console} from "test/TestHelper.sol"; -import {Stable2LUT1} from "src/functions/StableLut/Stable2LUT1.sol"; +import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; contract LookupTableTest is TestHelper { + Stable2LUT1 lookupTable; Stable2LUT1.PriceData pd; @@ -12,9 +15,9 @@ contract LookupTableTest is TestHelper { lookupTable = new Stable2LUT1(); } - function test_getAParameter() public view { + function test_getAParameter() public { uint256 a = lookupTable.getAParameter(); - assertEq(a, 1); + assertEq(a , 1); } //////////////// getRatiosFromPriceSwap //////////////// @@ -22,10 +25,11 @@ contract LookupTableTest is TestHelper { function test_getRatiosFromPriceSwapAroundDollarHigh() public { uint256 currentPrice = 1e6; // test 1.0 - 1.10 range - for (uint256 i; i < 10; i++) { + for (uint256 i; i<10 ; i++) { pd = lookupTable.getRatiosFromPriceSwap(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.08e6); + // 2 cent precision around the dollar mark + assertLt(diff, 0.02e6); currentPrice += 0.01e6; } } @@ -33,47 +37,54 @@ contract LookupTableTest is TestHelper { function test_getRatiosFromPriceSwapAroundDollarLow() public { uint256 currentPrice = 0.9e6; // test 0.9 - 1.0 range - for (uint256 i; i < 10; i++) { + for (uint256 i; i<10 ; i++) { pd = lookupTable.getRatiosFromPriceSwap(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.08e18); - currentPrice += 0.01e6; + // 2 cent precision around the dollar mark + assertLt(diff, 0.02e6); + currentPrice += 0.02e6; } } function test_getRatiosFromPriceSwapExtremeLow() public { - // pick a value close to the min (P=0.01) + // pick a value close to the min (P~=0.01) uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); } function test_getRatiosFromPriceSwapExtremeHigh() public { - // pick a value close to the max (P=9.85) + // pick a value close to the max (P~=10) uint256 currentPrice = 9.84e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); } function testFail_getRatiosFromPriceSwapExtremeLow() public { // pick an out of bounds value (P<0.01) - uint256 currentPrice = 0.001e6; + uint256 currentPrice = 0.0001e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + // assert no revert + assert(pd.highPrice > pd.lowPrice); } function testFail_getRatiosFromPriceSwapExtremeHigh() public { - // pick an out of bounds value (P>10.37) - uint256 currentPrice = 11e6; + // pick an out of bounds value (P>10) + uint256 currentPrice = 100e6; pd = lookupTable.getRatiosFromPriceSwap(currentPrice); + // assert no revert + assert(pd.highPrice > pd.lowPrice); } //////////////// getRatiosFromPriceLiquidity //////////////// + function test_getRatiosFromPriceLiquidityAroundDollarHigh() public { uint256 currentPrice = 1e6; // test 1.0 - 1.10 range - for (uint256 i; i < 10; i++) { + for (uint256 i; i<10 ; i++) { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.08e6); + // 2 cent precision around the dollar mark + assertLt(diff, 0.02e6); currentPrice += 0.01e6; } } @@ -81,35 +92,40 @@ contract LookupTableTest is TestHelper { function test_getRatiosFromPriceLiquidityAroundDollarLow() public { uint256 currentPrice = 0.9e6; // test 0.9 - 1.0 range - for (uint256 i; i < 10; i++) { + for (uint256 i; i<10 ; i++) { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); uint256 diff = pd.highPrice - pd.lowPrice; - assertLt(diff, 0.1e6); + // 2 cent precision around the dollar mark + assertLt(diff, 0.02e6); currentPrice += 0.01e6; } } function test_getRatiosFromPriceLiquidityExtremeLow() public { - // pick a value close to the min (P=0.01) + // pick a value close to the min (P=~0.01) uint256 currentPrice = 0.015e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + // assert no revert + assert(pd.highPrice > pd.lowPrice); } function test_getRatiosFromPriceLiquidityExtremeHigh() public { - // pick a value close to the max (P=9.92) + // pick a value close to the max (P~=10) uint256 currentPrice = 9.91e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); + // assert no revert + assert(pd.highPrice > pd.lowPrice); } function testFail_getRatiosFromPriceLiquidityExtremeLow() public { // pick an out of bounds value (P<0.01) - uint256 currentPrice = 0.001e6; + uint256 currentPrice = 0.00001e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); } function testFail_getRatiosFromPriceLiquidityExtremeHigh() public { - // pick an out of bounds value (P>10.37) - uint256 currentPrice = 11e6; + // pick an out of bounds value (P>10) + uint256 currentPrice = 100e6; pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); } @@ -118,7 +134,7 @@ contract LookupTableTest is TestHelper { function test_PriceRangeSwap() public { // test range 0.5 - 2.5 uint256 currentPrice = 0.5e6; - for (uint256 i; i < 200; i++) { + for (uint256 i; i<200 ; i++) { pd = lookupTable.getRatiosFromPriceSwap(currentPrice); assertGe(pd.highPrice, currentPrice); assertLt(pd.lowPrice, currentPrice); @@ -129,11 +145,10 @@ contract LookupTableTest is TestHelper { function test_PriceRangeLiq() public { // test range 0.5 - 2.5 uint256 currentPrice = 0.5e6; - for (uint256 i; i < 200; i++) { + for (uint256 i; i<200 ; i++) { pd = lookupTable.getRatiosFromPriceLiquidity(currentPrice); assertGe(pd.highPrice, currentPrice); assertLt(pd.lowPrice, currentPrice); - assertLt(pd.highPriceJ / pd.precision, 1e18); currentPrice += 0.01e6; } } From b7a44c0b399f9d4723e6e7dd1fa638da9bf83052 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 12:41:34 +0200 Subject: [PATCH 35/69] Clean up various tests and contracts. --- src/functions/Stable2.sol | 57 ++- src/functions/StableLUT/Stable2LUT1.sol | 524 ++++++------------------ test/functions/Stable2.t.sol | 19 +- 3 files changed, 155 insertions(+), 445 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index c65cff55..4104c06a 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -1,11 +1,10 @@ // SPDX-License-Identifier: MIT -pragma solidity ^0.8.17; +pragma solidity ^0.8.20; import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces/IBeanstalkWellFunction.sol"; import {ILookupTable} from "src/interfaces/ILookupTable.sol"; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; -import {LibMath} from "src/libraries/LibMath.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; /** * @author Brean @@ -31,8 +30,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { ILookupTable.PriceData lutData; } - using LibMath for uint256; - // 2 token Pool. uint256 constant N = 2; @@ -169,24 +166,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { rate = _calcRate(scaledReserves, i, j, lpTokenSupply); } - /** - * @notice internal calcRate function. - */ - function _calcRate( - uint256[] memory reserves, - uint256 i, - uint256 j, - uint256 lpTokenSupply - ) internal view returns (uint256 rate) { - // add 1e6 to reserves: - uint256[] memory _reserves = new uint256[](2); - _reserves[i] = reserves[i]; - _reserves[j] = reserves[j] + PRICE_PRECISION; - - // calculate rate: - rate = _reserves[i] - calcReserve(_reserves, i, lpTokenSupply, abi.encode(18, 18)); - } - /** * @inheritdoc IMultiFlowPumpWellFunction * @dev `calcReserveAtRatioSwap` fetches the closes approximate ratios from the target price, @@ -326,14 +305,6 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } } - function name() external pure returns (string memory) { - return "Stable2"; - } - - function symbol() external pure returns (string memory) { - return "S2"; - } - /** * @notice decodes the data encoded in the well. * @return decimals an array of the decimals of the tokens in the well. @@ -355,6 +326,32 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { decimals[1] = decimal1; } + function name() external pure returns (string memory) { + return "Stable2"; + } + + function symbol() external pure returns (string memory) { + return "S2"; + } + + /** + * @notice internal calcRate function. + */ + function _calcRate( + uint256[] memory reserves, + uint256 i, + uint256 j, + uint256 lpTokenSupply + ) internal view returns (uint256 rate) { + // add 1e6 to reserves: + uint256[] memory _reserves = new uint256[](2); + _reserves[i] = reserves[i]; + _reserves[j] = reserves[j] + PRICE_PRECISION; + + // calculate rate: + rate = _reserves[i] - calcReserve(_reserves, i, lpTokenSupply, abi.encode(18, 18)); + } + /** * @notice scale `reserves` by `precision`. * @dev this sets both reserves to 18 decimals. diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index e40f41c7..10b98905 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -5,13 +5,10 @@ pragma solidity ^0.8.20; import {ILookupTable} from "src/interfaces/ILookupTable.sol"; /** - * @title BasinStableswapLookupTable - * @dev This contract implements a lookup table of estimations used in the stableswap well function - * to calculate the token ratios in a stableswap pool to return to peg. - * It uses an if ladder structured as a binary tree to store and retrieve - * price-to-token ratio estimates needed for liquidity and swap operations - * within Beanstalk in O(log2(n)) time complexity. - * A lookup table was used to avoid the need for expensive price calculations on-chain. + * @title Stable2LUT1 + * @author DeadManWalking, Brean + * @notice Implements a lookup table of estimations used in the Stableswap Well Function + * to calculate the token ratios in a Stableswap pool for a given price. */ contract Stable2LUT1 is ILookupTable { /** @@ -25,8 +22,6 @@ contract Stable2LUT1 is ILookupTable { /** * @notice Returns the estimated range of reserve ratios for a given price, * assuming one token reserve remains constant. - * Needed to calculate the liquidity of a well for Beanstalk to return to peg. - * Used in `calcReserveAtRatioLiquidity` function in the stableswap well function. */ function getRatiosFromPriceLiquidity(uint256 price) external pure returns (PriceData memory) { if (price < 1.006758e6) { @@ -38,25 +33,12 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.001083e6) { revert("LUT: Invalid price"); } else { - return PriceData( - 0.27702e6, - 0.0e18, // x (should stay the same now) - 9.646293093274934449e18, // y - 0.001083e6, - 0.0e18, - 2000.0e18, - 1e18 - ); + return + PriceData(0.27702e6, 0, 9.646293093274934449e18, 0.001083e6, 0, 2000e18, 1e18); } } else { return PriceData( - 0.30624e6, - 0.0e18, - 8.612761690424049377e18, - 0.27702e6, - 0.0e18, - 9.646293093274934449e18, - 1e18 + 0.30624e6, 0, 8.612761690424049377e18, 0.27702e6, 0, 9.646293093274934449e18, 1e18 ); } } else { @@ -64,33 +46,27 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.337394e6) { return PriceData( 0.337394e6, - 0.0e18, + 0, 7.689965795021471706e18, 0.30624e6, - 0.0e18, + 0, 8.612761690424049377e18, 1e18 ); } else { return PriceData( 0.370355e6, - 0.0e18, + 0, 6.866040888412029197e18, 0.337394e6, - 0.0e18, + 0, 7.689965795021471706e18, 1e18 ); } } else { return PriceData( - 0.404944e6, - 0.0e18, - 6.130393650367882863e18, - 0.370355e6, - 0.0e18, - 6.866040888412029197e18, - 1e18 + 0.404944e6, 0, 6.130393650367882863e18, 0.370355e6, 0, 6.866040888412029197e18, 1e18 ); } } @@ -100,55 +76,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.440934e6) { return PriceData( 0.440934e6, - 0.0e18, + 0, 5.473565759257038366e18, 0.404944e6, - 0.0e18, + 0, 6.130393650367882863e18, 1e18 ); } else { return PriceData( 0.478063e6, - 0.0e18, + 0, 4.887112285050926097e18, 0.440934e6, - 0.0e18, + 0, 5.473565759257038366e18, 1e18 ); } } else { return PriceData( - 0.516039e6, - 0.0e18, - 4.363493111652613443e18, - 0.478063e6, - 0.0e18, - 4.887112285050926097e18, - 1e18 + 0.516039e6, 0, 4.363493111652613443e18, 0.478063e6, 0, 4.887112285050926097e18, 1e18 ); } } else { if (price < 0.554558e6) { return PriceData( - 0.554558e6, - 0.0e18, - 3.89597599254697613e18, - 0.516039e6, - 0.0e18, - 4.363493111652613443e18, - 1e18 + 0.554558e6, 0, 3.89597599254697613e18, 0.516039e6, 0, 4.363493111652613443e18, 1e18 ); } else { return PriceData( - 0.59332e6, - 0.0e18, - 3.478549993345514402e18, - 0.554558e6, - 0.0e18, - 3.89597599254697613e18, - 1e18 + 0.59332e6, 0, 3.478549993345514402e18, 0.554558e6, 0, 3.89597599254697613e18, 1e18 ); } } @@ -160,55 +118,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.632052e6) { return PriceData( 0.632052e6, - 0.0e18, + 0, 3.105848208344209382e18, 0.59332e6, - 0.0e18, + 0, 3.478549993345514402e18, 1e18 ); } else { return PriceData( 0.670518e6, - 0.0e18, + 0, 2.773078757450186949e18, 0.632052e6, - 0.0e18, + 0, 3.105848208344209382e18, 1e18 ); } } else { return PriceData( - 0.708539e6, - 0.0e18, - 2.475963176294809553e18, - 0.670518e6, - 0.0e18, - 2.773078757450186949e18, - 1e18 + 0.708539e6, 0, 2.475963176294809553e18, 0.670518e6, 0, 2.773078757450186949e18, 1e18 ); } } else { if (price < 0.746003e6) { return PriceData( - 0.746003e6, - 0.0e18, - 2.210681407406080101e18, - 0.708539e6, - 0.0e18, - 2.475963176294809553e18, - 1e18 + 0.746003e6, 0, 2.210681407406080101e18, 0.708539e6, 0, 2.475963176294809553e18, 1e18 ); } else { return PriceData( - 0.782874e6, - 0.0e18, - 1.973822685183999948e18, - 0.746003e6, - 0.0e18, - 2.210681407406080101e18, - 1e18 + 0.782874e6, 0, 1.973822685183999948e18, 0.746003e6, 0, 2.210681407406080101e18, 1e18 ); } } @@ -218,55 +158,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.819199e6) { return PriceData( 0.819199e6, - 0.0e18, + 0, 1.762341683200000064e18, 0.782874e6, - 0.0e18, + 0, 1.973822685183999948e18, 1e18 ); } else { return PriceData( 0.855108e6, - 0.0e18, + 0, 1.573519359999999923e18, 0.819199e6, - 0.0e18, + 0, 1.762341683200000064e18, 1e18 ); } } else { return PriceData( - 0.873157e6, - 0.0e18, - 1.485947395978354457e18, - 0.855108e6, - 0.0e18, - 1.573519359999999923e18, - 1e18 + 0.873157e6, 0, 1.485947395978354457e18, 0.855108e6, 0, 1.573519359999999923e18, 1e18 ); } } else { if (price < 0.879393e6) { return PriceData( - 0.879393e6, - 0.0e18, - 1.456811172527798348e18, - 0.873157e6, - 0.0e18, - 1.485947395978354457e18, - 1e18 + 0.879393e6, 0, 1.456811172527798348e18, 0.873157e6, 0, 1.485947395978354457e18, 1e18 ); } else { return PriceData( - 0.885627e6, - 0.0e18, - 1.428246247576273165e18, - 0.879393e6, - 0.0e18, - 1.456811172527798348e18, - 1e18 + 0.885627e6, 0, 1.428246247576273165e18, 0.879393e6, 0, 1.456811172527798348e18, 1e18 ); } } @@ -280,33 +202,27 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.89081e6) { return PriceData( 0.89081e6, - 0.0e18, + 0, 1.404927999999999955e18, 0.885627e6, - 0.0e18, + 0, 1.428246247576273165e18, 1e18 ); } else { return PriceData( 0.891863e6, - 0.0e18, + 0, 1.400241419192424397e18, 0.89081e6, - 0.0e18, + 0, 1.404927999999999955e18, 1e18 ); } } else { return PriceData( - 0.898101e6, - 0.0e18, - 1.372785705090612263e18, - 0.891863e6, - 0.0e18, - 1.400241419192424397e18, - 1e18 + 0.898101e6, 0, 1.372785705090612263e18, 0.891863e6, 0, 1.400241419192424397e18, 1e18 ); } } else { @@ -314,33 +230,27 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.904344e6) { return PriceData( 0.904344e6, - 0.0e18, + 0, 1.345868338324129665e18, 0.898101e6, - 0.0e18, + 0, 1.372785705090612263e18, 1e18 ); } else { return PriceData( 0.910594e6, - 0.0e18, + 0, 1.319478763062872151e18, 0.904344e6, - 0.0e18, + 0, 1.345868338324129665e18, 1e18 ); } } else { return PriceData( - 0.916852e6, - 0.0e18, - 1.293606630453796313e18, - 0.910594e6, - 0.0e18, - 1.319478763062872151e18, - 1e18 + 0.916852e6, 0, 1.293606630453796313e18, 0.910594e6, 0, 1.319478763062872151e18, 1e18 ); } } @@ -350,55 +260,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.92312e6) { return PriceData( 0.92312e6, - 0.0e18, + 0, 1.268241794562545266e18, 0.916852e6, - 0.0e18, + 0, 1.293606630453796313e18, 1e18 ); } else { return PriceData( 0.9266e6, - 0.0e18, + 0, 1.254399999999999959e18, 0.92312e6, - 0.0e18, + 0, 1.268241794562545266e18, 1e18 ); } } else { return PriceData( - 0.929402e6, - 0.0e18, - 1.243374308394652239e18, - 0.9266e6, - 0.0e18, - 1.254399999999999959e18, - 1e18 + 0.929402e6, 0, 1.243374308394652239e18, 0.9266e6, 0, 1.254399999999999959e18, 1e18 ); } } else { if (price < 0.935697e6) { return PriceData( - 0.935697e6, - 0.0e18, - 1.218994419994757328e18, - 0.929402e6, - 0.0e18, - 1.243374308394652239e18, - 1e18 + 0.935697e6, 0, 1.218994419994757328e18, 0.929402e6, 0, 1.243374308394652239e18, 1e18 ); } else { return PriceData( - 0.94201e6, - 0.0e18, - 1.195092568622310836e18, - 0.935697e6, - 0.0e18, - 1.218994419994757328e18, - 1e18 + 0.94201e6, 0, 1.195092568622310836e18, 0.935697e6, 0, 1.218994419994757328e18, 1e18 ); } } @@ -410,55 +302,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.948343e6) { return PriceData( 0.948343e6, - 0.0e18, + 0, 1.171659381002265521e18, 0.94201e6, - 0.0e18, + 0, 1.195092568622310836e18, 1e18 ); } else { return PriceData( 0.954697e6, - 0.0e18, + 0, 1.14868566764928004e18, 0.948343e6, - 0.0e18, + 0, 1.171659381002265521e18, 1e18 ); } } else { return PriceData( - 0.961075e6, - 0.0e18, - 1.12616241926400007e18, - 0.954697e6, - 0.0e18, - 1.14868566764928004e18, - 1e18 + 0.961075e6, 0, 1.12616241926400007e18, 0.954697e6, 0, 1.14868566764928004e18, 1e18 ); } } else { if (price < 0.962847e6) { return PriceData( - 0.962847e6, - 0.0e18, - 1.120000000000000107e18, - 0.961075e6, - 0.0e18, - 1.12616241926400007e18, - 1e18 + 0.962847e6, 0, 1.120000000000000107e18, 0.961075e6, 0, 1.12616241926400007e18, 1e18 ); } else { return PriceData( - 0.96748e6, - 0.0e18, - 1.104080803200000016e18, - 0.962847e6, - 0.0e18, - 1.120000000000000107e18, - 1e18 + 0.96748e6, 0, 1.104080803200000016e18, 0.962847e6, 0, 1.120000000000000107e18, 1e18 ); } } @@ -468,55 +342,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 0.973914e6) { return PriceData( 0.973914e6, - 0.0e18, + 0, 1.082432159999999977e18, 0.96748e6, - 0.0e18, + 0, 1.104080803200000016e18, 1e18 ); } else { return PriceData( 0.98038e6, - 0.0e18, + 0, 1.061208000000000151e18, 0.973914e6, - 0.0e18, + 0, 1.082432159999999977e18, 1e18 ); } } else { return PriceData( - 0.986882e6, - 0.0e18, - 1.040399999999999991e18, - 0.98038e6, - 0.0e18, - 1.061208000000000151e18, - 1e18 + 0.986882e6, 0, 1.040399999999999991e18, 0.98038e6, 0, 1.061208000000000151e18, 1e18 ); } } else { if (price < 0.993421e6) { return PriceData( - 0.993421e6, - 0.0e18, - 1.020000000000000018e18, - 0.986882e6, - 0.0e18, - 1.040399999999999991e18, - 1e18 + 0.993421e6, 0, 1.020000000000000018e18, 0.986882e6, 0, 1.040399999999999991e18, 1e18 ); } else { return PriceData( - 1.006758e6, - 0.0e18, - 0.980000000000000093e18, - 0.993421e6, - 0.0e18, - 1.020000000000000018e18, - 1e18 + 1.006758e6, 0, 0.980000000000000093e18, 0.993421e6, 0, 1.020000000000000018e18, 1e18 ); } } @@ -532,33 +388,27 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.013564e6) { return PriceData( 1.013564e6, - 0.0e18, + 0, 0.960400000000000031e18, 1.006758e6, - 0.0e18, + 0, 0.980000000000000093e18, 1e18 ); } else { return PriceData( 1.020422e6, - 0.0e18, + 0, 0.941192000000000029e18, 1.013564e6, - 0.0e18, + 0, 0.960400000000000031e18, 1e18 ); } } else { return PriceData( - 1.027335e6, - 0.0e18, - 0.922368159999999992e18, - 1.020422e6, - 0.0e18, - 0.941192000000000029e18, - 1e18 + 1.027335e6, 0, 0.922368159999999992e18, 1.020422e6, 0, 0.941192000000000029e18, 1e18 ); } } else { @@ -566,33 +416,27 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.034307e6) { return PriceData( 1.034307e6, - 0.0e18, + 0, 0.903920796799999926e18, 1.027335e6, - 0.0e18, + 0, 0.922368159999999992e18, 1e18 ); } else { return PriceData( 1.041342e6, - 0.0e18, + 0, 0.885842380864000023e18, 1.034307e6, - 0.0e18, + 0, 0.903920796799999926e18, 1e18 ); } } else { return PriceData( - 1.04366e6, - 0.0e18, - 0.880000000000000004e18, - 1.041342e6, - 0.0e18, - 0.885842380864000023e18, - 1e18 + 1.04366e6, 0, 0.880000000000000004e18, 1.041342e6, 0, 0.885842380864000023e18, 1e18 ); } } @@ -602,55 +446,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.048443e6) { return PriceData( 1.048443e6, - 0.0e18, + 0, 0.868125533246720038e18, 1.04366e6, - 0.0e18, + 0, 0.880000000000000004e18, 1e18 ); } else { return PriceData( 1.055613e6, - 0.0e18, + 0, 0.8507630225817856e18, 1.048443e6, - 0.0e18, + 0, 0.868125533246720038e18, 1e18 ); } } else { return PriceData( - 1.062857e6, - 0.0e18, - 0.833747762130149894e18, - 1.055613e6, - 0.0e18, - 0.8507630225817856e18, - 1e18 + 1.062857e6, 0, 0.833747762130149894e18, 1.055613e6, 0, 0.8507630225817856e18, 1e18 ); } } else { if (price < 1.070179e6) { return PriceData( - 1.070179e6, - 0.0e18, - 0.81707280688754691e18, - 1.062857e6, - 0.0e18, - 0.833747762130149894e18, - 1e18 + 1.070179e6, 0, 0.81707280688754691e18, 1.062857e6, 0, 0.833747762130149894e18, 1e18 ); } else { return PriceData( - 1.077582e6, - 0.0e18, - 0.800731350749795956e18, - 1.070179e6, - 0.0e18, - 0.81707280688754691e18, - 1e18 + 1.077582e6, 0, 0.800731350749795956e18, 1.070179e6, 0, 0.81707280688754691e18, 1e18 ); } } @@ -662,55 +488,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.085071e6) { return PriceData( 1.085071e6, - 0.0e18, + 0, 0.784716723734800059e18, 1.077582e6, - 0.0e18, + 0, 0.800731350749795956e18, 1e18 ); } else { return PriceData( 1.090025e6, - 0.0e18, + 0, 0.774399999999999977e18, 1.085071e6, - 0.0e18, + 0, 0.784716723734800059e18, 1e18 ); } } else { return PriceData( - 1.09265e6, - 0.0e18, - 0.769022389260104022e18, - 1.090025e6, - 0.0e18, - 0.774399999999999977e18, - 1e18 + 1.09265e6, 0, 0.769022389260104022e18, 1.090025e6, 0, 0.774399999999999977e18, 1e18 ); } } else { if (price < 1.100323e6) { return PriceData( - 1.100323e6, - 0.0e18, - 0.753641941474902044e18, - 1.09265e6, - 0.0e18, - 0.769022389260104022e18, - 1e18 + 1.100323e6, 0, 0.753641941474902044e18, 1.09265e6, 0, 0.769022389260104022e18, 1e18 ); } else { return PriceData( - 1.108094e6, - 0.0e18, - 0.738569102645403985e18, - 1.100323e6, - 0.0e18, - 0.753641941474902044e18, - 1e18 + 1.108094e6, 0, 0.738569102645403985e18, 1.100323e6, 0, 0.753641941474902044e18, 1e18 ); } } @@ -720,55 +528,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.115967e6) { return PriceData( 1.115967e6, - 0.0e18, + 0, 0.723797720592495919e18, 1.108094e6, - 0.0e18, + 0, 0.738569102645403985e18, 1e18 ); } else { return PriceData( 1.123949e6, - 0.0e18, + 0, 0.709321766180645907e18, 1.115967e6, - 0.0e18, + 0, 0.723797720592495919e18, 1e18 ); } } else { return PriceData( - 1.132044e6, - 0.0e18, - 0.695135330857033051e18, - 1.123949e6, - 0.0e18, - 0.709321766180645907e18, - 1e18 + 1.132044e6, 0, 0.695135330857033051e18, 1.123949e6, 0, 0.709321766180645907e18, 1e18 ); } } else { if (price < 1.14011e6) { return PriceData( - 1.14011e6, - 0.0e18, - 0.681471999999999967e18, - 1.132044e6, - 0.0e18, - 0.695135330857033051e18, - 1e18 + 1.14011e6, 0, 0.681471999999999967e18, 1.132044e6, 0, 0.695135330857033051e18, 1e18 ); } else { return PriceData( - 1.140253e6, - 0.0e18, - 0.681232624239892393e18, - 1.14011e6, - 0.0e18, - 0.681471999999999967e18, - 1e18 + 1.140253e6, 0, 0.681232624239892393e18, 1.14011e6, 0, 0.681471999999999967e18, 1e18 ); } } @@ -782,55 +572,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.148586e6) { return PriceData( 1.148586e6, - 0.0e18, + 0, 0.667607971755094454e18, 1.140253e6, - 0.0e18, + 0, 0.681232624239892393e18, 1e18 ); } else { return PriceData( 1.195079e6, - 0.0e18, + 0, 0.599695360000000011e18, 1.148586e6, - 0.0e18, + 0, 0.667607971755094454e18, 1e18 ); } } else { return PriceData( - 1.256266e6, - 0.0e18, - 0.527731916799999978e18, - 1.195079e6, - 0.0e18, - 0.599695360000000011e18, - 1e18 + 1.256266e6, 0, 0.527731916799999978e18, 1.195079e6, 0, 0.599695360000000011e18, 1e18 ); } } else { if (price < 1.325188e6) { return PriceData( - 1.325188e6, - 0.0e18, - 0.464404086784000025e18, - 1.256266e6, - 0.0e18, - 0.527731916799999978e18, - 1e18 + 1.325188e6, 0, 0.464404086784000025e18, 1.256266e6, 0, 0.527731916799999978e18, 1e18 ); } else { return PriceData( - 1.403579e6, - 0.0e18, - 0.408675596369920013e18, - 1.325188e6, - 0.0e18, - 0.464404086784000025e18, - 1e18 + 1.403579e6, 0, 0.408675596369920013e18, 1.325188e6, 0, 0.464404086784000025e18, 1e18 ); } } @@ -840,55 +612,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 1.493424e6) { return PriceData( 1.493424e6, - 0.0e18, + 0, 0.359634524805529598e18, 1.403579e6, - 0.0e18, + 0, 0.408675596369920013e18, 1e18 ); } else { return PriceData( 1.596984e6, - 0.0e18, + 0, 0.316478381828866062e18, 1.493424e6, - 0.0e18, + 0, 0.359634524805529598e18, 1e18 ); } } else { return PriceData( - 1.716848e6, - 0.0e18, - 0.278500976009402101e18, - 1.596984e6, - 0.0e18, - 0.316478381828866062e18, - 1e18 + 1.716848e6, 0, 0.278500976009402101e18, 1.596984e6, 0, 0.316478381828866062e18, 1e18 ); } } else { if (price < 1.855977e6) { return PriceData( - 1.855977e6, - 0.0e18, - 0.245080858888273884e18, - 1.716848e6, - 0.0e18, - 0.278500976009402101e18, - 1e18 + 1.855977e6, 0, 0.245080858888273884e18, 1.716848e6, 0, 0.278500976009402101e18, 1e18 ); } else { return PriceData( - 2.01775e6, - 0.0e18, - 0.215671155821681004e18, - 1.855977e6, - 0.0e18, - 0.245080858888273884e18, - 1e18 + 2.01775e6, 0, 0.215671155821681004e18, 1.855977e6, 0, 0.245080858888273884e18, 1e18 ); } } @@ -900,55 +654,37 @@ contract Stable2LUT1 is ILookupTable { if (price < 2.206036e6) { return PriceData( 2.206036e6, - 0.0e18, + 0, 0.189790617123079292e18, 2.01775e6, - 0.0e18, + 0, 0.215671155821681004e18, 1e18 ); } else { return PriceData( 2.425256e6, - 0.0e18, + 0, 0.167015743068309769e18, 2.206036e6, - 0.0e18, + 0, 0.189790617123079292e18, 1e18 ); } } else { return PriceData( - 2.680458e6, - 0.0e18, - 0.146973853900112583e18, - 2.425256e6, - 0.0e18, - 0.167015743068309769e18, - 1e18 + 2.680458e6, 0, 0.146973853900112583e18, 2.425256e6, 0, 0.167015743068309769e18, 1e18 ); } } else { if (price < 2.977411e6) { return PriceData( - 2.977411e6, - 0.0e18, - 0.129336991432099091e18, - 2.680458e6, - 0.0e18, - 0.146973853900112583e18, - 1e18 + 2.977411e6, 0, 0.129336991432099091e18, 2.680458e6, 0, 0.146973853900112583e18, 1e18 ); } else { return PriceData( - 3.322705e6, - 0.0e18, - 0.113816552460247203e18, - 2.977411e6, - 0.0e18, - 0.129336991432099091e18, - 1e18 + 3.322705e6, 0, 0.113816552460247203e18, 2.977411e6, 0, 0.129336991432099091e18, 1e18 ); } } @@ -958,45 +694,33 @@ contract Stable2LUT1 is ILookupTable { if (price < 3.723858e6) { return PriceData( 3.723858e6, - 0.0e18, + 0, 0.100158566165017532e18, 3.322705e6, - 0.0e18, + 0, 0.113816552460247203e18, 1e18 ); } else { return PriceData( 4.189464e6, - 0.0e18, + 0, 0.088139538225215433e18, 3.723858e6, - 0.0e18, + 0, 0.100158566165017532e18, 1e18 ); } } else { return PriceData( - 4.729321e6, - 0.0e18, - 0.077562793638189589e18, - 4.189464e6, - 0.0e18, - 0.088139538225215433e18, - 1e18 + 4.729321e6, 0, 0.077562793638189589e18, 4.189464e6, 0, 0.088139538225215433e18, 1e18 ); } } else { if (price < 10.37089e6) { return PriceData( - 10.37089e6, - 0.0e18, - 0.035714285714285712e18, - 4.729321e6, - 0.0e18, - 0.077562793638189589e18, - 1e18 + 10.37089e6, 0, 0.035714285714285712e18, 4.729321e6, 0, 0.077562793638189589e18, 1e18 ); } else { revert("LUT: Invalid price"); @@ -1011,8 +735,6 @@ contract Stable2LUT1 is ILookupTable { /** * @notice Returns the estimated range of reserve ratios for a given price, * assuming the pool liquidity remains constant. - * Needed to calculate the amounts of assets to swap in a well for Beanstalk to return to peg. - * Used in `calcReserveAtRatioSwap` function in the stableswap well function. */ function getRatiosFromPriceSwap(uint256 price) external pure returns (PriceData memory) { if (price < 0.993344e6) { @@ -2432,7 +2154,7 @@ contract Stable2LUT1 is ILookupTable { } else { return PriceData( 10.709509e6, - 3.0e18, + 3e18, 0.103912563829966526e18, 4.660591e6, 2.406619233691083881e18, diff --git a/test/functions/Stable2.t.sol b/test/functions/Stable2.t.sol index 31e9ab19..3cff992b 100644 --- a/test/functions/Stable2.t.sol +++ b/test/functions/Stable2.t.sol @@ -62,10 +62,7 @@ contract Stable2Test is WellFunctionHelper { uint256[] memory reserves = new uint256[](2); reserves[0] = STATE_A_B0; reserves[1] = STATE_A_B1; - assertEq( - _function.calcLpTokenSupply(reserves, _data), - STATE_A_LP // sqrt(10e18 * 10e18) * 2 - ); + assertEq(_function.calcLpTokenSupply(reserves, _data), STATE_A_LP); } /// @dev calcLpTokenSupply: diff decimals @@ -114,18 +111,12 @@ contract Stable2Test is WellFunctionHelper { // find reserves[0] reserves[0] = 0; reserves[1] = STATE_B_B1; - assertEq( - _function.calcReserve(reserves, 0, STATE_B_LP, _data), - STATE_B_B0 // (70710678118654 / 2)^2 / 1250e6 = ~1e18 - ); + assertEq(_function.calcReserve(reserves, 0, STATE_B_LP, _data), STATE_B_B0); // find reserves[1] - reserves[0] = STATE_B_B0; // placeholder - reserves[1] = 0; // ex. 1250 BEAN - assertEq( - _function.calcReserve(reserves, 1, STATE_B_LP, _data), - STATE_B_B1 // (70710678118654 / 2)^2 / 1e18 = 1250e6 - ); + reserves[0] = STATE_B_B0; + reserves[1] = 0; + assertEq(_function.calcReserve(reserves, 1, STATE_B_LP, _data), STATE_B_B1); } //////////// LP TOKEN SUPPLY //////////// From 6e4c6479bcf2ebeb6823b5f13ca3e8ef5e3d639d Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 12:41:59 +0200 Subject: [PATCH 36/69] add sims. --- .../stableswap/StableswapCalcRatiosLiqSim.s.sol | 13 ++++++------- .../stableswap/StableswapCalcRatiosSwapSim.s.sol | 14 +++++++------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol b/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol index 7e2bf4e9..16912c41 100644 --- a/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol +++ b/script/simulations/stableswap/StableswapCalcRatiosLiqSim.s.sol @@ -8,9 +8,9 @@ import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; /** * Stable2 well function simulation and precalculations used - * to produce the token ratios for the lookup table needed for the initial + * to produce the token ratios for the lookup table needed for the initial * `calcReserveAtRatioLiquidity` estimates. -*/ + */ contract StableswapCalcRatiosLiqSim is Script { function run() external { Stable2LUT1 stable2LUT1 = new Stable2LUT1(); @@ -34,7 +34,7 @@ contract StableswapCalcRatiosLiqSim is Script { console.log("Price (P),Reserve (x),Reserve (y)"); // calcReserveAtRatioLiquidity - for (uint256 i; i < 20 ; i++) { + for (uint256 i; i < 20; i++) { // update reserves reserve_y = reserve_y * 88 / 100; reserves[1] = reserve_y; @@ -47,7 +47,7 @@ contract StableswapCalcRatiosLiqSim is Script { reserve_y = init_reserve_y; // calcReserveAtRatioLiquidity - for (uint256 i; i < 20 ; i++) { + for (uint256 i; i < 20; i++) { // update reserves reserve_y = reserve_y * 98 / 100; reserves[1] = reserve_y; @@ -60,7 +60,7 @@ contract StableswapCalcRatiosLiqSim is Script { reserve_y = init_reserve_y; // calcReserveAtRatioLiquidity - for (uint256 i; i < 20 ; i++) { + for (uint256 i; i < 20; i++) { // update reserves reserve_y = reserve_y * 102 / 100; reserves[1] = reserve_y; @@ -72,9 +72,8 @@ contract StableswapCalcRatiosLiqSim is Script { // reset reserves reserve_y = init_reserve_y; - // calcReserveAtRatioLiquidity - for (uint256 i; i < 20 ; i++) { + for (uint256 i; i < 20; i++) { // update reserves reserve_y = reserve_y * 112 / 100; reserves[1] = reserve_y; diff --git a/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol b/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol index 7789abd0..1337f6a9 100644 --- a/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol +++ b/script/simulations/stableswap/StableswapCalcRatiosSwapSim.s.sol @@ -8,9 +8,9 @@ import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; /** * Stable2 well function simulation and precalculations used - * to produce the token ratios for the lookup table needed for the initial + * to produce the token ratios for the lookup table needed for the initial * `calcReserveAtRatioSwap` estimates. -*/ + */ contract StableswapCalcRatiosSwapSim is Script { function run() external { Stable2LUT1 stable2LUT1 = new Stable2LUT1(); @@ -37,7 +37,7 @@ contract StableswapCalcRatiosSwapSim is Script { // csv header console.log("Price (P),Reserve (x),Reserve (y)"); - for (uint256 i; i < 20 ; i++) { + for (uint256 i; i < 20; i++) { // update reserve x reserve_x = reserve_x * 92 / 100; reserves[0] = reserve_x; @@ -53,7 +53,7 @@ contract StableswapCalcRatiosSwapSim is Script { // reset reserves reserve_x = init_reserve_x; - for (uint256 i; i < 40 ; i++) { + for (uint256 i; i < 40; i++) { // update reserve x reserve_x = reserve_x * 99 / 100; reserves[0] = reserve_x; @@ -69,7 +69,7 @@ contract StableswapCalcRatiosSwapSim is Script { // reset reserves reserve_x = init_reserve_x; - for (uint256 i; i < 40 ; i++) { + for (uint256 i; i < 40; i++) { // update reserve x reserve_x = reserve_x * 101 / 100; reserves[0] = reserve_x; @@ -85,7 +85,7 @@ contract StableswapCalcRatiosSwapSim is Script { // reset reserves reserve_x = init_reserve_x; - for (uint256 i; i < 18 ; i++) { + for (uint256 i; i < 18; i++) { // update reserve x reserve_x = reserve_x * 105 / 100; reserves[0] = reserve_x; @@ -109,7 +109,7 @@ contract StableswapCalcRatiosSwapSim is Script { console.log("%d,%d,%d", price, reserve_x, reserve_y); // extreme high - reserve_x = init_reserve_x * 1/ 190; + reserve_x = init_reserve_x * 1 / 190; reserves[0] = reserve_x; reserve_y = stable2.calcReserve(reserves, 1, lpTokenSupply, data); reserves[1] = reserve_y; From f1f166c9ce6b4f1f5e4499f3ebaa0d804e1ebba2 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 12:46:05 +0200 Subject: [PATCH 37/69] Increase A parameter precision in LUT --- src/functions/Stable2.sol | 5 ++--- src/functions/StableLUT/Stable2LUT1.sol | 3 ++- test/Stable2/LookupTable.t.sol | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 4104c06a..c648c52b 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -80,7 +80,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // scale reserves to 18 decimals. uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); - uint256 Ann = a * N * N * A_PRECISION; + uint256 Ann = a * N * N; uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; if (sumReserves == 0) return 0; @@ -121,8 +121,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); // avoid stack too deep errors. - (uint256 c, uint256 b) = - getBandC(a * N * N * A_PRECISION, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]); + (uint256 c, uint256 b) = getBandC(a * N * N, lpTokenSupply, j == 0 ? scaledReserves[1] : scaledReserves[0]); reserve = lpTokenSupply; uint256 prevReserve; diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index 10b98905..f2ec5960 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -14,9 +14,10 @@ contract Stable2LUT1 is ILookupTable { /** * @notice Returns the amplification coefficient (A parameter) used to calculate the estimates. * @return The amplification coefficient. + * @dev 2 decimal precision. */ function getAParameter() external pure returns (uint256) { - return 1; + return 100; } /** diff --git a/test/Stable2/LookupTable.t.sol b/test/Stable2/LookupTable.t.sol index 73bebdcf..8e86c66f 100644 --- a/test/Stable2/LookupTable.t.sol +++ b/test/Stable2/LookupTable.t.sol @@ -15,9 +15,9 @@ contract LookupTableTest is TestHelper { lookupTable = new Stable2LUT1(); } - function test_getAParameter() public { + function test_getAParameter() public view { uint256 a = lookupTable.getAParameter(); - assertEq(a , 1); + assertEq(a , 100); } //////////////// getRatiosFromPriceSwap //////////////// From fd89761ce9299472bab4105c573267da139a357f Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 12:46:41 +0200 Subject: [PATCH 38/69] update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f25e0513..2dfa925c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.1.0-prerelease0", + "version": "1.2.0-prerelease0", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { From fd47d266682eea5cc64bad6438ffc95e59da2514 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 12:58:30 +0200 Subject: [PATCH 39/69] clean up wellUpgradeable. --- script/helpers/WellDeployer.sol | 1 - src/WellUpgradeable.sol | 52 ++++----------- src/libraries/LibMath.sol | 1 - .../LibWellUpgradeableConstructor.sol | 2 +- src/pumps/MultiFlowPump.sol | 1 - test/WellUpgradeable.t.sol | 64 ++++--------------- test/pumps/Pump.CapReserves.t.sol | 6 +- 7 files changed, 28 insertions(+), 99 deletions(-) diff --git a/script/helpers/WellDeployer.sol b/script/helpers/WellDeployer.sol index ff76d73d..028a437e 100644 --- a/script/helpers/WellDeployer.sol +++ b/script/helpers/WellDeployer.sol @@ -31,7 +31,6 @@ abstract contract WellDeployer { _well = Well(Aquifer(_aquifer).boreWell(_wellImplementation, immutableData, initData, _salt)); } - /** * @notice Encode the Well's immutable data, and deploys the well. Modified for upgradeable wells. * @param _aquifer The address of the Aquifer which will deploy this Well. diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index d1e4ca64..1ad69e39 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -7,31 +7,11 @@ import {UUPSUpgradeable} from "ozu/proxy/utils/UUPSUpgradeable.sol"; import {OwnableUpgradeable} from "ozu/access/OwnableUpgradeable.sol"; import {IERC20, SafeERC20} from "oz/token/ERC20/utils/SafeERC20.sol"; import {IAquifer} from "src/interfaces/IAquifer.sol"; -import {console} from "forge-std/console.sol"; /** * @title WellUpgradeable - * @author Publius, Silo Chad, Brean, Deadmanwalking - * @dev A Well is a constant function AMM allowing the provisioning of liquidity - * into a single pooled on-chain liquidity position. - * - * Given the dynamic storage layout of Wells initialized by an minimal proxy, - * Creating an upgradeable Well requires a custom initializer function that allows the Well - * to be initialized with immutable storage, but does not deploy a Well token. - * - * Rebasing Tokens: - * - Positive rebasing tokens are supported by Wells, but any tokens recieved from a - * rebase will not be rewarded to LP holders and instead can be extracted by anyone - * using `skim`, `sync` or `shift`. - * - Negative rebasing tokens should not be used in Well as the effect of a negative - * rebase will be realized by users interacting with the Well, not LP token holders. - * - * Fee on Tranfer (FoT) Tokens: - * - When transferring fee on transfer tokens to a Well (swapping from or adding liquidity), - * use `swapFromFeeOnTrasfer` or `addLiquidityFeeOnTransfer`. `swapTo` does not support - * fee on transfer tokens (See {swapTo}). - * - When recieving fee on transfer tokens from a Well (swapping to and removing liquidity), - * INCLUDE the fee that is taken on transfer when calculating amount out values. + * @author Deadmanwalking, Brean, Brendan, Silo Chad + * @notice WellUpgradeable is an upgradeable version of the Well contract. */ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { address private immutable ___self = address(this); @@ -40,7 +20,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * @notice verifies that the execution is called through an minimal proxy or is not a delegate call. */ modifier notDelegatedOrIsMinimalProxy() { - if (address(this) != ___self) { + if (address(this) != ___self) { address aquifer = aquifer(); address wellImplmentation = IAquifer(aquifer).wellImplementation(address(this)); require(wellImplmentation == ___self, "Function must be called by a Well bored by an aquifer"); @@ -50,10 +30,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { _; } - function init( - string memory _name, - string memory _symbol - ) external override reinitializer(2) { + function init(string memory _name, string memory _symbol) external override reinitializer(2) { // owner of Well param as the aquifier address will be the owner initially // ownable init transfers ownership to msg.sender __ERC20Permit_init(_name); @@ -74,9 +51,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { } /** - * @notice `initClone` allows for the Well to be initialized without deploying a Well token. - * @dev This function is required given intializing with the Well token would create two valid Wells. - * Sets ReentraryGuard to true to prevent users from interacting with the Well. + * @notice `initNoWellToken` allows for the Well to be initialized without deploying a Well token. */ function initNoWellToken() external initializer {} @@ -100,16 +75,13 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { // verify the new implmentation is a well bored by an aquifier. require( - IAquifer(aquifer).wellImplementation(newImplmentation) != - address(0), + IAquifer(aquifer).wellImplementation(newImplmentation) != address(0), "New implementation must be a well implmentation" ); // verify the new implmentation is a valid ERC-1967 implmentation. - console.log("here"); require( - UUPSUpgradeable(newImplmentation).proxiableUUID() == - _IMPLEMENTATION_SLOT, + UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT, "New implementation must be a valid ERC-1967 implmentation" ); } @@ -117,7 +89,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { /** * @notice Upgrades the implementation of the proxy to `newImplementation`. * Calls {_authorizeUpgrade}. - * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies + * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies * cloned (Bored) by an Aquifer. */ function upgradeTo(address newImplementation) public override { @@ -128,10 +100,10 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { /** * @notice Upgrades the implementation of the proxy to `newImplementation`. * Calls {_authorizeUpgrade}. - * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies + * @dev `upgradeTo` was modified to support ERC-1167 minimal proxies * cloned (Bored) by an Aquifer. */ - function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { + function upgradeToAndCall(address newImplementation, bytes memory data) public payable override { _authorizeUpgrade(newImplementation); _upgradeToAndCallUUPS(newImplementation, data, true); } @@ -142,7 +114,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * * IMPORTANT: A proxy pointing at a proxiable contract should not be considered proxiable itself, because this risks * bricking a proxy that upgrades to it, by delegating to itself until out of gas. However, Wells bored by Aquifers - * are ERC-1167 minimal immutable clones and cannot delgate to another proxy. Thus, `proxiableUUID` was updated to support + * are ERC-1167 minimal immutable clones and cannot delgate to another proxy. Thus, `proxiableUUID` was updated to support * this specific usecase. */ function proxiableUUID() external view override notDelegatedOrIsMinimalProxy returns (bytes32) { @@ -153,7 +125,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { return _getImplementation(); } - function getVersion() external pure virtual returns (uint256) { + function getVersion() external pure virtual returns (uint256) { return 1; } diff --git a/src/libraries/LibMath.sol b/src/libraries/LibMath.sol index 850adc88..c291c69f 100644 --- a/src/libraries/LibMath.sol +++ b/src/libraries/LibMath.sol @@ -68,7 +68,6 @@ library LibMath { * Implementation from: https://github.com/Gaussian-Process/solidity-sqrt/blob/main/src/FixedPointMathLib.sol * based on https://github.com/transmissions11/solmate/blob/main/src/utils/FixedPointMathLib.sol */ - function sqrt(uint256 a) internal pure returns (uint256 z) { /// @solidity memory-safe-assembly assembly { diff --git a/src/libraries/LibWellUpgradeableConstructor.sol b/src/libraries/LibWellUpgradeableConstructor.sol index be895335..367bc14c 100644 --- a/src/libraries/LibWellUpgradeableConstructor.sol +++ b/src/libraries/LibWellUpgradeableConstructor.sol @@ -84,4 +84,4 @@ library LibWellUpgradeableConstructor { function encodeCall(address target, bytes memory data) public pure returns (Call memory) { return Call(target, data); } -} \ No newline at end of file +} diff --git a/src/pumps/MultiFlowPump.sol b/src/pumps/MultiFlowPump.sol index ea29f6e7..15ece540 100644 --- a/src/pumps/MultiFlowPump.sol +++ b/src/pumps/MultiFlowPump.sol @@ -29,7 +29,6 @@ import {LibMath} from "src/libraries/LibMath.sol"; * Note: If an `update` call is made with a reserve of 0, the Geometric mean oracles will be set to 0. * Each Well is responsible for ensuring that an `update` call cannot be made with a reserve of 0. */ - contract MultiFlowPump is IPump, IMultiFlowPumpErrors, IInstantaneousPump, ICumulativePump { using LibLastReserveBytes for bytes32; using LibBytes16 for bytes32; diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index 3629cd88..a14b291d 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -12,7 +12,6 @@ import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; import {Aquifer} from "src/Aquifer.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; import {LibWellUpgradeableConstructor} from "src/libraries/LibWellUpgradeableConstructor.sol"; -import {LibContractInfo} from "src/libraries/LibContractInfo.sol"; import {MockToken} from "mocks/tokens/MockToken.sol"; import {WellDeployer} from "script/helpers/WellDeployer.sol"; import {ERC1967Proxy} from "oz/proxy/ERC1967/ERC1967Proxy.sol"; @@ -44,16 +43,13 @@ contract WellUpgradeTest is Test, WellDeployer { user = makeAddr("user"); // Mint tokens - MockToken(address(tokens[0])).mint(user, 10000000000000000); - MockToken(address(tokens[1])).mint(user, 10000000000000000); + MockToken(address(tokens[0])).mint(user, 10_000_000_000_000_000); + MockToken(address(tokens[1])).mint(user, 10_000_000_000_000_000); // Well Function IWellFunction cp2 = new ConstantProduct2(); vm.label(address(cp2), "CP2"); wellFunctionAddress = address(cp2); - Call memory wellFunction = Call( - address(cp2), - abi.encode("beanstalkFunction") - ); + Call memory wellFunction = Call(address(cp2), abi.encode("beanstalkFunction")); // Pump IPump mockPump = new MockPump(); @@ -69,14 +65,8 @@ contract WellUpgradeTest is Test, WellDeployer { initialOwner = makeAddr("owner"); // Well - WellUpgradeable well = encodeAndBoreWellUpgradeable( - aquifer, - wellImplementation, - tokens, - wellFunction, - pumps, - bytes32(0) - ); + WellUpgradeable well = + encodeAndBoreWellUpgradeable(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0)); wellAddress = address(well); vm.label(wellAddress, "upgradeableWell"); // Sum up of what is going on here @@ -102,10 +92,7 @@ contract WellUpgradeTest is Test, WellDeployer { vm.startPrank(initialOwner); ERC1967Proxy proxy = new ERC1967Proxy( address(well), // implementation address - LibWellUpgradeableConstructor.encodeWellInitFunctionCall( - tokens, - wellFunction - ) // init data + LibWellUpgradeableConstructor.encodeWellInitFunctionCall(tokens, wellFunction) // init data ); vm.stopPrank(); proxyAddress = address(proxy); @@ -140,12 +127,8 @@ contract WellUpgradeTest is Test, WellDeployer { } function testProxyGetWellFunction() public { - Call memory proxyWellFunction = WellUpgradeable(proxyAddress) - .wellFunction(); - assertEq( - address(proxyWellFunction.target), - address(wellFunctionAddress) - ); + Call memory proxyWellFunction = WellUpgradeable(proxyAddress).wellFunction(); + assertEq(address(proxyWellFunction.target), address(wellFunctionAddress)); assertEq(proxyWellFunction.data, abi.encode("beanstalkFunction")); } @@ -160,10 +143,7 @@ contract WellUpgradeTest is Test, WellDeployer { function testProxyNumTokens() public { uint256 expectedNumTokens = 2; - assertEq( - expectedNumTokens, - WellUpgradeable(proxyAddress).numberOfTokens() - ); + assertEq(expectedNumTokens, WellUpgradeable(proxyAddress).numberOfTokens()); } ///////////////// Interaction test ////////////////// @@ -173,18 +153,8 @@ contract WellUpgradeTest is Test, WellDeployer { uint256[] memory amounts = new uint256[](2); amounts[0] = 1000; amounts[1] = 1000; - WellUpgradeable(wellAddress).addLiquidity( - amounts, - 0, - user, - type(uint256).max - ); - WellUpgradeable(proxyAddress).addLiquidity( - amounts, - 0, - user, - type(uint256).max - ); + WellUpgradeable(wellAddress).addLiquidity(amounts, 0, user, type(uint256).max); + WellUpgradeable(proxyAddress).addLiquidity(amounts, 0, user, type(uint256).max); assertEq(amounts, WellUpgradeable(proxyAddress).getReserves()); vm.stopPrank(); } @@ -218,16 +188,10 @@ contract WellUpgradeTest is Test, WellDeployer { Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); Call[] memory pumps = new Call[](1); pumps[0] = Call(mockPumpAddress, abi.encode("2")); - // create new mock Well Implementation: + // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); - WellUpgradeable well2 = encodeAndBoreWellUpgradeable( - aquifer, - wellImpl, - tokens, - wellFunction, - pumps, - bytes32(abi.encode("2")) - ); + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); diff --git a/test/pumps/Pump.CapReserves.t.sol b/test/pumps/Pump.CapReserves.t.sol index e06d7aa4..d1586153 100644 --- a/test/pumps/Pump.CapReserves.t.sol +++ b/test/pumps/Pump.CapReserves.t.sol @@ -51,11 +51,7 @@ contract CapBalanceTest is TestHelper, MultiFlowPump { _well = address( new MockStaticWell( - deployMockTokens(2), - Call(address(wf), new bytes(0)), - deployPumps(1), - address(0), - new bytes(0) + deployMockTokens(2), Call(address(wf), new bytes(0)), deployPumps(1), address(0), new bytes(0) ) ); } From 2257db8dcd6e62a518aae76c4fdbad289fc75386 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 22:32:40 +0200 Subject: [PATCH 40/69] remove console logs. --- lib/forge-std | 2 +- .../BeanstalkStable2.calcReserveAtRatioSwap.t.sol | 4 ++-- test/pumps/Pump.Update.t.sol | 8 -------- 3 files changed, 3 insertions(+), 11 deletions(-) diff --git a/lib/forge-std b/lib/forge-std index 8948d45d..5125ce50 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 8948d45d3d9022c508b83eb5d26fd3a7a93f2f32 +Subproject commit 5125ce505fa60ca747df08ab2cc77db5445bd716 diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 534e08fb..7788e81f 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -97,8 +97,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 targetPrice = ratios[0] * 1e6 / ratios[1]; uint256 reservePrice0 = _f.calcRate(updatedReserves, 0, 1, data); - // estimated price and actual price are within 0.01% in the worst case. - assertApproxEqRel(reservePrice0, targetPrice, 0.0001e18, "reservePrice0 <> targetPrice"); + // estimated price and actual price are within 0.015% in the worst case. + assertApproxEqRel(reservePrice0, targetPrice, 0.00015e18, "reservePrice0 <> targetPrice"); } } } diff --git a/test/pumps/Pump.Update.t.sol b/test/pumps/Pump.Update.t.sol index acc5f4c3..30a0d6b5 100644 --- a/test/pumps/Pump.Update.t.sol +++ b/test/pumps/Pump.Update.t.sol @@ -156,23 +156,15 @@ contract PumpUpdateTest is TestHelper { mWell.update(address(pump), b, data); increaseTime(CAP_INTERVAL); - - console.log(1); - console.log(4); uint256[] memory emaReserves = pump.readInstantaneousReserves(address(mWell), data); - console.log(4); - console.log("EMA Reserves Length: %s", emaReserves.length); assertEq(emaReserves.length, 2); assertApproxEqAbs(emaReserves[0], 1_156_587, 1); // = 2^(log2(1000000) * 0.9^12 +log2(1224743) * (1-0.9^12)) assertApproxEqAbs(emaReserves[1], 1_729_223, 1); // = 2^(log2(2000000) * 0.9^12 +log2(1632992) * (1-0.9^12)) - console.log(3); bytes16[] memory cumulativeReserves = abi.decode(pump.readCumulativeReserves(address(mWell), data), (bytes16[])); assertApproxEqAbs(cumulativeReserves[0].div(ABDKMathQuad.fromUInt(12)).pow_2().toUInt(), 1_224_743, 1); assertApproxEqAbs(cumulativeReserves[1].div(ABDKMathQuad.fromUInt(12)).pow_2().toUInt(), 1_632_992, 1); - console.log(4); - (uint256[] memory twaReserves, bytes memory twaCumulativeReservesBytes) = pump.readTwaReserves(address(mWell), startCumulativeReserves, block.timestamp - CAP_INTERVAL, data); From 18b48eb26f48b72636f5f36687064f1c4d2551b9 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 23:19:33 +0200 Subject: [PATCH 41/69] update forge --- lib/forge-std | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/forge-std b/lib/forge-std index 5125ce50..07263d19 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 5125ce505fa60ca747df08ab2cc77db5445bd716 +Subproject commit 07263d193d621c4b2b0ce8b4d54af58f6957d97d From 4d40f45024baf06a729cb7bfbc4f30df4b9d9917 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 23 Jul 2024 23:34:57 +0200 Subject: [PATCH 42/69] update requirement.txt --- requirements.txt | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 44563cd0..65541fce 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,16 @@ -eth-abi==3.0.1 -pandas -numpy \ No newline at end of file +cytoolz==0.12.3 +eth-hash==0.7.0 +eth-typing==3.5.2 +eth-utils==2.3.1 +eth_abi==5.1.0 +numpy==2.0.1 +pandas==2.2.2 +parsimonious==0.10.0 +python-dateutil==2.9.0.post0 +pytz==2024.1 +regex==2024.5.15 +setuptools==71.1.0 +six==1.16.0 +toolz==0.12.1 +typing_extensions==4.12.2 +tzdata==2024.1 From cd4ae5f2990545e253482a0db1779e903340dbce Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 08:53:43 +0200 Subject: [PATCH 43/69] Update comment. --- src/WellUpgradeable.sol | 2 -- src/functions/Stable2.sol | 5 ++--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index 1ad69e39..ea60c50d 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -31,8 +31,6 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { } function init(string memory _name, string memory _symbol) external override reinitializer(2) { - // owner of Well param as the aquifier address will be the owner initially - // ownable init transfers ownership to msg.sender __ERC20Permit_init(_name); __ERC20_init(_name, _symbol); __ReentrancyGuard_init(); diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index c648c52b..ae26fc36 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -240,9 +240,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { /** * @inheritdoc IBeanstalkWellFunction - * @notice Calculates the amount of each reserve token underlying a given amount of LP tokens. - * @dev `calcReserveAtRatioLiquidity` fetches the closest approximate ratios from the target price, and - * perform an neutonian-estimation to calculate the reserves. + * @dev `calcReserveAtRatioLiquidity` fetches the closes approximate ratios from the target price, + * and performs newtons method in order to converge into a reserve. */ function calcReserveAtRatioLiquidity( uint256[] calldata reserves, From ae77893b1044292925bc33a23a4e25f2411396fb Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 10:15:24 +0200 Subject: [PATCH 44/69] update authors. --- src/functions/Stable2.sol | 4 ++-- src/functions/StableLUT/Stable2LUT1.sol | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index ae26fc36..ca25edc0 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -6,8 +6,9 @@ import {IBeanstalkWellFunction, IMultiFlowPumpWellFunction} from "src/interfaces import {ILookupTable} from "src/interfaces/ILookupTable.sol"; import {ProportionalLPToken2} from "src/functions/ProportionalLPToken2.sol"; import {IERC20} from "forge-std/interfaces/IERC20.sol"; + /** - * @author Brean + * @author brean, deadmanwalking * @title Gas efficient StableSwap pricing function for Wells with 2 tokens. * developed by curve. * @@ -21,7 +22,6 @@ import {IERC20} from "forge-std/interfaces/IERC20.sol"; * * @dev Limited to tokens with a maximum of 18 decimals. */ - contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { struct PriceData { uint256 targetPrice; diff --git a/src/functions/StableLUT/Stable2LUT1.sol b/src/functions/StableLUT/Stable2LUT1.sol index f2ec5960..9a78d7c3 100644 --- a/src/functions/StableLUT/Stable2LUT1.sol +++ b/src/functions/StableLUT/Stable2LUT1.sol @@ -6,7 +6,7 @@ import {ILookupTable} from "src/interfaces/ILookupTable.sol"; /** * @title Stable2LUT1 - * @author DeadManWalking, Brean + * @author Deadmanwalking, brean * @notice Implements a lookup table of estimations used in the Stableswap Well Function * to calculate the token ratios in a Stableswap pool for a given price. */ From 5fa75408aca0c0976604a70a5d70be9484f700aa Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 24 Jul 2024 23:50:35 +0200 Subject: [PATCH 45/69] update package version. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2dfa925c..27dc8b79 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.2.0-prerelease0", + "version": "1.2.0-prerelease1", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { From c6de3816fd3ec2b1af87ba7ae948b8387d3a28b3 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 13 Aug 2024 17:34:43 +0200 Subject: [PATCH 46/69] init --- src/functions/Stable2.sol | 41 +++++++++++++++++-- test/TestHelper.sol | 8 ++++ ...kStable2.calcReserveAtRatioLiquidity.t.sol | 18 +++++++- ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 26 +++++++++++- 4 files changed, 87 insertions(+), 6 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index ca25edc0..404f782e 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -26,6 +26,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { struct PriceData { uint256 targetPrice; uint256 currentPrice; + uint256 newPrice; uint256 maxStepSize; ILookupTable.PriceData lutData; } @@ -222,8 +223,26 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // calculate scaledReserve[i]: scaledReserves[i] = calcReserve(scaledReserves, i, lpTokenSupply, abi.encode(18, 18)); - // calc currentPrice: - pd.currentPrice = _calcRate(scaledReserves, i, j, lpTokenSupply); + // calculate new price from reserves: + pd.newPrice = _calcRate(scaledReserves, i, j, lpTokenSupply); + + // if the new current price is either lower or higher than both the previous current price and the target price, + // (i.e the target price lies between the current price and the previous current price), + // recalibrate high/low price. + if (pd.newPrice > pd.currentPrice && pd.newPrice > pd.targetPrice) { + pd.lutData.highPriceJ = scaledReserves[j] * 1e18 / parityReserve; + pd.lutData.highPriceI = scaledReserves[i] * 1e18 / parityReserve; + pd.lutData.highPrice = pd.newPrice; + } else if (pd.newPrice < pd.currentPrice && pd.newPrice < pd.targetPrice) { + pd.lutData.lowPriceJ = scaledReserves[j] * 1e18 / parityReserve; + pd.lutData.lowPriceI = scaledReserves[i] * 1e18 / parityReserve; + pd.lutData.lowPrice = pd.newPrice; + } + + // update max step size based on new scaled reserve. + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; + + pd.currentPrice = pd.newPrice; // check if new price is within 1 of target price: if (pd.currentPrice > pd.targetPrice) { @@ -288,7 +307,23 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { for (uint256 k; k < 255; k++) { scaledReserves[j] = updateReserve(pd, scaledReserves[j]); // calculate new price from reserves: - pd.currentPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); + pd.newPrice = calcRate(scaledReserves, i, j, abi.encode(18, 18)); + + // if the new current price is either lower or higher than both the previous current price and the target price, + // (i.e the target price lies between the current price and the previous current price), + // recalibrate high/lowPrice and continue. + if (pd.newPrice > pd.targetPrice && pd.targetPrice > pd.currentPrice) { + pd.lutData.highPriceJ = scaledReserves[j] * 1e18 / scaledReserves[i]; + pd.lutData.highPrice = pd.newPrice; + } else if (pd.newPrice < pd.targetPrice && pd.targetPrice < pd.currentPrice) { + pd.lutData.lowPriceJ = scaledReserves[j] * 1e18 / scaledReserves[i]; + pd.lutData.lowPrice = pd.newPrice; + } + + // update max step size based on new scaled reserve. + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; + + pd.currentPrice = pd.newPrice; // check if new price is within PRICE_THRESHOLD: if (pd.currentPrice > pd.targetPrice) { diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 3289be13..439b250d 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -13,6 +13,7 @@ import {Users} from "test/helpers/Users.sol"; import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; +import {ConstantProduct} from "src/functions/ConstantProduct.sol"; import {Stable2} from "src/functions/Stable2.sol"; import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; @@ -75,6 +76,13 @@ abstract contract TestHelper is Test, WellDeployer { setupWell(n, deployWellFunction(), deployPumps(1)); } + function setup3Well() internal { + Call memory _wellFunction; + _wellFunction.target = address(new ConstantProduct()); + _wellFunction.data = new bytes(0); + setupWell(3, deployWellFunction(), deployPumps(1)); + } + function setupWell(uint256 n, Call[] memory _pumps) internal { setupWell(n, deployWellFunction(), _pumps); } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index a229a992..83241aea 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -111,7 +111,7 @@ contract BeanstalkStable2LiquidityTest is TestHelper { // estimated price and actual price are within 0.04% in the worst case. assertApproxEqRel(reservePrice0, targetPrice, 0.0004e18, "reservePrice0 <> targetPrice"); assertApproxEqRel(reservePrice1, targetPrice, 0.0004e18, "reservePrice1 <> targetPrice"); - assertApproxEqRel(reservePrice0, reservePrice1, 0.0004e18, "reservePrice0 <> reservePrice1"); + assertApproxEqRel(reservePrice0, reservePrice1, 0.0005e18, "reservePrice0 <> reservePrice1"); } } @@ -121,4 +121,20 @@ contract BeanstalkStable2LiquidityTest is TestHelper { vm.expectRevert(); _f.calcReserveAtRatioLiquidity(reserves, 2, ratios, ""); } + + function test_calcReserveAtRatioLiquidityExtreme() public view { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 1e18; + reserves[1] = 1e18; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 8; + ratios[1] = 1; + + uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); + + reserves[0] = reserve0; + + uint256 price = _f.calcRate(reserves, 1, 0, data); + assertApproxEqRel(price, 125_000, 0.01e18); + } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 7788e81f..36a95493 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -60,8 +60,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 180.643950056605911775e18); // 180.64235400499155996e18, 100e18 - assertEq(reserve1, 39.474875366590812867e18); // 100e18, 39.474875366590812867e18 + assertEq(reserve0, 180.644064978044534737e18); // 180.644064978044534737e18, 100e18 + assertEq(reserve1, 39.475055811844664131e18); // 100e18, 39.475055811844664131e18 } function test_calcReserveAtRatioSwap_diff_diff() public view { @@ -101,4 +101,26 @@ contract BeanstalkStable2SwapTest is TestHelper { assertApproxEqRel(reservePrice0, targetPrice, 0.00015e18, "reservePrice0 <> targetPrice"); } } + + /** + * @notice verifies calcReserveAtRatioSwapExtreme works in the extreme ranges. + */ + function test_calcReserveAtRatioSwapExtreme() public view { + uint256[] memory reserves = new uint256[](2); + reserves[0] = 1e18; + reserves[1] = 1e18; + uint256[] memory ratios = new uint256[](2); + ratios[0] = 4202; + ratios[1] = 19_811; + uint256 targetPrice = uint256(ratios[0] * 1e6 / ratios[1]); + + uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); + uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); + + reserves[0] = reserve0; + reserves[1] = reserve1; + + uint256 price = _f.calcRate(reserves, 0, 1, data); + assertApproxEqAbs(price, targetPrice, 1); + } } From abf09ab7fc2b1e03f07e5f18951e1a3c0f39b4f7 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Tue, 13 Aug 2024 17:46:02 +0200 Subject: [PATCH 47/69] remove unused test helper function. --- test/TestHelper.sol | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/TestHelper.sol b/test/TestHelper.sol index 439b250d..3289be13 100644 --- a/test/TestHelper.sol +++ b/test/TestHelper.sol @@ -13,7 +13,6 @@ import {Users} from "test/helpers/Users.sol"; import {Well, Call, IERC20, IWell, IWellFunction} from "src/Well.sol"; import {Aquifer} from "src/Aquifer.sol"; import {ConstantProduct2} from "src/functions/ConstantProduct2.sol"; -import {ConstantProduct} from "src/functions/ConstantProduct.sol"; import {Stable2} from "src/functions/Stable2.sol"; import {Stable2LUT1} from "src/functions/StableLUT/Stable2LUT1.sol"; @@ -76,13 +75,6 @@ abstract contract TestHelper is Test, WellDeployer { setupWell(n, deployWellFunction(), deployPumps(1)); } - function setup3Well() internal { - Call memory _wellFunction; - _wellFunction.target = address(new ConstantProduct()); - _wellFunction.data = new bytes(0); - setupWell(3, deployWellFunction(), deployPumps(1)); - } - function setupWell(uint256 n, Call[] memory _pumps) internal { setupWell(n, deployWellFunction(), _pumps); } From 4eca74c1e15e08e15b3d30a44f82368fb8df53f0 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 14 Aug 2024 11:02:59 +0300 Subject: [PATCH 48/69] Add access control to authorizeUpgrade --- src/WellUpgradeable.sol | 2 +- test/WellUpgradeable.t.sol | 92 ++++++++++++++++++++++++++++++++------ 2 files changed, 80 insertions(+), 14 deletions(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index ea60c50d..6ae3c712 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -62,7 +62,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * @notice Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an ERC1167 minimal proxy from an aquifier, pointing to a well implmentation. */ - function _authorizeUpgrade(address newImplmentation) internal view override { + function _authorizeUpgrade(address newImplmentation) internal view override onlyOwner { // verify the function is called through a delegatecall. require(address(this) != ___self, "Function must be called through delegatecall"); diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index a14b291d..01d99b99 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -49,7 +49,10 @@ contract WellUpgradeTest is Test, WellDeployer { IWellFunction cp2 = new ConstantProduct2(); vm.label(address(cp2), "CP2"); wellFunctionAddress = address(cp2); - Call memory wellFunction = Call(address(cp2), abi.encode("beanstalkFunction")); + Call memory wellFunction = Call( + address(cp2), + abi.encode("beanstalkFunction") + ); // Pump IPump mockPump = new MockPump(); @@ -65,8 +68,14 @@ contract WellUpgradeTest is Test, WellDeployer { initialOwner = makeAddr("owner"); // Well - WellUpgradeable well = - encodeAndBoreWellUpgradeable(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0)); + WellUpgradeable well = encodeAndBoreWellUpgradeable( + aquifer, + wellImplementation, + tokens, + wellFunction, + pumps, + bytes32(0) + ); wellAddress = address(well); vm.label(wellAddress, "upgradeableWell"); // Sum up of what is going on here @@ -92,7 +101,10 @@ contract WellUpgradeTest is Test, WellDeployer { vm.startPrank(initialOwner); ERC1967Proxy proxy = new ERC1967Proxy( address(well), // implementation address - LibWellUpgradeableConstructor.encodeWellInitFunctionCall(tokens, wellFunction) // init data + LibWellUpgradeableConstructor.encodeWellInitFunctionCall( + tokens, + wellFunction + ) // init data ); vm.stopPrank(); proxyAddress = address(proxy); @@ -127,8 +139,12 @@ contract WellUpgradeTest is Test, WellDeployer { } function testProxyGetWellFunction() public { - Call memory proxyWellFunction = WellUpgradeable(proxyAddress).wellFunction(); - assertEq(address(proxyWellFunction.target), address(wellFunctionAddress)); + Call memory proxyWellFunction = WellUpgradeable(proxyAddress) + .wellFunction(); + assertEq( + address(proxyWellFunction.target), + address(wellFunctionAddress) + ); assertEq(proxyWellFunction.data, abi.encode("beanstalkFunction")); } @@ -143,7 +159,10 @@ contract WellUpgradeTest is Test, WellDeployer { function testProxyNumTokens() public { uint256 expectedNumTokens = 2; - assertEq(expectedNumTokens, WellUpgradeable(proxyAddress).numberOfTokens()); + assertEq( + expectedNumTokens, + WellUpgradeable(proxyAddress).numberOfTokens() + ); } ///////////////// Interaction test ////////////////// @@ -153,8 +172,18 @@ contract WellUpgradeTest is Test, WellDeployer { uint256[] memory amounts = new uint256[](2); amounts[0] = 1000; amounts[1] = 1000; - WellUpgradeable(wellAddress).addLiquidity(amounts, 0, user, type(uint256).max); - WellUpgradeable(proxyAddress).addLiquidity(amounts, 0, user, type(uint256).max); + WellUpgradeable(wellAddress).addLiquidity( + amounts, + 0, + user, + type(uint256).max + ); + WellUpgradeable(proxyAddress).addLiquidity( + amounts, + 0, + user, + type(uint256).max + ); assertEq(amounts, WellUpgradeable(proxyAddress).getReserves()); vm.stopPrank(); } @@ -173,9 +202,9 @@ contract WellUpgradeTest is Test, WellDeployer { } function testRevertTransferOwnershipFromNotOnwer() public { - vm.expectRevert(); address notOwner = makeAddr("notOwner"); vm.prank(notOwner); + vm.expectRevert(); WellUpgradeable(proxyAddress).transferOwnership(notOwner); } @@ -190,8 +219,14 @@ contract WellUpgradeTest is Test, WellDeployer { pumps[0] = Call(mockPumpAddress, abi.encode("2")); // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); - WellUpgradeable well2 = - encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2"))); + WellUpgradeable well2 = encodeAndBoreWellUpgradeable( + aquifer, + wellImpl, + tokens, + wellFunction, + pumps, + bytes32(abi.encode("2")) + ); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); @@ -199,9 +234,40 @@ contract WellUpgradeTest is Test, WellDeployer { proxy.upgradeTo(address(well2)); assertEq(initialOwner, MockWellUpgradeable(proxyAddress).owner()); // verify proxy was upgraded. - assertEq(address(well2), MockWellUpgradeable(proxyAddress).getImplementation()); + assertEq( + address(well2), + MockWellUpgradeable(proxyAddress).getImplementation() + ); assertEq(1, MockWellUpgradeable(proxyAddress).getVersion()); assertEq(100, MockWellUpgradeable(proxyAddress).getVersion(100)); vm.stopPrank(); } + + function testUpgradeToNewImplementationAccessControl() public { + IERC20[] memory tokens = new IERC20[](2); + tokens[0] = new MockToken("BEAN", "BEAN", 6); + tokens[1] = new MockToken("WETH", "WETH", 18); + Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); + Call[] memory pumps = new Call[](1); + pumps[0] = Call(mockPumpAddress, abi.encode("2")); + // create new mock Well Implementation: + address wellImpl = address(new MockWellUpgradeable()); + WellUpgradeable well2 = encodeAndBoreWellUpgradeable( + aquifer, + wellImpl, + tokens, + wellFunction, + pumps, + bytes32(abi.encode("2")) + ); + vm.label(address(well2), "upgradeableWell2"); + // set caller to not be the owner + address notOwner = makeAddr("notOwner"); + vm.startPrank(notOwner); + WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); + // expect revert + vm.expectRevert(); + proxy.upgradeTo(address(well2)); + vm.stopPrank(); + } } From 07e9924c64c5049c116bc3d5e1092630cbf4e17a Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 14 Aug 2024 11:22:13 +0300 Subject: [PATCH 49/69] Add explicit revert statements in the case of non convergence --- src/functions/Stable2.sol | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index ca25edc0..2514db25 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -100,6 +100,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; } } + revert("Non convergence: calcLpTokenSupply"); } /** @@ -140,7 +141,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } } } - revert("did not find convergence"); + revert("Non convergence: calcReserve"); } /** @@ -236,6 +237,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } } } + revert("Non convergence: calcReserveAtRatioSwap"); } /** @@ -301,6 +303,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } } } + revert("Non convergence: calcReserveAtRatioLiquidity"); } /** From 82a9f3ce5ada2c0f006441fdf27fb3a165af83ed Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 14 Aug 2024 10:53:24 +0200 Subject: [PATCH 50/69] impose limits on reserves, update formmating --- src/Well.sol | 27 +++++++++----------------- src/interfaces/IWell.sol | 7 +++---- src/libraries/LibLastReserveBytes.sol | 8 +++----- test/LiquidityHelper.sol | 14 ++++++------- test/functions/Stable2.t.sol | 11 +++++++---- test/integration/interfaces/ICurve.sol | 2 +- 6 files changed, 29 insertions(+), 40 deletions(-) diff --git a/src/Well.sol b/src/Well.sol index 84be74a8..f14793f4 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -484,12 +484,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Assumes that no tokens involved incur a fee on transfer. */ - function getAddLiquidityOut(uint256[] memory tokenAmountsIn) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountOut) - { + function getAddLiquidityOut( + uint256[] memory tokenAmountsIn + ) external view readOnlyNonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); @@ -527,12 +524,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityOut(uint256 lpAmountIn) - external - view - readOnlyNonReentrant - returns (uint256[] memory tokenAmountsOut) - { + function getRemoveLiquidityOut( + uint256 lpAmountIn + ) external view readOnlyNonReentrant returns (uint256[] memory tokenAmountsOut) { IERC20[] memory _tokens = tokens(); uint256[] memory reserves = _getReserves(_tokens.length); uint256 lpTokenSupply = totalSupply(); @@ -620,12 +614,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountIn) - { + function getRemoveLiquidityImbalancedIn( + uint256[] calldata tokenAmountsOut + ) external view readOnlyNonReentrant returns (uint256 lpAmountIn) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); diff --git a/src/interfaces/IWell.sol b/src/interfaces/IWell.sol index f7b00c47..1b734108 100644 --- a/src/interfaces/IWell.sol +++ b/src/interfaces/IWell.sol @@ -357,10 +357,9 @@ interface IWell { * @param tokenAmountsOut The amount of each underlying token to receive; MUST match the indexing of {Well.tokens} * @return lpAmountIn The amount of LP tokens burned */ - function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) - external - view - returns (uint256 lpAmountIn); + function getRemoveLiquidityImbalancedIn( + uint256[] calldata tokenAmountsOut + ) external view returns (uint256 lpAmountIn); //////////////////// RESERVES //////////////////// diff --git a/src/libraries/LibLastReserveBytes.sol b/src/libraries/LibLastReserveBytes.sol index 89aa9d1b..cfd43a34 100644 --- a/src/libraries/LibLastReserveBytes.sol +++ b/src/libraries/LibLastReserveBytes.sol @@ -82,11 +82,9 @@ library LibLastReserveBytes { /** * @dev Read `n` packed bytes16 reserves at storage position `slot`. */ - function readLastReserves(bytes32 slot) - internal - view - returns (uint8 n, uint40 lastTimestamp, uint256[] memory lastReserves) - { + function readLastReserves( + bytes32 slot + ) internal view returns (uint8 n, uint40 lastTimestamp, uint256[] memory lastReserves) { // Shortcut: two reserves can be quickly unpacked from one slot bytes32 temp; assembly { diff --git a/test/LiquidityHelper.sol b/test/LiquidityHelper.sol index 4f9164ba..37d047ce 100644 --- a/test/LiquidityHelper.sol +++ b/test/LiquidityHelper.sol @@ -48,10 +48,9 @@ contract LiquidityHelper is TestHelper { return beforeAddLiquidity(action); } - function beforeAddLiquidity(AddLiquidityAction memory action) - internal - returns (Snapshot memory, AddLiquidityAction memory) - { + function beforeAddLiquidity( + AddLiquidityAction memory action + ) internal returns (Snapshot memory, AddLiquidityAction memory) { Snapshot memory beforeSnapshot = _newSnapshot(); uint256[] memory amountToTransfer = new uint256[](tokens.length); @@ -97,10 +96,9 @@ contract LiquidityHelper is TestHelper { return beforeRemoveLiquidity(action); } - function beforeRemoveLiquidity(RemoveLiquidityAction memory action) - internal - returns (Snapshot memory, RemoveLiquidityAction memory) - { + function beforeRemoveLiquidity( + RemoveLiquidityAction memory action + ) internal returns (Snapshot memory, RemoveLiquidityAction memory) { Snapshot memory beforeSnapshot = _newSnapshot(); vm.expectEmit(true, true, true, true); diff --git a/test/functions/Stable2.t.sol b/test/functions/Stable2.t.sol index 3cff992b..b9385917 100644 --- a/test/functions/Stable2.t.sol +++ b/test/functions/Stable2.t.sol @@ -126,7 +126,9 @@ contract Stable2Test is WellFunctionHelper { _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); - reserves[1] = bound(_reserves[1], 10e18, MAX_RESERVE); + // reserve 1 must be at least 1/800th of the value of reserves[0]. + uint256 reserve1MinValue = (reserves[0] / 8e2) < 10e18 ? 10e18 : reserves[0] / 8e2; + reserves[1] = bound(_reserves[1], reserve1MinValue, MAX_RESERVE); uint256 lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); uint256[] memory underlying = _function.calcLPTokenUnderlying(lpTokenSupply, reserves, lpTokenSupply, _data); @@ -137,11 +139,12 @@ contract Stable2Test is WellFunctionHelper { //////////// FUZZ //////////// - function testFuzz_stableSwap(uint256 x, uint256 y, uint256 a) public { + function testFuzz_stableSwap(uint256 x, uint256 y) public { uint256[] memory reserves = new uint256[](2); reserves[0] = bound(x, 10e18, MAX_RESERVE); - reserves[1] = bound(y, 10e18, MAX_RESERVE); - a = bound(a, 1, 1_000_000); + // reserve 1 must be at least 1/800th of the value of reserves[0]. + uint256 reserve1MinValue = (reserves[0] / 8e2) < 10e18 ? 10e18 : reserves[0] / 8e2; + reserves[1] = bound(y, reserve1MinValue, MAX_RESERVE); _data = abi.encode(18, 18); diff --git a/test/integration/interfaces/ICurve.sol b/test/integration/interfaces/ICurve.sol index 3a0535f3..ef0850b6 100644 --- a/test/integration/interfaces/ICurve.sol +++ b/test/integration/interfaces/ICurve.sol @@ -1,6 +1,6 @@ // SPDX-License-Identifier: MIT pragma experimental ABIEncoderV2; -pragma solidity 0.8.20; +pragma solidity ^0.8.20; interface ICurvePool { function A_precise() external view returns (uint256); From af35125e4398eb27b93c5fb843a2d2f67e7ad1ac Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 14 Aug 2024 12:02:15 +0300 Subject: [PATCH 51/69] Add check for well tokens in authorizeUpgrade --- src/WellUpgradeable.sol | 8 ++++++ test/WellUpgradeable.t.sol | 57 +++++++++++++++++++++++++++++++++----- 2 files changed, 58 insertions(+), 7 deletions(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index ea60c50d..6bca00c1 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -77,6 +77,14 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { "New implementation must be a well implmentation" ); + // verify the new well uses the same tokens in the same order. + IERC20[] memory _tokens = tokens(); + IERC20[] memory newTokens = WellUpgradeable(newImplmentation).tokens(); + require(_tokens.length == newTokens.length, "New well must use the same number of tokens"); + for (uint256 i; i < _tokens.length; ++i) { + require(_tokens[i] == newTokens[i], "New well must use the same tokens in the same order"); + } + // verify the new implmentation is a valid ERC-1967 implmentation. require( UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT, diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index a14b291d..76f44bfe 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -28,12 +28,14 @@ contract WellUpgradeTest is Test, WellDeployer { address token2Address; address wellAddress; address wellImplementation; + IERC20[] tokens = new IERC20[](2); function setUp() public { // Tokens - IERC20[] memory tokens = new IERC20[](2); - tokens[0] = new MockToken("BEAN", "BEAN", 6); - tokens[1] = new MockToken("WETH", "WETH", 18); + IERC20 token0 = new MockToken("BEAN", "BEAN", 6); + IERC20 token1 = new MockToken("WETH", "WETH", 18); + tokens[0] = token0; + tokens[1] = token1; token1Address = address(tokens[0]); vm.label(token1Address, "token1"); @@ -182,18 +184,15 @@ contract WellUpgradeTest is Test, WellDeployer { ////////////////////// Upgrade Tests ////////////////////// function testUpgradeToNewImplementation() public { - IERC20[] memory tokens = new IERC20[](2); - tokens[0] = new MockToken("BEAN", "BEAN", 6); - tokens[1] = new MockToken("WETH", "WETH", 18); Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); Call[] memory pumps = new Call[](1); pumps[0] = Call(mockPumpAddress, abi.encode("2")); // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); + // bore new well with the same 2 tokens WellUpgradeable well2 = encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); - vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); proxy.upgradeTo(address(well2)); @@ -204,4 +203,48 @@ contract WellUpgradeTest is Test, WellDeployer { assertEq(100, MockWellUpgradeable(proxyAddress).getVersion(100)); vm.stopPrank(); } + + function testUpgradeToNewImplementationDiffTokens() public { + // create 2 new tokens with new addresses + IERC20[] memory newTokens = new IERC20[](2); + newTokens[0] = new MockToken("WBTC", "WBTC", 6); + newTokens[1] = new MockToken("WETH2", "WETH2", 18); + Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); + Call[] memory pumps = new Call[](1); + pumps[0] = Call(mockPumpAddress, abi.encode("2")); + // create new mock Well Implementation: + address wellImpl = address(new MockWellUpgradeable()); + // bore new well with the different tokens + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); + vm.label(address(well2), "upgradeableWell2"); + vm.startPrank(initialOwner); + WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); + // expect revert since new well uses different tokens + vm.expectRevert("New well must use the same tokens in the same order"); + proxy.upgradeTo(address(well2)); + vm.stopPrank(); + } + + function testUpgradeToNewImplementationDiffTokenOrder() public { + // create 2 new tokens with new addresses + IERC20[] memory newTokens = new IERC20[](2); + newTokens[0] = tokens[1]; + newTokens[1] = tokens[0]; + Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); + Call[] memory pumps = new Call[](1); + pumps[0] = Call(mockPumpAddress, abi.encode("2")); + // create new mock Well Implementation: + address wellImpl = address(new MockWellUpgradeable()); + // bore new well with the different tokens + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); + vm.label(address(well2), "upgradeableWell2"); + vm.startPrank(initialOwner); + WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); + // expect revert since new well uses different tokens + vm.expectRevert("New well must use the same tokens in the same order"); + proxy.upgradeTo(address(well2)); + vm.stopPrank(); + } } From 0143ccf549b441962f459d370ed889b45c49e7d0 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 14 Aug 2024 12:25:31 +0300 Subject: [PATCH 52/69] decimal fix --- src/functions/Stable2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index ca25edc0..bd5fed7b 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -314,7 +314,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { if (decimal0 == 0) { decimal0 = 18; } - if (decimal0 == 0) { + if (decimal1 == 0) { decimal1 = 18; } if (decimal0 > 18 || decimal1 > 18) revert InvalidTokenDecimals(); From 812bca2ddaf1fe7928e1059cc9ebf7fa1a738942 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 14 Aug 2024 12:48:58 +0300 Subject: [PATCH 53/69] remove else statment in notDelegatedOrIsMinimalProxy modifier --- src/WellUpgradeable.sol | 4 +--- test/WellUpgradeable.t.sol | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index ea60c50d..91c6d591 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -17,15 +17,13 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { address private immutable ___self = address(this); /** - * @notice verifies that the execution is called through an minimal proxy or is not a delegate call. + * @notice Verifies that the execution is called through an minimal proxy. */ modifier notDelegatedOrIsMinimalProxy() { if (address(this) != ___self) { address aquifer = aquifer(); address wellImplmentation = IAquifer(aquifer).wellImplementation(address(this)); require(wellImplmentation == ___self, "Function must be called by a Well bored by an aquifer"); - } else { - revert("UUPSUpgradeable: must not be called through delegatecall"); } _; } diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index a14b291d..deeb04d1 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -74,7 +74,7 @@ contract WellUpgradeTest is Test, WellDeployer { // The well upgradeable additionally takes in an owner address so we modify the init function call // to include the owner address. // When the new well is deployed, all init data are stored in the implementation storage - // including pump and well function data --> NOTE: This could be an issue but how do we solve this? + // including pump and well function data // Then we deploy a ERC1967Proxy proxy for the well upgradeable and call the init function on the proxy // When we deploy the proxy, the init data is stored in the proxy storage and the well is initialized // for the second time. We can now control the well via delegate calls to the proxy address. From 453d388ff1379dd99e55d82780e9116b42b798fe Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 14 Aug 2024 12:02:10 +0200 Subject: [PATCH 54/69] update tests --- test/Stable2/Well.Stable2.AddLiquidity.t.sol | 4 +++- .../BeanstalkStable2.calcReserveAtRatioSwap.t.sol | 8 +++++++- test/functions/Stable2.t.sol | 8 ++++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/test/Stable2/Well.Stable2.AddLiquidity.t.sol b/test/Stable2/Well.Stable2.AddLiquidity.t.sol index 710e6e9f..282bf692 100644 --- a/test/Stable2/Well.Stable2.AddLiquidity.t.sol +++ b/test/Stable2/Well.Stable2.AddLiquidity.t.sol @@ -158,7 +158,9 @@ contract WellStable2AddLiquidityTest is LiquidityHelper { // amounts to add as liquidity uint256[] memory amounts = new uint256[](2); amounts[0] = bound(x, 0, type(uint104).max); - amounts[1] = bound(y, 0, type(uint104).max); + // reserve 1 must be at least 1/600th of the value of amounts[0]. + uint256 reserve1MinValue = (amounts[0] / 6e2) < 10e18 ? 10e18 : amounts[0] / 6e2; + amounts[1] = bound(y, reserve1MinValue, type(uint104).max); mintTokens(user, amounts); Snapshot memory before; diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 7788e81f..4a70f807 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -83,7 +83,13 @@ contract BeanstalkStable2SwapTest is TestHelper { for (uint256 i; i < 2; ++i) { // Upper bound is limited by stableSwap, // due to the stableswap reserves being extremely far apart. - reserves[i] = bound(reserves[i], 1e18, 1e31); + + if (i == 1) { + uint256 reserve1MinValue = (reserves[0] / 6e2) < 10e18 ? 10e18 : reserves[0] / 6e2; + reserves[1] = bound(reserves[i], reserve1MinValue, 1e31); + } else { + reserves[i] = bound(reserves[i], 1e18, 1e31); + } ratios[i] = bound(ratios[i], 1e18, 4e18); } diff --git a/test/functions/Stable2.t.sol b/test/functions/Stable2.t.sol index b9385917..eeb9c731 100644 --- a/test/functions/Stable2.t.sol +++ b/test/functions/Stable2.t.sol @@ -126,8 +126,8 @@ contract Stable2Test is WellFunctionHelper { _data = abi.encode(18, 18); uint256[] memory reserves = new uint256[](2); reserves[0] = bound(_reserves[0], 10e18, MAX_RESERVE); - // reserve 1 must be at least 1/800th of the value of reserves[0]. - uint256 reserve1MinValue = (reserves[0] / 8e2) < 10e18 ? 10e18 : reserves[0] / 8e2; + // reserve 1 must be at least 1/600th of the value of reserves[0]. + uint256 reserve1MinValue = (reserves[0] / 6e2) < 10e18 ? 10e18 : reserves[0] / 6e2; reserves[1] = bound(_reserves[1], reserve1MinValue, MAX_RESERVE); uint256 lpTokenSupply = _function.calcLpTokenSupply(reserves, _data); @@ -142,8 +142,8 @@ contract Stable2Test is WellFunctionHelper { function testFuzz_stableSwap(uint256 x, uint256 y) public { uint256[] memory reserves = new uint256[](2); reserves[0] = bound(x, 10e18, MAX_RESERVE); - // reserve 1 must be at least 1/800th of the value of reserves[0]. - uint256 reserve1MinValue = (reserves[0] / 8e2) < 10e18 ? 10e18 : reserves[0] / 8e2; + // reserve 1 must be at least 1/600th of the value of reserves[0]. + uint256 reserve1MinValue = (reserves[0] / 6e2) < 10e18 ? 10e18 : reserves[0] / 6e2; reserves[1] = bound(y, reserve1MinValue, MAX_RESERVE); _data = abi.encode(18, 18); From 54ac748e50d22491ab18e9d2259ea97c58d09621 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 14 Aug 2024 12:08:08 +0200 Subject: [PATCH 55/69] update optimizer to compile. --- foundry.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/foundry.toml b/foundry.toml index fc84913c..e8ae0e3a 100644 --- a/foundry.toml +++ b/foundry.toml @@ -4,7 +4,7 @@ out = 'out' libs = ['lib', 'node_modules'] fuzz = { runs = 256 } optimizer = true -optimizer_runs = 400 +optimizer_runs = 200 remappings = [ '@openzeppelin/=node_modules/@openzeppelin/', ] @@ -27,7 +27,6 @@ ignore = ["src/libraries/LibClone.sol", "src/utils/Clone.sol", "src/libraries/AB int_types = "long" line_length = 120 multiline_func_header = "params_first" -number_underscore = "thousands" override_spacing = false quote_style = "double" tab_width = 4 From 5d12098b5b6706c986359c54ae8bf0151540a5e0 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 14 Aug 2024 12:09:33 +0200 Subject: [PATCH 56/69] update formmating --- src/Well.sol | 27 +++++++++------------------ src/interfaces/IWell.sol | 7 +++---- src/libraries/LibLastReserveBytes.sol | 8 +++----- test/LiquidityHelper.sol | 14 ++++++-------- 4 files changed, 21 insertions(+), 35 deletions(-) diff --git a/src/Well.sol b/src/Well.sol index 84be74a8..f14793f4 100644 --- a/src/Well.sol +++ b/src/Well.sol @@ -484,12 +484,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr /** * @dev Assumes that no tokens involved incur a fee on transfer. */ - function getAddLiquidityOut(uint256[] memory tokenAmountsIn) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountOut) - { + function getAddLiquidityOut( + uint256[] memory tokenAmountsIn + ) external view readOnlyNonReentrant returns (uint256 lpAmountOut) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); @@ -527,12 +524,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityOut(uint256 lpAmountIn) - external - view - readOnlyNonReentrant - returns (uint256[] memory tokenAmountsOut) - { + function getRemoveLiquidityOut( + uint256 lpAmountIn + ) external view readOnlyNonReentrant returns (uint256[] memory tokenAmountsOut) { IERC20[] memory _tokens = tokens(); uint256[] memory reserves = _getReserves(_tokens.length); uint256 lpTokenSupply = totalSupply(); @@ -620,12 +614,9 @@ contract Well is ERC20PermitUpgradeable, IWell, IWellErrors, ReentrancyGuardUpgr emit RemoveLiquidity(lpAmountIn, tokenAmountsOut, recipient); } - function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) - external - view - readOnlyNonReentrant - returns (uint256 lpAmountIn) - { + function getRemoveLiquidityImbalancedIn( + uint256[] calldata tokenAmountsOut + ) external view readOnlyNonReentrant returns (uint256 lpAmountIn) { IERC20[] memory _tokens = tokens(); uint256 tokensLength = _tokens.length; uint256[] memory reserves = _getReserves(tokensLength); diff --git a/src/interfaces/IWell.sol b/src/interfaces/IWell.sol index f7b00c47..1b734108 100644 --- a/src/interfaces/IWell.sol +++ b/src/interfaces/IWell.sol @@ -357,10 +357,9 @@ interface IWell { * @param tokenAmountsOut The amount of each underlying token to receive; MUST match the indexing of {Well.tokens} * @return lpAmountIn The amount of LP tokens burned */ - function getRemoveLiquidityImbalancedIn(uint256[] calldata tokenAmountsOut) - external - view - returns (uint256 lpAmountIn); + function getRemoveLiquidityImbalancedIn( + uint256[] calldata tokenAmountsOut + ) external view returns (uint256 lpAmountIn); //////////////////// RESERVES //////////////////// diff --git a/src/libraries/LibLastReserveBytes.sol b/src/libraries/LibLastReserveBytes.sol index 89aa9d1b..cfd43a34 100644 --- a/src/libraries/LibLastReserveBytes.sol +++ b/src/libraries/LibLastReserveBytes.sol @@ -82,11 +82,9 @@ library LibLastReserveBytes { /** * @dev Read `n` packed bytes16 reserves at storage position `slot`. */ - function readLastReserves(bytes32 slot) - internal - view - returns (uint8 n, uint40 lastTimestamp, uint256[] memory lastReserves) - { + function readLastReserves( + bytes32 slot + ) internal view returns (uint8 n, uint40 lastTimestamp, uint256[] memory lastReserves) { // Shortcut: two reserves can be quickly unpacked from one slot bytes32 temp; assembly { diff --git a/test/LiquidityHelper.sol b/test/LiquidityHelper.sol index 4f9164ba..37d047ce 100644 --- a/test/LiquidityHelper.sol +++ b/test/LiquidityHelper.sol @@ -48,10 +48,9 @@ contract LiquidityHelper is TestHelper { return beforeAddLiquidity(action); } - function beforeAddLiquidity(AddLiquidityAction memory action) - internal - returns (Snapshot memory, AddLiquidityAction memory) - { + function beforeAddLiquidity( + AddLiquidityAction memory action + ) internal returns (Snapshot memory, AddLiquidityAction memory) { Snapshot memory beforeSnapshot = _newSnapshot(); uint256[] memory amountToTransfer = new uint256[](tokens.length); @@ -97,10 +96,9 @@ contract LiquidityHelper is TestHelper { return beforeRemoveLiquidity(action); } - function beforeRemoveLiquidity(RemoveLiquidityAction memory action) - internal - returns (Snapshot memory, RemoveLiquidityAction memory) - { + function beforeRemoveLiquidity( + RemoveLiquidityAction memory action + ) internal returns (Snapshot memory, RemoveLiquidityAction memory) { Snapshot memory beforeSnapshot = _newSnapshot(); vm.expectEmit(true, true, true, true); From 1409811c8dd74001c1e346123ff6e5ad7858aed7 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 13:51:30 +0300 Subject: [PATCH 57/69] fix tests --- test/WellUpgradeable.t.sol | 48 +++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 13 deletions(-) diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index 1b3a8cea..61ca430e 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -241,15 +241,9 @@ contract WellUpgradeTest is Test, WellDeployer { vm.stopPrank(); } + ///////////////// Access Control //////////////////// + function testUpgradeToNewImplementationAccessControl() public { - IERC20[] memory tokens = new IERC20[](2); - tokens[0] = new MockToken("BEAN", "BEAN", 6); - tokens[1] = new MockToken("WETH", "WETH", 18); - function testUpgradeToNewImplementationDiffTokens() public { - // create 2 new tokens with new addresses - IERC20[] memory newTokens = new IERC20[](2); - newTokens[0] = new MockToken("WBTC", "WBTC", 6); - newTokens[1] = new MockToken("WETH2", "WETH2", 18); Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); Call[] memory pumps = new Call[](1); pumps[0] = Call(mockPumpAddress, abi.encode("2")); @@ -269,10 +263,32 @@ contract WellUpgradeTest is Test, WellDeployer { vm.startPrank(notOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); // expect revert - vm.expectRevert(); + vm.expectRevert("Ownable: caller is not the owner"); + proxy.upgradeTo(address(well2)); + vm.stopPrank(); + } + + ///////////////////// Token Check ////////////////////// + + function testUpgradeToNewImplementationDiffTokens() public { + // create 2 new tokens with new addresses + IERC20[] memory newTokens = new IERC20[](2); + newTokens[0] = new MockToken("WBTC", "WBTC", 6); + newTokens[1] = new MockToken("WETH2", "WETH2", 18); + Call memory wellFunction = Call(wellFunctionAddress, abi.encode("2")); + Call[] memory pumps = new Call[](1); + pumps[0] = Call(mockPumpAddress, abi.encode("2")); + // create new mock Well Implementation: + address wellImpl = address(new MockWellUpgradeable()); // bore new well with the different tokens - WellUpgradeable well2 = - encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); + WellUpgradeable well2 = encodeAndBoreWellUpgradeable( + aquifer, + wellImpl, + newTokens, + wellFunction, + pumps, + bytes32(abi.encode("2")) + ); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); @@ -293,8 +309,14 @@ contract WellUpgradeTest is Test, WellDeployer { // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); // bore new well with the different tokens - WellUpgradeable well2 = - encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); + WellUpgradeable well2 = encodeAndBoreWellUpgradeable( + aquifer, + wellImpl, + newTokens, + wellFunction, + pumps, + bytes32(abi.encode("2")) + ); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); From a9c1f68abda903293e5505b7b0ff258fca27556c Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 14:05:03 +0300 Subject: [PATCH 58/69] update comment in well upgr test --- test/WellUpgradeable.t.sol | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index 61ca430e..3e3c35ae 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -90,9 +90,8 @@ contract WellUpgradeTest is Test, WellDeployer { // When we deploy the proxy, the init data is stored in the proxy storage and the well is initialized // for the second time. We can now control the well via delegate calls to the proxy address. - // Every time we call the init function, we init the owner to be the msg.sender and - // then immidiately transfer ownership - // to an address of our choice (see WellUpgradeable.sol for more details on the init function) + // Every time we call the init function, we init the owner to be the msg.sender + // (see WellUpgradeable.sol for more details on the init function) // FROM OZ // If _data is nonempty, it’s used as data in a delegate call to _logic. From 3241f9cc30f68221990b33af501c3fe8fea97968 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Wed, 21 Aug 2024 15:33:12 +0200 Subject: [PATCH 59/69] update package version, formatting. --- package.json | 2 +- test/WellUpgradeable.t.sol | 92 ++++++++------------------------------ 2 files changed, 19 insertions(+), 75 deletions(-) diff --git a/package.json b/package.json index 27dc8b79..cc05a548 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.2.0-prerelease1", + "version": "1.2.0-prerelease2", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { diff --git a/test/WellUpgradeable.t.sol b/test/WellUpgradeable.t.sol index 3e3c35ae..e94ee079 100644 --- a/test/WellUpgradeable.t.sol +++ b/test/WellUpgradeable.t.sol @@ -51,10 +51,7 @@ contract WellUpgradeTest is Test, WellDeployer { IWellFunction cp2 = new ConstantProduct2(); vm.label(address(cp2), "CP2"); wellFunctionAddress = address(cp2); - Call memory wellFunction = Call( - address(cp2), - abi.encode("beanstalkFunction") - ); + Call memory wellFunction = Call(address(cp2), abi.encode("beanstalkFunction")); // Pump IPump mockPump = new MockPump(); @@ -70,14 +67,8 @@ contract WellUpgradeTest is Test, WellDeployer { initialOwner = makeAddr("owner"); // Well - WellUpgradeable well = encodeAndBoreWellUpgradeable( - aquifer, - wellImplementation, - tokens, - wellFunction, - pumps, - bytes32(0) - ); + WellUpgradeable well = + encodeAndBoreWellUpgradeable(aquifer, wellImplementation, tokens, wellFunction, pumps, bytes32(0)); wellAddress = address(well); vm.label(wellAddress, "upgradeableWell"); // Sum up of what is going on here @@ -102,10 +93,7 @@ contract WellUpgradeTest is Test, WellDeployer { vm.startPrank(initialOwner); ERC1967Proxy proxy = new ERC1967Proxy( address(well), // implementation address - LibWellUpgradeableConstructor.encodeWellInitFunctionCall( - tokens, - wellFunction - ) // init data + LibWellUpgradeableConstructor.encodeWellInitFunctionCall(tokens, wellFunction) // init data ); vm.stopPrank(); proxyAddress = address(proxy); @@ -140,12 +128,8 @@ contract WellUpgradeTest is Test, WellDeployer { } function testProxyGetWellFunction() public { - Call memory proxyWellFunction = WellUpgradeable(proxyAddress) - .wellFunction(); - assertEq( - address(proxyWellFunction.target), - address(wellFunctionAddress) - ); + Call memory proxyWellFunction = WellUpgradeable(proxyAddress).wellFunction(); + assertEq(address(proxyWellFunction.target), address(wellFunctionAddress)); assertEq(proxyWellFunction.data, abi.encode("beanstalkFunction")); } @@ -160,10 +144,7 @@ contract WellUpgradeTest is Test, WellDeployer { function testProxyNumTokens() public { uint256 expectedNumTokens = 2; - assertEq( - expectedNumTokens, - WellUpgradeable(proxyAddress).numberOfTokens() - ); + assertEq(expectedNumTokens, WellUpgradeable(proxyAddress).numberOfTokens()); } ///////////////// Interaction test ////////////////// @@ -173,18 +154,8 @@ contract WellUpgradeTest is Test, WellDeployer { uint256[] memory amounts = new uint256[](2); amounts[0] = 1000; amounts[1] = 1000; - WellUpgradeable(wellAddress).addLiquidity( - amounts, - 0, - user, - type(uint256).max - ); - WellUpgradeable(proxyAddress).addLiquidity( - amounts, - 0, - user, - type(uint256).max - ); + WellUpgradeable(wellAddress).addLiquidity(amounts, 0, user, type(uint256).max); + WellUpgradeable(proxyAddress).addLiquidity(amounts, 0, user, type(uint256).max); assertEq(amounts, WellUpgradeable(proxyAddress).getReserves()); vm.stopPrank(); } @@ -217,24 +188,15 @@ contract WellUpgradeTest is Test, WellDeployer { pumps[0] = Call(mockPumpAddress, abi.encode("2")); // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); - WellUpgradeable well2 = encodeAndBoreWellUpgradeable( - aquifer, - wellImpl, - tokens, - wellFunction, - pumps, - bytes32(abi.encode("2")) - ); + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); proxy.upgradeTo(address(well2)); assertEq(initialOwner, MockWellUpgradeable(proxyAddress).owner()); // verify proxy was upgraded. - assertEq( - address(well2), - MockWellUpgradeable(proxyAddress).getImplementation() - ); + assertEq(address(well2), MockWellUpgradeable(proxyAddress).getImplementation()); assertEq(1, MockWellUpgradeable(proxyAddress).getVersion()); assertEq(100, MockWellUpgradeable(proxyAddress).getVersion(100)); vm.stopPrank(); @@ -248,14 +210,8 @@ contract WellUpgradeTest is Test, WellDeployer { pumps[0] = Call(mockPumpAddress, abi.encode("2")); // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); - WellUpgradeable well2 = encodeAndBoreWellUpgradeable( - aquifer, - wellImpl, - tokens, - wellFunction, - pumps, - bytes32(abi.encode("2")) - ); + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, tokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); // set caller to not be the owner address notOwner = makeAddr("notOwner"); @@ -280,14 +236,8 @@ contract WellUpgradeTest is Test, WellDeployer { // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); // bore new well with the different tokens - WellUpgradeable well2 = encodeAndBoreWellUpgradeable( - aquifer, - wellImpl, - newTokens, - wellFunction, - pumps, - bytes32(abi.encode("2")) - ); + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); @@ -308,14 +258,8 @@ contract WellUpgradeTest is Test, WellDeployer { // create new mock Well Implementation: address wellImpl = address(new MockWellUpgradeable()); // bore new well with the different tokens - WellUpgradeable well2 = encodeAndBoreWellUpgradeable( - aquifer, - wellImpl, - newTokens, - wellFunction, - pumps, - bytes32(abi.encode("2")) - ); + WellUpgradeable well2 = + encodeAndBoreWellUpgradeable(aquifer, wellImpl, newTokens, wellFunction, pumps, bytes32(abi.encode("2"))); vm.label(address(well2), "upgradeableWell2"); vm.startPrank(initialOwner); WellUpgradeable proxy = WellUpgradeable(payable(proxyAddress)); From 7ee69fdf5a6ea59d2fb4a57a6669949450263cc4 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:25:35 +0300 Subject: [PATCH 60/69] fix typo in _authorizeUpgrade --- src/WellUpgradeable.sol | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index fe763814..a1c5da68 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -60,7 +60,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * @notice Check that the execution is being performed through a delegatecall call and that the execution context is * a proxy contract with an ERC1167 minimal proxy from an aquifier, pointing to a well implmentation. */ - function _authorizeUpgrade(address newImplmentation) internal view override onlyOwner { + function _authorizeUpgrade(address newImplementation) internal view override onlyOwner { // verify the function is called through a delegatecall. require(address(this) != ___self, "Function must be called through delegatecall"); @@ -71,13 +71,13 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { // verify the new implmentation is a well bored by an aquifier. require( - IAquifer(aquifer).wellImplementation(newImplmentation) != address(0), + IAquifer(aquifer).wellImplementation(newImplementation) != address(0), "New implementation must be a well implmentation" ); // verify the new well uses the same tokens in the same order. IERC20[] memory _tokens = tokens(); - IERC20[] memory newTokens = WellUpgradeable(newImplmentation).tokens(); + IERC20[] memory newTokens = WellUpgradeable(newImplementation).tokens(); require(_tokens.length == newTokens.length, "New well must use the same number of tokens"); for (uint256 i; i < _tokens.length; ++i) { require(_tokens[i] == newTokens[i], "New well must use the same tokens in the same order"); @@ -85,7 +85,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { // verify the new implmentation is a valid ERC-1967 implmentation. require( - UUPSUpgradeable(newImplmentation).proxiableUUID() == _IMPLEMENTATION_SLOT, + UUPSUpgradeable(newImplementation).proxiableUUID() == _IMPLEMENTATION_SLOT, "New implementation must be a valid ERC-1967 implmentation" ); } From 42a4dbc84fc0040f67f9184f5e363319e78845ae Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:25:58 +0300 Subject: [PATCH 61/69] clarify comment in calcReserveAtRatioSwap --- src/functions/Stable2.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 884f3249..f7ebb332 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -245,7 +245,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { pd.currentPrice = pd.newPrice; - // check if new price is within 1 of target price: + // check if new price is within PRICE_THRESHOLD: if (pd.currentPrice > pd.targetPrice) { if (pd.currentPrice - pd.targetPrice <= PRICE_THRESHOLD) { return scaledReserves[j] / (10 ** (18 - decimals[j])); From 6b510150197520e23d4d62d8526f54afcf303a17 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:36:58 +0300 Subject: [PATCH 62/69] change proxiableUUID visibility to public --- src/WellUpgradeable.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/WellUpgradeable.sol b/src/WellUpgradeable.sol index a1c5da68..171877e1 100644 --- a/src/WellUpgradeable.sol +++ b/src/WellUpgradeable.sol @@ -121,7 +121,7 @@ contract WellUpgradeable is Well, UUPSUpgradeable, OwnableUpgradeable { * are ERC-1167 minimal immutable clones and cannot delgate to another proxy. Thus, `proxiableUUID` was updated to support * this specific usecase. */ - function proxiableUUID() external view override notDelegatedOrIsMinimalProxy returns (bytes32) { + function proxiableUUID() public view override notDelegatedOrIsMinimalProxy returns (bytes32) { return _IMPLEMENTATION_SLOT; } From ec8e7e3aaa8f598b1f0ebad1ef072e794a6d3e06 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:41:49 +0300 Subject: [PATCH 63/69] remove unreachable code blocks in stable2 --- src/functions/Stable2.sol | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index f7ebb332..07b3b121 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -213,11 +213,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } // calculate max step size: - if (pd.lutData.lowPriceJ > pd.lutData.highPriceJ) { - pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; - } else { - pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; - } + // lowPriceJ will always be larger than highPriceJ so a check here is unnecessary. + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; for (uint256 k; k < 255; k++) { scaledReserves[j] = updateReserve(pd, scaledReserves[j]); @@ -300,11 +297,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { } // calculate max step size: - if (pd.lutData.lowPriceJ > pd.lutData.highPriceJ) { - pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; - } else { - pd.maxStepSize = scaledReserves[j] * (pd.lutData.highPriceJ - pd.lutData.lowPriceJ) / pd.lutData.highPriceJ; - } + // lowPriceJ will always be larger than highPriceJ so a check here is unnecessary. + pd.maxStepSize = scaledReserves[j] * (pd.lutData.lowPriceJ - pd.lutData.highPriceJ) / pd.lutData.lowPriceJ; for (uint256 k; k < 255; k++) { scaledReserves[j] = updateReserve(pd, scaledReserves[j]); From ba1ce3f308177c2bafc2224948f8e1cbcdfbe6f2 Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:45:34 +0300 Subject: [PATCH 64/69] remove redundant sumReserves check in calcLpTokenSupply --- src/functions/Stable2.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 07b3b121..9d9fe4a7 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -82,9 +82,8 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); uint256 Ann = a * N * N; - + uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; - if (sumReserves == 0) return 0; lpTokenSupply = sumReserves; for (uint256 i = 0; i < 255; i++) { uint256 dP = lpTokenSupply; From fa50f70d38f7ee1d9601f3fd215254401ea4d60f Mon Sep 17 00:00:00 2001 From: nickkatsios Date: Wed, 21 Aug 2024 17:45:51 +0300 Subject: [PATCH 65/69] add venv files to gitignore --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index ff8f73d9..36206d14 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,10 @@ cache/ out/ +# Python virtual environments +env/ +venv/ + .vscode # Ignores development broadcast logs From c6ce2023954ebffd8bfcfaf13df4643ecd0762f6 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 22 Aug 2024 09:34:49 +0200 Subject: [PATCH 66/69] update package version. --- package.json | 2 +- src/functions/Stable2.sol | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index cc05a548..2c73df3c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.2.0-prerelease2", + "version": "1.2.0-prerelease3", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": { diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 9d9fe4a7..05d2fa55 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -82,7 +82,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256[] memory scaledReserves = getScaledReserves(reserves, decimals); uint256 Ann = a * N * N; - + uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; lpTokenSupply = sumReserves; for (uint256 i = 0; i < 255; i++) { From 20bb66aed5dc58c612ee6478b6698951f0a5d4e8 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 29 Aug 2024 11:54:54 +0200 Subject: [PATCH 67/69] Add check for oscillation in calcLpTokenSupply. --- src/functions/Stable2.sol | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 05d2fa55..4f7ac989 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -86,6 +86,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 sumReserves = scaledReserves[0] + scaledReserves[1]; lpTokenSupply = sumReserves; for (uint256 i = 0; i < 255; i++) { + bool stableOscillation; uint256 dP = lpTokenSupply; // If division by 0, this will be borked: only withdrawal will work. And that is good dP = dP * lpTokenSupply / (scaledReserves[0] * N); @@ -93,10 +94,25 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 prevReserves = lpTokenSupply; lpTokenSupply = (Ann * sumReserves / A_PRECISION + (dP * N)) * lpTokenSupply / (((Ann - A_PRECISION) * lpTokenSupply / A_PRECISION) + ((N + 1) * dP)); + // Equality with the precision of 1 + // If the difference between the current lpTokenSupply and the previous lpTokenSupply is 2, + // Check that the oscillation is stable, and if so, return the average between the two. if (lpTokenSupply > prevReserves) { + if (lpTokenSupply - prevReserves == 2) { + if (stableOscillation) { + return lpTokenSupply - 1; + } + stableOscillation = true; + } if (lpTokenSupply - prevReserves <= 1) return lpTokenSupply; } else { + if (prevReserves - lpTokenSupply == 2) { + if (stableOscillation) { + return lpTokenSupply + 1; + } + stableOscillation = true; + } if (prevReserves - lpTokenSupply <= 1) return lpTokenSupply; } } From ff021fed640021ef10972035b7991b2328ce132e Mon Sep 17 00:00:00 2001 From: Brean0 Date: Thu, 29 Aug 2024 12:57:50 +0200 Subject: [PATCH 68/69] change absolute diff to relative diff, increase price precision. --- src/functions/Stable2.sol | 21 ++++++++++++++++--- ...kStable2.calcReserveAtRatioLiquidity.t.sol | 2 +- ...nstalkStable2.calcReserveAtRatioSwap.t.sol | 10 ++++----- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/functions/Stable2.sol b/src/functions/Stable2.sol index 4f7ac989..430683a3 100644 --- a/src/functions/Stable2.sol +++ b/src/functions/Stable2.sol @@ -42,7 +42,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // price threshold. more accurate pricing requires a lower threshold, // at the cost of higher execution costs. - uint256 constant PRICE_THRESHOLD = 100; // 0.01% + uint256 constant PRICE_THRESHOLD = 10; // 0.001% address immutable lookupTable; uint256 immutable a; @@ -213,7 +213,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { uint256 parityReserve = lpTokenSupply / 2; // update `scaledReserves` based on whether targetPrice is closer to low or high price: - if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + if (percentDiff(pd.lutData.highPrice, pd.targetPrice) > percentDiff(pd.lutData.lowPrice, pd.targetPrice)) { // targetPrice is closer to lowPrice. scaledReserves[i] = parityReserve * pd.lutData.lowPriceI / pd.lutData.precision; scaledReserves[j] = parityReserve * pd.lutData.lowPriceJ / pd.lutData.precision; @@ -297,7 +297,7 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { // update scaledReserve[j] such that calcRate(scaledReserves, i, j) = low/high Price, // depending on which is closer to targetPrice. - if (pd.lutData.highPrice - pd.targetPrice > pd.targetPrice - pd.lutData.lowPrice) { + if (percentDiff(pd.lutData.highPrice, pd.targetPrice) > percentDiff(pd.lutData.lowPrice, pd.targetPrice)) { // targetPrice is closer to lowPrice. scaledReserves[j] = scaledReserves[i] * pd.lutData.lowPriceJ / pd.lutData.precision; @@ -444,4 +444,19 @@ contract Stable2 is ProportionalLPToken2, IBeanstalkWellFunction { + pd.maxStepSize * (pd.currentPrice - pd.targetPrice) / (pd.lutData.highPrice - pd.lutData.lowPrice); } } + + /** + * @notice Calculate the percentage difference between two numbers. + * @return The percentage difference as a fixed-point number with 18 decimals. + * @dev This function calculates the absolute percentage difference: + * |(a - b)| / ((a + b) / 2) * 100 + * The result is scaled by 1e18 for precision. + */ + function percentDiff(uint256 _a, uint256 _b) internal pure returns (uint256) { + if (_a == _b) return 0; + uint256 difference = _a > _b ? _a - _b : _b - _a; + uint256 average = (_a + _b) / 2; + // Multiply by 100 * 1e18 to get percentage with 18 decimal places + return (difference * 100 * 1e18) / average; + } } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol index 83241aea..9d0395e4 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioLiquidity.t.sol @@ -61,7 +61,7 @@ contract BeanstalkStable2LiquidityTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioLiquidity(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioLiquidity(reserves, 1, ratios, data); - assertApproxEqRel(reserve0, 4.575771214546676444e18, 0.0001e18); + assertApproxEqRel(reserve0, 4.576236561359714812e18, 0.0001e18); assertApproxEqRel(reserve1, 0.21852354514449462e18, 0.0001e18); } diff --git a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol index 3a491d8c..a21d80fc 100644 --- a/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol +++ b/test/beanstalk/BeanstalkStable2.calcReserveAtRatioSwap.t.sol @@ -30,8 +30,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 100.005058322101089709e18); - assertEq(reserve1, 100.005058322101089709e18); + assertEq(reserve0, 99.999921040536083478e18); + assertEq(reserve1, 99.999921040536083478e18); } function test_calcReserveAtRatioSwap_equal_diff() public view { @@ -45,8 +45,8 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve0 = _f.calcReserveAtRatioSwap(reserves, 0, ratios, data); uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); - assertEq(reserve0, 73.517644476151580971e18); - assertEq(reserve1, 73.517644476151580971e18); + assertEq(reserve0, 73.513867858788351572e18); + assertEq(reserve1, 73.513867858788351572e18); } function test_calcReserveAtRatioSwap_diff_equal() public view { @@ -61,7 +61,7 @@ contract BeanstalkStable2SwapTest is TestHelper { uint256 reserve1 = _f.calcReserveAtRatioSwap(reserves, 1, ratios, data); assertEq(reserve0, 180.644064978044534737e18); // 180.644064978044534737e18, 100e18 - assertEq(reserve1, 39.475055811844664131e18); // 100e18, 39.475055811844664131e18 + assertEq(reserve1, 39.474244037189430513e18); // 100e18, 39.475055811844664131e18 } function test_calcReserveAtRatioSwap_diff_diff() public view { From c5507c51eeab152b8668d148aa98e70efe568564 Mon Sep 17 00:00:00 2001 From: Brean0 Date: Fri, 6 Sep 2024 11:42:17 +0200 Subject: [PATCH 69/69] update package version to v1.2.0. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2c73df3c..82da82b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@beanstalk/wells", - "version": "1.2.0-prerelease3", + "version": "1.2.0", "description": "A [{Well}](/src/Well.sol) is a constant function AMM that allows the provisioning of liquidity into a single pooled on-chain liquidity position.", "main": "index.js", "directories": {