在區塊鏈上開發可更新的智能合約

因爲區塊鏈不可篡改的特性,智能合約一旦部署在區塊鏈上,其執行的邏輯就沒法再更改。長期來看,這個重要的特性反而限制了智能合約的彈性和發展。web

接下來要介紹如何設計及部署合約才能讓合約在須要時能夠更新。但這裏的更新意思不是修改已經部署的合約,而是部署新的合約、新的執行邏輯但同時能繼續利用已經存在的資料。編程

首先要知道的是Ethereum Virtual Machine(EVM)要知道如何執行合約的那個函數。合約最後都會被編譯成字節碼,而你發起一個transaction要執行合約裏的某個函數時,交易裏的數據一樣也是字節碼,而不是人看得懂的函數名稱。 以一個簡單的合約爲例:windows

 

contract Multiply {
    function multiply(int x, int y) constant returns(int) {
        return x*y; 
    }
}

編譯完的二進制碼:app

 

6060604052341561000c57fe5b5b60ae8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c4308a814603a575bfe5b3415604157fe5b605e60048080359060200190919080359060200190919050506074565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820c40f61d36a3a1b7064b58c57c89d5c3d7c73b9116230f9948806b11836d2960c0029

若是你要執行multiply函數,算出8*7等於多少,你的transaction裏的數據是 0x3c4308a800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007 
分紅三部分: 第一個是四個字節的3c4308a8,第二和第三個分別是32個字節長的參數,8和7。函數

3c4308a8是multiply函數的signature(簽名),是取函數名稱和參數類型使用sha3取前四個byte而獲得(不包含0x):區塊鏈

 

sha3("multiply(int256,int256)"));
//0x3c4308a8851ef99b4bfa5ffd64b68e5f2b4307725b25ad0d14040bdb81e3bafc sha3("multiply(int256,int256)")).substr(2,8);
//3c4308a8

EVM就是靠函數的signature來知道該執行那個函數的。在合約編譯完的字節碼裏查詢也能找到這個signature。ui

接下來要介紹Solidity裏的三種調用方式:call、callcode和delegatecall。spa

  • call:通常的調用都是這種方式,執行背景跳到下一個函數的環境(這裏的環境是指msg的值和合約的Storage)。若是被調用的是不一樣合約的函數那麼變換成被調用的合約的環境,且msg.sender編程調用者。
  • callcode:和call相同,只是將被調用函數搬到調用者的環境裏執行。
    假設A合約的x函數用callcode方式調用B合約的y函數,就會在A合約裏執行y函數,使用A的參數,因此若是y函數裏修改某個參數的值且這個參數的名稱恰好和A的某個參數名稱一致,則A的該參數就會被修改。就把它想像成A多了一個y函數並執行。
  • delegatecall:和callcode相同,都是把被調用的函數搬到調用者的環境裏執行,只是在msg.sender的值上有區別。
    來看一個例子:加入A合約用delegatecall的方式調用B合約的函數,B合約的函數接下用callcode或call的方式調用C合約的函數,那麼函數裏看到的msg.sender會是B;但若是B改用delegatecall的方式調用C合約的函數的話,那麼函數裏看到的msg.sender會是A。就把它想像成把msg相關的值保持不變傳遞下去就ok了。

接下來實際來看一下delegatecall的效果:設計

contract Plus {
    int z;
    function plus(int x, int y) {
        z = x+y;
    }
}
contract Multiply {
    int public z;
    function multiply(int x, int y) {
        z = x*y;
    }
    function delegateToPlus(address _plus, int x, int y) {
        _plus.delegatecall( bytes4(sha3("plus(int256,int256)")) ,x ,
        y);
    }
}

部署並按順序執行Multiply的multiply和delegateToPlus並觀察z值的變化:3d

能夠看到執行delegatecall以後z的值變成是8+7。 因此若是要讓咱們將來能夠改變執行邏輯的話怎麼寫代碼呢?

contract Plus {
    int z;
    function plus(int x, int y) { //sig:"0xccf65503"
        z = x+y;
    }
}
contract Multiply {
    int z;
    function multiply(int x, int y) { //sig:"0x3c4308a8"
        z = x*y;
    }
}
contract Main {
    int public z;
    function delegateCall(address _dest, bytes4 sig, int x, int y) {
        _dest.delegatecall(sig, x , y);
    }
}

咱們將合約的地址和函數的signature當作參數傳遞給delegateCall去執行,假設本來是用Plus合約的執行路基,如今咱們更新成Multiply合約:

0x4429 是Plus合約的地址, 0xe905 是Multiply合約的地址。
咱們之後只要給它改變後的函數signature和合約地址就可使用新的執行邏輯了!

但若是合約不是隻給一我的使用的話,應當在更新合約的時候全部參與的人都必需要更新新合約的位置。這時候能夠用一個合約來幫助咱們導到新的合約位置,就像路由器似的,咱們統一發送(仍是以delegatecall的形式)到路由合約,再由路由合約幫咱們導到正確的位置,將來更新合約就只須要更新路由合約的資料便可。

