From 8c00656b2d3e7fda39f74d25f1f9a09263c7bc42 Mon Sep 17 00:00:00 2001 From: chefburger Date: Mon, 11 Nov 2024 15:37:05 +0800 Subject: [PATCH 1/4] fix: [hexen-r9] add overflow check for bin liquidity --- ...omCurveHookTest#test_Swap_CustomCurve.snap | 2 +- ...inHookTest#testDonateSucceedsWithHook.snap | 2 +- .../BinHookTest#testMintSucceedsWithHook.snap | 2 +- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- .../BinMintBurnFeeHookTest#test_Mint.snap | 2 +- .../BinPoolManagerBytecodeSize.snap | 2 +- .../BinPoolManagerTest#testGasDonate.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-1.snap | 2 +- ...nPoolManagerTest#testGasMintNneBins-2.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-1.snap | 2 +- ...inPoolManagerTest#testGasMintOneBin-2.snap | 2 +- ...olManagerTest#testGasSwapMultipleBins.snap | 2 +- ...nagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- ...nPoolManagerTest#testGasSwapSingleBin.snap | 2 +- ...oolManagerTest#testMintNativeCurrency.snap | 2 +- src/pool-bin/libraries/BinPool.sol | 26 ++++++++++++++++--- src/pool-bin/libraries/Constants.sol | 4 +++ test/pool-bin/libraries/BinPoolDonate.t.sol | 11 ++++++++ 18 files changed, 52 insertions(+), 19 deletions(-) diff --git a/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap b/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap index 6589b3a3..61b6efad 100644 --- a/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap +++ b/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap @@ -1 +1 @@ -142478 \ No newline at end of file +142475 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap index 6c9aac79..acb72c87 100644 --- a/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testDonateSucceedsWithHook.snap @@ -1 +1 @@ -188410 \ No newline at end of file +188265 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap index 607b6b5d..07ee066b 100644 --- a/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testMintSucceedsWithHook.snap @@ -1 +1 @@ -311254 \ No newline at end of file +311495 \ No newline at end of file diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 763303e6..211df2eb 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -189451 \ No newline at end of file +189959 \ No newline at end of file diff --git a/.forge-snapshots/BinMintBurnFeeHookTest#test_Mint.snap b/.forge-snapshots/BinMintBurnFeeHookTest#test_Mint.snap index 4128ba06..0e89e7af 100644 --- a/.forge-snapshots/BinMintBurnFeeHookTest#test_Mint.snap +++ b/.forge-snapshots/BinMintBurnFeeHookTest#test_Mint.snap @@ -1 +1 @@ -410336 \ No newline at end of file +410577 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerBytecodeSize.snap b/.forge-snapshots/BinPoolManagerBytecodeSize.snap index c0501746..07534292 100644 --- a/.forge-snapshots/BinPoolManagerBytecodeSize.snap +++ b/.forge-snapshots/BinPoolManagerBytecodeSize.snap @@ -1 +1 @@ -23287 \ No newline at end of file +23548 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap index 5deafddc..0d5a057d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasDonate.snap @@ -1 +1 @@ -118691 \ No newline at end of file +118546 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap index dc44a72f..2aa17409 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-1.snap @@ -1 +1 @@ -968475 \ No newline at end of file +970284 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap index 4a863ea0..eeef0a41 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintNneBins-2.snap @@ -1 +1 @@ -327787 \ No newline at end of file +329605 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap index f71e10bd..d25d3e11 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-1.snap @@ -1 +1 @@ -337511 \ No newline at end of file +337752 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap index 140b1ddb..ddde8d98 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasMintOneBin-2.snap @@ -1 +1 @@ -140062 \ No newline at end of file +140304 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 23969d6b..d1be9631 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -173098 \ No newline at end of file +177927 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 2925f2ee..7802c629 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -179126 \ No newline at end of file +184209 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 423d7fde..ba7cd49d 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -133129 \ No newline at end of file +133637 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap index f9388fc0..f7fe2469 100644 --- a/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap +++ b/.forge-snapshots/BinPoolManagerTest#testMintNativeCurrency.snap @@ -1 +1 @@ -304550 \ No newline at end of file +304791 \ No newline at end of file diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 8fb77084..410e2397 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -48,6 +48,7 @@ library BinPool { error BinPool__NoLiquidityToReceiveFees(); /// @dev if swap exactIn, x for y, unspecifiedToken = token y. if swap x for exact out y, unspecified token is x error BinPool__InsufficientAmountUnSpecified(); + error BinPool__MaxLiquidityPerBinExceeded(); /// @dev The state of a pool struct State { @@ -168,7 +169,15 @@ library BinPool { amountsInWithFees = amountsInWithFees.sub(pFee); } - self.reserveOfBin[swapState.activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); + bytes32 newReserve = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); + if ( + newReserve.getLiquidity(swapState.activeId.getPriceFromId(params.binStep)) + > Constants.MAX_LIQUIDITY_PER_BIN + ) { + revert BinPool__MaxLiquidityPerBinExceeded(); + } + + self.reserveOfBin[swapState.activeId] = newReserve; } } @@ -347,9 +356,13 @@ library BinPool { /// @dev overflow check on total reserves and the resulting liquidity uint256 price = activeId.getPriceFromId(binStep); - binReserves.add(amountIn).getLiquidity(price); + bytes32 newReserves = binReserves.add(amountIn); + uint256 liquidity = newReserves.getLiquidity(price); + if (liquidity > Constants.MAX_LIQUIDITY_PER_BIN) { + revert BinPool__MaxLiquidityPerBinExceeded(); + } - self.reserveOfBin[activeId] = binReserves.add(amountIn); + self.reserveOfBin[activeId] = newReserves; result = toBalanceDelta(-(amount0.safeInt128()), -(amount1.safeInt128())); } @@ -457,7 +470,12 @@ library BinPool { if (shares == 0 || amountsInToBin == 0) revert BinPool__ZeroShares(id); if (supply == 0) _addBinIdToTree(self, id); - self.reserveOfBin[id] = binReserves.add(amountsInToBin); + bytes32 newReserves = binReserves.add(amountsInToBin); + if (newReserves.getLiquidity(price) > Constants.MAX_LIQUIDITY_PER_BIN) { + revert BinPool__MaxLiquidityPerBinExceeded(); + } + + self.reserveOfBin[id] = newReserves; } /// @notice Subtract share from user's position and update total share supply of bin diff --git a/src/pool-bin/libraries/Constants.sol b/src/pool-bin/libraries/Constants.sol index 1f9d26bb..a2ddfe8e 100644 --- a/src/pool-bin/libraries/Constants.sol +++ b/src/pool-bin/libraries/Constants.sol @@ -11,4 +11,8 @@ library Constants { uint256 internal constant SQUARED_PRECISION = PRECISION * PRECISION; uint256 internal constant BASIS_POINT_MAX = 10_000; + + // (2^256 - 1) / (2 * log(2**128) / log(1.0001)) + uint256 internal constant MAX_LIQUIDITY_PER_BIN = + 65251743116719673010965625540244653191619923014385985379600384103134737; } diff --git a/test/pool-bin/libraries/BinPoolDonate.t.sol b/test/pool-bin/libraries/BinPoolDonate.t.sol index 9efc1ddf..5c8ebca8 100644 --- a/test/pool-bin/libraries/BinPoolDonate.t.sol +++ b/test/pool-bin/libraries/BinPoolDonate.t.sol @@ -17,11 +17,14 @@ import {PackedUint128Math} from "../../../src/pool-bin/libraries/math/PackedUint import {SafeCast} from "../../../src/pool-bin/libraries/math/SafeCast.sol"; import {BinPoolParametersHelper} from "../../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; import {BinTestHelper} from "../helpers/BinTestHelper.sol"; +import {Constants} from "../../../src/pool-bin/libraries/Constants.sol"; +import {PriceHelper} from "../../../src/pool-bin/libraries/PriceHelper.sol"; contract BinPoolDonateTest is BinTestHelper { using PackedUint128Math for bytes32; using BinPoolParametersHelper for bytes32; using SafeCast for uint256; + using BinHelper for bytes32; MockVault public vault; BinPoolManager public poolManager; @@ -119,6 +122,14 @@ contract BinPoolDonateTest is BinTestHelper { addLiquidityToBin(key, poolManager, bob, activeId, 1e18, 1e18, 1e18, 1e18, ""); poolManager.getPosition(poolId, bob, activeId, 0).share; + bytes32 newReserves = PackedUint128Math.encode(1e18 + amt0, 1e18 + amt1); + uint256 price = PriceHelper.getPriceFromId(activeId, poolParam.getBinStep()); + if (newReserves.getLiquidity(price) > Constants.MAX_LIQUIDITY_PER_BIN) { + vm.expectRevert(BinPool.BinPool__MaxLiquidityPerBinExceeded.selector); + poolManager.donate(key, amt0, amt1, ""); + return; + } + poolManager.donate(key, amt0, amt1, ""); // Verify reserve after donate From 9c8fa74a7e2dd712bd3e3dcd981bbda1d7aa29f0 Mon Sep 17 00:00:00 2001 From: chefburger Date: Mon, 11 Nov 2024 16:24:51 +0800 Subject: [PATCH 2/4] fix: forge coverage stack too deep --- .../BinHookTest#testSwapSucceedsWithHook.snap | 2 +- .forge-snapshots/BinPoolManagerBytecodeSize.snap | 2 +- .../BinPoolManagerTest#testGasSwapMultipleBins.snap | 2 +- ...inPoolManagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- .../BinPoolManagerTest#testGasSwapSingleBin.snap | 2 +- src/pool-bin/libraries/BinPool.sol | 11 +++++------ 6 files changed, 10 insertions(+), 11 deletions(-) diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 211df2eb..78731c4c 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -189959 \ No newline at end of file +190198 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerBytecodeSize.snap b/.forge-snapshots/BinPoolManagerBytecodeSize.snap index 07534292..270abcbb 100644 --- a/.forge-snapshots/BinPoolManagerBytecodeSize.snap +++ b/.forge-snapshots/BinPoolManagerBytecodeSize.snap @@ -1 +1 @@ -23548 \ No newline at end of file +23602 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index d1be9631..192e1ca9 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -177927 \ No newline at end of file +178883 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index 7802c629..b2772897 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -184209 \ No newline at end of file +185165 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index ba7cd49d..95111a71 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -133637 \ No newline at end of file +133876 \ No newline at end of file diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index 410e2397..b16ababa 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -169,15 +169,15 @@ library BinPool { amountsInWithFees = amountsInWithFees.sub(pFee); } - bytes32 newReserve = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); if ( - newReserve.getLiquidity(swapState.activeId.getPriceFromId(params.binStep)) - > Constants.MAX_LIQUIDITY_PER_BIN + (binReserves.add(amountsInWithFees).sub(amountsOutOfBin)).getLiquidity( + swapState.activeId.getPriceFromId(params.binStep) + ) > Constants.MAX_LIQUIDITY_PER_BIN ) { revert BinPool__MaxLiquidityPerBinExceeded(); } - self.reserveOfBin[swapState.activeId] = newReserve; + self.reserveOfBin[swapState.activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); } } @@ -357,8 +357,7 @@ library BinPool { /// @dev overflow check on total reserves and the resulting liquidity uint256 price = activeId.getPriceFromId(binStep); bytes32 newReserves = binReserves.add(amountIn); - uint256 liquidity = newReserves.getLiquidity(price); - if (liquidity > Constants.MAX_LIQUIDITY_PER_BIN) { + if (newReserves.getLiquidity(price) > Constants.MAX_LIQUIDITY_PER_BIN) { revert BinPool__MaxLiquidityPerBinExceeded(); } From 6b718c771897a6d73079fe007d1605a57dbf473e Mon Sep 17 00:00:00 2001 From: chefburger Date: Tue, 12 Nov 2024 11:22:25 +0800 Subject: [PATCH 3/4] optimization: minor adjust per comment --- .forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap | 2 +- .forge-snapshots/BinPoolManagerBytecodeSize.snap | 2 +- .../BinPoolManagerTest#testGasSwapMultipleBins.snap | 2 +- .../BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap | 2 +- .../BinPoolManagerTest#testGasSwapSingleBin.snap | 2 +- src/pool-bin/libraries/BinPool.sol | 6 +++--- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap index 78731c4c..1c4f322d 100644 --- a/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap +++ b/.forge-snapshots/BinHookTest#testSwapSucceedsWithHook.snap @@ -1 +1 @@ -190198 \ No newline at end of file +190107 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerBytecodeSize.snap b/.forge-snapshots/BinPoolManagerBytecodeSize.snap index 270abcbb..e8a0dfb4 100644 --- a/.forge-snapshots/BinPoolManagerBytecodeSize.snap +++ b/.forge-snapshots/BinPoolManagerBytecodeSize.snap @@ -1 +1 @@ -23602 \ No newline at end of file +23558 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap index 192e1ca9..14c6b9fb 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapMultipleBins.snap @@ -1 +1 @@ -178883 \ No newline at end of file +178450 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap index b2772897..a581a4bc 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapOverBigBinIdGate.snap @@ -1 +1 @@ -185165 \ No newline at end of file +184732 \ No newline at end of file diff --git a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap index 95111a71..33b2736b 100644 --- a/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap +++ b/.forge-snapshots/BinPoolManagerTest#testGasSwapSingleBin.snap @@ -1 +1 @@ -133876 \ No newline at end of file +133785 \ No newline at end of file diff --git a/src/pool-bin/libraries/BinPool.sol b/src/pool-bin/libraries/BinPool.sol index b16ababa..4cdc6455 100644 --- a/src/pool-bin/libraries/BinPool.sol +++ b/src/pool-bin/libraries/BinPool.sol @@ -169,15 +169,15 @@ library BinPool { amountsInWithFees = amountsInWithFees.sub(pFee); } + self.reserveOfBin[swapState.activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); + if ( - (binReserves.add(amountsInWithFees).sub(amountsOutOfBin)).getLiquidity( + self.reserveOfBin[swapState.activeId].getLiquidity( swapState.activeId.getPriceFromId(params.binStep) ) > Constants.MAX_LIQUIDITY_PER_BIN ) { revert BinPool__MaxLiquidityPerBinExceeded(); } - - self.reserveOfBin[swapState.activeId] = binReserves.add(amountsInWithFees).sub(amountsOutOfBin); } } From 1ca20e9e740562d911f26f6a03cf89564a927b63 Mon Sep 17 00:00:00 2001 From: chefburger Date: Tue, 12 Nov 2024 16:28:38 +0800 Subject: [PATCH 4/4] test: added test cases for swap and mint --- ...omCurveHookTest#test_Swap_CustomCurve.snap | 2 +- .../pool-bin/libraries/BinPoolLiquidity.t.sol | 101 ++++++++++++++++++ test/pool-bin/libraries/BinPoolSwap.t.sol | 23 ++++ 3 files changed, 125 insertions(+), 1 deletion(-) diff --git a/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap b/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap index 61b6efad..d09a157c 100644 --- a/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap +++ b/.forge-snapshots/BinCustomCurveHookTest#test_Swap_CustomCurve.snap @@ -1 +1 @@ -142475 \ No newline at end of file +142463 \ No newline at end of file diff --git a/test/pool-bin/libraries/BinPoolLiquidity.t.sol b/test/pool-bin/libraries/BinPoolLiquidity.t.sol index b71537f7..72f7d003 100644 --- a/test/pool-bin/libraries/BinPoolLiquidity.t.sol +++ b/test/pool-bin/libraries/BinPoolLiquidity.t.sol @@ -19,11 +19,14 @@ import {LiquidityConfigurations} from "../../../src/pool-bin/libraries/math/Liqu import {IBinPoolManager} from "../../../src/pool-bin/interfaces/IBinPoolManager.sol"; import {BinPoolParametersHelper} from "../../../src/pool-bin/libraries/BinPoolParametersHelper.sol"; import {BinTestHelper} from "../helpers/BinTestHelper.sol"; +import {PriceHelper} from "../../../src/pool-bin/libraries/PriceHelper.sol"; +import {BinHelper} from "../../../src/pool-bin/libraries/BinHelper.sol"; contract BinPoolLiquidityTest is BinTestHelper { using PackedUint128Math for bytes32; using BinPoolParametersHelper for bytes32; using SafeCast for uint256; + using BinHelper for bytes32; MockVault public vault; BinPoolManager public poolManager; @@ -53,6 +56,104 @@ contract BinPoolLiquidityTest is BinTestHelper { poolId = key.toId(); } + function test_MintFuzz(uint128 amountX, uint128 amountY) external { + amountX = uint128(bound(amountX, 1 ether, uint128(type(int128).max))); + amountY = uint128(bound(amountY, 1 ether, uint128(type(int128).max))); + + uint8 nbBinX = 6; + uint8 nbBinY = 6; + + poolManager.initialize(key, activeId); + + BinPool.MintArrays memory array; + vault.updateCurrentPoolKey(key); + + // check if the new liquidity will exceed the max liquidity per bin + bool shouldRevert = false; + bytes32 newReserves = PackedUint128Math.encode(amountX / nbBinX, amountY / nbBinY); + { + uint256 total = getTotalBins(nbBinX, nbBinY); + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + uint256 price = PriceHelper.getPriceFromId(id, poolParam.getBinStep()); + if (newReserves.getLiquidity(price) > Constants.MAX_LIQUIDITY_PER_BIN) { + shouldRevert = true; + break; + } + } + } + + if (shouldRevert) { + vm.expectRevert(BinPool.BinPool__MaxLiquidityPerBinExceeded.selector); + (, array) = addLiquidity(key, poolManager, bob, activeId, amountX, amountY, nbBinX, nbBinY); + return; + } + + (, array) = addLiquidity(key, poolManager, bob, activeId, amountX, amountY, nbBinX, nbBinY); + + { + // verify X and Y amount + uint256 amtXBalanceDelta = uint256(-int256(vault.balanceDeltaOfPool(poolId).amount0())); + uint256 amountXLeft = amountX - ((amountX * (Constants.PRECISION / nbBinX)) / 1e18) * nbBinX; + assertEq(amountX, amtXBalanceDelta + amountXLeft, "test_MintFuzz::1"); + + uint256 amtYBalanceDelta = uint256(-int256(vault.balanceDeltaOfPool(poolId).amount1())); + uint256 amountYLeft = amountY - ((amountY * (Constants.PRECISION / nbBinY)) / 1e18) * nbBinY; + assertEq(amountY, amtYBalanceDelta + amountYLeft, "test_MintFUzz::2"); + } + { + // verify each binId has the right reserve + uint256 total = getTotalBins(nbBinX, nbBinY); + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + (uint128 binReserveX, uint128 binReserveY,,) = poolManager.getBin(poolId, id); + + if (id < activeId) { + assertEq(binReserveX, 0, "test_MintFuzz::3"); + assertEq(binReserveY, (amountY * (Constants.PRECISION / nbBinY)) / 1e18, "test_MintFuzz::4"); + } else if (id == activeId) { + assertApproxEqRel( + binReserveX, (amountX * (Constants.PRECISION / nbBinX)) / 1e18, 1e15, "test_MintFuzz::5" + ); + assertApproxEqRel( + binReserveY, (amountY * (Constants.PRECISION / nbBinY)) / 1e18, 1e15, "test_MintFuzz::6" + ); + } else { + assertEq(binReserveX, (amountX * (Constants.PRECISION / nbBinX)) / 1e18, "test_MintFuzz::7"); + assertEq(binReserveY, 0, "test_MintFuzz::8"); + } + + assertGt(poolManager.getPosition(poolId, bob, id, 0).share, 0, "test_MintFuzz::9"); + } + } + { + uint256 total = getTotalBins(nbBinX, nbBinY); + for (uint256 i; i < total; ++i) { + uint24 id = getId(activeId, i, nbBinY); + + // verify id + assertEq(id, array.ids[i]); + + // verify amount + (uint128 x, uint128 y) = array.amounts[i].decode(); + if (id < activeId) { + assertEq(x, 0); + assertApproxEqRel(y, amountY / 6, 1e15); // approx amount within 0.1%, + } else if (id == activeId) { + assertApproxEqRel(y, amountY / 6, 1e15); // approx amount within 0.1% + assertApproxEqRel(x, amountX / 6, 1e15); // approx amount within 0.1% + } else { + assertApproxEqRel(x, amountX / 6, 1e15); // approx amount within 0.1% + assertEq(y, 0); + } + + // verify liquidity minted + assertEq(poolManager.getPosition(poolId, bob, id, 0).share, array.liquidityMinted[i]); + } + } + } + function test_SimpleMintX() external { poolManager.initialize(key, activeId); diff --git a/test/pool-bin/libraries/BinPoolSwap.t.sol b/test/pool-bin/libraries/BinPoolSwap.t.sol index 8849d246..4db8ba88 100644 --- a/test/pool-bin/libraries/BinPoolSwap.t.sol +++ b/test/pool-bin/libraries/BinPoolSwap.t.sol @@ -18,6 +18,7 @@ import {BinPoolParametersHelper} from "../../../src/pool-bin/libraries/BinPoolPa import {BinTestHelper} from "../helpers/BinTestHelper.sol"; import {IProtocolFeeController} from "../../../src/interfaces/IProtocolFeeController.sol"; import {MockProtocolFeeController} from "../../../src/test/fee/MockProtocolFeeController.sol"; +import {Constants} from "../../../src/pool-bin/libraries/Constants.sol"; contract BinPoolSwapTest is BinTestHelper { using PackedUint128Math for bytes32; @@ -234,6 +235,28 @@ contract BinPoolSwapTest is BinTestHelper { poolManager.swap(key, false, -int128(amountIn), "0x"); } + function test_revert_swapMaxLiquidityPerBinfuzz(int128 amountSpecified) external { + vm.assume(amountSpecified != 0); + + // Add liquidity to the point where it is close to the max liquidity per bin + poolManager.initialize(key, activeId); + addLiquidity( + key, + poolManager, + bob, + activeId, + // when price is 1:1, then Constants.MAX_LIQUIDITY_PER_BIN >> 128 / 2 is the threshold + (Constants.MAX_LIQUIDITY_PER_BIN >> 128) / 2, + (Constants.MAX_LIQUIDITY_PER_BIN >> 128) / 2, + 1, + 1 + ); + + // arbitrary amount of token will trigger the revert + vm.expectRevert(BinPool.BinPool__MaxLiquidityPerBinExceeded.selector); + poolManager.swap(key, false, amountSpecified, "0x"); + } + function test_revert_SwapOutOfLiquidity() external { // Add liquidity of 1e18 on each side poolManager.initialize(key, activeId);