Fun with CREATE2

Jaypee Gee
Nov 7 · 8 min read

Co-authored with pldespaigne.

Photo by Pawel Nolbert on Unsplash

Since the Canstantinople release of Ethereum (January 2019) a new instruction (or opcode) named create2 is available in the EVM. This instruction is not meant to replace the former create instruction. Their respective goals and usage are different while both allow the deployment of Smart Contracts on an Ethereum blockchain. What distinguishes them from each other is the particular way in which each one calculates the address of the smart contract before deploying it.

First things first, we need to be clear about few things. Indeed, it is necessary to distinguish DeployCode from RuntimeCode.

DeployCode (sometimes called initCode) is bytecode obtained after compilation of the source code (ex: Solidity) and then before deployment, including constructor’s code and also constructor’s parameter(s).

RuntimeCode is bytecode actually stored in the blockchain after deployment, without the constructor’s code and once the constructor’s code has been executed.

NB : Remix uses the term Bytecode as a synonym for DeployCode and Runtime Bytecode for the aforementioned RuntimeCode.

Now we can focus on the create and create2 instructions.

The create instruction requires as input the DeployCode of the smart contract to actually deploy it. create then returns the smart contract addresses computed thanks to the deployer's address issuer (which can be an EOA or a Smart Contract address) and its nonce (incremented at each transaction).

The create2 instruction in addition to the smart contract DeployCode as input parameter, requires also a salt (a 256 bits value). The returned address is computed thanks to the current address, the salt and the hash of the DeployCode, and that's it! In short, nothing we can't control. Consequently, it means that a smart contract address can be pre-computed without the inconvenience of the nonce changing at each transaction.

Here is the official definition of the create2 instruction (feel free to skip it), but for a detailed description you can consult the EIP-1014 submitted by Vitalik Buterin himself.

create2(v, p, n, s) create new contract with code mem[p…(p+n)) at address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and send v wei and return the new address, where 0xff is a 8 byte value, this is the current contract's address as a 20 byte value and s is a big-endian 256-bit value.

Pre-compute the contract address

Let’s have some fun now ! Find below a purposely simple smart contract we called Zombie.sol. This contract comes with a killme() function used to destroy the contract to show later that we can redeploy this same contract at the very same exact address without difficulties.

Zombie.sol

pragma solidity ^0.5.8;contract Zombie {
uint256 public value;

constructor() public {
value = 1;
}
function setValue(uint256 _value) public {
value = _value;
}
function killme() public {
selfdestruct(msg.sender);
}
}

In order to pre-compute the address and later deploy this contract with create2 we will use a "Factory" contract. You can find one on Goerli at create2.eth (0xebe6...11f7) address. Find below, the source code of the two functions we are going to use, namely computeAddress() and deploy().

Factory.sol

