Vanguard

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

Missing Zero Limit Validation in Constructor

Author Revealed upon completion

Description

  • phase1LimitBps and phase2LimitBps are validated to not exceed 10000 (100%), but they can be set to 0.

  • A zero limit means maxSwapAmount = 0, and any swap would exceed the limit, triggering penalties immediately.

constructor(...) BaseHook(_poolManager) {
// ...
// @> Only checks upper bound, not lower bound
if (
_phase1LimitBps > 10000 || _phase2LimitBps > 10000 ||
_phase1PenaltyBps > 10000 || _phase2PenaltyBps > 10000
) revert InvalidConstructorParams();
// @> Zero is allowed for limitBps
phase1LimitBps = _phase1LimitBps; // Could be 0
phase2LimitBps = _phase2LimitBps; // Could be 0
// ...
}

In _beforeSwap:

uint256 maxSwapAmount = (initialLiquidity * phaseLimitBps) / 10000;
// If phaseLimitBps = 0, maxSwapAmount = 0
if (!applyPenalty && addressSwappedAmount[sender] + swapAmount > maxSwapAmount) {
applyPenalty = true; // @> Always true if maxSwapAmount = 0
}

Risk

Likelihood:

  • Owner would need to deploy with 0 limit values

  • Unlikely in production but possible as configuration error

Impact:

  • All swaps in that phase would trigger penalty fees

  • Makes the pool effectively unusable during the phase

Proof of Concept

Deploy hook with 0 limit values - constructor accepts them without validation.

function test_ZeroLimitAllowed() public {
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager,
phase1Duration,
phase2Duration,
0, // @> Zero limit - all swaps trigger penalty
0, // @> Zero limit
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG);
(, bytes32 salt) = HookMiner.find(address(this), flags, creationCode, constructorArgs);
// This succeeds - zero limit is allowed
TokenLaunchHook strictHook = new TokenLaunchHook{salt: salt}(
manager,
phase1Duration,
phase2Duration,
0,
0,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
assertEq(strictHook.phase1LimitBps(), 0, "Zero limit accepted");
}

Recommended Mitigation

Add zero-check for limit parameters in constructor.

constructor(...) BaseHook(_poolManager) {
if (_phase1Duration == 0 || _phase2Duration == 0) revert InvalidConstructorParams();
+ if (_phase1LimitBps == 0 || _phase2LimitBps == 0) revert InvalidConstructorParams();
if (
_phase1LimitBps > 10000 || _phase2LimitBps > 10000 ||
_phase1PenaltyBps > 10000 || _phase2PenaltyBps > 10000
) revert InvalidConstructorParams();
// ...
}

Support

FAQs

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

Give us feedback!