Vanguard

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

Pool LP fee permanently stuck at 0

Author Revealed upon completion

Pool LP fee permanently stuck at 0

Description

Dynamic fee pools in Uniswap V4 have an initial LP fee of 0. As stated in the Uniswap documentation:

"if a dynamic fee pool wants a non-0 initial fee, it should call updateDynamicLPFee in the afterInitialize hook"

The TokenLaunchHook contract does not call updateDynamicLPFee in TokenLaunchHook::_afterInitialize, leaving the pool's base LP fee at 0. Furthermore, the contract has no function that allows calling updateDynamicLPFee at a later time.

function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
if (!key.fee.isDynamicFee()) {
revert MustUseDynamicFee();
}
launchStartBlock = block.number;
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
currentPhase = 1;
lastPhaseUpdateBlock = block.number;
@> // No updateDynamicLPFee() call
return BaseHook.afterInitialize.selector;
}

This is critical because PoolManager.updateDynamicLPFee() can only be called by the hook contract itself:

// From PoolManager.sol
function updateDynamicLPFee(PoolKey memory key, uint24 newDynamicLPFee) external {
@> if (!key.fee.isDynamicFee() || msg.sender != address(key.hooks)) {
UnauthorizedDynamicLPFeeUpdate.selector.revertWith();
}
newDynamicLPFee.validate();
PoolId id = key.toId();
_pools[id].setLPFee(newDynamicLPFee);
}

Since the hook never calls this function and has no mechanism to do so, the pool's LP fee is permanently stuck at 0% (outside of when penalty fees are applied).

Risk

Likelihood:

  • Every pool created with this hook will have a permanent 0% LP fee (outside of when penalty fees are applied)

  • There is no mechanism in the contract to ever change this, making it a guaranteed condition

Impact:

  • Liquidity providers earn zero fees on swaps, removing all economic incentive to provide liquidity

  • The pool becomes economically unviable as LPs have no reason to deposit funds

  • This contradicts the README which states "Post-launch: Standard Uniswap fees apply"

Proof of Concept

Add the following test to the TokenLaunchHookUnit.t.sol file.

This test clearly shows that the LP fee of the pool initialized with the TokenLaunchHook in the setUp function, has an LP fee of 0.

function test_PoolFeeIsZeroAfterInitialize() public view {
// To get the LP fee, read from pool state
(,,, uint24 lpFee) =
StateLibrary.getSlot0(manager, key.toId());
console.log("Actual LP fee:", lpFee);
assertEq(lpFee, 0, "LP fee is 0");
}

Recommended Mitigation

Call updateDynamicLPFee in TokenLaunchHook::_afterInitialize, to set the desired poolFee on initialization

+ uint256 public constant LP_FEE = 3000 // 0.3% (Adjust as desired)
function _afterInitialize(address, PoolKey calldata key, uint160, int24) internal override returns (bytes4) {
if (!key.fee.isDynamicFee()) {
revert MustUseDynamicFee();
}
launchStartBlock = block.number;
uint128 liquidity = StateLibrary.getLiquidity(poolManager, key.toId());
initialLiquidity = uint256(liquidity);
currentPhase = 1;
lastPhaseUpdateBlock = block.number;
+ // Set initial LP fee (e.g., 0.3% = 3000)
+ poolManager.updateDynamicLPFee(key, LP_FEE);
return BaseHook.afterInitialize.selector;
}

Support

FAQs

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

Give us feedback!