// create2 playground by @pldespaigne
// based on contract written by @miguelmota & @ricmoo
pragma solidity >0.4.99 <0.6.0;contract Factory {
event Deployed(address addr, uint256 salt);

function computeAddress(bytes memory code, uint256 salt) public view returns(address) {
uint8 prefix = 0xff;
bytes32 initCodeHash = keccak256(abi.encodePacked(code));
bytes32 hash = keccak256(abi.encodePacked(prefix, address(this), salt, initCodeHash));
return address(uint160(uint256(hash)));
}

function deploy(bytes memory code, uint256 salt) public returns(address) {
address addr;
assembly {
addr := create2(0, add(code, 0x20), mload(code), salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
emit Deployed(addr, salt);
return addr;
}
}

For now, we will not explain in detail this source code above.

The computeAddress() function requires the contract's DeployCode, so, our first step is to obtain this DeployCode from the Zombie.sol contract. We can use Remix to achieve this. Cut'n paste Zombie.sol source code in the Remix editor, select the right compiler version (0.5.8), launch compilation and copy the produced DeployCode.

Paste this hexadecimal string starting with 608060405234… in the computeAddress() function of the “Factory” available online here. Do not forget to (1) prefix the string with 0x, and (2) remove the double quotes.

Alternatively, you can cut’n paste the string below if you don’t want to mess with Remix.

Zombie DeployCode

0x608060405234801561001057600080fd5b50600160008190555061011e806100286000396000f3fe6080604052348015600f57600080fd5b5060043610603c5760003560e01c806324d97a4a1460415780633fa4f24514604957806355241077146065575b600080fd5b60476090565b005b604f60a9565b6040518082815260200191505060405180910390f35b608e60048036036020811015607957600080fd5b810190808035906020019092919050505060af565b005b3373ffffffffffffffffffffffffffffffffffffffff16ff5b60005481565b806000819055507f4eb405514a39e0e076ebb055cbd8c59320e434aa390808e13d45e7f0b61a64506000546040518082815260200191505060405180910390a15056fea165627a7a72305820cde26bd9373cc77db51e3cf3916dfd9fc8a735009013bb5acdac1068f6f4cf930029

Below is a snapshot of etherscan.io used here to simplify the interaction with the “Factory” contract. We choose 4242 as salt value.

Click the Query button. We obtain an address. If we actually deploy (with the create2 instruction) the Zombie contract, it will be deployed for sure at the said address 0x48f94bc7f6ee9d434630767f8e635438b0672457. Let’s check this !

Deploy the contract at the pre-computed address

Once again we will use the “Factory” contract to simplify the deployment procedure by using its deploy() function.

Because we are now going to change the state of the (Goerli) blockchain, we need some funds and a wallet. We assume in this article that readers know how to use Metamask and obtain some (Goerli) Ether from the faucet (https://goerli-faucet.slock.it).

Cut’n paste the same bytecode as above (prefixed by 0x and without double quotes) in the code field and set the salt value with the same value chosen before (i.e. 4242).

Click the Write button and, this time, accept to pay a fee to actually deploy the Zombie contract (do not forget to connect etherscan.io with Metamask).

We will now use Remix to interact with the Zombie contract. If not yet done, you will have to paste the Zombie.sol source code, compile it and select Injected Web3 at the top left of the Remix interface.

Set the pre-computed address (0x48f94bc7f6ee9d434630767f8e635438b0672457) in the At Address field. Immediately after you should be able to interact with the Zombie contract. Feel free to change the value with setValue() function. By default, the constructor of the Smart Contract set value to 1.

Now destroy the contract by calling selfdestruct instruction via the killme() function, and ask again for the value. It should be reset to zero. The contract has gone !

Redeploy the contract (with the “Factory’s” deploy() function as seen above). Value is back to one, as stated by the constructor.


Constructor with argument

Below is another contract called GreedyGreeter.sol.

This contract says ”Hello World !” when you call greet() if it has at least 0.1 Ether on it’s balance, but since it hasn’t any payable function (constructor() function included) you can not send Ether to the contract !

GreedyGreeter.sol

pragma solidity ^0.5.8;contract GreedyGreeter {

string message;
address payable public recoveryAddress;

event MessageSet(string message);

constructor(address payable _recoveryAddress) public {
message = “Hello World !”;
recoveryAddress = _recoveryAddress;
}

function setMessage(string memory _newMessage) public {
message = _newMessage;
emit MessageSet(message);
}

function greet() public view returns(string memory) {
require(address(this).balance >= 0.1 ether, “Not enough ether in the contract balance !”);
return message;
}

function destroy() public {
require(msg.sender == recoveryAddress, “You have not the permission to destroy the contract !”);
selfdestruct(recoveryAddress);
}
}

Thus, in order to successfully call greet() you have to :

  1. Pre-compute the deployment address of the contract
  2. Send some Ether to this address (at least 0.1 ETH)
  3. Deploy the contract at the given address thanks to create2
  4. Call the greet() function on the newly deployed contract
  5. Finally, you can now call destroy() in order to get back your Ether !

Notice that there is one more difficulty here. You must set the _recoveryAddress constructor’s parameter at deployment time, then at computeAddress() call time also.

To do that, take your own Ethereum address, remove the leading 0x and pad it to make it 64 char long (i.e. 24+40).

Example : 0x4D7e2f3ab055FC5d484d15aD744310dE98dD5Bc3 becomes 0000000000000000000000004D7e2f3ab055FC5d484d15aD744310dE98dD5Bc3

Then, past this at the end of the GreedyGreeter DeployCode.

NB: All the arguments of the constructor are simply pasted at the end of the DeployCode (cf. documentation).

GreedyGreeter DeployCode

0x608060405234801561001057600080fd5b506040516020806107908339810180604052602081101561003057600080fd5b81019080805190602001909291905050506040518060400160405280600d81526020017f48656c6c6f20576f726c642021000000000000000000000000000000000000008152506000908051906020019061008c9291906100d4565b5080600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555050610179565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061011557805160ff1916838001178555610143565b82800160010185558215610143579182015b82811115610142578251825591602001919060010190610127565b5b5090506101509190610154565b5090565b61017691905b8082111561017257600081600090555060010161015a565b5090565b90565b610608806101886000396000f3fe608060405234801561001057600080fd5b506004361061004c5760003560e01c8063368b877214610051578063710eb26c1461010c57806383197ef014610156578063cfae321714610160575b600080fd5b61010a6004803603602081101561006757600080fd5b810190808035906020019064010000000081111561008457600080fd5b82018360208201111561009657600080fd5b803590602001918460018302840111640100000000831117156100b857600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f8201169050808301925050505050505091929192905050506101e3565b005b6101146102b7565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b61015e6102dd565b005b6101686103be565b6040518080602001828103825283818151815260200191508051906020019080838360005b838110156101a857808201518184015260208101905061018d565b50505050905090810190601f1680156101d55780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b80600090805190602001906101f99291906104d8565b507f3d7f415c35b881f2d0a109b3d9a1377e1e14afec5cc1fd06b563ed160c5e2630600060405180806020018281038252838181546001816001161561010002031660029004815260200191508054600181600116156101000203166002900480156102a65780601f1061027b576101008083540402835291602001916102a6565b820191906000526020600020905b81548152906001019060200180831161028957829003601f168201915b50509250505060405180910390a150565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610383576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004018080602001828103825260358152602001806105a86035913960400191505060405180910390fd5b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16ff5b606067016345785d8a00003073ffffffffffffffffffffffffffffffffffffffff16311015610438576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040180806020018281038252602a81526020018061057e602a913960400191505060405180910390fd5b60008054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156104ce5780601f106104a3576101008083540402835291602001916104ce565b820191906000526020600020905b8154815290600101906020018083116104b157829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061051957805160ff1916838001178555610547565b82800160010185558215610547579182015b8281111561054657825182559160200191906001019061052b565b5b5090506105549190610558565b5090565b61057a91905b8082111561057657600081600090555060010161055e565b5090565b9056fe4e6f7420656e6f75676820657468657220696e2074686520636f6e74726163742062616c616e63652021596f752068617665206e6f7420746865207065726d697373696f6e20746f2064657374726f792074686520636f6e74726163742021a165627a7a723058204a6c3a3117b721bb90287d1a91c16a26484076e0f1c92406bcec3db3cc5cef4b0029 <-- your padded address here -->

You should obtain a hex string of 3938 chars ending with your Ethereum address. The 64 char address that you paste at the end of the bytecode corresponds to the _recoveryAddress constructor argument of the GreedyGreeter solidity code above.

1) Pre-compute the contract address

  • Go to the Etherscan > Goerli > 0xebe6eb096f5fdadea6de646b8f0daff6f63f11f7 > readContract tab > computeAddress function
  • Paste your DeployCode and choose a salt (it has to be a number)
  • Click the Query button. The function will return the address of the future contract instance.

2) Send Ether to pre-computed address

  • Send at least 0.1 ETH

3) Deploy the contract at the given address

  • Go back to the “Factory” contract on Etherscan
  • Go to the writeContract tab and click on Connect with MetaMask
  • Paste your bytecode and the same salt as step 1)
  • Click on the Write button to deploy the contract.

