Use Universal Factory to choose the address of your contracts anywhere!
Universal Factory is inspired by EIP-2470 Singleton Factory and EIP-3171 CREATE3 OPCODE, with an additional feature that allows the contract constructor to access arguments without including it in the bytecode, this way custom arguments can be provided and immutables can be set without influencing the final CREATE2
address.
When creating a contract, Universal Factory caches the provided arguments
, salt
, msg.sender
, and other relevant info locally. Then make it available to the contract constructor through the context()
method. The caching mechanism depends on the EVM version:
- For
cancun
it uses theTLOAD
andTSTORE
opcodes from EIP-1153 Transient Storage (~100 gas per word). - For
shanghai
it uses theSLOAD
andSSTORE
opcodes (~2900 gas per word), plus it make sure all values stored are non-zero, to guarantee an low and constant gas cost (see SSTORE gas calculation for more details).- Context data storage slots are initialized with non-zero values in the Universal Factory constructor.
msg.value
is only stored when greater than zero.salt
is XOR with context data being stored/loaded.arguments
are XOR with keccak256(arguments) before being stored/loaded.- Obs:
arguments
slots aren't initialized with non-zero, but once initialized, is impossible to set it back to zero again.
- The contract automatically detects the EVM version, and will use EIP-1153 if available.
For examples on how to use the Universal Factor, see the test/examples folder.
function create2(bytes32 salt, bytes calldata creationCode) external payable returns (address);
function create2(bytes32 salt, bytes calldata creationCode, bytes calldata arguments)
external
payable
returns (address);
function create2(bytes32 salt, bytes calldata creationCode, bytes calldata arguments, bytes calldata callback)
external
payable
returns (address);
salt
the salt of the contract creation, this value affect the resulting address.creationCode
Creation code (constructor) of the contract to be deployed, this value affect the resulting address.arguments
data that will be available atContext.data
, this field doesn't affect the resulting address.callback
callback called after create the contract, this field doesn't affect the resulting address.- Obs: when using
create2(bytes32,bytes,bytes,bytes)
method, the callback is always called, even if the callback is empty.
- Obs: when using
The address of a contracts deployed with CREATE2
can be deterministically computed as:
function computeCreate2Address(bytes32 salt, bytes memory creationCode) external pure returns (address contractAddress) {
bytes32 creationCodeHash = keccak256(creationCode);
bytes32 create2hash = keccak256(abi.encodePacked(
hex"ff0000000000001c4bf962df86e38f0c10c7972c6e",
salt,
creationCodeHash
));
contractAddress = address(uint160(uint256(create2hash)));
}
function create3(bytes32 salt, bytes calldata creationCode) external payable returns (address);
function create3(bytes32 salt, bytes calldata creationCode, bytes calldata arguments)
external
payable
returns (address);
function create3(bytes32 salt, bytes calldata creationCode, bytes calldata arguments, bytes calldata callback)
external
payable
returns (address);
Works the same way as CREATE2, except the resulting address is derived differently:
creationCode
doesn't influence the resulting address.msg.sender
or deployer address influence the resulting address.
The address of a contracts deployed with CREATE3
can be deterministically computed as:
function computeCreate3Address(address deployer, bytes32 salt) external pure returns (address contractAddress) {
bytes32 create3salt = keccak256(abi.encodePacked(deployer, salt));
bytes32 create2hash = keccak256(abi.encodePacked(
hex"ff0000000000001c4bf962df86e38f0c10c7972c6e",
create3salt,
hex"0281a97663cf81306691f0800b13a91c4d335e1d772539f127389adae654ffc6"
));
address create3proxy = address(uint160(uint256(create2hash)));
bytes32 create3hash = keccak256(abi.encodePacked(hex"d694", create3proxy, uint8(0x01)));
contractAddress = address(uint160(uint256(create3hash)));
}
Deployments Universal Factory
The Universal Factory is already available in 8 blockchains and 7 testnets at address 0x0000000000001C4Bf962dF86e38F0c10c7972C6E
:
- Polygon zkEVM
- Gnosis
- Optimism
- Moonbean
- Moonriver
- Astar zkEVM
- Shiden
If you are missing some network, please open an issue.
Once properly audited and reviewed, this contract is going to be deployed using the keyless deployment method—also known as Nick’s method—which relies on a single-use address. (See Nick’s article for more details).
This method works as follows:
-
Generate a transaction which deploys the contract from a new random account.
- This transaction MUST NOT use EIP-155 in order to work on any chain.
- This transaction MUST have a relatively high gas price to be deployed on any chain. In this case, it is going to be
200 Gwei
.
-
Set the v, r, s of the transaction signature to the following values:
v: 27,
r: 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
s: 0xdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef'
Those r
and s
values—made of a repeating pattern of deadbeef
’s—are predictable “random numbers” generated deterministically by a human.
- We recover the sender of this transaction, i.e., the single-use deployment account.
Thus we obtain an account that can broadcast that transaction, but we also have the warranty that nobody knows the private key of that account.
-
Send exactly
1 ether
to this single-use deployment account. -
Broadcast the deployment transaction.
You can also contribute to this repo in a number of ways, including.
- Asking questions
- Request features (will be analysed)
- Giving feedback
- Reporting bugs or vulnerabilities.
This project is released under MIT.