Create2.sol
Create2.sol
is a simple library for using the CREATE2 opcode, allowing for deployment and pre-computation of addresses when using it.
To learn more about all the cool things you can do with it, head to Getting the Most out of CREATE2
In this guide we will precompute the address where a contract will be deployed and send Ether to it. Then, we’ll deploy a contract to that same address, and use it to retrieve the funds previously sent there. (This is based on the guide Deploying Smart Contracts Using CREATE2
and the OpenZeppelin CLI )
Thanks to @k06a for requesting an example.
CREATE2
The whole idea behind this opcode is to make the resulting address independent of future events. Regardless of what may happen on the blockchain, it will always be possible to deploy the contract at the precomputed address.
New addresses are a function of:
-
0xFF
, a constant that prevents collisions withCREATE
- The sender’s own address
- A salt (an arbitrary value provided by the sender)
- The to-be-deployed contract’s bytecode
new_address = hash(0xFF, sender, salt, bytecode)
CREATE2
guarantees that if sender
ever deploys bytecode
using CREATE2
and the provided salt
, it will be stored in new_address
.
Because bytecode
is included in this computation other agents can rely on the fact that, if a contract is ever deployed to new_address
, it will be one they know about. This is the key concept behind counterfactual deployments.
Deploy VaultFactory
We will use Remix to deploy and interacts with our contracts. (OpenZeppelin CLI 2.8 is planned to support deployment of regular contracts)
We will compute the address where Vault
will be deployed, and send Ether there. Then, we will deploy Vault
using VaultFactory
which uses Create2.sol
and finally call the withdraw
method, retrieving the funds that were sent to it before deployment.
First create the Vault.sol
and VaultFactory.sol
contracts in Remix.
Next deploy VaultFactory.sol
to a test network. (Either a VM or a public testnet, for this guide I will use public testnet Rinkeby)
My VaultFactory
contract on Rinkeby:
https://rinkeby.etherscan.io/address/0xfea3f16aecd00a080403149fc5dd804494255657#code
Vault.sol
pragma solidity ^0.5.0;
import "https://github.com/OpenZeppelin/openzeppelin-sdk/blob/v2.7.1/packages/lib/contracts/Initializable.sol";
contract Vault is Initializable {
address payable public owner;
function initialize(address payable _owner) initializer public {
owner = _owner;
}
function withdraw() public {
require(owner == msg.sender);
owner.transfer(address(this).balance);
}
}
VaultFactory.sol
pragma solidity ^0.5.0;
import "./Vault.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.0/contracts/utils/Create2.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.0/contracts/utils/Address.sol";
contract VaultFactory {
event VaultCreated(address vault);
function deployVault(bytes32 salt, address payable owner) public {
address vaultAddress;
vaultAddress = Create2.deploy(salt, type(Vault).creationCode);
Vault(vaultAddress).initialize(owner);
emit VaultCreated(vaultAddress);
}
function computeAddress(bytes32 salt) public view returns (address) {
return Create2.computeAddress(salt, type(Vault).creationCode);
}
function sendValue(bytes32 salt) external payable {
address vaultAddress;
vaultAddress = Create2.computeAddress(salt, type(Vault).creationCode);
Address.sendValue(Address.toPayable(vaultAddress), msg.value);
}
}
Computing the Deployment Address
With VaultFactory
deployed, we can get the factory to compute the address where our Vault
contracts will be deployed using an arbitrary salt
by calling computeAddress
with a salt
such as 0x0000000000000000000000000000000000000000000000000000000000000001
Which for my deployed VaultFactory
gives an address of 0x262245f12519e61278A2d013721A6310661B6Cad
Interacting With the Counterfactual Contract
Under normal circumstances, sending funds to a random Ethereum address is a bad idea. Here however, we know we’ll be able to deploy Vault
at the computed address and retrieve our funds. So let’s do it!
Send some (test) Ether to the address that we got from computeAddress
.
I am going to use sendValue
function on VaultFactory
(mainly because I couldn’t see an easy way to send value to an address using Remix when using a VM).
In the transaction we can see that the address had 0.1 Ether sent to it.
https://rinkeby.etherscan.io/tx/0x01f28c4ca280b239ad2e923752142c2d71abaa2555f6468e8b5a8b21f420a3d6#statechange
Because the address has no bytecode and we don’t have its private keys, we cannot do much with it other than checking the funds are indeed there.
Next up is to get the funds out of the vault.
Withdrawing From Our Vault
First we need to deploy our Vault
.
Use Remix to call deployVault
on VaultFactory
using the same salt
as before and the owner
of the Vault
who can withdraw
funds.
The transaction deploying our Vault
:
https://rinkeby.etherscan.io/tx/0x14c7be217ab27a0adb4ef0263d6271da7c351a8f2275e4a133a35fd912a5ec95
If all went well, we should now be able to withdraw
from our Vault
.
Using Remix, show our Vault
contract using atAddress
so that we can interact with it.
Then call withdraw
which will send the funds to the owner of the Vault
The withdraw transaction showing the change in Ether:
https://rinkeby.etherscan.io/tx/0x5919cef5a5a5e4a8932d20ac06d6ae2148ede459650492f91a1f8ff8965a0d46#statechange
Success! Just to be sure, let’s verify the Vault
is indeed empty:
https://rinkeby.etherscan.io/address/0x262245f12519e61278A2d013721A6310661B6Cad
We’ve sent funds to an address we precomputed, knowing we’d be later able to deploy a contract there and retrieve them.