本文深入探讨了以太坊中的 create2
操作码及其实现原理,分析了在没有实际部署合约的情况下如何承诺特定代码到特定地址。文章通过各种示例和原型代码展示了 create2
的优势和潜在用途,并对其与传统 create
方法的比较进行了详细讨论,尤其是在合约地址的可预测性方面。同时,文章提到了一些对 create2
的批评意见,并展望了未来的应用前景。
create2
?前几天,我潜入了以太坊的 reddit 世界,注意到有人谈论关于新操作码的一些潜在漏洞。这个操作码称为 create2
,提供了一种创建合约的新机制;然而,它并不是在由创造者的地址和交易 nonce 派生的地址上创建合约,而是在由合约的 init_code
、一个 address
和一些 salt
(任意值)派生的地址上创建合约。更正式地说,它看起来像这样:
keccak256(0xff ++ address ++ salt ++ keccak256(init_code))[12:]
注意:根据 规范,不清楚这个 address
是发送者的地址,还是只有某个任意地址。我假设是前者(否则与 salt
重复),但我不能确定。
这一想法的目的是能够在部署之前预先确定合约的地址,这样合约可能永远不需要被实际部署,如果最终被认为是必要的(我相信这被称为“反事实实例化”)。
现在,这听起来很酷,但这在使用 create
时已经基本上是可能的。我在几个地方指出了这一点,并 迅速与方法的原始提议者之一展开了讨论。为了清晰起见,u/technocrypto
是一位 Jeff Coleman,来自 L4 Ventures,但我直到谈话的后来才知道这一点,当时候已经很明显他有些不悦。
他接着声称,在部署之前,承诺在特定地址上使用特定代码是不可能的。由于我对这是否真的是不可能而感兴趣,我开始尝试 一个原型。事实证明,这样的事不仅可能,而且实际上非常简单。
在继续之前,我想通过从第一原则重构这个原型来稍微详细讲解一下。关键目标是证明,在实际上不部署代码的情况下,承诺特定代码在特定地址上是可能的。
最小示例
这是这个模式的核心。这基本上是我们都知道和喜欢的工厂模式,唯一的警告是它在失效之前只能创建一个 Machine
。
然而,由于 合约地址是如何与 create
生成的,我们可以在调用 commit
之前确定我们的 Machine
在部署时将拥有什么地址。当我们的 MachineCommitment
被创建时,我们现在可以确定 Machine
只能存在于由 MachineCommitment
的地址和 nonce 1
计算出的地址。
如果我们愿意,甚至可以在 Solidity 中做到这一点:
address(keccak256(0xd6, 0x94, address(commitment), 0x01));
然而,单靠这个模式并不足够。这比简单地创建一个新的 Machine
需要更多的 gas,因为 MachineCommitment
需要知道 Machine
的代码才能部署它。
我们可以通过从工厂模式中借用来解决这个问题,本质上将这个问题拆分成其组成部分:承诺部署 某个代码 和承诺部署到 某个地址。
我们最终得到的与上述建议的原型非常接近:
带有一些注释的原型
顺便提一下,我保留了一些调试(即事件),因为事实证明这个特定模式在 ethfiddle 中运行不正常,但在 Remix 中运行正常。去想想吧!
通过部署工厂,我们现在创建了对某个特定代码的创建承诺。通过部署承诺,并仅提供工厂的地址,我们创建了特定地址的承诺。总体而言,这仍然提供了对 特定代码 在 特定地址 上的承诺,以可重用和可移植的形式。
提出这个模式所要解决的确切问题如下:
今天没有办法在不可靠的各方下,保证特定代码在特定地址上的部署,除非依靠疯狂的多个事务黑客,这些黑客依赖于被迫先承诺特定 gas 价格的无密钥签名,这可能在你的用例中甚至需要几个月或几年的时间,如果你猜错了所有状态可能会被永久销毁。
上面的模式正好可以做到这一点:它提前承诺在需要代码之前将特定代码部署到特定地址。它不需要 “多事务黑客[s]”、“无密钥签名” 或 “对特定 gas 价格的先承诺[ments]”。可以对这个模式进行简单的修改,以支持不可信或任意方作为部署者。在初始承诺之后,不对涉及该地址的(a)同步的交互进行任何假设。
当这个特定模式被提出为解决方案时,回应并不是指出这确实是可能的,而是批评它的各种方面。
第一个批评是,任何人都可以调用 commit
方法并消耗承诺。这是正确的,commit
是一个任何人都可以调用的公共函数。然而,这可以通过任何身份验证方案来解决,但在将来这可能实际上是一个期望的属性。当我们实际上用这个模式构建一些东西时,我们将回到这一点。
第二个批评是构造函数可能需要参数。然而,由于我们将我们的承诺与所部署的代码紧密耦合,这是可能的,只需 稍加修改。
第三个批评是,每个想将来的合约都要一个特定的工厂。这是有效的,但再一次,这仅仅是一个原型。我们可以通过多种方式实现这一点,但最简单的方式是借用 知名的代理模式 来创建通用的承诺:
一个更通用的承诺
如你所见,承诺现在完全与要部署的代码解耦。 factory
是一个通用地址,回退函数将所有调用数据转发给工厂,允许调用工厂上的任意方法。在这个特定示例中,可以使用部署的 Factory
上的 getSig
的返回值作为调用回退函数时的 calldata。
但如果我们想进一步发展呢?如果我们只想承诺某个任意代码?结果证明,我们也可以做到这一点。事实上,在我研究的这一点上,我意识到我 并不是第一个想到这个模式的人(搜索“Counterfactual”)。
为了实现对任意代码的承诺目标,让我们把所有的部分组合在一起:
这是不是一个可爱的小东西
所以,现在我们的 MachineCommitment
只是对某个特定代码的承诺,借助一个可以为我们部署代码的通用 Deployer
合约。我们还可以确保被部署的代码(即 init_code
)就是最初承诺的代码。我们仍然有两个必需的部分:对特定地址的承诺和对特定代码的承诺。
但我们可以把它做得更好。我们实际上不需要一个部署者。使用上述的 delegatecall 方法可以节省一些 gas,但实际上并不是一个必需的功能,并且增加了复杂性。
相反,我们可以创建一个几乎本身就是 create2
的合约。实际上这非常简单:
如果你想在稍微干净的环境中尝试一下(无意冒犯 ethfiddle,它只是缺少几个功能),可以尝试访问 Remix IDE 并将代码放入其中。你可以部署自己的 Machine
来捕获 init 代码值,并尝试部署不同的 Create2
承诺。这可能会稍微简化(例如,通过移除 committedAddress
辅助函数)。此外,如果你想使用与上面提供的不同的 init_code
,则需要自行计算 codeHash
。
我还把这个最终形式放到 一个 gist 中,以防需要更新或修复。
到现在为止,我们已经缩小了 create
所能做的和 create2
提供的之间的差距。实际上,这两者之间的唯一区别就是需要在链上发布 Create2
承诺。
现在我们已经提炼出了一种模式,使我们能够在一个确定的地址上对任意代码进行承诺……让我们将其付诸实践。在我的下一篇文章中,我将用这个模式创建一些东西。我不确定是什么。它可能是一个简单的状态通道,可能是另一个彩票(哈哈),或者可能是更奇特的东西,比如混合器!并不是说混合器本身需要这个模式,但它可能还是很有趣的。
同样值得注意的是,这个模式的“最终形式”本身对任何这些应用都不是特别有用。实际上,我认为我们会发现,在实际实现中,我们最终将使用模式的某些早期修订,因为我们实际上会 希望 对特定的、可验证的代码进行承诺,因此不需要更通用的变体。
我非常希望听到 连贯的 反馈,关于这个模式无法做到的事情,而 create2
能够做到。如果你能以编程的方式甚至在某种具体的方式描述这些差异,那将是首选。我不感兴趣的是听到“我们需要 create2
为了 x
”,而不具体说明 create2
实际上的基本功能需求是什么。
显然的区别是 create2
允许承诺特定代码在特定地址上,而无需链上交易——这很酷,但似乎更像是一种优化而已。如果有人能描述为什么这 不仅仅是优化,我会很高兴听到这个。
- 原文链接: medium.com/coinmonks/why...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!