From 1297c3822f0605e643155c35948959c0a0d05e17 Mon Sep 17 00:00:00 2001 From: 0x0Louis Date: Wed, 20 Mar 2024 17:11:31 +0100 Subject: [PATCH] Add liquidity overflow check during mint and swap --- src/libraries/BinHelper.sol | 62 +++++++++++++++++++++------------- src/libraries/Constants.sol | 4 +++ test/libraries/BinHelper.t.sol | 58 +++++++++++++++++++++++-------- 3 files changed, 85 insertions(+), 39 deletions(-) diff --git a/src/libraries/BinHelper.sol b/src/libraries/BinHelper.sol index cc08d22..7892bf8 100644 --- a/src/libraries/BinHelper.sol +++ b/src/libraries/BinHelper.sol @@ -29,6 +29,7 @@ library BinHelper { error BinHelper__CompositionFactorFlawed(uint24 id); error BinHelper__LiquidityOverflow(); + error BinHelper__MaxLiquidityPerBinExceeded(); /** * @dev Returns the amount of tokens that will be received when burning the given amount of liquidity @@ -107,6 +108,10 @@ library BinHelper { amountsIn = uint128(x).encode(uint128(y)); } + if (getLiquidity(binReserves.add(amountsIn), price) > Constants.MAX_LIQUIDITY_PER_BIN) { + revert BinHelper__MaxLiquidityPerBinExceeded(); + } + return (shares, amountsIn); } @@ -228,41 +233,50 @@ library BinHelper { ) internal pure returns (bytes32 amountsInWithFees, bytes32 amountsOutOfBin, bytes32 totalFees) { uint256 price = activeId.getPriceFromId(binStep); - uint128 binReserveOut = binReserves.decode(!swapForY); + { + uint128 binReserveOut = binReserves.decode(!swapForY); - uint128 maxAmountIn = swapForY - ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() - : uint256(binReserveOut).mulShiftRoundUp(price, Constants.SCALE_OFFSET).safe128(); + uint128 maxAmountIn = swapForY + ? uint256(binReserveOut).shiftDivRoundUp(Constants.SCALE_OFFSET, price).safe128() + : uint256(binReserveOut).mulShiftRoundUp(price, Constants.SCALE_OFFSET).safe128(); - uint128 totalFee = parameters.getTotalFee(binStep); - uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); + uint128 totalFee = parameters.getTotalFee(binStep); + uint128 maxFee = maxAmountIn.getFeeAmount(totalFee); - maxAmountIn += maxFee; + maxAmountIn += maxFee; - uint128 amountIn128 = amountsInLeft.decode(swapForY); - uint128 fee128; - uint128 amountOut128; + uint128 amountIn128 = amountsInLeft.decode(swapForY); + uint128 fee128; + uint128 amountOut128; - if (amountIn128 >= maxAmountIn) { - fee128 = maxFee; + if (amountIn128 >= maxAmountIn) { + fee128 = maxFee; - amountIn128 = maxAmountIn; - amountOut128 = binReserveOut; - } else { - fee128 = amountIn128.getFeeAmountFrom(totalFee); + amountIn128 = maxAmountIn; + amountOut128 = binReserveOut; + } else { + fee128 = amountIn128.getFeeAmountFrom(totalFee); - uint256 amountIn = amountIn128 - fee128; + uint256 amountIn = amountIn128 - fee128; - amountOut128 = swapForY - ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() - : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); + amountOut128 = swapForY + ? uint256(amountIn).mulShiftRoundDown(price, Constants.SCALE_OFFSET).safe128() + : uint256(amountIn).shiftDivRoundDown(Constants.SCALE_OFFSET, price).safe128(); - if (amountOut128 > binReserveOut) amountOut128 = binReserveOut; + if (amountOut128 > binReserveOut) amountOut128 = binReserveOut; + } + + (amountsInWithFees, amountsOutOfBin, totalFees) = swapForY + ? (amountIn128.encodeFirst(), amountOut128.encodeSecond(), fee128.encodeFirst()) + : (amountIn128.encodeSecond(), amountOut128.encodeFirst(), fee128.encodeSecond()); } - (amountsInWithFees, amountsOutOfBin, totalFees) = swapForY - ? (amountIn128.encodeFirst(), amountOut128.encodeSecond(), fee128.encodeFirst()) - : (amountIn128.encodeSecond(), amountOut128.encodeFirst(), fee128.encodeSecond()); + if ( + getLiquidity(binReserves.add(amountsInWithFees).sub(amountsOutOfBin), price) + > Constants.MAX_LIQUIDITY_PER_BIN + ) { + revert BinHelper__MaxLiquidityPerBinExceeded(); + } } /** diff --git a/src/libraries/Constants.sol b/src/libraries/Constants.sol index eacb63f..6138a04 100644 --- a/src/libraries/Constants.sol +++ b/src/libraries/Constants.sol @@ -19,6 +19,10 @@ library Constants { 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; + /// @dev The expected return after a successful flash loan bytes32 internal constant CALLBACK_SUCCESS = keccak256("LBPair.onFlashLoan"); } diff --git a/test/libraries/BinHelper.t.sol b/test/libraries/BinHelper.t.sol index 12004b5..ff45aff 100644 --- a/test/libraries/BinHelper.t.sol +++ b/test/libraries/BinHelper.t.sol @@ -63,18 +63,16 @@ contract BinHelperTest is TestHelper { uint256 totalSupply ) external pure { vm.assume( - price > 0 + price > 0 && uint256(binReserveX) + amountInX <= type(uint128).max + && uint256(binReserveY) + amountInY <= type(uint128).max && ( - binReserveX == 0 - || ( - price <= type(uint256).max / binReserveX - && price * binReserveX <= type(uint256).max - binReserveY << 128 - ) - ) - && ( - amountInX == 0 - || (price <= type(uint256).max / amountInX && price * amountInX <= type(uint256).max - amountInY << 128) + (uint256(binReserveX) + amountInX) == 0 + || price <= type(uint256).max / (uint256(binReserveX) + amountInX) ) + && price * (uint256(binReserveX) + amountInX) + <= type(uint256).max - ((uint256(binReserveY) + amountInY) << 128) + && price * (uint256(binReserveX) + amountInX) + ((uint256(binReserveY) + amountInY) << 128) + <= Constants.MAX_LIQUIDITY_PER_BIN ); bytes32 binReserves = binReserveX.encode(binReserveY); @@ -110,6 +108,8 @@ contract BinHelperTest is TestHelper { && price <= type(uint256).max / (uint256(amountX1) + amountX2) && uint256(amountY1) + amountY2 <= type(uint128).max && price * (uint256(amountX1) + amountX2) <= type(uint256).max - ((uint256(amountY1) + amountY2) << 128) + && price * (uint256(amountX1) + amountX2) + ((uint256(amountY1) + amountY2) << 128) + <= Constants.MAX_LIQUIDITY_PER_BIN ); // exploiter front run the tx and mint the min amount of shares, so the total supply is 2^128 @@ -161,10 +161,12 @@ contract BinHelperTest is TestHelper { ) external pure { // make sure p*x+y doesn't overflow vm.assume( - price > 0 && amountXIn > 0 && amountYIn > 0 && price <= type(uint256).max / amountXIn - && price * amountXIn <= type(uint256).max - uint256(amountYIn) << 128 - && (reserveX == 0 || price <= type(uint256).max / reserveX) - && price * reserveX <= type(uint256).max - uint256(reserveY) << 128 + price > 0 && amountXIn > 0 && amountYIn > 0 && uint256(reserveX) + amountXIn <= type(uint128).max + && uint256(reserveY) + amountYIn <= type(uint128).max + && uint256(reserveX) + amountXIn <= type(uint256).max / price + && price * uint256(reserveX + amountXIn) <= type(uint256).max - (uint256(reserveY + amountYIn) << 128) + && price * uint256(reserveX + amountXIn) + (uint256(reserveY + amountYIn) << 128) + <= Constants.MAX_LIQUIDITY_PER_BIN ); bytes32 binReserves = reserveX.encode(reserveY); @@ -219,7 +221,7 @@ contract BinHelperTest is TestHelper { bool swapForY, int16 deltaId, uint128 amountIn - ) external pure { + ) external view { bytes32 parameters = bytes32(0).setStaticFeeParameters( DEFAULT_BASE_FACTOR, DEFAULT_FILTER_PERIOD, @@ -241,6 +243,19 @@ contract BinHelperTest is TestHelper { uint128 maxFee = FeeHelper.getFeeAmount(uint128(maxAmountIn), parameters.getTotalFee(DEFAULT_BIN_STEP)); vm.assume(maxAmountIn <= type(uint128).max - maxFee && amountIn < maxAmountIn + maxFee); + + vm.assume( + swapForY + ? uint256(binReserveX) + maxAmountIn + maxFee <= type(uint128).max + : uint256(binReserveY) + maxAmountIn + maxFee <= type(uint128).max + ); + + vm.assume( + binReserveX <= type(uint256).max / price + && price * binReserveX <= type(uint256).max - (uint256(binReserveY) << 128) + && price * binReserveX + (uint256(binReserveY) << 128) + <= Constants.MAX_LIQUIDITY_PER_BIN / 1e18 * (1e18 - parameters.getTotalFee(DEFAULT_BIN_STEP)) + ); } bytes32 reserves = binReserveX.encode(binReserveY); @@ -296,6 +311,19 @@ contract BinHelperTest is TestHelper { uint128 maxFee = FeeHelper.getFeeAmount(uint128(maxAmountIn), parameters.getTotalFee(DEFAULT_BIN_STEP)); vm.assume(maxAmountIn <= type(uint128).max - maxFee && amountIn >= maxAmountIn + maxFee); + + vm.assume( + swapForY + ? uint256(binReserveX) + maxAmountIn + maxFee <= type(uint128).max + : uint256(binReserveY) + maxAmountIn + maxFee <= type(uint128).max + ); + + vm.assume( + binReserveX <= type(uint256).max / price + && price * binReserveX <= type(uint256).max - (uint256(binReserveY) << 128) + && price * binReserveX + (uint256(binReserveY) << 128) + <= Constants.MAX_LIQUIDITY_PER_BIN / 1e18 * (1e18 - parameters.getTotalFee(DEFAULT_BIN_STEP)) + ); } bytes32 reserves = binReserveX.encode(binReserveY);