TLDR
CREATE2’s deployment address depends on constructor argsCREATE3avoids this. It uses:- user-provided salt
- address of factory
- msg sender’s address*
- It does this by using
CREATE2to deploy a single-use factory. - This single-use factory uses
CREATEto deploy our smart contract.- This factory has a deterministic address, and always deploys with
nonce = 0.
- This factory has a deterministic address, and always deploys with
* see A Possible 3rd Parameter
Background - CREATE and CREATE2
CREATE and CREATE2 are opcodes in the Ethereum Virtual Machine. Each one deploys a new smart contract, but they differ in how the new smart contract’s address is determined.
CREATE
CREATE determines the address using:
- address of the deployer
- nonce of the deployer
Specifically, it uses the formula:
new_contract_address = keccak256(rlp(deployer ++ nonce))[12:]
where:
++denotes concatenationdeployeris the (EOA or smart contract) address which is deployingnonceis the nonce of the deployer[12:]denotes taking the last 20 bytes from the 32 byte keccak resultrlpdenotes an RLP encoding function.
CREATE2
CREATE2 determines the address using:
- address of the deployer
- arbitrary salt data
- the smart contract’s initcode
Specifically, it uses the formula:
new_contract_address = keccak256(0xff ++ deployer ++ salt ++ init_code_hash)[12:]
where:
++denotes concatenation0xffis to avoid collision withCREATE’s RLP encoding (this prefix requires a lot of encoded data)deployeris the (EOA or smart contract) address which is deployingsaltis an arbitrary 32 byte piece of datainit_code_hash=keccak256(init_code)[12:]denotes taking the last 20 bytes from the 32 byte keccak result
Limitation of CREATE2
CREATE2 suits many use cases. However, addresses are influenced by the constructor args. This is a limitation. When wanting to deploy across multiple chains, where constructor arguments are different in each environment, you would need to choose one of three solutions.
The first solution is to accept that your addresses will be different.
Secondly, you can replace the constructor with an initializer.
The third solution is to use CREATE3.
CREATE3
We are going to be looking at Axelar’s CREATE3 implementation. For other examples, see the Appendix. They should all function the same. I chose Axelar’s because it doesn’t rely as heavily on direct bytecode.
Inheritance Diagram
Here is a diagram of Axelar’s smart contract inheritance, for reference.

