How to use Create2 to deriving contract addresses
Part 2 of How to deploy a contract to the same address on multiple networks.
This is part 2 of How to deploy a contract to the same address on multiple networks. Navigate to part 1 and complete the setup and deployment steps before continuing:
How to deploy a contract to the same address on multiple networks (part 1)
Now that we have learned how to set equivalent nonces on all networks, let’s explore another solution to deterministic deployment. In this section, we will deploy a contract factory with create2.
Create2 is an alternative to deriving contract addresses that do not involve the nonce. Instead, a contract can be deployed using the deployer address, bytecode, and salt, as illustrated below:
-
Deployer address: The Ethereum wallet address used to deploy a contract.
-
bytecode: A low level compiled version of a Solidity smart contract that can be read by the EVM.
-
salt: A required but arbitrary value provided by the user.
Instead of the nonce, create2 uses a random salt hex set by the user. In the image above, the salt used is “HELLO”. With the salt hex, a specific contract address can then be derived with the wallet address and bytecode. If this transaction is run again on the same network, it will fail because a contract has already been deployed to that address.
Because create2 is a solidity feature, we can only utilize it on the Ethereum network. Hence, we will code a contract deployment factory with a create2 function and deploy the factory. Then our factory should be able to deploy any contract deterministically using a given salt.
Create2 contract factory
In the contracts
folder, create a new solidity file named DeterministicDeployFactory.sol
, and add the following lines:
Inside our contract, let’s add a deploy
function and pass in a bytecode
and salt
parameter:
Inside, let’s add an address named addr
and an inline assembly statement to communicate with the EVM at a low level:
Within the assembly statement, we can use Yul to call create2 and instruct the EVM to deploy our contract’s bytecode:
Then, let’s create an event that will emit our deployed address when the function is called. Above the function, add the following event:
Finally, at the end of the function body, add an emit statement:
If successful, your contract should look like this:
Before we continue, let’s compile our contract with the following Hardhat command:
If successful, you should see:
Great work! We are almost there. All that’s left is to create a deploy script and deploy our factory!
Factory deploy script
In your scripts
folder, create a new file named deployFactory.js
and paste the following async function and catch statement:
Inside our function body, add the following lines:
If you’ve never written a deploy script, see the above comments for context.
Your entire deploy script should look like this:
Now, let’s deploy! Because our nonces are set equally on each network, this factory will deploy to the same address everywhere. Let’s deploy to each network by running the following Hardhat commands:
If successful, each deployment should return the same address on every network:
Be sure to save your factory address so we can use it to later deploy other contracts.
✨Congratulations✨ You just created your own deployment factory and deployed it deterministically on four test networks!
Deploy a contract with the factory
Now that we’ve deployed our factory, let’s test its functionality by sending our vault contract to our factory for deployment with create2.
First, let’s make an important change to Vault.sol
. Because we are now deploying from a factory, we can no longer set the owner of our contract as msg.sender. This will set the factory address as the owner because it is technically the deployer. Since we don’t have the factory’s private key, the funds would then be locked indefinitely.
Instead, set your own Metamask address as the owner:
Be sure to change the owner address to your own MetaMask wallet.
Now, recompile the contract by typing the following in the terminal:
If successful, Hardhat will return:
Utility functions
Before we deploy, we need to create two helper functions. The first will encode and hash our constructor parameter (unlock time), so it is readable to the EVM. This is necessary because our deployment factory requires both the bytecode and constructor arguments to deploy. The second function will compute the Vault contract’s deployed address, which we need in both the depositVault
and withdrawVault
scripts.
In your project folder, create a new folder called utils
:
Inside utils
create, a file named utils.js
and add the following lines:
Let’s start with the encoder
function. We are passing the types and values of our constructor arguments. For example, in our Vault contract, the constructor is passed an unlock time. So to encode the time, we provide its type “uint” and Unix timestamp value (“1657434348”) to the encoder.
Inside the encoder
function, add the following lines:
Here, we use the AbiCoder from ethers.js and encode our constructor arguments into a hexadecimal value the EVM can read.
Next, let’s add to the create2address
function. Our function receives the deployer address(factory address) + salt(saltHex) + bytecode(initCode). These are all the necessary components to compute a create2 address.
Inside the function body, add the following:
We are using another ethers.js function which computes the create2 address and returns our deployed address.
Your utils.js
file should look like this:
Awesome! Now we can move on to the deploy script!
Edit Vault deploy script
Replace the contents of your vaultDeploy.js
file with the following:
Above, we have imported our Vault contract bytecode. Hardhat generates contract bytecode in the artifacts
folder after you compile a contract. Additionally, we have imported our encoder
and create2Address
functions from utils
.
We know our updated deploy script will need to do the following:
-
Generate the create2 address.
-
Connect to our deployed factory.
-
Call the deploy function, pass the Vault bytecode, and pass a salt.
-
Wait for the deployment event and print the deployed address.
To generate our create2 address, we need to create a timestamp, salt hex, and configure our deployment bytecode.
First, let’s generate another Unix timestamp and set the unlock time with our Hardhat task:
Navigate back to vaultDeploy.js
and create a variable inside the main function to store your new Unix timestamp:
Also, create a variable to store your deployed factory address, as we will need this to connect to the factory:
Now, let’s create a variable to store our salt hex. We can take an arbitrary value and get its hex value with the following ethers function:
The last parameter we will need to compute the create2 address is the contract deployment code. We can configure the deployment code by adding the encoded constructor arguments to the end of the bytecode like so:
Next, we call our imported create2Address
function, pass in our parameters and store it in a variable:
Your vaultDeploy.js
file should appear as follows:
In console, run the following Hardhat command:
If successful, you will get your precomputed Vault contract address:
Before we finalize our deploy script, ensure you replace 0xREPLACE_ADDRESS in vaultDeploy.js and vaultWithdraw.js with your precomputed address.
Now, create a connection to your factory using ethers by adding the following:
Lastly, call the factory deploy function and pass in our init code and salt hex. Then wait for the transaction receipt and print the deployed address:
Your vaultDeploy.js
file should look as follows:
Ensure your timestamp is still set ahead of the current time and run the deploy script for each network with this Hardhat command:
If successful, your vault contract will deploy to your precomputed create2 address on each testnet:
Woohoo, nice job!! 🥳🥳 You can now use your factory to deploy deterministically without maintaining your account nonces! If you have come this far, you’re ready to build some awesome projects! 🌠🌠🌠
Learning recap
In this tutorial, you learned:
-
How contract addresses are derived with nonces and salts.
-
The difference between standard and create2 deployment.
-
How to build, test, and deploy smart contracts in Hardhat.
-
How to deploy a contract to the same address on multiple networks by setting the nonce.
-
How to deploy a contract to the same precomputed address on multiple networks with create2.
Feel free to reach out to us in the Alchemy Discord for help or chat with us @Alchemy on Twitter!