在區塊鏈上建立可更新的智慧合約(一)
由於區(qū)塊鏈不可篡改的特性,智慧合約一但部署到區(qū)塊鏈上,其執(zhí)行的邏輯就無(wú)法再更改。長(zhǎng)期來(lái)看,這個(gè)重要的特性反而限制了合約的彈性和發(fā)展。
接下來(lái)要介紹如何設(shè)計(jì)及部署合約才能讓合約在需要時(shí)可以更新。但這裡的更新意思不是修改已經(jīng)部署的合約,而是部署新的合約、新的執(zhí)行邏輯但同時(shí)能繼續(xù)利用已經(jīng)存在資料。
首先要知道的是Ethereum Virtual Machine(EVM)如何知道要執(zhí)行合約的哪個(gè)函式。合約最後都會(huì)被編譯成bytecode,而你發(fā)起一個(gè)transaction要執(zhí)行合約裡的某個(gè)函式時(shí),交易裡的data欄位同樣也是bytecode而不是人看得懂的函式名稱。 以一個(gè)簡(jiǎn)單的合約為例:
contract Multiply {function multiply(int x, int y) constant returns(int) {return x*y; } }編譯完的bytecode:
6060604052341561000c57fe5b5b60ae8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c4308a814603a575bfe5b3415604157fe5b605e60048080359060200190919080359060200190919050506074565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820c40f61d36a3a1b7064b58c57c89d5c3d7c73b9116230f9948806b11836d2960c0029如果你今天要執(zhí)行multiply函式,算出8*7等於多少,你的transaction裡的data欄位會(huì)是?0x3c4308a800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007?
分成三個(gè)部分: 第一個(gè)是四個(gè)byte的3c4308a8,第二和第三個(gè)分別是32 byte長(zhǎng)的參數(shù),8和7。
3c4308a8是multiply函式的signature,是取函式名稱和參數(shù)型態(tài)丟進(jìn)雜湊後取前四個(gè)byte而得(不包含0x):
sha3("multiply(int256,int256)")); //0x3c4308a8851ef99b4bfa5ffd64b68e5f2b4307725b25ad0d14040bdb81e3bafc sha3("multiply(int256,int256)")).substr(2,8); //3c4308a8EVM就是靠函式的signature來(lái)知道該執(zhí)行哪個(gè)函式的。在合約編譯完的bytecode裡搜尋也能找到此signature。
接下來(lái)要介紹Solidity裡的三種函式呼叫方式:call、callcode和delegatecall。
- call:一般的呼叫都是這種方式,執(zhí)行背景跳到下一個(gè)函式的環(huán)境(這裡的環(huán)境指msg的值和合約的Storage)。如果被呼叫者是不同合約的函式則變成被呼叫者合約的環(huán)境,且msg.sender變成呼叫者。
- callcode:和call相同,只是將被呼叫者的函式搬到呼叫者的環(huán)境裡執(zhí)行。
假設(shè)A合約的x函式用callcode方式呼叫B合約的y函式,則會(huì)在A合約裡執(zhí)行y函式,使用A的變數(shù),所以如果y函式裡修改某個(gè)變數(shù)的值且這個(gè)變數(shù)的名稱剛好和A的某個(gè)變數(shù)名稱一樣,則A的該變數(shù)就會(huì)被修改。就把它想像成A多了一個(gè)y函式並執(zhí)行。 - delegatecall:和callcode相同,都是把被呼叫的函式搬到呼叫者的環(huán)境裡執(zhí)行,只是差在msg.sender的值。
用一個(gè)例子講解會(huì)比較清楚:假設(shè)A合約用delegatecall的方式呼叫B合約的函式,B合約的函式接下用callcode或call的方式呼叫C合約的函式,則函式裡看到的msg.sender會(huì)是B;但如果B改用delegatecall的方式呼叫C合約的函式,則函式裡看到的msg.sender會(huì)是A。就把它想像成把msg相關(guān)的值保持不變傳遞下去。
接下來(lái)實(shí)際來(lái)看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);} }部署並按順序執(zhí)行Multiply的multiply和delegateToPlus並觀察z值的變化:
可以看到執(zhí)行delegatecall之後z的值變成是8+7。 所以如果要讓我們未來(lái)可以改變執(zhí)行邏輯的話要怎麼寫(xiě)呢?
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當(dāng)作參數(shù)傳給delegateCall去執(zhí)行,假設(shè)原本是用Plus合約的執(zhí)行邏輯,現(xiàn)在我們更新成Multiply合約:
0x4429?是Plus合約的位址,?0xe905?是Multiply合約的位址。
我們以後只要給它改變後的函式的signature和合約地址就可以使用新的執(zhí)行邏輯了!
但如果合約不是只給一個(gè)人使用的話,當(dāng)要更新合約的時(shí)候所有參與的人都必須要更新新合約的位置。這時(shí)可以用一個(gè)合約來(lái)幫我們導(dǎo)到新的合約位置,就像路由器一樣,我們統(tǒng)一發(fā)送(還是以delegatecall的形式)到路由合約,再由路由合約幫我們導(dǎo)到正確的位置,未來(lái)更新合約就只需要更新路由合約的資料。
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)}} }執(zhí)行順序:
1. 執(zhí)行Main.deployDispatcher() 部署路由合約
2. 部署upgrade合約並將其address當(dāng)作Main.updateUpgrade()的參數(shù)傳入來(lái)更新upgrade合約的位址資訊。
3. 執(zhí)行Main.delegateCall(),參數(shù)是plus(int256,int256)的signature和任意兩個(gè)值。
4. 執(zhí)行Main.get(),藉由delegatecall去呼叫upgrade合約的get函式,回傳相加完的z值。因?yàn)槭莇elegatecall,所以這個(gè)z值其實(shí)是Main合約自己的,upgrade合約的z值還是零。
如果delegatecall呼叫的函式有回傳值的話,必須要用assembly來(lái)手動(dòng)搬移回傳值,因?yàn)閐elegatecall和call一樣,只會(huì)回傳true of false來(lái)代表執(zhí)行是否成功。Dispatcher在轉(zhuǎn)傳呼叫同樣也是用assembly code。
但因?yàn)槭怯胊ssembly手動(dòng)搬移回傳值,因此前提是回傳值的長(zhǎng)度必須是固定且已知的,所以當(dāng)我們?cè)诓襟E2更新upgrade合約時(shí),Dispatcher合約同時(shí)要去呼叫upgrade合約的initialize()函式,upgrade合約在initialize函式裡將它所有會(huì)有回傳值的函式的回傳值大小寫(xiě)入returnSizes中,之後如果呼叫具有回傳值的函式,Dispatcher就知道該返還多少大小的回傳值。
這裡還有一個(gè)重點(diǎn)是變數(shù)宣告的順序
因?yàn)楹霞s執(zhí)行要取用變數(shù)的值的時(shí)候,它會(huì)到對(duì)應(yīng)的Storage位置去找。所以如果你的合約變數(shù)宣告像這樣子
upgrade:
int x
int y
?—?—?—?—?
Dispathcer:
int x
int y
?—?—?—?—?
Main:
int x
int abc
int y
當(dāng)upgrade合約的函式要用到x和y的值的時(shí)候,它會(huì)找不到y(tǒng),因?yàn)镾torage是Main的。
Reference:
1.?http://ethereum.stackexchange.com/questions/3667/difference-between-call-callcode-and-delegatecall
2.?https://gist.github.com/Arachnid/4ca9da48d51e23e5cfe0f0e14dd6318f
原文地址:?https://medium.com/@twedusuck/%E5%9C%A8%E5%8D%80%E5%A1%8A%E9%8F%88%E4%B8%8A%E5%BB%BA%E7%AB%8B%E5%8F%AF%E6%9B%B4%E6%96%B0%E7%9A%84%E6%99%BA%E6%85%A7%E5%90%88%E7%B4%84-cbe015bdb339
總結(jié)
以上是生活随笔為你收集整理的在區塊鏈上建立可更新的智慧合約(一)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Parity 錢包合約漏洞
- 下一篇: 在區塊鏈上建立可更新的智慧合約(二)