Vanguard

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

[L-03] Missing Minimum Cooldown Validation

Author Revealed upon completion

Description

Constructor doesn't validate that cooldown values meet a minimum threshold, allowing ineffective or zero cooldown configurations.

Location: src/TokenLaunchHook.sol:62-88

constructor(...) BaseHook(_poolManager) {
if (_phase1Duration == 0 || _phase2Duration == 0) revert InvalidConstructorParams();
// BPS values are validated...
if (_phase1LimitBps > 10000 || ...) revert InvalidConstructorParams();
// BUT cooldowns are NOT validated!
@> phase1Cooldown = _phase1Cooldown; // Can be 0 or 1
@> phase2Cooldown = _phase2Cooldown; // Can be 0 or 1
}

When cooldown is 0, the check blocksSinceLastSwap < phaseCooldown is always false for uint256.
When cooldown is 1, bots only need to wait ~12 seconds between swaps, providing minimal protection.

Risk

Likelihood: Occurs during deployment with misconfigured parameters (cooldown set too low).

Impact:

  • Setting cooldown to 0 completely disables cooldown protection

  • Setting cooldown to 1 provides minimal protection (~12 seconds on Ethereum)

  • Misconfiguration during deployment leads to ineffective anti-bot protection

Proof of Concept

function test_PoC_L06_MissingMinCooldownValidation() public {
// Deploy a hook with cooldown = 0 or 1
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
0, // phase1Cooldown = 0 (NO VALIDATION!)
1, // phase2Cooldown = 1 (only 12 seconds!)
phase1PenaltyBps,
phase2PenaltyBps
);
uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(address(this), flags, creationCode, constructorArgs);
// Both values accepted without validation
TokenLaunchHook weakHook = new TokenLaunchHook{salt: salt}(
manager, phase1Duration, phase2Duration,
phase1LimitBps, phase2LimitBps,
0, 1, // Weak cooldowns accepted!
phase1PenaltyBps, phase2PenaltyBps
);
assertEq(weakHook.phase1Cooldown(), 0, "Zero cooldown accepted");
assertEq(weakHook.phase2Cooldown(), 1, "Cooldown of 1 block accepted");
}

Recommendations

Add minimum cooldown validation in constructor:

uint256 constant MIN_COOLDOWN = 5; // At least 5 blocks (~1 minute on Ethereum)
constructor(...) BaseHook(_poolManager) {
if (_phase1Duration == 0 || _phase2Duration == 0) revert InvalidConstructorParams();
+ if (_phase1Cooldown < MIN_COOLDOWN || _phase2Cooldown < MIN_COOLDOWN) revert InvalidConstructorParams();
// ... rest of constructor
}

Support

FAQs

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

Give us feedback!