Skip to main content

Create2

CREATE2 is not an arbitrary name — it is a dedicated contract-deployment opcode introduced by Ethereum in EIP-1014 (the Constantinople upgrade in 2019). With CREATE, the resulting address is unpredictable (unless you know the deployer's nonce), so you cannot hardcode the address in advance. The contract address is determined by keccak256(rlp(sender, nonce)), and is independent of the contract's code. With CREATE2, the address can be computed ahead of time: once the deployer + salt + init_code are fixed, the contract address is determined and independent of when deployment happens. Official docs: https://docs.soliditylang.org/zh-cn/v0.8.24/control-structures.html#create2

Two Deployment Approaches

Using Solidity directly: deploy via the new keyword, with an additional {salt} parameter compared to plain CREATE

Returns the contract type; salt is a bytes32.

D d = new D{salt: salt}(arg);

Yul Inline Assembly

Returns an address; salt is likewise a bytes32.

  assembly {
pair := create2(
0, // the amount of wei to send
add(bytecode, 32), // start position of the bytecode
mload(bytecode), // length of the bytecode
salt // salt
)
}

In Solidity, the memory layout of bytes memory is: the first 32 bytes hold the length of the data, and the actual data begins right after those 32 bytes.

| offset → | 0x00 ... 0x1f | 0x20 ...     |
| content | length (n) | actual byte data |

So we end up with:

create2(
v,
p, // the position of the actual data, which is the bytecode position plus 32 — i.e. add(bytecode, 32)
n, // the length to read; loading bytecode directly, the first thing read is exactly the length
s
)

Predicting the Address

bytes32 hash = keccak256(abi.encodePacked(
bytes1(0xff), // a fixed single byte
address(this), // the current contract's address
salt, // the bytes32 salt
keccak256(bytecode) // the hash of the bytecode
));
address predictedAddress = address(uint160(uint256(hash)));

bytecode

This is the contract's creation bytecode combined with the constructor arguments.

bytes memory bytecode = abi.encodePacked(type(Deployed).creationCode,abi.encode(arg));

Obtaining a Contract's Creation Bytecode

bytes memory bytecode = type(Deployed).creationCode; 

Related links: https://docs.soliditylang.org/zh-cn/v0.8.24/cheatsheet.html#index-9
https://docs.soliditylang.org/zh-cn/v0.8.24/units-and-global-variables.html#meta-type

Full Example

// SPDX-License-Identifier: GPL-3.0
pragma solidity ^0.8.24;
contract Deployed {
uint public x;
constructor(uint a) {
x = a;
}
}

contract C {

function createDSalted(uint salt, uint arg) public {

bytes32 s = bytes32(salt);
bytes memory bytecode = abi.encodePacked(
type(Deployed).creationCode,
abi.encode(arg)
);

bytes32 hash = keccak256(abi.encodePacked(
bytes1(0xff),
address(this),
s,
keccak256(bytecode)
));
address predictedAddress = address(uint160(uint256(hash)));

// Deploy using Solidity's new + {salt:salt}
// Deployed deployed = new Deployed{salt: bytes32(salt)}(arg);
// require(predictedAddress == address(deployed));

// ---------
// Deploy using create2 in inline assembly
address deployed;
assembly {
deployed := create2(0, add(bytecode, 32), mload(bytecode), s)
}
require(deployed != address(0), "CREATE2 failed");
require(predictedAddress == deployed);
}
}
📢 Share this article