您好,登錄后才能下訂單哦!
這篇文章主要介紹“怎么在區塊鏈上開發可更新的智能合約”,在日常操作中,相信很多人在怎么在區塊鏈上開發可更新的智能合約問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”怎么在區塊鏈上開發可更新的智能合約”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
由于區塊鏈不可篡改的特性,智能合約一旦部署在區塊鏈上,其執行的邏輯就無法再更改。長期來看,這個重要的特性反而限制了智能合約的彈性和發展。
接下來要介紹如何設計及部署合約才能讓合約在需要時可以更新。但這里的更新意思不是修改已經部署的合約,而是部署新的合約、新的執行邏輯但同時能繼續利用已經存在的資料。
首先要知道的是Ethereum Virtual Machine(EVM)要知道如何執行合約的那個函數。合約最后都會被編譯成字節碼,而你發起一個transaction要執行合約里的某個函數時,交易里的數據同樣也是字節碼,而不是人看得懂的函數名稱。 以一個簡單的合約為例:
contract Multiply { function multiply(int x, int y) constant returns(int) { return x*y; } }
編譯完的二進制碼:
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。
接下來要介紹Solidity里的三種調用方式:call、callcode和delegatecall。
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值的變化:
可以看到執行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的。
到此,關于“怎么在區塊鏈上開發可更新的智能合約”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。