Upgradable experiments
This commit is contained in:
commit
9efe30afda
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
build
|
9
README.mdown
Normal file
9
README.mdown
Normal 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
23
contracts/Migrations.sol
Normal 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
16
contracts/Token1.sol
Normal 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
11
contracts/Token2.sol
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
pragma solidity ^0.4.4;
|
||||||
|
|
||||||
|
import './Token1.sol';
|
||||||
|
|
||||||
|
contract Token2 is Token1 {
|
||||||
|
|
||||||
|
function mint() public {
|
||||||
|
value += 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
34
contracts/upgradeable/IRegistry.sol
Normal file
34
contracts/upgradeable/IRegistry.sol
Normal 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);
|
||||||
|
}
|
23
contracts/upgradeable/Migrations.sol
Normal file
23
contracts/upgradeable/Migrations.sol
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
36
contracts/upgradeable/Proxy.sol
Normal file
36
contracts/upgradeable/Proxy.sol
Normal 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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
contracts/upgradeable/Registry.sol
Normal file
46
contracts/upgradeable/Registry.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
29
contracts/upgradeable/UpgradeabilityProxy.sol
Normal file
29
contracts/upgradeable/UpgradeabilityProxy.sol
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
contracts/upgradeable/UpgradeabilityStorage.sol
Normal file
23
contracts/upgradeable/UpgradeabilityStorage.sol
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
19
contracts/upgradeable/Upgradeable.sol
Normal file
19
contracts/upgradeable/Upgradeable.sol
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
22
migrations/1520798600_setup.js
Normal file
22
migrations/1520798600_setup.js
Normal 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]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
|
14
migrations/1520802793_upgrade.js
Normal file
14
migrations/1520802793_upgrade.js
Normal 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);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
};
|
5
migrations/1_initial_migration.js
Normal file
5
migrations/1_initial_migration.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var Migrations = artifacts.require("./Migrations.sol");
|
||||||
|
|
||||||
|
module.exports = function(deployer) {
|
||||||
|
deployer.deploy(Migrations);
|
||||||
|
};
|
10
truffle.js
Normal file
10
truffle.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
Loading…
x
Reference in New Issue
Block a user