Vanguard

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

DeployHookScript mines hook address flags inconsistent with getHookPermissions(), preventing afterInitialize from running and causing permanent swap DoS

Author Revealed upon completion

Root + Impact

The hook declares afterInitialize: true and beforeInitialize: false, but the deploy script mines an address with BEFORE_INITIALIZE_FLAG instead of AFTER_INITIALIZE_FLAG. Uniswap v4 uses address-encoded flags to decide which callbacks to invoke; therefore afterInitialize is not invoked, leaving launchStartBlock == 0 forever. Any swap reverts with PoolNotInitialized().

// DeployHookScript.sol
// @> Mines for BEFORE_INITIALIZE_FLAG but hook does not implement beforeInitialize and requires afterInitialize
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_INITIALIZE_FLAG);

Risk

Likelihood:

  • The provided deployment procedure (DeployHookScript) is used to deploy the hook for production/contest use, so the mined address will lack AFTER_INITIALIZE_FLAG every time.

Impact:

  • All swaps in pools that rely on this deployed hook revert with PoolNotInitialized(), causing full trading DoS.

Proof of Concept

function test_POC_FlagsMismatch_AfterInitializeNotCalled_SwapsDoS() public {
// Mine/deploy a hook using the SAME flags as DeployHookScript (WRONG):
// BEFORE_SWAP_FLAG | BEFORE_INITIALIZE_FLAG (missing AFTER_INITIALIZE_FLAG)
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
uint160 wrongFlags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_INITIALIZE_FLAG);
(address hookAddress, bytes32 salt) = HookMiner.find(address(this), wrongFlags, creationCode, constructorArgs);
TokenLaunchHook wrongHook = new TokenLaunchHook{salt: salt}(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
assertEq(address(wrongHook), hookAddress, "Hook address mismatch");
// Initialize pool with wrongHook (dynamic fee enabled so hook would normally work)
(PoolKey memory badKey,) =
initPool(ethCurrency, tokenCurrency, wrongHook, LPFeeLibrary.DYNAMIC_FEE_FLAG, SQRT_PRICE_1_1_s);
// Because AFTER_INITIALIZE_FLAG is not set on the hook address,
// Uniswap will not call _afterInitialize => launchStartBlock remains 0
assertEq(wrongHook.launchStartBlock(), 0, "launchStartBlock should remain unset");
// Swaps now revert forever with PoolNotInitialized()
vm.deal(user1, 1 ether);
vm.startPrank(user1);
SwapParams memory params = SwapParams({
zeroForOne: true,
amountSpecified: -int256(0.001 ether),
sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1
});
PoolSwapTest.TestSettings memory testSettings =
PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false});
vm.expectRevert(TokenLaunchHook.PoolNotInitialized.selector);
swapRouter.swap{value: 0.001 ether}(badKey, params, testSettings, ZERO_BYTES);
vm.stopPrank();
}

Recommended Mitigation

Match the flags in the script and in the src

- remove this code
+ add this code

Support

FAQs

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

Give us feedback!