Vanguard

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

Permanent Loss of LP Fees in Phase 3 Due to Incorrect Override Flag

Author Revealed upon completion

ROOT + IMPACT

Description

The project documentation states that in Phase 3 (Post-launch), "Standard Uniswap fees apply." This implies that the hook should stop intervening in fee logic and allow the PoolManager to charge the static fee defined in the PoolKey (e.g., 0.3% or 1%).

However, the _beforeSwap function explicitly returns the LPFeeLibrary.OVERRIDE_FEE_FLAG even in Phase 3, without specifying a fee value. In Uniswap V4, returning this flag with a zero value (implicit) forces the swap fee to 0%.

Instead of reverting to the standard pool fee, the hook accidentally enforces a "Zero Fee" policy forever once Phase 3 begins. This results in Liquidity Providers (LPs) receiving zero revenue for their capital, breaking the economic incentive of the pool.

Solidity

// TokenLaunchHook.sol
if (currentPhase == 3) {
// @audit Returns the Override Flag but no fee value.
// This sets the fee to 0, overriding the pool's static fee (e.g. 0.3%).
return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, LPFeeLibrary.OVERRIDE_FEE_FLAG);
}

Risk

Likelihood: High (Certainty)

  • This logic executes for every single swap once the blocks corresponding to Phase 1 and 2 have passed.

Impact: High

  • Loss of Yield: LPs effectively provide free liquidity. They will earn 0% trading fees despite taking on impermanent loss risk.

  • Protocol Deviation: The behavior directly contradicts the documentation ("Standard Uniswap fees apply").

Proof of Concept

  1. Setup: A pool is created with a standard 0.30% fee (3000 pips).

  2. Phase Transition: The blockchain progresses past phase1Duration + phase2Duration. currentPhase becomes 3.

  3. Swap Execution: A user swaps 10 ETH.

  4. Hook Logic:

    • _beforeSwap detects currentPhase == 3.

    • It returns LPFeeLibrary.OVERRIDE_FEE_FLAG.

  5. PoolManager Logic:

    • The PoolManager sees the OVERRIDE_FEE_FLAG.

    • It looks at the lower 24 bits for the fee value. Since no value was OR'd (|) with the flag, the value is 0.

    • The PoolManager charges the user 0%.

  6. Result: The user gets a free trade. The LPs get 0 ETH in fees.

Recommended Mitigation

In Phase 3, the hook should return 0 for the fee/flag parameter. This tells the PoolManager "I have no opinion on the fee," causing it to fall back to the static fee defined in the PoolKey.

Diff

if (currentPhase == 3) {
- return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, LPFeeLibrary.OVERRIDE_FEE_FLAG);
+ // Return 0 (no flags) to allow the PoolManager to apply the standard pool fee
+ return (BaseHook.beforeSwap.selector, BeforeSwapDeltaLibrary.ZERO_DELTA, 0);
}

Support

FAQs

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

Give us feedback!