以太坊 - EOF - 函数

  • ethereum
  • 发布于 2025-03-11 14:17
  • 阅读 41

本文介绍了在EOF格式的字节码中实现多个代码段的能力,主要是通过引入新的操作码CALLFRETF来实现对函数的调用和返回。该EIP旨在消除动态跳转的需要,并通过编码输入和输出数量来提高代码分析的机会,同时限制每个函数的栈隔离。

摘要

引入在 EOF 格式的 (EIP-3540) 字节码中具有多个代码部分的能力,每个代码部分表示一个独立的子程序/函数。引入两个新指令,CALLFRETF,分别用于调用和返回此类函数。动态跳转指令是不允许的。

动机

当前,在 EVM 中,一切都是动态跳转。像 Solidity 这样的语言以静态方式生成大多数跳转(即,目标在跳转前推入栈中,PUSHn .. JUMP)。然而,不幸的是,大多数 EVM 解释器无法使用此方法,因为需要额外的验证/分析。这也限制了它们进行优化并可能降低跳转成本的能力。

EIP-4200 引入静态跳转指令,消除了对 大多数 动态跳转用例的需求,但并非所有情况都能通过它们解决。

本 EIP 的目标是消除需求并禁止动态跳转,因为它提供了这些跳转所用于的最重要的特性:调用和返回函数。

此外,它旨在通过对每个给定函数的输入和输出数量进行编码并隔离每个函数的栈(即,一个函数无法读取调用者/被调用者的栈),来改善分析机会。

规范

类型部分

EOF 容器的类型部分必须遵循以下要求:

  1. 此部分由一组元数据组成,其中元数据索引在类型部分对应于代码部分索引。因此,类型部分的大小必须为 n * 4 字节,其中 n 是代码部分的数量。
  2. 每个元数据项有 3 个属性:一个 uint8 inputs、一个 uint8 outputs 和一个 uint16 max_stack_height注意: 这暗示输入和输出的栈有 255 的限制。进一步限制为 127 的栈项,因为输入和输出字节的高位保留供将来使用(outputs == 0x80 在 EOF1 中已用于表示不返回的函数,如在单独的 EIP 中介绍)。max_stack_heightEIP-5450 中进一步定义。
  3. 第 0 个代码部分必须具有 0 个输入和 0 个输出。

参见 EIP-3540 以查看完整的良构 EOF 字节码结构。

EVM 中的新执行状态

引入了一个返回栈,与操作数栈分开。它是一个项目栈,代表函数执行结束后将返回的执行状态。每个项目由代码部分索引和代码部分中的偏移量(PC 值)组成。

注意:实现可以自由选择栈项的特定编码。在以下规范中,我们假设表示为两个无符号整数:code_section_indexoffset

返回栈的最大限制为 1024 项。

此外,EVM 会跟踪当前正在执行的部分的索引 - current_section_index

新指令

我们引入两个新指令:

  1. CALLF (0xe3) - 调用一个函数
  2. RETF (0xe4) - 从一个函数返回

如果代码是传统字节码,任何这些指令将导致 异常暂停。(注意:这意味着行为没有变化。)

首先我们定义几个辅助值:

  • type[i].inputs = type_section_contents[i * 4] - 第 i 个代码部分的输入数量
  • type[i].outputs = type_section_contents[i * 4 + 1] - 第 i 个代码部分的输出数量
  • type[i].max_stack_height = type_section_contents[i * 4 + 2:i * 4 + 4] - 第 i 个代码部分的最大操作数栈高度

如果代码是有效的 EOF1,适用以下执行规则:

CALLF

  1. 有一个立即参数,target_section_index,编码为 16 位无符号大端值。
  2. 注意: EOF 验证 EIP-5450 保证操作数栈有足够的项可以作为被调用者的输入。
  3. 如果操作数栈的大小超过 1024 - type[target_section_index].max_stack_height + type[target_section_index].inputs (即,如果被调用的函数可能超过全局栈高度限制),执行将导致异常暂停。这也保证了调用后的栈高度在限制内。
  4. 如果返回栈已经有 1024 项,执行将导致异常暂停。
  5. 费用为 5 个 gas。
  6. 不从操作数栈弹出任何内容,也不向操作数栈推送任何内容。
  7. 向返回栈推送一个项:

    (code_section_index = current_section_index, 
    offset = PC_post_instruction)

    PC_post_instruction 下,我们指的是 CALLF 的整个立即参数之后的 PC 位置。

    注意: EOF 验证 EIP-5450 保证在 CALLF 之后总是有一条指令(因终止指令或无条件跳转要求为该部分的最后一条指令),因此 PC_post_instruction 总是指向部分范围内的指令。

  8. current_section_index 设置为 target_section_index 并将 PC 设置为 0,然后在被调用的部分继续执行。

