├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── chain.json ├── contracts ├── Account.sol ├── Factory.sol └── Migrations.sol ├── migrations ├── 1_initial_migration.js └── 2_factory.js ├── package-lock.json ├── package.json ├── test ├── Factory.spec.js ├── test.js └── utils │ └── index.js ├── truffle-config.js └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | build 3 | node_modules 4 | 5 | parity 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (C) 2018 Miguel Mota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: build 2 | 3 | .PHONY: download-parity-mac 4 | download-parity-mac: 5 | rm -rf parity 6 | wget https://releases.parity.io/ethereum/v2.1.4/x86_64-apple-darwin/parity 7 | chmod +x parity 8 | 9 | .PHONY: download-parity-linux 10 | download-parity-linux: 11 | rm -rf parity 12 | wget https://releases.parity.io/ethereum/v2.1.4/x86_64-unknown-linux-gnu/parity 13 | chmod +x parity 14 | 15 | .PHONY: install 16 | install: 17 | npm install 18 | 19 | .PHONY: build 20 | build: 21 | npm run build 22 | 23 | .PHONY: start-parity 24 | start-parity: 25 | ./parity -c dev-insecure --chain chain.json --jsonrpc-cors=all --ws-interface all --ws-origins all --ws-hosts all 26 | 27 | .PHONY: reset-parity 28 | reset-parity: 29 | ./parity -c dev-insecure --chain chain.json db kill 30 | 31 | .PHONY: deploy 32 | deploy: 33 | rm -rf build && npx truffle deploy --network=development --reset 34 | 35 | .PHONY: compile 36 | compile: 37 | npx truffle compile 38 | 39 | .PHONY: test 40 | test: 41 | npx truffle test --reset 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Solidity `CREATE2` example 2 | 3 | > Example of how to use the [`CREATE2`](https://github.com/ethereum/EIPs/pull/1014) opcode released in the [Constantinople](https://github.com/paritytech/parity-ethereum/issues/8427) update for Ethereum. 4 | 5 | ## Tutorial 6 | 7 | These tutorial will show you how to predetermine a smart contract address off-chain and then deploy using `create2` from a smart contract. 8 | 9 | `Factory.sol` - a contract that deploys other contracts using the `create2` opcode: 10 | 11 | ```solidity 12 | pragma solidity >0.4.99 <0.6.0; 13 | 14 | contract Factory { 15 | event Deployed(address addr, uint256 salt); 16 | 17 | function deploy(bytes memory code, uint256 salt) public { 18 | address addr; 19 | assembly { 20 | addr := create2(0, add(code, 0x20), mload(code), salt) 21 | if iszero(extcodesize(addr)) { 22 | revert(0, 0) 23 | } 24 | } 25 | 26 | emit Deployed(addr, salt); 27 | } 28 | } 29 | ``` 30 | 31 | `Account.sol` - the contract to counterfactual instantiate: 32 | 33 | ```solidity 34 | pragma solidity >0.4.99 <0.6.0; 35 | 36 | contract Account { 37 | address public owner; 38 | 39 | constructor(address payable _owner) public { 40 | owner = _owner; 41 | } 42 | 43 | function setOwner(address _owner) public { 44 | require(msg.sender == owner); 45 | owner = _owner; 46 | } 47 | 48 | function destroy(address payable recipient) public { 49 | require(msg.sender == owner); 50 | selfdestruct(recipient); 51 | } 52 | 53 | function() payable external {} 54 | } 55 | ``` 56 | 57 | Create helper functions: 58 | 59 | ```js 60 | // deterministically computes the smart contract address given 61 | // the account the will deploy the contract (factory contract) 62 | // the salt as uint256 and the contract bytecode 63 | function buildCreate2Address(creatorAddress, saltHex, byteCode) { 64 | return `0x${web3.utils.sha3(`0x${[ 65 | 'ff', 66 | creatorAddress, 67 | saltHex, 68 | web3.utils.sha3(byteCode) 69 | ].map(x => x.replace(/0x/, '')) 70 | .join('')}`).slice(-40)}`.toLowerCase() 71 | } 72 | 73 | // converts an int to uint256 74 | function numberToUint256(value) { 75 | const hex = value.toString(16) 76 | return `0x${'0'.repeat(64-hex.length)}${hex}` 77 | } 78 | 79 | // encodes parameter to pass as contract argument 80 | function encodeParam(dataType, data) { 81 | return web3.eth.abi.encodeParameter(dataType, data) 82 | } 83 | 84 | // returns true if contract is deployed on-chain 85 | async function isContract(address) { 86 | const code = await web3.eth.getCode(address) 87 | return code.slice(2).length > 0 88 | } 89 | ``` 90 | 91 | Deploy factory address: 92 | 93 | ```js 94 | const Factory = new web3.eth.Contract(factoryAbi) 95 | const {_address: factoryAddress} = await Factory.deploy({ 96 | data: factoryBytecode 97 | }).send({ 98 | from: '0x303de46de694cc75a2f66da93ac86c6a6eee607e' 99 | }) 100 | 101 | console.log(factoryAddress) // "0xb03F3ED17b679671C9B638f2FCd48ADcE5e26d0e" 102 | ``` 103 | 104 | Now you can compute off-chain deterministically the address of the account contract: 105 | 106 | ```js 107 | // constructor arguments are appended to contract bytecode 108 | const bytecode = `${accountBytecode}${encodeParam('address', '0x262d41499c802decd532fd65d991e477a068e132').slice(2)}` 109 | const salt = 1 110 | 111 | const computedAddr = buildCreate2Address( 112 | factoryAddress, 113 | numberToUint256(salt), 114 | bytecode 115 | ) 116 | 117 | console.log(computedAddr) // "0x45d673256f870c135b2858e593653fb22d39795f" 118 | console.log(await isContract(computedAddr)) // false (not deployed on-chain) 119 | ``` 120 | 121 | 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). 122 | 123 | Let's deploy the account contract using the factory: 124 | 125 | ```js 126 | const factory = new web3.eth.Contract(factoryAbi, factoryAddress) 127 | const salt = 1 128 | const bytecode = `${accountBytecode}${encodeParam('address', '0x262d41499c802decd532fd65d991e477a068e132').slice(2)}` 129 | const result = await factory.methods.deploy(bytecode, salt).send({ 130 | from: account, 131 | gas: 4500000, 132 | gasPrice: 10000000000, 133 | nonce 134 | }) 135 | 136 | 137 | const addr = result.events.Deployed.returnValues.addr.toLowerCase() 138 | console.log(computedAddr == addr) // true (deployed contract address is the same as precomputed address) 139 | console.log(result.transactionHash) // "0x4b0f212af772aab80094b5fe6b5f3f3c544c099d43ce3ca7343c63bbb0776de4" 140 | console.log(addr) // "0x45d673256f870c135b2858e593653fb22d39795f" 141 | console.log(await isContract(computedAddr)) // true (deployed on-chain) 142 | ``` 143 | 144 | Example code found [here](./test/). 145 | 146 | ## Development 147 | 148 | Download Parity binary from [releases](https://github.com/paritytech/parity-ethereum/releases) page (at the time of this writing Parity is only full client that supports the new opcode): 149 | 150 | ```bash 151 | make download-parity-mac 152 | ``` 153 | 154 | Start parity: 155 | 156 | ```bash 157 | make start-parity 158 | ``` 159 | 160 | Compile contracts: 161 | 162 | ```bash 163 | make compile 164 | ``` 165 | 166 | Deploy contracts: 167 | 168 | ```bash 169 | make deploy 170 | ``` 171 | 172 | Test contracts: 173 | 174 | ```bash 175 | make test 176 | ``` 177 | 178 | Note: if using a different mnemonic seed, update the accounts in `chain.json` 179 | 180 | ## Resources 181 | 182 | - [EIP 1014: Skinny CREATE2](https://eips.ethereum.org/EIPS/eip-1014) 183 | 184 | ## Credits 185 | 186 | - [@stanislaw-glogowski](https://github.com/stanislaw-glogowski/CREATE2) for initial implementation example 187 | 188 | ## License 189 | 190 | [MIT](LICENSE) 191 | -------------------------------------------------------------------------------- /chain.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Constantinople (development)", 3 | "engine": { 4 | "instantSeal": null 5 | }, 6 | "params": { 7 | "homesteadTransition": "0x0", 8 | "gasLimitBoundDivisor": "0x0400", 9 | "accountStartNonce": "0x0", 10 | "maximumExtraDataSize": "0x20", 11 | "minGasLimit": "0x1388", 12 | "networkID" : "0x11", 13 | "registrar" : "0x0000000000000000000000000000000000001337", 14 | "eip98Transition": "0xffffffffffffffff", 15 | "eip100bTransition": "0x0", 16 | "eip150Transition": "0x0", 17 | "eip160Transition": "0x0", 18 | "eip161abcTransition": "0x0", 19 | "eip161dTransition": "0x0", 20 | "eip140Transition": "0x0", 21 | "eip211Transition": "0x0", 22 | "eip214Transition": "0x0", 23 | "eip155Transition": "0x0", 24 | "eip658Transition": "0x0", 25 | "eip145Transition": "0x0", 26 | "eip1014Transition": "0x0", 27 | "eip1052Transition": "0x0", 28 | "eip1283Transition": "0x0", 29 | "maxCodeSize": 24576, 30 | "maxCodeSizeTransition": "0x0", 31 | "wasmActivationTransition": "0x0" 32 | }, 33 | "genesis": { 34 | "seal": { 35 | "generic": "0x0" 36 | }, 37 | "difficulty": "0x20000", 38 | "author": "0x0000000000000000000000000000000000000000", 39 | "timestamp": "0x00", 40 | "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", 41 | "extraData": "0x", 42 | "gasLimit": "0x7A1200" 43 | }, 44 | "accounts": { 45 | "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, 46 | "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, 47 | "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, 48 | "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, 49 | "0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } }, 50 | "0000000000000000000000000000000000000006": { "balance": "1", "builtin": { "name": "alt_bn128_add", "activate_at": 0, "pricing": { "linear": { "base": 500, "word": 0 } } } }, 51 | "0000000000000000000000000000000000000007": { "balance": "1", "builtin": { "name": "alt_bn128_mul", "activate_at": 0, "pricing": { "linear": { "base": 40000, "word": 0 } } } }, 52 | "0000000000000000000000000000000000000008": { "balance": "1", "builtin": { "name": "alt_bn128_pairing", "activate_at": 0, "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } }, 53 | "0x303de46de694cc75a2f66da93ac86c6a6eee607e": { "balance": "100000000000000000000" }, 54 | "0x262d41499c802decd532fd65d991e477a068e132": { "balance": "100000000000000000000" }, 55 | "0x51d9ddc02f0d365a62a21c377305acf67e8ecfb5": { "balance": "100000000000000000000" } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /contracts/Account.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.99 <0.6.0; 2 | 3 | contract Account { 4 | address public owner; 5 | 6 | constructor(address payable _owner) public { 7 | owner = _owner; 8 | } 9 | 10 | function setOwner(address _owner) public { 11 | require(msg.sender == owner); 12 | owner = _owner; 13 | } 14 | 15 | function destroy(address payable recipient) public { 16 | require(msg.sender == owner); 17 | selfdestruct(recipient); 18 | } 19 | 20 | function() payable external {} 21 | } 22 | -------------------------------------------------------------------------------- /contracts/Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.99 <0.6.0; 2 | 3 | contract Factory { 4 | event Deployed(address addr, uint256 salt); 5 | 6 | function deploy(bytes memory code, uint256 salt) public { 7 | address addr; 8 | assembly { 9 | addr := create2(0, add(code, 0x20), mload(code), salt) 10 | if iszero(extcodesize(addr)) { 11 | revert(0, 0) 12 | } 13 | } 14 | 15 | emit Deployed(addr, salt); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity >0.4.99 <0.6.0; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | constructor() public { 8 | owner = msg.sender; 9 | } 10 | 11 | modifier restricted() { 12 | if (msg.sender == owner) _; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/2_factory.js: -------------------------------------------------------------------------------- 1 | var Factory = artifacts.require("./Factory.sol") 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Factory); 5 | } 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "solidity-create2-example", 3 | "version": "0.0.1", 4 | "description": "## Instructions", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "lib": "lib", 8 | "test": "test" 9 | }, 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "solc": "^0.5.1", 13 | "truffle": "^5.0.0-beta.2", 14 | "truffle-hdwallet-provider": "0.0.6", 15 | "web3": "^1.0.0-beta.36" 16 | }, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": "", 21 | "license": "MIT" 22 | } 23 | -------------------------------------------------------------------------------- /test/Factory.spec.js: -------------------------------------------------------------------------------- 1 | const Factory = artifacts.require('Factory') 2 | 3 | // TODO: proper tests 4 | 5 | contract('Factory', (accounts) => { 6 | let instance 7 | 8 | beforeEach('setup', async () => { 9 | instance = await Factory.new() 10 | }) 11 | 12 | describe('test deploy', () => { 13 | it('should deploy', async () => { 14 | // TODO 15 | assert.ok(true) 16 | }) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | // TODO: proper tests 2 | 3 | const { 4 | web3, 5 | deployFactory, 6 | deployAccount, 7 | buildCreate2Address, 8 | numberToUint256, 9 | encodeParam, 10 | isContract 11 | } = require('./utils') 12 | 13 | const { abi:accountAbi, bytecode:accountBytecode } = require('../build/contracts/Account.json') 14 | 15 | async function main() { 16 | const factoryAddress = await deployFactory() 17 | const salt = 1 18 | 19 | console.log(factoryAddress) 20 | 21 | const bytecode = `${accountBytecode}${encodeParam('address', '0x303de46de694cc75a2f66da93ac86c6a6eee607e').slice(2)}` 22 | 23 | const computedAddr = buildCreate2Address( 24 | factoryAddress, 25 | numberToUint256(salt), 26 | bytecode 27 | ) 28 | 29 | console.log(computedAddr) 30 | console.log(await isContract(computedAddr)) 31 | 32 | const result = await deployAccount(factoryAddress, salt, '0x303de46de694cc75a2f66da93ac86c6a6eee607e') 33 | 34 | console.log(result.txHash) 35 | console.log(result.address) 36 | 37 | console.log(await isContract(computedAddr)) 38 | } 39 | 40 | main() 41 | -------------------------------------------------------------------------------- /test/utils/index.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const Web3 = require('web3') 3 | const HDWalletProvider = require('truffle-hdwallet-provider') 4 | 5 | // TODO: clean up 6 | 7 | const provider = new HDWalletProvider( 8 | 'wine churn waste cabbage admit security brisk knife swallow fancy rib observe', 9 | 'http://localhost:8545', 10 | ) 11 | 12 | const web3 = new Web3(provider) 13 | 14 | const { abi:factoryAbi, bytecode:factoryBytecode } = require('../../build/contracts/Factory.json') 15 | const { abi:accountAbi, bytecode:accountBytecode } = require('../../build/contracts/Account.json') 16 | 17 | async function deployFactory() { 18 | const Factory = new web3.eth.Contract(factoryAbi) 19 | const {_address: factoryAddress} = await Factory.deploy({ 20 | data: factoryBytecode 21 | }).send({ 22 | from: '0x303de46de694cc75a2f66da93ac86c6a6eee607e' 23 | }) 24 | 25 | return factoryAddress 26 | } 27 | 28 | async function deployAccount (factoryAddress, salt, recipient) { 29 | const factory = new web3.eth.Contract(factoryAbi, factoryAddress) 30 | const account = '0x303de46de694cc75a2f66da93ac86c6a6eee607e' 31 | const nonce = await web3.eth.getTransactionCount(account) 32 | const bytecode = `${accountBytecode}${encodeParam('address', recipient).slice(2)}` 33 | const result = await factory.methods.deploy(bytecode, salt).send({ 34 | from: account, 35 | gas: 4500000, 36 | gasPrice: 10000000000, 37 | nonce 38 | }) 39 | 40 | const computedAddr = buildCreate2Address( 41 | factoryAddress, 42 | numberToUint256(salt), 43 | bytecode 44 | ) 45 | 46 | const addr = result.events.Deployed.returnValues.addr.toLowerCase() 47 | assert.equal(addr, computedAddr) 48 | 49 | return { 50 | txHash: result.transactionHash, 51 | address: addr, 52 | receipt: result 53 | } 54 | } 55 | 56 | function buildCreate2Address(creatorAddress, saltHex, byteCode) { 57 | return `0x${web3.utils.sha3(`0x${[ 58 | 'ff', 59 | creatorAddress, 60 | saltHex, 61 | web3.utils.sha3(byteCode) 62 | ].map(x => x.replace(/0x/, '')) 63 | .join('')}`).slice(-40)}`.toLowerCase() 64 | } 65 | 66 | function numberToUint256(value) { 67 | const hex = value.toString(16) 68 | return `0x${'0'.repeat(64-hex.length)}${hex}` 69 | } 70 | 71 | function encodeParam(dataType, data) { 72 | return web3.eth.abi.encodeParameter(dataType, data) 73 | } 74 | 75 | async function isContract(address) { 76 | const code = await web3.eth.getCode(address) 77 | return code.slice(2).length > 0 78 | } 79 | 80 | module.exports = { 81 | web3, 82 | deployFactory, 83 | deployAccount, 84 | buildCreate2Address, 85 | numberToUint256, 86 | encodeParam, 87 | isContract 88 | } 89 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | }; 19 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | const HDWalletProvider = require('truffle-hdwallet-provider') 16 | 17 | module.exports = { 18 | // See 19 | // to customize your Truffle configuration! 20 | networks: { 21 | development: { 22 | host: 'localhost', 23 | port: 8545, 24 | network_id: '*', // Match any network id 25 | from: '0x303de46de694cc75a2f66da93ac86c6a6eee607e', 26 | provider: new HDWalletProvider( 27 | 'wine churn waste cabbage admit security brisk knife swallow fancy rib observe', 28 | 'http://localhost:8545', 29 | ), 30 | gas: 4500000, 31 | gasPrice: 10000000000, 32 | }, 33 | }, 34 | compilers: { 35 | solc: { 36 | version: '0.5.0-nightly.2018.11.5+commit.88aee34c', 37 | settings: { 38 | optimizer: { 39 | enabled: true, 40 | runs: 200 41 | }, 42 | }, 43 | }, 44 | }, 45 | }; 46 | --------------------------------------------------------------------------------