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

  • Ashton
  • 更新于 2019-02-21 10:24
  • 阅读 5658

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

不升级,出了 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. 如果实在需要可升级合约,尽量限制升级合约所影响的范围。进一步也可以使用多签机制,当社区里超过一定数量的用户签名同意升级后才能触发升级操作。
点赞 5
收藏 2
分享

0 条评论

请先 登录 后评论
Ashton
Ashton
专注于 EVM 和比特币生态的区块链开发者