├── .gitattributes ├── .gitignore ├── .solhint.json ├── .travis.yml ├── LICENSE.md ├── README.md ├── contracts ├── ImmutableCreate2Factory.sol ├── MetamorphicContractFactory.sol ├── Metapod.sol ├── TransientContract.sol └── mock │ ├── CodeCheck.sol │ ├── ContractOne.sol │ ├── ContractTwo.sol │ ├── ContractWithConstructor.sol │ └── KakunaBasicTest.sol ├── package.json ├── scripts ├── kakuna │ ├── ci.js │ └── kakuna.js └── test │ ├── ci.js │ └── test.js └── truffle-config.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.sol linguist-language=Solidity -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | .DS_Store 4 | ganache-output.log -------------------------------------------------------------------------------- /.solhint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "strict", 3 | "rules": { 4 | "indent": ["warn", 2] 5 | } 6 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language: node_js 3 | node_js: 4 | - "10" 5 | before_install: 6 | # yarn 7 | - sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg 8 | - echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list 9 | - sudo apt-get update -qq 10 | - sudo apt-get install -y -qq yarn 11 | install: 12 | - yarn install 13 | script: 14 | - yarn ci 15 | cache: 16 | yarn: true -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 0age 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do 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 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Metamorphic 2 | 3 | ![GitHub](https://img.shields.io/github/license/0age/metamorphic.svg?colorB=brightgreen) 4 | [![Build Status](https://travis-ci.org/0age/metamorphic.svg?branch=master)](https://travis-ci.org/0age/metamorphic) 5 | [![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) 6 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) 7 | 8 | > Metamorphic - A factory contract for creating metamorphic (i.e. redeployable) contracts. 9 | 10 | This [factory contract](https://github.com/0age/metamorphic/blob/master/contracts/MetamorphicContractFactory.sol) creates *metamorphic contracts*, or contracts that can be redeployed with new code to the same address. It does so by deploying the metamorphic contract with fixed, non-deterministic initialization code via the CREATE2 opcode. This initalization code clones a given implementation contract and optionally initializes it in one operation. Once a contract undergoes metamorphosis, all existing storage will be deleted and any existing contract code will be replaced with the deployed contract code of the new implementation contract. Alternately, the factory can also create metamorphic contracts that utilize a constructor by deploying them with an intermediate [transient contract](https://github.com/0age/metamorphic/blob/master/contracts/TransientContract.sol) - otherwise an argument for atomically calling an initialization function after cloning an instance may be used. There is also an [immutable create2 factory](https://github.com/0age/metamorphic/blob/master/contracts/ImmutableCreate2Factory.sol) that will not perform contract redeployments, thereby preventing metamorphism of any contract it deploys *(although they may still deploy their own metamorphic contracts)*. 11 | 12 | This repo also includes [Metapod](https://github.com/0age/metamorphic/blob/master/contracts/Metapod.sol), a factory for deploying "hardened" metamorphic contracts. *(Note that the provided version of Metapod uses a hard-coded address, used by the local test suite, throughout the contract that will all need to be altered it order to deploy to any other address.)* All contracts deployed through Metapod must include a prelude *(or initial snippet of code)* that allows it to destroy the contract and forward all funds to a dedicated vault contract. In order to insert the prelude into your contract, any stack items used by `JUMP` or `JUMPI` destinations, as well as by `CODECOPY` offsets, must first be modified. To try this out, there's a provided utility called [Kakuna](https://github.com/0age/metamorphic/blob/master/scripts/kakuna/kakuna.js), an **error-prone POC** for analyzing a contract and inserting a prelude. 13 | 14 | **DISCLAIMER: this implements highly experimental features / bugs - be sure to implement appropriate controls on your metamorphic contracts and *educate the users of your contract* if it will be interacted with! These contracts have not yet been fully tested or audited - proceed with caution and please share any exploits or optimizations you discover.** 15 | 16 | See [this medium post](https://medium.com/@0age/the-promise-and-the-peril-of-metamorphic-contracts-9eb8b8413c5e) for context. 17 | 18 | Metamorphic Contract Factory on Mainnet: [0x00000000e82eb0431756271F0d00CFB143685e7B](https://etherscan.io/address/0x00000000e82eb0431756271f0d00cfb143685e7b) 19 | 20 | Metamorphic Contract Factory on Ropsten: [0x00000000D63fB7385Ae38E7753F70e36d190abc2](https://ropsten.etherscan.io/address/0x00000000D63fB7385Ae38E7753F70e36d190abc2) 21 | 22 | Immutable Create2 Factory on Mainnet: [0x000000000063b99B8036c31E91c64fC89bFf9ca7](https://etherscan.io/address/0x000000000063b99b8036c31e91c64fc89bff9ca7#code) 23 | 24 | Immutable Create2 Factory on Ropsten: [0x000000B64Df4e600F23000dbAEEB8c0052C88e73](https://ropsten.etherscan.io/address/0x000000b64df4e600f23000dbaeeb8c0052c88e73) 25 | 26 | Metapod on Mainnet: [0x00000000002B13cCcEC913420A21e4D11b2DCd3C](https://etherscan.io/address/0x00000000002b13cccec913420a21e4d11b2dcd3c) 27 | 28 | Metapod on Ropsten: [0x0000000000f647BA29e4Dd009D2B7CADa21c1c68](https://ropsten.etherscan.io/address/0x0000000000f647ba29e4dd009d2b7cada21c1c68) 29 | 30 | ## Table of Contents 31 | 32 | - [Install](#install) 33 | - [Usage](#usage) 34 | - [API](#api) 35 | - [Maintainers](#maintainers) 36 | - [Contribute](#contribute) 37 | - [License](#license) 38 | 39 | ## Install 40 | To install locally, you'll need Node.js 10+ and Yarn *(or npm)*. To get everything set up: 41 | ```sh 42 | $ git clone https://github.com/0age/metamorphic.git 43 | $ cd metamorphic 44 | $ yarn install 45 | $ yarn build 46 | ``` 47 | 48 | ## Usage 49 | In a new terminal window, start the testRPC, run tests, and tear down the testRPC *(you can do all of this at once via* `yarn all` *if you prefer)*: 50 | ```sh 51 | $ yarn start 52 | $ yarn test 53 | $ yarn linter 54 | $ yarn stop 55 | ``` 56 | 57 | To use Kakuna, first build the contracts, then run the following, replacing the contract name and prelude as desired (you'll need to get and insert the correct prelude for use with Metapod): 58 | ```sh 59 | $ yarn kakuna ContractOne 0x4150 60 | ``` 61 | 62 | ## API 63 | 64 | **This documentation is incomplete - see the source code of each contract for a more complete summary.** 65 | 66 | - [MetamorphicContractFactory.sol](#metamorphiccontractfactorysol) 67 | - [ImmutableCreate2Factory.sol](#immutablecreate2factorysol) 68 | 69 | ### [MetamorphicContractFactory.sol](https://github.com/0age/metamorphic/blob/master/contracts/MetamorphicContractFactory.sol) 70 | 71 | This contract creates metamorphic contracts, or contracts that can be redeployed with new code to the same address. It does so by deploying a contract with fixed, non-deterministic initialization code via the `CREATE2` opcode. This contract clones the implementation contract in its constructor. Once a contract undergoes metamorphosis, all existing storage will be deleted and any existing contract code will be replaced with the deployed contract code of the new implementation contract. 72 | 73 | #### Events 74 | 75 | ```Solidity 76 | event Metamorphosed(address metamorphicContract, address newImplementation); 77 | ``` 78 | 79 | #### Functions 80 | 81 | - [deployMetamorphicContract](#deploymetamorphiccontract) 82 | - [deployMetamorphicContractFromExistingImplementation](#deploymetamorphiccontractfromexistingimplementation) 83 | - [getImplementation](#getimplementation) 84 | - [getImplementationContractAddress](#getimplementationcontractaddress) 85 | - [findMetamorphicContractAddress](#findmetamorphiccontractaddress) 86 | - [getMetamorphicContractInitializationCode](#getmetamorphiccontractinitializationcode) 87 | - [getMetamorphicContractInitializationCodeHash](#getmetamorphiccontractinitializationcodehash) 88 | 89 | 90 | #### deployMetamorphicContract 91 | 92 | Deploy a metamorphic contract by submitting a given salt or nonce along with the initialization code for the metamorphic contract, and optionally provide calldata for initializing the new metamorphic contract. To replace the contract, first selfdestruct the current contract, then call with the same salt value and new initialization code *(be aware that all existing state will be wiped from the existing contract)*. Also note that the first 20 bytes of the salt must match the calling address, which prevents contracts from being created by unintended parties. 93 | 94 | ```Solidity 95 | function deployMetamorphicContract( 96 | bytes32 salt, 97 | bytes implementationContractInitializationCode, 98 | bytes metamorphicContractInitializationCalldata 99 | ) external payable returns ( 100 | address metamorphicContractAddress 101 | ) 102 | ``` 103 | 104 | Arguments: 105 | 106 | | Name | Type | Description | 107 | | ------------- |------------- | -----| 108 | | salt | bytes32 | The nonce that will be passed into the CREATE2 call and thus will determine the resulant address of the metamorphic contract. | 109 | | implementationContractInitializationCode | bytes | The initialization code for the implementation contract for the metamorphic contract. It will be used to deploy a new contract that the metamorphic contract will then clone in its constructor. | 110 | | metamorphicContractInitializationCalldata | bytes | An optional data parameter that can be used to atomically initialize the metamorphic contract. | 111 | 112 | Returns: Address of the metamorphic contract that will be created. 113 | 114 | #### deployMetamorphicContractFromExistingImplementation 115 | 116 | Deploy a metamorphic contract by submitting a given salt or nonce along with the address of an existing implementation contract to clone, and optionally provide calldata for initializing the new metamorphic contract. To replace the contract, first selfdestruct the current contract, then call with the same salt value and a new implementation address *(be aware that all existing state will be wiped from the existing contract)*. Also note that the first 20 bytes of the salt must match the calling address, which prevents contracts from being created by unintended parties. 117 | 118 | ```Solidity 119 | function deployMetamorphicContractFromExistingImplementation( 120 | bytes32 salt, 121 | address implementationContract, 122 | bytes metamorphicContractInitializationCalldata 123 | ) external payable returns ( 124 | address metamorphicContractAddress 125 | ) 126 | ``` 127 | 128 | Arguments: 129 | 130 | | Name | Type | Description | 131 | | ------------- |------------- | -----| 132 | | salt | bytes32 | The nonce that will be passed into the CREATE2 call and thus will determine the resulant address of the metamorphic contract. | 133 | | implementationContract | address | The address of the existing implementation contract to clone. | 134 | | metamorphicContractInitializationCalldata | bytes | An optional data parameter that can be used to atomically initialize the metamorphic contract. | 135 | 136 | Returns: Address of the metamorphic contract that will be created. 137 | 138 | #### getImplementation 139 | 140 | View function for retrieving the address of the implementation contract to clone. Called by the constructor of each metamorphic contract. 141 | 142 | ```Solidity 143 | function getImplementation() external view returns (address implementation) 144 | ``` 145 | 146 | #### getImplementationContractAddress 147 | 148 | View function for retrieving the address of the current implementation contract of a given metamorphic contract, where the address of the contract is supplied as an argument. Be aware that the implementation contract has an independent state and may have been altered or selfdestructed from when it was last cloned by the metamorphic contract. 149 | 150 | ```Solidity 151 | function getImplementationContractAddress( 152 | address metamorphicContractAddress 153 | ) external view returns ( 154 | address implementationContractAddress 155 | ) 156 | ``` 157 | 158 | Arguments: 159 | 160 | | Name | Type | Description | 161 | | ------------- |------------- | -----| 162 | | metamorphicContractAddress | address | The address of the metamorphic contract. | 163 | 164 | Returns: Address of the corresponding implementation contract. 165 | 166 | #### findMetamorphicContractAddress 167 | 168 | Compute the address of the metamorphic contract that will be created upon submitting a given salt to the contract. 169 | 170 | ```Solidity 171 | function findMetamorphicContractAddress( 172 | bytes32 salt 173 | ) external view returns ( 174 | address metamorphicContractAddress 175 | ) 176 | ``` 177 | 178 | Arguments: 179 | 180 | | Name | Type | Description | 181 | | ------------- |------------- | -----| 182 | | salt | bytes32 | The nonce passed into CREATE2 by metamorphic contract. | 183 | 184 | Returns: Address of the corresponding metamorphic contract. 185 | 186 | #### getMetamorphicContractInitializationCode 187 | 188 | View function for retrieving the initialization code of metamorphic contracts for purposes of verification. 189 | 190 | ```Solidity 191 | function getMetamorphicContractInitializationCode() external view returns ( 192 | bytes metamorphicContractInitializationCode 193 | ) 194 | ``` 195 | 196 | #### getMetamorphicContractInitializationCodeHash 197 | 198 | View function for retrieving the keccak256 hash of the initialization code of metamorphic contracts for purposes of verification. 199 | 200 | ```Solidity 201 | function getMetamorphicContractInitializationCodeHash() external view returns ( 202 | bytes32 metamorphicContractInitializationCodeHash 203 | ) 204 | ``` 205 | 206 | ### [ImmutableCreate2Factory.sol](https://github.com/0age/metamorphic/blob/master/contracts/ImmutableCreate2Factory.sol) 207 | 208 | This contract provides a safeCreate2 function that takes a salt value and a block of initialization code as arguments and passes them into inline assembly. The contract prevents redeploys by maintaining a mapping of all contracts that have already been deployed, and prevents frontrunning or other collisions by requiring that the first 20 bytes of the salt are equal to the address of the caller *(this can be bypassed by setting the first 20 bytes to the null address)*. There is also a view function that computes the address of the contract that will be created when submitting a given salt or nonce along with a given block of initialization code. 209 | 210 | #### Functions 211 | 212 | - [safeCreate2](#safecreate2) 213 | - [findCreate2Address](#findcreate2address) 214 | - [findCreate2AddressViaHash](#findcreate2addressviahash) 215 | - [hasBeenDeployed](#hasbeendeployed) 216 | 217 | #### safeCreate2 218 | 219 | Create a contract using `CREATE2` by submitting a given salt or nonce along with the initialization code for the contract. Note that the first 20 bytes of the salt must match those of the calling address, which prevents contract creation events from being submitted by unintended parties. 220 | 221 | ```Solidity 222 | function safeCreate2( 223 | bytes32 salt, 224 | bytes initializationCode 225 | ) external payable returns ( 226 | address deploymentAddress 227 | ) 228 | ``` 229 | 230 | Arguments: 231 | 232 | | Name | Type | Description | 233 | | ------------- |------------- | -----| 234 | | salt | bytes32 | The nonce that will be passed into the CREATE2 call. | 235 | | initializationCode | bytes | The initialization code that will be passed into the CREATE2 call. | 236 | 237 | Returns: Address of the contract that will be created, or the null address if a contract already exists at that address. 238 | 239 | #### findCreate2Address 240 | 241 | Compute the address of the contract that will be created when submitting a given salt or nonce to the contract along with the contract's initialization code. The `CREATE2` address is computed in accordance with EIP-1014, and adheres to the formula therein of `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:]` when performing the computation. The computed address is then checked for any existing contract code - if so, the null address will be returned instead. 242 | 243 | ```Solidity 244 | function findCreate2Address( 245 | bytes32 salt, 246 | bytes initCode 247 | ) external view returns ( 248 | address deploymentAddress 249 | ) 250 | ``` 251 | 252 | Arguments: 253 | 254 | | Name | Type | Description | 255 | | ------------- |------------- | -----| 256 | | salt | bytes32 | The nonce passed into the CREATE2 address calculation. | 257 | | initCode | bytes | The contract initialization code to be used that will be passed into the CREATE2 address calculation. | 258 | 259 | Returns: Address of the contract that will be created, or the null address if a contract has already been deployed to that address. 260 | 261 | #### findCreate2AddressViaHash 262 | 263 | Compute the address of the contract that will be created when submitting a given salt or nonce to the contract along with the keccak256 hash of the contract's initialization code. The `CREATE2` address is computed in accordance with EIP-1014, and adheres to the formula therein of `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:]` when performing the computation. The computed address is then checked for any existing contract code - if so, the null address will be returned instead. 264 | 265 | ```Solidity 266 | function findCreate2AddressViaHash( 267 | bytes32 salt, 268 | bytes32 initCodeHash 269 | ) external view returns ( 270 | address deploymentAddress 271 | ) 272 | ``` 273 | 274 | Arguments: 275 | 276 | | Name | Type | Description | 277 | | ------------- |------------- | -----| 278 | | salt | bytes32 | The nonce passed into the CREATE2 address calculation. | 279 | | initCodeHash | bytes32 | The keccak256 hash of the initialization code that will be passed into the CREATE2 address calculation. | 280 | 281 | Returns: Address of the contract that will be created, or the null address if a contract has already been deployed to that address. 282 | 283 | #### hasBeenDeployed 284 | 285 | Determine if a contract has already been deployed by the factory to a given address. 286 | 287 | ```Solidity 288 | function hasBeenDeployed(address deploymentAddress) external view returns (bool) 289 | ``` 290 | 291 | Arguments: 292 | 293 | | Name | Type | Description | 294 | | ------------- |------------- | -----| 295 | | deploymentAddress | address | The contract address to check. | 296 | 297 | Returns: True if the contract has been deployed, false otherwise. 298 | 299 | ## Maintainers 300 | 301 | [@0age](https://github.com/0age) 302 | 303 | ## Contribute 304 | 305 | PRs accepted gladly - make sure the tests and linters pass. 306 | 307 | ## License 308 | 309 | MIT © 2019 0age 310 | -------------------------------------------------------------------------------- /contracts/ImmutableCreate2Factory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title Immutable Create2 Contract Factory 6 | * @author 0age 7 | * @notice This contract provides a safeCreate2 function that takes a salt value 8 | * and a block of initialization code as arguments and passes them into inline 9 | * assembly. The contract prevents redeploys by maintaining a mapping of all 10 | * contracts that have already been deployed, and prevents frontrunning or other 11 | * collisions by requiring that the first 20 bytes of the salt are equal to the 12 | * address of the caller (this can be bypassed by setting the first 20 bytes to 13 | * the null address). There is also a view function that computes the address of 14 | * the contract that will be created when submitting a given salt or nonce along 15 | * with a given block of initialization code. 16 | * @dev CREATE2 will not be available on mainnet until (at least) block 17 | * 7,280,000. This contract has not yet been fully tested or audited - proceed 18 | * with caution and please share any exploits or optimizations you discover. 19 | */ 20 | contract ImmutableCreate2Factory { 21 | // mapping to track which addresses have already been deployed. 22 | mapping(address => bool) private _deployed; 23 | 24 | /** 25 | * @dev Create a contract using CREATE2 by submitting a given salt or nonce 26 | * along with the initialization code for the contract. Note that the first 20 27 | * bytes of the salt must match those of the calling address, which prevents 28 | * contract creation events from being submitted by unintended parties. 29 | * @param salt bytes32 The nonce that will be passed into the CREATE2 call. 30 | * @param initializationCode bytes The initialization code that will be passed 31 | * into the CREATE2 call. 32 | * @return Address of the contract that will be created, or the null address 33 | * if a contract already exists at that address. 34 | */ 35 | function safeCreate2( 36 | bytes32 salt, 37 | bytes calldata initializationCode 38 | ) external payable containsCaller(salt) returns (address deploymentAddress) { 39 | // move the initialization code from calldata to memory. 40 | bytes memory initCode = initializationCode; 41 | 42 | // determine the target address for contract deployment. 43 | address targetDeploymentAddress = address( 44 | uint160( // downcast to match the address type. 45 | uint256( // convert to uint to truncate upper digits. 46 | keccak256( // compute the CREATE2 hash using 4 inputs. 47 | abi.encodePacked( // pack all inputs to the hash together. 48 | hex"ff", // start with 0xff to distinguish from RLP. 49 | address(this), // this contract will be the caller. 50 | salt, // pass in the supplied salt value. 51 | keccak256( // pass in the hash of initialization code. 52 | abi.encodePacked( 53 | initCode 54 | ) 55 | ) 56 | ) 57 | ) 58 | ) 59 | ) 60 | ); 61 | 62 | // ensure that a contract hasn't been previously deployed to target address. 63 | require( 64 | !_deployed[targetDeploymentAddress], 65 | "Invalid contract creation - contract has already been deployed." 66 | ); 67 | 68 | // using inline assembly: load data and length of data, then call CREATE2. 69 | assembly { // solhint-disable-line 70 | let encoded_data := add(0x20, initCode) // load initialization code. 71 | let encoded_size := mload(initCode) // load the init code's length. 72 | deploymentAddress := create2( // call CREATE2 with 4 arguments. 73 | callvalue, // forward any attached value. 74 | encoded_data, // pass in initialization code. 75 | encoded_size, // pass in init code's length. 76 | salt // pass in the salt value. 77 | ) 78 | } 79 | 80 | // check address against target to ensure that deployment was successful. 81 | require( 82 | deploymentAddress == targetDeploymentAddress, 83 | "Failed to deploy contract using provided salt and initialization code." 84 | ); 85 | 86 | // record the deployment of the contract to prevent redeploys. 87 | _deployed[deploymentAddress] = true; 88 | } 89 | 90 | /** 91 | * @dev Compute the address of the contract that will be created when 92 | * submitting a given salt or nonce to the contract along with the contract's 93 | * initialization code. The CREATE2 address is computed in accordance with 94 | * EIP-1014, and adheres to the formula therein of 95 | * `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:]` when 96 | * performing the computation. The computed address is then checked for any 97 | * existing contract code - if so, the null address will be returned instead. 98 | * @param salt bytes32 The nonce passed into the CREATE2 address calculation. 99 | * @param initCode bytes The contract initialization code to be used. 100 | * that will be passed into the CREATE2 address calculation. 101 | * @return Address of the contract that will be created, or the null address 102 | * if a contract has already been deployed to that address. 103 | */ 104 | function findCreate2Address( 105 | bytes32 salt, 106 | bytes calldata initCode 107 | ) external view returns (address deploymentAddress) { 108 | // determine the address where the contract will be deployed. 109 | deploymentAddress = address( 110 | uint160( // downcast to match the address type. 111 | uint256( // convert to uint to truncate upper digits. 112 | keccak256( // compute the CREATE2 hash using 4 inputs. 113 | abi.encodePacked( // pack all inputs to the hash together. 114 | hex"ff", // start with 0xff to distinguish from RLP. 115 | address(this), // this contract will be the caller. 116 | salt, // pass in the supplied salt value. 117 | keccak256( // pass in the hash of initialization code. 118 | abi.encodePacked( 119 | initCode 120 | ) 121 | ) 122 | ) 123 | ) 124 | ) 125 | ) 126 | ); 127 | 128 | // return null address to signify failure if contract has been deployed. 129 | if (_deployed[deploymentAddress]) { 130 | return address(0); 131 | } 132 | } 133 | 134 | /** 135 | * @dev Compute the address of the contract that will be created when 136 | * submitting a given salt or nonce to the contract along with the keccak256 137 | * hash of the contract's initialization code. The CREATE2 address is computed 138 | * in accordance with EIP-1014, and adheres to the formula therein of 139 | * `keccak256( 0xff ++ address ++ salt ++ keccak256(init_code)))[12:]` when 140 | * performing the computation. The computed address is then checked for any 141 | * existing contract code - if so, the null address will be returned instead. 142 | * @param salt bytes32 The nonce passed into the CREATE2 address calculation. 143 | * @param initCodeHash bytes32 The keccak256 hash of the initialization code 144 | * that will be passed into the CREATE2 address calculation. 145 | * @return Address of the contract that will be created, or the null address 146 | * if a contract has already been deployed to that address. 147 | */ 148 | function findCreate2AddressViaHash( 149 | bytes32 salt, 150 | bytes32 initCodeHash 151 | ) external view returns (address deploymentAddress) { 152 | // determine the address where the contract will be deployed. 153 | deploymentAddress = address( 154 | uint160( // downcast to match the address type. 155 | uint256( // convert to uint to truncate upper digits. 156 | keccak256( // compute the CREATE2 hash using 4 inputs. 157 | abi.encodePacked( // pack all inputs to the hash together. 158 | hex"ff", // start with 0xff to distinguish from RLP. 159 | address(this), // this contract will be the caller. 160 | salt, // pass in the supplied salt value. 161 | initCodeHash // pass in the hash of initialization code. 162 | ) 163 | ) 164 | ) 165 | ) 166 | ); 167 | 168 | // return null address to signify failure if contract has been deployed. 169 | if (_deployed[deploymentAddress]) { 170 | return address(0); 171 | } 172 | } 173 | 174 | /** 175 | * @dev Determine if a contract has already been deployed by the factory to a 176 | * given address. 177 | * @param deploymentAddress address The contract address to check. 178 | * @return True if the contract has been deployed, false otherwise. 179 | */ 180 | function hasBeenDeployed( 181 | address deploymentAddress 182 | ) external view returns (bool) { 183 | // determine if a contract has been deployed to the provided address. 184 | return _deployed[deploymentAddress]; 185 | } 186 | 187 | /** 188 | * @dev Modifier to ensure that the first 20 bytes of a submitted salt match 189 | * those of the calling account. This provides protection against the salt 190 | * being stolen by frontrunners or other attackers. The protection can also be 191 | * bypassed if desired by setting each of the first 20 bytes to zero. 192 | * @param salt bytes32 The salt value to check against the calling address. 193 | */ 194 | modifier containsCaller(bytes32 salt) { 195 | // prevent contract submissions from being stolen from tx.pool by requiring 196 | // that the first 20 bytes of the submitted salt match msg.sender. 197 | require( 198 | (address(bytes20(salt)) == msg.sender) || 199 | (bytes20(salt) == bytes20(0)), 200 | "Invalid salt - first 20 bytes of the salt must match calling address." 201 | ); 202 | _; 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /contracts/MetamorphicContractFactory.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title Metamorphic Contract Factory 6 | * @author 0age 7 | * @notice This contract creates metamorphic contracts, or contracts that can be 8 | * redeployed with new code to the same address. It does so by deploying a 9 | * contract with fixed, non-deterministic initialization code via the CREATE2 10 | * opcode. This contract clones the implementation contract in its constructor. 11 | * Once a contract undergoes metamorphosis, all existing storage will be deleted 12 | * and any existing contract code will be replaced with the deployed contract 13 | * code of the new implementation contract. 14 | * @dev CREATE2 will not be available on mainnet until (at least) block 15 | * 7,280,000. This contract has not yet been fully tested or audited - proceed 16 | * with caution and please share any exploits or optimizations you discover. 17 | */ 18 | contract MetamorphicContractFactory { 19 | // fires when a metamorphic contract is deployed by cloning another contract. 20 | event Metamorphosed(address metamorphicContract, address newImplementation); 21 | 22 | // fires when a metamorphic contract is deployed through a transient contract. 23 | event MetamorphosedWithConstructor( 24 | address metamorphicContract, 25 | address transientContract 26 | ); 27 | 28 | // store the initialization code for metamorphic contracts. 29 | bytes private _metamorphicContractInitializationCode; 30 | 31 | // store hash of the initialization code for metamorphic contracts as well. 32 | bytes32 private _metamorphicContractInitializationCodeHash; 33 | 34 | // store init code for transient contracts that deploy metamorphic contracts. 35 | bytes private _transientContractInitializationCode; 36 | 37 | // store the hash of the initialization code for transient contracts as well. 38 | bytes32 private _transientContractInitializationCodeHash; 39 | 40 | // maintain a mapping of metamorphic contracts to metamorphic implementations. 41 | mapping(address => address) private _implementations; 42 | 43 | // maintain a mapping of transient contracts to metamorphic init codes. 44 | mapping(address => bytes) private _initCodes; 45 | 46 | /** 47 | * @dev In the constructor, set up the initialization code for metamorphic 48 | * contracts as well as the keccak256 hash of the given initialization code. 49 | * @param transientContractInitializationCode bytes The initialization code 50 | * that will be used to deploy any transient contracts, which will deploy any 51 | * metamorphic contracts that require the use of a constructor. 52 | * 53 | * Metamorphic contract initialization code (29 bytes): 54 | * 55 | * 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3 56 | * 57 | * Description: 58 | * 59 | * pc|op|name | [stack] | 60 | * 61 | * ** set the first stack item to zero - used later ** 62 | * 00 58 getpc [0] <> 63 | * 64 | * ** set second stack item to 32, length of word returned from staticcall ** 65 | * 01 60 push1 66 | * 02 20 outsize [0, 32] <> 67 | * 68 | * ** set third stack item to 0, position of word returned from staticcall ** 69 | * 03 81 dup2 [0, 32, 0] <> 70 | * 71 | * ** set fourth stack item to 4, length of selector given to staticcall ** 72 | * 04 58 getpc [0, 32, 0, 4] <> 73 | * 74 | * ** set fifth stack item to 28, position of selector given to staticcall ** 75 | * 05 60 push1 76 | * 06 1c inpos [0, 32, 0, 4, 28] <> 77 | * 78 | * ** set the sixth stack item to msg.sender, target address for staticcall ** 79 | * 07 33 caller [0, 32, 0, 4, 28, caller] <> 80 | * 81 | * ** set the seventh stack item to msg.gas, gas to forward for staticcall ** 82 | * 08 5a gas [0, 32, 0, 4, 28, caller, gas] <> 83 | * 84 | * ** set the eighth stack item to selector, "what" to store via mstore ** 85 | * 09 63 push4 86 | * 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <> 87 | * 88 | * ** set the ninth stack item to 0, "where" to store via mstore *** 89 | * 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <> 90 | * 91 | * ** call mstore, consume 8 and 9 from the stack, place selector in memory ** 92 | * 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42> 93 | * 94 | * ** call staticcall, consume items 2 through 7, place address in memory ** 95 | * 13 fa staticcall [0, 1 (if successful)]
96 | * 97 | * ** flip success bit in second stack item to set to 0 ** 98 | * 14 15 iszero [0, 0]
99 | * 100 | * ** push a third 0 to the stack, position of address in memory ** 101 | * 15 81 dup2 [0, 0, 0]
102 | * 103 | * ** place address from position in memory onto third stack item ** 104 | * 16 51 mload [0, 0, address] <> 105 | * 106 | * ** place address to fourth stack item for extcodesize to consume ** 107 | * 17 80 dup1 [0, 0, address, address] <> 108 | * 109 | * ** get extcodesize on fourth stack item for extcodecopy ** 110 | * 18 3b extcodesize [0, 0, address, size] <> 111 | * 112 | * ** dup and swap size for use by return at end of init code ** 113 | * 19 80 dup1 [0, 0, address, size, size] <> 114 | * 20 93 swap4 [size, 0, address, size, 0] <> 115 | * 116 | * ** push code position 0 to stack and reorder stack items for extcodecopy ** 117 | * 21 80 dup1 [size, 0, address, size, 0, 0] <> 118 | * 22 91 swap2 [size, 0, address, 0, 0, size] <> 119 | * 23 92 swap3 [size, 0, size, 0, 0, address] <> 120 | * 121 | * ** call extcodecopy, consume four items, clone runtime code to memory ** 122 | * 24 3c extcodecopy [size, 0] 123 | * 124 | * ** return to deploy final code in memory ** 125 | * 25 f3 return [] *deployed!* 126 | * 127 | * 128 | * Transient contract initialization code derived from TransientContract.sol. 129 | */ 130 | constructor(bytes memory transientContractInitializationCode) public { 131 | // assign the initialization code for the metamorphic contract. 132 | _metamorphicContractInitializationCode = ( 133 | hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3" 134 | ); 135 | 136 | // calculate and assign keccak256 hash of metamorphic initialization code. 137 | _metamorphicContractInitializationCodeHash = keccak256( 138 | abi.encodePacked( 139 | _metamorphicContractInitializationCode 140 | ) 141 | ); 142 | 143 | // store the initialization code for the transient contract. 144 | _transientContractInitializationCode = transientContractInitializationCode; 145 | 146 | // calculate and assign keccak256 hash of transient initialization code. 147 | _transientContractInitializationCodeHash = keccak256( 148 | abi.encodePacked( 149 | _transientContractInitializationCode 150 | ) 151 | ); 152 | } 153 | 154 | /* solhint-disable function-max-lines */ 155 | /** 156 | * @dev Deploy a metamorphic contract by submitting a given salt or nonce 157 | * along with the initialization code for the metamorphic contract, and 158 | * optionally provide calldata for initializing the new metamorphic contract. 159 | * To replace the contract, first selfdestruct the current contract, then call 160 | * with the same salt value and new initialization code (be aware that all 161 | * existing state will be wiped from the existing contract). Also note that 162 | * the first 20 bytes of the salt must match the calling address, which 163 | * prevents contracts from being created by unintended parties. 164 | * @param salt bytes32 The nonce that will be passed into the CREATE2 call and 165 | * thus will determine the resulant address of the metamorphic contract. 166 | * @param implementationContractInitializationCode bytes The initialization 167 | * code for the implementation contract for the metamorphic contract. It will 168 | * be used to deploy a new contract that the metamorphic contract will then 169 | * clone in its constructor. 170 | * @param metamorphicContractInitializationCalldata bytes An optional data 171 | * parameter that can be used to atomically initialize the metamorphic 172 | * contract. 173 | * @return Address of the metamorphic contract that will be created. 174 | */ 175 | function deployMetamorphicContract( 176 | bytes32 salt, 177 | bytes calldata implementationContractInitializationCode, 178 | bytes calldata metamorphicContractInitializationCalldata 179 | ) external payable containsCaller(salt) returns ( 180 | address metamorphicContractAddress 181 | ) { 182 | // move implementation init code and initialization calldata to memory. 183 | bytes memory implInitCode = implementationContractInitializationCode; 184 | bytes memory data = metamorphicContractInitializationCalldata; 185 | 186 | // move the initialization code from storage to memory. 187 | bytes memory initCode = _metamorphicContractInitializationCode; 188 | 189 | // declare variable to verify successful metamorphic contract deployment. 190 | address deployedMetamorphicContract; 191 | 192 | // determine the address of the metamorphic contract. 193 | metamorphicContractAddress = _getMetamorphicContractAddress(salt); 194 | 195 | // declare a variable for the address of the implementation contract. 196 | address implementationContract; 197 | 198 | // load implementation init code and length, then deploy via CREATE. 199 | /* solhint-disable no-inline-assembly */ 200 | assembly { 201 | let encoded_data := add(0x20, implInitCode) // load initialization code. 202 | let encoded_size := mload(implInitCode) // load init code's length. 203 | implementationContract := create( // call CREATE with 3 arguments. 204 | 0, // do not forward any endowment. 205 | encoded_data, // pass in initialization code. 206 | encoded_size // pass in init code's length. 207 | ) 208 | } /* solhint-enable no-inline-assembly */ 209 | 210 | require( 211 | implementationContract != address(0), 212 | "Could not deploy implementation." 213 | ); 214 | 215 | // store the implementation to be retrieved by the metamorphic contract. 216 | _implementations[metamorphicContractAddress] = implementationContract; 217 | 218 | // load metamorphic contract data and length of data and deploy via CREATE2. 219 | /* solhint-disable no-inline-assembly */ 220 | assembly { 221 | let encoded_data := add(0x20, initCode) // load initialization code. 222 | let encoded_size := mload(initCode) // load the init code's length. 223 | deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments. 224 | 0, // do not forward any endowment. 225 | encoded_data, // pass in initialization code. 226 | encoded_size, // pass in init code's length. 227 | salt // pass in the salt value. 228 | ) 229 | } /* solhint-enable no-inline-assembly */ 230 | 231 | // ensure that the contracts were successfully deployed. 232 | require( 233 | deployedMetamorphicContract == metamorphicContractAddress, 234 | "Failed to deploy the new metamorphic contract." 235 | ); 236 | 237 | // initialize the new metamorphic contract if any data or value is provided. 238 | if (data.length > 0 || msg.value > 0) { 239 | /* solhint-disable avoid-call-value */ 240 | (bool success,) = deployedMetamorphicContract.call.value(msg.value)(data); 241 | /* solhint-enable avoid-call-value */ 242 | 243 | require(success, "Failed to initialize the new metamorphic contract."); 244 | } 245 | 246 | emit Metamorphosed(deployedMetamorphicContract, implementationContract); 247 | } /* solhint-enable function-max-lines */ 248 | 249 | /** 250 | * @dev Deploy a metamorphic contract by submitting a given salt or nonce 251 | * along with the address of an existing implementation contract to clone, and 252 | * optionally provide calldata for initializing the new metamorphic contract. 253 | * To replace the contract, first selfdestruct the current contract, then call 254 | * with the same salt value and a new implementation address (be aware that 255 | * all existing state will be wiped from the existing contract). Also note 256 | * that the first 20 bytes of the salt must match the calling address, which 257 | * prevents contracts from being created by unintended parties. 258 | * @param salt bytes32 The nonce that will be passed into the CREATE2 call and 259 | * thus will determine the resulant address of the metamorphic contract. 260 | * @param implementationContract address The address of the existing 261 | * implementation contract to clone. 262 | * @param metamorphicContractInitializationCalldata bytes An optional data 263 | * parameter that can be used to atomically initialize the metamorphic 264 | * contract. 265 | * @return Address of the metamorphic contract that will be created. 266 | */ 267 | function deployMetamorphicContractFromExistingImplementation( 268 | bytes32 salt, 269 | address implementationContract, 270 | bytes calldata metamorphicContractInitializationCalldata 271 | ) external payable containsCaller(salt) returns ( 272 | address metamorphicContractAddress 273 | ) { 274 | // move initialization calldata to memory. 275 | bytes memory data = metamorphicContractInitializationCalldata; 276 | 277 | // move the initialization code from storage to memory. 278 | bytes memory initCode = _metamorphicContractInitializationCode; 279 | 280 | // declare variable to verify successful metamorphic contract deployment. 281 | address deployedMetamorphicContract; 282 | 283 | // determine the address of the metamorphic contract. 284 | metamorphicContractAddress = _getMetamorphicContractAddress(salt); 285 | 286 | // store the implementation to be retrieved by the metamorphic contract. 287 | _implementations[metamorphicContractAddress] = implementationContract; 288 | 289 | // using inline assembly: load data and length of data, then call CREATE2. 290 | /* solhint-disable no-inline-assembly */ 291 | assembly { 292 | let encoded_data := add(0x20, initCode) // load initialization code. 293 | let encoded_size := mload(initCode) // load the init code's length. 294 | deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments. 295 | 0, // do not forward any endowment. 296 | encoded_data, // pass in initialization code. 297 | encoded_size, // pass in init code's length. 298 | salt // pass in the salt value. 299 | ) 300 | } /* solhint-enable no-inline-assembly */ 301 | 302 | // ensure that the contracts were successfully deployed. 303 | require( 304 | deployedMetamorphicContract == metamorphicContractAddress, 305 | "Failed to deploy the new metamorphic contract." 306 | ); 307 | 308 | // initialize the new metamorphic contract if any data or value is provided. 309 | if (data.length > 0 || msg.value > 0) { 310 | /* solhint-disable avoid-call-value */ 311 | (bool success,) = metamorphicContractAddress.call.value(msg.value)(data); 312 | /* solhint-enable avoid-call-value */ 313 | 314 | require(success, "Failed to initialize the new metamorphic contract."); 315 | } 316 | 317 | emit Metamorphosed(deployedMetamorphicContract, implementationContract); 318 | } 319 | 320 | /* solhint-disable function-max-lines */ 321 | /** 322 | * @dev Deploy a metamorphic contract by submitting a given salt or nonce 323 | * along with the initialization code to a transient contract which will then 324 | * deploy the metamorphic contract before immediately selfdestructing. To 325 | * replace the metamorphic contract, first selfdestruct the current contract, 326 | * then call with the same salt value and new initialization code (be aware 327 | * that all existing state will be wiped from the existing contract). Also 328 | * note that the first 20 bytes of the salt must match the calling address, 329 | * which prevents contracts from being created by unintended parties. 330 | * @param salt bytes32 The nonce that will be passed into the CREATE2 call and 331 | * thus will determine the resulant address of the metamorphic contract. 332 | * @param initializationCode bytes The initialization code for the metamorphic 333 | * contract that will be deployed by the transient contract. 334 | * @return Address of the metamorphic contract that will be created. 335 | */ 336 | function deployMetamorphicContractWithConstructor( 337 | bytes32 salt, 338 | bytes calldata initializationCode 339 | ) external payable containsCaller(salt) returns ( 340 | address metamorphicContractAddress 341 | ) { 342 | // move transient contract initialization code from storage to memory. 343 | bytes memory initCode = _transientContractInitializationCode; 344 | 345 | // declare variable to verify successful transient contract deployment. 346 | address deployedTransientContract; 347 | 348 | // determine the address of the transient contract. 349 | address transientContractAddress = _getTransientContractAddress(salt); 350 | 351 | // store the initialization code to be retrieved by the transient contract. 352 | _initCodes[transientContractAddress] = initializationCode; 353 | 354 | // load transient contract data and length of data, then deploy via CREATE2. 355 | /* solhint-disable no-inline-assembly */ 356 | assembly { 357 | let encoded_data := add(0x20, initCode) // load initialization code. 358 | let encoded_size := mload(initCode) // load the init code's length. 359 | deployedTransientContract := create2( // call CREATE2 with 4 arguments. 360 | callvalue, // forward any supplied endowment. 361 | encoded_data, // pass in initialization code. 362 | encoded_size, // pass in init code's length. 363 | salt // pass in the salt value. 364 | ) 365 | } /* solhint-enable no-inline-assembly */ 366 | 367 | // ensure that the contracts were successfully deployed. 368 | require( 369 | deployedTransientContract == transientContractAddress, 370 | "Failed to deploy metamorphic contract using given salt and init code." 371 | ); 372 | 373 | metamorphicContractAddress = _getMetamorphicContractAddressWithConstructor( 374 | transientContractAddress 375 | ); 376 | 377 | emit MetamorphosedWithConstructor( 378 | metamorphicContractAddress, 379 | transientContractAddress 380 | ); 381 | } /* solhint-enable function-max-lines */ 382 | 383 | /** 384 | * @dev View function for retrieving the address of the implementation 385 | * contract to clone. Called by the constructor of each metamorphic contract. 386 | */ 387 | function getImplementation() external view returns (address implementation) { 388 | return _implementations[msg.sender]; 389 | } 390 | 391 | /** 392 | * @dev View function for retrieving the initialization code for a given 393 | * metamorphic contract to deploy via a transient contract. Called by the 394 | * constructor of each transient contract. 395 | * @return The initialization code to use to deploy the metamorphic contract. 396 | */ 397 | function getInitializationCode() external view returns ( 398 | bytes memory initializationCode 399 | ) { 400 | return _initCodes[msg.sender]; 401 | } 402 | 403 | /** 404 | * @dev View function for retrieving the address of the current implementation 405 | * contract of a given metamorphic contract, where the address of the contract 406 | * is supplied as an argument. Be aware that the implementation contract has 407 | * an independent state and may have been altered or selfdestructed from when 408 | * it was last cloned by the metamorphic contract. 409 | * @param metamorphicContractAddress address The address of the metamorphic 410 | * contract. 411 | * @return Address of the corresponding implementation contract. 412 | */ 413 | function getImplementationContractAddress( 414 | address metamorphicContractAddress 415 | ) external view returns (address implementationContractAddress) { 416 | return _implementations[metamorphicContractAddress]; 417 | } 418 | 419 | /** 420 | * @dev View function for retrieving the initialization code for a given 421 | * metamorphic contract instance deployed via a transient contract, where the address 422 | * of the transient contract is supplied as an argument. 423 | * @param transientContractAddress address The address of the transient 424 | * contract that deployed the metamorphic contract. 425 | * @return The initialization code used to deploy the metamorphic contract. 426 | */ 427 | function getMetamorphicContractInstanceInitializationCode( 428 | address transientContractAddress 429 | ) external view returns (bytes memory initializationCode) { 430 | return _initCodes[transientContractAddress]; 431 | } 432 | 433 | /** 434 | * @dev Compute the address of the metamorphic contract that will be created 435 | * upon submitting a given salt to the contract. 436 | * @param salt bytes32 The nonce passed into CREATE2 by metamorphic contract. 437 | * @return Address of the corresponding metamorphic contract. 438 | */ 439 | function findMetamorphicContractAddress( 440 | bytes32 salt 441 | ) external view returns (address metamorphicContractAddress) { 442 | // determine the address where the metamorphic contract will be deployed. 443 | metamorphicContractAddress = _getMetamorphicContractAddress(salt); 444 | } 445 | 446 | /** 447 | * @dev Compute the address of the transient contract that will be created 448 | * upon submitting a given salt to the contract. 449 | * @param salt bytes32 The nonce passed into CREATE2 when deploying the 450 | * transient contract. 451 | * @return Address of the corresponding transient contract. 452 | */ 453 | function findTransientContractAddress( 454 | bytes32 salt 455 | ) external view returns (address transientContractAddress) { 456 | // determine the address where the transient contract will be deployed. 457 | transientContractAddress = _getTransientContractAddress(salt); 458 | } 459 | 460 | /** 461 | * @dev Compute the address of the metamorphic contract that will be created 462 | * by the transient contract upon submitting a given salt to the contract. 463 | * @param salt bytes32 The nonce passed into CREATE2 when deploying the 464 | * transient contract. 465 | * @return Address of the corresponding metamorphic contract. 466 | */ 467 | function findMetamorphicContractAddressWithConstructor( 468 | bytes32 salt 469 | ) external view returns (address metamorphicContractAddress) { 470 | // determine the address of the metamorphic contract. 471 | metamorphicContractAddress = _getMetamorphicContractAddressWithConstructor( 472 | _getTransientContractAddress(salt) 473 | ); 474 | } 475 | 476 | /** 477 | * @dev View function for retrieving the initialization code of metamorphic 478 | * contracts for purposes of verification. 479 | */ 480 | function getMetamorphicContractInitializationCode() external view returns ( 481 | bytes memory metamorphicContractInitializationCode 482 | ) { 483 | return _metamorphicContractInitializationCode; 484 | } 485 | 486 | /** 487 | * @dev View function for retrieving the keccak256 hash of the initialization 488 | * code of metamorphic contracts for purposes of verification. 489 | */ 490 | function getMetamorphicContractInitializationCodeHash() external view returns ( 491 | bytes32 metamorphicContractInitializationCodeHash 492 | ) { 493 | return _metamorphicContractInitializationCodeHash; 494 | } 495 | 496 | /** 497 | * @dev View function for retrieving the initialization code of transient 498 | * contracts for purposes of verification. 499 | */ 500 | function getTransientContractInitializationCode() external view returns ( 501 | bytes memory transientContractInitializationCode 502 | ) { 503 | return _transientContractInitializationCode; 504 | } 505 | 506 | /** 507 | * @dev View function for retrieving the keccak256 hash of the initialization 508 | * code of transient contracts for purposes of verification. 509 | */ 510 | function getTransientContractInitializationCodeHash() external view returns ( 511 | bytes32 transientContractInitializationCodeHash 512 | ) { 513 | return _transientContractInitializationCodeHash; 514 | } 515 | 516 | /** 517 | * @dev Internal view function for calculating a metamorphic contract address 518 | * given a particular salt. 519 | */ 520 | function _getMetamorphicContractAddress( 521 | bytes32 salt 522 | ) internal view returns (address) { 523 | // determine the address of the metamorphic contract. 524 | return address( 525 | uint160( // downcast to match the address type. 526 | uint256( // convert to uint to truncate upper digits. 527 | keccak256( // compute the CREATE2 hash using 4 inputs. 528 | abi.encodePacked( // pack all inputs to the hash together. 529 | hex"ff", // start with 0xff to distinguish from RLP. 530 | address(this), // this contract will be the caller. 531 | salt, // pass in the supplied salt value. 532 | _metamorphicContractInitializationCodeHash // the init code hash. 533 | ) 534 | ) 535 | ) 536 | ) 537 | ); 538 | } 539 | 540 | /** 541 | * @dev Internal view function for calculating a transient contract address 542 | * given a particular salt. 543 | */ 544 | function _getTransientContractAddress( 545 | bytes32 salt 546 | ) internal view returns (address) { 547 | // determine the address of the transient contract. 548 | return address( 549 | uint160( // downcast to match the address type. 550 | uint256( // convert to uint to truncate upper digits. 551 | keccak256( // compute the CREATE2 hash using 4 inputs. 552 | abi.encodePacked( // pack all inputs to the hash together. 553 | hex"ff", // start with 0xff to distinguish from RLP. 554 | address(this), // this contract will be the caller. 555 | salt, // pass in the supplied salt value. 556 | _transientContractInitializationCodeHash // supply init code hash. 557 | ) 558 | ) 559 | ) 560 | ) 561 | ); 562 | } 563 | 564 | /** 565 | * @dev Internal view function for calculating a metamorphic contract address 566 | * that has been deployed via a transient contract given the address of the 567 | * transient contract. 568 | */ 569 | function _getMetamorphicContractAddressWithConstructor( 570 | address transientContractAddress 571 | ) internal pure returns (address) { 572 | // determine the address of the metamorphic contract. 573 | return address( 574 | uint160( // downcast to match the address type. 575 | uint256( // set to uint to truncate upper digits. 576 | keccak256( // compute CREATE hash via RLP encoding. 577 | abi.encodePacked( // pack all inputs to the hash together. 578 | byte(0xd6), // first RLP byte. 579 | byte(0x94), // second RLP byte. 580 | transientContractAddress, // called by the transient contract. 581 | byte(0x01) // nonce begins at 1 for contracts. 582 | ) 583 | ) 584 | ) 585 | ) 586 | ); 587 | } 588 | 589 | /** 590 | * @dev Modifier to ensure that the first 20 bytes of a submitted salt match 591 | * those of the calling account. This provides protection against the salt 592 | * being stolen by frontrunners or other attackers. 593 | * @param salt bytes32 The salt value to check against the calling address. 594 | */ 595 | modifier containsCaller(bytes32 salt) { 596 | // prevent contract submissions from being stolen from tx.pool by requiring 597 | // that the first 20 bytes of the submitted salt match msg.sender. 598 | require( 599 | address(bytes20(salt)) == msg.sender, 600 | "Invalid salt - first 20 bytes of the salt must match calling address." 601 | ); 602 | _; 603 | } 604 | } 605 | -------------------------------------------------------------------------------- /contracts/Metapod.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title Metapod 6 | * @author 0age 7 | * @notice This contract creates "hardened" metamorphic contracts, or contracts 8 | * that can be redeployed with new code to the same address, with additional 9 | * protections against creating non-metamorphic contracts or losing any balance 10 | * held by the contract when it is destroyed. It does so by first setting the 11 | * desired contract initialization code in temporary storage. Next, a vault 12 | * contract corresponding to the target address is checked for a balance, and if 13 | * one exists it will be sent to the address of an intermediate deployer, or a 14 | * transient contract with fixed, non-deterministic initialization code. Once 15 | * deployed via CREATE2, the transient contract retrieves the initialization 16 | * code from storage and uses it to deploy the contract via CREATE, forwarding 17 | * the entire balance to the new contract, and then SELFDESTRUCTs. Finally, the 18 | * contract prelude is checked to ensure that it is properly destructible and 19 | * that it designates the vault as the forwarding address. Once the contract 20 | * undergoes metamorphosis, all existing storage will be deleted and any balance 21 | * will be forwarded to the vault that can then resupply the metamorphic 22 | * contract upon redeployment. 23 | * @dev This contract has not yet been fully tested or audited - proceed with 24 | * abundant caution and please share any exploits or optimizations you discover. 25 | * Also, bear in mind that any initialization code provided to the contract must 26 | * contain the proper prelude, or initial sequence, with a length of 44 bytes: 27 | * 28 | * `0x6e03212eb796dee588acdbbbd777d4e73318602b5773 + vault_address + 0xff5b` 29 | * 30 | * For the required vault address, use `findVaultContractAddress(bytes32 salt)`. 31 | * Any initialization code generated by Solidity or another compiler will need 32 | * to have the stack items provided to JUMP, JUMPI, and CODECOPY altered 33 | * appropriately upon inserting this code, and may also need to alter some PC 34 | * operations (especially if it is not Solidity code). Be aware that contracts 35 | * are still accessible after they have been scheduled for deletion until the 36 | * transaction is completed, and that ether may still be sent to them - as the 37 | * funds forwarding step is performed immediately, not as part of the 38 | * transaction substate with the account removal. If those funds do not move to 39 | * a non-destructing account by the end of the transaction, they will be 40 | * irreversibly burned. Lastly, due to the mechanics of SELFDESTRUCT, a contract 41 | * cannot be destroyed and redeployed in a single transaction - to avoid 42 | * "downtime" of the contract, consider utilizing multiple contracts and having 43 | * the callers determine the current contract by using EXTCODEHASH. 44 | */ 45 | contract Metapod { 46 | // fires when a metamorphic contract is deployed. 47 | event Metamorphosed(address metamorphicContract, bytes32 salt); 48 | 49 | // fires when a metamorphic contract is destroyed. 50 | event Cocooned(address metamorphicContract, bytes32 salt); 51 | 52 | // initialization code for transient contract to deploy metamorphic contracts. 53 | /* ## op operation [stack] {return_buffer} *contract_deploy* 54 | 00 58 PC [0] 55 | 01 60 PUSH1 0x1c [0, 28] 56 | 03 59 MSIZE [0, 28, 0] 57 | 04 58 PC [0, 28, 0, 4] 58 | 05 59 MSIZE [0, 28, 0, 4, 0] 59 | 06 92 SWAP3 [0, 0, 0, 4, 28] 60 | 07 33 CALLER [0, 0, 0, 4, 28, caller] 61 | 08 5a GAS [0, 0, 0, 4, 28, caller, gas] 62 | 09 63 PUSH4 0x57b9f523 [0, 0, 0, 4, 28, caller, gas, selector] 63 | 14 59 MSIZE [0, 0, 0, 4, 28, caller, gas, selector, 0] 64 | 15 52 MSTORE [0, 0, 0, 4, 28, caller, gas] 65 | 16 fa STATICCALL [0, 1 => success] {init_code} 66 | 17 50 POP [0] 67 | 18 60 PUSH1 0x40 [0, 64] 68 | 20 30 ADDRESS [0, 64, address] 69 | 21 31 BALANCE [0, 64, balance] 70 | 22 81 DUP2 [0, 64, balance, 64] 71 | 23 3d RETURNDATASIZE [0, 64, balance, 64, size] 72 | 24 03 SUB [0, 64, balance, size - 64] 73 | 25 83 DUP4 [0, 64, balance, size - 64, 0] 74 | 26 92 SWAP3 [0, 0, balance, size - 64, 64] 75 | 27 81 DUP2 [0, 0, balance, size - 64, 64, size - 64] 76 | 28 94 SWAP5 [size - 64, 0, balance, size - 64, 64, 0] 77 | 29 3e RETURNDATACOPY [size - 64, 0, balance] 78 | 30 f0 CREATE [contract_address or 0] *init_code* 79 | 31 80 DUP1 [contract_address or 0, contract_address or 0] 80 | 32 15 ISZERO [contract_address or 0, 0 or 1] 81 | 33 60 PUSH1 0x25 [contract_address or 0, 0 or 1, 37] 82 | 35 57 JUMPI [contract_address] 83 | 36 ff SELFDESTRUCT [] 84 | 37 5b JUMPDEST [0] 85 | 38 80 DUP1 [0, 0] 86 | 39 fd REVERT [] 87 | */ 88 | bytes private constant TRANSIENT_CONTRACT_INITIALIZATION_CODE = ( 89 | hex"58601c59585992335a6357b9f5235952fa5060403031813d03839281943ef08015602557ff5b80fd" 90 | ); 91 | 92 | // store the hash of the initialization code for transient contracts as well. 93 | bytes32 private constant TRANSIENT_CONTRACT_INITIALIZATION_CODE_HASH = bytes32( 94 | 0xb7d11e258d6663925ce8e43f07ba3b7792a573ecc2fd7682d01f8a70b2223294 95 | ); 96 | 97 | // the "empty data hash" is used to determine if the vault has been deployed. 98 | bytes32 private constant EMPTY_DATA_HASH = bytes32( 99 | 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470 100 | ); 101 | 102 | // maintain a temporary storage slot for metamorphic initialization code. 103 | bytes private _initCode; 104 | 105 | constructor() public { 106 | // ensure that the deployment address is correct. 107 | // factory: 0x2415e7092bC80213E128536E6B22a54c718dC67A 108 | // caller: 0xaFD79DB96D018f333deb9ac821cc170F5cc81Ea8 109 | // init code hash: 0x8954ff8965dbf871b7b4f49acc85a2a7c96c93ebc16ba59a4d07c52d8d0b6ec2 110 | // salt: 0xafd79db96d018f333deb9ac821cc170f5cc81ea810f74f2d75490b0486060000 111 | require( 112 | address(this) == address(0x000000000003212eb796dEE588acdbBbD777D4E7), 113 | "Incorrect deployment address." 114 | ); 115 | 116 | // ensure the transient initialization code hash constant value is correct. 117 | require( 118 | keccak256( 119 | abi.encodePacked(TRANSIENT_CONTRACT_INITIALIZATION_CODE) 120 | ) == TRANSIENT_CONTRACT_INITIALIZATION_CODE_HASH, 121 | "Incorrect hash for transient initialization code." 122 | ); 123 | 124 | // ensure the empty data hash constant value is correct. 125 | require( 126 | keccak256(abi.encodePacked(hex"")) == EMPTY_DATA_HASH, 127 | "Incorrect hash for empty data." 128 | ); 129 | } 130 | 131 | /** 132 | * @dev Deploy a metamorphic contract by submitting a given salt or nonce 133 | * along with the initialization code to a transient contract which will then 134 | * deploy the metamorphic contract before immediately SELFDESTRUCTing. To 135 | * replace the metamorphic contract, call destroy() with the same salt value, 136 | * then call with the same salt value and new initialization code (be aware 137 | * that all existing state will be wiped from the contract). 138 | * @param identifier uint96 The last twelve bytes of the salt that will be 139 | * passed into the CREATE2 call (with the first twenty bytes of the salt set 140 | * to `msg.sender`) and thus will determine the resulant address of the 141 | * metamorphic contract. 142 | * @param initializationCode bytes The initialization code for the metamorphic 143 | * contract that will be deployed by the transient contract. 144 | * @return The address of the deployed metamorphic contract. 145 | */ 146 | function deploy( 147 | uint96 identifier, 148 | bytes calldata initializationCode 149 | ) external payable returns (address metamorphicContract) { 150 | // compute the salt using the supplied identifier. 151 | bytes32 salt = _getSalt(identifier); 152 | 153 | // store the initialization code to be retrieved by the transient contract. 154 | _initCode = initializationCode; 155 | 156 | // get vault contract and provide any funds therein to transient contract. 157 | address vaultContract = _triggerVaultFundsRelease(salt); 158 | 159 | // declare variable to verify successful transient contract deployment. 160 | address transientContract; 161 | 162 | // move transient contract initialization code into memory. 163 | bytes memory initCode = TRANSIENT_CONTRACT_INITIALIZATION_CODE; 164 | 165 | // load transient contract init data and size, then deploy via CREATE2. 166 | assembly { /* solhint-disable no-inline-assembly */ 167 | let encoded_data := add(0x20, initCode) // load initialization code. 168 | let encoded_size := mload(initCode) // load the init code's length. 169 | transientContract := create2( // call CREATE2 with 4 arguments. 170 | callvalue, // forward any supplied endowment. 171 | encoded_data, // pass in initialization code. 172 | encoded_size, // pass in init code's length. 173 | salt // pass in the salt value. 174 | ) 175 | } /* solhint-enable no-inline-assembly */ 176 | 177 | // ensure that the contracts were successfully deployed. 178 | require(transientContract != address(0), "Failed to deploy contract."); 179 | 180 | // get the address of the deployed metamorphic contract. 181 | metamorphicContract = _getMetamorphicContractAddress(transientContract); 182 | 183 | // ensure that the deployed runtime code has the required prelude. 184 | _verifyPrelude(metamorphicContract, _getPrelude(vaultContract)); 185 | 186 | // clear the supplied initialization code from temporary storage. 187 | delete _initCode; 188 | 189 | // emit an event to signify that the contract was successfully deployed. 190 | emit Metamorphosed(metamorphicContract, salt); 191 | } 192 | 193 | /** 194 | * @dev Destroy a metamorphic contract by calling into it, which will trigger 195 | * a SELFDESTRUCT and forward all funds to the designated vault contract. Be 196 | * aware that all existing state will be wiped from the contract. 197 | * @param identifier uint96 The last twelve bytes of the salt that was passed 198 | * into the CREATE2 call (with the first twenty bytes of the salt set to 199 | * `msg.sender`) that determined resulant address of the metamorphic contract. 200 | */ 201 | function destroy(uint96 identifier) external { 202 | // compute the salt using the supplied identifier. 203 | bytes32 salt = _getSalt(identifier); 204 | 205 | // determine the address of the metamorphic contract. 206 | address metamorphicContract = _getMetamorphicContractAddress( 207 | _getTransientContractAddress(salt) 208 | ); 209 | 210 | // call it to trigger a SELFDESTRUCT that forwards any funds to the vault. 211 | metamorphicContract.call(""); /* solhint-disable-line avoid-low-level-calls */ 212 | 213 | // emit an event to signify that the contract was scheduled for deletion. 214 | emit Cocooned(metamorphicContract, salt); 215 | } 216 | 217 | /** 218 | * @dev Recover the funds from a metamorphic contract, the associated vault, 219 | * and the associated transient contract by deploying a dedicated metamorphic 220 | * contract that will forward funds to `msg.sender` and immediately 221 | * SELFDESTRUCT. The contract must be "cocooned" or else it will fail. 222 | * @param identifier uint96 The last twelve bytes of the salt that was passed 223 | * into the CREATE2 call (with the first twenty bytes of the salt set to 224 | * `msg.sender`) that determined resulant address of the metamorphic contract. 225 | */ 226 | function recover(uint96 identifier) external { 227 | // compute the salt using the supplied identifier. 228 | bytes32 salt = _getSalt(identifier); 229 | 230 | // trigger the vault contract to forward funds to the transient contract. 231 | _triggerVaultFundsRelease(salt); 232 | 233 | // construct recovery contract initialization code and set in temp storage. 234 | _initCode = abi.encodePacked( 235 | bytes2(0x5873), // PC PUSH20 236 | msg.sender, // 237 | bytes13(0x905959593031856108fcf150ff) 238 | // SWAP1 MSIZEx3 ADDRESS BALANCE DUP6 PUSH2 2300 CALL POP SELFDESTRUCT 239 | ); 240 | 241 | // declare variable to verify successful transient contract deployment. 242 | address transientContract; 243 | 244 | // move transient contract initialization code into memory. 245 | bytes memory initCode = TRANSIENT_CONTRACT_INITIALIZATION_CODE; 246 | 247 | // load transient contract init data and size, then deploy via CREATE2. 248 | assembly { /* solhint-disable no-inline-assembly */ 249 | let encoded_data := add(0x20, initCode) // load initialization code. 250 | let encoded_size := mload(initCode) // load the init code's length. 251 | transientContract := create2( // call CREATE2 with 4 arguments. 252 | callvalue, // forward any supplied endowment. 253 | encoded_data, // pass in initialization code. 254 | encoded_size, // pass in init code's length. 255 | salt // pass in the salt value. 256 | ) 257 | } /* solhint-enable no-inline-assembly */ 258 | 259 | // ensure that the recovery contract was successfully deployed. 260 | require( 261 | transientContract != address(0), 262 | "Recovery failed - ensure that the contract has been destroyed." 263 | ); 264 | 265 | // clear recovery contract initialization code from temporary storage. 266 | delete _initCode; 267 | } 268 | 269 | /** 270 | * @dev View function for retrieving the initialization code for a given 271 | * metamorphic contract to deploy via a transient contract. Called by the 272 | * constructor of each transient contract - not meant to be called by users. 273 | * @return The initialization code to use to deploy the metamorphic contract. 274 | */ 275 | function getInitializationCode() external view returns ( 276 | bytes memory initializationCode 277 | ) { 278 | // return the current initialization code from temporary storage. 279 | initializationCode = _initCode; 280 | } 281 | 282 | /** 283 | * @dev Compute the address of the transient contract that will be created 284 | * upon submitting a given salt to the contract. 285 | * @param salt bytes32 The nonce passed into CREATE2 when deploying the 286 | * transient contract, composed of caller ++ identifier. 287 | * @return The address of the corresponding transient contract. 288 | */ 289 | function findTransientContractAddress( 290 | bytes32 salt 291 | ) external pure returns (address transientContract) { 292 | // determine the address where the transient contract will be deployed. 293 | transientContract = _getTransientContractAddress(salt); 294 | } 295 | 296 | /** 297 | * @dev Compute the address of the metamorphic contract that will be created 298 | * upon submitting a given salt to the contract. 299 | * @param salt bytes32 The nonce used to create the transient contract that 300 | * deploys the metamorphic contract, composed of caller ++ identifier. 301 | * @return The address of the corresponding metamorphic contract. 302 | */ 303 | function findMetamorphicContractAddress( 304 | bytes32 salt 305 | ) external pure returns (address metamorphicContract) { 306 | // determine the address of the metamorphic contract. 307 | metamorphicContract = _getMetamorphicContractAddress( 308 | _getTransientContractAddress(salt) 309 | ); 310 | } 311 | 312 | /** 313 | * @dev Compute the address of the vault contract that will be set as the 314 | * recipient of funds from the metamorphic contract when it is destroyed. 315 | * @param salt bytes32 The nonce used to create the transient contract that 316 | * deploys the metamorphic contract, composed of caller ++ identifier. 317 | * @return The address of the corresponding vault contract. 318 | */ 319 | function findVaultContractAddress( 320 | bytes32 salt 321 | ) external pure returns (address vaultContract) { 322 | vaultContract = _getVaultContractAddress( 323 | _getVaultContractInitializationCode( 324 | _getTransientContractAddress(salt) 325 | ) 326 | ); 327 | } 328 | 329 | /** 330 | * @dev View function for retrieving the prelude that will be required for any 331 | * metamorphic contract deployed via a specific salt. 332 | * @param salt bytes32 The nonce used to create the transient contract that 333 | * deploys the metamorphic contract, composed of caller ++ identifier. 334 | * @return The prelude that will be need to be present at the start of the 335 | * deployed runtime code for any metamorphic contracts deployed using the 336 | * provided salt. 337 | */ 338 | function getPrelude(bytes32 salt) external pure returns ( 339 | bytes memory prelude 340 | ) { 341 | // compute and return the prelude. 342 | prelude = _getPrelude( 343 | _getVaultContractAddress( 344 | _getVaultContractInitializationCode( 345 | _getTransientContractAddress(salt) 346 | ) 347 | ) 348 | ); 349 | } 350 | 351 | /** 352 | * @dev View function for retrieving the initialization code of metamorphic 353 | * contracts for purposes of verification. 354 | * @return The initialization code used to deploy transient contracts. 355 | */ 356 | function getTransientContractInitializationCode() external pure returns ( 357 | bytes memory transientContractInitializationCode 358 | ) { 359 | // return the initialization code used to deploy transient contracts. 360 | transientContractInitializationCode = ( 361 | TRANSIENT_CONTRACT_INITIALIZATION_CODE 362 | ); 363 | } 364 | 365 | /** 366 | * @dev View function for retrieving the keccak256 hash of the initialization 367 | * code of metamorphic contracts for purposes of verification. 368 | * @return The keccak256 hash of the initialization code used to deploy 369 | * transient contracts. 370 | */ 371 | function getTransientContractInitializationCodeHash() external pure returns ( 372 | bytes32 transientContractInitializationCodeHash 373 | ) { 374 | // return hash of initialization code used to deploy transient contracts. 375 | transientContractInitializationCodeHash = ( 376 | TRANSIENT_CONTRACT_INITIALIZATION_CODE_HASH 377 | ); 378 | } 379 | 380 | /** 381 | * @dev View function for calculating a salt given a particular caller and 382 | * identifier. 383 | * @param identifier bytes12 The last twelve bytes of the salt (the first 384 | * twenty bytes are set to `msg.sender`). 385 | * @return The salt that will be supplied to CREATE2 upon providing the given 386 | * identifier from the calling account. 387 | */ 388 | function getSalt(uint96 identifier) external view returns (bytes32 salt) { 389 | salt = _getSalt(identifier); 390 | } 391 | 392 | /** 393 | * @dev Internal view function for calculating a salt given a particular 394 | * caller and identifier. 395 | * @param identifier bytes12 The last twelve bytes of the salt (the first 396 | * twenty bytes are set to `msg.sender`). 397 | * @return The salt that will be supplied to CREATE2. 398 | */ 399 | function _getSalt(uint96 identifier) internal view returns (bytes32 salt) { 400 | assembly { /* solhint-disable no-inline-assembly */ 401 | salt := or(shl(96, caller), identifier) // caller: first 20, ID: last 12 402 | } /* solhint-enable no-inline-assembly */ 403 | } 404 | 405 | /** 406 | * @dev Internal function for determining the required prelude for metamorphic 407 | * contracts deployed through the factory based on the corresponding vault 408 | * contract. 409 | * @param vaultContract address The address of the vault contract. 410 | * @return The prelude that will be required for given a vault contract. 411 | */ 412 | function _getPrelude( 413 | address vaultContract 414 | ) internal pure returns (bytes memory prelude) { 415 | prelude = abi.encodePacked( 416 | // PUSH15 CALLER XOR PUSH1 43 JUMPI PUSH20 417 | bytes22(0x6e03212eb796dee588acdbbbd777d4e73318602b5773), 418 | vaultContract, // 419 | bytes2(0xff5b) // SELFDESTRUCT JUMPDEST 420 | ); 421 | } 422 | 423 | /** 424 | * @dev Internal function for determining if deployed metamorphic contract has 425 | * the necessary prelude at the start of its runtime code. The prelude ensures 426 | * that the contract can be destroyed by a call originating from this contract 427 | * and that any funds will be forwarded to the corresponding vault contract. 428 | * @param metamorphicContract address The address of the metamorphic contract. 429 | * @param prelude bytes The prelude that must be present on the contract. 430 | */ 431 | function _verifyPrelude( 432 | address metamorphicContract, 433 | bytes memory prelude 434 | ) internal view { 435 | // get the first 44 bytes of metamorphic contract runtime code. 436 | bytes memory runtimeHeader; 437 | 438 | assembly { /* solhint-disable no-inline-assembly */ 439 | // set and update the pointer based on the size of the runtime header. 440 | runtimeHeader := mload(0x40) 441 | mstore(0x40, add(runtimeHeader, 0x60)) 442 | 443 | // store the runtime code and length in memory. 444 | mstore(runtimeHeader, 44) 445 | extcodecopy(metamorphicContract, add(runtimeHeader, 0x20), 0, 44) 446 | } /* solhint-enable no-inline-assembly */ 447 | 448 | // ensure that the contract's runtime code has the correct initial sequence. 449 | require( 450 | keccak256( 451 | abi.encodePacked(prelude) 452 | ) == keccak256( 453 | abi.encodePacked(runtimeHeader) 454 | ), 455 | "Deployed runtime code does not have the required prelude." 456 | ); 457 | } 458 | 459 | /** 460 | * @dev Internal function for determining if a vault contract has a balance 461 | * and tranferring the balance to the corresponding transient contract if so. 462 | * This is achieved via deploying the vault contract if no contract exists yet 463 | * or by calling the contract if it has already been deployed. 464 | * @param salt bytes32 The nonce used to create the transient contract that 465 | * deploys the metamorphic contract associated with a corresponding vault. 466 | * @return The address of the vault contract. 467 | */ 468 | function _triggerVaultFundsRelease( 469 | bytes32 salt 470 | ) internal returns (address vaultContract) { 471 | // determine the address of the transient contract. 472 | address transientContract = _getTransientContractAddress(salt); 473 | 474 | // determine the initialization code of the vault contract. 475 | bytes memory vaultContractInitCode = _getVaultContractInitializationCode( 476 | transientContract 477 | ); 478 | 479 | // determine the address of the vault contract. 480 | vaultContract = _getVaultContractAddress(vaultContractInitCode); 481 | 482 | // determine if the vault has a balance. 483 | if (vaultContract.balance > 0) { 484 | // determine if the vault has already been deployed. 485 | bytes32 vaultContractCodeHash; 486 | 487 | assembly { /* solhint-disable no-inline-assembly */ 488 | vaultContractCodeHash := extcodehash(vaultContract) 489 | } /* solhint-enable no-inline-assembly */ 490 | 491 | // if it hasn't been deployed, deploy it to send funds to transient. 492 | if (vaultContractCodeHash == EMPTY_DATA_HASH) { 493 | assembly { /* solhint-disable no-inline-assembly */ 494 | let encoded_data := add(0x20, vaultContractInitCode) // init code. 495 | let encoded_size := mload(vaultContractInitCode) // init length. 496 | let _ := create2( // call CREATE2. 497 | 0, // do not supply any endowment. 498 | encoded_data, // pass in initialization code. 499 | encoded_size, // pass in init code's length. 500 | 0 // pass in zero as the salt value. 501 | ) 502 | } /* solhint-enable no-inline-assembly */ 503 | // otherwise, just call it which will also send funds to transient. 504 | } else { 505 | vaultContract.call(""); /* solhint-disable-line avoid-low-level-calls */ 506 | } 507 | } 508 | } 509 | 510 | /** 511 | * @dev Internal view function for calculating a transient contract address 512 | * given a particular salt. 513 | * @param salt bytes32 The nonce used to create the transient contract. 514 | * @return The address of the transient contract. 515 | */ 516 | function _getTransientContractAddress( 517 | bytes32 salt 518 | ) internal pure returns (address transientContract) { 519 | // determine the address of the transient contract. 520 | transientContract = address( 521 | uint160( // downcast to match the address type. 522 | uint256( // convert to uint to truncate upper digits. 523 | keccak256( // compute the CREATE2 hash using 4 inputs. 524 | abi.encodePacked( // pack all inputs to the hash together. 525 | hex"ff", // start with 0xff to distinguish from RLP. 526 | address(0x000000000003212eb796dEE588acdbBbD777D4E7), // this. 527 | salt, // pass in the supplied salt value. 528 | TRANSIENT_CONTRACT_INITIALIZATION_CODE_HASH // the init code hash. 529 | ) 530 | ) 531 | ) 532 | ) 533 | ); 534 | } 535 | 536 | /** 537 | * @dev Internal view function for calculating a metamorphic contract address 538 | * that has been deployed via a transient contract given the address of the 539 | * transient contract. 540 | * @param transientContract address The address of the transient contract. 541 | * @return The address of the metamorphic contract. 542 | */ 543 | function _getMetamorphicContractAddress( 544 | address transientContract 545 | ) internal pure returns (address metamorphicContract) { 546 | // determine the address of the metamorphic contract. 547 | metamorphicContract = address( 548 | uint160( // downcast to match the address type. 549 | uint256( // set to uint to truncate upper digits. 550 | keccak256( // compute CREATE hash via RLP encoding. 551 | abi.encodePacked( // pack all inputs to the hash together. 552 | bytes2(0xd694), // first two RLP bytes. 553 | transientContract, // called by the transient contract. 554 | byte(0x01) // nonce begins at 1 for contracts. 555 | ) 556 | ) 557 | ) 558 | ) 559 | ); 560 | } 561 | 562 | /** 563 | * @dev Internal view function for calculating a initialization code for a 564 | * given vault contract based on the corresponding transient contract. 565 | * @param transientContract address The address of the transient contract. 566 | * @return The initialization code for the vault contract. 567 | */ 568 | function _getVaultContractInitializationCode( 569 | address transientContract 570 | ) internal pure returns (bytes memory vaultContractInitializationCode) { 571 | vaultContractInitializationCode = abi.encodePacked( 572 | // PC PUSH15 CALLER XOR PC JUMPI MSIZEx3 ADDRESS BALANCE PUSH20 573 | bytes27(0x586e03212eb796dee588acdbbbd777d4e733185857595959303173), 574 | // the transient contract is the recipient of funds 575 | transientContract, 576 | // GAS CALL PUSH1 49 MSIZE DUP2 MSIZEx2 CODECOPY RETURN 577 | bytes10(0x5af160315981595939f3) 578 | ); 579 | } 580 | 581 | /** 582 | * @dev Internal view function for calculating a vault contract address given 583 | * the initialization code for the vault contract. 584 | * @param vaultContractInitializationCode bytes The initialization code of the 585 | * vault contract. 586 | * @return The address of the vault contract. 587 | */ 588 | function _getVaultContractAddress( 589 | bytes memory vaultContractInitializationCode 590 | ) internal pure returns (address vaultContract) { 591 | // determine the address of the vault contract. 592 | vaultContract = address( 593 | uint160( // downcast to match the address type. 594 | uint256( // convert to uint to truncate upper digits. 595 | keccak256( // compute the CREATE2 hash using 4 inputs. 596 | abi.encodePacked( // pack all inputs to the hash together. 597 | byte(0xff), // start with 0xff to distinguish from RLP. 598 | address(0x000000000003212eb796dEE588acdbBbD777D4E7), // this. 599 | bytes32(0), // leave the salt value set to zero. 600 | keccak256( // hash the supplied initialization code. 601 | vaultContractInitializationCode 602 | ) 603 | ) 604 | ) 605 | ) 606 | ) 607 | ); 608 | } 609 | } 610 | -------------------------------------------------------------------------------- /contracts/TransientContract.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title Metamorphic Contract Factory Interface 6 | * @notice An interface to the factory contract that holds a reference to the 7 | * initialization code that will be used by the transient contract to deploy 8 | * the metamorphic contract. 9 | */ 10 | interface FactoryInterface { 11 | function getInitializationCode() external view returns ( 12 | bytes memory initializationCode 13 | ); 14 | } 15 | 16 | 17 | /** 18 | * @title Transient Contract 19 | * @author 0age 20 | * @notice This contract will create a metamorphic contract, or an upgradeable 21 | * contract that does not rely on a transparent proxy, when deployed using 22 | * CREATE2. Unlike with upgradeable transparent proxies, the state of a 23 | * metamorphic contract will be wiped clean with each upgrade. The metamorphic 24 | * contract can also use a constructor if desired. With great power comes great 25 | * responsibility - implement appropriate controls and educate the users of your 26 | * contract if it will be interacted with! 27 | */ 28 | contract TransientContract { 29 | /** 30 | * @dev In the constructor, retrieve the initialization code for the new 31 | * version of the metamorphic contract, use it to deploy the metamorphic 32 | * contract while forwarding any value, and destroy the transient contract. 33 | */ 34 | constructor() public payable { 35 | // retrieve the target implementation address from creator of this contract. 36 | bytes memory initCode = FactoryInterface(msg.sender).getInitializationCode(); 37 | 38 | // set up a memory location for the address of the new metamorphic contract. 39 | address payable metamorphicContractAddress; 40 | 41 | // deploy the metamorphic contract address using the supplied init code. 42 | /* solhint-disable no-inline-assembly */ 43 | assembly { 44 | let encoded_data := add(0x20, initCode) // load initialization code. 45 | let encoded_size := mload(initCode) // load init code's length. 46 | metamorphicContractAddress := create( // call CREATE with 3 arguments. 47 | callvalue, // forward any supplied endowment. 48 | encoded_data, // pass in initialization code. 49 | encoded_size // pass in init code's length. 50 | ) 51 | } /* solhint-enable no-inline-assembly */ 52 | 53 | // ensure that the metamorphic contract was successfully deployed. 54 | require(metamorphicContractAddress != address(0)); 55 | 56 | // destroy transient contract and forward all value to metamorphic contract. 57 | selfdestruct(metamorphicContractAddress); 58 | } 59 | } -------------------------------------------------------------------------------- /contracts/mock/CodeCheck.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title CodeCheck 6 | * @notice This contract checks the deployed runtime code of another contract. 7 | */ 8 | contract CodeCheck { 9 | function check(address target) public view returns (bytes memory code) { 10 | /* solhint-disable no-inline-assembly */ 11 | assembly { 12 | // retrieve the size of the external code 13 | let size := extcodesize(target) 14 | // allocate output byte array 15 | code := mload(0x40) 16 | // new "memory end" including padding 17 | mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) 18 | // store length in memory 19 | mstore(code, size) 20 | // get the code using extcodecopy 21 | extcodecopy(target, add(code, 0x20), 0, size) 22 | } /* solhint-enable no-inline-assembly */ 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /contracts/mock/ContractOne.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title ContractOne 6 | * @notice This is the first implementation of an example metamorphic contract. 7 | */ 8 | contract ContractOne { 9 | uint256 private _x; 10 | 11 | /** 12 | * @dev test function 13 | * @return 1 once initialized (otherwise 0) 14 | */ 15 | function test() external view returns (uint256 value) { 16 | return _x; 17 | } 18 | 19 | /** 20 | * @dev initialize function 21 | */ 22 | function initialize() public { 23 | _x = 1; 24 | } 25 | 26 | /** 27 | * @dev destroy function, allows for the metamorphic contract to be redeployed 28 | */ 29 | function destroy() public { 30 | selfdestruct(msg.sender); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /contracts/mock/ContractTwo.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title ContractTwo 6 | * @notice This is the second implementation of an example metamorphic contract. 7 | */ 8 | contract ContractTwo { 9 | event Paid(uint256 amount); 10 | 11 | uint256 private _x; 12 | 13 | /** 14 | * @dev Payable fallback function that emits an event logging the payment 15 | */ 16 | function () external payable { 17 | if (msg.value > 0) { 18 | emit Paid(msg.value); 19 | } 20 | } 21 | 22 | /** 23 | * @dev Test function 24 | * @return 0 - storage is NOT carried over from the first implementation 25 | */ 26 | function test() external view returns (uint256 value) { 27 | return _x; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /contracts/mock/ContractWithConstructor.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | /** 5 | * @title ContractWithConstructor 6 | * @notice This is an implementation of an example metamorphic contract that has 7 | * a constructor function rather than an initializer function. 8 | */ 9 | contract ContractWithConstructor { 10 | uint256 private _x; 11 | 12 | /** 13 | * @dev constructor function 14 | */ 15 | constructor() public { 16 | _x = 3; 17 | } 18 | 19 | /** 20 | * @dev test function 21 | * @return 3 (set in constructor) 22 | */ 23 | function test() external view returns (uint256 value) { 24 | return _x; 25 | } 26 | 27 | /** 28 | * @dev destroy function, allows for the metamorphic contract to be redeployed 29 | */ 30 | function destroy() public { 31 | selfdestruct(msg.sender); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /contracts/mock/KakunaBasicTest.sol: -------------------------------------------------------------------------------- 1 | pragma solidity 0.5.6; 2 | 3 | 4 | contract KakunaBasicTest { 5 | string private constant TEST_STRING = "This is an example string for testing CODECOPY."; 6 | 7 | bool private _constructorTest; 8 | 9 | constructor() public { 10 | _constructorTest = true; 11 | } 12 | 13 | function constructorTest() public view returns (bool) { 14 | return _constructorTest; 15 | } 16 | 17 | function codecopyTest() public pure returns (string memory) { 18 | return TEST_STRING; 19 | } 20 | 21 | function jumpTest() public view returns (bool) { 22 | if (uint160(gasleft()) % 2 == 0) { 23 | return _jumpTestOne(); 24 | } 25 | 26 | return _jumpTestTwo(); 27 | } 28 | 29 | function _jumpTestOne() internal pure returns (bool) { 30 | return false; 31 | } 32 | 33 | function _jumpTestTwo() internal pure returns (bool) { 34 | uint256 x; 35 | for (uint256 i; i < 10; i++) { 36 | x = i; 37 | } 38 | return true; 39 | } 40 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "metamorphic", 3 | "version": "0.3.2", 4 | "description": "A factory contract for creating metamorphic (i.e. redeployable) contracts.", 5 | "author": "0age", 6 | "license": "MIT", 7 | "dependencies": { 8 | "ethereumjs-util": "^6.1.0", 9 | "ethereumjs-vm": "^2.6.0", 10 | "ganache-cli": "6.3.0", 11 | "js-sha256": "^0.9.0", 12 | "solhint": "^1.4.1", 13 | "truffle": "5.0.4", 14 | "web3": "1.0.0-beta.37" 15 | }, 16 | "scripts": { 17 | "all": "./node_modules/.bin/ganache-cli 2>&1 > ganache-output.log & echo 'local chain started.' && ./node_modules/.bin/truffle compile && node scripts/kakuna/ci.js KakunaBasicTest 0x4150 && node scripts/test/ci.js && ./node_modules/.bin/solhint 'contracts/**/*.sol'; kill -9 \"$(ps -ax | grep '[n]ode ./node_modules/.bin/ganache-cli' | awk '{print $1;}')\" && echo 'local chain stopped.'", 18 | "build": "./node_modules/.bin/truffle compile && node scripts/kakuna/ci.js KakunaBasicTest 0x4150", 19 | "ci": "./node_modules/.bin/ganache-cli -q & echo 'local chain started.' && ./node_modules/.bin/truffle compile && node scripts/kakuna/ci.js KakunaBasicTest 0x4150 && node scripts/test/ci.js && ./node_modules/.bin/solhint 'contracts/**/*.sol'", 20 | "kakuna": "node scripts/kakuna/ci.js", 21 | "linter": "./node_modules/.bin/solhint 'contracts/**/*.sol'", 22 | "start": "./node_modules/.bin/ganache-cli 2>&1 > ganache-output.log & echo 'local chain started.'", 23 | "stop": "kill -9 \"$(ps -ax | grep '[n]ode ./node_modules/.bin/ganache-cli' | awk '{print $1;}')\" && echo 'local chain stopped.'", 24 | "test": "./node_modules/.bin/truffle compile && node scripts/kakuna/ci.js KakunaBasicTest 0x4150 && node scripts/test/ci.js" 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /scripts/kakuna/ci.js: -------------------------------------------------------------------------------- 1 | var kakuna = require('./kakuna.js') 2 | 3 | async function main() { 4 | if (process.argv.length < 4) { 5 | throw new Error('supply the name of the compiled contract and the bytecode of the prelude as arguments.') 6 | } 7 | 8 | const contractName = process.argv[2] 9 | const preludeRaw = process.argv[3] 10 | 11 | if (!(preludeRaw.slice(0, 2) === '0x')) { 12 | throw new Error('Be sure to format the prelude as a hex string: `0xc0de...`') 13 | } 14 | let initCode 15 | let runtimeCode 16 | 17 | code = await kakuna.kakuna(contractName, preludeRaw, true) 18 | 19 | console.log('INIT CODE:', code[0]) 20 | console.log() 21 | console.log('RUNTIME CODE:', code[1]) 22 | } 23 | 24 | main() -------------------------------------------------------------------------------- /scripts/kakuna/kakuna.js: -------------------------------------------------------------------------------- 1 | BN = require('bn.js') 2 | fs = require('fs') 3 | utils = require('ethereumjs-util') 4 | VM = require('ethereumjs-vm') 5 | Web3 = require('web3') 6 | 7 | module.exports = {kakuna: async function (contractName, preludeRaw, logging) { 8 | vm = new VM() 9 | web3 = new Web3() 10 | 11 | const prelude = Buffer.from(preludeRaw.slice(2), 'hex') 12 | const preludeSize = prelude.length 13 | 14 | if (logging) { 15 | console.log(`Attempting jump analysis and prelude insertion for ${contractName} contract.`) 16 | console.log(`Prelude: ${preludeRaw} (length: ${preludeSize} bytes)`) 17 | } 18 | 19 | // get the bytecode of the test contract that we want to add a prelude to 20 | const Artifact = require(`../../build/contracts/${contractName}.json`) 21 | 22 | /* init code: checks and constructor logic, then keyword, then runtime code. 23 | 24 | keyword: 0x61****8061****60003960f3fe (13 bytes) 25 | 26 | # op opcode 27 | 0 61 PUSH2 0x022b // length - this will need to be increased 28 | 1 80 DUP1 // duplicated so the return can use it too 29 | 2 61 PUSH2 0x003a // offset - this will not change 30 | 3 60 PUSH1 0x00 // dest offset - runtime code at 0 in memory 31 | 4 39 CODECOPY // get the code and place it in memory 32 | 5 60 PUSH1 0x00 // return offset - runtime starting at 0 33 | 6 F3 RETURN // return runtime code from memory and deploy 34 | 35 | also note that the PUSH2s might actually be PUSH1s, depending on the size. 36 | */ 37 | let initCode = Buffer.from(Artifact.bytecode.slice(2), 'hex') 38 | 39 | // parse out the init code by each instruction. 40 | initOps = nameOpCodes(initCode) 41 | 42 | // find the first occurrence of the full sequence of opcodes in the keyword 43 | const sequence = ['PUSH', 'DUP1', 'PUSH', 'PUSH', 'CODECOPY', 'PUSH', 'RETURN'] 44 | let candidate = { 45 | sequenceStart: 0, 46 | sequenceProgress: 0, 47 | found: false 48 | } 49 | initOps.some((op, i) => { 50 | if (op.opcode.includes(sequence[candidate.sequenceProgress])) { 51 | candidate.sequenceProgress++ 52 | } else { 53 | candidate.sequenceStart = i + 1 54 | candidate.sequenceProgress = 0 55 | } 56 | if (candidate.sequenceProgress >= 7) { 57 | candidate.found = true 58 | return true 59 | } 60 | }) 61 | 62 | if (!candidate.found) { 63 | throw new Error( 64 | 'No valid runtime keyword found for supplied initialization code.' 65 | ) 66 | } 67 | 68 | // mark the start of runtime code based on the extracted keyword. 69 | const codeStart = parseInt(initOps[candidate.sequenceStart + 2].push.data, 16) 70 | 71 | if (logging) { 72 | console.log() 73 | console.log( 74 | '##################################################################' 75 | ) 76 | console.log( 77 | `runtime payload at instruction #${ 78 | candidate.sequenceStart 79 | }: pc ${ 80 | initOps[candidate.sequenceStart].pc 81 | }, length ${ 82 | parseInt(initOps[candidate.sequenceStart].push.data, 16) 83 | }, offset ${ 84 | codeStart 85 | }.` 86 | ) 87 | console.log(initOps[candidate.sequenceStart]) 88 | console.log( 89 | '##################################################################' 90 | ) 91 | } 92 | 93 | // get the initialization code without the runtime portion. 94 | initCodeWithoutRuntime = initCode.slice(0, codeStart) 95 | 96 | // get the runtime code and ensure that it matches the existing runtime code 97 | let runtime = Buffer.from(Artifact.deployedBytecode.slice(2), 'hex') 98 | extractedRuntime = initCode.slice(codeStart, initCode.length) 99 | if (!(runtime.toString() === extractedRuntime.toString())) { 100 | throw new Error('runtime does not match expected runtime.') 101 | process.exit() 102 | } 103 | 104 | // parse out individual op instructions from runtime (remove metadata hash) 105 | ops = nameOpCodes(Buffer.from(Artifact.deployedBytecode.slice(2, -86), 'hex')) 106 | 107 | // include each prior instruction - used to check for adjacent PUSHes + JUMPs. 108 | priors = [null].concat(ops.slice(0, -1)) 109 | opsAndPriors = [] 110 | ops.forEach((op, index) => { 111 | if (priors[index] !== null) { 112 | opsAndPriors.push({ 113 | i: index, 114 | x: op.x, 115 | pc: op.pc, 116 | opcode: op.opcode, 117 | push: {data: op.push.data}, 118 | prior: { 119 | x: priors[index].x, 120 | pc: priors[index].pc, 121 | opcode: priors[index].opcode, 122 | push: {data: priors[index].push.data} 123 | } 124 | }) 125 | } else { 126 | opsAndPriors.push({ 127 | i: index, 128 | x: op.x, 129 | pc: op.pc, 130 | opcode: op.opcode, 131 | push: {data: op.push.data}, 132 | prior: null 133 | }) 134 | } 135 | }) 136 | 137 | // locate and index each PUSH instruction. 138 | originalPushes = [] 139 | originalPushIndexes = {} 140 | originalPushIndex = 0 141 | opsAndPriors.forEach(op => { 142 | if (op.opcode.includes('PUSH')) { 143 | originalPushIndexes[op.pc] = originalPushIndex 144 | originalPushes.push(op) 145 | originalPushIndex++ 146 | } 147 | }) 148 | 149 | // locate and index each JUMP / JUMPI instruction. 150 | originalJumps = [] 151 | originalJumpIndexes = {} 152 | originalJumpIndex = 0 153 | opsAndPriors.forEach(op => { 154 | if (op.opcode.includes('JUMP') && !op.opcode.includes('JUMPDEST')) { 155 | originalJumpIndexes[op.pc] = originalJumpIndex 156 | originalJumps.push(op) 157 | originalJumpIndex++ 158 | } 159 | }) 160 | 161 | // locate and index each JUMPDEST instruction. 162 | originalJumpdests = [] 163 | originalJumpdestIndexes = {} 164 | originalJumpdestIndex = 0 165 | opsAndPriors.forEach(op => { 166 | if (op.opcode.includes('JUMPDEST')) { 167 | originalJumpdestIndexes[op.pc] = originalJumpdestIndex 168 | originalJumpdests.push(op) 169 | originalJumpdestIndex++ 170 | } 171 | }) 172 | 173 | // construct a more detailed set of instructions with contextual information. 174 | opsAndJumps = [] 175 | opsAndPriors.forEach(op => { 176 | if (op.opcode.includes("JUMP")) { 177 | if (op.opcode.includes("DEST")) { 178 | opsAndJumps.push({ 179 | i: op.i, 180 | x: op.x, 181 | pc: op.pc, 182 | opcode: op.opcode, 183 | push: {data: op.push.data}, 184 | prior: op.prior, 185 | jump: null, 186 | jumpdest: { 187 | index: originalJumpdestIndexes[op.pc] 188 | } 189 | }) 190 | } else { 191 | opsAndJumps.push({ 192 | i: op.i, 193 | x: op.x, 194 | pc: op.pc, 195 | opcode: op.opcode, 196 | push: {data: op.push.data}, 197 | prior: op.prior, 198 | jump: { 199 | index: originalJumpIndexes[op.pc] 200 | }, 201 | jumpdest: null 202 | }) 203 | } 204 | } else if (op.opcode.includes("PUSH")) { 205 | opsAndJumps.push({ 206 | i: op.i, 207 | x: op.x, 208 | pc: op.pc, 209 | opcode: op.opcode, 210 | push: {index: originalPushIndexes[op.pc], data: op.push.data, dataBaseTen: parseInt(op.push.data, 16)}, 211 | prior: op.prior, 212 | jump: null, 213 | jumpdest: null 214 | }) 215 | } else { 216 | opsAndJumps.push({ 217 | i: op.i, 218 | x: op.x, 219 | pc: op.pc, 220 | opcode: op.opcode, 221 | push: {data: op.push.data, dataBaseTen: parseInt(op.push.data, 16)}, 222 | prior: op.prior, 223 | jump: null, 224 | jumpdest: null 225 | }) 226 | } 227 | }) 228 | 229 | // create a mapping from each program counter to each instruction index. 230 | instructions = {} 231 | opsAndJumps.forEach(op => { 232 | instructions[op.pc] = op.i 233 | }) 234 | 235 | // create a new instance of a Stack class. 236 | stack = new Stack() 237 | 238 | // create a new instance of a Counter class. 239 | counter = new Counter() 240 | 241 | // track which code branches have already been run (circular references) 242 | completedOneBranches = {} 243 | completedZeroBranches = {} 244 | 245 | // run through code & build up modified stack until halt or breaker is tripped 246 | breaker = 0 247 | function run(fork, currentStack, counter) { 248 | while (!counter.stopped && breaker < 10000) { 249 | var op = opsAndJumps[counter.instruction]; 250 | var stackSize = currentStack.length 251 | 252 | // make sure the opcode is included in opFns 253 | if (!op.opcode in opFns) { 254 | console.log('DEBUG:', op.opcode) 255 | throw new Error('Missing opcode') 256 | } 257 | 258 | // update CODECOPY in the event that the offset stack item is known 259 | if ( 260 | op.opcode === 'CODECOPY' && 261 | currentStack._store[currentStack.length - 2].constant === true 262 | ) { 263 | opsAndJumps[counter.instruction].codecopy = { 264 | constant: true, 265 | origin: currentStack._store[currentStack.length - 2].origin, 266 | codeOffset: currentStack._store[currentStack.length - 2].item, 267 | duplicated: currentStack._store[currentStack.length - 2].duplicated 268 | } 269 | } 270 | 271 | // strip out number from PUSH / DUP / SWAP / LOG, will be added back later 272 | if ( 273 | op.opcode.includes("PUSH") || 274 | op.opcode.includes("DUP") || 275 | op.opcode.includes("SWAP") || 276 | op.opcode.includes("LOG") 277 | ) { 278 | // execute the instruction 279 | opFns[op.opcode.slice( 280 | 0, 281 | ( 282 | op.opcode.includes("DUP") || 283 | op.opcode.includes("LOG") 284 | ) ? 3 : 4 285 | )](op, currentStack, counter) 286 | 287 | // ensure that the stack size is still OK 288 | if (stackSize + stackDelta(op) !== currentStack.length) { 289 | console.log('DEBUG:', op, stackSize, stackDelta(op), currentStack.length) 290 | throw new Error('unexpected stack size change!') 291 | } 292 | // For JUMPI: if condition is unknown, fork and take both possible paths 293 | } else if ( 294 | op.opcode === 'JUMPI' && 295 | currentStack._store[currentStack.length - 2].constant === false 296 | ) { 297 | // clone the stack for use in the fork 298 | var clone = Object.assign( 299 | Object.create(Object.getPrototypeOf(currentStack)), 300 | currentStack 301 | ) 302 | clone._store = clone._store.slice(0) 303 | 304 | // clone the counter too 305 | var c = Object.assign(Object.create(Object.getPrototypeOf(counter)), counter) 306 | 307 | // set the condition to True 308 | clone._store[clone.length - 2] = { 309 | constant: true, 310 | item: new BN(1) 311 | } 312 | 313 | // designate a key based on the stack and the jump location 314 | var key = JSON.stringify({i: op.jump.index, s: clone._store.slice(0).map(x => {return {c: x.constant, d: x.duplicated}})}) 315 | 316 | // only perform fork if stack has changed & hasn't already forked a lot 317 | if (completedOneBranches[key] !== true && fork < 30) { 318 | // recursive call, then record that truthy has been tried for stack 319 | run(fork + 1, clone, c) 320 | completedOneBranches[key] = true 321 | } 322 | 323 | // now set the condition to false in the old stack and proceed as normal 324 | currentStack._store[currentStack.length - 2] = { 325 | constant: true, 326 | item: new BN(0) 327 | } 328 | 329 | // execute instruction & record that not-truthy has been tried for stack 330 | opFns[op.opcode](op, currentStack, counter) 331 | completedZeroBranches[op.jump.index] = true // not using this yet :) 332 | 333 | // ensure that the stack size is still OK 334 | if (stackSize + stackDelta(op) !== currentStack.length) { 335 | console.log('DEBUG:', op, stackSize, stackDelta(op), currentStack.length) 336 | throw new Error('unexpected stack size change!') 337 | } 338 | // if none of the above applies, just keep going through the opcodes 339 | } else { 340 | // execute instruction 341 | opFns[op.opcode](op, currentStack, counter) 342 | // ensure that the stack size is still OK 343 | if (stackSize + stackDelta(op) !== currentStack.length) { 344 | console.log('DEBUG:', op, stackSize, stackDelta(op), currentStack.length) 345 | throw new Error('unexpected stack size change!') 346 | } 347 | } 348 | 349 | // increment the counter and breaker, stopping if we reach the end 350 | counter.increment() 351 | if (counter.instruction >= opsAndJumps.length) { 352 | counter.stop() 353 | } 354 | breaker++ 355 | } 356 | } 357 | 358 | // kick off run to start populating modified stack and search for stack items 359 | run(0, stack, counter) 360 | 361 | // TODO: work BACKWARDS from each remaining problem, looking for the PUSH 362 | // (basically, all the routes through the code might not be located) 363 | 364 | // last-ditch attempt in case only basic static jumps remain 365 | opsAndJumps.forEach(op => { 366 | if ( 367 | op.opcode.includes('JUMP') && 368 | !op.opcode.includes('DEST') && 369 | !op.jump.origins && 370 | op.prior.opcode.includes('PUSH') 371 | ) { 372 | op.jump.origins = [{ 373 | index: originalPushIndexes[op.prior.pc], 374 | pc: op.prior.pc, 375 | static: true, 376 | duplicated: false 377 | }] 378 | op.jump.dests = [{ 379 | index: originalJumpdestIndexes[parseInt(op.prior.push.data, 16)], 380 | pc: parseInt(op.prior.push.data, 16) 381 | }] 382 | } 383 | }) 384 | 385 | // build up an updated group of jumps, identifying any remaining problems 386 | jumps = [] 387 | if (logging) { 388 | console.log() 389 | console.log( 390 | '************* PUSH ************* '+ 391 | ' ************ JUMP ************ '+ 392 | ' ********** JUMPDEST **********' 393 | ) 394 | } 395 | opsAndJumps.forEach(op => { 396 | if (op.opcode.includes('JUMP') && !op.opcode.includes('DEST')) { 397 | if (op.jump.origins) { 398 | if (logging) { 399 | // don't mind the mess :D 400 | console.log( 401 | `${op.jump.origins.map(x => `${x.index} (pc ${x.pc}: ${ 402 | originalPushes[x.index].opcode 403 | } 0x${originalPushes[x.index].push.data}${ 404 | x.duplicated ? ", DUP'd!" : ''})`).join(', ') 405 | } \t=>\t ${op.jump.index} (pc ${op.pc}: ${op.opcode 406 | }) \t=>\t${op.jump.dests.map( 407 | x => `${x.index} (pc ${x.pc}: JUMPDEST)` 408 | )}` 409 | ) 410 | } 411 | jumps.push({ 412 | problem: false, 413 | index: op.jump.index, 414 | pc: op.pc, 415 | origins: op.jump.origins, 416 | dests: op.jump.dests 417 | }) 418 | } else { 419 | if (logging) { 420 | console.log('PROBLEM:', op.jump.index, op) 421 | } 422 | jumps.push({ 423 | problem: true, 424 | index: op.jump.index, 425 | pc: op.pc, 426 | origins: [], 427 | dests: [] 428 | }) 429 | } 430 | } 431 | }) 432 | 433 | // do the same for codecopies 434 | codecopies = [] 435 | if (logging) { 436 | console.log() 437 | console.log( 438 | '************* PUSH ************* '+ 439 | ' ******************** CODECOPY ********************' 440 | ) 441 | } 442 | codecopyIndex = 0 443 | opsAndJumps.forEach(op => { 444 | if (op.opcode === 'CODECOPY') { 445 | if (op.codecopy && op.codecopy.constant === true && op.codecopy.origin) { 446 | const originIndex = originalPushIndexes[op.codecopy.origin] 447 | let origin = originalPushes[originIndex] 448 | origin.duplicated = op.codecopy.duplicated 449 | if (logging) { 450 | // can't be bothered to clean up... 451 | console.log( 452 | `${originIndex} (pc ${origin.pc}: ${origin.opcode} 0x${ 453 | origin.push.data}${origin.duplicated ? ", DUP'd!" : '' 454 | }) \t=>\t ${codecopyIndex} (pc ${op.pc}: ${ 455 | op.opcode 456 | } )`) 457 | } 458 | codecopies.push({ 459 | problem: false, 460 | op: op 461 | }) 462 | } else { 463 | if (logging) { 464 | console.log( 465 | `???????????????????????????????????????\t=>\t ${ 466 | codecopyIndex 467 | } (pc ${op.pc}: ${op.opcode} )` 468 | ) 469 | } 470 | codecopies.push({ 471 | problem: true, 472 | op: op 473 | }) 474 | } 475 | codecopyIndex++ 476 | } 477 | }) 478 | 479 | if (logging) { 480 | console.log() 481 | } 482 | 483 | // sanity check for jumps 484 | opsAndJumps.forEach(op => { 485 | if ( 486 | op.opcode.includes('JUMP') && 487 | !op.opcode.includes('DEST') && ( 488 | typeof op.jump.origins === 'undefined' || 489 | op.jump.origins.some(o => o.static === false) 490 | )) { 491 | if (logging) { 492 | console.log(op) 493 | } 494 | throw new Error("cannot assign a specific push to each jump.") 495 | } 496 | }) 497 | 498 | // Increase each PUSH to a JUMPDEST in runtime code by length of prelude 499 | let jumpPushSize = null; 500 | jumps.forEach(jump => { 501 | if (!(jump.problem === false)) { 502 | throw new Error('cannot adjust problematic push used to jump.') 503 | } 504 | 505 | jump.origins.forEach((origin, index) => { 506 | if (!(origin.static === true)) { 507 | throw new Error('only static pushes are supported for now.') 508 | } 509 | 510 | if (!(origin.duplicated === false)) { 511 | throw new Error('only unduplicated pushes are supported for now.') 512 | } 513 | 514 | // get the relevant instruction so that we can parse out the data field 515 | let pushInstruction = opsAndJumps[instructions[origin.pc]] 516 | 517 | // find out if it's a PUSH1 or PUSH2 and adjust accordingly 518 | let pushSize = 0 519 | if (pushInstruction.opcode === 'PUSH1') { 520 | pushSize = 1 521 | if (jumpPushSize === null) { 522 | jumpPushSize = 1 523 | } 524 | } else if (pushInstruction.opcode === 'PUSH2') { 525 | pushSize = 2 526 | if (jumpPushSize === null) { 527 | jumpPushSize = 2 528 | } 529 | } 530 | 531 | if (pushSize === 0) { 532 | throw new Error('only PUSH1 and PUSH2 opcodes are supported for now.') 533 | } 534 | 535 | if (jumpPushSize !== pushSize) { 536 | throw new Error('size of PUSH opcodes must be consistent.') 537 | } 538 | 539 | // modify the relevant runtime code (TODO: ensure it doesn't overflow!) 540 | let modified = Buffer.from( 541 | ( 542 | runtime.slice( 543 | pushInstruction.pc + 1, 544 | pushInstruction.pc + 1 + pushSize 545 | ).readUIntBE(0, pushSize) + preludeSize 546 | ).toString(16).padStart(2 * pushSize, '0'), 547 | 'hex' 548 | ) 549 | 550 | // update the runtime with the new modified code 551 | let runtimeSegments = [ 552 | runtime.slice(0, pushInstruction.pc + 1), 553 | modified, 554 | runtime.slice(pushInstruction.pc + 1 + pushSize) 555 | ]; 556 | runtime = Buffer.concat(runtimeSegments); 557 | }) 558 | }) 559 | 560 | // Increase each PUSH to a CODECOPY offset in runtime code by prelude length 561 | let codecopyPushSize = null; 562 | codecopies.forEach(codecopy => { 563 | if (!(codecopy.problem === false)) { 564 | throw new Error('cannot adjust problematic codecopy offset.') 565 | } 566 | 567 | // get the relevant instruction so that we can parse out the data field 568 | let pushInstruction = opsAndJumps[instructions[codecopy.op.codecopy.origin]] 569 | 570 | // find out if it's a PUSH1 or PUSH2 and adjust accordingly 571 | let pushSize = 0 572 | if (pushInstruction.opcode === 'PUSH1') { 573 | pushSize = 1 574 | if (codecopyPushSize === null) { 575 | codecopyPushSize = 1 576 | } 577 | } else if (pushInstruction.opcode === 'PUSH2') { 578 | pushSize = 2 579 | if (codecopyPushSize === null) { 580 | codecopyPushSize = 2 581 | } 582 | } 583 | 584 | if (pushSize === 0) { 585 | throw new Error('only PUSH1 and PUSH2 opcodes are supported for now.') 586 | } 587 | 588 | if (codecopyPushSize !== pushSize) { 589 | throw new Error('size of PUSH opcodes must be consistent.') 590 | } 591 | 592 | // modify the relevant runtime code (TODO: ensure it doesn't overflow!) 593 | let modified = Buffer.from( 594 | ( 595 | runtime.slice( 596 | pushInstruction.pc + 1, 597 | pushInstruction.pc + 1 + pushSize 598 | ).readUIntBE(0, pushSize) + preludeSize 599 | ).toString(16).padStart(2 * pushSize, '0'), 600 | 'hex' 601 | ) 602 | 603 | // update the runtime with the new modified code 604 | let runtimeSegments = [ 605 | runtime.slice(0, pushInstruction.pc + 1), 606 | modified, 607 | runtime.slice(pushInstruction.pc + 1 + pushSize) 608 | ]; 609 | runtime = Buffer.concat(runtimeSegments); 610 | }) 611 | 612 | // Increase PUSH to CODECOPY length & RETURN in init code by prelude length 613 | let pushSize = 0 614 | if (initOps[candidate.sequenceStart].opcode === 'PUSH1') { 615 | pushSize = 1 616 | } else if (initOps[candidate.sequenceStart].opcode === 'PUSH2') { 617 | pushSize = 2 618 | } 619 | 620 | if (pushSize === 0) { 621 | throw new Error('only PUSH1 and PUSH2 opcodes are supported for now.') 622 | } 623 | 624 | runtimeLength = initOps[candidate.sequenceStart].pc 625 | 626 | // modify the relevant runtime code (TODO: ensure it doesn't overflow!) 627 | const modified = Buffer.from( 628 | ( 629 | initCodeWithoutRuntime.slice( 630 | runtimeLength + 1, 631 | runtimeLength + 1 + pushSize 632 | ).readUIntBE(0, pushSize) + preludeSize 633 | ).toString(16).padStart(2 * pushSize, '0'), 634 | 'hex' 635 | ) 636 | 637 | // update the init code with the new modified length 638 | var initSegments = [ 639 | initCodeWithoutRuntime.slice(0, runtimeLength + 1), 640 | modified, 641 | initCodeWithoutRuntime.slice(runtimeLength + 1 + pushSize) 642 | ]; 643 | var updatedInitCode = Buffer.concat(initSegments); 644 | 645 | // concatenate new init code, prelude, and runtime code to get final init code 646 | var finalSegments = [updatedInitCode, prelude, runtime]; 647 | var final = Buffer.concat(finalSegments); 648 | 649 | // get the final runtime code too 650 | var finalRuntimeSegments = [prelude, runtime]; 651 | var finalRuntime = Buffer.concat(finalRuntimeSegments); 652 | 653 | // make the kakuna build directory if it doesn't currently exist 654 | if (!fs.existsSync('build/kakuna')) { 655 | fs.mkdir('build/kakuna', { recursive: true }, (err) => { 656 | if (err) throw err; 657 | }); 658 | } 659 | 660 | // write new init code and runtime code to a file (along with some metadata) 661 | const out = JSON.stringify({ 662 | contractName: contractName, 663 | prelude: preludeRaw, 664 | preludeSize: preludeSize, 665 | bytecode: '0x' + final.toString('hex'), 666 | deployedBytecode: '0x' + finalRuntime.toString('hex') 667 | }, null, 2) 668 | fs.writeFileSync(`build/kakuna/${contractName}.json`, out, {flag: 'w'}) 669 | 670 | // Wow, it actually worked! Return kakuna'd initialization code & runtime code 671 | return ['0x' + final.toString('hex'), '0x' + finalRuntime.toString('hex')] 672 | }} 673 | 674 | /** 675 | * Modified implementation of the stack used in evm, where values are objects 676 | * rather than BN values. This is so we can deal with unknown stack items and 677 | * track whether the items are ever duplicated. 678 | */ 679 | class Stack { 680 | constructor () { 681 | this._store = [] 682 | } 683 | 684 | get length () { 685 | return this._store.length 686 | } 687 | 688 | push (value) { 689 | if (this._store.length > 1023) { 690 | throw new Error("stack overflow") 691 | } 692 | 693 | if (!this._isValidValue(value)) { 694 | throw new Error("out of range") 695 | } 696 | if (value.duplicated !== true) { 697 | value.duplicated = false 698 | } 699 | this._store.push(value) // assign each {constant: false} a unique ID? 700 | } 701 | 702 | pop () { 703 | if (this._store.length < 1) { 704 | throw new Error("stack underflow") 705 | } 706 | 707 | return this._store.pop() 708 | } 709 | 710 | /** 711 | * Pop multiple items from stack. Top of stack is first item 712 | * in returned array. 713 | * @param {Number} num - Number of items to pop 714 | * @returns {Array} 715 | */ 716 | popN (num = 1) { 717 | if (this._store.length < num) { 718 | throw new Error("stack underflow") 719 | } 720 | 721 | if (num === 0) { 722 | return [] 723 | } 724 | 725 | return this._store.splice(-1 * num).reverse() 726 | } 727 | 728 | /** 729 | * Swap top of stack with an item in the stack. 730 | * @param {Number} position - Index of item from top of the stack (0-indexed) 731 | */ 732 | swap (position) { 733 | if (this._store.length <= position) { 734 | throw new Error("stack underflow") 735 | } 736 | 737 | const head = this._store.length - 1 738 | const i = this._store.length - position - 1 739 | 740 | const tmp = this._store[head] 741 | this._store[head] = this._store[i] 742 | this._store[i] = tmp 743 | } 744 | 745 | /** 746 | * Pushes a copy of an item in the stack. 747 | * @param {Number} position - Index of item to be copied (1-indexed) 748 | */ 749 | dup (position) { 750 | if (this._store.length < position) { 751 | throw new Error("stack underflow") 752 | } 753 | 754 | const i = this._store.length - position 755 | this._store[i].duplicated = true 756 | this.push(this._store[i]) 757 | } 758 | 759 | _isValidValue (value) { 760 | if (BN.isBN(value.item)) { 761 | if (value.item.lte(utils.MAX_INTEGER)) { 762 | return true 763 | } 764 | } else if (Buffer.isBuffer(value.item)) { 765 | if (value.item.length <= 32) { 766 | return true 767 | } 768 | } 769 | 770 | return !value.constant 771 | } 772 | } 773 | 774 | // used to track the program counter and whether execution has halted. 775 | class Counter { 776 | constructor () { 777 | this.instruction = 0 778 | this.stopped = false 779 | } 780 | 781 | jump (value) { 782 | this.instruction = value 783 | } 784 | 785 | increment () { 786 | this.instruction++ 787 | } 788 | 789 | stop() { 790 | this.stopped = true 791 | } 792 | } 793 | 794 | // the functions that will operate on our modified stack for each opcode. 795 | opFns = { 796 | JUMP: function (op, stack, counter) { 797 | let dest = stack.pop() 798 | 799 | // this means we found a jump constant. 800 | if (dest.constant && dest.origin) { 801 | if ( 802 | typeof opsAndJumps[op.i].jump.dests === 'undefined' && 803 | typeof opsAndJumps[op.i].jump.origins === 'undefined' 804 | ) { 805 | opsAndJumps[op.i].jump.dests = [{ 806 | index: originalJumpdestIndexes[dest.item.toNumber()], 807 | pc: dest.item.toNumber() 808 | }] 809 | opsAndJumps[op.i].jump.origins = [{ 810 | index: originalPushIndexes[dest.origin], 811 | pc: dest.origin, 812 | static: true, 813 | duplicated: dest.duplicated 814 | }] 815 | } else { 816 | const jumpdest = { 817 | index: originalJumpdestIndexes[dest.item.toNumber()], 818 | pc: dest.item.toNumber() 819 | } 820 | const origin = { 821 | index: originalPushIndexes[dest.origin], 822 | pc: dest.origin, 823 | static: true, 824 | duplicated: dest.duplicated 825 | } 826 | 827 | if ( 828 | !(opsAndJumps[op.i].jump.dests 829 | .map(x => x.pc) 830 | .includes(jumpdest.pc)) && 831 | !(opsAndJumps[op.i].jump.origins 832 | .map(x => x.pc) 833 | .includes(origin.pc)) 834 | ) { 835 | opsAndJumps[op.i].jump.dests.push(jumpdest) 836 | opsAndJumps[op.i].jump.origins.push(origin) 837 | } 838 | } 839 | 840 | // determine what instruction # the pc points to (ie no counting pushData) 841 | newInstruction = instructions[dest.item.toNumber()] 842 | 843 | // check that it points to a jumpdest 844 | if (opsAndJumps[newInstruction].opcode !== 'JUMPDEST') { 845 | throw new Error('selected jump does not point to a jumpdest.') 846 | } 847 | 848 | // alter instruction 849 | counter.jump(newInstruction) 850 | } else { 851 | console.log('DEBUG:', op, stack, counter) 852 | throw new Error('jump does not have a constant') 853 | } 854 | }, 855 | JUMPI: function (op, stack, counter) { 856 | let [dest, cond] = stack.popN(2) 857 | 858 | // this means we found a jumpi constant. 859 | if (dest.constant) { 860 | if ( 861 | typeof opsAndJumps[op.i].jump.dests === 'undefined' && 862 | typeof opsAndJumps[op.i].jump.origins === 'undefined' 863 | ) { 864 | opsAndJumps[op.i].jump.dests = [{ 865 | index: originalJumpdestIndexes[dest.item.toNumber()], 866 | pc: dest.item.toNumber() 867 | }] 868 | opsAndJumps[op.i].jump.origins = [{ 869 | index: originalPushIndexes[dest.origin], 870 | pc: dest.origin, 871 | static: true, 872 | duplicated: dest.duplicated 873 | }] 874 | } else { 875 | const jumpdest = { 876 | index: originalJumpdestIndexes[dest.item.toNumber()], 877 | pc: dest.item.toNumber() 878 | } 879 | const origin = { 880 | index: originalPushIndexes[dest.origin], 881 | pc: dest.origin, 882 | static: true, 883 | duplicated: dest.duplicated 884 | } 885 | 886 | if ( 887 | !(opsAndJumps[op.i].jump.dests 888 | .map(x => x.pc) 889 | .includes(jumpdest.pc)) && 890 | !(opsAndJumps[op.i].jump.origins 891 | .map(x => x.pc) 892 | .includes(origin.pc)) 893 | ) { 894 | opsAndJumps[op.i].jump.dests.push(jumpdest) 895 | opsAndJumps[op.i].jump.origins.push(origin) 896 | } 897 | } 898 | 899 | } else { 900 | console.log('DEBUG:', op, stack, counter) 901 | throw new Error('jumpi does not have a constant') 902 | } 903 | 904 | if (!cond.constant) { 905 | throw new Error('cannot pass a non-constant as a JUMPI condition') 906 | } 907 | 908 | if (dest.constant && cond.constant && !cond.item.isZero()) { 909 | 910 | // determine what instruction # the pc points to (ie no counting pushData) 911 | newInstruction = instructions[dest.item.toNumber().toString()] 912 | 913 | // check that it points to a jumpdest 914 | if (opsAndJumps[newInstruction].opcode !== 'JUMPDEST') { 915 | throw new Error('selected jump does not point to a jumpdest.') 916 | } 917 | 918 | // alter instruction 919 | counter.jump(newInstruction) 920 | } 921 | }, 922 | JUMPDEST: function (op, stack, counter) {}, 923 | CODECOPY: function (op, stack, counter) { 924 | // this could be used as constants - requires working with memory 925 | let [memOffset, codeOffset, dataLength] = stack.popN(3) 926 | }, 927 | POP: function (op, stack, counter) { 928 | stack.pop() 929 | }, 930 | PUSH: function (op, stack, counter) { 931 | stack.push({constant: true, item: web3.utils.toBN('0x' + op.push.data), origin: op.pc}) 932 | }, 933 | DUP: function (op, stack, counter) { 934 | const stackPos = op.x - 127 935 | stack.dup(op.x - 127) 936 | }, 937 | SWAP: function (op, stack, counter) { 938 | stack.swap(op.x - 143) 939 | }, 940 | PC: function (op, stack, counter) { 941 | stack.push({constant: false}) // could set to true (op.pc) and transform? 942 | }, 943 | MSIZE: function (op, stack, counter) { 944 | stack.push({constant: false}) 945 | }, 946 | STOP: function (op, stack, counter) { 947 | counter.stop() 948 | }, 949 | ADD: function (op, stack, counter) { 950 | const [a, b] = stack.popN(2) 951 | let r 952 | if (a.constant && b.constant) { 953 | r = {constant: true, item: a.item.add(b.item).mod(utils.TWO_POW256)} 954 | } else { 955 | r = {constant: false} 956 | } 957 | 958 | stack.push(r) 959 | }, 960 | MUL: function (op, stack, counter) { 961 | const [a, b] = stack.popN(2) 962 | let r 963 | if (a.constant && b.constant) { 964 | r = {constant: true, item: a.item.mul(b.item).mod(utils.TWO_POW256)} 965 | } else { 966 | r = {constant: false} 967 | } 968 | 969 | stack.push(r) 970 | }, 971 | SUB: function (op, stack, counter) { 972 | const [a, b] = stack.popN(2) 973 | let r 974 | if (a.constant && b.constant) { 975 | r = {constant: true, item: a.item.sub(b.item).toTwos(256)} 976 | } else { 977 | r = {constant: false} 978 | } 979 | 980 | stack.push(r) 981 | }, 982 | DIV: function (op, stack, counter) { 983 | const [a, b] = stack.popN(2) 984 | let r 985 | if (b.constant && b.item.isZero()) { 986 | r = {constant: true, item: new BN(b.item)} 987 | } else if (a.constant && b.constant) { 988 | r = {constant: true, item: a.item.div(b.item)} 989 | } else { 990 | r = {constant: false} 991 | } 992 | 993 | stack.push(r) 994 | }, 995 | SDIV: function (op, stack, counter) { 996 | let [a, b] = stack.popN(2) 997 | let r 998 | if (b.constant && b.item.isZero()) { 999 | r = {constant: true, item: new BN(b.item)} 1000 | } else if (a.constant && b.constant) { 1001 | x = a.item.fromTwos(256) 1002 | y = b.item.fromTwos(256) 1003 | r = {constant: true, item: x.div(y).toTwos(256)} 1004 | } else { 1005 | r = {constant: false} 1006 | } 1007 | 1008 | stack.push(r) 1009 | }, 1010 | MOD: function (op, stack, counter) { 1011 | const [a, b] = stack.popN(2) 1012 | let r 1013 | if (b.constant && b.item.isZero()) { 1014 | r = {constant: true, item: new BN(b.item)} 1015 | } else if (a.constant && b.constant) { 1016 | r = {constant: true, item: a.item.mod(b.item)} 1017 | } else { 1018 | r = {constant: false} 1019 | } 1020 | 1021 | stack.push(r) 1022 | }, 1023 | SMOD: function (op, stack, counter) { 1024 | let [a, b] = stack.popN(2) 1025 | let r 1026 | if (b.constant && b.item.isZero()) { 1027 | r = {constant: true, item: new BN(b.item)} 1028 | } else if (a.constant && b.constant) { 1029 | x = a.item.fromTwos(256) 1030 | y = b.item.fromTwos(256) 1031 | z = x.abs().mod(y.abs()) 1032 | if (z.isNeg()) { 1033 | z = z.ineg() 1034 | } 1035 | r = {constant: true, item: z.toTwos(256)} 1036 | } else { 1037 | r = {constant: false} 1038 | } 1039 | 1040 | stack.push(r) 1041 | }, 1042 | ADDMOD: function (op, stack, counter) { 1043 | const [a, b, c] = stack.popN(3) 1044 | let r 1045 | if (c.constant && c.item.isZero()) { 1046 | r = {constant: true, item: new BN(c.item)} 1047 | } else if (a.constant && b.constant && c.constant) { 1048 | r = a.item.add(b.item).mod(c.item) 1049 | } else { 1050 | r = {constant: false} 1051 | } 1052 | 1053 | stack.push(r) 1054 | }, 1055 | MULMOD: function (op, stack, counter) { 1056 | const [a, b, c] = stack.popN(3) 1057 | let r 1058 | if (c.constant && c.item.isZero()) { 1059 | r = {constant: true, item: new BN(c.item)} 1060 | } else if (a.constant && b.constant && c.constant) { 1061 | r = a.item.mul(b.item).mod(c.item) 1062 | } else { 1063 | r = {constant: false} 1064 | } 1065 | 1066 | stack.push(r) 1067 | }, 1068 | EXP: function (op, stack, counter) { 1069 | let [base, exponent] = stack.popN(2) 1070 | if (exponent.constant && exponent.item.isZero()) { 1071 | stack.push({constant: true, item: new BN(1)}) 1072 | return 1073 | } 1074 | if (exponent.constant) { 1075 | const byteLength = exponent.item.byteLength() 1076 | if (byteLength < 1 || byteLength > 32) { 1077 | throw new Error("out of range") 1078 | } 1079 | } 1080 | 1081 | if (base.constant && base.item.isZero()) { 1082 | stack.push({constant: true, item: new BN(0)}) 1083 | return 1084 | } 1085 | 1086 | let r 1087 | if (base.constant && exponent.constant) { 1088 | const m = BN.red(utils.TWO_POW256) 1089 | b = base.item.toRed(m) 1090 | r = {constant: true, item: b.redPow(exponent.item)} 1091 | } else { 1092 | r = {constant: false} 1093 | } 1094 | 1095 | stack.push(r) 1096 | }, 1097 | SIGNEXTEND: function (op, stack, counter) { 1098 | let [k, val] = stack.popN(2) 1099 | if (!k.constant || !val.constant) { 1100 | stack.push({constant: false}) 1101 | return 1102 | } 1103 | val = val.item.toArrayLike(Buffer, 'be', 32) 1104 | var extendOnes = false 1105 | 1106 | if (k.item.lten(31)) { 1107 | k = k.item.toNumber() 1108 | 1109 | if (val[31 - k] & 0x80) { 1110 | extendOnes = true 1111 | } 1112 | 1113 | // 31-k-1 since k-th byte shouldn't be modified 1114 | for (var i = 30 - k; i >= 0; i--) { 1115 | val[i] = extendOnes ? 0xff : 0 1116 | } 1117 | } 1118 | 1119 | stack.push({constant: true, item: new BN(val)}) 1120 | }, 1121 | // 0x10 range - bit ops 1122 | LT: function (op, stack, counter) { 1123 | const [a, b] = stack.popN(2) 1124 | if (!a.constant || !b.constant) { 1125 | stack.push({constant: false}) 1126 | return 1127 | } 1128 | const r = {constant: true, item: new BN(a.item.lt(b.item) ? 1 : 0)} 1129 | 1130 | stack.push(r) 1131 | }, 1132 | GT: function (op, stack, counter) { 1133 | const [a, b] = stack.popN(2) 1134 | if (!a.constant || !b.constant) { 1135 | stack.push({constant: false}) 1136 | return 1137 | } 1138 | const r = {constant: true, item: new BN(a.item.gt(b.item) ? 1 : 0)} 1139 | 1140 | stack.push(r) 1141 | }, 1142 | SLT: function (op, stack, counter) { 1143 | const [a, b] = stack.popN(2) 1144 | if (!a.constant || !b.constant) { 1145 | stack.push({constant: false}) 1146 | return 1147 | } 1148 | const r = { 1149 | constant: true, 1150 | item: new BN(a.item.fromTwos(256).lt(b.item.fromTwos(256)) ? 1 : 0) 1151 | } 1152 | 1153 | stack.push(r) 1154 | }, 1155 | SGT: function (op, stack, counter) { 1156 | const [a, b] = stack.popN(2) 1157 | if (!a.constant || !b.constant) { 1158 | stack.push({constant: false}) 1159 | return 1160 | } 1161 | const r = { 1162 | constant: true, 1163 | item: new BN(a.item.fromTwos(256).gt(b.item.fromTwos(256)) ? 1 : 0) 1164 | } 1165 | 1166 | stack.push(r) 1167 | }, 1168 | EQ: function (op, stack, counter) { 1169 | const [a, b] = stack.popN(2) 1170 | if (!a.constant || !b.constant) { 1171 | stack.push({constant: false}) 1172 | return 1173 | } 1174 | const r = {constant: true, item: new BN(a.item.eq(b.item) ? 1 : 0)} 1175 | 1176 | stack.push(r) 1177 | }, 1178 | ISZERO: function (op, stack, counter) { 1179 | const a = stack.pop() 1180 | if (!a.constant) { 1181 | stack.push({constant: false}) 1182 | return 1183 | } 1184 | 1185 | const r = {constant: true, item: new BN(a.item.isZero() ? 1 : 0)} 1186 | 1187 | stack.push(r) 1188 | }, 1189 | AND: function (op, stack, counter) { 1190 | const [a, b] = stack.popN(2) 1191 | if (!a.constant || !b.constant) { 1192 | stack.push({constant: false}) 1193 | return 1194 | } 1195 | 1196 | const r = {constant: true, item: a.item.and(b.item)} 1197 | 1198 | stack.push(r) 1199 | }, 1200 | OR: function (op, stack, counter) { 1201 | const [a, b] = stack.popN(2) 1202 | if (!a.constant || !b.constant) { 1203 | stack.push({constant: false}) 1204 | return 1205 | } 1206 | 1207 | const r = {constant: true, item: a.item.or(b.item)} 1208 | 1209 | stack.push(r) 1210 | }, 1211 | XOR: function (op, stack, counter) { 1212 | const [a, b] = stack.popN(2) 1213 | if (!a.constant || !b.constant) { 1214 | stack.push({constant: false}) 1215 | return 1216 | } 1217 | 1218 | const r = {constant: true, item: a.item.xor(b.item)} 1219 | 1220 | stack.push(r) 1221 | }, 1222 | NOT: function (op, stack, counter) { 1223 | const a = stack.pop() 1224 | if (!a.constant) { 1225 | stack.push({constant: false}) 1226 | return 1227 | } 1228 | 1229 | const r = {constant: true, item: a.item.notn(256)} 1230 | 1231 | stack.push(r) 1232 | }, 1233 | BYTE: function (op, stack, counter) { 1234 | const [pos, word] = stack.popN(2) 1235 | if (pos.constant && pos.item.gten(32)) { 1236 | stack.push({constant: true, item: new BN(0)}) 1237 | return 1238 | } 1239 | 1240 | if (!pos.constant || !word.constant) { 1241 | stack.push({constant: false}) 1242 | return 1243 | } 1244 | 1245 | const r = { 1246 | constant: true, 1247 | item: new BN(word.item.shrn((31 - pos.item.toNumber()) * 8).andln(0xff)) 1248 | } 1249 | 1250 | stack.push(r) 1251 | }, 1252 | SHL: function (op, stack, counter) { 1253 | const [a, b] = stack.popN(2) 1254 | // expects constantinople! 1255 | if (a.constant && a.item.gten(256)) { 1256 | stack.push(new BN(0)) 1257 | return 1258 | } 1259 | 1260 | if (!a.constant || !b.constant) { 1261 | stack.push({constant: false}) 1262 | return 1263 | } 1264 | 1265 | const r = { 1266 | constant: true, 1267 | item: b.item.shln(a.item.toNumber()).iand(utils.MAX_INTEGER) 1268 | } 1269 | 1270 | stack.push(r) 1271 | }, 1272 | SHR: function (op, stack, counter) { 1273 | const [a, b] = stack.popN(2) 1274 | // expects constantinople! 1275 | if (a.constant && a.item.gten(256)) { 1276 | stack.push(new BN(0)) 1277 | return 1278 | } 1279 | 1280 | if (!a.constant || !b.constant) { 1281 | stack.push({constant: false}) 1282 | return 1283 | } 1284 | 1285 | const r = { 1286 | constant: true, 1287 | item: b.item.shrn(a.item.toNumber()).iand(utils.MAX_INTEGER) 1288 | } 1289 | 1290 | stack.push(r) 1291 | }, 1292 | SAR: function (op, stack, counter) { 1293 | const [a, b] = stack.popN(2) 1294 | // expects constantinople! 1295 | if (!a.constant || !b.constant) { 1296 | stack.push({constant: false}) 1297 | return 1298 | } 1299 | 1300 | let r 1301 | const isSigned = b.item.testn(255) 1302 | if (a.item.gten(256)) { 1303 | if (isSigned) { 1304 | r = {constant: true, item: new BN(utils.MAX_INTEGER)} 1305 | } else { 1306 | r = {constant: true, item: new BN(0)} 1307 | } 1308 | stack.push(r) 1309 | return 1310 | } 1311 | 1312 | const c = b.item.shrn(a.item.toNumber()) 1313 | if (isSigned) { 1314 | const shiftedOutWidth = 255 - a.item.toNumber() 1315 | const mask = utils.MAX_INTEGER.shrn(shiftedOutWidth).shln(shiftedOutWidth) 1316 | r = {constant: true, item: c.ior(mask)} 1317 | } else { 1318 | r = {constant: true, item: c} 1319 | } 1320 | stack.push(r) 1321 | }, 1322 | // 0x20 range - crypto 1323 | SHA3: function (op, stack, counter) { 1324 | const [offset, length] = stack.popN(2) 1325 | 1326 | stack.push({constant: false}) 1327 | }, 1328 | // 0x30 range - closure state 1329 | ADDRESS: function (op, stack, counter) { 1330 | stack.push({constant: false}) 1331 | }, 1332 | BALANCE: function (op, stack, counter) { 1333 | let address = stack.pop() 1334 | 1335 | stack.push({constant: false}) 1336 | }, 1337 | ORIGIN: function (op, stack, counter) { 1338 | stack.push({constant: false}) 1339 | }, 1340 | CALLER: function (op, stack, counter) { 1341 | stack.push({constant: false}) 1342 | }, 1343 | CALLVALUE: function (op, stack, counter) { 1344 | stack.push({constant: false}) 1345 | }, 1346 | CALLDATALOAD: function (op, stack, counter) { 1347 | let pos = stack.pop() 1348 | stack.push({constant: false}) 1349 | }, 1350 | CALLDATASIZE: function (op, stack, counter) { 1351 | stack.push({constant: false}) 1352 | }, 1353 | CALLDATACOPY: function (op, stack, counter) { 1354 | let [memOffset, dataOffset, dataLength] = stack.popN(3) 1355 | }, 1356 | CODESIZE: function (op, stack, counter) { 1357 | stack.push({constant: false}) 1358 | }, 1359 | EXTCODESIZE: function (op, stack, counter) { 1360 | let address = stack.pop() 1361 | stack.push({constant: false}) 1362 | }, 1363 | EXTCODECOPY: function (op, stack, counter) { 1364 | let [address, memOffset, codeOffset, length] = stack.popN(4) 1365 | }, 1366 | EXTCODEHASH: function (op, stack, counter) { 1367 | let address = stack.pop() 1368 | stack.push({constant: false}) 1369 | }, 1370 | RETURNDATASIZE: function (op, stack, counter) { 1371 | stack.push({constant: false}) 1372 | }, 1373 | RETURNDATACOPY: function (op, stack, counter) { 1374 | let [memOffset, returnDataOffset, length] = stack.popN(3) 1375 | }, 1376 | GASPRICE: function (op, stack, counter) { 1377 | stack.push({constant: false}) 1378 | }, 1379 | // '0x40' range - block operations 1380 | BLOCKHASH: function (op, stack, counter) { 1381 | const number = stack.pop() 1382 | stack.push({constant: false}) 1383 | }, 1384 | COINBASE: function (op, stack, counter) { 1385 | stack.push({constant: false}) 1386 | }, 1387 | TIMESTAMP: function (op, stack, counter) { 1388 | stack.push({constant: false}) 1389 | }, 1390 | NUMBER: function (op, stack, counter) { 1391 | stack.push({constant: false}) 1392 | }, 1393 | DIFFICULTY: function (op, stack, counter) { 1394 | stack.push({constant: false}) 1395 | }, 1396 | GASLIMIT: function (op, stack, counter) { 1397 | stack.push({constant: false}) 1398 | }, 1399 | 1400 | MLOAD: function (op, stack, counter) { 1401 | const pos = stack.pop() 1402 | 1403 | stack.push({constant: false}) 1404 | }, 1405 | MSTORE: function (op, stack, counter) { 1406 | let [offset, word] = stack.popN(2) 1407 | }, 1408 | MSTORE8: function (op, stack, counter) { 1409 | let [offset, byte] = stack.popN(2) 1410 | }, 1411 | SLOAD: function (op, stack, counter) { 1412 | let key = stack.pop() 1413 | stack.push({constant: false}) 1414 | }, 1415 | SSTORE: function (op, stack, counter) { 1416 | // check for staticcall 1417 | let [key, val] = stack.popN(2) 1418 | }, 1419 | GAS: function (op, stack, counter) { 1420 | stack.push({constant: false}) 1421 | }, 1422 | LOG: function (op, stack, counter) { 1423 | // check for staticcall 1424 | stack.popN(op.x - 158) 1425 | }, 1426 | // '0xf0' range - closures 1427 | CREATE: function (op, stack, counter) { 1428 | // check for staticcall 1429 | const [value, offset, length] = stack.popN(3) 1430 | 1431 | stack.push({constant: false}) 1432 | }, 1433 | CREATE2: function (op, stack, counter) { 1434 | // check for constantinople 1435 | 1436 | // check for staticcall 1437 | 1438 | const [value, offset, length, salt] = stack.popN(4) 1439 | 1440 | stack.push({constant: false}) 1441 | }, 1442 | CALL: function (op, stack, counter) { 1443 | let [gasLimit, toAddress, value, inOffset, inLength, outOffset, outLength] = stack.popN(7) 1444 | 1445 | stack.push({constant: false}) 1446 | }, 1447 | CALLCODE: function (op, stack, counter) { 1448 | let [gasLimit, toAddress, value, inOffset, inLength, outOffset, outLength] = stack.popN(7) 1449 | 1450 | stack.push({constant: false}) 1451 | }, 1452 | DELEGATECALL: function (op, stack, counter) { 1453 | let [gas, toAddress, inOffset, inLength, outOffset, outLength] = stack.popN(6) 1454 | 1455 | stack.push({constant: false}) 1456 | }, 1457 | STATICCALL: function (op, stack, counter) { 1458 | let [gasLimit, toAddress, inOffset, inLength, outOffset, outLength] = stack.popN(6) 1459 | 1460 | stack.push({constant: false}) 1461 | }, 1462 | RETURN: function (op, stack, counter) { 1463 | const [offset, length] = stack.popN(2) 1464 | 1465 | counter.stop() 1466 | }, 1467 | REVERT: function (op, stack, counter) { 1468 | const [offset, length] = stack.popN(2) 1469 | 1470 | counter.stop() 1471 | }, 1472 | INVALID: function (op, stack, counter) { 1473 | counter.stop() 1474 | }, 1475 | SELFDESTRUCT: function (op, stack, counter) { 1476 | let selfdestructToAddress = stack.pop() 1477 | 1478 | counter.stop() 1479 | } 1480 | } 1481 | 1482 | // used to get opcode information from a given opcode. 1483 | function opcodes (op, full, freeLogs) { 1484 | const codes = { 1485 | // 0x0 range - arithmetic ops 1486 | // name, baseCost, off stack, on stack, dynamic, async 1487 | 0x00: ['STOP', 0, false], 1488 | 0x01: ['ADD', 3, false], 1489 | 0x02: ['MUL', 5, false], 1490 | 0x03: ['SUB', 3, false], 1491 | 0x04: ['DIV', 5, false], 1492 | 0x05: ['SDIV', 5, false], 1493 | 0x06: ['MOD', 5, false], 1494 | 0x07: ['SMOD', 5, false], 1495 | 0x08: ['ADDMOD', 8, false], 1496 | 0x09: ['MULMOD', 8, false], 1497 | 0x0a: ['EXP', 10, false], 1498 | 0x0b: ['SIGNEXTEND', 5, false], 1499 | 1500 | // 0x10 range - bit ops 1501 | 0x10: ['LT', 3, false], 1502 | 0x11: ['GT', 3, false], 1503 | 0x12: ['SLT', 3, false], 1504 | 0x13: ['SGT', 3, false], 1505 | 0x14: ['EQ', 3, false], 1506 | 0x15: ['ISZERO', 3, false], 1507 | 0x16: ['AND', 3, false], 1508 | 0x17: ['OR', 3, false], 1509 | 0x18: ['XOR', 3, false], 1510 | 0x19: ['NOT', 3, false], 1511 | 0x1a: ['BYTE', 3, false], 1512 | 0x1b: ['SHL', 3, false], 1513 | 0x1c: ['SHR', 3, false], 1514 | 0x1d: ['SAR', 3, false], 1515 | 1516 | // 0x20 range - crypto 1517 | 0x20: ['SHA3', 30, false], 1518 | 1519 | // 0x30 range - closure state 1520 | 0x30: ['ADDRESS', 2, true], 1521 | 0x31: ['BALANCE', 400, true, true], 1522 | 0x32: ['ORIGIN', 2, true], 1523 | 0x33: ['CALLER', 2, true], 1524 | 0x34: ['CALLVALUE', 2, true], 1525 | 0x35: ['CALLDATALOAD', 3, true], 1526 | 0x36: ['CALLDATASIZE', 2, true], 1527 | 0x37: ['CALLDATACOPY', 3, true], 1528 | 0x38: ['CODESIZE', 2, false], 1529 | 0x39: ['CODECOPY', 3, false], 1530 | 0x3a: ['GASPRICE', 2, false], 1531 | 0x3b: ['EXTCODESIZE', 700, true, true], 1532 | 0x3c: ['EXTCODECOPY', 700, true, true], 1533 | 0x3d: ['RETURNDATASIZE', 2, true], 1534 | 0x3e: ['RETURNDATACOPY', 3, true], 1535 | 0x3f: ['EXTCODEHASH', 400, true, true], 1536 | 1537 | // '0x40' range - block operations 1538 | 0x40: ['BLOCKHASH', 20, true, true], 1539 | 0x41: ['COINBASE', 2, true], 1540 | 0x42: ['TIMESTAMP', 2, true], 1541 | 0x43: ['NUMBER', 2, true], 1542 | 0x44: ['DIFFICULTY', 2, true], 1543 | 0x45: ['GASLIMIT', 2, true], 1544 | 1545 | // 0x50 range - 'storage' and execution 1546 | 0x50: ['POP', 2, false], 1547 | 0x51: ['MLOAD', 3, false], 1548 | 0x52: ['MSTORE', 3, false], 1549 | 0x53: ['MSTORE8', 3, false], 1550 | 0x54: ['SLOAD', 200, true, true], 1551 | 0x55: ['SSTORE', 0, true, true], 1552 | 0x56: ['JUMP', 8, false], 1553 | 0x57: ['JUMPI', 10, false], 1554 | 0x58: ['PC', 2, false], 1555 | 0x59: ['MSIZE', 2, false], 1556 | 0x5a: ['GAS', 2, false], 1557 | 0x5b: ['JUMPDEST', 1, false], 1558 | 1559 | // 0x60, range 1560 | 0x60: ['PUSH', 3, false], 1561 | 0x61: ['PUSH', 3, false], 1562 | 0x62: ['PUSH', 3, false], 1563 | 0x63: ['PUSH', 3, false], 1564 | 0x64: ['PUSH', 3, false], 1565 | 0x65: ['PUSH', 3, false], 1566 | 0x66: ['PUSH', 3, false], 1567 | 0x67: ['PUSH', 3, false], 1568 | 0x68: ['PUSH', 3, false], 1569 | 0x69: ['PUSH', 3, false], 1570 | 0x6a: ['PUSH', 3, false], 1571 | 0x6b: ['PUSH', 3, false], 1572 | 0x6c: ['PUSH', 3, false], 1573 | 0x6d: ['PUSH', 3, false], 1574 | 0x6e: ['PUSH', 3, false], 1575 | 0x6f: ['PUSH', 3, false], 1576 | 0x70: ['PUSH', 3, false], 1577 | 0x71: ['PUSH', 3, false], 1578 | 0x72: ['PUSH', 3, false], 1579 | 0x73: ['PUSH', 3, false], 1580 | 0x74: ['PUSH', 3, false], 1581 | 0x75: ['PUSH', 3, false], 1582 | 0x76: ['PUSH', 3, false], 1583 | 0x77: ['PUSH', 3, false], 1584 | 0x78: ['PUSH', 3, false], 1585 | 0x79: ['PUSH', 3, false], 1586 | 0x7a: ['PUSH', 3, false], 1587 | 0x7b: ['PUSH', 3, false], 1588 | 0x7c: ['PUSH', 3, false], 1589 | 0x7d: ['PUSH', 3, false], 1590 | 0x7e: ['PUSH', 3, false], 1591 | 0x7f: ['PUSH', 3, false], 1592 | 1593 | 0x80: ['DUP', 3, false], 1594 | 0x81: ['DUP', 3, false], 1595 | 0x82: ['DUP', 3, false], 1596 | 0x83: ['DUP', 3, false], 1597 | 0x84: ['DUP', 3, false], 1598 | 0x85: ['DUP', 3, false], 1599 | 0x86: ['DUP', 3, false], 1600 | 0x87: ['DUP', 3, false], 1601 | 0x88: ['DUP', 3, false], 1602 | 0x89: ['DUP', 3, false], 1603 | 0x8a: ['DUP', 3, false], 1604 | 0x8b: ['DUP', 3, false], 1605 | 0x8c: ['DUP', 3, false], 1606 | 0x8d: ['DUP', 3, false], 1607 | 0x8e: ['DUP', 3, false], 1608 | 0x8f: ['DUP', 3, false], 1609 | 1610 | 0x90: ['SWAP', 3, false], 1611 | 0x91: ['SWAP', 3, false], 1612 | 0x92: ['SWAP', 3, false], 1613 | 0x93: ['SWAP', 3, false], 1614 | 0x94: ['SWAP', 3, false], 1615 | 0x95: ['SWAP', 3, false], 1616 | 0x96: ['SWAP', 3, false], 1617 | 0x97: ['SWAP', 3, false], 1618 | 0x98: ['SWAP', 3, false], 1619 | 0x99: ['SWAP', 3, false], 1620 | 0x9a: ['SWAP', 3, false], 1621 | 0x9b: ['SWAP', 3, false], 1622 | 0x9c: ['SWAP', 3, false], 1623 | 0x9d: ['SWAP', 3, false], 1624 | 0x9e: ['SWAP', 3, false], 1625 | 0x9f: ['SWAP', 3, false], 1626 | 1627 | 0xa0: ['LOG', 375, false], 1628 | 0xa1: ['LOG', 375, false], 1629 | 0xa2: ['LOG', 375, false], 1630 | 0xa3: ['LOG', 375, false], 1631 | 0xa4: ['LOG', 375, false], 1632 | 1633 | // '0xf0' range - closures 1634 | 0xf0: ['CREATE', 32000, true, true], 1635 | 0xf1: ['CALL', 700, true, true], 1636 | 0xf2: ['CALLCODE', 700, true, true], 1637 | 0xf3: ['RETURN', 0, false], 1638 | 0xf4: ['DELEGATECALL', 700, true, true], 1639 | 0xf5: ['CREATE2', 32000, true, true], 1640 | 0xfa: ['STATICCALL', 700, true, true], 1641 | 0xfd: ['REVERT', 0, false], 1642 | 1643 | // '0x70', range - other 1644 | 0xfe: ['INVALID', 0, false], 1645 | 0xff: ['SELFDESTRUCT', 5000, false, true] 1646 | } 1647 | 1648 | var code = codes[op] ? codes[op] : ['INVALID', 0, false, false] 1649 | var opcode = code[0] 1650 | 1651 | if (full) { 1652 | if (opcode === 'LOG') { 1653 | opcode += op - 0xa0 1654 | } 1655 | 1656 | if (opcode === 'PUSH') { 1657 | opcode += op - 0x5f 1658 | } 1659 | 1660 | if (opcode === 'DUP') { 1661 | opcode += op - 0x7f 1662 | } 1663 | 1664 | if (opcode === 'SWAP') { 1665 | opcode += op - 0x8f 1666 | } 1667 | } 1668 | 1669 | var fee = code[1] 1670 | 1671 | if (freeLogs) { 1672 | if (opcode === 'LOG') { 1673 | fee = 0 1674 | } 1675 | } 1676 | 1677 | return {name: opcode, opcode: op, fee: fee, dynamic: code[2], async: code[3]} 1678 | } 1679 | 1680 | // used to get opcodes from raw bytes. 1681 | function nameOpCodes (raw) { 1682 | operations = [] 1683 | var push = {data: ''} 1684 | 1685 | for (var i = 0; i < raw.length; i++) { 1686 | var pc = i 1687 | var curOpCode = opcodes(raw[pc], true).name 1688 | 1689 | // no destinations into the middle of PUSH 1690 | if (curOpCode.slice(0, 4) === 'PUSH') { 1691 | var jumpNum = raw[pc] - 0x5f 1692 | push.data = raw.slice(pc + 1, pc + jumpNum + 1) 1693 | i += jumpNum 1694 | } 1695 | 1696 | operations.push({ 1697 | x: raw[pc], 1698 | pc: pc, 1699 | opcode: curOpCode, 1700 | push: {data: push.data.toString('hex')} 1701 | }) 1702 | 1703 | push.data = '' 1704 | } 1705 | return operations 1706 | } 1707 | 1708 | // used to calculate and verify the change in the stack size for each opcode. 1709 | function stackDelta(op) { 1710 | const minusSix = [ 1711 | 'LOG4', 1712 | 'CALL', 1713 | 'CALLCODE' 1714 | ] 1715 | 1716 | const minusFive = [ 1717 | 'LOG3', 1718 | 'DELEGATECALL', 1719 | 'STATICCALL' 1720 | ] 1721 | 1722 | const minusFour = [ 1723 | 'EXTCODECOPY', 1724 | 'LOG2' 1725 | ] 1726 | 1727 | const minusThree = [ 1728 | 'CALLDATACOPY', 1729 | 'CODECOPY', 1730 | 'RETURNDATACOPY', 1731 | 'LOG1', 1732 | 'CREATE2' 1733 | ] 1734 | 1735 | const minusTwo = [ 1736 | 'ADDMOD', 1737 | 'MULMOD', 1738 | 'MSTORE', 1739 | 'MSTORE8', 1740 | 'SSTORE', 1741 | 'JUMPI', 1742 | 'LOG0', 1743 | 'CREATE', 1744 | 'RETURN', 1745 | 'REVERT' 1746 | ] 1747 | 1748 | const minusOne = [ 1749 | 'ADD', 1750 | 'MUL', 1751 | 'SUB', 1752 | 'DIV', 1753 | 'SDIV', 1754 | 'MOD', 1755 | 'SMOD', 1756 | 'EXP', 1757 | 'SIGNEXTEND', 1758 | 'LT', 1759 | 'GT', 1760 | 'SLT', 1761 | 'SGT', 1762 | 'EQ', 1763 | 'AND', 1764 | 'OR', 1765 | 'XOR', 1766 | 'BYTE', 1767 | 'SHL', 1768 | 'SHR', 1769 | 'SAR', 1770 | 'SHA3', 1771 | 'POP', 1772 | 'JUMP', 1773 | 'SELFDESTRUCT' 1774 | ] 1775 | 1776 | const zero = [ 1777 | 'STOP', 1778 | 'ISZERO', 1779 | 'NOT', 1780 | 'BALANCE', 1781 | 'CALLDATALOAD', 1782 | 'EXTCODESIZE', 1783 | 'BLOCKHASH', 1784 | 'MLOAD', 1785 | 'SLOAD', 1786 | 'JUMPDEST', 1787 | 'SWAP', // 1-16 1788 | 'INVALID' 1789 | 1790 | ] 1791 | const plusOne = [ 1792 | 'ADDRESS', 1793 | 'ORIGIN', 1794 | 'CALLER', 1795 | 'CALLVALUE', 1796 | 'CALLDATASIZE', 1797 | 'CODESIZE', 1798 | 'GASPRICE', 1799 | 'RETURNDATASIZE', 1800 | 'COINBASE', 1801 | 'TIMESTAMP', 1802 | 'NUMBER', 1803 | 'DIFFICULTY', 1804 | 'GASLIMIT', 1805 | 'PC', 1806 | 'MSIZE', 1807 | 'GAS', 1808 | 'PUSH', // 1-32 1809 | 'DUP' // 1-16 1810 | 1811 | ] 1812 | if (plusOne.includes(op.opcode)) return 1 1813 | if (zero.includes(op.opcode)) return 0 1814 | if (minusOne.includes(op.opcode)) return -1 1815 | if (minusTwo.includes(op.opcode)) return -2 1816 | if (minusThree.includes(op.opcode)) return -3 1817 | if (minusFour.includes(op.opcode)) return -4 1818 | if (minusFive.includes(op.opcode)) return -5 1819 | if (minusSix.includes(op.opcode)) return -6 1820 | if (op.opcode.includes('PUSH')) return 1 1821 | if (op.opcode.includes('DUP')) return 1 1822 | if (op.opcode.includes('SWAP')) return 0 1823 | console.log('DEBUG:', op.opcode) 1824 | throw new Error("cannot locate stack delta for given opcode") 1825 | } 1826 | 1827 | function pad (num, size) { 1828 | var s = num + '' 1829 | while (s.length < size) s = '0' + s 1830 | return s 1831 | } 1832 | 1833 | function log (num, base) { 1834 | return Math.log(num) / Math.log(base) 1835 | } 1836 | 1837 | function roundLog (num, base) { 1838 | return Math.ceil(log(num, base)) 1839 | } -------------------------------------------------------------------------------- /scripts/test/ci.js: -------------------------------------------------------------------------------- 1 | const connectionConfig = require('../../truffle-config.js') 2 | 3 | const connection = connectionConfig.networks['development'] 4 | 5 | let web3Provider = connection.provider 6 | 7 | // import tests 8 | var test = require('./test.js') 9 | 10 | // run tests 11 | async function runTests() { 12 | await test.test(web3Provider, 'development') 13 | process.exit(0) 14 | } 15 | 16 | runTests() 17 | -------------------------------------------------------------------------------- /scripts/test/test.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert') 2 | var fs = require('fs') 3 | var util = require('ethereumjs-util') 4 | 5 | var kakuna = require('../kakuna/kakuna.js') 6 | 7 | const MetamorphicContractFactoryArtifact = require('../../build/contracts/MetamorphicContractFactory.json') 8 | const TransientContractArtifact = require('../../build/contracts/TransientContract.json') 9 | const ImmutableCreate2FactoryArtifact = require('../../build/contracts/ImmutableCreate2Factory.json') 10 | const ContractOneArtifact = require('../../build/contracts/ContractOne.json') 11 | const ContractTwoArtifact = require('../../build/contracts/ContractTwo.json') 12 | const ContractWithConstructorArtifact = require('../../build/contracts/ContractWithConstructor.json') 13 | const CodeCheckArtifact = require('../../build/contracts/CodeCheck.json') 14 | const MetapodArtifact = require('../../build/contracts/Metapod.json') 15 | const KakunaBasicTestArtifact = require('../../build/contracts/KakunaBasicTest.json') 16 | const KakunaBasicTestWithPreludeArtifact = require('../../build/kakuna/KakunaBasicTest.json') 17 | 18 | const MetamorphicContractBytecode = '0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3' 19 | 20 | module.exports = {test: async function (provider, testingContext) { 21 | var web3 = provider 22 | let passed = 0 23 | let failed = 0 24 | let gasUsage = {} 25 | console.log('running tests...') 26 | 27 | // get available addresses and assign them to various roles 28 | const addresses = await web3.eth.getAccounts() 29 | if (addresses.length < 1) { 30 | console.log('cannot find enough addresses to run tests!') 31 | process.exit(1) 32 | } 33 | 34 | const originalAddress = addresses[0] 35 | 36 | async function send( 37 | title, 38 | instance, 39 | method, 40 | args, 41 | from, 42 | value, 43 | gas, 44 | gasPrice, 45 | shouldSucceed, 46 | assertionCallback 47 | ) { 48 | let succeeded = true 49 | receipt = await instance.methods[method](...args).send({ 50 | from: from, 51 | value: value, 52 | gas: gas, 53 | gasPrice: gasPrice 54 | }).catch(error => { 55 | //console.error(error) 56 | succeeded = false 57 | }) 58 | 59 | if (succeeded !== shouldSucceed) { 60 | return false 61 | } else if (!shouldSucceed) { 62 | return true 63 | } 64 | 65 | assert.ok(receipt.status) 66 | 67 | let assertionsPassed 68 | try { 69 | assertionCallback(receipt) 70 | assertionsPassed = true 71 | } catch(error) { 72 | assertionsPassed = false 73 | } 74 | 75 | return assertionsPassed 76 | } 77 | 78 | async function call( 79 | title, 80 | instance, 81 | method, 82 | args, 83 | from, 84 | value, 85 | gas, 86 | gasPrice, 87 | shouldSucceed, 88 | assertionCallback 89 | ) { 90 | let succeeded = true 91 | returnValues = await instance.methods[method](...args).call({ 92 | from: from, 93 | value: value, 94 | gas: gas, 95 | gasPrice: gasPrice 96 | }).catch(error => { 97 | //console.error(error) 98 | succeeded = false 99 | }) 100 | 101 | if (succeeded !== shouldSucceed) { 102 | return false 103 | } else if (!shouldSucceed) { 104 | return true 105 | } 106 | 107 | let assertionsPassed 108 | try { 109 | assertionCallback(returnValues) 110 | assertionsPassed = true 111 | } catch(error) { 112 | assertionsPassed = false 113 | } 114 | 115 | return assertionsPassed 116 | } 117 | 118 | async function runTest( 119 | title, 120 | instance, 121 | method, 122 | callOrSend, 123 | args, 124 | shouldSucceed, 125 | assertionCallback, 126 | from, 127 | value, 128 | gas 129 | ) { 130 | if (typeof(callOrSend) === 'undefined') { 131 | callOrSend = 'send' 132 | } 133 | if (typeof(args) === 'undefined') { 134 | args = [] 135 | } 136 | if (typeof(shouldSucceed) === 'undefined') { 137 | shouldSucceed = true 138 | } 139 | if (typeof(assertionCallback) === 'undefined') { 140 | assertionCallback = (value) => {} 141 | } 142 | if (typeof(from) === 'undefined') { 143 | from = address 144 | } 145 | if (typeof(value) === 'undefined') { 146 | value = 0 147 | } 148 | if (typeof(gas) === 'undefined') { 149 | gas = gasLimit - 1 150 | } 151 | let ok = false 152 | if (callOrSend === 'send') { 153 | ok = await send( 154 | title, 155 | instance, 156 | method, 157 | args, 158 | from, 159 | value, 160 | gas, 161 | 10 ** 1, 162 | shouldSucceed, 163 | assertionCallback 164 | ) 165 | } else if (callOrSend === 'call') { 166 | ok = await call( 167 | title, 168 | instance, 169 | method, 170 | args, 171 | from, 172 | value, 173 | gas, 174 | 10 ** 1, 175 | shouldSucceed, 176 | assertionCallback 177 | ) 178 | } else { 179 | console.error('must use call or send!') 180 | process.exit(1) 181 | } 182 | 183 | if (ok) { 184 | console.log(` ✓ ${title}`) 185 | passed++ 186 | } else { 187 | console.log(` ✘ ${title}`) 188 | failed++ 189 | } 190 | } 191 | 192 | async function setupNewDefaultAddress(newPrivateKey) { 193 | const pubKey = await web3.eth.accounts.privateKeyToAccount(newPrivateKey) 194 | await web3.eth.accounts.wallet.add(pubKey) 195 | 196 | const txCount = await web3.eth.getTransactionCount(pubKey.address) 197 | 198 | if (txCount > 0) { 199 | console.warn( 200 | `warning: ${pubKey.address} has already been used, which may cause ` + 201 | 'some tests to fail.' 202 | ) 203 | } 204 | 205 | await web3.eth.sendTransaction({ 206 | from: originalAddress, 207 | to: pubKey.address, 208 | value: 10 ** 18, 209 | gas: '0x5208', 210 | gasPrice: '0x4A817C800' 211 | }) 212 | 213 | return pubKey.address 214 | } 215 | 216 | async function raiseGasLimit(necessaryGas) { 217 | iterations = 9999 218 | if (necessaryGas > 8000000) { 219 | console.error('the gas needed is too high!') 220 | process.exit(1) 221 | } else if (typeof necessaryGas === 'undefined') { 222 | iterations = 20 223 | necessaryGas = 8000000 224 | } 225 | 226 | // bring up gas limit if necessary by doing additional transactions 227 | var block = await web3.eth.getBlock("latest") 228 | while (iterations > 0 && block.gasLimit < necessaryGas) { 229 | await web3.eth.sendTransaction({ 230 | from: originalAddress, 231 | to: originalAddress, 232 | value: '0x01', 233 | gas: '0x5208', 234 | gasPrice: '0x4A817C800' 235 | }) 236 | var block = await web3.eth.getBlock("latest") 237 | iterations-- 238 | } 239 | 240 | console.log("raising gasLimit, currently at " + block.gasLimit) 241 | return block.gasLimit 242 | } 243 | 244 | async function getDeployGas(dataPayload) { 245 | await web3.eth.estimateGas({ 246 | from: address, 247 | data: dataPayload 248 | }).catch(async error => { 249 | if ( 250 | error.message === ( 251 | 'Returned error: gas required exceeds allowance or always failing ' + 252 | 'transaction' 253 | ) 254 | ) { 255 | await raiseGasLimit() 256 | await getDeployGas(dataPayload) 257 | } 258 | }) 259 | 260 | deployGas = await web3.eth.estimateGas({ 261 | from: address, 262 | data: dataPayload 263 | }) 264 | 265 | return deployGas 266 | } 267 | 268 | // *************************** deploy contracts *************************** // 269 | let address = await setupNewDefaultAddress( 270 | '0xfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeedfeed' 271 | ) 272 | 273 | let deployGas 274 | let latestBlock = await web3.eth.getBlock('latest') 275 | const gasLimit = latestBlock.gasLimit 276 | 277 | const MetamorphicContractFactoryDeployer = new web3.eth.Contract( 278 | MetamorphicContractFactoryArtifact.abi 279 | ) 280 | 281 | const ImmutableCreate2FactoryDeployer = new web3.eth.Contract( 282 | ImmutableCreate2FactoryArtifact.abi 283 | ) 284 | 285 | const ContractTwoDeployer = new web3.eth.Contract( 286 | ContractTwoArtifact.abi 287 | ) 288 | 289 | const CodeCheckDeployer = new web3.eth.Contract( 290 | CodeCheckArtifact.abi 291 | ) 292 | 293 | const KakunaBasicTestDeployer = new web3.eth.Contract( 294 | KakunaBasicTestArtifact.abi 295 | ) 296 | 297 | let dataPayload = ImmutableCreate2FactoryDeployer.deploy({ 298 | data: ImmutableCreate2FactoryArtifact.bytecode 299 | }).encodeABI() 300 | 301 | deployGas = await getDeployGas(dataPayload) 302 | 303 | const ImmutableCreate2Factory = await ImmutableCreate2FactoryDeployer.deploy({ 304 | data: ImmutableCreate2FactoryArtifact.bytecode 305 | }).send({ 306 | from: address, 307 | gas: deployGas, 308 | gasPrice: 10 ** 1 309 | }).catch(error => { 310 | console.error(error) 311 | console.log( 312 | ` ✘ ImmutableCreate2Factory contract deploys successfully for ${deployGas} gas` 313 | ) 314 | failed++ 315 | process.exit(1) 316 | }) 317 | 318 | console.log( 319 | ` ✓ ImmutableCreate2Factory contract deploys successfully for ${deployGas} gas` 320 | ) 321 | passed++ 322 | 323 | dataPayload = MetamorphicContractFactoryDeployer.deploy({ 324 | data: MetamorphicContractFactoryArtifact.bytecode, 325 | arguments: [TransientContractArtifact.bytecode] 326 | }).encodeABI() 327 | 328 | deployGas = await getDeployGas(dataPayload) 329 | 330 | const MetamorphicContractFactory = await MetamorphicContractFactoryDeployer.deploy({ 331 | data: MetamorphicContractFactoryArtifact.bytecode, 332 | arguments: [TransientContractArtifact.bytecode] 333 | }).send({ 334 | from: address, 335 | gas: deployGas, 336 | gasPrice: 10 ** 1 337 | }).catch(error => { 338 | console.error(error) 339 | console.log( 340 | ` ✘ Metamorphic Contract Factory deploys successfully for ${deployGas} gas` 341 | ) 342 | failed++ 343 | process.exit(1) 344 | }) 345 | 346 | console.log( 347 | ` ✓ Metamorphic Contract Factory deploys successfully for ${deployGas} gas` 348 | ) 349 | passed++ 350 | 351 | dataPayload = ContractTwoDeployer.deploy({ 352 | data: ContractTwoArtifact.bytecode 353 | }).encodeABI() 354 | 355 | deployGas = await getDeployGas(dataPayload) 356 | 357 | const ContractTwo = await ContractTwoDeployer.deploy({ 358 | data: ContractTwoArtifact.bytecode 359 | }).send({ 360 | from: address, 361 | gas: deployGas, 362 | gasPrice: 10 ** 1 363 | }).catch(error => { 364 | console.error(error) 365 | console.log( 366 | ` ✘ New implementation contract deploys successfully for ${deployGas} gas` 367 | ) 368 | failed++ 369 | process.exit(1) 370 | }) 371 | 372 | console.log( 373 | ` ✓ New implementation contract deploys successfully for ${deployGas} gas` 374 | ) 375 | passed++ 376 | 377 | dataPayload = CodeCheckDeployer.deploy({ 378 | data: CodeCheckArtifact.bytecode 379 | }).encodeABI() 380 | 381 | deployGas = await getDeployGas(dataPayload) 382 | 383 | const CodeCheck = await CodeCheckDeployer.deploy({ 384 | data: CodeCheckArtifact.bytecode 385 | }).send({ 386 | from: address, 387 | gas: deployGas, 388 | gasPrice: 10 ** 1 389 | }).catch(error => { 390 | console.error(error) 391 | console.log( 392 | ` ✘ CodeCheck contract deploys successfully for ${deployGas} gas` 393 | ) 394 | failed++ 395 | process.exit(1) 396 | }) 397 | 398 | console.log( 399 | ` ✓ CodeCheck contract deploys successfully for ${deployGas} gas` 400 | ) 401 | passed++ 402 | 403 | /* 404 | console.log(MetamorphicContractFactory.options.address) 405 | console.log(address) 406 | await runTest( 407 | 'MetamorphicContractFactory can get init code hash of transient contract', 408 | MetamorphicContractFactory, 409 | 'getTransientContractInitializationCodeHash', 410 | 'call', 411 | [], 412 | true, 413 | value => { 414 | console.log(value) 415 | } 416 | ) 417 | */ 418 | 419 | await runTest( 420 | 'MetamorphicContractFactory can deploy Metapod', 421 | MetamorphicContractFactory, 422 | 'deployMetamorphicContractWithConstructor', 423 | 'send', 424 | [ 425 | //address + 'e73fb603a03b4000007eb5df', 426 | address + '10f74f2d75490b0486060000', 427 | //address + '10f74f2d75490b0486060000', 428 | MetapodArtifact.bytecode 429 | ], 430 | true, 431 | receipt => { 432 | assert.strictEqual( 433 | receipt.events.MetamorphosedWithConstructor.returnValues.metamorphicContract, 434 | '0x000000000003212eb796dEE588acdbBbD777D4E7' 435 | ) 436 | } 437 | ) 438 | 439 | const Metapod = new web3.eth.Contract( 440 | MetapodArtifact.abi, 441 | '0x000000000003212eb796dEE588acdbBbD777D4E7' 442 | ) 443 | 444 | dataPayload = KakunaBasicTestDeployer.deploy({ 445 | data: KakunaBasicTestArtifact.bytecode 446 | }).encodeABI() 447 | 448 | deployGas = await getDeployGas(dataPayload) 449 | 450 | const KakunaBasicTest = await KakunaBasicTestDeployer.deploy({ 451 | data: KakunaBasicTestArtifact.bytecode 452 | }).send({ 453 | from: address, 454 | gas: deployGas, 455 | gasPrice: 10 ** 1 456 | }).catch(error => { 457 | console.error(error) 458 | console.log( 459 | ` ✘ Kakuna test contract deploys successfully for ${deployGas} gas` 460 | ) 461 | failed++ 462 | process.exit(1) 463 | }) 464 | 465 | console.log( 466 | ` ✓ Kakuna test contract deploys successfully for ${deployGas} gas` 467 | ) 468 | passed++ 469 | 470 | KakunaBasicTestWithPreludeCode = await kakuna.kakuna( 471 | 'KakunaBasicTest', 472 | '0x4150', // COINBASE POP, 4 gas no-op 473 | false // no logging 474 | ) 475 | KakunaBasicTestWithPreludeInitCode = KakunaBasicTestWithPreludeCode[0] 476 | KakunaBasicTestWithPreludeRuntime = KakunaBasicTestWithPreludeCode[1] 477 | 478 | dataPayload = KakunaBasicTestDeployer.deploy({ 479 | data: KakunaBasicTestWithPreludeInitCode 480 | }).encodeABI() 481 | 482 | deployGas = await getDeployGas(dataPayload) 483 | 484 | const KakunaBasicTestWithPrelude = await KakunaBasicTestDeployer.deploy({ 485 | data: KakunaBasicTestWithPreludeInitCode 486 | }).send({ 487 | from: address, 488 | gas: deployGas, 489 | gasPrice: 10 ** 1 490 | }).catch(error => { 491 | console.error(error) 492 | console.log( 493 | ` ✘ Kakuna test contract with prelude deploys successfully for ${deployGas} gas` 494 | ) 495 | failed++ 496 | process.exit(1) 497 | }) 498 | 499 | console.log( 500 | ` ✓ Kakuna test contract with prelude deploys successfully for ${deployGas} gas` 501 | ) 502 | passed++ 503 | 504 | let create2payload = ( 505 | '0xff' + 506 | MetamorphicContractFactory.options.address.slice(2) + 507 | address.slice(2) + '000000000000000000000000' + 508 | web3.utils.keccak256(MetamorphicContractBytecode, {encoding: "hex"}).slice(2) 509 | ) 510 | 511 | let targetMetamorphicContractAddress = web3.utils.toChecksumAddress( 512 | '0x' + web3.utils.sha3( 513 | create2payload, 514 | {encoding: "hex"} 515 | ).slice(12).substring(14) 516 | ) 517 | 518 | await runTest( 519 | 'MetamorphicContractFactory can check for address of a metamorphic contract', 520 | MetamorphicContractFactory, 521 | 'findMetamorphicContractAddress', 522 | 'call', 523 | [ 524 | address + '000000000000000000000000' 525 | ], 526 | true, 527 | value => { 528 | assert.strictEqual(value, targetMetamorphicContractAddress) 529 | } 530 | ) 531 | 532 | await runTest( 533 | 'MetamorphicContractFactory can get init code of metamorphic contract', 534 | MetamorphicContractFactory, 535 | 'getMetamorphicContractInitializationCode', 536 | 'call', 537 | [], 538 | true, 539 | value => { 540 | assert.strictEqual(value, MetamorphicContractBytecode) 541 | } 542 | ) 543 | 544 | await runTest( 545 | 'MetamorphicContractFactory can get init code hash of metamorphic contract', 546 | MetamorphicContractFactory, 547 | 'getMetamorphicContractInitializationCodeHash', 548 | 'call', 549 | [], 550 | true, 551 | value => { 552 | assert.strictEqual( 553 | value, 554 | web3.utils.keccak256(MetamorphicContractBytecode, {encoding: "hex"}) 555 | ) 556 | } 557 | ) 558 | 559 | await runTest( 560 | 'MetamorphicContractFactory can deploy a metamorphic contract', 561 | MetamorphicContractFactory, 562 | 'deployMetamorphicContract', 563 | 'send', 564 | [ 565 | address + '000000000000000000000000', 566 | ContractOneArtifact.bytecode, 567 | '0x8129fc1c' // initialize() 568 | ], 569 | true, 570 | receipt => { 571 | assert.strictEqual( 572 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 573 | targetMetamorphicContractAddress 574 | ) 575 | } 576 | ) 577 | 578 | await runTest( 579 | 'CodeCheck can check the code of the metamorphic contract', 580 | CodeCheck, 581 | 'check', 582 | 'call', 583 | [ 584 | targetMetamorphicContractAddress 585 | ], 586 | true, 587 | value => { 588 | assert.strictEqual(value, ContractOneArtifact.deployedBytecode) 589 | } 590 | ) 591 | 592 | let Metamorphic = new web3.eth.Contract( 593 | ContractOneArtifact.abi, 594 | targetMetamorphicContractAddress 595 | ) 596 | 597 | await runTest( 598 | 'Metamorphic contract can check for initialized test value', 599 | Metamorphic, 600 | 'test', 601 | 'call', 602 | [], 603 | true, 604 | value => { 605 | assert.strictEqual(value, '1') 606 | } 607 | ) 608 | 609 | await runTest( 610 | 'Metamorphic contract can be destroyed', 611 | Metamorphic, 612 | 'destroy' 613 | ) 614 | 615 | await runTest( 616 | 'MetamorphicContractFactory can redeploy a contract using a new implementation', 617 | MetamorphicContractFactory, 618 | 'deployMetamorphicContractFromExistingImplementation', 619 | 'send', 620 | [ 621 | address + '000000000000000000000000', 622 | ContractTwo.options.address, 623 | '0x' 624 | ], 625 | true, 626 | receipt => { 627 | assert.strictEqual( 628 | receipt.events[0].raw.data, 629 | '0x0000000000000000000000000000000000000000000000000000000000000001' 630 | ) 631 | }, 632 | address, 633 | 1 634 | ) 635 | 636 | await runTest( 637 | 'CodeCheck can check the code of the new metamorphic contract', 638 | CodeCheck, 639 | 'check', 640 | 'call', 641 | [ 642 | targetMetamorphicContractAddress 643 | ], 644 | true, 645 | value => { 646 | assert.strictEqual(value, ContractTwoArtifact.deployedBytecode) 647 | } 648 | ) 649 | 650 | await runTest( 651 | 'Metamorphic contract can check for new test value', 652 | Metamorphic, 653 | 'test', 654 | 'call', 655 | [], 656 | true, 657 | value => { 658 | assert.strictEqual(value, '0') 659 | } 660 | ) 661 | 662 | create2payload = ( 663 | '0xff' + 664 | ImmutableCreate2Factory.options.address.slice(2) + 665 | address.slice(2) + '000000000000000000000000' + 666 | web3.utils.keccak256('0x3838533838f3').slice(2) 667 | ) 668 | 669 | const targetImmutableContractAddress = web3.utils.toChecksumAddress( 670 | '0x' + web3.utils.sha3( 671 | create2payload, 672 | {encoding: "hex"} 673 | ).slice(12).substring(14) 674 | ) 675 | 676 | create2payload = ( 677 | '0xff' + 678 | MetamorphicContractFactory.options.address.slice(2) + 679 | address.slice(2) + '000000000000000000000000' + 680 | web3.utils.keccak256(TransientContractArtifact.bytecode).slice(2) 681 | ) 682 | 683 | const targetTransientContractAddress = web3.utils.toChecksumAddress( 684 | '0x' + web3.utils.sha3( 685 | create2payload, 686 | {encoding: "hex"} 687 | ).slice(12).substring(14) 688 | ) 689 | 690 | targetMetamorphicContractAddress = web3.utils.toChecksumAddress( 691 | '0x' + web3.utils.sha3( 692 | '0xd694' + targetTransientContractAddress.slice(2) + '01', 693 | {encoding: "hex"} 694 | ).slice(12).substring(14) 695 | ) 696 | 697 | await runTest( 698 | 'MetamorphicContractFactory can check for address of a transient contract', 699 | MetamorphicContractFactory, 700 | 'findTransientContractAddress', 701 | 'call', 702 | [ 703 | address + '000000000000000000000000' 704 | ], 705 | true, 706 | value => { 707 | assert.strictEqual(value, targetTransientContractAddress) 708 | } 709 | ) 710 | 711 | await runTest( 712 | 'MetamorphicContractFactory can check metamorphic address from transient', 713 | MetamorphicContractFactory, 714 | 'findMetamorphicContractAddressWithConstructor', 715 | 'call', 716 | [ 717 | address + '000000000000000000000000' 718 | ], 719 | true, 720 | value => { 721 | assert.strictEqual(value, targetMetamorphicContractAddress) 722 | } 723 | ) 724 | 725 | await runTest( 726 | 'MetamorphicContractFactory can get init code of transient contract', 727 | MetamorphicContractFactory, 728 | 'getTransientContractInitializationCode', 729 | 'call', 730 | [], 731 | true, 732 | value => { 733 | assert.strictEqual(value, TransientContractArtifact.bytecode) 734 | } 735 | ) 736 | 737 | await runTest( 738 | 'MetamorphicContractFactory can get init code hash of transient contract', 739 | MetamorphicContractFactory, 740 | 'getTransientContractInitializationCodeHash', 741 | 'call', 742 | [], 743 | true, 744 | value => { 745 | assert.strictEqual( 746 | value, 747 | web3.utils.keccak256( 748 | TransientContractArtifact.bytecode, 749 | {encoding: 'hex'} 750 | ) 751 | ) 752 | } 753 | ) 754 | 755 | await runTest( 756 | 'MetamorphicContractFactory can deploy metamorphic contract w/ constructor', 757 | MetamorphicContractFactory, 758 | 'deployMetamorphicContractWithConstructor', 759 | 'send', 760 | [ 761 | address + '000000000000000000000000', 762 | ContractWithConstructorArtifact.bytecode 763 | ], 764 | true, 765 | receipt => { 766 | assert.strictEqual( 767 | receipt.events.MetamorphosedWithConstructor.returnValues.metamorphicContract, 768 | targetMetamorphicContractAddress 769 | ) 770 | assert.strictEqual( 771 | receipt.events.MetamorphosedWithConstructor.returnValues.transientContract, 772 | targetTransientContractAddress 773 | ) 774 | } 775 | ) 776 | 777 | Metamorphic = new web3.eth.Contract( 778 | ContractWithConstructorArtifact.abi, 779 | targetMetamorphicContractAddress 780 | ) 781 | 782 | await runTest( 783 | 'Metamorphic contract can check for new test value', 784 | Metamorphic, 785 | 'test', 786 | 'call', 787 | [], 788 | true, 789 | value => { 790 | assert.strictEqual(value, '3') 791 | } 792 | ) 793 | 794 | await runTest( 795 | 'Metamorphic contract can be destroyed', 796 | Metamorphic, 797 | 'destroy' 798 | ) 799 | 800 | await runTest( 801 | 'MetamorphicContractFactory can redeploy a metamorphic contract w/ transient', 802 | MetamorphicContractFactory, 803 | 'deployMetamorphicContractWithConstructor', 804 | 'send', 805 | [ 806 | address + '000000000000000000000000', 807 | ContractTwoArtifact.bytecode 808 | ], 809 | true, 810 | receipt => { 811 | assert.strictEqual( 812 | receipt.events.MetamorphosedWithConstructor.returnValues.metamorphicContract, 813 | targetMetamorphicContractAddress 814 | ) 815 | assert.strictEqual( 816 | receipt.events.MetamorphosedWithConstructor.returnValues.transientContract, 817 | targetTransientContractAddress 818 | ) 819 | } 820 | ) 821 | 822 | await runTest( 823 | 'Metamorphic contract can check for new test value', 824 | Metamorphic, 825 | 'test', 826 | 'call', 827 | [], 828 | true, 829 | value => { 830 | assert.strictEqual(value, '0') 831 | } 832 | ) 833 | 834 | await runTest( 835 | 'ImmutableCreate2Factory can check a deployment address', 836 | ImmutableCreate2Factory, 837 | 'findCreate2Address', 838 | 'call', 839 | [ 840 | address + '000000000000000000000000', 841 | '0x3838533838f3' 842 | ], 843 | true, 844 | value => { 845 | assert.strictEqual(value, targetImmutableContractAddress) 846 | } 847 | ) 848 | 849 | await runTest( 850 | 'ImmutableCreate2Factory can check deployment address using init code hash', 851 | ImmutableCreate2Factory, 852 | 'findCreate2AddressViaHash', 853 | 'call', 854 | [ 855 | address + '000000000000000000000000', 856 | web3.utils.keccak256('0x3838533838f3', {encoding: "hex"}) 857 | ], 858 | true, 859 | value => { 860 | assert.strictEqual(value, targetImmutableContractAddress) 861 | } 862 | ) 863 | 864 | await runTest( 865 | 'ImmutableCreate2Factory can check if a contract has been deployed', 866 | ImmutableCreate2Factory, 867 | 'hasBeenDeployed', 868 | 'call', 869 | [ 870 | targetImmutableContractAddress 871 | ], 872 | true, 873 | value => { 874 | assert.strictEqual(value, false) 875 | } 876 | ) 877 | 878 | await runTest( 879 | 'ImmutableCreate2Factory can deploy a contract with collision avoidance', 880 | ImmutableCreate2Factory, 881 | 'safeCreate2', 882 | 'send', 883 | [ 884 | address + '000000000000000000000000', 885 | '0x3838533838f3' 886 | ] 887 | ) 888 | 889 | await runTest( 890 | 'ImmutableCreate2Factory can check once a contract has been deployed', 891 | ImmutableCreate2Factory, 892 | 'hasBeenDeployed', 893 | 'call', 894 | [ 895 | targetImmutableContractAddress 896 | ], 897 | true, 898 | value => { 899 | assert.strictEqual(value, true) 900 | } 901 | ) 902 | 903 | await runTest( 904 | 'ImmutableCreate2Factory cannot deploy the same contract twice', 905 | ImmutableCreate2Factory, 906 | 'safeCreate2', 907 | 'send', 908 | [ 909 | address + '000000000000000000000000', 910 | '0x3838533838f3' 911 | ], 912 | false 913 | ) 914 | 915 | await runTest( 916 | 'ImmutableCreate2Factory cannot deploy a contract from invalid address', 917 | ImmutableCreate2Factory, 918 | 'safeCreate2', 919 | 'send', 920 | [ 921 | '0x1000000000000000000000000000000000000000000000000000000000000000', 922 | '0x3838533838f3' 923 | ], 924 | false 925 | ) 926 | 927 | await runTest( 928 | 'ImmutableCreate2Factory can deploy a contract with no collision avoidance', 929 | ImmutableCreate2Factory, 930 | 'safeCreate2', 931 | 'send', 932 | [ 933 | '0x0000000000000000000000000000000000000000000000000000000000000000', 934 | '0x3838533838f3' 935 | ] 936 | ) 937 | 938 | let metapodMetamorphicContractAddress 939 | await runTest( 940 | 'Metapod can determine the address of a metamorphic contract', 941 | Metapod, 942 | 'findMetamorphicContractAddress', 943 | 'call', 944 | [ 945 | address + '000000000000000000000000' 946 | ], 947 | true, 948 | value => { 949 | metapodMetamorphicContractAddress = value 950 | } 951 | ) 952 | 953 | let metapodTransientContractAddress 954 | await runTest( 955 | 'Metapod can determine the address of a transient contract', 956 | Metapod, 957 | 'findTransientContractAddress', 958 | 'call', 959 | [ 960 | address + '000000000000000000000000' 961 | ], 962 | true, 963 | value => { 964 | metapodTransientContractAddress = value 965 | } 966 | ) 967 | 968 | let metapodVaultContractAddress 969 | await runTest( 970 | 'Metapod can determine the address of a vault contract', 971 | Metapod, 972 | 'findVaultContractAddress', 973 | 'call', 974 | [ 975 | address + '000000000000000000000000' 976 | ], 977 | true, 978 | value => { 979 | metapodVaultContractAddress = value 980 | } 981 | ) 982 | 983 | await runTest( 984 | 'Metapod reverts when deploying a metamorphic contract that reverts', 985 | Metapod, 986 | 'deploy', 987 | 'send', 988 | [ 989 | 0, 990 | '0xfe' 991 | ], 992 | false 993 | ) 994 | 995 | await runTest( 996 | 'CodeCheck confirms that no transient contract exists yet', 997 | CodeCheck, 998 | 'check', 999 | 'call', 1000 | [ 1001 | metapodTransientContractAddress 1002 | ], 1003 | true, 1004 | value => { 1005 | assert.strictEqual(value, null) 1006 | } 1007 | ) 1008 | 1009 | await runTest( 1010 | 'CodeCheck confirms that no metamorphic contract exists yet', 1011 | CodeCheck, 1012 | 'check', 1013 | 'call', 1014 | [ 1015 | metapodMetamorphicContractAddress 1016 | ], 1017 | true, 1018 | value => { 1019 | assert.strictEqual(value, null) 1020 | } 1021 | ) 1022 | 1023 | await runTest( 1024 | 'Metapod can deploy a contract', 1025 | Metapod, 1026 | 'deploy', 1027 | 'send', 1028 | [ 1029 | 0, 1030 | ( 1031 | '0x603680600b6000396000f36e' + 1032 | Metapod.options.address.slice(2 + (2 * 5)) + 1033 | '3318602b5773' + 1034 | metapodVaultContractAddress.slice(2) + 1035 | 'ff5b600160005260206000f3' 1036 | ) 1037 | ], 1038 | true, 1039 | receipt => { 1040 | assert.strictEqual( 1041 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 1042 | metapodMetamorphicContractAddress 1043 | ) 1044 | } 1045 | ) 1046 | 1047 | await runTest( 1048 | 'CodeCheck can check the code of the new transient contract', 1049 | CodeCheck, 1050 | 'check', 1051 | 'call', 1052 | [ 1053 | metapodTransientContractAddress 1054 | ], 1055 | true, 1056 | value => { 1057 | assert.strictEqual(value, null) 1058 | } 1059 | ) 1060 | 1061 | await runTest( 1062 | 'Metapod can determine the necessary prelude', 1063 | Metapod, 1064 | 'getPrelude', 1065 | 'call', 1066 | [ 1067 | address + '000000000000000000000000', 1068 | ], 1069 | true, 1070 | value => { 1071 | assert.strictEqual( 1072 | value, 1073 | ( 1074 | '0x6e' + 1075 | Metapod.options.address.slice(2 + (2 * 5)).toLowerCase() + 1076 | '3318602b5773' + 1077 | metapodVaultContractAddress.slice(2).toLowerCase() + 1078 | 'ff5b' 1079 | ) 1080 | ) 1081 | } 1082 | ) 1083 | 1084 | await runTest( 1085 | 'CodeCheck can check the code of the new metamorphic contract', 1086 | CodeCheck, 1087 | 'check', 1088 | 'call', 1089 | [ 1090 | metapodMetamorphicContractAddress 1091 | ], 1092 | true, 1093 | value => { 1094 | assert.strictEqual( 1095 | value, 1096 | ( 1097 | '0x6e' + 1098 | Metapod.options.address.slice(2 + (2 * 5)).toLowerCase() + 1099 | '3318602b5773' + 1100 | metapodVaultContractAddress.slice(2).toLowerCase() + 1101 | 'ff5b600160005260206000f3' 1102 | ) 1103 | ) 1104 | } 1105 | ) 1106 | 1107 | await runTest( 1108 | 'CodeCheck confirms that no vault contract exists yet', 1109 | CodeCheck, 1110 | 'check', 1111 | 'call', 1112 | [ 1113 | metapodVaultContractAddress 1114 | ], 1115 | true, 1116 | value => { 1117 | assert.strictEqual(value, null) 1118 | } 1119 | ) 1120 | 1121 | await runTest( 1122 | 'Metapod can destroy a metamorphic contract', 1123 | Metapod, 1124 | 'destroy', 1125 | 'send', 1126 | [ 1127 | 0 1128 | ] 1129 | ) 1130 | 1131 | await runTest( 1132 | 'Metapod can get the salt given an identifier', 1133 | Metapod, 1134 | 'getSalt', 1135 | 'call', 1136 | [ 1137 | 0 1138 | ], 1139 | true, 1140 | value => { 1141 | assert.strictEqual( 1142 | value, 1143 | address.toLowerCase() + '000000000000000000000000' 1144 | ) 1145 | } 1146 | ) 1147 | 1148 | await runTest( 1149 | 'Metapod can get the salt given the max identifier', 1150 | Metapod, 1151 | 'getSalt', 1152 | 'call', 1153 | [ 1154 | -1 1155 | ], 1156 | true, 1157 | value => { 1158 | assert.strictEqual( 1159 | value, 1160 | address.toLowerCase() + 'ffffffffffffffffffffffff' 1161 | ) 1162 | } 1163 | ) 1164 | 1165 | result = await web3.eth.call({ 1166 | from: address, 1167 | to: Metapod.options.address, 1168 | value: 0, 1169 | gas: 100000, 1170 | gasPrice: '0x4A817C800', 1171 | data: '0x1215c971ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff' 1172 | }) 1173 | assert.strictEqual(result, address.toLowerCase() + 'ffffffffffffffffffffffff') 1174 | console.log(' ✓ extra dirty bits are correctly cleaned from identifier') 1175 | passed++ 1176 | 1177 | 1178 | await runTest( 1179 | 'CodeCheck confirms that no metamorphic contract exists anymore', 1180 | CodeCheck, 1181 | 'check', 1182 | 'call', 1183 | [ 1184 | metapodMetamorphicContractAddress 1185 | ], 1186 | true, 1187 | value => { 1188 | assert.strictEqual(value, null) 1189 | } 1190 | ) 1191 | 1192 | await runTest( 1193 | 'Metapod can redeploy a contract and include a value', 1194 | Metapod, 1195 | 'deploy', 1196 | 'send', 1197 | [ 1198 | 0, 1199 | ( 1200 | '0x603680600b6000396000f36e' + 1201 | Metapod.options.address.slice(2 + (2 * 5)) + 1202 | '3318602b5773' + 1203 | metapodVaultContractAddress.slice(2) + 1204 | 'ff5b600160005260206000f3' 1205 | ) 1206 | ], 1207 | true, 1208 | receipt => { 1209 | assert.strictEqual( 1210 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 1211 | metapodMetamorphicContractAddress 1212 | ) 1213 | }, 1214 | address, 1215 | 12345 1216 | ) 1217 | 1218 | await runTest( 1219 | 'CodeCheck can check the code of the new transient contract', 1220 | CodeCheck, 1221 | 'check', 1222 | 'call', 1223 | [ 1224 | metapodTransientContractAddress 1225 | ], 1226 | true, 1227 | value => { 1228 | assert.strictEqual(value, null) 1229 | } 1230 | ) 1231 | 1232 | await runTest( 1233 | 'CodeCheck can check the code of the new metamorphic contract', 1234 | CodeCheck, 1235 | 'check', 1236 | 'call', 1237 | [ 1238 | metapodMetamorphicContractAddress 1239 | ], 1240 | true, 1241 | value => { 1242 | assert.strictEqual( 1243 | value, 1244 | ( 1245 | '0x6e' + 1246 | Metapod.options.address.slice(2 + (2 * 5)).toLowerCase() + 1247 | '3318602b5773' + 1248 | metapodVaultContractAddress.slice(2).toLowerCase() + 1249 | 'ff5b600160005260206000f3' 1250 | ) 1251 | ) 1252 | } 1253 | ) 1254 | 1255 | balance = await web3.eth.getBalance(metapodMetamorphicContractAddress) 1256 | assert.strictEqual(balance, '12345') 1257 | console.log(' ✓ balance of metamorphic contract is correct') 1258 | passed++ 1259 | 1260 | await runTest( 1261 | 'CodeCheck confirms that no vault contract exists yet', 1262 | CodeCheck, 1263 | 'check', 1264 | 'call', 1265 | [ 1266 | metapodVaultContractAddress 1267 | ], 1268 | true, 1269 | value => { 1270 | assert.strictEqual(value, null) 1271 | } 1272 | ) 1273 | 1274 | await runTest( 1275 | 'Metapod can destroy a metamorphic contract with a balance', 1276 | Metapod, 1277 | 'destroy', 1278 | 'send', 1279 | [ 1280 | 0 1281 | ] 1282 | ) 1283 | 1284 | await runTest( 1285 | 'CodeCheck confirms that no vault contract exists yet', 1286 | CodeCheck, 1287 | 'check', 1288 | 'call', 1289 | [ 1290 | metapodVaultContractAddress 1291 | ], 1292 | true, 1293 | value => { 1294 | assert.strictEqual(value, null) 1295 | } 1296 | ) 1297 | 1298 | balance = await web3.eth.getBalance(metapodVaultContractAddress) 1299 | assert.strictEqual(balance, '12345') 1300 | console.log(' ✓ balance of vault contract is correct') 1301 | passed++ 1302 | 1303 | await runTest( 1304 | 'Metapod can redeploy a contract when the vault has a balance', 1305 | Metapod, 1306 | 'deploy', 1307 | 'send', 1308 | [ 1309 | 0, 1310 | ( 1311 | '0x603680600b6000396000f36e' + 1312 | Metapod.options.address.slice(2 + (2 * 5)) + 1313 | '3318602b5773' + 1314 | metapodVaultContractAddress.slice(2) + 1315 | 'ff5b600160005260206000f3' 1316 | ) 1317 | ], 1318 | true, 1319 | receipt => { 1320 | assert.strictEqual( 1321 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 1322 | metapodMetamorphicContractAddress 1323 | ) 1324 | }, 1325 | address, 1326 | 1 1327 | ) 1328 | 1329 | await runTest( 1330 | 'CodeCheck confirms that a vault contract now exists', 1331 | CodeCheck, 1332 | 'check', 1333 | 'call', 1334 | [ 1335 | metapodVaultContractAddress 1336 | ], 1337 | true, 1338 | value => { 1339 | assert.strictEqual( 1340 | value, 1341 | ( 1342 | "0x586e" + 1343 | Metapod.options.address.slice(2 + (2 * 5)).toLowerCase() + 1344 | "33185857595959303173" + 1345 | metapodTransientContractAddress.slice(2).toLowerCase() + 1346 | "5af1" 1347 | ) 1348 | ) 1349 | } 1350 | ) 1351 | 1352 | balance = await web3.eth.getBalance(metapodMetamorphicContractAddress) 1353 | assert.strictEqual(balance, '12346') 1354 | console.log(' ✓ balance of metamorphic contract is correct') 1355 | passed++ 1356 | 1357 | await runTest( 1358 | 'Metapod can destroy a metamorphic contract with balance once vault exists', 1359 | Metapod, 1360 | 'destroy', 1361 | 'send', 1362 | [ 1363 | 0 1364 | ] 1365 | ) 1366 | 1367 | balance = await web3.eth.getBalance(metapodVaultContractAddress) 1368 | assert.strictEqual(balance, '12346') 1369 | console.log(' ✓ balance of vault contract is correct') 1370 | passed++ 1371 | 1372 | await runTest( 1373 | 'Metapod can redeploy a contract when the vault is deployed with a balance', 1374 | Metapod, 1375 | 'deploy', 1376 | 'send', 1377 | [ 1378 | 0, 1379 | ( 1380 | '0x603680600b6000396000f36e' + 1381 | Metapod.options.address.slice(2 + (2 * 5)) + 1382 | '3318602b5773' + 1383 | metapodVaultContractAddress.slice(2) + 1384 | 'ff5b600160005260206000f3' 1385 | ) 1386 | ], 1387 | true, 1388 | receipt => { 1389 | assert.strictEqual( 1390 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 1391 | metapodMetamorphicContractAddress 1392 | ) 1393 | }, 1394 | address, 1395 | 10 1396 | ) 1397 | 1398 | balance = await web3.eth.getBalance(metapodMetamorphicContractAddress) 1399 | assert.strictEqual(balance, '12356') 1400 | console.log(' ✓ balance of metamorphic contract is correct') 1401 | passed++ 1402 | 1403 | await runTest( 1404 | 'Metapod can destroy a metamorphic contract with balance ahead of recover', 1405 | Metapod, 1406 | 'destroy', 1407 | 'send', 1408 | [ 1409 | 0 1410 | ] 1411 | ) 1412 | 1413 | balance = await web3.eth.getBalance(metapodVaultContractAddress) 1414 | assert.strictEqual(balance, '12356') 1415 | console.log(' ✓ balance of vault contract is correct') 1416 | passed++ 1417 | 1418 | // check address balance beforehand (use proxy?) 1419 | await runTest( 1420 | 'Metapod can recover the balance', 1421 | Metapod, 1422 | 'recover', 1423 | 'send', 1424 | [ 1425 | 0 1426 | ] 1427 | ) 1428 | // check address balance afterwards 1429 | 1430 | balance = await web3.eth.getBalance(metapodVaultContractAddress) 1431 | assert.strictEqual(balance, '0') 1432 | console.log(' ✓ balance of vault contract is correct') 1433 | passed++ 1434 | 1435 | console.log('### Testing KakunaBasicTest without prelude ###') 1436 | 1437 | await runTest( 1438 | 'constructor correctly sets test value', 1439 | KakunaBasicTest, 1440 | 'constructorTest', 1441 | 'call', 1442 | [], 1443 | true, 1444 | value => { 1445 | assert.ok(value) 1446 | } 1447 | ) 1448 | 1449 | await runTest( 1450 | 'codecopy test correctly retrieves constant value', 1451 | KakunaBasicTest, 1452 | 'codecopyTest', 1453 | 'call', 1454 | [], 1455 | true, 1456 | value => { 1457 | assert.strictEqual( 1458 | value, 1459 | 'This is an example string for testing CODECOPY.' 1460 | ) 1461 | } 1462 | ) 1463 | 1464 | await runTest( 1465 | 'jump test correctly jumps to first internal function', 1466 | KakunaBasicTest, 1467 | 'jumpTest', 1468 | 'call', 1469 | [], 1470 | true, 1471 | value => { 1472 | assert.strictEqual( 1473 | value, 1474 | false 1475 | ) 1476 | }, 1477 | address, 1478 | 0, 1479 | 30001 1480 | ) 1481 | 1482 | await runTest( 1483 | 'jump test correctly jumps to second internal function', 1484 | KakunaBasicTest, 1485 | 'jumpTest', 1486 | 'call', 1487 | [], 1488 | true, 1489 | value => { 1490 | assert.ok(value) 1491 | }, 1492 | address, 1493 | 0, 1494 | 30002 1495 | ) 1496 | 1497 | console.log('### Testing KakunaBasicTest with prelude ###') 1498 | 1499 | await runTest( 1500 | 'constructor correctly sets test value', 1501 | KakunaBasicTestWithPrelude, 1502 | 'constructorTest', 1503 | 'call', 1504 | [], 1505 | true, 1506 | value => { 1507 | assert.ok(value) 1508 | } 1509 | ) 1510 | 1511 | await runTest( 1512 | 'codecopy test correctly retrieves constant value', 1513 | KakunaBasicTestWithPrelude, 1514 | 'codecopyTest', 1515 | 'call', 1516 | [], 1517 | true, 1518 | value => { 1519 | assert.strictEqual( 1520 | value, 1521 | 'This is an example string for testing CODECOPY.' 1522 | ) 1523 | } 1524 | ) 1525 | 1526 | await runTest( 1527 | 'jump test correctly jumps to first internal function', 1528 | KakunaBasicTestWithPrelude, 1529 | 'jumpTest', 1530 | 'call', 1531 | [], 1532 | true, 1533 | value => { 1534 | assert.strictEqual( 1535 | value, 1536 | false 1537 | ) 1538 | }, 1539 | address, 1540 | 0, 1541 | 30001 1542 | ) 1543 | 1544 | await runTest( 1545 | 'jump test correctly jumps to second internal function', 1546 | KakunaBasicTestWithPrelude, 1547 | 'jumpTest', 1548 | 'call', 1549 | [], 1550 | true, 1551 | value => { 1552 | assert.ok(value) 1553 | }, 1554 | address, 1555 | 0, 1556 | 30002 1557 | ) 1558 | 1559 | console.log('### Testing Metapod with Kakuna ###') 1560 | 1561 | metapodPrelude = `0x6e${ 1562 | Metapod.options.address.toLowerCase().slice(2 + (2 * 5)) 1563 | }3318602b5773${ 1564 | metapodVaultContractAddress.toLowerCase().slice(2) 1565 | }ff5b` 1566 | 1567 | ContractOneWithPrelude = await kakuna.kakuna( 1568 | 'ContractOne', 1569 | metapodPrelude, 1570 | false // no logging 1571 | ) 1572 | kakunaInitCode = ContractOneWithPrelude[0] 1573 | kakunaRuntime = ContractOneWithPrelude[1] 1574 | 1575 | await runTest( 1576 | 'Metapod can deploy a contract from Kakuna with the appropriate prelude', 1577 | Metapod, 1578 | 'deploy', 1579 | 'send', 1580 | [ 1581 | 0, 1582 | Buffer.from(kakunaInitCode.slice(2), 'hex') 1583 | ], 1584 | true, 1585 | receipt => { 1586 | assert.strictEqual( 1587 | receipt.events.Metamorphosed.returnValues.metamorphicContract, 1588 | metapodMetamorphicContractAddress 1589 | ) 1590 | } 1591 | ) 1592 | 1593 | const MetapodKakunaContract = new web3.eth.Contract( 1594 | ContractOneArtifact.abi, 1595 | metapodMetamorphicContractAddress 1596 | ) 1597 | 1598 | await runTest( 1599 | 'CodeCheck can check the code of the new metamorphic contract', 1600 | CodeCheck, 1601 | 'check', 1602 | 'call', 1603 | [ 1604 | metapodMetamorphicContractAddress 1605 | ], 1606 | true, 1607 | value => { 1608 | assert.strictEqual( 1609 | value, 1610 | kakunaRuntime 1611 | ) 1612 | } 1613 | ) 1614 | 1615 | await runTest( 1616 | 'Deployed contract can be interacted with', 1617 | MetapodKakunaContract, 1618 | 'initialize' 1619 | ) 1620 | 1621 | await runTest( 1622 | 'Deployed contract interaction works', 1623 | MetapodKakunaContract, 1624 | 'test', 1625 | 'call', 1626 | [], 1627 | true, 1628 | value => { 1629 | assert.strictEqual(value, '1') 1630 | } 1631 | ) 1632 | 1633 | await runTest( 1634 | 'Metapod can destroy a metamorphic contract from Kakuna', 1635 | Metapod, 1636 | 'destroy', 1637 | 'send', 1638 | [0] 1639 | ) 1640 | 1641 | console.log( 1642 | `completed ${passed + failed} test${passed + failed === 1 ? '' : 's'} ` + 1643 | `with ${failed} failure${failed === 1 ? '' : 's'}.` 1644 | ) 1645 | 1646 | if (failed > 0) { 1647 | process.exit(1) 1648 | } 1649 | 1650 | // exit. 1651 | return 0 1652 | 1653 | }} 1654 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | var Web3 = require('web3') 2 | 3 | module.exports = { 4 | networks: { 5 | development: { 6 | provider: new Web3('ws://localhost:8545'), 7 | network_id: "*" 8 | } 9 | }, 10 | compilers: { 11 | solc: { 12 | version: "0.5.6", 13 | settings: { 14 | optimizer: { 15 | enabled: true, 16 | runs: 999 17 | }, 18 | evmVersion: "constantinople" 19 | } 20 | } 21 | } 22 | } 23 | --------------------------------------------------------------------------------