Solidity

中繼合約鏈

  • October 16, 2017

我正在嘗試將 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

引用自:https://ethereum.stackexchange.com/questions/28393