在區塊鏈上建立可更新的智慧合約(一)
由於區塊鏈不可篡改的特性,智慧合約一但部署到區塊鏈上,其執行的邏輯就無法再更改。長期來看,這個重要的特性反而限制了合約的彈性和發展。
接下來要介紹如何設計及部署合約才能讓合約在需要時可以更新。但這裡的更新意思不是修改已經部署的合約,而是部署新的合約、新的執行邏輯但同時能繼續利用已經存在資料。
首先要知道的是Ethereum Virtual Machine(EVM)如何知道要執行合約的哪個函式。合約最後都會被編譯成bytecode,而你發起一個transaction要執行合約裡的某個函式時,交易裡的data欄位同樣也是bytecode而不是人看得懂的函式名稱。 以一個簡單的合約為例:
contract Multiply {function multiply(int x, int y) constant returns(int) {return x*y; }
}
編譯完的bytecode:
6060604052341561000c57fe5b5b60ae8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c4308a814603a575bfe5b3415604157fe5b605e60048080359060200190919080359060200190919050506074565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820c40f61d36a3a1b7064b58c57c89d5c3d7c73b9116230f9948806b11836d2960c0029
如果你今天要執行multiply函式,算出8*7等於多少,你的transaction裡的data欄位會是 0x3c4308a800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007
分成三個部分: 第一個是四個byte的3c4308a8,第二和第三個分別是32 byte長的參數,8和7。
3c4308a8是multiply函式的signature,是取函式名稱和參數型態丟進雜湊後取前四個byte而得(不包含0x
):
sha3("multiply(int256,int256)"));
//0x3c4308a8851ef99b4bfa5ffd64b68e5f2b4307725b25ad0d14040bdb81e3bafc sha3("multiply(int256,int256)")).substr(2,8);
//3c4308a8
EVM就是靠函式的signature來知道該執行哪個函式的。在合約編譯完的bytecode裡搜尋也能找到此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相關的值保持不變傳遞下去。
接下來實際來看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的。
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
在區塊鏈上建立可更新的智慧合約(一)相关推荐
- 在區塊鏈上建立可更新的智慧合約(二)
這篇介紹用library的方式來建立可更新的合約邏輯. Library Library是另外一種形式的contract,宣告方式也幾乎一樣: Library libA{}.Library會被部署在鏈上 ...
- 1分鐘帶你了解最新區塊鏈支付系統「GuardPay 神盾支付」
美國矽谷的一個區塊鏈新創團隊,於近日正式啟動了區塊鏈支付系統「GuardPay 神盾支付」,旨在提高支付系統的效率和安全性,還有更重要的是,將區塊鏈技術進一步落地應用,於目前已經於日本和韓國等地區進行 ...
- 这就是搜索引擎--读书笔记五--索引的建立与更新
索引的建立和更新 索引的建立 前一总结里说到,如果索引结构建立好了,可以提高搜索的速度,那么给定一个文档集合,索引是如何建立起来的呢?建立索引的方式有很多种,在这里我就书中提到的三种方法简单总结一下. ...
- .git文件夹_如何使用git把本地代码上传(更新)到github上
什么是git?git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理.git是一种工具,它能更好的让我们管理代码.很多时候如果我们需要保持本地代码和github代码版本 ...
- 笔记本上建立WIFI供安卓手机使用
今天较大家用两种方法在笔记本上建立WIFI供安卓手机使用一,用是用软件,connetify这个软件你可以去下载,虽然是英文,但是很容易使用. 二,用win7建立无线局域网,可以共享上网可以局域网游戏. ...
- 通道结构体_超账结构中系统通道和应用程序通道上的配置更新
通道结构体 介绍(Introduction) Early this July my article System Channel and Application Channel was about a ...
- 真实世界的Windows Azure: 为了加强和振兴Luxor旅游业,埃及世界文化遗址在Windows Azure上建立移动门户...
作为真实世界 Windows Azure 系列的一部分,在埃及通信和信息技术部(MCIT),我们联系了省开发顾问Wafaa Hassa,探索公司怎样在WindowsAzure上建立Luxor移动门户, ...
- 重大利好!印度国家支付公司批准WhatsApp在UPI上建立支付服务!
早在2018年的年初,WhatsApp就开始在印度对100万用户测试其支付服务,但是受限于各方面掣肘,WhatsApp在印度的支付业务一直未能扩展开来. 母公司Facebook也一直致力于想让What ...
- 使用git把本地代码上传(更新)到github上
什么是git?git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理.git是一种工具,它能更好的让我们管理代码.很多时候如果我们需要保持本地代码和github代码版本 ...
最新文章
- vc2019切分位图图像带动态创建工具条
- 试着理解cookie和session
- .NET Core/.NET之Stream简介
- 基于HTML5陀螺仪实现ofo首页眼睛移动效果
- hadoop2.2.0 分布式存储hdfs完全分布式搭建及功能测试记录(一)----架构及原理介绍...
- python 最小二乘法_最小二乘法及其python实现详解
- Java实训项目10:GUI学生信息管理系统 - 实现步骤 - 创建数据访问接口实现类
- java map 变量_Java源码解析HashMap成员变量
- 【题解】(排序) —— POJ 0811:牛的选举
- python-docx下载_python-docx
- 3992. 树上有猴-AcWing题库
- php视频弹幕,php超仿bilbili播放器带弹幕库后台管理系统
- android悬浮窗服务卡死,Android 悬浮窗兼容问题谈
- DirectoryEntry的应用
- ACTS:首屈一指的软件测试策略是什么?
- python的一些技巧操作,提高编码效率
- 当远程工作成为未来的工作方式......
- 午芯高科WXP380气压传感器
- 1分钟链圈 | 区块链从业者平均年收入在20万元左右!纳斯达克报告:只有5%的IT供应商部署了区块链...
- 整理 node-sass 安装失败的原因及解决办法