Skip to content

Commit

Permalink
Initial Proxy implementation forked from github.com/zeppelinos/labs/t…
Browse files Browse the repository at this point in the history
…ree/master/upgradeability_using_inherited_storage
  • Loading branch information
krebernisak committed Apr 24, 2018
1 parent 03da07b commit ce60010
Show file tree
Hide file tree
Showing 9 changed files with 279 additions and 3 deletions.
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
# r8-app
🌱Ethereum upgradeable application using a DelegateProxy with RBAC governance

This is a simplified version of the system being used by the
[AragonOS](https://github.com/aragon/aragonOS) and [ZepplinOS](https://github.com/zeppelinos/core) systems.

## Getting started

You will need Truffle to work with EthPM:
Expand All @@ -14,12 +17,10 @@ To test this project locally, clone the repo and run:

```bash
# install dependencies
npm install
truffle install zeppelin

# run tests
testrpc&
npm test
truffle test
```

To use the code as an EthPM package:
Expand All @@ -28,3 +29,9 @@ To use the code as an EthPM package:
# install dependencies
truffle install r8-app
```

## The upgrade mechanism

The pattern used is `Upgradeability using Inherited Storage` as described in [this repo](https://github.com/zeppelinos/labs/tree/master/upgradeability_using_inherited_storage) by [Zeppelin](https://zeppelinos.org/).

Understanding the [DELEGATECALL](https://solidity.readthedocs.io/en/develop/introduction-to-smart-contracts.html#delegatecall-callcode-and-libraries) Ethereum opcode is key to understanding the upgradeable proxy pattern.
34 changes: 34 additions & 0 deletions contracts/IRegistry.sol
Original file line number Diff line number Diff line change
@@ -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);
}
35 changes: 35 additions & 0 deletions contracts/Proxy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
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));

assembly {
let ptr := mload(0x40)
calldatacopy(ptr, 0, calldatasize)
let result := delegatecall(gas, _impl, ptr, calldatasize, 0, 0)
let size := returndatasize
returndatacopy(ptr, 0, size)

switch result
case 0 { revert(ptr, size) }
default { return(ptr, size) }
}
}
}
46 changes: 46 additions & 0 deletions contracts/Registry.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 29 additions & 0 deletions contracts/UpgradeabilityProxy.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 23 additions & 0 deletions contracts/UpgradeabilityStorage.sol
Original file line number Diff line number Diff line change
@@ -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 changes: 19 additions & 0 deletions contracts/Upgradeable.sol
Original file line number Diff line number Diff line change
@@ -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));
}
}
47 changes: 47 additions & 0 deletions contracts/test/Token.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
pragma solidity ^0.4.18;

import '../Upgradeable.sol';

contract TokenV1_0 is Upgradeable {
mapping (address => uint) balances;

event Transfer(address indexed from, address indexed to, uint256 _value);

function initialize(address sender) public payable {
super.initialize(sender);
mint(sender, 10000);
}

function balanceOf(address addr) public view returns (uint) {
return balances[addr];
}

function transfer(address to, uint256 value) public {
require(balances[msg.sender] >= value);
balances[msg.sender] -= value;
balances[to] += value;
Transfer(msg.sender, to, value);
}

function mint(address to, uint256 value) public {
balances[to] += value;
Transfer(0x0, to, value);
}

}

contract TokenV1_1 is TokenV1_0 {
mapping (address => mapping (address => uint)) allowances;

function transferFrom(address from, address to, uint256 value) public {
require(allowances[from][msg.sender] >= value);
allowances[from][msg.sender] -= value;
balances[from] -= value;
balances[to] += value;
Transfer(from, to, value);
}

function approve(address spender, uint256 value) public {
allowances[msg.sender][spender] = value;
}
}
36 changes: 36 additions & 0 deletions test/Upgradeable.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
const TokenV1_0 = artifacts.require('TokenV1_0')
const TokenV1_1 = artifacts.require('TokenV1_1')

const Registry = artifacts.require('Registry')
const Proxy = artifacts.require('UpgradeabilityProxy')

contract('Upgradeable', function ([sender, receiver]) {

it('should work', async function () {
const impl_v1_0 = await TokenV1_0.new()
const impl_v1_1 = await TokenV1_1.new()

const registry = await Registry.new()
await registry.addVersion("1.0", impl_v1_0.address)
await registry.addVersion("1.1", impl_v1_1.address)

const {logs} = await registry.createProxy("1.0")

const {proxy} = logs.find(l => l.event === 'ProxyCreated').args

await TokenV1_0.at(proxy).mint(sender, 100)

await Proxy.at(proxy).upgradeTo("1.1")

await TokenV1_1.at(proxy).mint(sender, 100)

const transferTx = await TokenV1_1.at(proxy).transfer(receiver, 10, { from: sender })

console.log("Transfer TX gas cost using Inherited Storage Proxy", transferTx.receipt.gasUsed);

const balance = await TokenV1_1.at(proxy).balanceOf(sender)
assert(balance.eq(10190))

})

})

0 comments on commit ce60010

Please sign in to comment.