Skip to content

Commit

Permalink
Initial Commit
Browse files Browse the repository at this point in the history
  • Loading branch information
0age committed Feb 25, 2019
0 parents commit c3fef15
Show file tree
Hide file tree
Showing 15 changed files with 1,464 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.sol linguist-language=Solidity
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
node_modules/
build/
.DS_Store
ganache-output.log
6 changes: 6 additions & 0 deletions .solhint.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extends": "strict",
"rules": {
"indent": ["warn", 2]
}
}
16 changes: 16 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
sudo: required
language: node_js
node_js:
- "10"
before_install:
# yarn
- sudo apt-key adv --fetch-keys http://dl.yarnpkg.com/debian/pubkey.gpg
- echo "deb http://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
- sudo apt-get update -qq
- sudo apt-get install -y -qq yarn
install:
- yarn install
script:
- yarn ci
cache:
yarn: true
21 changes: 21 additions & 0 deletions LICENSE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2019 0age

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
55 changes: 55 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Metamorphic

![GitHub](https://img.shields.io/github/license/0age/metamorphic.svg?colorB=brightgreen)
[![Build Status](https://travis-ci.org/0age/metamorphic.svg?branch=master)](https://travis-ci.org/0age/metamorphic)
[![standard-readme compliant](https://img.shields.io/badge/standard--readme-OK-green.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com)

> 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.

**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.**

## Table of Contents

- [Install](#install)
- [Usage](#usage)
- [API](#api)
- [Maintainers](#maintainers)
- [Contribute](#contribute)
- [License](#license)

## Install
To install locally, you'll need Node.js 10+ and Yarn *(or npm)*. To get everything set up:
```sh
$ git clone https://github.com/0age/metamorphic.git
$ cd metamorphic
$ yarn install
$ yarn build
```

## Usage
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)*:
```sh
$ yarn start
$ yarn test
$ yarn linter
$ yarn stop
```

## API

Refer to contract source and natspec for documentation and usage information.

## Maintainers

[@0age](https://github.com/0age)

## Contribute

PRs accepted gladly - make sure the tests and linters pass.

## License

MIT © 2019 0age
204 changes: 204 additions & 0 deletions contracts/ImmutableCreate2Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
pragma solidity 0.5.3;


/**
* @title Immutable Create2 Contract Factory
* @author 0age
* @notice 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.
* @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 ImmutableCreate2Factory {
// mapping to track which addresses have already been deployed.
mapping(address => bool) private _deployed;

/**
* @dev 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.
* @param salt bytes32 The nonce that will be passed into the CREATE2 call.
* @param initializationCode bytes The initialization code that will be passed
* into the CREATE2 call.
* @return Address of the contract that will be created, or the null address
* if a contract already exists at that address.
*/
function safeCreate2(
bytes32 salt,
bytes calldata initializationCode
) external payable containsCaller(salt) returns (address deploymentAddress) {
// move the initialization code from calldata to memory.
bytes memory initCode = initializationCode;

// determine the target address for contract deployment.
address targetDeploymentAddress = address(
uint160( // downcast to match the address type.
uint256( // convert to uint to truncate upper digits.
keccak256( // compute the CREATE2 hash using 4 inputs.
abi.encodePacked( // pack all inputs to the hash together.
hex"ff", // start with 0xff to distinguish from RLP.
address(this), // this contract will be the caller.
salt, // pass in the supplied salt value.
keccak256( // pass in the hash of initialization code.
abi.encodePacked(
initCode
)
)
)
)
)
)
);

// ensure that a contract hasn't been previously deployed to target address.
require(
!_deployed[targetDeploymentAddress],
"Invalid contract creation - contract has already been deployed."
);

// using inline assembly: load data and length of data, then call CREATE2.
assembly { // solhint-disable-line
let encoded_data := add(0x20, initCode) // load initialization code.
let encoded_size := mload(initCode) // load the init code's length.
deploymentAddress := create2( // call CREATE2 with 4 arguments.
callvalue, // forward any attached value.
encoded_data, // pass in initialization code.
encoded_size, // pass in init code's length.
salt // pass in the salt value.
)
}

// check address against target to ensure that deployment was successful.
require(
deploymentAddress == targetDeploymentAddress,
"Failed to deploy contract using provided salt and initialization code."
);

// record the deployment of the contract to prevent redeploys.
_deployed[deploymentAddress] = true;
}

/**
* @dev 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.
* @param salt bytes32 The nonce passed into the CREATE2 address calculation.
* @param initCode bytes The contract initialization code to be used.
* that will be passed into the CREATE2 address calculation.
* @return Address of the contract that will be created, or the null address
* if a contract has already been deployed to that address.
*/
function findCreate2Address(
bytes32 salt,
bytes calldata initCode
) external view returns (address deploymentAddress) {
// determine the address where the contract will be deployed.
deploymentAddress = address(
uint160( // downcast to match the address type.
uint256( // convert to uint to truncate upper digits.
keccak256( // compute the CREATE2 hash using 4 inputs.
abi.encodePacked( // pack all inputs to the hash together.
hex"ff", // start with 0xff to distinguish from RLP.
address(this), // this contract will be the caller.
salt, // pass in the supplied salt value.
keccak256( // pass in the hash of initialization code.
abi.encodePacked(
initCode
)
)
)
)
)
)
);

// return null address to signify failure if contract has been deployed.
if (_deployed[deploymentAddress]) {
return address(0);
}
}

/**
* @dev 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.
* @param salt bytes32 The nonce passed into the CREATE2 address calculation.
* @param initCodeHash bytes32 The keccak256 hash of the initialization code
* that will be passed into the CREATE2 address calculation.
* @return Address of the contract that will be created, or the null address
* if a contract has already been deployed to that address.
*/
function findCreate2AddressViaHash(
bytes32 salt,
bytes32 initCodeHash
) external view returns (address deploymentAddress) {
// determine the address where the contract will be deployed.
deploymentAddress = address(
uint160( // downcast to match the address type.
uint256( // convert to uint to truncate upper digits.
keccak256( // compute the CREATE2 hash using 4 inputs.
abi.encodePacked( // pack all inputs to the hash together.
hex"ff", // start with 0xff to distinguish from RLP.
address(this), // this contract will be the caller.
salt, // pass in the supplied salt value.
initCodeHash // pass in the hash of initialization code.
)
)
)
)
);

// return null address to signify failure if contract has been deployed.
if (_deployed[deploymentAddress]) {
return address(0);
}
}

/**
* @dev Determine if a contract has already been deployed by the factory to a
* given address.
* @param deploymentAddress address The contract address to check.
* @return True if the contract has been deployed, false otherwise.
*/
function hasBeenDeployed(
address deploymentAddress
) external view returns (bool) {
// determine if a contract has been deployed to the provided address.
return _deployed[deploymentAddress];
}

/**
* @dev Modifier to ensure that the first 20 bytes of a submitted salt match
* those of the calling account. This provides protection against the salt
* being stolen by frontrunners or other attackers. The protection can also be
* bypassed if desired by setting each of the first 20 bytes to zero.
* @param salt bytes32 The salt value to check against the calling address.
*/
modifier containsCaller(bytes32 salt) {
// prevent contract submissions from being stolen from tx.pool by requiring
// that the first 20 bytes of the submitted salt match msg.sender.
require(
(address(bytes20(salt)) == msg.sender) ||
(bytes20(salt) == bytes20(0)),
"Invalid salt - first 20 bytes of the salt must match calling address."
);
_;
}
}
Loading

0 comments on commit c3fef15

Please sign in to comment.