Understanding Solidity’s create & create2 with Tornado Cash $1M Hack

Manaan Ansari
CoinsBench
Published in
4 min readMay 2, 2024

--

Can we compute a smart contract address before deployment?
Go ahead, take a guess.
If you said yes, give yourself a pat on the back — because you’re right!

I used to believe smart contract addresses were generated randomly. Silly me!
In reality, smart contract addresses are deterministic. This means that given the deployer’s address and nonce, we can determine what the contract address will be.

In this Blog, I’ll explain this concept without diving too deeply into assembly language.
However, if you’re interested in delving deeper, I’ll share relevant resources for further exploration.

Create

The ‘create’ opcode is the most commonly used contract creation opcode. When contracts are deployed from scripts or other development environments, the create opcode is the low-level instruction executed by the EVM to deploy and generate a contract address

contract deployer {
function deploy() public {
address deployed_contract_address = address(new A());
// some logic ...
}
}

The contract address is computed by the EVM using the following logic:

contract_address = keccak256(deployer_address, nonce); // with RLP encoding of [sender, nonce]

We can replicate the same logic in Solidity using the following function:

function computeAddress(address _origin, uint _nonce) public pure returns (address) {

bytes memory data;

if (_nonce == 0x00) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, bytes1(0x80));

else if (_nonce <= 0x7f) data = abi.encodePacked(bytes1(0xd6), bytes1(0x94), _origin, uint8(_nonce));

else if (_nonce <= 0xff) data = abi.encodePacked(bytes1(0xd7), bytes1(0x94), _origin, bytes1(0x81), uint8(_nonce));

else if (_nonce <= 0xffff) data = abi.encodePacked(bytes1(0xd8), bytes1(0x94), _origin, bytes1(0x82), uint16(_nonce));

else if (_nonce <= 0xffffff) data = abi.encodePacked(bytes1(0xd9), bytes1(0x94), _origin, bytes1(0x83), uint24(_nonce));

else data = abi.encodePacked(bytes1(0xda), bytes1(0x94), _origin, bytes1(0x84), uint32(_nonce));

return address(uint160(uint256(keccak256(data))));

}

Create 2

1. A fixed prefix, which is always ‘0xFF’.
2. The sender’s address ensures the contract is tied to a specific creator.
3. A chosen salt value adds uniqueness to the contract address.
4. The bytecode contains the code of the new contract to be deployed.

By combining these parameters, CREATE2 computes a deterministic address for the new contract, which remains the same even as the blockchain evolves

contract deployer {
function deploy(uint256 salt) public {
address deployed_contract_address = address(new A{salt: bytes32(salt)}());
// some logic ...
}
}

The contract address is computed by the EVM using the following logic:

deployed_contract_address = keccak256(0xFF, deployer_address, salt, contract_bytecode); 

We can replicate the same logic in Solidity using the following function:

function predictAddress(uint salt) public view returns( address){

return address(uint160(uint(keccak256(abi.encodePacked(

bytes1(0xff),

address(this),

bytes32(salt),

keccak256(abi.encodePacked(

type(SimpleContract).creationCode,

abi.encode()

))

)))));

}

Note: You will find all the code in this repository

Deterministic smart contracts can be both a blessing and a curse.

Metamorphic Smart Contracts

with the same bytecode and salt, result in the same address in each iteration.

Tornado Cash Hack

The Tornado Cash hack exploited the use of metamorphic smart contracts. Here’s how the attack unfolded:

Step 1
1. Create a deployer contract.
2. Use `CREATE2` to deploy a buffer contract.
3. The buffer contract then deploys a proposal contract using `CREATE` (nonce=1).

Step 1

Step 2
1. Once the proposal gets approved by the governance.
2. Self-destruct both the buffer and proposal contracts to reset the nonce.

Step 2

Step 3
1. Deploy the buffer contract again using `CREATE2` (same as step 1).
2. The buffer contract now deploys malicious code (as the nonce is 1). The malicious code is deployed on the same address as the proposal contract, which was approved by the DAO governance contract.
3. the governance contract executes the logic in the malicious contract

Step3

--

--