contract Upgrade {
    mapping(bytes4=>uint32) returnSizes;
    int z;
    
    function initialize() {
        returnSizes[bytes4(sha3("get()"))] = 32;
    }
    
    function plus(int _x, int _y) {
        z = _x + _y;
    }
    function get() returns(int) {
        return z;
    }
}
contract Dispatcher {
    mapping(bytes4=>uint32) returnSizes;
    int z;
    address upgradeContract;
    address public dispatcherContract;
    function replace(address newUpgradeContract) {
        upgradeContract = newUpgradeContract;
        upgradeContract.delegatecall(bytes4(sha3("initialize()")));
    }
    function() {
        bytes4 sig;
        assembly { sig := calldataload(0) }
        var len = returnSizes[sig];
        var target = upgradeContract;
        
        assembly {
            calldatacopy(mload(0x40), 0x0, calldatasize)
            delegatecall(sub(gas, 10000), target, mload(0x40),
                         calldatasize, mload(0x40), len)
            return(mload(0x40), len)
        }
    }
}
contract Main {
    mapping(bytes4=>uint32) public returnSizes;
    int public z;
    address public upgradeContract;
    address public dispatcherContract;
    
    function deployDispatcher() {
        dispatcherContract = new Dispatcher();
    }
    
    function updateUpgrade(address newUpgradeContract) {
        dispatcherContract.delegatecall(
            bytes4( sha3("replace(address)")), newUpgradeContract
        );
    }
    
    function delegateCall(bytes4 _sig, int _x, int _y) {
        dispatcherContract.delegatecall(_sig, _x, _y);
    }
    
    function get() constant returns(int output){
        dispatcherContract.delegatecall(bytes4( sha3("get()")));
        assembly {
            output := mload(0x60)
        }
    }
}

執行順序:
1. 執行Main.deployDispatcher() 部署路由合約
2. 部署upgrade合約並將其address當作Main.updateUpgrade()的參數傳入用來更新upgrade合約的地址資料。
3. 執行Main.delegateCall(),參數是plus(int256,int256)的signature和任意兩個值。
4. 執行Main.get(),由delegatecall去調用upgrade合約的get函數,回傳相加完的z值。由於是delegatecall,因此這個z值實際上是Main合約本身的,upgrade合約的z值是零。

若是delegatecall調用的函數有返回值的話,必需要用assembly來手動得到返回值,由於delegatecall和call同樣,只會回傳true of false來表明執行是否成功。Dispatcher在調用是一樣也是用assembly code。
但由於是用assembly手動得到返回值,所以前提是返回值的長度必須是固定且已知的,因此當咱們在步驟2更新upgrade合約時,Dispatcher合約同時去調用upgrade合約的initialize()函數,upgrade合約在initialize函數裏將它全部會有返回值的函數的返回值大小寫入returnSizes中,以後若是調用具備返回值的函數時,Dispatcher就知道返回值的大小了。

這個還有一個重點是參數定義的順序
由於合約執行要用參數值的時候,它會到對應的Storage位置去找。因此若是你的合約參數定義像這樣子
upgrade:
int x
int y
 — — — — 
Dispathcer:
int x
int y
 — — — — 
Main:
int x
int abc
int y
當upgrade合約的函數須要用到x和y的值的時候,它會找不到y,由於Storage是Main的。

 

分享兩個教程和一些免費資料給讀者:

一個適合區塊鏈新手的以太坊DApp開發教程:

http://xc.hubwiz.com/course/5a952991adb3847553d205d1

一個用區塊鏈、星際文件系統(IPFS)、Node.js和MongoDB來構建電商平臺:

http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6

收集整理了一些免費區塊鏈、以太坊技術開發相關的文件,有須要的能夠下載,文件連接:

1. web3.js API官方文檔中文版:https://pan.baidu.com/s/1hOV9hEzi7hFxJCL4LTvC6g
2. 以太坊官方文檔中文版     :https://pan.baidu.com/s/1ktODJKLMBmkOsi8MPrpIJA
3. 以太坊白皮書中文版       :https://pan.baidu.com/s/1bzAFnzJ35hlQxJ2J4Oj-Ow
4. Solidity的官方文檔中文版 :https://pan.baidu.com/s/18yp9XjEqAHpiFm2ZSCygHw
5. Truffle的官方文檔中文版  :https://pan.baidu.com/s/1y6SVd7lSLUHK21YF5FzIUQ
6. C#區塊鏈編程指南         :https://pan.baidu.com/s/1sJPLqp1eQqkG7jmxqwn3EA
7. 區塊鏈技術指南          :https://pan.baidu.com/s/13cJxAa80I6iMCczA04CZhg
8. 精通比特幣中文版        :https://pan.baidu.com/s/1lz6te3wcQuNJm28rFvBfxg
9. Node.js區塊鏈開發        :https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgyg
10. geth使用指南文檔中文版   :https://pan.baidu.com/s/1M0WxhmumF_fRqzt_cegnag
11. 以太坊DApp開發環境搭建-Ubuntu   : https://pan.baidu.com/s/10qL4q-uKooMehv9X2R1qSA
12. 以太坊DApp開發環境搭建-windows  :https://pan.baidu.com/s/1cyYkhIJIFuI2oyxM9Ut0eA
13. 以太坊DApp開發私鏈搭建-Ubuntu   : https://pan.baidu.com/s/1aBOFZT2bCjD2o0EILBWs-g
14. 以太坊DApp開發私鏈搭建-windows  :https://pan.baidu.com/s/10Y6F1cqUltZNN99aJv9kAA

相關文章
相關標籤/搜索