
Solidity CREATE2 Deployer
This library is a minimal utility for deploying ethereum contracts at detereministic addresss using CREATE2. It allows for contracts to be deployed at the same address on all networks.
CREATE2 opcode was released in the Constantinople update for Ethereum.
Example Usage
const {
ethers,
deployContract,
deployFactory,
getCreate2Address,
isDeployed
} = require("solidity-create2-deployer");
const salt = "hello";
const bytecode = "0x...";
const privateKey = "0x...";
const constructorTypes = ["address", "uint256", "..."];
const constructorArgs = ["0x...", "123...", "..."];
const provider = ethers.getDefaultProvider();
const signer = new ethers.Wallet(privateKey, provider);
const computedAddress = getCreate2Address({
salt: salt,
contractBytecode: bytecode,
constructorTypes: constructorTypes,
constructorArgs: constructorArgs
});
const { txHash, address, receipt } = await deployContract({
salt: salt,
contractBytecode: bytecode,
constructorTypes: constructorTypes,
constructorArgs: constructorArgs,
signer: signer
});
const success = await isDeployed(address, provider);
const factoryAddress = await deployFactory(provider);
Caveats
Contracts deployed using this library need to follow these guidelines:
msg.sender cannot be used in the constructor as it will refer to the factory contract.
tx.origin should not bee used in the constructor as the deploy transaction can be front-run.
- In order to produce a deterministic address on all networks, the salt and constructor parameters must be the same.
API Documentation
Tutorial
These tutorial will show you how to predetermine a smart contract address off-chain and then deploy using create2 from a smart contract.
Factory.sol - a contract that deploys other contracts using the create2 opcode:
pragma solidity >0.4.99 <0.6.0;
contract Factory {
event Deployed(address addr, uint256 salt);
function deploy(bytes memory code, uint256 salt) public {
address addr;
assembly {
addr := create2(0, add(code, 0x20), mload(code), salt)
if iszero(extcodesize(addr)) {
revert(0, 0)
}
}
emit Deployed(addr, salt);
}
}
Account.sol - the contract to counterfactual instantiate:
pragma solidity >0.4.99 <0.6.0;
contract Account {
address public owner;
constructor(address payable _owner) public {
owner = _owner;
}
function setOwner(address _owner) public {
require(msg.sender == owner);
owner = _owner;
}
function destroy(address payable recipient) public {
require(msg.sender == owner);
selfdestruct(recipient);
}
function() payable external {}
}
Create helper functions:
function buildCreate2Address(creatorAddress, saltHex, byteCode) {
return `0x${ethers.utils
.keccak256(
`0x${["ff", creatorAddress, saltHex, ethers.utils.keccak256(byteCode)]
.map(x => x.replace(/0x/, ""))
.join("")}`
)
.slice(-40)}`.toLowerCase();
}
function numberToUint256(value) {
const hex = value.toString(16);
return `0x${"0".repeat(64 - hex.length)}${hex}`;
}
function encodeParam(dataType, data) {
const abiCoder = ethers.utils.defaultAbiCoder;
return abiCoder.encode([dataType], [data]);
}
async function isContract(address) {
const code = await ethers.provider.getCode(address);
return code.slice(2).length > 0;
}
Now you can compute off-chain deterministically the address of the account contract:
const bytecode = `${accountBytecode}${encodeParam(
"address",
"0x262d41499c802decd532fd65d991e477a068e132"
).slice(2)}`;
const salt = 1;
const computedAddr = buildCreate2Address(
factoryAddress,
numberToUint256(salt),
bytecode
);
console.log(computedAddr);
console.log(await isContract(computedAddr));
You can send eth to the precomputed contract address 0x45d673256f870c135b2858e593653fb22d39795f even though it's not deployed. Once there's eth in the contract you can deploy the contract and have the funds sent to a different address if you wish. CREATE2 is useful because you don't need to deploy a new contract on-chain for new users; you or anyone can deploy the contract only once there's already funds in it (which the contract can have refund logic for gas).
Let's deploy the account contract using the factory:
const signer = (await ethers.getSigners())[0];
const factory = new ethers.Contract(factoryAddress, factoryAbi, signer);
const salt = 1;
const bytecode = `${accountBytecode}${encodeParam(
"address",
"0x262d41499c802decd532fd65d991e477a068e132"
).slice(2)}`;
const result = await (await factory.deploy(bytecode, salt)).wait();
const addr = result.events[0].args.addr.toLowerCase();
console.log(computedAddr == addr);
console.log(result.transactionHash);
console.log(addr);
console.log(await isContract(computedAddr));
Example code found here.
Development
Install dependencies:
yarn
Test contracts:
yarn test
Resources
Credits
License
MIT