Cadena de contratos de relevos

Estoy tratando de aplicar la arquitectura de contratos actualizables de Arachnid (la fuente es https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f ) al siguiente problema.

Imagine que hay numerosos contratos que deben actualizarse. Deben heredar el contrato de Despachador. Cuando es necesario 'cargar' los cambios, cada contrato debe llamar al método 'reemplazar'. En caso de numerosos contratos que pueden resultar difíciles e incontrolables. Lo que pregunto es cómo se puede construir tal esquema: todos los contratos están dirigidos al despachador principal que puede reemplazar su propio objetivo 'de trabajo'.

A continuación se muestran mis experimentos. Seguro que me falta algún pequeño detalle.

Los contratos de Arachnid son

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

He bifurcado una esencia para mostrar mis experimentos ( 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;
    }
}

El problema es que cuando conecto un Dispatcher a otro, el resultado de la llamada a la función id2.getUint.call()es basura.

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

Esto es lo que veo en console.log

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

Respuestas (1)

Parece que encontré una respuesta.

No puede conectar un despachador a otro ya que comparten el mismo almacenamiento. Eso significa que no hay 2 objetivos diferentes cuando llamamos a métodos en el despachador externo ( d2en prueba).

En cambio, veo la solución como se describe en este artículo https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd