因爲區塊鏈不可篡改的特性,智能合約一旦部署在區塊鏈上,其執行的邏輯就沒法再更改。長期來看,這個重要的特性反而限制了智能合約的彈性和發展。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
接下來實際來看一下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