From 679e5a7422a1341e02ef9bbf365cfe907d09edee Mon Sep 17 00:00:00 2001 From: 0age <0age@protonmail.com> Date: Wed, 27 Feb 2019 10:29:13 -0500 Subject: [PATCH] Remove transient contracts, deploy metamorphic directly --- README.md | 2 +- contracts/MetamorphicContractFactory.sol | 263 ++++++++++++++--------- contracts/TransientContract.sol | 60 ------ contracts/mock/CodeCheck.sol | 25 +++ package.json | 2 +- scripts/test/test.js | 121 +++++++---- 6 files changed, 262 insertions(+), 211 deletions(-) delete mode 100644 contracts/TransientContract.sol create mode 100644 contracts/mock/CodeCheck.sol diff --git a/README.md b/README.md index 3eb7208..0fb2d9d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ > Metamorphic - A factory contract for creating metamorphic (i.e. redeployable) contracts. -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 first deploying a [transient contract](https://github.com/0age/metamorphic/blob/master/contracts/TransientContract.sol) with fixed, non-deterministic initialization code via the CREATE2 opcode. This transient contract then creates the metamorphic contract by cloning a given implementation contract and deploying via CREATE, then immediately self-destructs. 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. 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 in contracts it deploys. +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. 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 in contracts it deploys. **DISCLAIMER: this implements a highly experimental feature / bug - be sure to implement appropriate controls on your metamorphic contracts and *educate the users of your contract* if it will be interacted with! CREATE2 will not be available on mainnet until (at least) block 7,280,000. This contract has not yet been fully tested or audited - proceed with caution and please share any exploits or optimizations you discover.** diff --git a/contracts/MetamorphicContractFactory.sol b/contracts/MetamorphicContractFactory.sol index f75d1d8..3e97a12 100644 --- a/contracts/MetamorphicContractFactory.sol +++ b/contracts/MetamorphicContractFactory.sol @@ -5,42 +5,117 @@ pragma solidity 0.5.3; * @title Metamorphic Contract Factory * @author 0age * @notice This contract creates metamorphic contracts, or contracts that can be - * redeployed with new code to the same address. It does so by first deploying a - * transient contract with fixed, non-deterministic initialization code via the - * CREATE2 opcode. This transient contract then creates the metamorphic contract - * by cloning a given implementation contract and deploying via CREATE, then - * immediately self-destructs. 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. + * 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. * @dev CREATE2 will not be available on mainnet until (at least) block * 7,280,000. This contract has not yet been fully tested or audited - proceed * with caution and please share any exploits or optimizations you discover. */ contract MetamorphicContractFactory { - // store the initialization code for transient contracts. - bytes private _transientContractInitializationCode; + event Metamorphosed(address metamorphicContract, address newImplementation); - // store the hash of the initialization code for transient contracts as well. - bytes32 private _transientContractInitializationCodeHash; + // store the initialization code for metamorphic contracts. + bytes private _metamorphicContractInitializationCode; - // maintain a mapping of transient contracts to metamorphic implementations. + // store hash of the initialization code for metamorphic contracts as well. + bytes32 private _metamorphicContractInitializationCodeHash; + + // maintain a mapping of metamorphic contracts to metamorphic implementations. mapping(address => address) private _implementations; /** - * @dev In the constructor, set up the initialization code for transient + * @dev In the constructor, set up the initialization code for metamorphic * contracts as well as the keccak256 hash of the given initialization code. - * @param transientContractInitializationCode bytes The initialization code - * that will be used to deploy each transient contract, which in turn deploy - * each metamorphic contract. + * + * Metamorphic contract initialization code (29 bytes): + * + * 0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3 + * + * Description: + * + * pc|op|name | [stack] | + * + * ** set the first stack item to zero - used later ** + * 00 58 getpc [0] <> + * + * ** set second stack item to 32, length of word returned from staticcall ** + * 01 60 push1 + * 02 20 outsize [0, 32] <> + * + * ** set third stack item to 0, position of word returned from staticcall ** + * 03 81 dup2 [0, 32, 0] <> + * + * ** set fourth stack item to 4, length of selector given to staticcall ** + * 04 58 getpc [0, 32, 0, 4] <> + * + * ** set fifth stack item to 28, position of selector given to staticcall ** + * 05 60 push1 + * 06 1c inpos [0, 32, 0, 4, 28] <> + * + * ** set the sixth stack item to msg.sender, target address for staticcall ** + * 07 33 caller [0, 32, 0, 4, 28, caller] <> + * + * ** set the seventh stack item to msg.gas, gas to forward for staticcall ** + * 08 5a gas [0, 32, 0, 4, 28, caller, gas] <> + * + * ** set the eighth stack item to selector, "what" to store via mstore ** + * 09 63 push4 + * 10 aaf10f42 selector [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42] <> + * + * ** set the ninth stack item to 0, "where" to store via mstore *** + * 11 87 dup8 [0, 32, 0, 4, 28, caller, gas, 0xaaf10f42, 0] <> + * + * ** call mstore, consume 8 and 9 from the stack, place selector in memory ** + * 12 52 mstore [0, 32, 0, 4, 0, caller, gas] <0xaaf10f42> + * + * ** call staticcall, consume items 2 through 7, place address in memory ** + * 13 fa staticcall [0, 1 (if successful)]
+ * + * ** flip success bit in second stack item to set to 0 ** + * 14 15 iszero [0, 0]
+ * + * ** push a third 0 to the stack, position of address in memory ** + * 15 81 dup2 [0, 0, 0]
+ * + * ** place address from position in memory onto third stack item ** + * 16 51 mload [0, 0, address] <> + * + * ** place address to fourth stack item for extcodesize to consume ** + * 17 80 dup1 [0, 0, address, address] <> + * + * ** get extcodesize on fourth stack item for extcodecopy ** + * 18 3b extcodesize [0, 0, address, size] <> + * + * ** dup and swap size for use by return at end of init code ** + * 19 80 dup1 [0, 0, address, size, size] <> + * 20 93 swap4 [size, 0, address, size, 0] <> + * + * ** push code position 0 to stack and reorder stack items for extcodecopy ** + * 21 80 dup1 [size, 0, address, size, 0, 0] <> + * 22 91 swap2 [size, 0, address, 0, 0, size] <> + * 23 92 swap3 [size, 0, size, 0, 0, address] <> + * + * ** call extcodecopy, consume four items, clone runtime code to memory ** + * 24 3c extcodecopy [size, 0] + * + * ** return to deploy final code in memory ** + * 25 f3 return [] *deployed!* + * */ - constructor(bytes memory transientContractInitializationCode) public { - // assign the supplied initialization code for the transient contract. - _transientContractInitializationCode = transientContractInitializationCode; + constructor() public { + // assign the initialization code for the metamorphic contract. + _metamorphicContractInitializationCode = ( + hex"5860208158601c335a63aaf10f428752fa158151803b80938091923cf3" + ); // calculate and assign the keccak256 hash of the initialization code. - _transientContractInitializationCodeHash = keccak256( + _metamorphicContractInitializationCodeHash = keccak256( abi.encodePacked( - transientContractInitializationCode + _metamorphicContractInitializationCode ) ); } @@ -59,8 +134,8 @@ contract MetamorphicContractFactory { * thus will determine the resulant address of the metamorphic contract. * @param implementationContractInitializationCode bytes The initialization * code for the implementation contract for the metamorphic contract. It will - * be used to deploy a new contract that the transient contract will then - * clone and use to create the metamorphic contract. + * be used to deploy a new contract that the metamorphic contract will then + * clone in its constructor. * @param metamorphicContractInitializationCalldata bytes An optional data * parameter that can be used to atomically initialize the metamorphic * contract. @@ -78,45 +153,43 @@ contract MetamorphicContractFactory { bytes memory data = metamorphicContractInitializationCalldata; // move the initialization code from storage to memory. - bytes memory initCode = _transientContractInitializationCode; + bytes memory initCode = _metamorphicContractInitializationCode; - // declare variable to verify successful transient contract deployment. - address deployedTransientContract; + // declare variable to verify successful metamorphic contract deployment. + address deployedMetamorphicContract; - // determine the address of the transient contract. - address transientContractAddress = _getTransientContractAddress(salt); + // determine the address of the metamorphic contract. + metamorphicContractAddress = _getMetamorphicContractAddress(salt); // declare a variable for the address of the implementation contract. - address implementation; + address implementationContract; // load implementation init code and length, then deploy via CREATE. /* solhint-disable no-inline-assembly */ assembly { let encoded_data := add(0x20, implInitCode) // load initialization code. let encoded_size := mload(implInitCode) // load init code's length. - implementation := create( // call CREATE with 3 arguments. + implementationContract := create( // call CREATE with 3 arguments. 0, // do not forward any endowment. encoded_data, // pass in initialization code. encoded_size // pass in init code's length. ) } /* solhint-enable no-inline-assembly */ - require(implementation != address(0), "Could not deploy implementation."); - - // store the implementation to be retrieved by the transient contract. - _implementations[transientContractAddress] = implementation; - - // determine the address of the metamorphic contract. - metamorphicContractAddress = _getMetamorphicContractAddress( - transientContractAddress + require( + implementationContract != address(0), + "Could not deploy implementation." ); - // load transient contract data and length of data, then deploy via CREATE2. + // store the implementation to be retrieved by the metamorphic contract. + _implementations[metamorphicContractAddress] = implementationContract; + + // load metamorphic contract data and length of data and deploy via CREATE2. /* solhint-disable no-inline-assembly */ assembly { let encoded_data := add(0x20, initCode) // load initialization code. let encoded_size := mload(initCode) // load the init code's length. - deployedTransientContract := create2( // call CREATE2 with 4 arguments. + deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments. 0, // do not forward any endowment. encoded_data, // pass in initialization code. encoded_size, // pass in init code's length. @@ -126,18 +199,20 @@ contract MetamorphicContractFactory { // ensure that the contracts were successfully deployed. require( - deployedTransientContract == transientContractAddress, + deployedMetamorphicContract == metamorphicContractAddress, "Failed to deploy the new metamorphic contract." ); // initialize the new metamorphic contract if any data or value is provided. if (data.length > 0 || msg.value > 0) { /* solhint-disable avoid-call-value */ - (bool success,) = metamorphicContractAddress.call.value(msg.value)(data); + (bool success,) = deployedMetamorphicContract.call.value(msg.value)(data); /* solhint-enable avoid-call-value */ require(success, "Failed to initialize the new metamorphic contract."); } + + emit Metamorphosed(deployedMetamorphicContract, implementationContract); } /* solhint-enable function-max-lines */ /** @@ -169,28 +244,23 @@ contract MetamorphicContractFactory { bytes memory data = metamorphicContractInitializationCalldata; // move the initialization code from storage to memory. - bytes memory initCode = _transientContractInitializationCode; + bytes memory initCode = _metamorphicContractInitializationCode; - // declare variable to verify successful transient contract deployment. - address deployedTransientContract; - - // determine the address of the transient contract. - address transientContractAddress = _getTransientContractAddress(salt); + // declare variable to verify successful metamorphic contract deployment. + address deployedMetamorphicContract; // determine the address of the metamorphic contract. - metamorphicContractAddress = _getMetamorphicContractAddress( - transientContractAddress - ); + metamorphicContractAddress = _getMetamorphicContractAddress(salt); - // store the implementation to be retrieved by the transient contract. - _implementations[transientContractAddress] = implementationContract; + // store the implementation to be retrieved by the metamorphic contract. + _implementations[metamorphicContractAddress] = implementationContract; // using inline assembly: load data and length of data, then call CREATE2. /* solhint-disable no-inline-assembly */ assembly { let encoded_data := add(0x20, initCode) // load initialization code. let encoded_size := mload(initCode) // load the init code's length. - deployedTransientContract := create2( // call CREATE2 with 4 arguments. + deployedMetamorphicContract := create2( // call CREATE2 with 4 arguments. 0, // do not forward any endowment. encoded_data, // pass in initialization code. encoded_size, // pass in init code's length. @@ -200,7 +270,7 @@ contract MetamorphicContractFactory { // ensure that the contracts were successfully deployed. require( - deployedTransientContract == transientContractAddress, + deployedMetamorphicContract == metamorphicContractAddress, "Failed to deploy the new metamorphic contract." ); @@ -212,72 +282,75 @@ contract MetamorphicContractFactory { require(success, "Failed to initialize the new metamorphic contract."); } + + emit Metamorphosed(deployedMetamorphicContract, implementationContract); } /** * @dev View function for retrieving the address of the implementation - * contract to clone. Called by the constructor of each transient contract. + * contract to clone. Called by the constructor of each metamorphic contract. */ function getImplementation() external view returns (address implementation) { return _implementations[msg.sender]; } /** - * @dev Compute the address of the transient contract that will be created - * upon submitting a given salt to the contract. - * @param salt bytes32 The nonce passed into CREATE2 by transient contract. - * @return Address of the corresponding transient contract. + * @dev 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. + * @param metamorphicContractAddress address The address of the metamorphic + * contract. + * @return Address of the corresponding implementation contract. */ - function findTransientContractAddress( - bytes32 salt - ) external view returns (address transientContractAddress) { - // determine the address where the transient contract will be deployed. - transientContractAddress = _getTransientContractAddress(salt); + function getImplementationContractAddress( + address metamorphicContractAddress + ) external view returns (address implementationContractAddress) { + return _implementations[metamorphicContractAddress]; } /** * @dev Compute the address of the metamorphic contract that will be created - * by the transient contract upon submitting a given salt to the contract. - * @param salt bytes32 The nonce passed into CREATE2 by transient contract. + * upon submitting a given salt to the contract. + * @param salt bytes32 The nonce passed into CREATE2 by metamorphic contract. * @return Address of the corresponding metamorphic contract. */ function findMetamorphicContractAddress( bytes32 salt ) external view returns (address metamorphicContractAddress) { - // determine the address of the metamorphic contract. - metamorphicContractAddress = _getMetamorphicContractAddress( - _getTransientContractAddress(salt) - ); + // determine the address where the metamorphic contract will be deployed. + metamorphicContractAddress = _getMetamorphicContractAddress(salt); } /** - * @dev View function for retrieving the initialization code of transient + * @dev View function for retrieving the initialization code of metamorphic * contracts for purposes of verification. */ - function getTransientContractInitializationCode() external view returns ( - bytes memory transientContractInitializationCode + function getMetamorphicContractInitializationCode() external view returns ( + bytes memory metamorphicContractInitializationCode ) { - return _transientContractInitializationCode; + return _metamorphicContractInitializationCode; } /** * @dev View function for retrieving the keccak256 hash of the initialization - * code of transient contracts for purposes of verification. + * code of metamorphic contracts for purposes of verification. */ - function getTransientContractInitializationCodeHash() external view returns ( - bytes32 transientContractInitializationCodeHash + function getMetamorphicContractInitializationCodeHash() external view returns ( + bytes32 metamorphicContractInitializationCodeHash ) { - return _transientContractInitializationCodeHash; + return _metamorphicContractInitializationCodeHash; } /** - * @dev Internal view function for calculating a transient contract address + * @dev Internal view function for calculating a metamorphic contract address * given a particular salt. */ - function _getTransientContractAddress( + function _getMetamorphicContractAddress( bytes32 salt ) internal view returns (address) { - // determine the address of the transient contract. + // determine the address of the metamorphic contract. return address( uint160( // downcast to match the address type. uint256( // convert to uint to truncate upper digits. @@ -286,31 +359,7 @@ contract MetamorphicContractFactory { hex"ff", // start with 0xff to distinguish from RLP. address(this), // this contract will be the caller. salt, // pass in the supplied salt value. - _transientContractInitializationCodeHash // supply init code hash. - ) - ) - ) - ) - ); - } - - /** - * @dev Internal view function for calculating a metamorphic contract address - * given a particular salt. - */ - function _getMetamorphicContractAddress( - address transientContractAddress - ) internal pure returns (address payable) { - // determine the address of the metamorphic contract. - return address( - uint160( // downcast to match the address type. - uint256( // set to uint to truncate upper digits. - keccak256( // compute CREATE hash via RLP encoding. - abi.encodePacked( // pack all inputs to the hash together. - byte(0xd6), // first RLP byte. - byte(0x94), // second RLP byte. - transientContractAddress, // the transient contract is the sender. - byte(0x01) // nonce begins at 1 for contracts. + _metamorphicContractInitializationCodeHash // the init code hash. ) ) ) diff --git a/contracts/TransientContract.sol b/contracts/TransientContract.sol deleted file mode 100644 index f086403..0000000 --- a/contracts/TransientContract.sol +++ /dev/null @@ -1,60 +0,0 @@ -pragma solidity 0.5.3; - - -/** - * @title Metamorphic Contract Factory Interface - * @notice An interface to the factory contract that holds a reference to the - * implementation contract that will be cloned by the transient contract when - * creating the metamorphic contract. - */ -interface FactoryInterface { - function getImplementation() external view returns (address implementation); -} - - -/** - * @title Transient Contract - * @author 0age - * @notice This contract will create a metamorphic contract, or an upgradeable - * contract that does not rely on a transparent proxy, when deployed using - * CREATE2. Unlike with upgradeable transparent proxies, the state of a - * metamorphic contract will be wiped clean with each upgrade. With great power - * comes great responsibility - implement appropriate controls and educate the - * users of your contract if it will be interacted with! - */ -contract TransientContract { - /** - * @dev In the constructor, retrieve the address of the implementation - * contract for the new version of the metamorphic contract, clone the - * implementation, deploy the cloned code to a consistent address, and destroy - * the transient contract, forwarding any value to the metamorphic contract. - */ - constructor() public { - // retrieve the target implementation address from creator of this contract. - address implementation = FactoryInterface(msg.sender).getImplementation(); - - // set up a memory location for the address of the new metamorphic contract. - address payable metamorphicContractAddress; - - // clone target implementation's code to the metamorphic contract address. - // see https://gist.github.com/holiman/069de8d056a531575d2b786df3345665 - /* solhint-disable no-inline-assembly */ - assembly { - mstore( - 0, // use the first memory location. - or ( // place the implementation address into @mhswende's gnarly uint. - 0x5880730000000000000000000000000000000000000000803b80938091923cF3, - mul(implementation, 0x1000000000000000000) - ) - ) - // create the metamorphic contract using the clone of the target. - metamorphicContractAddress := create(0, 0, 32) - } /* solhint-enable no-inline-assembly */ - - // ensure that the metamorphic contract was successfully deployed. - require(metamorphicContractAddress != address(0)); - - // destroy transient contract and forward all value to metamorphic contract. - selfdestruct(metamorphicContractAddress); - } -} diff --git a/contracts/mock/CodeCheck.sol b/contracts/mock/CodeCheck.sol new file mode 100644 index 0000000..a0bd2c4 --- /dev/null +++ b/contracts/mock/CodeCheck.sol @@ -0,0 +1,25 @@ +pragma solidity 0.5.3; + + +/** + * @title CodeCheck + * @author 0age + * @notice This contract checks the deployed runtime code of another contract. + */ +contract CodeCheck { + function check(address target) public view returns (bytes memory code) { + /* solhint-disable no-inline-assembly */ + assembly { + // retrieve the size of the external code + let size := extcodesize(target) + // allocate output byte array + code := mload(0x40) + // new "memory end" including padding + mstore(0x40, add(code, and(add(add(size, 0x20), 0x1f), not(0x1f)))) + // store length in memory + mstore(code, size) + // get the code using extcodecopy + extcodecopy(target, add(code, 0x20), 0, size) + } /* solhint-enable no-inline-assembly */ + } +} diff --git a/package.json b/package.json index 7bde9aa..d52657f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "metamorphic", - "version": "0.0.1", + "version": "0.1.0", "description": "A factory contract for creating metamorphic (i.e. redeployable) contracts.", "author": "0age", "license": "MIT", diff --git a/scripts/test/test.js b/scripts/test/test.js index 3cd0abd..b48d554 100644 --- a/scripts/test/test.js +++ b/scripts/test/test.js @@ -3,10 +3,12 @@ var fs = require('fs') var util = require('ethereumjs-util') const MetamorphicContractFactoryArtifact = require('../../build/contracts/MetamorphicContractFactory.json') -const TransientContractArtifact = require('../../build/contracts/TransientContract.json') const ImmutableCreate2FactoryArtifact = require('../../build/contracts/ImmutableCreate2Factory.json') const ContractOneArtifact = require('../../build/contracts/ContractOne.json') const ContractTwoArtifact = require('../../build/contracts/ContractTwo.json') +const CodeCheckArtifact = require('../../build/contracts/CodeCheck.json') + +const MetamorphicContractBytecode = '0x5860208158601c335a63aaf10f428752fa158151803b80938091923cf3' module.exports = {test: async function (provider, testingContext) { var web3 = provider @@ -265,10 +267,6 @@ module.exports = {test: async function (provider, testingContext) { MetamorphicContractFactoryArtifact.abi ) - const TransientContractDeployer = new web3.eth.Contract( - TransientContractArtifact.abi - ) - const ImmutableCreate2FactoryDeployer = new web3.eth.Contract( ImmutableCreate2FactoryArtifact.abi ) @@ -277,6 +275,10 @@ module.exports = {test: async function (provider, testingContext) { ContractTwoArtifact.abi ) + const CodeCheckDeployer = new web3.eth.Contract( + CodeCheckArtifact.abi + ) + let dataPayload = ImmutableCreate2FactoryDeployer.deploy({ data: ImmutableCreate2FactoryArtifact.bytecode }).encodeABI() @@ -304,15 +306,13 @@ module.exports = {test: async function (provider, testingContext) { passed++ dataPayload = MetamorphicContractFactoryDeployer.deploy({ - data: MetamorphicContractFactoryArtifact.bytecode, - arguments: [TransientContractArtifact.bytecode] + data: MetamorphicContractFactoryArtifact.bytecode }).encodeABI() deployGas = await getDeployGas(dataPayload) const MetamorphicContractFactory = await MetamorphicContractFactoryDeployer.deploy({ - data: MetamorphicContractFactoryArtifact.bytecode, - arguments: [TransientContractArtifact.bytecode] + data: MetamorphicContractFactoryArtifact.bytecode }).send({ from: address, gas: deployGas, @@ -357,41 +357,46 @@ module.exports = {test: async function (provider, testingContext) { ) passed++ + dataPayload = CodeCheckDeployer.deploy({ + data: CodeCheckArtifact.bytecode + }).encodeABI() + + deployGas = await getDeployGas(dataPayload) + + const CodeCheck = await CodeCheckDeployer.deploy({ + data: CodeCheckArtifact.bytecode + }).send({ + from: address, + gas: deployGas, + gasPrice: 10 ** 1 + }).catch(error => { + console.error(error) + console.log( + ` ✘ CodeCheck contract deploys successfully for ${deployGas} gas` + ) + failed++ + process.exit(1) + }) + + console.log( + ` ✓ CodeCheck contract deploys successfully for ${deployGas} gas` + ) + passed++ + let create2payload = ( '0xff' + MetamorphicContractFactory.options.address.slice(2) + address.slice(2) + '000000000000000000000000' + - web3.utils.keccak256(TransientContractArtifact.bytecode).slice(2) - ) - - const targetTransientContractAddress = web3.utils.toChecksumAddress( - '0x' + web3.utils.sha3( - create2payload, - {encoding: "hex"} - ).slice(12).substring(14) + web3.utils.keccak256(MetamorphicContractBytecode, {encoding: "hex"}).slice(2) ) const targetMetamorphicContractAddress = web3.utils.toChecksumAddress( '0x' + web3.utils.sha3( - '0xd694' + targetTransientContractAddress.slice(2) + '01', + create2payload, {encoding: "hex"} ).slice(12).substring(14) ) - await runTest( - 'MetamorphicContractFactory can check for address of a transient contract', - MetamorphicContractFactory, - 'findTransientContractAddress', - 'call', - [ - address + '000000000000000000000000' - ], - true, - value => { - assert.strictEqual(value, targetTransientContractAddress) - } - ) - await runTest( 'MetamorphicContractFactory can check for address of a metamorphic contract', MetamorphicContractFactory, @@ -407,31 +412,28 @@ module.exports = {test: async function (provider, testingContext) { ) await runTest( - 'MetamorphicContractFactory can get init code of transient contract', + 'MetamorphicContractFactory can get init code of metamorphic contract', MetamorphicContractFactory, - 'getTransientContractInitializationCode', + 'getMetamorphicContractInitializationCode', 'call', [], true, value => { - assert.strictEqual(value, TransientContractArtifact.bytecode) + assert.strictEqual(value, MetamorphicContractBytecode) } ) await runTest( - 'MetamorphicContractFactory can get init code hash of transient contract', + 'MetamorphicContractFactory can get init code hash of metamorphic contract', MetamorphicContractFactory, - 'getTransientContractInitializationCodeHash', + 'getMetamorphicContractInitializationCodeHash', 'call', [], true, value => { assert.strictEqual( value, - web3.utils.keccak256( - TransientContractArtifact.bytecode, - {encoding: 'hex'} - ) + web3.utils.keccak256(MetamorphicContractBytecode, {encoding: "hex"}) ) } ) @@ -445,9 +447,30 @@ module.exports = {test: async function (provider, testingContext) { address + '000000000000000000000000', ContractOneArtifact.bytecode, '0x8129fc1c' // initialize() - ] + ], + true, + receipt => { + assert.strictEqual( + receipt.events.Metamorphosed.returnValues.metamorphicContract, + targetMetamorphicContractAddress + ) + } ) + await runTest( + 'CodeCheck can check the code of the metamorphic contract', + CodeCheck, + 'check', + 'call', + [ + targetMetamorphicContractAddress + ], + true, + value => { + assert.strictEqual(value, ContractOneArtifact.deployedBytecode) + } + ) + const Metamorphic = new web3.eth.Contract( ContractOneArtifact.abi, targetMetamorphicContractAddress @@ -492,6 +515,20 @@ module.exports = {test: async function (provider, testingContext) { 1 ) + await runTest( + 'CodeCheck can check the code of the new metamorphic contract', + CodeCheck, + 'check', + 'call', + [ + targetMetamorphicContractAddress + ], + true, + value => { + assert.strictEqual(value, ContractTwoArtifact.deployedBytecode) + } + ) + await runTest( 'Metamorphic contract can check for new test value', Metamorphic,