Relaisvertragskette

Ich versuche, die aktualisierbare Vertragsarchitektur von Arachnid (die Quelle ist https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f ) auf das folgende Problem anzuwenden.

Stellen Sie sich vor, es gibt zahlreiche Verträge, die erweiterbar sein müssen. Sie sollten den Dispatcher-Vertrag erben. Wenn Änderungen „hochgeladen“ werden müssen, sollte jeder Vertrag die Methode „Ersetzen“ aufrufen. Bei zahlreichen Verträgen kann das schwierig und unkontrollierbar sein. Was ich frage, ist, wie man ein solches Schema aufbauen kann: Alle Verträge sind auf den Hauptverteiler ausgerichtet, der sein eigenes „funktionierendes“ Ziel ersetzen kann.

Unten sind meine Experimente. Ich bin mir sicher, dass ich ein kleines Detail übersehe.

Arachnids Verträge sind

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

Ich habe einen Kern gegabelt, um meine Experimente zu zeigen ( 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;
    }
}

Das Problem ist, dass ich, wenn ich einen Dispatcher mit einem anderen verbinde, das Ergebnis des Funktionsaufrufs id2.getUint.call()etwas Müll ist.

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

Folgendes sehe ich in console.log

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

Antworten (1)

Anscheinend habe ich eine Antwort gefunden.

Sie können einen Dispatcher nicht mit einem anderen verbinden, da sie sich denselben Speicher teilen. Das bedeutet, dass es keine zwei verschiedenen Ziele gibt, wenn wir Methoden für den äußeren Dispatcher aufrufen ( d2im Test).

Stattdessen sehe ich eine Lösung wie in diesem Artikel https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd beschrieben