0

I am trying to aplpy Arachnid's upgradable contracts architecture (the source is https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f) to the following problem.

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 contracts 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.

Below are my experiments. I am sure I am missing some small detail.

Arachnid's contracts are

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)
        }
    }
}

I've forked a gist to show my experiments (https://gist.github.com/olekon/27710c731c58fd0e0bd2503e02f4e144).

/* Example contracts storage scheme */
contract ExampleStorage {
    uint public _value;
    uint public _value2;
}

/* Dispatcher for Example contracts */
contract ExampleDispatcher is ExampleStorage, Dispatcher {    

    function ExampleDispatcher(address target) 
        Dispatcher(target) {
    }

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
        _sizes[bytes4(sha3("getValues()"))] = 32 + 32;
    }
}

/* Example contracts interface */
contract IExample {
    function getUint() returns (uint);
    function getValues() returns (uint256 v1, uint256 v2);
    function setUint(uint value);
}

/* Base version of Example class */
contract ExampleV1 is ExampleStorage, IExample, Upgradeable {

    function ExampleV1() {}

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
        _sizes[bytes4(sha3("getValues()"))] = 32 + 32;
    }

    function getUint() returns (uint) {
        return _value;
    }

    function getValues() returns (uint256 v1, uint256 v2) {
        v1 = _value;
        v2 = 2;
    }

    function setUint(uint value) {
        _value = value;
    }
}

/* The 'upgraded' version of ExampleV1 which modifies getUint to return _value+10  */
contract ExampleV2 is ExampleStorage, IExample, Upgradeable {    

    function ExampleV2() {}

    function initialize() {
        _sizes[bytes4(sha3("getUint()"))] = 32;
        _sizes[bytes4(sha3("getValues()"))] = 32 + 32;
        _sizes[bytes4(sha3("newVar()"))] = 32;
    }

    function getUint() returns (uint) {
        return _value + 10;
    }

    function getValues() returns (uint256 v1, uint256 v2) {
        v1 = 100;
        v2 = _value;
    }

    function setUint(uint value) {
        _value = value;
    }
}

The problem is I when I connect one Dispatcher to another, the result of function call id2.getUint.call() is some garbage.

var Dispatcher = artifacts.require("ExampleDispatcher");
var ExampleV1 = artifacts.require("ExampleV1"); 
var ExampleV2 = artifacts.require("ExampleV2");
var IExample = artifacts.require("IExample");
contract("Dispatcher chain", function(accounts) {
    it("Connect dispatcher to dispatcher", async function() {
        var contract1 = await ExampleV1.new();
        var d1 = await Dispatcher.new(contract1.address);
        var id1 = IExample.at(d1.address);
        var d2 = await Dispatcher.new(d1.address);
        var id2 = IExample.at(d2.address);
        console.log(await id2.getUint.call());
    })
})

Here is what I see in console.log

{ [String: '4.248995434529609434198700245774641872687908509570084385311853389717438464e+72']
  s: 1,
  e: 72,
  c:
   [ 424,
     89954345296094,
     34198700245774,
     64187268790850,
     95700843853118,
     53389717438464 ] }
0

Seems I found an answer.

You can't connect one dispatcher to another one as they share the same storage. That means there are no 2 different targets when we call methods on outer dispatcher (d2 in test).

Instead, I see solution as described in this article https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.