├── .gitignore ├── migrations ├── 3_TokenV1_0.js ├── 2_registry.js └── 1_initial_migration.js ├── README.md ├── contracts ├── Migrations.sol ├── Upgradeable.sol ├── UpgradeabilityStorage.sol ├── UpgradeabilityProxy.sol ├── Proxy.sol ├── IRegistry.sol ├── test │ └── Token.sol └── Registry.sol ├── truffle-config.js ├── package.json └── truffle.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | build 3 | node_modules 4 | -------------------------------------------------------------------------------- /migrations/3_TokenV1_0.js: -------------------------------------------------------------------------------- 1 | var TokenV1_0 = artifacts.require("TokenV1_0"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(TokenV1_0); 5 | }; -------------------------------------------------------------------------------- /migrations/2_registry.js: -------------------------------------------------------------------------------- 1 | var Registry = artifacts.require("./Registry.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Registry); 5 | }; 6 | -------------------------------------------------------------------------------- /migrations/1_initial_migration.js: -------------------------------------------------------------------------------- 1 | var Migrations = artifacts.require("./Migrations.sol"); 2 | 3 | module.exports = function(deployer) { 4 | deployer.deploy(Migrations); 5 | }; 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 如何初始化 2 | 3 | 1. 部署`Registry`合约 4 | 2. 部署逻辑合约的初始版本(V1),并确保它继承了`Upgradeable`合约 5 | 3. 向`Registry`合约中注册这个最初版本(V1)的地址 6 | 4. 要求`Registry`合约创建一个`UpgradeabilityProxy`实例 7 | 5. 调用你的`UpgrageabilityProxy`实例来升级到你最初版本(V1) 8 | 9 | 10 | ### 如何升级 11 | 12 | 1. 部署一个继承了你最初版本合约的新版本(V2),V2必须继承V1 13 | 2. 向`Registry`中注册合约的新版本V2 14 | 3. 调用你的`UpgradeabilityProxy`实例来升级到最新注册的版本 15 | 16 | 17 | ### 如何调用 18 | 对proxy套用当前版本的逻辑合约的ABI,正常调用方法 19 | 20 | ### 如何转移proxy合约所有权 21 | 调用`Registry`中的`transferProxyOwnership`方法进行所有权转移; 22 | 23 | -------------------------------------------------------------------------------- /contracts/Migrations.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | contract Migrations { 4 | address public owner; 5 | uint public last_completed_migration; 6 | 7 | modifier restricted() { 8 | if (msg.sender == owner) _; 9 | } 10 | 11 | constructor() public { 12 | owner = msg.sender; 13 | } 14 | 15 | function setCompleted(uint completed) public restricted { 16 | last_completed_migration = completed; 17 | } 18 | 19 | function upgrade(address new_address) public restricted { 20 | Migrations upgraded = Migrations(new_address); 21 | upgraded.setCompleted(last_completed_migration); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /truffle-config.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | }; 19 | -------------------------------------------------------------------------------- /contracts/Upgradeable.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import './UpgradeabilityStorage.sol'; 4 | 5 | /** 6 | * @title Upgradeable 7 | * @dev This contract holds all the minimum required functionality for a behavior to be upgradeable. 8 | * This means, required state variables for owned upgradeability purpose and simple initialization validation. 9 | */ 10 | contract Upgradeable is UpgradeabilityStorage { 11 | /** 12 | * @dev Validates the caller is the versions registry. 13 | * THIS FUNCTION SHOULD BE OVERRIDDEN CALLING SUPER 14 | * @param sender representing the address deploying the initial behavior of the contract 15 | */ 16 | function initialize(address sender) public payable { 17 | require(msg.sender == address(registry)); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /contracts/UpgradeabilityStorage.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import './IRegistry.sol'; 4 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 5 | 6 | /** 7 | * @title UpgradeabilityStorage 8 | * @dev This contract holds all the necessary state variables to support the upgrade functionality 9 | */ 10 | contract UpgradeabilityStorage is Ownable { 11 | // Versions registry 12 | IRegistry internal registry; 13 | 14 | // Address of the current implementation 15 | address internal _implementation; 16 | 17 | /** 18 | * @dev Tells the address of the current implementation 19 | * @return address of the current implementation 20 | */ 21 | function implementation() public view returns (address) { 22 | return _implementation; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Proxy", 3 | "version": "1.0.0", 4 | "description": "Proxy patterns for ungradeability", 5 | "main": "truffle-config.js", 6 | "directories": { 7 | "test": "test" 8 | }, 9 | "dependencies": { 10 | "openzeppelin-solidity": "^1.11.0-rc.1", 11 | "truffle-hdwallet-provider": "0.0.5", 12 | "truffle-privatekey-provider": "^0.1.0" 13 | }, 14 | "devDependencies": {}, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "git+https://github.com/hammeWang/Proxy.git" 21 | }, 22 | "keywords": [], 23 | "author": "", 24 | "license": "ISC", 25 | "bugs": { 26 | "url": "https://github.com/hammeWang/Proxy/issues" 27 | }, 28 | "homepage": "https://github.com/hammeWang/Proxy#readme" 29 | } 30 | -------------------------------------------------------------------------------- /contracts/UpgradeabilityProxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | import './Proxy.sol'; 4 | import './IRegistry.sol'; 5 | import './UpgradeabilityStorage.sol'; 6 | 7 | /** 8 | * @title UpgradeabilityProxy 9 | * @dev This contract represents a proxy where the implementation address to which it will delegate can be upgraded 10 | */ 11 | contract UpgradeabilityProxy is Proxy, UpgradeabilityStorage { 12 | 13 | /** 14 | * @dev Constructor function 15 | */ 16 | constructor (string _version) public { 17 | registry = IRegistry(msg.sender); 18 | upgradeTo(_version); 19 | } 20 | 21 | /** 22 | * @dev Upgrades the implementation to the requested version 23 | * @param _version representing the version name of the new implementation to be set 24 | */ 25 | function upgradeTo(string _version) public onlyOwner{ 26 | _implementation = registry.getVersion(_version); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /truffle.js: -------------------------------------------------------------------------------- 1 | /* 2 | * NB: since truffle-hdwallet-provider 0.0.5 you must wrap HDWallet providers in a 3 | * function when declaring them. Failure to do so will cause commands to hang. ex: 4 | * ``` 5 | * mainnet: { 6 | * provider: function() { 7 | * return new HDWalletProvider(mnemonic, 'https://mainnet.infura.io/') 8 | * }, 9 | * network_id: '1', 10 | * gas: 4500000, 11 | * gasPrice: 10000000000, 12 | * }, 13 | */ 14 | 15 | module.exports = { 16 | // See 17 | // to customize your Truffle configuration! 18 | networks: { 19 | development: { 20 | host: "localhost", 21 | port: 8545, 22 | network_id: "*" // Match any network id 23 | }, 24 | 25 | solc: { 26 | optimizer: { 27 | enabled: true, 28 | runs: 200 29 | } 30 | }, 31 | // https://truffle.readthedocs.io/en/beta/advanced/configuration/ 32 | mocha: { 33 | bail: true 34 | } 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /contracts/Proxy.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title Proxy 5 | * @dev Gives the possibility to delegate any call to a foreign implementation. 6 | */ 7 | contract Proxy { 8 | 9 | /** 10 | * @dev Tells the address of the implementation where every call will be delegated. 11 | * @return address of the implementation to which it will be delegated 12 | */ 13 | function implementation() public view returns (address); 14 | 15 | /** 16 | * @dev Fallback function allowing to perform a delegatecall to the given implementation. 17 | * This function will return whatever the implementation call returns 18 | */ 19 | function () payable public { 20 | address _impl = implementation(); 21 | require(_impl != address(0)); 22 | 23 | assembly { 24 | let ptr := mload(0x40) 25 | calldatacopy(ptr, 0, calldatasize) 26 | let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0) 27 | let size := returndatasize 28 | returndatacopy(ptr, 0, size) 29 | 30 | switch result 31 | case 0 { revert(ptr, size) } 32 | default { return(ptr, size) } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /contracts/IRegistry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.24; 2 | 3 | /** 4 | * @title IRegistry 5 | * @dev This contract represents the interface of a registry contract 6 | */ 7 | interface IRegistry { 8 | /** 9 | * @dev This event will be emitted every time a new proxy is created 10 | * @param proxy representing the address of the proxy created 11 | */ 12 | event ProxyCreated(address proxy); 13 | 14 | /** 15 | * @dev This event will be emitted every time a new implementation is registered 16 | * @param version representing the version name of the registered implementation 17 | * @param implementation representing the address of the registered implementation 18 | */ 19 | event VersionAdded(string version, address implementation); 20 | 21 | /** 22 | * @dev Registers a new version with its implementation address 23 | * @param version representing the version name of the new implementation to be registered 24 | * @param implementation representing the address of the new implementation to be registered 25 | */ 26 | function addVersion(string version, address implementation) public; 27 | 28 | /** 29 | * @dev Tells the address of the implementation for a given version 30 | * @param version to query the implementation of 31 | * @return address of the implementation registered for the given version 32 | */ 33 | function getVersion(string version) public view returns (address); 34 | } 35 | -------------------------------------------------------------------------------- /contracts/test/Token.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.18; 2 | 3 | import '../Upgradeable.sol'; 4 | 5 | contract TokenV1_0 is Upgradeable { 6 | mapping (address => uint) balances; 7 | 8 | event Transfer(address indexed from, address indexed to, uint256 _value); 9 | 10 | function initialize(address sender) public payable { 11 | super.initialize(sender); 12 | mint(sender, 10000); 13 | } 14 | 15 | function balanceOf(address addr) public view returns (uint) { 16 | return balances[addr]; 17 | } 18 | 19 | function transfer(address to, uint256 value) public { 20 | require(balances[msg.sender] >= value); 21 | balances[msg.sender] -= value; 22 | balances[to] += value; 23 | Transfer(msg.sender, to, value); 24 | } 25 | 26 | function mint(address to, uint256 value) public { 27 | balances[to] += value; 28 | Transfer(0x0, to, value); 29 | } 30 | 31 | } 32 | 33 | contract TokenV1_1 is TokenV1_0 { 34 | mapping (address => mapping (address => uint)) allowances; 35 | 36 | function transferFrom(address from, address to, uint256 value) public { 37 | require(allowances[from][msg.sender] >= value); 38 | allowances[from][msg.sender] -= value; 39 | balances[from] -= value; 40 | balances[to] += value; 41 | Transfer(from, to, value); 42 | } 43 | 44 | function approve(address spender, uint256 value) public { 45 | allowances[msg.sender][spender] = value; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /contracts/Registry.sol: -------------------------------------------------------------------------------- 1 | pragma solidity ^0.4.23; 2 | 3 | import './IRegistry.sol'; 4 | import './Upgradeable.sol'; 5 | import './UpgradeabilityProxy.sol'; 6 | import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; 7 | 8 | /** 9 | * @title Registry 10 | * @dev This contract works as a registry of versions, it holds the implementations for the registered versions. 11 | */ 12 | contract Registry is IRegistry,Ownable { 13 | // Mapping of versions to implementations of different functions 14 | mapping(string => address) internal versions; 15 | 16 | /** 17 | * @dev Registers a new version with its implementation address 18 | * @param version representing the version name of the new implementation to be registered 19 | * @param implementation representing the address of the new implementation to be registered 20 | */ 21 | function addVersion(string version, address implementation) onlyOwner public { 22 | require(versions[version] == 0x0); 23 | versions[version] = implementation; 24 | VersionAdded(version, implementation); 25 | } 26 | 27 | /** 28 | * @dev Tells the address of the implementation for a given version 29 | * @param version to query the implementation of 30 | * @return address of the implementation registered for the given version 31 | */ 32 | function getVersion(string version) public view returns (address) { 33 | return versions[version]; 34 | } 35 | 36 | /** 37 | * @dev Creates an upgradeable proxy 38 | * @param version representing the first version to be set for the proxy 39 | * @return address of the new proxy created 40 | */ 41 | function createProxy(string version) onlyOwner public payable returns (UpgradeabilityProxy) { 42 | UpgradeabilityProxy proxy = new UpgradeabilityProxy(version); 43 | Upgradeable(proxy).initialize.value(msg.value)(msg.sender); 44 | ProxyCreated(proxy); 45 | return proxy; 46 | } 47 | 48 | function transferProxyOwnership(address proxy, address newowner) onlyOwner public payable { 49 | Ownable(proxy).transferOwnership(newowner); 50 | } 51 | } 52 | --------------------------------------------------------------------------------