Я пытаюсь применить архитектуру обновляемых контрактов 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 ] }
Кажется, я нашел ответ.
Вы не можете подключить один диспетчер к другому, так как они используют одно и то же хранилище. Это означает, что нет двух разных целей, когда мы вызываем методы внешнего диспетчера ( d2
в тесте).
Вместо этого я вижу решение, как описано в этой статье https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd