Alert Source Discuss
⚠️ Review Standards Track: Core

EIP-3670: EOF - 代码验证

在部署时验证 EOF 格式的字节码的正确性。

Authors Alex Beregszaszi (@axic), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast)
Created 2021-06-23
Requires EIP-3540

摘要

为 EOF 格式 (EIP-3540) 的合约引入合约创建时的代码验证。拒绝包含截断的 PUSH 数据或未定义指令的合约。传统的字节码(非 EOF 格式的代码)不受此更改的影响。

动机

目前现有的合约不需要正确性验证,EVM 实现可以决定如何处理截断的字节码或未定义的指令。此更改旨在将代码有效性引入共识,从而更容易推断字节码。此外,EVM 实现可能需要更少的路径来确定哪个指令在当前执行上下文中有效。

如果希望在不升级 EOF 版本的情况下引入新指令,则已部署的未定义指令可能会破坏此类合约,因为某些指令可能会更改其行为。拒绝部署未定义的指令允许引入新的指令,无论是否升级 EOF 版本。

EOF1 向前兼容性

EOF1 格式提供以下向前兼容性属性:

  1. 可以为先前未分配的操作码定义新指令。这些指令可能具有立即数。
  2. 强制性的 EOF 部分可以变为可选。
  3. 可以引入新的可选 EOF 部分。它们可以相对于先前定义的部分以任何顺序放置。

规范

此功能与 EIP-3540 在同一区块启用,因此每个兼容 EOF1 的字节码都必须根据这些规则进行验证。

  1. 之前已弃用的指令 CALLCODE (0xf2) 和 SELFDESTRUCT (0xff),以及 EIP-3540 中弃用的指令,均无效且其操作码未定义。(注意:根据单独的 EIP 规范,EOF 中有更多指令被弃用和拒绝)
  2. 在合约创建时,对 EOF 容器的每个代码段执行代码验证。如果以下任何检查失败,则代码无效。对于每个指令:
    1. 检查操作码是否已定义。INVALID (0xfe) 被认为是已定义的。
    2. 检查所有指令的立即数字节是否存在于代码中(代码不在指令中间结束)。

理由

立即数数据

允许 PUSH 指令的隐式零立即数数据会给 EVM 实现带来低效率,而没有任何实际用例(EVM 无法观察到代码结尾处 PUSH 指令的值)。此 EIP 要求所有立即数字节都显式存在于代码中。

拒绝弃用的指令

valid_opcodes 列表中删除已弃用的指令 CALLCODE (0xf2) 和 SELFDESTRUCT (0xff),以防止将来使用它们。

BLOCKHASH 指令

BLOCKHASH 指令已由 EIP-2935 中引入的系统合约很好地替代。但是,尽管引入了替代方案,但此操作码尚未被弃用。此操作码在 EOF 中仍然有效,以便与传统字节码区分开来。

向后兼容性

此更改不会对向后兼容性构成任何风险,因为它与 EIP-3540 同时引入。验证不包括传统字节码(非 EOF 格式的代码)。

测试用例

合约创建

每个案例都应通过将 EOF 容器提交到 EOF 合约创建(如单独的 EIP 中所述)进行测试。应通过将代码放置在不同索引的代码段中来测试每个案例。

有效代码

  • 包含 INVALID 的 EOF 代码
  • 包含未定义指令的字节的数据段的 EOF 代码

无效代码

  • 包含未定义指令的 EOF 代码
  • 以不完整的 PUSH 指令结尾的 EOF 代码

参考实现

# 以下范围由上海的执行规范指定。
# 注意:range(s, e) 不包括 e,因此 +1
shanghai_opcodes = [
    *range(0x00, 0x0b + 1),
    *range(0x10, 0x1d + 1),
    0x20,
    *range(0x30, 0x3f + 1),
    *range(0x40, 0x48 + 1),
    *range(0x50, 0x5b + 1),
    0x5f,
    *range(0x60, 0x6f + 1),
    *range(0x70, 0x7f + 1),
    *range(0x80, 0x8f + 1),
    *range(0x90, 0x9f + 1),
    *range(0xa0, 0xa4 + 1),
    # 注意:0xfe 被认为是已分配的。
    0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xfa, 0xfd, 0xfe, 0xff
]

# 删除此处以及 EIP-3540 中弃用和拒绝的操作码
rejected_in_eof = [
    0x38, 0x39, 0x3b, 0x3c, 0x3f, 0x5a, 0xf1, 0xf2, 0xf4, 0xfa, 0xff
]
valid_opcodes = [op for op in shanghai_opcodes not in rejected_in_eof]

immediate_sizes = 256 * [0]
immediate_sizes[0x60:0x7f + 1] = range(1, 32 + 1)  # PUSH1..PUSH32


# 对无效代码引发 ValidationException
def validate_instructions(code: bytes):
    # 请注意,EOF1 已经通过代码段要求断言了这一点
    assert len(code) > 0

    pos = 0
    while pos < len(code):
        # 确保操作码有效
        opcode = code[pos]
        if opcode not in valid_opcodes:
            raise ValidationException("undefined opcode")

        # 跳过立即数数据
        pos += 1 + immediate_sizes[opcode]

    # 确保最后一个指令的立即数不会超出代码结尾
    if pos != len(code):
        raise ValidationException("truncated immediate")

安全考虑

参见 EIP-3540 的安全考虑

版权

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

Citation

Please cite this document as:

Alex Beregszaszi (@axic), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast), "EIP-3670: EOF - 代码验证 [DRAFT]," Ethereum Improvement Proposals, no. 3670, June 2021. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-3670.