Upgradable experiments

This commit is contained in:
bumi 2018-03-11 23:12:29 +01:00
commit 9efe30afda
16 changed files with 321 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
build

9
README.mdown Normal file
View File

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

23
contracts/Migrations.sol Normal file
View File

@ -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);
}
}

16
contracts/Token1.sol Normal file
View File

@ -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;
}
}

11
contracts/Token2.sol Normal file
View File

@ -0,0 +1,11 @@
pragma solidity ^0.4.4;
import './Token1.sol';
contract Token2 is Token1 {
function mint() public {
value += 20;
}
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -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) }
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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));
}
}

View File

@ -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]);
});
});
});
};

View File

@ -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);
});
})
};

View File

@ -0,0 +1,5 @@
var Migrations = artifacts.require("./Migrations.sol");
module.exports = function(deployer) {
deployer.deploy(Migrations);
};

10
truffle.js Normal file
View File

@ -0,0 +1,10 @@
module.exports = {
// See <http://truffleframework.com/docs/advanced/configuration>
networks: {
development: {
host: "127.0.0.1",
port: 7545,
network_id: "*" // Match any network id
}
}
};