Skip to content

Instantly share code, notes, and snippets.

@pprados
Created February 23, 2017 11:01
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pprados/c7841682f60f775b0474cd6a3f13c019 to your computer and use it in GitHub Desktop.
Save pprados/c7841682f60f775b0474cd6a3f13c019 to your computer and use it in GitHub Desktop.
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