Vanguard

First Flight #56
Beginner FriendlyDeFiFoundry
0 EXP
Submission Details
Impact: high
Likelihood: high

Inaccurate user limit calculation if pool liquidity change

Author Revealed upon completion

Inaccurate user limit calculation if pool liquidity change

Description

During _beforeSwap() calculation inside TokenLaunchUnit.sol contract, the limit is calculated based on initial liquidity. The logic will be broken if there is additional liquidity is added after that, thus user limit should change also.

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
if (launchStartBlock == 0) revert PoolNotInitialized();
if (initialLiquidity == 0) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity); // <@ only accounted for initial liquidity
}
// other codes

Risk

  • User will get an inaccurate limit amount if the liquidity change after that

Likelihood:

  • It will always occurs whenever protocol is using current code

Impact:

  • User will get an inaccurate limit amount if the liquidity change after that

Proof of Concept

On the existing test suit TokenLaunchHook.t.sol add this function
Run test suite with forge test --match-test test_changeLiquidity -vvv

function test_changeLiquidity() public {
assertEq(antiBotHook.getCurrentPhase(), 1, "Should start in phase 1");
vm.deal(user1, 10 ether);
uint128 initialliquidity = StateLibrary.getLiquidity(manager, key.toId());
console.log("Initial liquidity: ", initialliquidity);
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(1 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
vm.prank(user1);
swapRouter.swap{value: 1 ether}(key, params, testSettings, ZERO_BYTES);
console.log("Swap 1 ether");
uint256 ethToAdd = 5 ether;
uint160 sqrtPriceAtTickUpper = TickMath.getSqrtPriceAtTick(60);
uint128 liquidityDelta = LiquidityAmounts.getLiquidityForAmount1(
TickMath.MIN_SQRT_PRICE, sqrtPriceAtTickUpper, ethToAdd);
modifyLiquidityRouter.modifyLiquidity{value: ethToAdd}(
key,
ModifyLiquidityParams({
tickLower: -60,
tickUpper: 60,
liquidityDelta: int256(uint256(liquidityDelta)),
salt: bytes32(0)
}),
ZERO_BYTES
);
console.log("Add liquidity: ", liquidityDelta);
uint128 liquidity = StateLibrary.getLiquidity(manager, key.toId());
uint256 limitphase1 = liquidity * 100 /10000;
console.log("Current liquidity: ", liquidity);
console.log("Phase 1 limit is : ", limitphase1);
console.log("Phase 1 limit should be (liquidity * 100 / 10_000)");
vm.prank(user1);
swapRouter.swap{value: 1 ether}(key, params, testSettings, ZERO_BYTES);
console.log("Swap 1 ether");
vm.prank(user1);
uint256 remainingLimit1 = antiBotHook.getUserRemainingLimit(address(swapRouter));
console.log("Remaining limit for phase 1:", remainingLimit1);
assertEq(remainingLimit1, (limitphase1 - 2e18));
}

Log outputs:

Logs:
Initial liquidity: 3338502497096994491347
Swap 1 ether
Add liquidity: 4985023225220446095
Current liquidity: 3343487520322214937442
Phase 1 limit is : 33434875203222149374
Phase 1 limit should be (liquidity * 100 / 10_000)
Swap 1 ether
Remaining limit for phase 1: 31385024970969944913

Recommended Mitigation

Change the limit calculation not based on initial liquidity but based on current liquidity

function _beforeSwap(address sender, PoolKey calldata key, SwapParams calldata params, bytes calldata)
internal
override
returns (bytes4, BeforeSwapDelta, uint24)
{
if (launchStartBlock == 0) revert PoolNotInitialized();
- if (initialLiquidity == 0) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
// state variable can be renamed to reflect the change
initialLiquidity = uint256(liquidity);
- }
// other codes

Support

FAQs

Can't find an answer? Chat with us on Discord, Twitter or Linkedin.

Give us feedback!