$1,000,000 in security audit grants are live now, Apply here →

← Resource Hub

Metamorphic Contracts and the Tornado Cash Exploit

Updated 7 min read

Quick answer. A metamorphic contract changes its code while keeping its address: it self-destructs, then new code is redeployed there with CREATE or CREATE2. Since EIP-6780, this only works if it self-destructs in its creation transaction.

Smart contracts are meant to be immutable. A metamorphic contract breaks that assumption: the address stays the same, but the code behind it changes.

For a long time this was a curiosity with no high-profile attacks. Then the Tornado Cash governance exploit used one to take over the protocol's governance, and a new EIP followed.

Below: how that exploit worked, how CREATE and CREATE2 choose addresses, two ways to build a metamorphic contract, and what EIP-6780 changed.

How Tornado Cash Was Exploited

Security researcher samczsun was among the first to publicly lay out the details of the attack.

The attacker granted themselves over a million votes and took over the entire governance system.

It started with a proposal that was not checked thoroughly. Either voters did not look closely, or there was no third party validating proposals at all.

The proposal's contract included an emergencyStop function, and that function contained selfdestruct.

The attacker even described the proposal as using the same logic as a past one, inviting voters to wave it through.

Once the proposal passed, the attack ran in three steps:

  1. The attacker called emergencyStop, which self-destructed the approved proposal contract.
  2. They deployed a brand new contract with arbitrary code at the same address.
  3. The new code unlocked a large number of governance votes, which they used to take TORN tokens for themselves, among other impacts.

According to samczsun's breakdown, the attacker could withdraw all locked votes, drain all tokens in the governance contract and brick the router.

The crux of the exploit is that one function with a self-destruct in it. To see why that was enough, start with how contract addresses are chosen.

How CREATE and CREATE2 Pick Addresses

The CREATE and CREATE2 opcodes both deploy a new contract. They differ in the inputs that decide where it lands, and that difference is the key to metamorphic contracts.

OpcodeThe new address is a hash of
CREATEThe sender's address and the sender's account nonce
CREATE2A 0xff byte, the sender's address, a salt and the hash of the creation code

The 0xff byte keeps CREATE2 addresses from colliding with CREATE addresses from the same sender. It does not matter for metamorphic contracts.

One more piece matters a lot: when an account self-destructs, its account nonce is reset.

CREATE depends only on the sender and the nonce. That already suggests one way to put different code at the same address.

Method 1: Reset the Factory's Nonce

Security researcher pcaversaccio published a simple proof of concept for this method. It uses three contracts:

  • A has a kill function that self-destructs.
  • B has no self-destruct and is a completely different implementation.
  • Factory deploys A or B with the new keyword, which uses CREATE, and can also self-destruct.
contract A {
    function kill() external { selfdestruct(payable(address(0))); }
}

contract B {
    uint256 private x;
    constructor(uint256 x_) { x = x_; }
}

contract Factory {
    function deployA() external returns (address) { return address(new A()); }
    function deployB() external returns (address) { return address(new B(1337)); }
    function kill() external { selfdestruct(payable(address(0))); }
}

// Transaction 1: deploy Factory with CREATE2 and a fixed salt,
// call deployA(), then kill both A and the Factory.
// Transaction 2: redeploy Factory with the same salt,
// then deployB() returns A's old address.

Here is why it works. Say the Factory lives at address 1 with a nonce of zero, and its first CREATE puts A at address 2.

  1. A self-destructs, so the code at address 2 is gone.
  2. The Factory self-destructs too, which resets its nonce to zero.
  3. The Factory is redeployed with CREATE2 and the same salt, so it lands at address 1 again. The sender for its next CREATE is unchanged.
  4. Its nonce is zero again, so its next CREATE also lands at address 2. CREATE does not depend on the bytecode, so this time it can deploy B.

The redeployment has to happen in a separate transaction. The end result: people reviewed the code at address 2 and approved it, and then the code was swapped out for anything the attacker wanted.

Method 2: Creation Code That Fetches Its Code

0age documented a second approach in a detailed article on metamorphic contracts.

