EIP-7873: EOF - TXCREATE 和 InitcodeTransaction 类型
向 EOF 添加 `TXCREATE` 指令以及配套的交易类型,允许从交易数据创建 EOF 合约
Authors | Piotr Dobaczewski (@pdobacz), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), Alex Beregszaszi (@axic), Danno Ferrin (@shemnon) |
---|---|
Created | 2025-01-31 |
Requires | EIP-3540, EIP-3670, EIP-3860, EIP-7620 |
Table of Contents
摘要
EVM Object Format (EOF) 移除了使用创建交易(带有空的 to
字段),CREATE
或 CREATE2
指令创建合约的可能性。我们引入一个新的指令:TXCREATE
,以及一个新的交易类型 (InitcodeTransaction
),以提供一种使用交易数据中的 EOF 容器创建合约的方法。
动机
此 EIP 使用来自 EIP-3540 的术语,该 EIP 引入了 EOF 格式。
创建交易和创建指令 CREATE
和 CREATE2
是传统 EVM 提供的部署新代码的方式,但根据移除代码可观察性的要求,它们不允许部署 EOF 代码。为了允许外部拥有账户 (EOA) 部署 EOF 合约,必须有一种方法可以使用交易数据中传递的字节码来创建 EOF 合约。
此外,此 EIP 中引入的新指令和交易类型使合约能够使用来自交易数据的 initcode 创建其他合约,这在传统 EVM 中是通过组合 CREATE
或 CREATE2
并从 calldata 加载 initcode 来实现的。
该机制补充了来自 EIP-7620 的 EOFCREATE
和 RETURNCODE
指令,因此传统 EVM 中可用的所有合约创建用例都为 EOF 启用。
由于 TXCREATE
不限于 EOF 容器,它也服务于将 EOF 合约引导到状态的目的。
规范
本文档中的关键词“必须”,“不得”,“必需”,“应”,“不应”,“推荐”,“不推荐”,“可以”和“可选”应解释为 RFC 2119 和 RFC 8174 中所述。
参数
常量 | 值 |
---|---|
INITCODE_TX_TYPE |
Bytes1(0x06) |
MAX_INITCODE_COUNT |
256 |
TX_CREATE_COST |
在 Ethereum Execution Layer Specs 中定义为 32000 |
STACK_DEPTH_LIMIT |
在 Ethereum Execution Layer Specs 中定义为 1024 |
GAS_CODE_DEPOSIT |
在 Ethereum Execution Layer Specs 中定义为 200 |
TX_DATA_COST_PER_ZERO |
在 Ethereum Execution Layer Specs 中定义为 4 |
TX_DATA_COST_PER_NON_ZERO |
在 Ethereum Execution Layer Specs 中定义为 16 |
MAX_CODE_SIZE |
在 EIP-170 中定义为 24576 |
MAX_INITCODE_SIZE |
在 EIP-3860 中定义为 2 * MAX_CODE_SIZE |
交易类型
引入新的交易 InitcodeTransaction
(类型 INITCODE_TX_TYPE
),它通过添加一个新字段 initcodes: List[ByteList[MAX_INITCODE_SIZE], MAX_INITCODE_COUNT]
扩展了 EIP-1559(类型 2)交易。
initcodes
只能通过 TXCREATE
指令(见下文)访问,因此 InitcodeTransactions
旨在发送到包括 TXCREATE
在其执行中的合约。
Gas 消耗计划
initcodes
项目数据消耗与 calldata 相同的 gas:InitcodeTransaction
的交易 gas 被扩展为包括 initcodes
中的 tokens 以及 calldata
中的 tokens。使用 EIP-7623 中的约定,交易 gas 的计算方式如下:
STANDARD_TOKEN_COST = 4
TOTAL_COST_FLOOR_PER_TOKEN = 10
tokens_in_calldata = zero_bytes_in_calldata + nonzero_bytes_in_calldata * 4
tokens_in_initcodes = 0
for initcode in initcodes:
tokens_in_initcodes += zero_bytes_in_initcode + nonzero_bytes_in_initcode * 4
tx.gasUsed = (
21000
+
max(
STANDARD_TOKEN_COST * (tokens_in_calldata + tokens_in_initcodes)
+ execution_gas_used,
TOTAL_COST_FLOOR_PER_TOKEN * (tokens_in_calldata + tokens_in_initcodes)
)
)
交易验证
- 如果
initcodes
中有零个条目,或者条目多于MAX_INITCODE_COUNT
,则InitcodeTransaction
无效。 - 如果
initcodes
中的任何条目的长度为零,或者任何条目超过MAX_INITCODE_SIZE
,则InitcodeTransaction
无效。 - 如果
to
为nil
,则InitcodeTransaction
无效。
在交易验证规则下,不对 initcodes
进行验证以使其符合 EOF 规范。仅在通过 TXCREATE
访问时才对其进行验证。这样可以避免潜在的 mempool DoS 攻击。如果在 InitcodeTransaction
的执行期间未调用任何 TXCREATE
指令,则该交易仍然有效。
其他支持合约创建的创建交易(特别是类型 0“Frontier”,类型 1“AccessList”,类型 2“FeeMarket”交易,其中 to
字段为空)将不会尝试解析其 input
字段中的 EOF 容器,并将代码作为非 EOF 代码执行。这将导致立即执行未定义的 0xEF
指令并停止。
RLP 和签名
给定 EIP-2718 中的定义,InitcodeTransaction
的 TransactionPayload
是以下内容的 RLP 序列化:
[chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, initcodes, y_parity, r, s]
TransactionType
为 INITCODE_TX_TYPE
,签名值 y_parity
、r
和 s
是通过对以下摘要构造 secp256k1 签名来计算的:
keccak256(INITCODE_TX_TYPE || rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, initcodes]))
此交易的 EIP-2718 ReceiptPayload
为 rlp([status, cumulative_transaction_gas_used, logs_bloom, logs])
。
执行语义
只要没有明确列出,EOF 合约创建规则以及 TXCREATE
指令应与 CREATE2
指令的规则相同或类似。这包括但不限于:
- 关于
accessed_addresses
的行为和地址冲突 (EIP-684 和 EIP-2929) - 为
TXCREATE
initcode 创建的 EVM 执行帧 - 内存,帐户上下文等。 - 新创建合约的帐户的 nonce 递增 EIP-161
- 用于创建捐赠 (
value
参数) 的余额检查和转移
在激活 EIP-3540 的相同区块号上引入一个新指令:TXCREATE
(0xed
)。
TXCREATE
- 扣除
TX_CREATE_COST
gas - 如果当前帧处于
static-mode
,则异常失败并停止。 - 从操作数堆栈中弹出
tx_initcode_hash
、salt
、input_offset
、input_size
、value
- 使用
[input_offset, input_size]
执行(并收取费用)内存扩展 - 从哈希到
tx_initcode_hash
的交易initcodes
数组中加载 initcode EOF 容器- 如果交易中不存在这样的 initcode,或者从
TransactionType
不是INITCODE_TX_TYPE
的交易调用,则失败(在堆栈上返回 0)- 不更新调用者的 nonce,并且不消耗 initcode 执行的 gas。
- 令
initcontainer
为该 EOF 容器,initcontainer_size
为其字节长度
- 如果交易中不存在这样的 initcode,或者从
- 检查当前调用深度是否低于
STACK_DEPTH_LIMIT
,并且调用者余额是否足以转移value
- 如果失败,则在堆栈上返回 0,则不更新调用者的 nonce,并且不消耗 initcode 执行的 gas。
- 验证 initcode 容器及其所有子容器(递归地)
- 与一般验证不同,此外还要求
initcontainer
在标头中声明的data_size
等于实际的data_section
大小。 - 验证包括检查
initcontainer
是否不包含RETURN
或STOP
- 与一般验证不同,此外还要求
- 如果容器无效,则失败(在堆栈上返回 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 sender
的 nonce 保持更新
- 如果执行
- 成功执行以 initcode 执行
RETURNCODE{deploy_container_index}(aux_data_offset, aux_data_size)
指令结束(请参阅 EIP-7620)。之后:- 在从中执行
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
请注意,期望实现缓存当前交易执行期间的容器验证结果,因此每个容器验证的成本已充分包含在 InitcodeTransaction
固有成本(initcodes 费用)中。
原理
TXCREATE
失败模式
TXCREATE
在 initcontainer 不存在以及 EOF 验证不成功的情况下有两种“轻量级”失败模式。考虑了一种替代设计,其中这两种情况都导致“硬”失败(消耗所有可用的 gas)。我们决定采用更精细和更宽容的失败模式,以便使产生的 gas 成本与 EVM 执行的实际工作对齐。
允许在传统 EVM 中使用 TXCREATE
EOF 合约创建需要一种特殊的可能性,即在传统代码中调用 EOF 操作码 - TXCREATE
,因为否则传统合约和创建交易都无法部署 EOF 代码来引导。替代方法是继续使用传统创建机制,要么仍然依赖于从内存中获取 initcode 并且不满足代码非可观察性的总体要求,要么滥用传统创建交易机制,要么引入一个预部署的创建者合约到状态中。
这也使得 EIP-7698 (EOF - Creation transaction) 不再是将 EOF 合约部署到链上的基本要求。该 EIP 可以从 EOFv1 中删除并撤回。
新的地址哈希方案
TXCREATE
使用方案 new_address = keccak256(0xff || sender32 || salt)[12:]
,与 EOFCREATE
指令相同。是否将 initcontainer 哈希包含到 salt 中的决定留给 TXCREATE
调用者。有关详细原理,请参阅 EIP-7620。
EOF 创建交易与部署模式
依赖 EOF 创建交易作为替代解决方案使得智能合约钱包无法部署任意 EOF 合约(只有 EOA 可以)。同时,这是一种当前传统创建规则允许的用例,这要归功于 CREATE
和 CREATE2
指令。一种解决方法是首先将这些任意 EOF 合约“上传”到工厂合约,然后使用 EXTDELEGATECALL
-EOFCREATE
序列进行部署,这非常昂贵,因为它需要将部署的合约放在链上两次。因此,此 EIP 中提出的方法与账户抽象 (AA) 路线图更加兼容,在 AA 路线图中,智能合约钱包应与 EOA 具有同等的功能。
最重要的是,依赖于基于 nonce 的哈希方案来获取新创建合约的地址,就像 EOF 创建交易的情况一样,将阻止 EOF 合约以确定性的跨链地址进行反事实部署。TXCREATE
指令的引入开箱即用地支持这一点。可以编写 ERC 来提供 toehold 合约,这些合约将满足部署模式,例如无盐部署和将发送者的地址作为 salt 的一部分进行哈希处理。
在现有交易类型中处理带有 0xEF00
前缀的代码。
三种现有的交易类型(类型 0 “Frontier”,类型 1 “AccessList”,类型 2 “FeeMarket”)在某些配置中接受代码作为其输入数据的一部分。此代码可以以 EF
开头,因为它是一个 initcode,而不是已部署的合约。
处理这些交易中潜在 EOF 容器的一种可能方法是,如果他们试图将 EOF 容器作为 initcode 执行,则使它们无效。具体来说,如果输入数据以 0xef00
开头,并且 to
字段设置为 nil,表示合约创建交易,则该交易无效。这将使包含这些交易的区块无效,而在包含此 EIP 的分叉之前,它们将是有效的。
区块构建者会看到一个影响,因为他们需要确保无效的交易不包含在区块中。对此有先例,因为 EIP-7623 为交易中的 gas 限制建立了一个新的下限,在其采用之前,该限制较低。但是,区块构建者对 gas 限制和交易进行了现有的检查,并且该更改正在调整公式和常量。
如果 EIP-7702 委托指定显示为合约创建交易中的代码,我们也应该考虑对其进行处理。该交易有效,作为普通字节码执行,并且执行的第一个操作是 0xEF
,这是一个无效的操作码,会导致整个交易中止。
让类型 0、1 和 2 合约创建交易的 输入数据 作为普通 EVM 代码执行,而不管魔术 0xEF00
字节如何, 都能 保持客户端代码中的一致行为,并防止区块构建者必须更新其旧交易类型的逻辑。
向后兼容性
此更改不会对向后兼容性构成风险,因为它是在与 EIP-3540 同步引入的。尽管为传统字节码(未进行 EOF 格式化的代码)引入了新指令,但有意义的合约不太可能无意中以正式有效的操作数执行 0xed
指令,并无意中导致它运行 EOF initcode(这也需要使用 InitcodeTransaction
,否则 initcode 查找将失败)。
将 TXCREATE
指令引入传统 EVM 不会影响 JUMPDEST
分析,因为该指令没有直接参数。
在此更改激活之前,新类型的交易无效。
合约创建选项不会为传统字节码更改,包括当 calldata 中遇到可能看起来像 EOF 容器的代码时,现有 to: nil
交易的行为方式。
安全注意事项
需要讨论。
版权
通过 CC0 放弃版权和相关权利。
Citation
Please cite this document as:
Piotr Dobaczewski (@pdobacz), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), Alex Beregszaszi (@axic), Danno Ferrin (@shemnon), "EIP-7873: EOF - TXCREATE 和 InitcodeTransaction 类型 [DRAFT]," Ethereum Improvement Proposals, no. 7873, January 2025. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7873.