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.