Creating contracts on blockchains that work with the Ethereum Virtual Machine (EVM) involves the use of special instructions called opcodes. Among these instructions:
While the first two are actual opcodes, “create3” is a useful library that acts similarly. This guide will give you a quick overview of what each one does and why they matter.
The create
opcode is the most commonly used contract creation opcode. When contracts are deployed from scripts or other development environments, the create
opcode is the low-level instruction executed by the EVM to deploy and generate a contract address.
A fixed prefix, which is always 0xFF
.
The sender’s address ensures the contract is tied to a specific creator.
A chosen salt value adds uniqueness to the contract address.
The bytecode contains the code of the new contract to be deployed.
By combining these parameters, CREATE2 computes a deterministic address for the new contract, which remains the same even as the blockchain evolves.
CREATE3 is similar to CREATE2 but without including the contract initCode
on the address derivation formula. It can be used to generate deterministic contract addresses that aren’t tied to a specific contract code.
CREATE3 is a way to use CREATE and CREATE2 in combination such that bytecode no longer affects the deployment address. CREATE3 is more expensive than CREATE or CREATE2 (Fixed extra cost of ~55k gas).
//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;
/**
@title A library for deploying contracts EIP-3171 style.
@author Agustin Aguilar <[email protected]>
*/
library Create3 {
error ErrorCreatingProxy();
error ErrorCreatingContract();
error TargetAlreadyExists();
/**
@notice The bytecode for a contract that proxies the creation of another contract
@dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address
0x67363d3d37363d34f03d5260086018f3:
0x00 0x67 0x67XXXXXXXXXXXXXXXX PUSH8 bytecode 0x363d3d37363d34f0
0x01 0x3d 0x3d RETURNDATASIZE 0 0x363d3d37363d34f0
0x02 0x52 0x52 MSTORE
0x03 0x60 0x6008 PUSH1 08 8
0x04 0x60 0x6018 PUSH1 18 24 8
0x05 0xf3 0xf3 RETURN
0x363d3d37363d34f0:
0x00 0x36 0x36 CALLDATASIZE cds
0x01 0x3d 0x3d RETURNDATASIZE 0 cds
0x02 0x3d 0x3d RETURNDATASIZE 0 0 cds
0x03 0x37 0x37 CALLDATACOPY
0x04 0x36 0x36 CALLDATASIZE cds
0x05 0x3d 0x3d RETURNDATASIZE 0 cds
0x06 0x34 0x34 CALLVALUE val 0 cds
0x07 0xf0 0xf0 CREATE addr
*/
bytes internal constant PROXY_CHILD_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3";
// KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE);
bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
/**
@notice Returns the size of the code on a given address
@param _addr Address that may or may not contain code
@return size of the code on the given `_addr`
*/
function codeSize(address _addr) internal view returns (uint256 size) {
assembly { size := extcodesize(_addr) }
}
/**
@notice Creates a new contract with given `_creationCode` and `_salt`
@param _salt Salt of the contract creation, resulting address will be derivated from this value only
@param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
@return addr of the deployed contract, reverts on error
*/
function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) {
return create3(_salt, _creationCode, 0);
}
/**
@notice Creates a new contract with given `_creationCode` and `_salt`
@param _salt Salt of the contract creation, resulting address will be derivated from this value only
@param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address
@param _value In WEI of ETH to be forwarded to child contract
@return addr of the deployed contract, reverts on error
*/
function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) {
// Creation code
bytes memory creationCode = PROXY_CHILD_BYTECODE;
// Get target final address
addr = addressOf(_salt);
if (codeSize(addr) != 0) revert TargetAlreadyExists();
// Create CREATE2 proxy
address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)}
if (proxy == address(0)) revert ErrorCreatingProxy();
// Call proxy with final init code
(bool success,) = proxy.call{ value: _value }(_creationCode);
if (!success || codeSize(addr) == 0) revert ErrorCreatingContract();
}
/**
@notice Computes the resulting address of a contract deployed using address(this) and the given `_salt`
@param _salt Salt of the contract creation, resulting address will be derivated from this value only
@return addr of the deployed contract, reverts on error
@dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01]))
*/
function addressOf(bytes32 _salt) internal view returns (address) {
address proxy = address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex'ff',
address(this),
_salt,
KECCAK256_PROXY_CHILD_BYTECODE
)
)
)
)
);
return address(
uint160(
uint256(
keccak256(
abi.encodePacked(
hex"d6_94",
proxy,
hex"01"
)
)
)
)
);
}
}
contract Child {
function hola() external view returns (string memory) {
return "mundo";
}
}
contract Deployer {
function deployChild() external {
Create3.create3(keccak256(bytes("<my salt>")), type(Child).creationCode);
}
}
contract Child2 {
uint256 meaningOfLife;
address owner;
constructor(uint256 _meaning, address _owner) {
meaningOfLife = _meaning;
owner = _owner;
}
}
contract Deployer2 {
function deployChild() external {
Create3.create3(
keccak256(bytes("<my salt>")),
abi.encodePacked(
type(Child).creationCode,
abi.encode(
42,
msg.sender
)
)
);
}
}
When the goal is to deploy your contracts to the same addresses on multiple blockchains, fewer factors affecting deployment addresses make it easier to achieve such a goal. So in this sense, CREATE3 is better to use than CREATE2.
“Create,” “create2”, and “create3” are essential tools for anyone working with Ethereum contracts. Each one offers a different way to deploy contracts, with “create” being straightforward, “create2” providing predictable contract addresses, and “create3” offering multi-chain compatibility through a library approach.
For hands-on details, check out:
For more on Ethereum development and insights, visit smart-contracts-developer.com and solichain.com. Connect with me on Twitter, LinkedIn, and GitHub.
Also published here.