4) Call the greet() function

  • Go to Remix (http://remix.ethereum.org)
  • Copy/Paste the solidity code of the GreedyGreeter.sol in the editor
  • Compile it
  • Go to the Run tab
  • Be sure you selected Injected Web3, then paste the newly deployed contract address and click on At address
  • call the greet() function !

5) Call destroy()

You can now call the destroy() function, this will selfdestruct the contract and send you back your Ether !

Conclusion

Because the create2 address is calculated from the hash of the DeployCode, which therefore includes the arguments of the constructor, then, if the argument(s) change, the contract address will change too. In some cases, it might become a limitation.

Anyway ! Bravo ! You are now able to pre-compute the address of any smart contract. Even those requiring some parameters for the constructor. As seen, create2 keeps the creation of contract as simple as keys generation. It may solve user adoption difficulties by skipping key pair management issues. Indeed, keys should provide access to funds and not hold the funds.

For example, you can assign a contract, like a smart wallet contract to thousands of potential customers. At no cost you can now provide them with an address without actually deploying those smart wallets on-chain. If some of these customers finally turn out to be real clients adopting your solution, there will always be time to actually deploy their contract, and pay the Gas only for those early adopters.

To summarize, as stated in EIP-1014, create2 is intended to solve usability and scalability to simplify Layer2 solutions creation.

Jaypee Gee

Written by

Blockchain technologies enthusiast.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade