Understanding CREATE3
What is CREATE3?
CREATE3
is an emerging pattern in smart contract development that enhances the deployment process. It builds upon the deterministic address generation mechanism of CREATE2
, introducing even greater flexibility and predictability. While not an opcode officially supported by the Ethereum Virtual Machine (EVM) at the time of writing, CREATE3
can be implemented using Solidity and existing EVM features.
Why CREATE3?
The primary innovation of CREATE3
is its ability to deploy contracts with deterministic addresses across multiple deployment transactions and even across chains. This is achieved by encoding unique identifiers (like salts) with the bytecode, making contract addresses predictable without relying on the deployer's nonce.
How CREATE3 Works
The CREATE3
pattern leverages a combination of the deployer address, a nonce, and a unique salt to generate a deterministic contract address. Here’s a basic outline of how it works:
- Deployer Address: The address of the account deploying the contract.
- Nonce: A counter that increments with each contract deployment.
- Salt: A unique value provided by the developer to ensure uniqueness.
Benefits of CREATE3
- Cross-Chain Consistency: Contract addresses generated using
CREATE3
remain deterministic across multiple Ethereum-compatible chains, facilitating seamless multi-chain deployments. - Deployment Flexibility: Contracts can be deployed in any order while maintaining predictable addresses, eliminating dependency on the deployer's nonce.
- Precomputed Addresses: Developers can precompute and verify contract addresses before deployment, enhancing transparency and reducing errors.
- Gas Optimization: With
CREATE3
, contracts can be efficiently redeployed by reusing the same deterministic address, saving gas in scenarios where updates are required.
Use Cases for CREATE3
- Factory Contracts
Deploy multiple contracts with predictable addresses, simplifying deployment pipelines. - Multi-Chain Deployment
Deploy contracts with the same address across Ethereum-compatible chains, making cross-chain dApps more robust. - Upgradeability
Redeploy contracts at a known address without modifying upstream contracts. - Testing Environments
Precompute addresses for mock contracts to simplify testing setups.
Implementation
CREATE3.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.0;
3
4library CREATE3 {
5
6 //--------------------------------------------------------------------------------//
7 // Opcode | Opcode + Arguments | Description | Stack View //
8 //--------------------------------------------------------------------------------//
9 // 0x36 | 0x36 | CALLDATASIZE | size //
10 // 0x3d | 0x3d | RETURNDATASIZE | 0 size //
11 // 0x3d | 0x3d | RETURNDATASIZE | 0 0 size //
12 // 0x37 | 0x37 | CALLDATACOPY | //
13 // 0x36 | 0x36 | CALLDATASIZE | size //
14 // 0x3d | 0x3d | RETURNDATASIZE | 0 size //
15 // 0x34 | 0x34 | CALLVALUE | value 0 size //
16 // 0xf0 | 0xf0 | CREATE | newContract //
17 //--------------------------------------------------------------------------------//
18 // Opcode | Opcode + Arguments | Description | Stack View //
19 //--------------------------------------------------------------------------------//
20 // 0x67 | 0x67XXXXXXXXXXXXXXXX | PUSH8 bytecode | bytecode //
21 // 0x3d | 0x3d | RETURNDATASIZE | 0 bytecode //
22 // 0x52 | 0x52 | MSTORE | //
23 // 0x60 | 0x6008 | PUSH1 08 | 8 //
24 // 0x60 | 0x6018 | PUSH1 18 | 24 8 //
25 // 0xf3 | 0xf3 | RETURN | //
26 //--------------------------------------------------------------------------------//
27
28 uint256 private constant PROXY_BYTECODE = 0x67363d3d37363d34f03d5260086018f3;
29
30 bytes32 private constant PROXY_BYTECODE_HASH =
31 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f;
32
33 function create3(bytes32 salt, bytes memory creationCode) internal returns (address instance) {
34 return create3(salt, creationCode, 0);
35 }
36
37 function create3(
38 bytes32 salt,
39 bytes memory creationCode,
40 uint256 value
41 ) internal returns (address instance) {
42 // compute target final address
43 instance = addressOf(salt);
44
45 assembly ("memory-safe") {
46 // verify that the computed address is not a contract yet
47 if iszero(iszero(extcodesize(instance))) {
48 mstore(0x00, 0xcd43efa1) // TargetAlreadyExists()
49 revert(0x1c, 0x04)
50 }
51
52 // create proxy with CREATE2
53
54 mstore(0x00, PROXY_BYTECODE)
55
56 let proxy := create2(0x00, 0x10, 0x10, salt)
57
58 if iszero(proxy) {
59 mstore(0x00, 0xd49e7d74) // ProxyCreationFailed()
60 revert(0x1c, 0x04)
61 }
62
63 mstore(0x14, proxy)
64 mstore(0x00, 0xd694)
65 mstore8(0x34, 0x01)
66
67 instance := keccak256(0x1e, 0x17)
68
69 // call proxy with final init code
70 if iszero(
71 and(
72 iszero(iszero(extcodesize(instance))), // the instance must be a deployed contract by now
73 call(gas(), proxy, value, add(creationCode, 0x20), mload(creationCode), 0x00, 0x00)
74 )
75 ) {
76 mstore(0x00, 0xa28c2473) // ContractCreationFailed()
77 revert(0x1c, 0x04)
78 }
79 }
80 }
81
82 function addressOf(bytes32 salt) internal view returns (address instance) {
83 return addressOf(salt, address(this));
84 }
85
86 function addressOf(bytes32 salt, address deployer) internal pure returns (address instance) {
87 assembly ("memory-safe") {
88 let ptr := mload(0x40)
89
90 // compute the address of the proxy to be deployed
91 mstore(0x00, deployer)
92 mstore8(0x0b, 0xff)
93 mstore(0x20, salt)
94 mstore(0x40, PROXY_BYTECODE_HASH)
95 mstore(0x14, keccak256(0x0b, 0x55))
96
97 // compute the address of the contract to be deployed by the proxy above
98 mstore(0x40, ptr)
99 mstore(0x00, 0xd694)
100 mstore8(0x34, 0x01)
101
102 instance := keccak256(0x1e, 0x17)
103 }
104 }
105}
106
Create3Factory.sol
1// SPDX-License-Identifier: MIT
2pragma solidity ^0.8.0;
3
4contract Create3Factory {
5 using CREATE3 for bytes32;
6
7 function deploy(bytes32 salt, bytes memory creationCode)
8 external
9 payable
10 override
11 returns (address instance)
12 {
13 // hash salt with the address of the deployer to give each deployer its own namespace
14 assembly ("memory-safe") {
15 let ptr := mload(0x40)
16
17 mstore(ptr, shl(0x60, caller()))
18 mstore(add(ptr, 0x14), salt)
19 salt := keccak256(ptr, 0x34)
20 }
21
22 return salt.create3(creationCode, msg.value);
23 }
24
25 function computeAddress(bytes32 salt, address deployer)
26 external
27 view
28 override
29 returns (address instance)
30 {
31 // hash salt with the address of the deployer to give each deployer its own namespace
32 assembly ("memory-safe") {
33 let ptr := mload(0x40)
34
35 mstore(ptr, shl(0x60, caller()))
36 mstore(add(ptr, 0x14), salt)
37 salt := keccak256(ptr, 0x34)
38 }
39
40 return salt.addressOf(deployer);
41 }
42}
Conclusion
The CREATE3
pattern represents a significant leap forward in deterministic contract deployment, offering flexibility and predictability beyond what CREATE2
provides. While not yet an EVM opcode, it can be implemented effectively in Solidity, enabling developers to build more robust and scalable systems.
As the Ethereum ecosystem evolves, patterns like CREATE3
pave the way for innovation in smart contract engineering. By adopting these advanced techniques, developers can create more secure, efficient, and versatile decentralized applications.