Solidity
中繼合約鏈
我正在嘗試將 Arachnid 的可升級契約架構(來源是https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f)應用於以下問題。
想像一下,有許多合約需要升級。他們應該繼承 Dispatcher 契約。當需要“上傳”更改時,每個合約都應呼叫“替換”方法。如果有許多契約可能很困難且無法控制。我要問的是如何建構這樣的方案:所有契約都針對可以替換其自己的“工作”目標的主調度程序。
下面是我的實驗。我確定我遺漏了一些小細節。
Arachnid 的契約是
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
在測試中)上的方法時,沒有 2 個不同的目標。相反,我看到了本文中描述的解決方案https://blog.zeppelin.solutions/proxy-libraries-in-solidity-79fbe4b970fd