Vanguard

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

Hook logic bypass due to mismatch between address flags and implemented hook functions

Author Revealed upon completion

Root + Impact


Description

  • In Uniswap v4, the PoolManager uses a bitmasking system on the hook's address to determine which callback functions (like afterInitialize or beforeSwap) should be executed. For a hook's logic to run, the deployment script must mine a salt that produces an address where the specific bits corresponding to those functions are "flipped on."

    The current deployment script incorrectly mines for the BEFORE_INITIALIZE flag, but the TokenLaunchHook.sol contract implements the _afterInitialize internal function.

// in file deployLaunchHook.s.sol::Line no. 29
@> uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_INITIALIZE_FLAG);
// ... mining logic ...
(address hookAddress, bytes32 salt) = HookMiner.find( CREATE2_FACTORY,
@> flags, type(TokenLaunchHook).creationCode, constructorArgs );

Risk

Likelihood:

  • This will occur every time the script is used for deployment.

  • The mining process is deterministic; it will successfully find an address that satisfies the BEFORE_INITIALIZE bit. However, because the contract logic resides in afterInitialize, the PoolManager will look at the hook's address, see that the AFTER_INITIALIZE bit is 0, and skip the call entirely.

Impact:

  • Permanent Denial of Protection: The launchStartBlock and initialLiquidity variables will never be initialized because _afterInitialize is never called.

  • Broken Core Logic: Since launchStartBlock remains 0, the beforeSwap function will revert on every single trade due to the if (launchStartBlock == 0) revert PoolNotInitialized(); check, making the pool unusable.

Proof of Concept-
1. The developer runs the script.
2. The miner finds an address like 0x01... (where the 1st bit is for BEFORE_INITIALIZE).
3. The pool is initialized via PoolManager.
4. PoolManager checks the hook address for the AFTER_INITIALIZE bit (the 3rd bit).
5. The bit is not set. PoolManager skips the hook and completes initialization.
6.A user attempts to swap.
- beforeSwap runs, sees launchStartBlock is still the default 0, and reverts the user's transaction.

Recommended Mitigation

Update the flags in the deployment script to match the actual function overrides implemented in the contract.

- uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_INITIALIZE_FLAG);
+ uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG);

Support

FAQs

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

Give us feedback!