Case Study 12.2: CREATE2 and Metamorphic Contracts — When Contract Addresses Become Predictable

Background

In January 2019, the Constantinople hard fork introduced EIP-1014 — the CREATE2 opcode. The motivation was straightforward and well-intentioned: enable deterministic contract deployment addresses. If you know the deployer address, the salt, and the init code, you can compute where the contract will live before it exists. This enables state channels, counterfactual instantiation, and elegant factory patterns.

But within months, security researchers realized that CREATE2, combined with SELFDESTRUCT, created something the Ethereum ecosystem had never dealt with before: metamorphic contracts — contracts that could be destroyed and redeployed at the same address with entirely different code.

This case study traces how a useful opcode enabled a new class of trust violations, examines the real-world exploits that followed, and explores the ongoing consequences for the Ethereum ecosystem.

The Mechanics: How Metamorphic Contracts Work

Step 1: Understanding CREATE2 Address Derivation

Recall from Section 12.7 that CREATE2 computes the contract address as:

address = keccak256(0xFF ++ deployer ++ salt ++ keccak256(init_code))[12:]

The address depends on three things: 1. The deployer's address (fixed) 2. A 32-byte salt (chosen by the deployer) 3. The hash of the initialization code

Notice what is not in this formula: the runtime code. The init_code is the constructor bytecode, not the permanent contract code. The same init_code can produce different runtime bytecodes depending on external state.

Step 2: The Metamorphic Init Code Trick

Here is where the attack becomes possible. A metamorphic factory works like this:

contract MetamorphicFactory {
    address public implementation;

    function deploy(bytes32 salt) public returns (address) {
        // The init code does NOT contain the contract logic directly.
        // Instead, it reads the 'implementation' address from the factory
        // and copies that contract's code as its own runtime code.
        bytes memory initCode = hex"5860208158601c335a63aaf10f01601457fd5b60003d6000816000...";
        // This init code:
        // 1. Calls back to the factory to get the current implementation address
        // 2. Uses EXTCODECOPY to copy the implementation's bytecode
        // 3. Returns that bytecode as its own runtime code

        address deployed;
        assembly {
            deployed := create2(0, add(initCode, 0x20), mload(initCode), salt)
        }
        return deployed;
    }

    function setImplementation(address _impl) public {
        implementation = _impl;
    }
}

The critical insight: the init_code is always the same, so keccak256(init_code) is always the same, which means the CREATE2 address is always the same for a given salt. But the init code reads the implementation address from the factory at deployment time, so the runtime code that gets stored at that address can be anything the factory owner chooses.

Step 3: The Metamorphic Lifecycle

  1. Factory owner sets implementation = ContractA (a benign ERC-20 token, for example).
  2. Factory calls deploy(salt=0x01). The init code reads ContractA's bytecode and deploys it at address 0xABC....
  3. Users interact with 0xABC... believing it to be a trustworthy ERC-20 token. They approve it to spend their tokens. They deposit funds.
  4. The contract at 0xABC... calls SELFDESTRUCT, destroying itself.
  5. Factory owner sets implementation = ContractB (a malicious contract that drains all approved tokens).
  6. Factory calls deploy(salt=0x01) again. The address is identical: 0xABC.... But the code is now ContractB.
  7. Users' existing approvals still point to 0xABC.... The malicious contract uses those approvals to drain funds.

The address never changed. The code did.

Step 4: Why This Works

The metamorphic pattern exploits a fundamental assumption: users and contracts trust addresses as stable identifiers. When you approve a token contract to spend your USDC, you are trusting that the code at that address will remain the same. When a protocol whitelists a contract address, it assumes the code behind that address is immutable.

CREATE2 combined with SELFDESTRUCT violated this assumption. An address could be associated with one set of code today and entirely different code tomorrow.

Real-World Incidents

The Tornado Cash Governance Attack (May 2023)

The most prominent real-world exploit of metamorphic contracts targeted Tornado Cash, the Ethereum privacy protocol. In May 2023, an attacker used a metamorphic contract to take over Tornado Cash's governance system.

