本文是关于确定性部署系列文章的第一部分,探讨了如何在多个区块链上以相同的地址部署合约。文章首先回顾了部署交易、初始化代码与运行时代码的区别、合约地址的派生方式以及CREATE操作码等基础概念,然后详细介绍了三种方法:私钥管理、尼克方法和预签名交易,并分析了它们的优缺点。
这是一系列关于确定性部署的第一篇文章:我们如何在多个链上以相同的地址部署合约?这个表面上简单的问题有许多可能的解决方案,新想法不断涌现。
在本文中,我们将介绍三种可能的方法:仔细管理私钥、使用 Nick 的方法或预签名交易。本系列的其余部分将涵盖其他方法,如 CREATE2 factories、CREATE3、ERC-7955 等。
在开始之前,让我们回顾一些理解其余讨论所必需的基本概念。如果你熟悉以下内容,请随意跳过本节:
部署交易如何工作
init code 和 runtime code 之间的区别
合约地址如何派生
CREATE opcode 的行为方式与部署交易类似
我们可以将以太坊交易分为两类:
具有接收者地址的交易。该接收者可以是 EOA1 或合约。如果是合约,则会执行其代码。我们称之为 普通交易。
没有接收者的交易。这些用于创建新合约,因此我们称之为 部署交易。
普通交易与部署交易
当我们使用普通交易调用合约时,会执行该合约的代码,并且交易中的 data 字段用作输入。在这种情况下,我们将合约代码称为其 runtime code。但是当我们发送部署交易时,会发生一些非常不同的事情:是 data 字段被执行。此执行的结果用作已部署合约的 runtime code2。在这种情况下,我们说部署交易的数据是合约的 init code。
Init code 与 Runtime code
强调这两种情况有多么不同很重要。当我们调用合约时,data 字段通常编码我们正在调用的函数和我们正在传递的参数。也就是说,它只是数据。但在部署交易中,data 是有效的 EVM 代码。
我们现在知道新创建合约的 runtime code 来自执行其 init code。但合约部署在哪里?它的地址由以下公式确定:
address = keccak256(rlp([sender, nonce]))[12:]
也就是说:我们对发送部署交易的账户的地址和 nonce 进行 RLP 编码,哈希结果,然后取该哈希的最后 20 字节。但细节并不那么重要。对我们而言重要的是,已部署合约的地址仅取决于发送者的地址和 nonce。
交易不是创建新合约的唯一方式。现有合约也可以进行部署。它们可以通过两种方式中的一种来做到这一点,即使用 CREATE opcode(另一种方式是使用 CREATE2 opcode,我们将在本系列的下一篇文章中讨论)。
对于本次讨论,部署交易和执行 CREATE opcode 之间没有太大区别:
两者都执行一些 init code,但 CREATE opcode 从当前执行的 EVM 内存中获取它,而不是从交易数据中获取。
在这两种情况下,创建的合约的地址都取决于部署者的地址及其 nonce。当合约进行部署时,会使用其地址和 nonce。这里唯一的细微之处在于,合约仅在部署合约时才增加其 nonce,并且其起始 nonce 为 1。
我们想要弄清楚如何在多个链上以相同的地址部署合约,并且我们解释说合约的地址仅取决于发送部署交易的账户的地址和 nonce。这意味着我们的问题有一个直接的解决方案:在每个链上使用相同的私钥部署合约,确保它是从该地址发送的第一笔交易(或第二笔,或第五笔;只要 nonce 始终相同,就没有关系)。
这是确定性部署问题最简单的答案,但它有几个缺点:
私钥需要永久妥善保管。如果你失去对其的访问权限,则无法在新链上部署到目标地址。
如果由于任何原因部署交易回滚,nonce 会被消耗,并且在该链上部署到所需地址的可能性将永远丧失。
部署不能由任何人完成。只有拥有私钥访问权限的人才能部署合约。
Nick 的方法是一种在不使用私钥的情况下创建有效交易的技术。要理解它是如何工作的,我们首先需要正确地了解交易由什么组成。
在我们的第一个图中,交易由如下对象表示:
{
"from": EOA,
"to": Foo,
"data": "0x1234...",
// ...other fields
}
虽然这是一个有用的简化,但它在技术上并不正确。更准确的表示是:
{
"to": Foo,
"data": "0x1234...",
// ...other fields...
"r": "0xabcd...",
"s": "0xfedc...",
"v": 27,
}
r/s/v 字段是交易签名的值。请注意我们已删除 from 字段:由于发送者地址可以从签名值中恢复,因此将其包含在交易中是多余和浪费的。
获取签名值的通常方法是编码交易并用私钥签名。但问题是:很容易使用任意 r/s/v 值并获得给定未签名交易的有效签名3。对应于此签名交易的 from 将是有效随机的,但如果我们向该地址注资,然后传播已签名交易,它将被挖出。
我们可以使用这种方法制造一个已签名的部署交易,并在多个链中传播它。已部署合约的地址在每个链上都将相同,因为发送者相同且 nonce 始终为 0(我们必须使用 nonce 为 0 的交易才能使其有效,因为发送者地址是有效随机的,完全不可能有非零的 nonce)。
这种方法,通常称为 Nick 的方法4,改进了上一节中管理私钥方法的两个缺点:
没有必要保护私钥安全,因为不涉及任何私钥。
由于制造的交易是公开的,任何人都可以通过资助发送者并传播交易,在新链中部署合约。
但该方法有几个缺点是以前的方法所没有的:
交易永远不能更改,因为那会使签名失效。这意味着所有字段都必须对每个可能的链都有效。如果,例如,gas 限制太紧,并且我们在 opcode 更昂贵的链中传播交易,那么执行将因 out-of-gas 错误而回滚。在那种情况下,部署到该地址的可能性将永远丧失。
这种方法可以看作是管理私钥方法的一个扩展,而不是独立的方法。我将其放在单独的一节中解释,因为在了解了 Nick 的方法之后,更容易理解它的权衡。
我们说过,管理私钥方法意味着永久保护私钥安全,并且只有有权访问私钥的人才能部署合约。但是,正如我们在上一节中看到的,只要是有效的,任何人都可以传播已签名交易。这意味着可以对部署交易进行签名(不带重放保护)并公开共享,以便任何人都可以在其他链中使用它。
在某种程度上,这种方法结合了前两种方法的最佳方面:
与 Nick 的方法一样,即使私钥丢失,任何人也可以进行部署。
与 Nick 的方法不同,如果预签名交易不起作用(例如,在强制执行重放保护的链中),我们可以使用管理私钥作为回退。
并且我们可以签名并共享多笔部署相同合约的交易。这对于拥有具有不同 gas 限制的预签名交易很有用6。
在本系列的下一篇文章中,我们将探讨确定性部署问题的另外两种方法:CREATE2 factories 和 CREATE3。你可以订阅此博客,以便在第二部分发布时收到通知。
Externally Owned Accounts。这些是没有代码且由私钥控制的账户。
更准确地说:如果执行不回滚,返回数据将包含用于已部署合约的 runtime code。例如,代码 0x620102035f526003601df3 返回 0x010203。如果你发送了一个带有该 init code 的部署交易,则会创建一个具有(无意义的)runtime code 0x010203 的合约。如果你想深入了解其工作原理,请查看这个 EVM playground。
r 和 s 值,我们有近 50% 的机会获得有效签名。然后可以从 r 和 s 派生 v。
↩请注意,这存在一种悲观攻击风险。如果具有最低 gas 限制的预签名交易在给定链上不起作用,那么有人可以资助发送者并传播它,使其因 out-of-gas 错误而回滚,从而阻止合约部署到目标地址。
- 原文链接: paragraph.com/@cethology...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!