Vanguard

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

initialLiquidity Uses Pool Total Liquidity Instead of Token-Specific Amount

Author Revealed upon completion

Description

  • The contract uses initialLiquidity to calculate swap limits: maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000

  • initialLiquidity is set by calling StateLibrary.getLiquidity(), which returns the pool's liquidity value (L) in Uniswap's concentrated liquidity formula, not actual token amounts.

function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
// ...
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity); // @> L value, not token amount
// ...
}
function _beforeSwap(...) internal override returns (...) {
// ...
if (initialLiquidity == 0) {
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
}
// ...
uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000; // @> Uses L for limits
}

Risk

Likelihood:

  • Occurs on every deployment and limit calculation

  • The liquidity value may not correlate well with desired token limits

Impact:

  • Swap limits may not align with actual token amounts in the pool

  • Depending on the price tick, the same "L" value represents different token amounts

  • Limits could be unexpectedly high or low relative to actual token supply

Proof of Concept

The limit is calculated from pool's L value, which doesn't directly map to token amounts.

function test_LiquidityVsTokenAmount() public {
// The initialLiquidity is set from pool's L value
uint256 liquidityL = antiBotHook.initialLiquidity();
// At the current price tick, L translates to different token amounts
// For a 1% limit in Phase 1:
uint256 maxSwap = (liquidityL * 100) / 10000; // 1% of L
// But actual token0 and token1 amounts in pool depend on price
// At SQRT_PRICE_1_1, the relationship is roughly:
// amount0 ≈ L / sqrt(price)
// amount1 ≈ L * sqrt(price)
// The limit is on "L" not on actual tokens, which may be confusing
console.log("Liquidity L:", liquidityL);
console.log("Max swap (1% of L):", maxSwap);
}

Recommended Mitigation

Calculate actual token amount from L and price, or accept initial supply as constructor param.

// Option 1: Use actual token balance for one side
function _afterInitialize(address, PoolKey calldata key, uint160 sqrtPriceX96, int24) internal override returns (bytes4) {
// ...
- uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
- initialLiquidity = uint256(liquidity);
+ // Calculate actual token amount based on liquidity and price
+ uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
+ // For currency1 (the launched token), calculate actual amount
+ uint256 tokenAmount = FullMath.mulDiv(liquidity, sqrtPriceX96, 2**96);
+ initialLiquidity = tokenAmount;
// ...
}
// Option 2: Accept a constructor parameter for initial supply
+ uint256 public immutable initialTokenSupply;
+
+ constructor(..., uint256 _initialTokenSupply) {
+ initialTokenSupply = _initialTokenSupply;
+ // Use this instead of querying pool liquidity
+ }

Support

FAQs

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

Give us feedback!