├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ └── tests.yml ├── .gitignore ├── .prettierignore ├── .solcover.js ├── .solhint.json ├── .solhintignore ├── README.md ├── contracts ├── Create3.sol └── test_utils │ ├── Create3Imp.sol │ └── OgCreates.sol ├── hardhat.config.js ├── package.json ├── scripts └── plot.js ├── test └── create3-test.js └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage 5 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | es2021: true, 5 | mocha: true, 6 | node: true 7 | }, 8 | extends: [ 9 | 'standard', 10 | 'plugin:node/recommended' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 12 14 | }, 15 | overrides: [ 16 | { 17 | files: ['hardhat.config.js'], 18 | globals: { task: true } 19 | }, 20 | { 21 | files: ['scripts/**'], 22 | rules: { 'no-process-exit': 'off' } 23 | }, 24 | { 25 | files: ['hardhat.config.js', 'scripts/**', 'test/**'], 26 | rules: { 27 | 'node/no-unpublished-require': 'off', 28 | semi: [2, 'never'], 29 | 'node/no-missing-require': 'off' 30 | } 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/tests.yml: -------------------------------------------------------------------------------- 1 | 2 | on: [push] 3 | 4 | name: tests 5 | 6 | jobs: 7 | install: 8 | name: Install dependencies 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - uses: actions/setup-node@v1 13 | with: 14 | node-version: '14.x' 15 | - uses: actions/cache@master 16 | id: yarn-cache 17 | with: 18 | path: | 19 | node_modules 20 | */*/node_modules 21 | key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/yarn.lock') }} 22 | - run: yarn install --network-concurrency 1 23 | if: ${{ steps.yarn-cache.outputs.cache-hit != 'true' }} 24 | 25 | lint: 26 | name: Run lint 27 | runs-on: ubuntu-latest 28 | needs: [install] 29 | steps: 30 | - uses: actions/checkout@v1 31 | - uses: actions/setup-node@v1 32 | with: 33 | node-version: '14.x' 34 | - uses: actions/cache@master 35 | id: yarn-cache 36 | with: 37 | path: | 38 | node_modules 39 | */*/node_modules 40 | key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/yarn.lock') }} 41 | - run: yarn lint 42 | 43 | tests: 44 | name: Run tests 45 | runs-on: ubuntu-latest 46 | needs: [install] 47 | steps: 48 | - uses: actions/checkout@v1 49 | - uses: actions/setup-node@v1 50 | with: 51 | node-version: '14.x' 52 | - uses: actions/cache@master 53 | id: yarn-cache 54 | with: 55 | path: | 56 | node_modules 57 | */*/node_modules 58 | key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/yarn.lock') }} 59 | - run: yarn test 60 | 61 | build: 62 | name: Run build 63 | runs-on: ubuntu-latest 64 | needs: [install] 65 | steps: 66 | - uses: actions/checkout@v1 67 | - uses: actions/setup-node@v1 68 | with: 69 | node-version: '14.x' 70 | - uses: actions/cache@master 71 | id: yarn-cache 72 | with: 73 | path: | 74 | node_modules 75 | */*/node_modules 76 | key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/yarn.lock') }} 77 | - run: yarn build 78 | - uses: actions/upload-artifact@v2 79 | with: 80 | name: artifacts 81 | path: artifacts 82 | 83 | coverage: 84 | name: Run coverage 85 | runs-on: ubuntu-latest 86 | needs: [install] 87 | steps: 88 | - uses: actions/checkout@v1 89 | - uses: actions/setup-node@v1 90 | with: 91 | node-version: '14.x' 92 | - uses: actions/cache@master 93 | id: yarn-cache 94 | with: 95 | path: | 96 | node_modules 97 | */*/node_modules 98 | key: ${{ runner.os }}-install-${{ hashFiles('**/package.json', '**/yarn.lock') }} 99 | - run: yarn coverage 100 | - uses: actions/upload-artifact@v2 101 | with: 102 | name: test-coverage 103 | path: coverage -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .env 3 | 4 | #Hardhat files 5 | cache 6 | artifacts 7 | 8 | cost.csv 9 | 10 | coverage.json 11 | coverage 12 | 13 | config/prod.env 14 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | artifacts 3 | cache 4 | coverage* 5 | gasReporterOutput.json 6 | -------------------------------------------------------------------------------- /.solcover.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | skipFiles: ['test_utils/'] 3 | } 4 | -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "solhint:recommended", 3 | "rules": { 4 | "compiler-version": ["error", "^0.8.0"], 5 | "func-visibility": ["warn", { "ignoreConstructors": true }] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.solhintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CREATE3 2 | 3 | [![tests](https://github.com/0xsequence/create3/actions/workflows/tests.yml/badge.svg)](https://github.com/0xsequence/create3/actions/workflows/tests.yml) 4 | [![License: Unlicense](https://img.shields.io/badge/license-Unlicense-blue.svg)](http://unlicense.org/) 5 | 6 | 7 | This contract library enables EVM contract creation in a similar way to CREATE2, but without including the contract `initCode` on the address derivation formula. It can be used to generate deterministic contract addresses that aren't tied to a specific contract code. 8 | 9 | [EIP-3171](https://github.com/ethereum/EIPs/pull/3171) aims to implement this funcionality natively, in the meantime this library should emulate the same behavior with a small cost overhead. 10 | 11 | ### Features 12 | 13 | - Deterministic contract address based on `msg.sender` + salt 14 | - Same contract addresses on different EVM networks 15 | - Lightweight and without clutter 16 | - Supports any EVM compatible chain with support for CREATE2 17 | - Payable contract creation (forwarded to child contract) 18 | - Constructors supported 19 | - Standard contract init code (aka, Etherscan verification supported) 20 | 21 | ### Limitations 22 | 23 | - More expensive than CREATE or CREATE2 (Fixed extra cost of ~55k gas) 24 | 25 | ## Usage 26 | 27 | Call the `create3` method on the `Create3` library, provide the contract `creationCode` and a salt. Different contract codes will result on the same address as long as the same salt is provided. 28 | 29 | ### Install 30 | 31 | `yarn add https://github.com/0xsequence/create3` 32 | 33 | or 34 | 35 | `npm install --save https://github.com/0xsequence/create3` 36 | 37 | ### Contract example 38 | 39 | ```solidity 40 | //SPDX-License-Identifier: Unlicense 41 | pragma solidity ^0.8.0; 42 | 43 | import "@0xsequence/create3/contracts/Create3.sol"; 44 | 45 | 46 | contract Child { 47 | function hola() external view returns (string) { 48 | return "mundo"; 49 | } 50 | } 51 | 52 | contract Deployer { 53 | function deployChild() external { 54 | Create3.create3(keccak256(bytes("")), type(Child).creationCode); 55 | } 56 | } 57 | ``` 58 | Example with constructor arguments: 59 | ```solidity 60 | //SPDX-License-Identifier: Unlicense 61 | pragma solidity ^0.8.0; 62 | 63 | import "@0xsequence/create3/contracts/Create3.sol"; 64 | 65 | 66 | contract Child { 67 | uint256 meaningOfLife; 68 | address owner; 69 | 70 | constructor(uint256 _meaning, address _owner) { 71 | meaningOfLife = _meaning; 72 | owner = _owner; 73 | } 74 | } 75 | 76 | contract Deployer { 77 | function deployChild() external { 78 | Create3.create3( 79 | keccak256(bytes("")), 80 | abi.encodePacked( 81 | type(Child).creationCode, 82 | abi.encode( 83 | 42, 84 | msg.sender 85 | ) 86 | ) 87 | ); 88 | } 89 | } 90 | ``` 91 | 92 | # License - Unlicense 93 | 94 | ``` 95 | This is free and unencumbered software released into the public domain. 96 | 97 | Anyone is free to copy, modify, publish, use, compile, sell, or 98 | distribute this software, either in source code form or as a compiled 99 | binary, for any purpose, commercial or non-commercial, and by any 100 | means. 101 | 102 | In jurisdictions that recognize copyright laws, the author or authors 103 | of this software dedicate any and all copyright interest in the 104 | software to the public domain. We make this dedication for the benefit 105 | of the public at large and to the detriment of our heirs and 106 | successors. We intend this dedication to be an overt act of 107 | relinquishment in perpetuity of all present and future rights to this 108 | software under copyright law. 109 | 110 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 111 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 112 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 113 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 114 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 115 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 116 | OTHER DEALINGS IN THE SOFTWARE. 117 | 118 | For more information, please refer to 119 | ``` 120 | -------------------------------------------------------------------------------- /contracts/Create3.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | /** 5 | @title A library for deploying contracts EIP-3171 style. 6 | @author Agustin Aguilar 7 | */ 8 | library Create3 { 9 | error ErrorCreatingProxy(); 10 | error ErrorCreatingContract(); 11 | error TargetAlreadyExists(); 12 | 13 | /** 14 | @notice The bytecode for a contract that proxies the creation of another contract 15 | @dev If this code is deployed using CREATE2 it can be used to decouple `creationCode` from the child contract address 16 | 17 | 0x67363d3d37363d34f03d5260086018f3: 18 | 0x00 0x67 0x67XXXXXXXXXXXXXXXX PUSH8 bytecode 0x363d3d37363d34f0 19 | 0x01 0x3d 0x3d RETURNDATASIZE 0 0x363d3d37363d34f0 20 | 0x02 0x52 0x52 MSTORE 21 | 0x03 0x60 0x6008 PUSH1 08 8 22 | 0x04 0x60 0x6018 PUSH1 18 24 8 23 | 0x05 0xf3 0xf3 RETURN 24 | 25 | 0x363d3d37363d34f0: 26 | 0x00 0x36 0x36 CALLDATASIZE cds 27 | 0x01 0x3d 0x3d RETURNDATASIZE 0 cds 28 | 0x02 0x3d 0x3d RETURNDATASIZE 0 0 cds 29 | 0x03 0x37 0x37 CALLDATACOPY 30 | 0x04 0x36 0x36 CALLDATASIZE cds 31 | 0x05 0x3d 0x3d RETURNDATASIZE 0 cds 32 | 0x06 0x34 0x34 CALLVALUE val 0 cds 33 | 0x07 0xf0 0xf0 CREATE addr 34 | */ 35 | 36 | bytes internal constant PROXY_CHILD_BYTECODE = hex"67_36_3d_3d_37_36_3d_34_f0_3d_52_60_08_60_18_f3"; 37 | 38 | // KECCAK256_PROXY_CHILD_BYTECODE = keccak256(PROXY_CHILD_BYTECODE); 39 | bytes32 internal constant KECCAK256_PROXY_CHILD_BYTECODE = 0x21c35dbe1b344a2488cf3321d6ce542f8e9f305544ff09e4993a62319a497c1f; 40 | 41 | /** 42 | @notice Returns the size of the code on a given address 43 | @param _addr Address that may or may not contain code 44 | @return size of the code on the given `_addr` 45 | */ 46 | function codeSize(address _addr) internal view returns (uint256 size) { 47 | assembly { size := extcodesize(_addr) } 48 | } 49 | 50 | /** 51 | @notice Creates a new contract with given `_creationCode` and `_salt` 52 | @param _salt Salt of the contract creation, resulting address will be derivated from this value only 53 | @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address 54 | @return addr of the deployed contract, reverts on error 55 | */ 56 | function create3(bytes32 _salt, bytes memory _creationCode) internal returns (address addr) { 57 | return create3(_salt, _creationCode, 0); 58 | } 59 | 60 | /** 61 | @notice Creates a new contract with given `_creationCode` and `_salt` 62 | @param _salt Salt of the contract creation, resulting address will be derivated from this value only 63 | @param _creationCode Creation code (constructor) of the contract to be deployed, this value doesn't affect the resulting address 64 | @param _value In WEI of ETH to be forwarded to child contract 65 | @return addr of the deployed contract, reverts on error 66 | */ 67 | function create3(bytes32 _salt, bytes memory _creationCode, uint256 _value) internal returns (address addr) { 68 | // Creation code 69 | bytes memory creationCode = PROXY_CHILD_BYTECODE; 70 | 71 | // Get target final address 72 | addr = addressOf(_salt); 73 | if (codeSize(addr) != 0) revert TargetAlreadyExists(); 74 | 75 | // Create CREATE2 proxy 76 | address proxy; assembly { proxy := create2(0, add(creationCode, 32), mload(creationCode), _salt)} 77 | if (proxy == address(0)) revert ErrorCreatingProxy(); 78 | 79 | // Call proxy with final init code 80 | (bool success,) = proxy.call{ value: _value }(_creationCode); 81 | if (!success || codeSize(addr) == 0) revert ErrorCreatingContract(); 82 | } 83 | 84 | /** 85 | @notice Computes the resulting address of a contract deployed using address(this) and the given `_salt` 86 | @param _salt Salt of the contract creation, resulting address will be derivated from this value only 87 | @return addr of the deployed contract, reverts on error 88 | 89 | @dev The address creation formula is: keccak256(rlp([keccak256(0xff ++ address(this) ++ _salt ++ keccak256(childBytecode))[12:], 0x01])) 90 | */ 91 | function addressOf(bytes32 _salt) internal view returns (address) { 92 | address proxy = address( 93 | uint160( 94 | uint256( 95 | keccak256( 96 | abi.encodePacked( 97 | hex'ff', 98 | address(this), 99 | _salt, 100 | KECCAK256_PROXY_CHILD_BYTECODE 101 | ) 102 | ) 103 | ) 104 | ) 105 | ); 106 | 107 | return address( 108 | uint160( 109 | uint256( 110 | keccak256( 111 | abi.encodePacked( 112 | hex"d6_94", 113 | proxy, 114 | hex"01" 115 | ) 116 | ) 117 | ) 118 | ) 119 | ); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /contracts/test_utils/Create3Imp.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | import "../Create3.sol"; 5 | 6 | 7 | contract Create3Imp { 8 | function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) { 9 | /* 10 | 0x00 0x63 0x63XXXXXX PUSH4 _code.length size 11 | 0x01 0x80 0x80 DUP1 size size 12 | 0x02 0x60 0x600e PUSH1 14 14 size size 13 | 0x03 0x60 0x6000 PUSH1 00 0 14 size size 14 | 0x04 0x39 0x39 CODECOPY size 15 | 0x05 0x60 0x6000 PUSH1 00 0 size 16 | 0x06 0xf3 0xf3 RETURN 17 | 18 | */ 19 | 20 | return abi.encodePacked( 21 | hex"63", 22 | uint32(_code.length), 23 | hex"80_60_0E_60_00_39_60_00_F3", 24 | _code 25 | ); 26 | } 27 | 28 | function create(bytes32 _salt, bytes memory _runtimeCode) external payable returns (address addr) { 29 | return Create3.create3(_salt, creationCodeFor(_runtimeCode), msg.value); 30 | } 31 | 32 | function addressOf(bytes32 _salt) external view returns (address) { 33 | return Create3.addressOf(_salt); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/test_utils/OgCreates.sol: -------------------------------------------------------------------------------- 1 | //SPDX-License-Identifier: Unlicense 2 | pragma solidity ^0.8.0; 3 | 4 | 5 | contract OgCreates { 6 | error ErrorDeployingContract(); 7 | 8 | function creationCodeFor(bytes memory _code) internal pure returns (bytes memory) { 9 | /* 10 | 63 PUSH4 11 | 80 DUP1 12 | 60 PUSH1 (0x0e - 14) 13 | 60 PUSH1 00 14 | 39 CODECOPY 15 | 60 PUSH1 00 16 | F3 RETURN 17 | 18 | */ 19 | 20 | return abi.encodePacked( 21 | hex"63", 22 | uint32(_code.length), 23 | hex"80_60_0E_60_00_39_60_00_F3", 24 | _code 25 | ); 26 | } 27 | 28 | function create1(bytes calldata _code) external { 29 | bytes memory code = creationCodeFor(_code); 30 | address res; assembly { res := create(0, add(code, 32), mload(code)) } 31 | if (res == address(0)) revert ErrorDeployingContract(); 32 | } 33 | 34 | function create2(bytes calldata _code, bytes32 _salt) external { 35 | bytes memory code = creationCodeFor(_code); 36 | address res; assembly { res := create2(0, add(code, 32), mload(code), _salt) } 37 | if (res == address(0)) revert ErrorDeployingContract(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /hardhat.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | 3 | require('@nomiclabs/hardhat-waffle') 4 | require('hardhat-gas-reporter') 5 | require('solidity-coverage') 6 | 7 | // You need to export an object to set up your config 8 | // Go to https://hardhat.org/config/ to learn more 9 | 10 | /** 11 | * @type import('hardhat/config').HardhatUserConfig 12 | */ 13 | module.exports = { 14 | solidity: '0.8.9', 15 | networks: { 16 | hardhat: { 17 | initialBaseFeePerGas: 0 // workaround from https://github.com/sc-forks/solidity-coverage/issues/652#issuecomment-896330136 . Remove when that issue is closed. 18 | } 19 | }, 20 | gasReporter: { 21 | enabled: process.env.REPORT_GAS !== undefined, 22 | currency: 'USD' 23 | }, 24 | etherscan: { 25 | apiKey: process.env.ETHERSCAN_API_KEY 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@0xsequence/create3", 3 | "version": "3.0.0", 4 | "files": [ 5 | "/contracts/**/*.sol", 6 | "/artifacts/contracts/*.json", 7 | "/networks/**/*", 8 | "!/contracts/test_utils/**/*" 9 | ], 10 | "scripts": { 11 | "test": "hardhat test", 12 | "lint": "eslint .", 13 | "build": "hardhat compile", 14 | "plot": "NODE_OPTIONS=--max-old-space-size=32000 hardhat run scripts/plot.js --max-memory 32000", 15 | "coverage": "COVERAGE=true hardhat coverage", 16 | "prepare": "yarn build" 17 | }, 18 | "devDependencies": { 19 | "@nomiclabs/hardhat-ethers": "^2.0.2", 20 | "@nomiclabs/hardhat-waffle": "^2.0.1", 21 | "chai": "^4.3.4", 22 | "dotenv": "^10.0.0", 23 | "eslint": "^7.32.0", 24 | "eslint-config-prettier": "^8.3.0", 25 | "eslint-config-standard": "^16.0.3", 26 | "eslint-plugin-import": "^2.24.2", 27 | "eslint-plugin-node": "^11.1.0", 28 | "eslint-plugin-prettier": "^3.4.1", 29 | "eslint-plugin-promise": "^5.1.0", 30 | "ethereum-waffle": "^3.4.0", 31 | "ethers": "^5.4.7", 32 | "hardhat": "^2.6.4", 33 | "hardhat-gas-reporter": "^1.0.4", 34 | "prettier": "^2.4.1", 35 | "prettier-plugin-solidity": "^1.0.0-beta.18", 36 | "solhint": "^3.3.6", 37 | "solidity-coverage": "^0.7.17" 38 | }, 39 | "dependencies": { 40 | "csv-writer": "^1.6.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/plot.js: -------------------------------------------------------------------------------- 1 | 2 | const hre = require('hardhat') 3 | const ethers = require('ethers') 4 | 5 | function genRandomBytecode (size) { 6 | const bytecode = ethers.utils.randomBytes(size) 7 | 8 | if (bytecode[0] === 239) { 9 | bytecode[0] = 240 10 | } 11 | 12 | return bytecode 13 | } 14 | 15 | async function main () { 16 | const OgCreates = await hre.ethers.getContractFactory('OgCreates') 17 | const Create3 = await hre.ethers.getContractFactory('Create3Imp') 18 | 19 | const ogcreates = await OgCreates.deploy() 20 | const create3 = await Create3.deploy() 21 | 22 | await Promise.all([ogcreates.deployed(), create3.deployed()]) 23 | 24 | const bytecode = genRandomBytecode(24576) 25 | 26 | // let results = [] 27 | 28 | const createCsvWriter = require('csv-writer').createObjectCsvWriter 29 | const csvWriter = createCsvWriter({ 30 | path: './cost.csv', 31 | header: [ 32 | { id: 'size', title: 'SIZE' }, 33 | { id: 'create1', title: 'CREATE1' }, 34 | { id: 'create2', title: 'CREATE2' }, 35 | { id: 'create3', title: 'CREATE3' } 36 | ] 37 | }) 38 | 39 | for (let i = 1; i < bytecode.length; i++) { 40 | const slice = ethers.utils.hexlify(bytecode.slice(0, i)) 41 | 42 | const c1 = await (async () => { 43 | // CREATE1 44 | const tx = await ogcreates.create1(slice, { gasLimit: 29000000 }) 45 | const receipt = await tx.wait() 46 | return receipt.gasUsed 47 | })() 48 | 49 | const salt = ethers.utils.hexZeroPad(i, 32) 50 | 51 | const c2 = await (async () => { 52 | // CREATE2 53 | const tx = await ogcreates.create2(slice, salt, { gasLimit: 29000000 }) 54 | const receipt = await tx.wait() 55 | return receipt.gasUsed 56 | })() 57 | 58 | const c3 = await (async () => { 59 | // CREATE3 60 | const tx = await create3.create(salt, slice, { gasLimit: 29000000 }) 61 | const receipt = await tx.wait() 62 | return receipt.gasUsed 63 | })() 64 | 65 | console.log(i, 'Diff...', c3.sub(c2).toString()) 66 | await csvWriter.writeRecords([{ size: i, create1: c1.toString(), create2: c2.toString(), create3: c3.toString() }]) 67 | } 68 | } 69 | 70 | main() 71 | .then(() => process.exit(0)) 72 | .catch((error) => { 73 | console.error(error) 74 | process.exit(1) 75 | }) 76 | -------------------------------------------------------------------------------- /test/create3-test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai') 2 | const { ethers } = require('hardhat') 3 | 4 | async function deployCreate3 () { 5 | const Create3 = await ethers.getContractFactory('Create3Imp') 6 | const create3 = await Create3.deploy() 7 | await create3.deployed() 8 | return create3 9 | } 10 | 11 | function genRandomBytecode (size) { 12 | const bytecode = ethers.utils.randomBytes(size) 13 | 14 | if (bytecode[0] === 239) { 15 | bytecode[0] = 240 16 | } 17 | 18 | return ethers.utils.hexlify(bytecode) 19 | } 20 | 21 | describe('Create3', () => { 22 | let { create3 } = {} 23 | 24 | beforeEach(async () => { 25 | create3 = await deployCreate3() 26 | }) 27 | 28 | it('Should create contract', async () => { 29 | const bytecode1 = genRandomBytecode(911) 30 | 31 | await create3.create(ethers.constants.HashZero, bytecode1) 32 | 33 | const address1 = await create3.addressOf(ethers.constants.HashZero) 34 | 35 | expect(await ethers.provider.getCode(address1)).to.equal(bytecode1) 36 | }) 37 | 38 | it('Should fail to create contract with invalid bytecode', async () => { 39 | // EIP-3541 forbids the creation of contracts starting with 0xef 40 | const bytecode = '0xef' 41 | 42 | const tx = create3.create(ethers.constants.HashZero, bytecode) 43 | await expect(tx).to.be.reverted 44 | }) 45 | 46 | it('Should fail to deploy contract if address is already in use', async () => { 47 | const bytecode1 = genRandomBytecode(911) 48 | const bytecode2 = genRandomBytecode(211) 49 | 50 | await create3.create(ethers.constants.HashZero, bytecode1) 51 | const tx = create3.create(ethers.constants.HashZero, bytecode2) 52 | 53 | await expect(tx).to.be.reverted 54 | }) 55 | 56 | it('Should forward payable amount to child contract', async () => { 57 | const bytecode = genRandomBytecode(211) 58 | await create3.create(ethers.constants.HashZero, bytecode, { value: 2 }) 59 | const address = await create3.addressOf(ethers.constants.HashZero) 60 | expect(await ethers.provider.getBalance(address)).to.equal(2) 61 | }) 62 | 63 | if (!process.env.COVERAGE) { 64 | it('Should fail to deploy contract above EIP-170 limit', async () => { 65 | const bytecode = genRandomBytecode(24577) 66 | 67 | const tx = create3.create(ethers.constants.HashZero, bytecode, { gasLimit: 28000000 }) 68 | await expect(tx).to.be.reverted 69 | }) 70 | } 71 | 72 | it('Should fail to create empty contract', async () => { 73 | const bytecode = '0x' 74 | const salt = ethers.utils.randomBytes(32) 75 | 76 | const tx = create3.create(salt, bytecode) 77 | await expect(tx).to.be.reverted 78 | }) 79 | 80 | it('Should create contracts with all bytecode sizes between 0 and 2049', async () => { 81 | await Promise.all(new Array(2049).fill(0).map(async (_, i) => { 82 | const bytecode = genRandomBytecode(i + 1) 83 | const salt = ethers.utils.randomBytes(32) 84 | 85 | await create3.create(salt, bytecode) 86 | const address = await create3.addressOf(salt) 87 | 88 | expect(await ethers.provider.getCode(address)).to.equal(bytecode) 89 | })) 90 | }).timeout(15 * 60 * 1000) 91 | }) 92 | --------------------------------------------------------------------------------