Vanguard

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

Undefined CREATE2_FACTORY constant

Author Revealed upon completion

Description

  • The deployment script should mine a salt with HookMiner.find(...) using the same address that will actually perform the CREATE2 deployment, and all referenced constants must be defined so the script compiles and runs.

  • In DeployHookScript, the CREATE2_FACTORY constant is commented out, but the script still references it in HookMiner.find(...). This leaves an undefined symbol, causing compilation to fail. Even if the constant were defined, the script currently deploys with new TokenLaunchHook{salt: salt}(...) (EOA/vm.startBroadcast), so the mining “factory” address should match the actual deployer (the broadcaster), not an external factory, otherwise the mined address will not match the deployed address.

// DeployHookScript.sol
// address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C; // @> commented out
// ...
(address hookAddress, bytes32 salt) =
HookMiner.find(CREATE2_FACTORY, /* @> referenced but undefined */
flags,
type(TokenLaunchHook).creationCode,
constructorArgs);
vm.startBroadcast();
// deploys using EOA, not the factory passed to HookMiner.find(...)
TokenLaunchHook hook = new TokenLaunchHook{salt: salt}(...);
vm.stopBroadcast();
require(address(hook) == hookAddress, "DeployHookScript: Hook Address Mismatch");

Risk

Likelihood: High

  • Any run of the script as‑is fails at compile time because CREATE2_FACTORY is commented out while still referenced. This occurs when teams try to automate deployment via this script.

  • Common deployment workflow relies on this script; thus, teams are likely to hit the error immediately when preparing a production/staging deployment.

Impact: Low

  • Deployment blocked: The script cannot compile, halting releases and CI pipelines that rely on scripted deployment.

  • Address mismatch risk (secondary effect): If a developer naively reinstates CREATE2_FACTORY but keeps deploying via EOA (new ...{salt:}), the mined address will not equal the deployed address, tripping the final require and breaking deployment.

Proof of Concept

  • Create DeployScriptUndefinedFactory.t.sol under test directory and copy code below.

  • Run command forge test --mp DeployScriptUndefinedFactory -vvvv.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {Test} from "forge-std/Test.sol";
import {Deployers} from "@uniswap/v4-core/test/utils/Deployers.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {HookMiner} from "v4-periphery/src/utils/HookMiner.sol";
import {TokenLaunchHook} from "../src/TokenLaunchHook.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
// Unit tests can’t assert a compile‑time failure caused by an undefined symbol.
// Instead, this PoC reproduces the runtime symptom tied to misconfiguring the factory for mining
contract DeployScriptUndefinedFactoryTest is Test, Deployers {
uint256 phase1Duration = 100;
uint256 phase2Duration = 200;
uint256 phase1LimitBps = 100;
uint256 phase2LimitBps = 300;
uint256 phase1Cooldown = 5;
uint256 phase2Cooldown = 3;
uint256 phase1PenaltyBps = 500;
uint256 phase2PenaltyBps = 200;
function setUp() public {
deployFreshManagerAndRouters();
}
/// Demonstrates the practical consequence of using a wrong/undefined factory:
/// Mining with an incorrect factory address yields a different address than the
/// actual deployment address (EOA), breaking the "address match" invariant.
function test_MiningWithWrongFactoryYieldsAddressMismatch() public {
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
// Correct flags for this hook (AFTER_INITIALIZE + BEFORE_SWAP)
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG);
// Simulate an incorrectly configured factory (e.g., left undefined or wrong).
// Here we deliberately use a bogus address to mimic the script's misuse.
address wrongFactory = address(0xDEAD);
// Mine with the wrong factory
(address minedWithWrong, bytes32 saltWrong) =
HookMiner.find(wrongFactory, flags, creationCode, constructorArgs);
// Attempt to deploy with CREATE2 from the EOA (not the wrong factory)
vm.expectRevert();
TokenLaunchHook hookWrong = new TokenLaunchHook{salt: saltWrong}(
IPoolManager(manager),
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
}
// Mirrors the working pattern: mine with the actual deployer address,
// then deploy with CREATE2 from the same address — addresses match.
function test_MiningWithDeployerAddressMatches() public {
bytes memory creationCode = type(TokenLaunchHook).creationCode;
bytes memory constructorArgs = abi.encode(
manager,
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
uint160 flags = uint160(Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_INITIALIZE_FLAG);
// This follows the existing tests' approach
(address mined, bytes32 salt) =
HookMiner.find(address(this), flags, creationCode, constructorArgs);
TokenLaunchHook hook = new TokenLaunchHook{salt: salt}(
IPoolManager(manager),
phase1Duration,
phase2Duration,
phase1LimitBps,
phase2LimitBps,
phase1Cooldown,
phase2Cooldown,
phase1PenaltyBps,
phase2PenaltyBps
);
assertEq(address(hook), mined, "Mined address should equal deployed when factory matches");
}
}

Output:

[⠊] Compiling...
[⠢] Compiling 2 files with Solc 0.8.26
[⠆] Solc 0.8.26 finished in 61.52ms
No files changed, compilation skipped
Ran 2 tests for test/DeployScriptUndefinedFactory.t.sol:DeployScriptUndefinedFactoryTest
[PASS] test_MiningWithDeployerAddressMatches() (gas: 2689374)
Traces:
[2689374] DeployScriptUndefinedFactoryTest::test_MiningWithDeployerAddressMatches()
├─ [1091585] → new TokenLaunchHook@0x7E4A8F76FEc89Ed2EbF193e4E5Cc1c1ab32E9080
│ └─ ← [Return] 5406 bytes of code
└─ ← [Return]
[PASS] test_MiningWithWrongFactoryYieldsAddressMismatch() (gas: 14736703)
Traces:
[14736703] DeployScriptUndefinedFactoryTest::test_MiningWithWrongFactoryYieldsAddressMismatch()
├─ [0] VM::expectRevert(custom error 0xf4844814)
│ └─ ← [Return]
├─ [1419] → new <unknown>@0x7718eF19253979b9C433c248b3c3abF95093f703
│ └─ ← [Revert] 0xe65af6a00000000000000000000000007718ef19253979b9c433c248b3c3abf95093f703
└─ ← [Return]
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 230.52ms (260.77ms CPU time)
Ran 1 test suite in 233.48ms (230.52ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)

Recommended Mitigation

  • Mine with the actual deployer (it matches existing tests)

  • Deploy via vm.startBroadcast() (EOA), pass that deployer address to HookMiner.find(...).

- // address constant CREATE2_FACTORY = 0x4e59b44847b379578588920cA78FbF26c0B4956C;
// ...
- (address hookAddress, bytes32 salt) =
- HookMiner.find(CREATE2_FACTORY, flags, type(TokenLaunchHook).creationCode, constructorArgs);
+ (address hookAddress, bytes32 salt) =
+ HookMiner.find(deployerAddress, flags, type(TokenLaunchHook).creationCode, constructorArgs);
vm.startBroadcast();
TokenLaunchHook hook = new TokenLaunchHook{salt: salt}(
poolManager,
PHASE1_DURATION,
PHASE2_DURATION,
PHASE1_LIMIT_BPS,
PHASE2_LIMIT_BPS,
PHASE1_COOLDOWN,
PHASE2_COOLDOWN,
PHASE1_PENALTY_BPS,
PHASE2_PENALTY_BPS
);
vm.stopBroadcast();
require(address(hook) == hookAddress, "DeployHookScript: Hook Address Mismatch");

Support

FAQs

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

Give us feedback!