Цепочка контрактов эстафеты

Я пытаюсь применить архитектуру обновляемых контрактов Arachnid (источник https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f ) к следующей проблеме.

Представьте, что существует множество контрактов, которые необходимо обновить. Они должны наследовать контракт Dispatcher. Когда изменения необходимо «загрузить», каждый контракт должен вызывать метод «replace». В случае многочисленных контрактов это может быть сложно и неконтролируемо. Я спрашиваю, как можно построить такую ​​схему: все контракты нацелены на главного диспетчера, который может заменить свою «рабочую» цель.

Ниже мои эксперименты. Я уверен, что упускаю какую-то маленькую деталь.

Контракты Арахнида

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

Я разветвил суть, чтобы показать свои эксперименты ( 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;
    }
}

Проблема в том, что когда я подключаю один Dispatcher к другому, результатом вызова функции id2.getUint.call()является какая-то фигня.

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

Вот что я вижу в console.log

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

Ответы (1)

Кажется, я нашел ответ.

Вы не можете подключить один диспетчер к другому, так как они используют одно и то же хранилище. Это означает, что нет двух разных целей, когда мы вызываем методы внешнего диспетчера ( d2в тесте).

Вместо этого я вижу решение, как описано в этой статье https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd