Alert Source Discuss
⚠️ Review Standards Track: Core

EIP-7620: EOF 合约创建

引入 `EOFCREATE` 和 `RETURNCODE` 指令

Authors Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Andrei Maiboroda (@gumb0), Piotr Dobaczewski (@pdobacz)
Created 2024-02-12
Requires EIP-170, EIP-684, EIP-2929, EIP-3540, EIP-3541, EIP-3670

摘要

EVM Object Format (EOF) 移除了使用 CREATECREATE2 指令创建合约的可能性。我们引入了一种新的/替代方法,以 EOFCREATERETURNCODE 指令对的形式,提供一种使用 EOF 容器创建合约的方式。

动机

此 EIP 使用了 EIP-3540 中的术语,该 EIP 引入了 EOF 格式。

EOF 的目标是移除代码可观察性,这是使用传统风格创建交易、CREATECREATE2 的传统 EVM 合约创建逻辑的先决条件,因为 initcode 和 code 都可以被 EVM 访问和操作。基于相同的原则,EOF 移除了像 CODECOPYEXTCODECOPY 这样的操作码,引入了 EOF 子容器作为替代方案,以满足创建其他合约的工厂合约的需求。

此 EIP 中引入的新指令在 EOF 容器上运行,从而实现了传统 EVM 拥有的工厂合约用例。

规范

本文档中的关键词“必须”,“不得”,“必需”,“应”,“不应”,“建议”,“不建议”,“可以”和“可选”应解释为 RFC 2119 和 RFC 8174 中所述。

在未明确列出的地方,EOF 合约创建的规则以及 EOFCREATE 指令应与 CREATE2 指令的规则相同或类似。这包括但不限于:

  • accessed_addresses 上的行为和地址冲突 (EIP-684EIP-2929)
  • EOFCREATE initcode 创建的 EVM 执行帧 - 内存,帐户上下文等。
  • 新创建合约的帐户的 nonce 递增 EIP-161
  • 用于创建捐赠的余额检查和转移(value 参数)

参数

常量
TX_CREATE_COST Ethereum Execution Layer Specs 中定义为 32000
STACK_DEPTH_LIMIT Ethereum Execution Layer Specs 中定义为 1024
GAS_CODE_DEPOSIT Ethereum Execution Layer Specs 中定义为 200
MAX_CODE_SIZE EIP-170 中定义为 24576

我们引入了两个新的指令,它们与 EIP-3540 在同一区块号上激活:

  1. EOFCREATE (0xec)
  2. RETURNCODE (0xee)