The attack sequence:

  1. The attacker created a proposal to Tornado Cash governance. The proposal included logic that appeared benign — it claimed to penalize certain relayers.

  2. The proposal contained a reference to a contract deployed via CREATE2. When governance participants reviewed the proposal, they examined the code at the referenced address and found nothing malicious.

  3. After the proposal was submitted (but before it was executed by the governance timelock), the attacker: - Called SELFDESTRUCT on the referenced contract. - Redeployed a new contract at the same address using CREATE2 with the same salt and the same init code (which read from a now-different implementation).

  4. When the governance timelock executed the proposal, it called the redeployed contract, which now contained code that granted the attacker 1.2 million fake TORN votes — enough to control governance completely.

  5. With governance control, the attacker could drain the governance vault, modify the protocol, or hold the protocol hostage.

The attack succeeded because the governance timelock checked the proposal's code at submission time, not at execution time. Between submission and execution, the code at the target address changed — something that should have been impossible under the old assumption that deployed code is immutable.

The Damage

The attacker gained control of approximately $2.1 million in TORN tokens locked in governance. More critically, they gained full control of Tornado Cash's governance mechanism, allowing them to: - Modify the protocol's relayer registry - Change fee structures - Potentially drain protocol funds

The Tornado Cash community eventually negotiated with the attacker, who submitted a governance proposal to return control. But the incident demonstrated that metamorphic contracts could undermine the trust assumptions of even well-designed governance systems.

Technical Deep Dive: The SELFDESTRUCT Requirement

The metamorphic pattern requires SELFDESTRUCT to clear the code at an address before redeployment. Without SELFDESTRUCT, a CREATE2 deployment to an already-occupied address simply fails.

This created a clear taxonomy: - Contracts without SELFDESTRUCT: Truly immutable once deployed. Address and code are permanently linked. - Contracts with SELFDESTRUCT (or DELEGATECALL to a contract with SELFDESTRUCT): Potentially metamorphic if deployed via CREATE2.

The DELEGATECALL Subtlety

A contract does not need to contain SELFDESTRUCT directly. If it contains DELEGATECALL to an external contract, and that external contract contains SELFDESTRUCT, the delegatecalling contract can be destroyed. This makes the metamorphic risk harder to detect — the SELFDESTRUCT opcode might not appear in the contract's own bytecode.

contract Victim {
    address public implementation;

    function execute(bytes calldata data) public {
        implementation.delegatecall(data);
    }
}

contract Destroyer {
    function destroy() public {
        selfdestruct(payable(msg.sender));
    }
}

If Victim delegatecalls Destroyer.destroy(), the Victim contract is destroyed (not Destroyer), because DELEGATECALL runs in the caller's context. If Victim was deployed via CREATE2, it can now be redeployed with different code.

The Response: EIP-6780 and the Death of SELFDESTRUCT

The metamorphic contract problem was a significant motivation behind EIP-6780, implemented in the Dencun upgrade (March 2024). This EIP changed SELFDESTRUCT's behavior:

Before EIP-6780: - SELFDESTRUCT removes the contract's code, zeroes its storage, and sends its ETH balance to a designated address.

After EIP-6780: - SELFDESTRUCT only removes code and storage if called in the same transaction that created the contract. - In all other cases, SELFDESTRUCT sends the ETH balance but does not remove code or storage.

This change effectively kills the metamorphic pattern. If a contract cannot be destroyed (its code persists regardless of SELFDESTRUCT), it cannot be redeployed at the same address with different code. CREATE2 deployments to occupied addresses fail, and the address-to-code binding becomes permanent.

Why Not Just Remove SELFDESTRUCT Entirely?

EIP-6780 preserved SELFDESTRUCT's ability to destroy contracts within their creation transaction for two reasons:

  1. Factory patterns: Some legitimate patterns create temporary contracts within a single transaction (e.g., deploying a contract, using it, and destroying it to reclaim gas via the old refund mechanism). Preserving same-transaction SELFDESTRUCT allows these patterns to continue.

  2. Verkle tree migration: Ethereum's planned migration from Merkle Patricia tries to Verkle trees requires that contract code persist at known addresses. If contracts could disappear mid-epoch, the Verkle tree structure would need to handle deletions, adding significant complexity.

Detecting Metamorphic Risk

Before EIP-6780, several tools and techniques existed for detecting metamorphic contracts:

Static Analysis

Security tools like Slither and Securify could flag contracts that: - Were deployed via CREATE2 - Contained SELFDESTRUCT or DELEGATECALL - Had init code that read external state (making the runtime code dependent on deployment-time conditions)

Bytecode Verification

