Notes on EIP-1014: create2 (1) – Martinet Lee
Ethereum Constantinpole update introduced a new operation create2 to facilitate interactions on state channels and also helps on-boarding user to the Ethereum ecosystem. Here I would summarize the technical aspect of create2 opcode, including its EIP specification, implementation in Solidity, and known security issues.
- create2 deploys contract at an address determined by user controlled input and NOT dependent on any state on the blockchain. This allows developers to calculate the address of a contract before it is deployed.
- The inputs used to calculate the address also include the
init_codeof the deployed contract and thus ensures theinit_codeis fixed.init_codeis a piece of code that is executed before contract deployment and EVM would deploy the contract bytecode returned by theinit_code. It is important to note that this DOES NOT guarantee that the contracts that would be deployed on the same address are the same.
link: https://eips.ethereum.org/EIPS/eip-1014
The address of the deployed contract is calculated with the formula below
keccak256( 0xff ++ address ++ salt ++ keccak256(init_code))[12:]
address is 20 bytes, this is the address of the deployer of the new contract.
salt is a 32 bytes input provided by the user.
init_code is a piece of code that executes then returns the byteCode to be deployed on the blockchain. In terms of solidity, the code to be executed is the constructor of the contract and the arguments of the constructor are also included in the init_code.
To use create2 in solidity, one needs to use assembly. According to the documentation:
create2(v, p, n, s)
create new contract with code mem[p…(p+n)) at
address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and
send v wei and
return the new address, where 0xff is a 1 byte value,
this is the current contract’s address as a 20 byte value and
s is a big-endian 256-bit value
The following function can be used to deploy arbitrary contract
function deployArbitrary(bytes memory code, uint256 salt) public
emit DeployedArbitrary(addr, salt);
}
The code argument here is the compiled bytecode along with the constructor arguments. The bytecode (without the constructor arguments) can be found via compiling with solc with the --bin option. If you’re using Truffle, the compiled bytecode can also be found in the json file under build.
Here we will get into more details by providing an example. Suppose that we want to deploy the following contract anOwnableContract and lock the owner.
pragma solidity ^0.5.10;contract anOwnableContract
}
e.g. ./build/contracts/anOwnableContract.json. The bytecode here is useful to develop tests.
The bytecode of our example contract (again, this is without the constructor arguments) is
0x608060405234801561001057600080fd5b506040516101623803806101628339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060cf806100936000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80638da5cb5b14602d575b600080fd5b60336075565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a72305820541511aabbdf4bf16726ae3426b3cdd4d99008af7121d008e0a8ddb9ce7074e864736f6c634300050a0032
We could import the bytecode in the tests using the following statement.
const = require('../build/contracts/anOwnableContract.json');
After obtaining the bytecode, we need to encode the arguments and append it after the bytecode. Suppose that we want to pass in 0x262d41499c802decd532fd65d991e477a068e132 as the owner of the contract, this is how you would encode the parameter using web3 api:
web3.eth.abi.encodeParameter('address', '0x262d41499c802decd532fd65d991e477a068e132').slice(2)
slice(2) is used to get rid of 0x prefix.
The complete init_code that we need to pass in can be obtained as follows:
const init_bytecode = `$$`;
and the bytecode with the constructor argument is:
0x608060405234801561001057600080fd5b506040516101623803806101628339818101604052602081101561003357600080fd5b8101908080519060200190929190505050806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055505060cf806100936000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c80638da5cb5b14602d575b600080fd5b60336075565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000809054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a72305820541511aabbdf4bf16726ae3426b3cdd4d99008af7121d008e0a8ddb9ce7074e864736f6c634300050a0032000000000000000000000000262d41499c802decd532fd65d991e477a068e132
Actually Deploy the contract
Now we could deploy the contract by calling the deployArbitrary function and provide the argument there.
let mySalt = 12345;
let result = await cFactoryContract.deployArbitrary(init_bytecode, mySalt);
console.log("Deployed address: " + result.logs[0].args.addr);
Calculate the address of the contract
The whole point of create2 is that it is possible to calculate the address of the contract offline, even before it is being deployed.
function calCreate2Address(creatorAddress, salt, init_code)// converts int to uint256 format
function numberToUint256(value) $`;
return returnMe;
}
The calCreate2Address is crafted according to the specificiation, .slice(2) above is used to get rid of 0x in the outputs. numberToUint256 is used to covert an int to its hex representation in 32 bytes, with 0 as prefix.
To calculate the address, one can simply do the following, where cFactoryContract is the contract with the deployArbitrary function, mySalt and init_bytecode are specified as before.
calCreate2Address(cFactoryContract.address, mySalt, init_bytecode)
The basic example illustrates how to use create2 opcode to deploy a fixed contract with fixed constructor arguments and show that it is possible to calculate the address offline or before its deployment. In this case, the contract that would be deployed will always be anOwnableContract with the owner always being locked to a specific address.
While the example above always deploys the same contract with the same owner, it is important to note that this is not generalizable: using create2 doesn’t guarantee that contracts that would be deployed to the same address has the same bytecode. There are other interesting cases that I would like to follow-up with examples to show the edge cases of create2:
- If the contract contains self-destruction code, it is possible to be self-destructed and redeployed to the same address again
- Deploy different contract bytecode to the same contract address using create2
- How to use create2 when the contract deployed are following a proxy pattern.
When playing with the code, I’ve encountered some problem in crafting a function that only deploys a specific contract. The following is the contract for my cFactory, which contains the deployArbitrary.
pragma solidity 0.5.10;contract cFactory
emit DeployedArbitrary(addr, salt);
} event DeployedSpecific( address addr, uint256 salt ); function deploySpecific(uint256 salt) public
emit DeployedSpecific(addr, salt);
}
}
The problem here is that, deploySpecific seems to fail all the time, emitting DeployedSpecific event with addr being 0x0000000000000000000000000000000000000000. I don’t really see the difference between providing deployArbitrary with the same bytecode and the deploySpecific.
Published at Mon, 06 Jan 2020 03:26:36 +0000
{flickr|100|campaign}
