BG悬赏令--如何实现未来项目F的智能合约升级?


#1

在商业系统中如何实现可升级的智能合约

现在众多的商业机构都在研究如何在已有的业务逻辑里如何更好的利用区块链这个技术,来解决传统业务逻辑上的问题,这不可避免的会遇到一些实现上的问题,在我的一群小伙伴中,他们正面临这样的困扰,还记着在上一篇 文章中我们讨论了关于商业应用到底需要多少TPS的问题 吗?本章中我们试图讨论另外一个问题 《在商业系统中如何实现可升级的智能合约》。

这些问题的产生都基于小伙伴们的新型商业系统为前提,我先简单介绍下这个“未来商业系统F”,这个系统F首先包含数十万家商户、近10亿的用户、数万个自治社群,用户、商户和自治社群都能产生协作和交易,并从中获得应有的收益,这些“收益” 包括但不限于财富、名誉、影响力等等,如果这些角色间的交易规则都体现在智能合约里,那么对于如何部署智能合约就是个挑战,并且这个系统里的智能合约必定会面临经常变换、升级迭代甚至丢弃。

还记着上文中的阿乐同学吗?他从智能合约的实现方案上给出了建议,简单来说就是写2个合约,现在ERC721 已经有类似的的Delegate实现方法了,例如:A为基础合约,B为代理子合约,通过A来调用B合约,业务逻辑可以实现在B合约上,新的业务逻辑可以实现在代理的C合约上,然后A合约调用C合约,实现合约的刚更新,我从网上也看到了一些类似的方案,下文尝试解释这个方案,如果你有更好的主意,欢迎探讨!

更新合同的一种方法是使用版本控制系统。例如,您可以拥有一个入口合同,该合同只是将所有调用转发到最新版本的合同,由可更新地址参数定义,您还可以使用名称注册表,并将其更新为指向最新的合同版本。

另一种方法是将您的逻辑代码放在库中,然后通过Solidity中的库使用CALLCODE功能来调用位于指定的可更新地址的代码,这样用户数据在版本之间保持不变,这具有以下限制:逻辑契约的ABI必须随时间保持相同。

基础:

从Homestead发布开始,现在有一个DELEGATECALL操作码。 这允许你在维护msg.sender和所有存储时基本上将调用转发到单独的合同,例如,您可以拥有一个维护相同地址和存储的合同,但将所有调用转发到存储在变量中的地址:

contract Relay {
    address public currentVersion;
    address public owner;

    function Relay(address initAddr){
        currentVersion = initAddr;
        owner = msg.sender;
    }

    function update(address newAddress){
        if(msg.sender != owner) throw;
        currentVersion = newAddress;
    }

    function(){
        if(!currentVersion.delegatecall(msg.data)) throw;
    }
}

如果你有升级合约的需求,你需要考虑以下5点来设计您的智能合约:

  1. 保持智能合约**模块化和相当独立的数据结构规则和逻辑,因此,如果您需要更改某些内容,您将只更改相关合同,而无需更改许多或所有合同。
  2. 你应该做好准备能够在任何迁移过程中停止所有操作。 因为你不希望处于以下情况:人们仍然可以在迁移时及之后将数据更新插入到旧版本的智能合约中。
  3. 你应该之前提供了从智能合约中读取所有数据的功能。 当然,你可以通过限制将所有数据读取给所有者或任何其他可信用户或甚至另一个智能合约来进行许可读取,您需要阅读旧版智能合约并插入新版本。
  4. 您将使用以下策略与智能合约进行通信。这里有个智能合约升级最佳实践可以参考下,其中有开发者贡献了一段代码,大家可以看下。

示例1:使用注册表合同来存储最新版本的合同

在此示例中,不转发呼叫,因此用户应该获取当前地址,如下:

contract SomeRegister {
    address backendContract;
    address[] previousBackends;
    address owner;

    function SomeRegister() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        require(msg.sender == owner)
        _;
    }

    function changeBackend(address newBackend) public
    onlyOwner()
    returns (bool)
    {
        if(newBackend != backendContract) {
            previousBackends.push(backendContract);
            backendContract = newBackend;
            return true;
        }

        return false;
    }
}

这种方法有几个缺点

  1. 用户必须始终查找当前地址以及任何失败的人,这样做有使用旧版本合同的风险。

  2. 您需要仔细考虑如何处理合同&替换合同时的数据。

  3. 另一种方法是将合同转发呼叫和数据发送给合同的最新版本。

示例2:使用DELEGATECALL转发数据和调用

contract Relay {
    address public currentVersion;
    address public owner;

    modifier onlyOwner() {
        require(msg.sender == owner);
        _;
    }

    function Relay(address initAddr) {
        currentVersion = initAddr;
        owner = msg.sender; // this owner may be another contract with multisig, not a single contract owner
    }

    function changeContract(address newVersion) public
    onlyOwner()
    {
        currentVersion = newVersion;
    }

    function() {
        require(currentVersion.delegatecall(msg.data));
    }
}

这种方法避免了以前的问题,但存在问题,你必须非常小心地将数据存储在此合同中,你的数据可能最终被破坏,另外这个简单 模式的版本不能从函数返回值 转发它们,这限制了它的适用性。

但是,也有人建议检查由Zeppelin Solutions和Aragon发布的Solidity中的代理库 ,有一个计划为这个问题制定行业标准,你必须有一个良好的测试策略和策略,因为更新智能合约的成本真的会毁了你的生活,

有个外国小哥哥在Medium上专门讨论了此问题,有兴趣可以前去一观,以太坊dApps的基本设计考虑因素(1):可升级的智能合约 ,而且现在越来越多的方法出现了,还出现了越来越标准的方法库,大家可以去研究下,有好玩的,欢迎过来分享。

另外我还看到有项目优化了智能合约升级这个需求,具体可以看下这个链接,这都是业内对其的改进。

BG 悬赏令

文章的最后发个BG悬赏令,不是比武招亲哈!基于上面这个未来项目F,如果你跟小伙伴们有更棒的技术解决方案来实现smart contract的更新,欢迎组队来挑战,请实现你的demo,测试你的代码,整理成文,发表在Blockgeek.com的技术快答论坛上,我们会选择这2个星期内最优秀的技术DEMO给予激励,并发表在BG的公众号等各个渠道上,除了有机会接受众多BG开发者的膜拜和拍砖,还可以得到200个HPB Token的鼓励,BG寻得HPB芯链基金会的赞助,希望其他牛逼的项目团队也像HPB基金会学习,给BG的开发者们来点赞助,多少都是爱,来点吧!