由於區塊鏈不可篡改的特性,智慧合約一但部署到區塊鏈上,其執行的邏輯就無法再更改。長期來看,這個重要的特性反而限制了合約的彈性和發展。

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

首先要知道的是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

在區塊鏈上建立可更新的智慧合約(一)相关推荐

  1. 在區塊鏈上建立可更新的智慧合約(二)

    這篇介紹用library的方式來建立可更新的合約邏輯. Library Library是另外一種形式的contract,宣告方式也幾乎一樣: Library libA{}.Library會被部署在鏈上 ...

  2. 1分鐘帶你了解最新區塊鏈支付系統「GuardPay 神盾支付」

    美國矽谷的一個區塊鏈新創團隊,於近日正式啟動了區塊鏈支付系統「GuardPay 神盾支付」,旨在提高支付系統的效率和安全性,還有更重要的是,將區塊鏈技術進一步落地應用,於目前已經於日本和韓國等地區進行 ...

  3. 这就是搜索引擎--读书笔记五--索引的建立与更新

    索引的建立和更新 索引的建立 前一总结里说到,如果索引结构建立好了,可以提高搜索的速度,那么给定一个文档集合,索引是如何建立起来的呢?建立索引的方式有很多种,在这里我就书中提到的三种方法简单总结一下. ...

  4. .git文件夹_如何使用git把本地代码上传(更新)到github上

    什么是git?git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理.git是一种工具,它能更好的让我们管理代码.很多时候如果我们需要保持本地代码和github代码版本 ...

  5. 笔记本上建立WIFI供安卓手机使用

    今天较大家用两种方法在笔记本上建立WIFI供安卓手机使用一,用是用软件,connetify这个软件你可以去下载,虽然是英文,但是很容易使用. 二,用win7建立无线局域网,可以共享上网可以局域网游戏. ...

  6. 通道结构体_超账结构中系统通道和应用程序通道上的配置更新

    通道结构体 介绍(Introduction) Early this July my article System Channel and Application Channel was about a ...

  7. 真实世界的Windows Azure: 为了加强和振兴Luxor旅游业,埃及世界文化遗址在Windows Azure上建立移动门户...

    作为真实世界 Windows Azure 系列的一部分,在埃及通信和信息技术部(MCIT),我们联系了省开发顾问Wafaa Hassa,探索公司怎样在WindowsAzure上建立Luxor移动门户, ...

  8. 重大利好!印度国家支付公司批准WhatsApp在UPI上建立支付服务!

    早在2018年的年初,WhatsApp就开始在印度对100万用户测试其支付服务,但是受限于各方面掣肘,WhatsApp在印度的支付业务一直未能扩展开来. 母公司Facebook也一直致力于想让What ...

  9. 使用git把本地代码上传(更新)到github上

    什么是git?git是一个开源的分布式版本控制系统,可以有效.高速地处理从很小到非常大的项目版本管理.git是一种工具,它能更好的让我们管理代码.很多时候如果我们需要保持本地代码和github代码版本 ...

最新文章

  1. vc2019切分位图图像带动态创建工具条
  2. 试着理解cookie和session
  3. .NET Core/.NET之Stream简介
  4. 基于HTML5陀螺仪实现ofo首页眼睛移动效果
  5. hadoop2.2.0 分布式存储hdfs完全分布式搭建及功能测试记录(一)----架构及原理介绍...
  6. python 最小二乘法_最小二乘法及其python实现详解
  7. Java实训项目10:GUI学生信息管理系统 - 实现步骤 - 创建数据访问接口实现类
  8. java map 变量_Java源码解析HashMap成员变量
  9. 【题解】(排序) —— POJ 0811:牛的选举
  10. python-docx下载_python-docx
  11. 3992. 树上有猴-AcWing题库
  12. php视频弹幕,php超仿bilbili播放器带弹幕库后台管理系统
  13. android悬浮窗服务卡死,Android 悬浮窗兼容问题谈
  14. DirectoryEntry的应用
  15. ACTS:首屈一指的软件测试策略是什么?
  16. python的一些技巧操作,提高编码效率
  17. 当远程工作成为未来的工作方式......
  18. 午芯高科WXP380气压传感器
  19. 1分钟链圈 | 区块链从业者平均年收入在20万元左右!纳斯达克报告:只有5%的IT供应商部署了区块链...
  20. 整理 node-sass 安装失败的原因及解决办法

热门文章

  1. MySql数据库安装修改密码开启远程连接图解
  2. unix cheatsheet
  3. 第一阶段冲刺最后一天
  4. 手机web——自适应网页设计(html/css控制) - 51CTO.COM
  5. CSU 1337 搞笑版费马大定理(2013湖南省程序设计竞赛J题)
  6. 用numpy把一个矩阵的一行或一列删除,再把剩下的拼在一起
  7. django+mysql 配置一系列问题
  8. 机器学习导论(张志华):核定义(2)
  9. [云炬创业管理笔记]第二章成为创业者讨论2
  10. 2-1 什么是人工智能|人工智能框架TensorFlow应用实践