Etherscan and similar block explorers verify source code against deployed bytecode. If a contract at a known address has verified source code, you can be reasonably confident the code matches. However, if the contract were destroyed and redeployed, the verified status would become stale.

On-Chain Monitoring

Monitoring services could watch for SELFDESTRUCT events at CREATE2-deployed addresses and alert if redeployment occurred. The time window between destruction and redeployment was the critical attack surface.

The Init Code Hash Check

The most robust detection method: check whether the init code at a CREATE2 address is self-contained (deterministic) or reads external state. If the init code contains EXTCODECOPY, SLOAD on a factory address, or CALL to an external contract, the runtime code is not determined solely by the init code hash — it can change between deployments.

Broader Implications

Trust Model Changes

The metamorphic contract problem forced a re-evaluation of what it means to "trust" a smart contract:

Trust Assumption Pre-Metamorphic Post-Metamorphic
"Code at address X is immutable" Generally true Only true if contract cannot SELFDESTRUCT (or post-EIP-6780)
"I verified the code, so it's safe" One-time verification sufficient Must verify the code has not changed since last check
"Approvals to address X are safe" Safe if code is safe Unsafe if code can change
"Governance proposals targeting address X are safe" Safe at review time Must re-verify at execution time

Design Pattern Changes

The metamorphic risk led to several defensive design patterns:

  1. Time-locked governance with re-verification: Governance proposals should re-check the bytecode hash of target contracts at execution time, not just at submission time.

  2. Code hash checks in approvals: Advanced protocols check extcodehash of target contracts before executing approvals, comparing against a known good hash.

  3. Immutable factory registries: Instead of trusting individual contract addresses, protocols trust factory contracts that deploy immutable children (without SELFDESTRUCT).

  4. Counterfactual verification: Before interacting with a CREATE2 contract, independently compute the expected address from the known deployer, salt, and init code, and verify the deployed code matches expectations.

The Proxy Distinction

It is worth noting that proxy contracts (using DELEGATECALL) already allowed "mutable" behavior — the logic behind a proxy can be changed by updating the implementation address. The difference is transparency:

  • Proxies are explicitly upgradeable. Users can inspect the proxy's state to see which implementation it points to. The upgrade mechanism is visible on-chain.
  • Metamorphic contracts are covertly mutable. The address appears to host a normal, immutable contract. The mutability is hidden in the deployment mechanism.

The deception is the problem, not the mutability itself.

Discussion Questions

  1. EIP-6780 largely solved the metamorphic contract problem by neutering SELFDESTRUCT. But it also removed a legitimate tool (contract cleanup). Was this the right trade-off? What alternatives existed?

  2. The Tornado Cash governance attack succeeded because the timelock did not re-verify code at execution time. Should all governance systems implement code verification at execution time? What is the gas cost trade-off?

  3. CREATE2 was designed for legitimate use cases (state channels, counterfactual deployment). The metamorphic abuse was an unintended consequence. How should the Ethereum community evaluate the risk of new opcodes before adoption?

  4. Some argue that the metamorphic contract problem was always implicit in the SELFDESTRUCT opcode — CREATE2 just made it more practical. Is this accurate? Could metamorphic contracts exist with only CREATE (not CREATE2)?

  5. After EIP-6780, are there remaining mechanisms by which code at an address could change? Consider proxy patterns, DELEGATECALL, and potential future EIPs.

Timeline

Date Event
January 2018 EIP-1014 (CREATE2) proposed by Vitalik Buterin
February 2019 Constantinople activates; CREATE2 goes live
Late 2019 Security researchers identify the metamorphic contract pattern
2020 Tools for detecting metamorphic risk emerge (0age's metamorphic contract framework)
May 2023 Tornado Cash governance attack via metamorphic contract
June 2023 EIP-6780 proposed to neuter SELFDESTRUCT
March 2024 Dencun hard fork activates with EIP-6780

Sources and Further Reading

  • 0age, "The Promise and the Peril of Metamorphic Contracts" (2019)
  • EIP-1014: Skinny CREATE2
  • EIP-6780: SELFDESTRUCT only in same transaction
  • samczsun, "Tornado Cash Governance Attack Analysis" (May 2023)
  • Trail of Bits, "Contract Metamorphism and Trust" (2020)
  • Ethereum Foundation, "Dencun Upgrade FAQ" (2024)