美文网首页
关于智能合约升级,也许原来的认知是错的

关于智能合约升级,也许原来的认知是错的

作者: Ashton | 来源:发表于2019-02-21 10:24 被阅读25次

    不升级,出了 bug 怎么办?可升级,智能合约还有什么信任度可言?估计很多区块链从业技术人员都会像我一样有这样的纠结与困惑。
    这两天看到一篇文章 Upgradeability Is a Bug 感觉讲的不错。本文大部分内容也来自那篇文章。

    0x01 智能合约因其去信任性而有用

    如果我给你说我这边有个不错的投资机会,你今天给我 1 块钱,我明天会返还给你两块,你会不会投?好吧,也许你会看在我的面子上投上 1 块钱,不是因为你相信我,而是因为 1 块钱即使丢了也无所谓。恐怕很少人愿意为了明天的 200万 而在今天付 100 万给我,跑路风险太大了,即使我们签个书面合约。即使你相信我的人品不会跑路,也会有很多很多的质疑,这就是信任的成本。

    这个时候,我在以太坊上部署一个下面的智能合约,并往里面放 1000 个以太币,你知道智能合约一旦部署就是不可更改的,里面的代码逻辑清清楚楚写着你现在投资合约所存以太币的一半,也就是 500 个以太币,24 小时后就可以把合约里全部的 1000 个以太币全部取出。你还犹豫什么?赶紧投啊。

    // 投资机会合约
    contract InvestmentOpportunity {
        address public investor;
        uint256 public payday;
        // 有了这个构造函数,我可以在部署合约时放若干个以太币进去
        constructor() public payable {}
       // 你可以调用这个函数进行投资
        function invest() external payable {
            // 如果有人投过了就不能再投了
            require(investor == address(0), "Someone beat you to it!");
            // 投资数额应是我所放到合约里的以太币数量的一半
            require(msg.value == address(this).balance / 2,
                "You must match the contract balance.");
            // 记录投资人    
            investor = msg.sender;
           // 当前时间加 24 小时作为投资兑现时间
            payday = now + 24 hours;
        }
        // 你可以通过这个函数取钱
        function withdraw() external {
            // 确保取钱人是投资人
            require(msg.sender == investor,
                "Only the investor can withdraw.");
            // 过了投资兑现时间才能把钱取出
            require(now >= payday,
                "You must wait until the payday time."); 
            // 将合约里的钱赚到你的账户里       
            msg.sender.transfer(address(this).balance);
        }
    }
    

    这个时候,智能合约已经帮你我完成了所谓的去信任。你不需要相信我,只需要确保合约是好的就成了。这合约和纸质的合约不同,你不需要担心执行问题。

    0x02 不可变性是去信任的基础

    我们再来回顾一下上面的案例,为啥我口述一个回报那么丰厚的投资机会,你还会担心犹豫不敢投?担心我会变卦呗,你会从我的历史,我的动机各个方面分析后,确保我有明确的自私性理由才有可能肯相信我是真的不是信口开河,不是会拿钱跑路而是信守承诺。有时候单纯的做好事是很难的......

    所谓信任度,很多时候就是取决于对变化的一种担心程度。

    而基于区块链的智能合约给人们提供了一种选择,可以将合约内容和合约的执行都以不可变的形式确定下来。

    0x03 合约的可升级会破坏不可变性

    然而是程序就机会不可避免会有 bug,并且这种 bug 出现的几率会随着程序复杂度的增加而增加。常见的修复 bug 的方式就是对程序进行升级,所以很多人乐此不疲的研究可升级的合约设计,并总结出一些合约升级模式。
    比如上面我们提到的那个合约,改成可升级的可能会变成下面这个样子。

    基础合约保存了核心数据。

    contract Base {
        // proxy state
        address owner;
        address implementation;
        // implementation state
        address public investor;
        uint256 public payday;
    }
    

    实现合约提供了投资逻辑的实现。

    contract Implementation is Base {
        function invest() external payable {
            require(investor == address(0), "Someone beat you to it!");
            require(msg.value == address(this).balance / 2,
                "You must match the contract balance.");
            
            investor = msg.sender;
            payday = now + 24 hours;
        }
        
        function withdraw() external {
            require(msg.sender == investor,
                "Only the investor can withdraw.");
            require(now >= payday,
                "You must wait until the payday time.");
            
            msg.sender.transfer(address(this).balance);
        }
    }
    

    代理合约继承了基础合约,并可以通过 setImplementation 函数来更换业务逻辑实现合约。

    contract Proxy is Base {
        constructor(address _implementation) public payable {
            owner = msg.sender;
            implementation = _implementation;
        }
        function setImplementation(address _implementation) external {
            require(msg.sender == owner);
            implementation = _implementation;
        }
    function() external payable {
            address impl = implementation;
            assembly {
                let ptr := mload(0x40)
                calldatacopy(ptr, 0, calldatasize)
                let result := delegatecall(gas, impl, ptr,
                    calldatasize, 0, 0)
                let size := returndatasize
                returndatacopy(ptr, 0, size)
            
                switch result
                case 0 { revert(ptr, size) }
                default { return(ptr, size) }
             }
        }
    }
    

    投资逻辑没变,但这个时候让你通过代理合约来投资你会投吗?作为一个聪明又理性的人,你很可能会拒绝投资。因为现在智能合约带来的那种不可变性已经不复存在了,你不知道这个合约会不会按照当前的逻辑执行,因为我可以随时把业务逻辑实现合约替换掉。

    如果我把业务逻辑实现合约替换为下面这个,随时可以卷钱跑路,智能合约又能起到什么保护作用!

    contract ExitScam is Base {
        function exit() external {
            require(msg.sender == owner);
            selfdestruct(msg.sender);
        }
    }
    

    也许作为合约开发者,我们基本不会这么做,但是,谁会信呢?我们使用智能合约,不就是为了让用户不必去信任我们么?!

    0x04 应该怎么办?

    难道有 bug 就不改了么?难道合约被黑客攻击了就眼睁睁看着资产流失么?
    非也。

    1. 我们可以在合约上添加业务暂停的开关,有异常发生时暂停业务。
    2. 与其设计可升级的合约,不如设计可迁移的合约。当问题发生时,部署新的合约,并将老合约的状态数据迁移至新合约。
    3. 如果实在需要可升级合约,尽量限制升级合约所影响的范围。进一步也可以使用多签机制,当社区里超过一定数量的用户签名同意升级后才能触发升级操作。

    相关文章

      网友评论

          本文标题:关于智能合约升级,也许原来的认知是错的

          本文链接:https://www.haomeiwen.com/subject/wdqcyqtx.html