Vanguard

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

Deployment Flag Mismatch Bricks Protocol

Author Revealed upon completion

Root + Impact

Description

  • The deployment script deployLaunchHook.s.sol contains a critical flag mismatch: it mines for hook addresses with BEFORE_INITIALIZE_FLAG (bit 0), but the TokenLaunchHook contract implements afterInitialize (requires AFTER_INITIALIZE_FLAG, bit 1). This discrepancy causes address bitmask conflicts with the actual hook permissions, permanently bricking pool initialization.

// deployLaunchHook.s.sol:28
uint160 flags = uint160(
Hooks.BEFORE_SWAP_FLAG | Hooks.BEFORE_INITIALIZE_FLAG
);
// TokenLaunchHook.sol (actual implementation)
function afterInitialize(
address,
PoolKey calldata key,
uint160,
int24
) external override poolManagerOnly returns (bytes4) {
// Sets launchStartBlock - CRITICAL for protocol function
}

Risk

Likelihood:

  • The deployment script is deterministically incorrect; every deployment will produce a hook address incompatible with the actual callback implementation.

Impact:

  • Immediate: PoolManager.initialize() reverts due to address/permission bitmask mismatch (security check prevents initialization)

Proof of Concept

No complex exploit needed—this is a deterministic deployment failure. The Foundry script will generate addresses with 0x01... prefix while the contract emits afterInitialize callbacks. Attempting to initialize a pool with this hook results in revert.

function test_DeploymentFlagMismatchPoC() public {
// This test simulates the bug in deployLaunchHook.s.sol
// The script uses BEFORE_INITIALIZE instead of AFTER_INITIALIZE
uint256 phase1Dur = 100;
uint256 phase2Dur = 100;
uint256 p1Limit = 100;
uint256 p2Limit = 100;
uint256 p1CD = 0;
uint256 p2CD = 0;
uint256 p1Pen = 0;
uint256 p2Pen = 0;
bytes memory constructorArgs = abi.encode(
manager,
phase1Dur,
phase2Dur,
p1Limit,
p2Limit,
p1CD,
p2CD,
p1Pen,
p2Pen
);
// WRONG FLAGS: Uses BEFORE_INITIALIZE (0x01) instead of AFTER_INITIALIZE (0x02)
uint160 wrongFlags = uint160(Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG);
// Mine the address with the WRONG flags
(address hookAddress, bytes32 salt) = HookMiner.find(address(this), wrongFlags, type(TokenLaunchHook).creationCode, constructorArgs);
TokenLaunchHook brokenHook = new TokenLaunchHook{salt: salt}(
manager, phase1Dur, phase2Dur, p1Limit, p2Limit, p1CD, p2CD, p1Pen, p2Pen
);
PoolKey memory brokenTestKey = PoolKey({
currency0: ethCurrency,
currency1: tokenCurrency,
fee: LPFeeLibrary.DYNAMIC_FEE_FLAG,
tickSpacing: 60,
hooks: brokenHook
});
// The PoolManager will revert during initialization because the hook address bits (from script)
// do not match the contract's actual required bits.
vm.expectRevert(abi.encodeWithSelector(Hooks.HookAddressNotValid.selector, address(brokenHook)));
manager.initialize(brokenTestKey, SQRT_PRICE_1_1);
console.log("Bug Confirmed: Pool initialization fails due to flag mismatch between deployment script and contract");
}

Recommended Mitigation

Update the deployment script to use AFTER_INITIALIZE_FLAG to match the actual hook implementation. Ensure the mined address bitmask aligns with the afterInitialize callback.

// deployLaunchHook.s.sol:28 (FIXED)
uint160 flags = uint160(Hooks.AFTER_INITIALIZE_FLAG); // CORRECT: bit 1 for afterInitialize
// Or if implementing both hooks:
uint160 flags = uint160(
Hooks.AFTER_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG
);

Support

FAQs

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

Give us feedback!