Created
February 23, 2017 11:01
-
-
Save pprados/c7841682f60f775b0474cd6a3f13c019 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pragma solidity ^0.4.8; | |
contract Versionable { | |
event VersionChanged(Versionable version); | |
Versionable public /* FIXME internal*/ currentVersion; | |
bool internal isInit; | |
address private owner; | |
/** | |
* Constructor. | |
*/ | |
function Versionable() { | |
owner = msg.sender; | |
} | |
/** | |
* Kill the old version. | |
* @param proxy The proxy to transfert some coins. | |
*/ | |
function killVersion(Versionable proxy) { | |
if (msg.sender == owner) selfdestruct(proxy); | |
} | |
function kill() { | |
selfdestruct(0); | |
} | |
} | |
// -- Technical contract | |
contract Proxy is Versionable { | |
/** | |
* Create a proxy to delegate call to the current version of contract. | |
* @param _currentVersion The first version to use. | |
*/ | |
function Proxy(Versionable _currentVersion) { | |
currentVersion=_currentVersion; | |
} | |
/** | |
* Change the current version. | |
* @param _newVersion The new version. | |
*/ | |
function changeVersion(Versionable _newVersion) { | |
currentVersion.killVersion(this); | |
currentVersion=_newVersion; | |
VersionChanged(_newVersion); | |
} | |
/** | |
* Propagate the current call to another contract. | |
* Use TARGET code with THIS storage, but also keep caller and callvalue. | |
* Invoke delegateCall(). | |
* In order for this to work with callcode, the data-members needs to be identical to the target contract. | |
* | |
* @param target The target contract. | |
* @param returnSize The maximum return size of all methods invoked. | |
*/ | |
function propagateDelegateCall(address target, int returnSize) internal { | |
assembly { | |
let brk := mload(0x40) // Special solidity slot with top memory | |
calldatacopy(brk, 0, calldatasize) // Copy data to memory at offset brk | |
let retval := delegatecall(sub(gas,150) | |
,target //address | |
,brk // memory in | |
,calldatasize // input size | |
,brk // reuse mem | |
,returnSize) // arbitrary return size | |
// 0 == it threw, by jumping to bad destination (00) | |
jumpi(0x00,iszero(retval)) // Throw (access invalid code) | |
return(brk,returnSize) // Return returnSize from memory to the caller | |
} | |
} | |
function () payable { | |
/* 32 is the maximum return size for all methods in all versions. */ | |
propagateDelegateCall(currentVersion,32); | |
} | |
} | |
/** | |
* The version 1 of the contract. | |
* The attr is initialized to 1000. | |
* The method doSomething() return attr + version = 1001 | |
*/ | |
contract ContractV1 is Versionable { | |
uint constant private version=1; | |
uint public attr; | |
/** return attr+version (1001). */ | |
function doSomething() external | |
returns(uint) { | |
return attr+version; // 1001 | |
} | |
/** Post-construction. */ | |
function init() { | |
attr=1000; | |
isInit=true; | |
} | |
} | |
/** | |
* The version 2 of the contract. | |
* To preserve all the attributs from the v1 version, this version IS a ContractV1. | |
* All methods can be rewrite, new one can be added and some attributs can be added. | |
* | |
* The newAttr is initialized to 100. | |
* The method doSomething() return attr + newAtttr + version = 1102 | |
*/ | |
contract ContractV2 is ContractV1 { | |
uint constant private version=2; | |
uint public newAttr; | |
/** return attr+newAttr+version (1102). */ | |
function doSomething() external returns(uint) { | |
return attr+newAttr+2; // 1102 | |
} | |
/** return 42. Another method in version 2. */ | |
function doOtherThing() external returns(uint) { | |
return 42; | |
} | |
/** Post-construction. */ | |
function init() { | |
newAttr=100; | |
isInit=true; | |
} | |
} | |
// ------------- | |
/** | |
* Unit test. | |
* | |
* After created an instance, | |
* - call 'init()' | |
* - call 'doSomething()' return 1001 | |
* - call 'changeToV2' | |
* - call 'doSomething()' return 1102 | |
* - call 'doOtherthing()' return 42 | |
*/ | |
contract A_UnitTest { | |
event VersionChanged(Versionable version); | |
/** A reference to a version of contract, via a proxy. */ | |
ContractV1 private aContract; // FIXME: aContract | |
/** | |
* Initialise useContract with a ContractV1 encapsulated by a proxy. | |
*/ | |
function init() { | |
// Create an instance of version 1 | |
ContractV1 v1=new ContractV1(); | |
// Encapsulate this instance in a proxy | |
Proxy proxy=new Proxy(v1); | |
// Cast the proxy to ContractV1 | |
aContract=ContractV1(proxy); | |
// Init the instance via the proxy | |
aContract.init(); | |
} | |
/** Invoke a method valide with a contract v1 or v2. */ | |
function doSomething() constant returns(uint) { | |
return aContract.doSomething(); | |
} | |
/** Invoke a method only valide with a contract v2. */ | |
function doOtherThing() constant returns(uint) { | |
return ContractV2(aContract).doOtherThing(); | |
} | |
/** Change to V2. */ | |
function changeToV2() { | |
// Create an instance of version 2 | |
ContractV2 v2=new ContractV2(); | |
// Cast the current contract to be a Proxy | |
Proxy proxy=Proxy(aContract); | |
// Change the delegate instance to v2. | |
proxy.changeVersion(v2); | |
// Init the version 2 via the proxy. | |
aContract.init(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment