EIP-3670: EOF - 代码验证
在部署时验证 EOF 格式的字节码的正确性。
Authors | Alex Beregszaszi (@axic), Andrei Maiboroda (@gumb0), Paweł Bylica (@chfast) |
---|---|
Created | 2021-06-23 |
Requires | EIP-3540 |
Table of Contents
摘要
为 EOF 格式 (EIP-3540) 的合约引入合约创建时的代码验证。拒绝包含截断的 PUSH
数据或未定义指令的合约。传统的字节码(非 EOF 格式的代码)不受此更改的影响。
动机
目前现有的合约不需要正确性验证,EVM 实现可以决定如何处理截断的字节码或未定义的指令。此更改旨在将代码有效性引入共识,从而更容易推断字节码。此外,EVM 实现可能需要更少的路径来确定哪个指令在当前执行上下文中有效。
如果希望在不升级 EOF 版本的情况下引入新指令,则已部署的未定义指令可能会破坏此类合约,因为某些指令可能会更改其行为。拒绝部署未定义的指令允许引入新的指令,无论是否升级 EOF 版本。
EOF1 向前兼容性
EOF1 格式提供以下向前兼容性属性:
- 可以为先前未分配的操作码定义新指令。这些指令可能具有立即数。
- 强制性的 EOF 部分可以变为可选。
- 可以引入新的可选 EOF 部分。它们可以相对于先前定义的部分以任何顺序放置。
规范
此功能与 EIP-3540 在同一区块启用,因此每个兼容 EOF1 的字节码都必须根据这些规则进行验证。
- 之前已弃用的指令
CALLCODE
(0xf2) 和SELFDESTRUCT
(0xff),以及 EIP-3540 中弃用的指令,均无效且其操作码未定义。(注意:根据单独的 EIP 规范,EOF 中有更多指令被弃用和拒绝) - 在合约创建时,对 EOF 容器的每个代码段执行代码验证。如果以下任何检查失败,则代码无效。对于每个指令:
- 检查操作码是否已定义。
INVALID
(0xfe) 被认为是已定义的。 - 检查所有指令的立即数字节是否存在于代码中(代码不在指令中间结束)。
- 检查操作码是否已定义。
理由
立即数数据
允许 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.