如果代码是传统字节码,则任何这些指令都会导致异常停止。 (注意:这意味着行为没有改变。

执行语义

  • 指令 CREATECREATE2 已过时,并且在 EOF 合约中被验证拒绝。它们仅在传统合约中可用。
  • 如果指令 CREATECREATE2 具有 EOF 代码作为 initcode(以 EF00 魔数开头)
    • 部署失败(在堆栈上返回 0)
    • 调用者的 nonce 不会更新,并且不会消耗 initcode 执行的 gas

在传统字节码执行的上下文中,任何这些指令(EOFCREATERETURNCODE)都会导致异常停止。 (注意:这意味着行为没有改变。

新合约创建流程概述

在 EOF EVM 中,通过 InitcodeTransaction 传递 EOF 容器 (initcontainer),将新字节码引入到状态中。这样的容器可以包括任意深度嵌套的子容器。initcontainer 及其子容器根据适用于所讨论的 EOF 版本的各种验证规则进行递归验证。接下来,执行 initcontainer 的第 0 个代码段,并且可能最终调用 RETURNCODE 指令,该指令将引用要最终部署到地址的子容器。

InitcodeTransactionsEIP-7873 中详细定义。

EOFCREATE 指令反过来替代了 CREATECREATE2 传统指令,允许工厂合约创建其他合约。与 InitcodeTransaction 的主要区别在于,选择 initcontainer 作为调用 EOFCREATE 的 EOF 容器的子容器之一(而不是 transaction.initcodes 之一)。值得注意的是,此时不执行任何验证,因为在部署包含 EOFCREATE 的工厂合约时已经执行了验证。

有关每个指令的详细信息,请参见以下各节。

EOFCREATE

  • 扣除 TX_CREATE_COST gas
  • 如果当前帧处于 static-mode,则异常失败而停止。
  • 读取立即数操作数 initcontainer_index,编码为 8 位无符号值
  • 从操作数堆栈中弹出 saltinput_offsetinput_sizevalue
  • 使用 [input_offset, input_size] 执行(并收取费用)内存扩展
  • 在执行 EOFCREATE 的容器中,加载 initcontainer_index 处的 initcode EOF 子容器
    • initcontainer 为该 EOF 容器
  • 检查当前调用深度是否低于 STACK_DEPTH_LIMIT 以及调用者余额是否足以转移 value
    • 如果失败,则在堆栈上返回 0,调用者的 nonce 不会更新,并且不会消耗 initcode 执行的 gas。
  • 调用者的内存切片 [input_offset:input_size] 用作 calldata
  • 执行容器并扣除执行 gas。来自 EIP-150 的 63/64 规则适用。
  • 递增 sender 帐户的 nonce
  • new_address 计算为 keccak256(0xff || sender32 || salt)[12:],其中 sender32 是发送者地址,左侧用零填充为 32 字节
  • initcode 执行不成功会导致将 0 推送到堆栈上
    • 如果执行 REVERT,则可以填充 returndata
  • 成功执行以 initcode 执行 RETURNCODE{deploy_container_index}(aux_data_offset, aux_data_size) 指令结束(请参见下文)。在那之后:
    • 在执行 RETURNCODE 的容器中,加载 deploy_container_index 处的部署 EOF 子容器
    • 将数据段与 (aux_data_offset, aux_data_offset + aux_data_size) 内存段连接起来,并在需要时更新标头中的数据大小。
    • 如果更新后的部署容器大小超过 MAX_CODE_SIZE,则该指令会异常中止
    • state[new_address].code 设置为更新后的部署容器
    • new_address 推送到堆栈上
  • 扣除 GAS_CODE_DEPOSIT * deployed_code_size gas

RETURNCODE

  • 读取立即数操作数 deploy_container_index,编码为 8 位无符号值
  • 从操作数堆栈中弹出两个值:aux_data_offsetaux_data_size,指向将附加到已部署容器的数据的内存段
  • 消耗 0 gas + aux 数据的可能内存扩展
  • 结束 initcode 帧执行并将控制权返回给 EOFCREATE 调用者帧,在该帧中,deploy_container_indexaux_data 用于构造已部署的合约(请参见上文)
  • 如果在附加之后,数据段大小将溢出最大数据段大小或下溢(即小于标头中声明的数据段大小),则该指令会异常中止

代码验证

我们扩展了代码段验证规则(如 EIP-3670 中所定义)。

  1. EOFCREATE initcontainer_index 必须小于 num_container_sections
  2. EOFCREATE initcontainer_index 指向的子容器的 len(data_section) 必须等于 data_size,即数据段内容与标头中声明的大小完全相同(请参阅 数据段生命周期
  3. EOFCREATE initcontainer_index 指向的子容器不得包含 RETURNSTOP 指令
  4. RETURNCODE deploy_container_index 必须小于 num_container_sections
  5. RETURNCODE deploy_container_index 指向的子容器不得包含 RETURNCODE 指令
  6. 容器同时包含 RETURNCODERETURNSTOP 是一种错误
  7. 子容器从未在其父容器中被引用是一种错误
  8. 给定的子容器同时被 RETURNCODEEOFCREATE 引用是一种错误
  9. RJUMPRJUMPIRJUMPV 立即数参数值(跳转目标相对偏移量)验证:如果偏移量指向紧跟在 EOFCREATERETURNCODE 指令之后的字节,则代码段无效。

数据段生命周期

对于尚未部署的 EOF 容器data_section 只是部署后最终 data_section 的一部分。 让我们将其定义为 pre_deploy_data_section 并将 pre_deploy_data_size 定义为该容器的标头中声明的 data_sizepre_deploy_data_size >= len(pre_deploy_data_section),这表示在部署过程中将更多数据附加到 pre_deploy_data_section

pre_deploy_data_section
|                                      |
\___________pre_deploy_data_size______/

对于已部署的 EOF 容器,最终的 data_section 变为:

pre_deploy_data_section | static_aux_data | dynamic_aux_data
|                         |             |                  |
|                          \___________aux_data___________/
|                                       |                  |
\___________pre_deploy_data_size______/                    |
|                                                          |
\________________________data_size_______________________/

其中:

  • aux_data 是在 RETURNCODE 指令上附加到 pre_deploy_data_section 的数据。
  • static_aux_dataaux_data 的子范围,其大小在 RETURNCODE 之前已知,并且等于 pre_deploy_data_size - len(pre_deploy_data_section)
  • dynamic_aux_dataaux_data 的其余部分。

已部署容器标头中的 data_size 将更新为等于 len(data_section)

总而言之,在最终数据段中有 pre_deploy_data_size 个字节,这些字节保证在部署 EOF 容器之前存在,并且只有在之后才知道存在的 len(dynamic_aux_data) 个字节。 这会影响数据段访问指令的验证和行为:DATALOADDATALOADNDATACOPY,请参见 EIP-7480

理由

数据段附加

在合约创建期间附加数据段,并且还需要在标头中更新其大小。考虑了替代设计,其中:

  • 引入了数据段的附加段类型
  • 引入了描述子容器的附加字段
  • 将会覆盖数据段而不是附加到数据段,因此需要在部署之前用 0 字节填充

所有这些替代方案要么使原本简单的数据结构复杂化,要么带走了有用的功能(例如数据段的动态大小部分)。

new_address 哈希方案中的 keccak256(initcontainer)

最初提出 new_address = keccak256(0xff || sender || salt || keccak256(initcontainer))[12:] 作为计算新创建合约地址的方式,类似于 CREATE2 使用的方式,但不完全相同。

但是,这种替代方法与代码不可观察性背道而驰,因为它锁定了 initcontainer 的内容,例如,阻止了在将来的升级中对其进行重写。而且,它似乎不必要地昂贵:EOFCREATE 只能选取其子容器之一,但是每次执行 EOFCREATE 都需要重新计算哈希值。

考虑了其他消除代码可观察性的方法,但保留了某种形式的代码见证。但是,仅保留 sendersalt 允许工厂合约的实现者(即包含 EOFCREATE 指令的合约)无论如何都通过 salt 包含代码见证,如果这对于特定用例是必要的。因此,保持 new_address 公式最小化是最灵活的方法,并具有更好的关注点分离。

new_address 哈希中删除 keccak256(initcontainer) 还具有使 new_address 独立于元数据段(在单独的 EIP 中为 EOF 提议)的优势,这是一个期望的属性。不幸的是,如果工厂想要选择提交到特定的 initcontainer,则需要将其包含在 salt 中,这将包括元数据段。

EOFCREATE 堆栈参数顺序

与传统指令 *CALL 相比,EIP-7069 中的 EXT*CALL 指令已更改了其堆栈参数顺序。我们遵循相同的更改,以使 EOFCREATE 堆栈参数顺序与 EXTCALL 的顺序匹配。

向后兼容性

此更改不会对向后兼容性构成任何风险,因为它与 EIP-3540 同时引入。新指令不会为传统字节码(非 EOF 格式的代码)引入,并且传统字节码的合约创建选项不会更改。

带有 EF00 initcode 的 CREATECREATE2 调用会尽早失败,而不会执行 initcode。以前,在这两种情况下,initcode 执行都会开始并在第一个未定义的指令 EF 上失败。

测试用例

创建交易,CREATECREATE2 不能使其 code0xEF 开头,但这些情况已经在 EIP-3541 中涵盖。但是,必须添加新的用例,其中 CREATECREATE2initcode 是(有效或无效地)EOF 格式的:

Initcode 预期结果
0xEF initcode 开始执行并失败
0xEF01 initcode 开始执行并失败
0xEF5f initcode 开始执行并失败
0xEF00 CREATE / CREATE2 尽早失败,返回 0 并保持发件人 nonce 不变
0xEF0001 如上
有效的 EOFv1 容器 如上

安全注意事项

EOF InitcodeTransaction(在 EIP-7873 中指定)需要详细的审查和讨论,因为这是外部未验证的代码进入状态的地方。除其他外:

  1. 其复杂性是否受到控制,从而排除了任何 DoS 尝试
  2. 它是否已正确定价并始终收费
  3. 验证是否全面并且不允许将有问题的代码保存到状态中

版权

版权及相关权利通过 CC0 放弃。

Citation

Please cite this document as:

Alex Beregszaszi (@axic), Paweł Bylica (@chfast), Andrei Maiboroda (@gumb0), Piotr Dobaczewski (@pdobacz), "EIP-7620: EOF 合约创建 [DRAFT]," Ethereum Improvement Proposals, no. 7620, February 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7620.