变形合约的承诺与风险

  • 0age__
  • 发布于 2019-03-02 10:20
  • 阅读 9

文章介绍了以太坊Constantinople硬分叉后,通过EIP-1014引入的CREATE2操作码,使得新形式的智能合约——变形合约成为可能。这种合约可以在销毁后重新部署到相同地址并更换新的字节码,从而带来潜在的安全风险。文章详细探讨了变形合约的原理、实现方式及防御策略,并与现有的透明代理模式进行了对比。

在以太坊区块链上,Constantinople 硬分叉之后,出现了一个新事物。

如果你对以太坊上的智能合约有所了解,你就会知道它们是不可变的——一旦字节码被部署,除非合约调用 selfdestruct,否则它将保持不变。

如果这对你来说已经足够明显了,那么请做好准备……因为游戏规则即将改变。Constantinople 硬分叉包含了臭名昭著的 EIP-1014,它引入了一个新的操作码 CREATE2。这个操作码引入了一种新的魔法,在特定条件下,合约可以被销毁,然后重新部署到相同的地址,并带有新的字节码

哦,不好了,中本聪:我们已经不在堪萨斯了。

CREATE2 合约的奇特案例

CREATE2 部署的合约地址取决于调用者的地址、提供的 salt 参数以及将被创建的合约的初始化代码。如果这些参数中的任何一个被改变,合约地址也会随之改变。这似乎表明,由于你不能改变合约的初始化代码,因此你也不能改变最终的字节码。

即使这是真的,仍然存在新的风险,即合约被销毁并重新创建,这将清除它的存储。然而,还有一个需要考虑的复杂性:合约的初始化代码实际上可能是非确定性的,这意味着额外的因素可能会导致结果字节码发生变化。作为一个非确定性的例子,初始化代码可以调用某个具有可变存储的外部合约,并使用返回的数据来构造最终的字节码。

通过利用非确定性的初始化代码,合约可以突然在原地变异,并替换为任意的字节码。如果可升级性是一个漏洞,那么这个怪物就是一个变成六头狼蛛的毛毛虫:换句话说,一个变形合约

防御黑魔法

如果你对一个合约突然且意外地变形的想法感到不安,这里有一些补救措施可以用来保护自己:

  • 确保合约无法直接或通过 delegatecallcallcode 访问到 selfdestruct 操作码。如果合约无法被 selfdestruct,它就无法被重新部署。
  • 确保合约是从不允许重新部署的源部署的(例如,不使用 CREATE2,或者存储每次部署并防止重复部署)。你还需要确保部署者本身不是变形合约。
  • 在事务的其余部分继续之前,确保你正在交互的合约在事务开始时没有通过 EXTCODEHASH 等方式发生变化。

对于大多数 CREATE2 的诚实应用(例如在状态通道和反事实实例化中),这不应该是一个太大的问题。总的来说,你应该非常小心与任何可以 selfdestruct 或以其他危险方式变化的合约进行交互。但如果你正在寻找一个轻量级的可升级合约,希望有一个适当的控制和治理机制,那么这里正是你要找的!

灾难的配方

重新包装一只猫的方法不止一种,但创建一个变形合约的相对直接的方法如下:

  • 首先部署一个实现合约,它不依赖于构造函数(但可以有一个像 initialize 这样的普通函数来执行相同的角色),并且也有能力 selfdestruct
  • 然后,在存储中固定且已知的位置存储实现合约地址的引用。将发起 CREATE2 调用的工厂合约是一个自然的选择。
  • 使用 CREATE2,部署一个具有固定、非确定性初始化代码的变形合约,它将从工厂函数中检索实现地址,克隆该位置的运行时字节码,并使用它来部署变形合约的运行时字节码。(你也可以使用一个带有固定初始化代码的中间临时合约,然后通过 CREATE 部署变形合约,然后立即 selfdestruct。)
  • 当需要更改变形合约时,只需 selfdestruct 现有的合约,部署并引用一个新的实现,然后重新部署合约,它将克隆新的实现。由于初始化代码是相同的,变形合约的地址也将相同!

这种方法,以及一种在仍然访问 CREATE2 的同时防止部署变形合约的方法,可以在这个仓库中找到,关于如何构造可用于引用和克隆合约的初始化代码的详细分解可以在这里找到。请务必小心,欢迎来到这个勇敢而狂野的新世界!

透明代理的丑陋继兄弟

最后,简要比较一下最流行的创建可升级合约的现有方法,透明代理

  • 透明代理的升级将在升级时保留存储,而变形合约将完全清除状态(包括账户余额——小心不要将 selfdestruct 的余额直接转发回同一地址)。这使得透明代理成为可升级 ERC20 或 ERC721 合约的自然选择,而变形合约可能更适合 ERC725 身份合约或其他自主合约。
  • 调用变形合约的开销将比调用透明代理的开销要低,因为透明代理必须首先检查调用者以确保它不是升级管理员,然后 delegatecall 到逻辑合约。
  • 变形合约的升级过程不会像透明代理那样顺利,因为 selfdestruct 操作被记录在事务子状态中,并在事务结束时执行。这意味着升级将需要两次事务,因此在两次事务之间,合约代码将为空(并且容易受到中间使用的影响)。另一方面,碰到 selfdestruct 的透明代理将被完全销毁,但变形合约仍然可以恢复。
  • 这两种方法都不允许在合约初始化期间使用构造函数。相反,你应该提供一个在设置新合约后立即调用的 initialize 函数。唯一的例外是如果你使用通过 CREATE2 部署的中间临时合约来通过 CREATE 部署变形合约。在这种情况下,你仍然可以在部署变形合约时使用构造函数。

感谢 Jason Carver 让这种现象曝光,感谢 Martin Holst Swende (@mhswende) 提供的变形合约工厂使用的合约克隆机制,感谢整个 Zeppelin 团队,以及所有为这个主题的探索和教育做出贡献的人。弗兰肯斯坦的怪物已经重生——但用玛丽·雪莱的话来说,“我们的灵魂是如此奇怪地构建的,通过轻微的纽带,我们与繁荣和毁灭联系在一起。”

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

0 条评论

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