Example of how to use the
CREATE2
opcode released in the Constantinople update for Ethereum.
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:
// deterministically computes the smart contract address given
// the account the will deploy the contract (factory contract)
// the salt as uint256 and the contract bytecode
function buildCreate2Address(creatorAddress, saltHex, byteCode) {
return `0x${web3.utils.sha3(`0x${[
'ff',
creatorAddress,
saltHex,
web3.utils.sha3(byteCode)
].map(x => x.replace(/0x/, ''))
.join('')}`).slice(-40)}`.toLowerCase()
}
// converts an int to uint256
function numberToUint256(value) {
const hex = value.toString(16)
return `0x${'0'.repeat(64-hex.length)}${hex}`
}
// encodes parameter to pass as contract argument
function encodeParam(dataType, data) {
return web3.eth.abi.encodeParameter(dataType, data)
}
// returns true if contract is deployed on-chain
async function isContract(address) {
const code = await web3.eth.getCode(address)
return code.slice(2).length > 0
}
Deploy factory address:
const Factory = new web3.eth.Contract(factoryAbi)
const {_address: factoryAddress} = await Factory.deploy({
data: factoryBytecode
}).send({
from: '0x303de46de694cc75a2f66da93ac86c6a6eee607e'
})
console.log(factoryAddress) // "0xb03F3ED17b679671C9B638f2FCd48ADcE5e26d0e"
Now you can compute off-chain deterministically the address of the account contract:
// constructor arguments are appended to contract bytecode
const bytecode = `${accountBytecode}${encodeParam('address', '0x262d41499c802decd532fd65d991e477a068e132').slice(2)}`
const salt = 1
const computedAddr = buildCreate2Address(
factoryAddress,
numberToUint256(salt),
bytecode
)
console.log(computedAddr) // "0x45d673256f870c135b2858e593653fb22d39795f"
console.log(await isContract(computedAddr)) // false (not deployed on-chain)
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 factory = new web3.eth.Contract(factoryAbi, factoryAddress)
const salt = 1
const bytecode = `${accountBytecode}${encodeParam('address', '0x262d41499c802decd532fd65d991e477a068e132').slice(2)}`
const result = await factory.methods.deploy(bytecode, salt).send({
from: account,
gas: 4500000,
gasPrice: 10000000000,
nonce
})
const addr = result.events.Deployed.returnValues.addr.toLowerCase()
console.log(computedAddr == addr) // true (deployed contract address is the same as precomputed address)
console.log(result.transactionHash) // "0x4b0f212af772aab80094b5fe6b5f3f3c544c099d43ce3ca7343c63bbb0776de4"
console.log(addr) // "0x45d673256f870c135b2858e593653fb22d39795f"
console.log(await isContract(computedAddr)) // true (deployed on-chain)
Example code found here.
Download Parity binary from releases page (at the time of this writing Parity is only full client that supports the new opcode):
make download-parity-mac
Start parity:
make start-parity
Compile contracts:
make compile
Deploy contracts:
make deploy
Test contracts:
make test
Note: if using a different mnemonic seed, update the accounts in chain.json
- @stanislaw-glogowski for initial implementation example