可升级性是一种缺陷

  • smarx_
  • 发布于 2019-02-07 15:41
  • 阅读 12

文章讨论了智能合约中的可升级性问题,认为可升级性破坏了智能合约的不可变性,因此是一个缺陷。文章通过代码示例详细解释了如何使用代理模式实现可升级性,并提出了几种缓解策略,如限制可变性和使用参数等。

摘要

  1. 智能合约很有用,因为它们是去信任的
  2. 不可变性是实现去信任的关键特性。
  3. 可升级性会破坏合约的不可变性。
  4. 因此,可升级性是一个 bug。(但也有缓解措施!)

为什么我们需要智能合约?

我想给你提供一个一生只有一次的投资机会。如果你今天发送 1 以太币给我,我明天会发送 2 以太币给你。你愿意吗?

尽管有一些 证据表明人们会接受这样的提议,但我希望你会拒绝。你没有任何理由相信我是否会履行承诺。

智能合约登场。智能合约让我们能够在不信任对方的情况下进行各种交易。下面是一个简单的以太坊智能合约,我们可以用它来提供这个投资机会。如果合约中有 1 以太币,你可以发送 1 以太币,并在明天提取 2 以太币:

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;
        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);
    }
}

如果我将该合约部署到以太坊主网并存入 1 以太币,你会毫不犹豫地投资。通过将规则编码到智能合约中,我们使我们的金融交易变得“去信任”。

(这个“投资”场景有点不切实际,但我会坚持使用它,因为代码简单。如果你愿意,可以想象一下点对点赌博的类似场景。)

不可变性的重要性

当我在本文开头向你提供投资机会时,你拒绝了,因为你不知道我会怎么做。我会像承诺的那样付钱给你,还是会卷款逃跑?

现在我们有了一个智能合约,你可以放心接受这个提议,因为你确实知道智能合约会做什么。你可以自己阅读代码,以太坊保证了这段代码将精确执行,因为智能合约代码是不可变的。

我无法影响结果,所以你根本不需要信任我。

可升级性 = 可变性

迟早,每个学习智能合约的开发人员都会问一个问题:“我如何修复 bug?”

许多开发人员独立提出了在运行时交换代码的方法。在过去的一年里,标准的可升级性方法变得流行起来。也许最常见的是“代理模式”。

下面是我们使用代理模式实现的可升级投资机会合约。Base 合约跟踪状态变量,Implementation 提供业务逻辑,Proxy 允许在运行时替换实现:

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);
    }
}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;
    }

    // adapted from https://github.com/zeppelinos/labs/blob/master/upgradeability_using_eternal_storage/contracts/Proxy.sol
    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) }
         }
    }
}

如果我将该 Proxy 合约(使用那个 Implementation)部署到以太坊并存入 1 以太币,你会愿意投资吗?

希望你会再次拒绝。因为合约代码不再不可变,你不再知道合约会做什么。阅读代码对你没有帮助,因为我随时可以更改代码。

以下是我可能“升级”到的替代实现:

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

请注意,在这个特定的例子中,你的以太币会被锁定 24 小时,因此我有充足的时间窗口来进行更改。但即使支付是即时的,我也总是可以与你的交易竞争,并尝试首先更改实现。

缓解措施

如果可升级性是一个 bug,该怎么办?

直接说不

我的第一个建议——我意识到这在我的团队内部也是有争议的——是你可能不应该让你的合约可升级。大多数情况下,添加可升级性实际上并不会帮助你,它会破坏智能合约的价值,并且可能是 bug 的来源本身

限制可变性

如果你发现可升级性是你真正的需求,考虑将合约可变性限制在不会伤害任何人的范围内。

例如,在本文描述的合约中,我可以要求代码只能在发出事件和 48 小时延迟后才能更改。因为资金只被锁定 24 小时,这将给用户留出足够的时间,如果他们不喜欢待定的代码更改,可以停止使用合约。

使用参数

参数是限制合约可变性的另一种好方法。我们一开始的投资机会合约将投资者的资金翻倍。只要只增加,这个倍数就可以在运行时安全地更改。如果我更改代码以将投资增加到三倍或四倍,没有人会介意。

迁移而非突变

最后,升级智能合约最常见的方法是部署一个新合约。如果新合约对每个人来说都更好,用户会乐意停止使用旧合约并转而使用新合约。如果这种交换涉及从旧合约中读取状态,有时这被称为“迁移模式”。

更复杂的模式也存在。例如,一些合约使用代理进行升级,但要求用户选择加入后才能获得新实现。

最后的话

对于开发人员来说,可升级性的诱惑是显而易见的。能够在合约部署后修复 bug 并添加功能是很棒,但这种能力本身会破坏智能合约的一个关键特性。

请记住,你的智能合约的用户不信任你,而你写智能合约的初衷就是为了这一点。

  • 原文链接: medium.com/consensys-dil...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
smarx_
smarx_
江湖只有他的大名,没有他的介绍。