在区块链上开发可更新的智能合约

编程狂魔 ☉ 文 来源:编程狂魔
2018-04-24 @ 哈希力量文库

文库划重点:由于区块链不可篡改的特性,智能合约一旦部署在区块链上,其执行的逻辑就无法再更改。长期来看,这个重要的特性反而限制了智能合约的


由于区块链不可篡改的特性,智能合约一旦部署在区块链上,其执行的逻辑就无法再更改。长期来看,这个重要的特性反而限制了智能合约的弹性和发展。nf7哈希力量 | 消除一切智能鸿沟

接下来要介绍如何设计及部署合约才能让合约在需要时可以更新。但这里的更新意思不是修改已经部署的合约,而是部署新的合约、新的执行逻辑但同时能继续利用已经存在的资料。nf7哈希力量 | 消除一切智能鸿沟

首先要知道的是Ethereum Virtual Machine(EVM)要知道如何执行合约的那个函数。合约最后都会被编译成字节码,而你发起一个transaction要执行合约里的某个函数时,交易里的数据同样也是字节码,而不是人看得懂的函数名称。 以一个简单的合约为例:nf7哈希力量 | 消除一切智能鸿沟

contract Multiply {nf7哈希力量 | 消除一切智能鸿沟

    function multiply(int x, int y) constant returns(int) {nf7哈希力量 | 消除一切智能鸿沟

        return x*y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

编译完的二进制码:nf7哈希力量 | 消除一切智能鸿沟

6060604052341561000c57fe5b5b60ae8061001b6000396000f30060606040526000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff1680633c4308a814603a575bfe5b3415604157fe5b605e60048080359060200190919080359060200190919050506074565b6040518082815260200191505060405180910390f35b600081830290505b929150505600a165627a7a72305820c40f61d36a3a1b7064b58c57c89d5c3d7c73b9116230f9948806b11836d2960c0029nf7哈希力量 | 消除一切智能鸿沟

如果你要执行multiply函数,算出8*7等于多少,你的transaction里的数据是0x3c4308a800000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000007nf7哈希力量 | 消除一切智能鸿沟

分成三部分: 第一个是四个字节的3c4308a8,第二和第三个分別是32个字节长的参数,8和7。nf7哈希力量 | 消除一切智能鸿沟

3c4308a8是multiply函数的signature(签名),是取函数名称和参数类型使用sha3取前四个byte而得到(不包含0x):nf7哈希力量 | 消除一切智能鸿沟

sha3("multiply(int256,int256)"));nf7哈希力量 | 消除一切智能鸿沟

//0x3c4308a8851ef99b4bfa5ffd64b68e5f2b4307725b25ad0d14040bdb81e3bafc sha3("multiply(int256,int256)")).substr(2,8);nf7哈希力量 | 消除一切智能鸿沟

//3c4308a8nf7哈希力量 | 消除一切智能鸿沟

EVM就是靠函数的signature来知道该执行那个函数的。在合约编译完的字节码里查询也能找到这个signature。nf7哈希力量 | 消除一切智能鸿沟

接下来要介紹Solidity里的三种调用方式:call、callcode和delegatecall。nf7哈希力量 | 消除一切智能鸿沟

call:一般的调用都是这种方式,执行背景跳到下一个函数的环境(这里的环境是指msg的值和合约的Storage)。如果被调用的是不同合约的函数那么变换成被调用的合约的环境,且msg.sender编程调用者。nf7哈希力量 | 消除一切智能鸿沟

callcode:和call相同,只是将被调用函数搬到调用者的环境里执行。nf7哈希力量 | 消除一切智能鸿沟

假设A合约的x函数用callcode方式调用B合约的y函数,就会在A合约里执行y函数,使用A的参数,所以如果y函数里修改某个参数的值且这个参数的名称刚好和A的某个参数名称一致,则A的该参数就会被修改。就把它想像成A多了一个y函数并执行。nf7哈希力量 | 消除一切智能鸿沟

delegatecall:和callcode相同,都是把被调用的函数搬到调用者的环境里执行,只是在msg.sender的值上有区别。nf7哈希力量 | 消除一切智能鸿沟

来看一个例子:加入A合约用delegatecall的方式调用B合约的函数,B合约的函数接下用callcode或call的方式调用C合约的函数,那么函数里看到的msg.sender会是B;但如果B改用delegatecall的方式调用C合约的函数的话,那么函数里看到的msg.sender会是A。就把它想像成把msg相关的值保持不变传递下去就ok了。nf7哈希力量 | 消除一切智能鸿沟

接下来实际来看一下delegatecall的效果:nf7哈希力量 | 消除一切智能鸿沟

contract Plus {nf7哈希力量 | 消除一切智能鸿沟

    int z;nf7哈希力量 | 消除一切智能鸿沟

    function plus(int x, int y) {nf7哈希力量 | 消除一切智能鸿沟

        z = x y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

contract Multiply {nf7哈希力量 | 消除一切智能鸿沟

    int public z;nf7哈希力量 | 消除一切智能鸿沟

    function multiply(int x, int y) {nf7哈希力量 | 消除一切智能鸿沟

        z = x*y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

    function delegateToPlus(address _plus, int x, int y) {nf7哈希力量 | 消除一切智能鸿沟

        _plus.delegatecall( bytes4(sha3("plus(int256,int256)")) ,x ,nf7哈希力量 | 消除一切智能鸿沟

        y);nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

部署并按顺序执行Multiply的multiply和delegateToPlus并观察z值的变化:nf7哈希力量 | 消除一切智能鸿沟

可以看到执行delegatecall之后z的值变成是8 7。 所以如果要让我们未来可以改变执行逻辑的话怎么写代码呢?nf7哈希力量 | 消除一切智能鸿沟

contract Plus {nf7哈希力量 | 消除一切智能鸿沟

    int z;nf7哈希力量 | 消除一切智能鸿沟

    function plus(int x, int y) { //sig:"0xccf65503"nf7哈希力量 | 消除一切智能鸿沟

        z = x y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

contract Multiply {nf7哈希力量 | 消除一切智能鸿沟

    int z;nf7哈希力量 | 消除一切智能鸿沟

    function multiply(int x, int y) { //sig:"0x3c4308a8"nf7哈希力量 | 消除一切智能鸿沟

        z = x*y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

contract Main {nf7哈希力量 | 消除一切智能鸿沟

    int public z;nf7哈希力量 | 消除一切智能鸿沟

    function delegateCall(address _dest, bytes4 sig, int x, int y) {nf7哈希力量 | 消除一切智能鸿沟

        _dest.delegatecall(sig, x , y);nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

我们将合约的地址和函数的signature当做参数传递给delegateCall去执行,假设原本是用Plus合约的执行路基,现在我们更新成Multiply合约:nf7哈希力量 | 消除一切智能鸿沟

0x4429是Plus合约的地址,0xe905是Multiply合约的地址。nf7哈希力量 | 消除一切智能鸿沟

我们以后只要给它改变后的函数signature和合约地址就可以使用新的执行逻辑了!nf7哈希力量 | 消除一切智能鸿沟

但如果合约不是只给一个人使用的话,应当在更新合约的時候所有参与的人都必须要更新新合约的位置。这时候可以用一个合约来帮助我们导到新的合约位置,就像路由器似的,我们统一发送(还是以delegatecall的形式)到路由合约,再由路由合约帮我们导到正确的位置,未来更新合约就只需要更新路由合约的资料即可。nf7哈希力量 | 消除一切智能鸿沟

contract Upgrade {nf7哈希力量 | 消除一切智能鸿沟

    mapping(bytes4=>uint32) returnSizes;nf7哈希力量 | 消除一切智能鸿沟

    int z;nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function initialize() {nf7哈希力量 | 消除一切智能鸿沟

        returnSizes[bytes4(sha3("get()"))] = 32;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function plus(int _x, int _y) {nf7哈希力量 | 消除一切智能鸿沟

        z = _x _y;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

    function get() returns(int) {nf7哈希力量 | 消除一切智能鸿沟

        return z;nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

contract Dispatcher {nf7哈希力量 | 消除一切智能鸿沟

    mapping(bytes4=>uint32) returnSizes;nf7哈希力量 | 消除一切智能鸿沟

    int z;nf7哈希力量 | 消除一切智能鸿沟

    address upgradeContract;nf7哈希力量 | 消除一切智能鸿沟

    address public dispatcherContract;nf7哈希力量 | 消除一切智能鸿沟

    function replace(address newUpgradeContract) {nf7哈希力量 | 消除一切智能鸿沟

        upgradeContract = newUpgradeContract;nf7哈希力量 | 消除一切智能鸿沟

        upgradeContract.delegatecall(bytes4(sha3("initialize()")));nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

    function() {nf7哈希力量 | 消除一切智能鸿沟

        bytes4 sig;nf7哈希力量 | 消除一切智能鸿沟

        assembly { sig := calldataload(0) }nf7哈希力量 | 消除一切智能鸿沟

        var len = returnSizes[sig];nf7哈希力量 | 消除一切智能鸿沟

        var target = upgradeContract;nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

        assembly {nf7哈希力量 | 消除一切智能鸿沟

            calldatacopy(mload(0x40), 0x0, calldatasize)nf7哈希力量 | 消除一切智能鸿沟

            delegatecall(sub(gas, 10000), target, mload(0x40),nf7哈希力量 | 消除一切智能鸿沟

                        calldatasize, mload(0x40), len)nf7哈希力量 | 消除一切智能鸿沟

            return(mload(0x40), len)nf7哈希力量 | 消除一切智能鸿沟

        }nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

contract Main {nf7哈希力量 | 消除一切智能鸿沟

    mapping(bytes4=>uint32) public returnSizes;nf7哈希力量 | 消除一切智能鸿沟

    int public z;nf7哈希力量 | 消除一切智能鸿沟

    address public upgradeContract;nf7哈希力量 | 消除一切智能鸿沟

    address public dispatcherContract;nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function deployDispatcher() {nf7哈希力量 | 消除一切智能鸿沟

        dispatcherContract = new Dispatcher();nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function updateUpgrade(address newUpgradeContract) {nf7哈希力量 | 消除一切智能鸿沟

        dispatcherContract.delegatecall(nf7哈希力量 | 消除一切智能鸿沟

            bytes4( sha3("replace(address)")), newUpgradeContractnf7哈希力量 | 消除一切智能鸿沟

        );nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function delegateCall(bytes4 _sig, int _x, int _y) {nf7哈希力量 | 消除一切智能鸿沟

        dispatcherContract.delegatecall(_sig, _x, _y);nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟


nf7哈希力量 | 消除一切智能鸿沟

    function get() constant returns(int output){nf7哈希力量 | 消除一切智能鸿沟

        dispatcherContract.delegatecall(bytes4( sha3("get()")));nf7哈希力量 | 消除一切智能鸿沟

        assembly {nf7哈希力量 | 消除一切智能鸿沟

            output := mload(0x60)nf7哈希力量 | 消除一切智能鸿沟

        }nf7哈希力量 | 消除一切智能鸿沟

    }nf7哈希力量 | 消除一切智能鸿沟

}nf7哈希力量 | 消除一切智能鸿沟

执行顺序:nf7哈希力量 | 消除一切智能鸿沟

1. 执行Main.deployDispatcher() 部署路由合约nf7哈希力量 | 消除一切智能鸿沟

2. 部署upgrade合约并将其address当做Main.updateUpgrade()的参数传入用来更新upgrade合约的地址资料。nf7哈希力量 | 消除一切智能鸿沟

3. 执行Main.delegateCall(),参数是plus(int256,int256)的signature和任意两个值。nf7哈希力量 | 消除一切智能鸿沟

4. 执行Main.get(),由delegatecall去调用upgrade合约的get函数,回传相加完的z值。因为是delegatecall,所以这个z值其实是Main合约自己的,upgrade合约的z值是零。nf7哈希力量 | 消除一切智能鸿沟

如果delegatecall调用的函数有返回值的话,必须要用assembly来手动获得返回值,因为delegatecall和call一样,只会回传true of false来代表执行是否成功。Dispatcher在调用是同样也是用assembly code。nf7哈希力量 | 消除一切智能鸿沟

但因为是用assembly手动获得返回值,因此前提是返回值的长度必须是固定且已知的,所以当我们在步骤2更新upgrade合约时,Dispatcher合约同时去调用upgrade合约的initialize()函数,upgrade合约在initialize函数里将它所有会有返回值的函数的返回值大小写入returnSizes中,之后如果调用具有返回值的函数时,Dispatcher就知道返回值的大小了。nf7哈希力量 | 消除一切智能鸿沟

這个还有一个重点是参数定义的顺序nf7哈希力量 | 消除一切智能鸿沟

因为合约执行要用参数值的时候,它会到对应的Storage位置去找。所以如果你的合约参数定义像這樣子nf7哈希力量 | 消除一切智能鸿沟

upgrade:nf7哈希力量 | 消除一切智能鸿沟

int xnf7哈希力量 | 消除一切智能鸿沟

int ynf7哈希力量 | 消除一切智能鸿沟

— — — —nf7哈希力量 | 消除一切智能鸿沟

Dispathcer:nf7哈希力量 | 消除一切智能鸿沟

int xnf7哈希力量 | 消除一切智能鸿沟

int ynf7哈希力量 | 消除一切智能鸿沟

— — — —nf7哈希力量 | 消除一切智能鸿沟

Main:nf7哈希力量 | 消除一切智能鸿沟

int xnf7哈希力量 | 消除一切智能鸿沟

int abcnf7哈希力量 | 消除一切智能鸿沟

int ynf7哈希力量 | 消除一切智能鸿沟

当upgrade合约的函数需要用到x和y的值的时候,它会找不到y,因为Storage是Main的。nf7哈希力量 | 消除一切智能鸿沟

分享两个教程和一些免费资料给读者:nf7哈希力量 | 消除一切智能鸿沟

一个适合区块链新手的以太坊DApp开发教程:nf7哈希力量 | 消除一切智能鸿沟

http://xc.hubwiz.com/course/5a952991adb3847553d205d1nf7哈希力量 | 消除一切智能鸿沟

一个用区块链、星际文件系统(IPFS)、Node.js和MongoDB来构建电商平台:nf7哈希力量 | 消除一切智能鸿沟

http://xc.hubwiz.com/course/5abbb7acc02e6b6a59171dd6nf7哈希力量 | 消除一切智能鸿沟

收集整理了一些免费区块链、以太坊技术开发相关的文件,有需要的可以下载,文件链接:nf7哈希力量 | 消除一切智能鸿沟

1. web3.js API官方文档中文版:https://pan.baidu.com/s/1hOV9hEzi7hFxJCL4LTvC6gnf7哈希力量 | 消除一切智能鸿沟

2. 以太坊官方文档中文版     :https://pan.baidu.com/s/1ktODJKLMBmkOsi8MPrpIJAnf7哈希力量 | 消除一切智能鸿沟

3. 以太坊白皮书中文版       :https://pan.baidu.com/s/1bzAFnzJ35hlQxJ2J4Oj-Ownf7哈希力量 | 消除一切智能鸿沟

4. Solidity的官方文档中文版 :https://pan.baidu.com/s/18yp9XjEqAHpiFm2ZSCygHwnf7哈希力量 | 消除一切智能鸿沟

5. Truffle的官方文档中文版  :https://pan.baidu.com/s/1y6SVd7lSLUHK21YF5FzIUQnf7哈希力量 | 消除一切智能鸿沟

6. C#区块链编程指南         :https://pan.baidu.com/s/1sJPLqp1eQqkG7jmxqwn3EAnf7哈希力量 | 消除一切智能鸿沟

7. 区块链技术指南          :https://pan.baidu.com/s/13cJxAa80I6iMCczA04CZhgnf7哈希力量 | 消除一切智能鸿沟

8. 精通比特币中文版        :https://pan.baidu.com/s/1lz6te3wcQuNJm28rFvBfxgnf7哈希力量 | 消除一切智能鸿沟

9. Node.js区块链开发        :https://pan.baidu.com/s/1Ldpn0DvJ5LgLqwix6eWgygnf7哈希力量 | 消除一切智能鸿沟

10.geth使用指南文档中文版   :https://pan.baidu.com/s/1M0WxhmumF_fRqzt_cegnagnf7哈希力量 | 消除一切智能鸿沟

11.以太坊DApp开发环境搭建-Ubuntuhttps://pan.baidu.com/s/10qL4q-uKooMehv9X2R1qSAnf7哈希力量 | 消除一切智能鸿沟

12.以太坊DApp开发环境搭建-windows https://pan.baidu.com/s/1cyYkhIJIFuI2oyxM9Ut0eAnf7哈希力量 | 消除一切智能鸿沟

13.以太坊DApp开发私链搭建Ubuntu https://pan.baidu.com/s/1aBOFZT2bCjD2o0EILBWs-gnf7哈希力量 | 消除一切智能鸿沟

14.以太坊DApp开发私链搭建-windowshttps://pan.baidu.com/s/10Y6F1cqUltZNN99aJv9kAAnf7哈希力量 | 消除一切智能鸿沟



收录于哈希力量,手机站省略本文固定网址