Vanguard

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

initialLiquidity Can Be Re-Set Via _beforeSwap If Initially Zero - Manipulable Launch Protection

Author Revealed upon completion

Description

Normal Behavior: The initialLiquidity value should be set once during pool initialization and remain immutable, as it determines the maximum swap amount throughout the token launch protection period.

Issue: If initialLiquidity is 0 at initialization (common when pool is created but not yet funded), the _beforeSwap function re-reads it from the pool on the first swap:

function _beforeSwap(/* params */) {
// Line 95-99: Re-initialization logic
if (initialLiquidity == 0) {
initialLiquidity = poolManager.getLiquidity(key.toId());
}
}

Root Cause

The initialLiquidity can be set at an attacker-controlled moment:

// _afterInitialize sets initialLiquidity
initialLiquidity = poolManager.getLiquidity(key.toId());
// If pool has no liquidity yet, this is 0
// Later in _beforeSwap, if still 0, it re-reads:
if (initialLiquidity == 0) {
initialLiquidity = poolManager.getLiquidity(key.toId());
// Attacker can manipulate liquidity before this point
}

Impact

  • Attacker-Controlled maxSwapAmount: Since maxSwapAmount = initialLiquidity * phaseLimitBps / 10000, an attacker can manipulate the base liquidity value

  • Sandwich Attack Vector: Attacker can add minimal liquidity → trigger first swap → remove liquidity, locking in a low maxSwapAmount that griefs all users

  • Permanent Effect: Once set, initialLiquidity is never updated, so manipulation is permanent for the pool's lifetime

Proof of Concept

// 1. Pool initializes with 0 liquidity (common pattern)
// initialLiquidity = 0 after _afterInitialize
// 2. Attacker adds tiny liquidity (e.g., 1 wei)
pool.addLiquidity(1 wei);
// 3. Attacker triggers first swap (even a dust swap)
// _beforeSwap runs: initialLiquidity = 1 wei
// maxSwapAmount = 1 wei * 1000 / 10000 = 0 (truncated!)
// 4. All future swaps are griefed with near-zero limit

Recommended Mitigation

// Option 1: Require minimum liquidity
function _beforeSwap(/* params */) {
if (initialLiquidity == 0) {
uint128 currentLiquidity = poolManager.getLiquidity(key.toId());
+ require(currentLiquidity >= MIN_INITIAL_LIQUIDITY, "Insufficient liquidity");
initialLiquidity = currentLiquidity;
}
}
// Option 2: Make initialLiquidity immutable after first set
// Add a flag to prevent re-reading

Support

FAQs

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

Give us feedback!