Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
/**
* Base contract that all upgradeable contracts should use.
*
* Contracts implementing this interface are all called using delegatecall from
* a dispatcher. As a result, the _sizes and _dest variables are shared with the
* dispatcher contract, which allows the called contract to update these at will.
*
* _sizes is a map of function signatures to return value sizes. Due to EVM
* limitations, these need to be populated by the target contract, so the
* dispatcher knows how many bytes of data to return from called functions.
* Unfortunately, this makes variable-length return values impossible.
*
* _dest is the address of the contract currently implementing all the
* functionality of the composite contract. Contracts should update this by
* calling the internal function `replace`, which updates _dest and calls
* `initialize()` on the new contract.
*
* When upgrading a contract, restrictions on permissible changes to the set of
* storage variables must be observed. New variables may be added, but existing
* ones may not be deleted or replaced. Changing variable names is acceptable.
* Structs in arrays may not be modified, but structs in maps can be, following
* the same rules described above.
*/
contract Upgradeable {
mapping(bytes4=>uint32) _sizes;
address _dest;
/**
* This function is called using delegatecall from the dispatcher when the
* target contract is first initialized. It should use this opportunity to
* insert any return data sizes in _sizes, and perform any other upgrades
* necessary to change over from the old contract implementation (if any).
*
* Implementers of this function should either perform strictly harmless,
* idempotent operations like setting return sizes, or use some form of
* access control, to prevent outside callers.
*/
function initialize();
/**
* Performs a handover to a new implementing contract.
*/
function replace(address target) internal {
_dest = target;
target.delegatecall(bytes4(sha3("initialize()")));
}
}
/**
* The dispatcher is a minimal 'shim' that dispatches calls to a targeted
* contract. Calls are made using 'delegatecall', meaning all storage and value
* is kept on the dispatcher. As a result, when the target is updated, the new
* contract inherits all the stored data and value from the old contract.
*/
contract Dispatcher is Upgradeable {
function Dispatcher(address target) {
replace(target);
}
function initialize() {
// Should only be called by on target contracts, not on the dispatcher
throw;
}
function() {
bytes4 sig;
assembly { sig := calldataload(0) }
var len = _sizes[sig];
var target = _dest;
assembly {
// return _dest.delegatecall(msg.data)
calldatacopy(0x0, 0x0, calldatasize)
delegatecall(sub(gas, 10000), target, 0x0, calldatasize, 0, len)
return(0, len)
}
}
}
contract Example is Upgradeable {
uint _value;
function initialize() {
_sizes[bytes4(sha3("getUint()"))] = 32;
}
function getUint() returns (uint) {
return _value;
}
function setUint(uint value) {
_value = value;
}
}

An interesting technique that I just though of to deal with the storage variable issue is that the contracts that this forwards to could write sto storage starting at sha3(this) for that particular contract. In a delegatecall is this maintained?

I just tested it, it's not. So maybe the contract should accept a storage pointer where it is allowed to write to storage....

@Arachnid : Nick, that contract is a great practice during development
stages of any platform, however to give strong security to the users value
we should add to that contract something to make updateable functionality
obsolete.

something like:

function cancelUpdates(){

      if (!msg.sender == owner) availableUpdate = false;

}

function replace(address target) internal {
        
        if (!availableUpdates) throw; 

        _dest = target;
        target.delegatecall(bytes4(sha3("initialize()")));
 }

alesl commented Apr 5, 2017

What is the reason 10000 gas is deducted when making delegatecall, since 40 (or 700 after EIP150) should suffice?

@alesl

Actually if you look at the latest revision (specification 2) of EIP150 you'll see that the gas cost of delegatecall has been increased to 4000 for every block after Metropolis. So having a contract only subtract 700 would fail as soon as Metropolis is released. This is just a guess, but I think to answer your original question the author is envisioning future gas cost increases for delegatecall and trying to give himself some cushion room.

olekon commented Oct 12, 2017

I just wanted to make sure I understood the concept and usage scenarios correctly. In order for Dispatcher to work properly it should have the same storage scheme as Example contracts, right? I've made a fork to this gist and extended it with modified Example contracts and test file with supposed usage scenario here https://gist.github.com/olekon/27710c731c58fd0e0bd2503e02f4e144
Could you please look at it and correct me if anything is wrong.

olekon commented Oct 13, 2017

@Arachnid, Is it possible to chain dispatchers?
Imagine there are numerous contracts that need to be upgradeable. They should inherit Dispatcher contract. When changes are needed to be 'uploaded', every contract should call 'replace' method. In case of numerous contract that can be difficult and uncontrollable.
What I ask is how can one build such scheme: all the contracts are targeted to the main dispatcher which can replace its own 'working' target.

I tried to play with Dispatcher classes (https://gist.github.com/olekon/27710c731c58fd0e0bd2503e02f4e144) but getUint in the last test returns garbage

In this gist I didn't see any access control logic, or did I miss anything? The question is, can anyone upgrade the target contract?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment