Skip to content

Commit

Permalink
Add liquidity overflow check during mint and swap
Browse files Browse the repository at this point in the history
  • Loading branch information
0x0Louis committed Apr 9, 2024
1 parent 191e2ac commit 1297c38
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 39 deletions.
62 changes: 38 additions & 24 deletions src/libraries/BinHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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();
}
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/libraries/Constants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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");
}
58 changes: 43 additions & 15 deletions test/libraries/BinHelper.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 1297c38

Please sign in to comment.