Vanguard

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

Fee Override Flag Misuse Causes Zero LP Fees

Author Revealed upon completion

Description

  • In Uniswap v4, a hook that does not intend to modify swap fees must return 0 as the fee override value, allowing the pool to apply its configured default LP fee.

  • The root cause of this issue is the incorrect use of LPFeeLibrary.OVERRIDE_FEE_FLAG. The hook applies the override flag even when no fee value is provided. When the override flag is set without any fee bits, Uniswap v4 interprets this as an explicit override to an LP fee of 0, rather than preserving the pool’s default fee.

Root cause #1:

// Root cause #1:
// Phase 3 returns ONLY the override flag (no fee encoded in the lower bits).
if (currentPhase == 3) {
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
LPFeeLibrary.OVERRIDE_FEE_FLAG // @> Override enabled, but fee bits are zero => effective LP fee = 0
);
}

Root cause #2

// Root cause #2:
// feeOverride defaults to 0 and is only populated when applyPenalty == true.
// @> The override flag is always OR-ed in, so when applyPenalty == false the hook still overrides the LP fee to 0.
uint24 feeOverride = 0;
if (applyPenalty) {
feeOverride = uint24(phasePenaltyBps * 100);
}
return (
BaseHook.beforeSwap.selector,
BeforeSwapDeltaLibrary.ZERO_DELTA,
feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG // @> Override always on; when feeOverride == 0 => effective LP fee = 0
);

Risk

Likelihood:

  • In Phase 3, it becomes systematic: the hook returns LPFeeLibrary.OVERRIDE_FEE_FLAG with no fee bits, so every swap applies an LP fee of 0. Occurs on all swaps where the penalty logic is not triggered, which represents the common execution path.

  • No special conditions, privileges, or sophistication are required—anyone swapping (or any router) can “exploit” it simply by routing trades through the pool.

Impact:

  • Direct and sustained financial harm: LPs receive zero LP fees on affected swaps, causing continuous revenue loss.

  • Amplification risk: routers/aggregators are economically incentivized to route volume through a zero-fee pool, increasing affected volume and compounding LP losses.

Proof of Concept

The Proof of Concept demonstrates that the hook returns only LPFeeLibrary.OVERRIDE_FEE_FLAG when no penalty is applied. In Uniswap v4, the presence of the override flag instructs the PoolManager to ignore the pool’s default LP fee and instead use the fee encoded in the lower bits of the return value. Since no fee bits are set, the resulting LP fee is interpreted as zero.

//Proof of Concept
// Non-penalty path
uint24 feeOverride = 0;
uint24 returned = feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG;
// Override flag is set with zero fee bits
// PoolManager applies LP fee = 0
assert(returned == LPFeeLibrary.OVERRIDE_FEE_FLAG);
// Phase 3 path
uint24 returnedPhase3 = LPFeeLibrary.OVERRIDE_FEE_FLAG;
// All swaps execute with LP fee = 0

Fast check value uint24 = LPFeeLibrary.OVERRIDE_FEE_FLAG

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {LPFeeLibrary} from "@uniswap/v4-core/src/libraries/LPFeeLibrary.sol";
contract LPFeePoC {
// Returns the flag only (no fee encoded in the lower bits)
function rawFlag() external pure returns (uint24) {
return LPFeeLibrary.OVERRIDE_FEE_FLAG;
}
// Returns override + fee (e.g., fee = 3000)
function encodeOverride(uint24 fee) external pure returns (uint24) {
// In v4, the fee is encoded as a uint24 value (e.g., 3000 = 0.30%),
// and the override is signaled by OR-ing with OVERRIDE_FEE_FLAG.
return LPFeeLibrary.OVERRIDE_FEE_FLAG | fee;
}
// Removes the flag and returns only the embedded fee
function removeOverrideFlag(uint24 x) external pure returns (uint24) {
return x & LPFeeLibrary.REMOVE_OVERRIDE_MASK;
}
// Demonstrates the case you asked about:
// "If you return only OVERRIDE_FEE_FLAG, what embedded fee remains?"
function embeddedFeeIfOnlyFlag() external pure returns (uint24) {
uint24 x = LPFeeLibrary.OVERRIDE_FEE_FLAG;
return x & LPFeeLibrary.REMOVE_OVERRIDE_MASK; // expected: 0
}
}

Recommended Mitigation

The mitigation ensures that the override flag is applied exclusivelywhen a non-zero penalty fee must be enforced. When no penalty applies, the hook returns 0, allowing the PoolManager to preserve the pool’s default LP fee. This prevents unintended fee overrides and restores the intended fee semantics across all phases.

- if (currentPhase == 3) {
- return (
- BaseHook.beforeSwap.selector,
- BeforeSwapDeltaLibrary.ZERO_DELTA,
- LPFeeLibrary.OVERRIDE_FEE_FLAG
- );
- }
-
- uint24 feeOverride = 0;
- if (applyPenalty) {
- feeOverride = uint24((phasePenaltyBps * 100));
- }
-
- return (
- BaseHook.beforeSwap.selector,
- BeforeSwapDeltaLibrary.ZERO_DELTA,
- feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG
- );
+ if (applyPenalty) {
+ uint24 feeOverride = uint24((phasePenaltyBps * 100));
+ return (
+ BaseHook.beforeSwap.selector,
+ BeforeSwapDeltaLibrary.ZERO_DELTA,
+ feeOverride | LPFeeLibrary.OVERRIDE_FEE_FLAG
+ );
+ }
+
+ return (
+ BaseHook.beforeSwap.selector,
+ BeforeSwapDeltaLibrary.ZERO_DELTA,
+ 0 // @> 0 is returned for lpFeeOverride,
+ // meaning we're not changing the default LP fee.
+ );

References:

Support

FAQs

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

Give us feedback!