A CREATE2 address depends on the creation code. Changing the creation code changes the address, which seems to rule out metamorphic contracts entirely.

But the creation code can get the runtime bytecode, or a chunk of it, by calling an external contract. The creation code itself never changes, while what it returns depends on a third-party contract.

For example, the creation code calls a function foo to get the bytecode. On the first call, foo returns good code.

Then the attacker flips a switch in that contract, such as a boolean. On the next call, foo returns malicious code.

Once the original contract is removed, CREATE2 runs again with the same creation code, sender and salt.

The only difference is the bytecode the external call returns, so new code lands at the old address.

Both Methods Need SELFDESTRUCT

Both methods need the original contract to self-destruct first. That gives reviewers a clear test.

If a contract has no reachable SELFDESTRUCT opcode, and never delegatecalls an address that could contain one, it cannot metamorphose.

Metamorphic contracts existed in this form for a long time without major exploits. The Tornado Cash governance exploit changed that and helped motivate EIP-6780.

What EIP-6780 Changes

As of June 2023, EIP-6780 was still a draft and subject to change. It has since been finalized and shipped with Ethereum's Dencun upgrade in March 2024.

Under EIP-6780, SELFDESTRUCT only deletes an account if it runs in the same transaction that created the contract.

Called in any later transaction, it only sends the contract's entire ETH balance to the target. The code stays, the account stays and the nonce is not reset.

Does it stop metamorphic contracts?

Somewhat. Take the Tornado Cash flow: deploy a contract, let that transaction finish, let people review and vote on it, then call the self-destruct.

That later call no longer deletes anything, so the Tornado Cash exploit would not have been possible under EIP-6780.

But a metamorphic contract is still possible within a single transaction. Deploy a contract, have some system inspect its code and accept it, then self-destruct it, all in that one transaction.

That is a full account clear: the nonce resets and the bytecode is removed.

You cannot redeploy to the address in that same transaction, because the account is only deleted at the very end of it. Redeployment, by either method above, has to happen in a separate transaction.

In our view, EIP-6780 is a good change.

It curtails much of the metamorphic contract risk and should help prevent more governance exploits on high-TVL protocols, even though it does not remove the technique entirely.

Metamorphic Contract Checklist

  • Search every contract that governance or users approve for a reachable SELFDESTRUCT, including inside innocent-looking functions like an emergency stop.
  • Check for delegatecalls to any address that could contain a self-destruct.
  • Check how the contract was deployed: CREATE from a factory that can self-destruct, or CREATE2 creation code that loads bytecode from another contract.
  • Do not accept "same logic as a past proposal" as a review. Have proposals validated by a third party.
  • Since EIP-6780, look closely at any flow that inspects and trusts code within the transaction that created it.

For how this fits into governance attacks more broadly, see DAO governance attacks.

For more on how contracts can change under a fixed address, see upgradeable smart contracts explained, and for opcode-level background, how the EVM works.

How Guardian Helps

A Guardian smart contract audit reviews governance, roles and upgrade paths at the design stage, then senior researchers attack privileged flows with proof-of-concept depth.

With Vanguard, Guardian keeps reviewing updates to the audited scope for six months after production.

Frequently Asked Questions

What is a metamorphic contract?

A contract whose code changes while its address stays the same. It self-destructs, then new code is deployed there, by resetting a factory's nonce under CREATE or with CREATE2 creation code that loads bytecode from another contract.

How did the Tornado Cash governance exploit work?

A malicious proposal contract hid selfdestruct in an emergencyStop function. After the vote, the attacker destroyed it and deployed new code at the same address, granting themselves over a million votes and control of governance.

Does EIP-6780 stop metamorphic contracts?

Mostly. SELFDESTRUCT now deletes an account only in the transaction that created it. A contract can still be created, accepted and destroyed in one transaction, then redeployed in a later one.

Can CREATE2 deploy different code to the same address?

Yes. The address depends on the creation code, not the runtime code. If the creation code fetches bytecode from an external contract, it can deploy different code to the same address once the old contract is gone.

Sources

Catch metamorphic code before it reaches a vote

Guardian reviews proposal contracts, deployment paths and governance logic before they hold real funds.