commit 9efe30afda42b488b711d48d13fbbfc338043ea2 Author: bumi Date: Sun Mar 11 23:12:29 2018 +0100 Upgradable experiments diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c795b05 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build \ No newline at end of file diff --git a/README.mdown b/README.mdown new file mode 100644 index 0000000..2b745ff --- /dev/null +++ b/README.mdown @@ -0,0 +1,9 @@ + + +## Development + +For local development it is recommended to use [ganache-cli](https://github.com/trufflesuite/ganache-cli) to run a local development chain. + +We default to port 7545 for development to not get in conflict with the default Ethereum RPC port. + + $ ganache-cli -p 7545 diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol new file mode 100644 index 0000000..f170cb4 --- /dev/null +++ b/contracts/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.17; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _; + } + + function Migrations() public { + owner = msg.sender; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/contracts/Token1.sol b/contracts/Token1.sol new file mode 100644 index 0000000..6759f7f --- /dev/null +++ b/contracts/Token1.sol @@ -0,0 +1,16 @@ +pragma solidity ^0.4.4; + +import './upgradeable/Upgradeable.sol'; + +contract Token1 is Upgradeable { + + uint public value; + function Token() public { + value = 1; + } + + function mint() public { + value += 10; + } + +} diff --git a/contracts/Token2.sol b/contracts/Token2.sol new file mode 100644 index 0000000..a8d6490 --- /dev/null +++ b/contracts/Token2.sol @@ -0,0 +1,11 @@ +pragma solidity ^0.4.4; + +import './Token1.sol'; + +contract Token2 is Token1 { + + function mint() public { + value += 20; + } + +} diff --git a/contracts/upgradeable/IRegistry.sol b/contracts/upgradeable/IRegistry.sol new file mode 100644 index 0000000..55223e8 --- /dev/null +++ b/contracts/upgradeable/IRegistry.sol @@ -0,0 +1,34 @@ +pragma solidity ^0.4.18; + +/** + * @title IRegistry + * @dev This contract represents the interface of a registry contract + */ +interface IRegistry { + /** + * @dev This event will be emitted every time a new proxy is created + * @param proxy representing the address of the proxy created + */ + event ProxyCreated(address proxy); + + /** + * @dev This event will be emitted every time a new implementation is registered + * @param version representing the version name of the registered implementation + * @param implementation representing the address of the registered implementation + */ + event VersionAdded(string version, address implementation); + + /** + * @dev Registers a new version with its implementation address + * @param version representing the version name of the new implementation to be registered + * @param implementation representing the address of the new implementation to be registered + */ + function addVersion(string version, address implementation) public; + + /** + * @dev Tells the address of the implementation for a given version + * @param version to query the implementation of + * @return address of the implementation registered for the given version + */ + function getVersion(string version) public view returns (address); +} diff --git a/contracts/upgradeable/Migrations.sol b/contracts/upgradeable/Migrations.sol new file mode 100644 index 0000000..f170cb4 --- /dev/null +++ b/contracts/upgradeable/Migrations.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.17; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _; + } + + function Migrations() public { + owner = msg.sender; + } + + function setCompleted(uint completed) public restricted { + last_completed_migration = completed; + } + + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} diff --git a/contracts/upgradeable/Proxy.sol b/contracts/upgradeable/Proxy.sol new file mode 100644 index 0000000..194ae56 --- /dev/null +++ b/contracts/upgradeable/Proxy.sol @@ -0,0 +1,36 @@ +pragma solidity ^0.4.18; + +/** + * @title Proxy + * @dev Gives the possibility to delegate any call to a foreign implementation. + */ +contract Proxy { + + /** + * @dev Tells the address of the implementation where every call will be delegated. + * @return address of the implementation to which it will be delegated + */ + function implementation() public view returns (address); + + /** + * @dev Fallback function allowing to perform a delegatecall to the given implementation. + * This function will return whatever the implementation call returns + */ + function () payable public { + address _impl = implementation(); + require(_impl != address(0)); + bytes memory data = msg.data; + + assembly { + let result := delegatecall(gas, _impl, add(data, 0x20), mload(data), 0, 0) + let size := returndatasize + + let ptr := mload(0x40) + returndatacopy(ptr, 0, size) + + switch result + case 0 { revert(ptr, size) } + default { return(ptr, size) } + } + } +} diff --git a/contracts/upgradeable/Registry.sol b/contracts/upgradeable/Registry.sol new file mode 100644 index 0000000..f80247b --- /dev/null +++ b/contracts/upgradeable/Registry.sol @@ -0,0 +1,46 @@ +pragma solidity ^0.4.18; + +import './IRegistry.sol'; +import './Upgradeable.sol'; +import './UpgradeabilityProxy.sol'; + +/** + * @title Registry + * @dev This contract works as a registry of versions, it holds the implementations for the registered versions. + */ +contract Registry is IRegistry { + // Mapping of versions to implementations of different functions + mapping (string => address) internal versions; + + /** + * @dev Registers a new version with its implementation address + * @param version representing the version name of the new implementation to be registered + * @param implementation representing the address of the new implementation to be registered + */ + function addVersion(string version, address implementation) public { + require(versions[version] == 0x0); + versions[version] = implementation; + VersionAdded(version, implementation); + } + + /** + * @dev Tells the address of the implementation for a given version + * @param version to query the implementation of + * @return address of the implementation registered for the given version + */ + function getVersion(string version) public view returns (address) { + return versions[version]; + } + + /** + * @dev Creates an upgradeable proxy + * @param version representing the first version to be set for the proxy + * @return address of the new proxy created + */ + function createProxy(string version) public payable returns (UpgradeabilityProxy) { + UpgradeabilityProxy proxy = new UpgradeabilityProxy(version); + Upgradeable(proxy).initialize.value(msg.value)(msg.sender); + ProxyCreated(proxy); + return proxy; + } +} diff --git a/contracts/upgradeable/UpgradeabilityProxy.sol b/contracts/upgradeable/UpgradeabilityProxy.sol new file mode 100644 index 0000000..e279e79 --- /dev/null +++ b/contracts/upgradeable/UpgradeabilityProxy.sol @@ -0,0 +1,29 @@ +pragma solidity ^0.4.18; + +import './Proxy.sol'; +import './IRegistry.sol'; +import './UpgradeabilityStorage.sol'; + +/** + * @title UpgradeabilityProxy + * @dev This contract represents a proxy where the implementation address to which it will delegate can be upgraded + */ +contract UpgradeabilityProxy is Proxy, UpgradeabilityStorage { + + /** + * @dev Constructor function + */ + function UpgradeabilityProxy(string _version) public { + registry = IRegistry(msg.sender); + upgradeTo(_version); + } + + /** + * @dev Upgrades the implementation to the requested version + * @param _version representing the version name of the new implementation to be set + */ + function upgradeTo(string _version) public { + _implementation = registry.getVersion(_version); + } + +} diff --git a/contracts/upgradeable/UpgradeabilityStorage.sol b/contracts/upgradeable/UpgradeabilityStorage.sol new file mode 100644 index 0000000..08ad454 --- /dev/null +++ b/contracts/upgradeable/UpgradeabilityStorage.sol @@ -0,0 +1,23 @@ +pragma solidity ^0.4.18; + +import './IRegistry.sol'; + +/** + * @title UpgradeabilityStorage + * @dev This contract holds all the necessary state variables to support the upgrade functionality + */ +contract UpgradeabilityStorage { + // Versions registry + IRegistry internal registry; + + // Address of the current implementation + address internal _implementation; + + /** + * @dev Tells the address of the current implementation + * @return address of the current implementation + */ + function implementation() public view returns (address) { + return _implementation; + } +} diff --git a/contracts/upgradeable/Upgradeable.sol b/contracts/upgradeable/Upgradeable.sol new file mode 100644 index 0000000..2bae160 --- /dev/null +++ b/contracts/upgradeable/Upgradeable.sol @@ -0,0 +1,19 @@ +pragma solidity ^0.4.18; + +import './UpgradeabilityStorage.sol'; + +/** + * @title Upgradeable + * @dev This contract holds all the minimum required functionality for a behavior to be upgradeable. + * This means, required state variables for owned upgradeability purpose and simple initialization validation. + */ +contract Upgradeable is UpgradeabilityStorage { + /** + * @dev Validates the caller is the versions registry. + * THIS FUNCTION SHOULD BE OVERRIDDEN CALLING SUPER + * @param sender representing the address deploying the initial behavior of the contract + */ + function initialize(address sender) public payable { + require(msg.sender == address(registry)); + } +} diff --git a/migrations/1520798600_setup.js b/migrations/1520798600_setup.js new file mode 100644 index 0000000..500db01 --- /dev/null +++ b/migrations/1520798600_setup.js @@ -0,0 +1,22 @@ +var Registry = artifacts.require('./upgradeable/Registry.sol'); + +var Token = artifacts.require('./Token1.sol'); + +module.exports = function(deployer) { + deployer.deploy(Registry).then(function() { + return Registry.deployed(); + }).then(function(registry) { + return deployer.deploy(Token); + }).then(function(token) { + console.log('Registry address: ', Registry.address); + console.log('Token address: ', Token.address); + Registry.deployed().then(function(registry) { + registry.addVersion('Token_1.0', Token.address); + registry.createProxy('Token_1.0').then(function(r) { + console.log(r.logs[0]); + }); + }); + }); +}; + + diff --git a/migrations/1520802793_upgrade.js b/migrations/1520802793_upgrade.js new file mode 100644 index 0000000..92ec36c --- /dev/null +++ b/migrations/1520802793_upgrade.js @@ -0,0 +1,14 @@ +var Token = artifacts.require('./Token2.sol'); +var Registry = artifacts.require('./upgradeable/Registry'); +var UpgradeabilityProxy = artifacts.require('./upgradeable/UpgradeabilityProxy'); + +module.exports = function(deployer) { + deployer.deploy(Token).then(function(t) { + return Token.deployed(); + }).then(function(token) { + Registry.deployed().then(function(registry) { + console.log('Token address: ', Token.address); + registry.addVersion('Token_2.0', Token.address); + }); + }) +}; diff --git a/migrations/1_initial_migration.js b/migrations/1_initial_migration.js new file mode 100644 index 0000000..4d5f3f9 --- /dev/null +++ b/migrations/1_initial_migration.js @@ -0,0 +1,5 @@ +var Migrations = artifacts.require("./Migrations.sol"); + +module.exports = function(deployer) { + deployer.deploy(Migrations); +}; diff --git a/truffle.js b/truffle.js new file mode 100644 index 0000000..f591088 --- /dev/null +++ b/truffle.js @@ -0,0 +1,10 @@ +module.exports = { + // See + networks: { + development: { + host: "127.0.0.1", + port: 7545, + network_id: "*" // Match any network id + } + } +};