Deployer Contract
First, let’s look at the main entry point, the Deployer contract.
(omitting comments)
abstract contract Deployer is IDeployer {
using SafeNativeTransfer for address;
function deploy(bytes memory bytecode, bytes32 salt) external payable returns (address deployedAddress_) {
bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt));
deployedAddress_ = _deploy(bytecode, deploySalt);
emit Deployed(deployedAddress_, msg.sender, salt, keccak256(bytecode));
}
function deployAndInit(
bytes memory bytecode,
bytes32 salt,
bytes calldata init
) external payable returns (address deployedAddress_) {
bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt));
deployedAddress_ = _deploy(bytecode, deploySalt);
emit Deployed(deployedAddress_, msg.sender, salt, keccak256(bytecode));
(bool success, ) = deployedAddress_.call(init);
if (!success) revert DeployInitFailed();
}
function deployedAddress(
bytes memory bytecode,
address sender,
bytes32 salt
) public view returns (address) {
bytes32 deploySalt = keccak256(abi.encode(sender, salt));
return _deployedAddress(bytecode, deploySalt);
}
function _deploy(bytes memory bytecode, bytes32 deploySalt) internal virtual returns (address);
function _deployedAddress(bytes memory bytecode, bytes32 deploySalt) internal view virtual returns (address);
}
It is an abstract contract, so where do the implementations for _deploy and _deployedAddress reside? We can find them in Create3Deployer.
Create3Deployer
contract Create3Deployer is Create3, Deployer {
function _deploy(bytes memory bytecode, bytes32 deploySalt) internal override returns (address) {
return _create3(bytecode, deploySalt);
}
function _deployedAddress(
bytes memory, /* bytecode */
bytes32 deploySalt
) internal view override returns (address) {
return _create3Address(deploySalt);
}
}
OK, so we have a generic Deployer which can get inherited from either this Create3Deployer, or presumably a Create2Deployer too (wen create4? 👀👀). Something like a template method pattern in OOP talk.
Create3 Contract
Now let’s look at that other parent contract, Create3.
contract Create3 {
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
deployed = _create3Address(deploySalt);
if (bytecode.length == 0) revert EmptyBytecode();
if (deployed.isContract()) revert AlreadyDeployed();
if (msg.value > 0) {
deployed.safeNativeTransfer(msg.value);
}
// Deploy using create2
CreateDeploy create = new CreateDeploy{ salt: deploySalt }();
if (address(create) == address(0)) revert DeployFailed();
// Deploy using create
create.deploy(bytecode);
}
function _create3Address(bytes32 deploySalt) internal view returns (address deployed) {
...
}
}
Now we are getting somewhere. The first step seems to be fetching the deterministic CREATE3 deployed address. Then it deploys a CreateDeploy contract. I wonder what this is for… And then finally it calls CreateDeploy::deploy, passing it the initcode of our smart contract.
Address calculation
Let’s look at _create3Address.
contract Create3 {
function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) {
...
}
function _create3Address(bytes32 deploySalt) internal view returns (address deployed) {
address deployer = address(
uint160(uint256(keccak256(abi.encodePacked(hex'ff', address(this), deploySalt, DEPLOYER_BYTECODE_HASH))))
);
deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex'd6_94', deployer, hex'01')))));
}
}
Right. So it calculates two separate addresses. The first is keccak256(0xff ++ address(this) ++ deploySalt ++ DEPLOYER_BYTECODE_HASH).
Look familiar? That looks like a CREATE2 address, using address(this) (the deployer factory), the user-provided salt, and a hardcoded bytecode hash. This indicates that the smart contract is going to deploy the CreateDeploy smart contract using CREATE2.
The next line is keccak256(0xd694 ++ deployer ++ 0x01). This looks like a CREATE deterministic address. It has the predicted CreateDeploy address, followed by 1 (the first nonce). What is that 0xd694? That’s the RLP encoding. Let’s step through it.
First byte = 0xd6. This is the prefix to encode 0x94 ++ deployer ++ 0x01. In other words, we are encoding a list, where the combined length of everything inside the list is 1 + 20 + 1 = 22 == 0x16. Since 22 < 55, this prefix should be 0xc0 + 0x16 = 0xd6.
Second byte = 0x94. This is the prefix to encode deployer. deployer is of length 20. Since 20 < 55, this prefix should be 0x80 + 0x14 = 0x94.
And then the final 0x01 is already encoded as per the “single byte 0 < x < 0x7f” rule.
This is the same RLP encoding as you will see in any Solidity code that predicts a CREATE address.
OK so this address estimation is saying that we are apparently going to, later in this transaction, deploy some CreateDeploy contract using CREATE2. Using that, we will then deploy our contract with CREATE. Let’s look at how this actually happens.
Ephemeral CREATE Factory
The next part of Create3::_create3 is
// Deploy using create2
CreateDeploy create = new CreateDeploy{ salt: deploySalt }();
Just deploying this CreateDeploy contract with CREATE2. That’s where the salt comes in! So we don’t actually even use the salt when deploying our contract. We use it when deploying this ephemeral CreateDeploy factory contract.
Let’s have a look at this contract.
contract CreateDeploy {
function deploy(bytes memory bytecode) external payable {
assembly {
if iszero(create(0, add(bytecode, 32), mload(bytecode))) {
revert(0, 0)
}
}
}
}
Simple. It has a deploy function which uses create to deploy the bytecode provided.
Final Step
Finally, we actually call the CreateDeploy::deploy function.
create.deploy(bytecode);
Summary
CREATE2 has a problem: initcode is part of what determines the address, meaning different constructor args lead to different deployment addresses.
CREATE3 first deploys a single-use factory called CreateDeploy, using CREATE2(hash(0xFF|sender|salt|bytecode), with the user-provided salt.
CreateDeploy’s bytecode never changes. Therefore, assuming the CREATE3 factory’s address is constant, the user-provided salt is the only factor that influences this contract’s address.
CreateDeploy deploys using CREATE, and it’s nonce is always zero (it is ephemeral). Therefore, it always deploys to the same address.
Therefore, the only thing that influences the deployed address is the user-provided salt, unless:
CREATE3factory address is not constant across networks- The
CREATE3factory does some operations on the salt, such as appendingmsg.sender(see A Possible 3rd Parameter)
A Possible 3rd Parameter
Since collisions could easily happen with equivalent salts, many implementations combine the transacation’s sender address with their salt like bytes32 deploySalt = keccak256(abi.encode(msg.sender, salt));.
This isn’t actually a requirement, but maybe should be.