RETF

  1. 没有立即参数。
  2. 注意: EOF 验证 EIP-5450 保证操作数栈有确切数量的项可以作为输出。
  3. 费用为 3 个 gas。
  4. 不从操作数栈弹出任何内容,也不向操作数栈推送任何内容。
  5. 从返回栈弹出一项,并将 current_section_indexPC 设置为此项中的值。

注意: 对于第 0 个代码部分不返回的验证要求(单独 EIP 中介绍的不返回部分)保证在 RETF 之前返回栈不能是空的。

代码验证

除了上述容器格式验证规则外,我们扩展了代码部分验证规则(如 EIP-3670 中定义)。

  1. EIP-3670 的代码验证规则适用于每个代码部分。
  2. 如果任何 CALLF 的立即参数大于或等于代码部分的总数,则该代码部分无效。
  3. RJUMPRJUMPIRJUMPV 立即参数值(跳转目的地相对偏移)验证:
    1. 如果偏移指向部分范围外的位置,则代码部分无效。
    2. 如果偏移指向直接跟随 CALLF 指令的两个字节之一,则代码部分无效。
  4. 不允许有不可达的代码部分,即每个代码部分都可以通过一系列 CALLF / JUMPFJUMPF 在单独的 EIP 中介绍)指令从第 0 个代码部分访问(第 0 个代码部分总是可以访问)。

不允许的指令

动态跳转指令 JUMP (0x56) 和 JUMPI (0x57) 是无效的,其操作码未定义。

JUMPDEST (0x5b) 指令被重新命名为 NOP(“无操作”),且其行为未改变:它不会弹出任何内容,也不会向操作数栈推送任何内容,除了 PC 增加和收取 1 个 gas 外没有其他效果。

PC (0x58) 指令变为无效,其操作码未定义。

注意: 此更改意味着不再需要对 EOF 代码进行 JUMPDEST 分析。

执行

  1. 执行从第 0 个代码部分的第一个字节开始,PC 设置为 0。
  2. 返回栈初始化为空。
  3. 不再执行栈下溢检查。注意: EOF 验证 EIP-5450 保证在运行时不会发生。
  4. 不再执行栈上溢检查,除非在 CALLF 时,如上所述。

理由

顶层帧中的 RETF 结束执行 VS 异常暂停 VS 在验证期间不被允许

顶层帧中 RETF 的替代逻辑可能是允许在代码验证期间使用它,并使其:

  • 如果返回栈因 RETF 被清空而结束执行,或
  • 如果返回栈在 RETF 之前为空,则异常暂停。

这已被顶层帧(第 0 个代码部分)为不返回部分的验证规则所取代(不返回的部分在单独的 EIP 中介绍),因为验证函数的不返回状态本身出于其他原因是有价值的。因此,在顶层帧中,RETF 的所有运行时行为考虑已过时。

“最小”函数类型

让我们考虑一个只有单个指令 RETF 的简单函数。 这样的函数具有“最小”类型,inputs = 0, outputs = 0。 然而,任何其他类型如 inputs = k, outputs = k 对于这样的函数也是有效的。 曾考虑强制所有函数使用“最小”类型。 这需要额外的验证规则,以检查函数中的任何指令是否访问底部栈操作数。 此规则可以被编译器遵守,但会造成相当大的烦恼。 另一方面,它对 EVM 实现几乎没有好处。 最后,决定不强制遵守。

代码部分限制和指令大小

代码部分的数量限制为 1024。这要求 CALLF 需要 2 字节的立即数,并留有将来增加限制的空间。讨论了 256 的限制(1 字节的立即数),并提出了可能不足的担忧。

NOP 指令

我们不是废除 JUMPDEST,而是将其重新赋予 NOP 指令,因为 JUMPDEST 实际上是一个“无操作”指令,已经在各种上下文中被用作这样的用途。这对某些链下工具可能很有用,例如基准测试 EVM 实现(NOP 指令的性能是 EVM 解释器循环的性能),作为强制代码对齐的填充,作为动态代码组合中的占位符。

不再进行 JUMPDEST 分析

JUMPDEST 分析的目的是在代码中找到不恰好位于 PUSH 立即数中的有效 JUMPDEST 字节。只有动态跳转指令(JUMPJUMPI)要求目的地为 JUMPDEST 指令。相对静态跳转(RJUMPRJUMPI)不具有此要求,并在部署时 EOP 指令验证中验证。因此,在没有动态跳转指令的情况下,不再需要 JUMPDEST 分析。

向后兼容性

此更改对向后兼容性没有风险,因为它只针对 EOF1 合约引入,部署未定义指令是不允许的,因此没有现有合约使用这些指令。新指令未针对传统字节码引入(未格式化为 EOF 的代码)。

新的执行状态和多部分控制流程对向后兼容性没有风险,因为它是执行单个代码部分的广义化。执行现有合约(无论是传统合约还是 EOF1 合约)没有用户可观察的变化。

安全考虑

待补充

版权

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

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

0 条评论

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