EIP-7834: 为 EOF 分离元数据部分
为以太坊对象格式 (EOF) 引入一个新的独立的元数据部分,该部分无法被代码访问,并且对其进行的任何更改都不会影响代码。
Authors | Kaan Uzdogan (@kuzdogan), Marco Castignoli (@marcocastignoli), Manuel Wedler (@manuelwedler) |
---|---|
Created | 2024-12-06 |
Requires | EIP-3540 |
摘要
为以太坊对象格式 (EOF) 引入一个新的独立的元数据部分,该部分无法被代码访问,并且对其进行的任何更改都不会影响代码。
动机
出于各种原因,希望在合约的字节码中包含元数据。例如,Solidity 和 Vyper 编译器默认包含用于编译的语言和编译器版本。Vyper(使用 0.4.1)在 CBOR 编码中将完整性哈希附加到 initcode。Solidity 还会包含 Solidity 合约 metadata.json 文件的 IPFS 或 Swarm 哈希,以及实验性的 Solidity 标志。当前(pre-EOF)的做法是在合约的运行时字节码中附加此 CBOR 编码的元数据部分,后跟 CBOR 编码的字节的 2 字节长度。
Solidity ┌──────────────────────────────────────────0x0033 bytes──────────────────────────────────────────────┐
...7265206c656e677468a2646970667358221220dceca8706b29e917dacf25fceef95acac8d90d765ac926663ce4096195952b6164736f6c634300060b0033
这给源代码验证带来了一个问题,即链上字节码与给定源代码的已编译字节码进行比较。在合约验证期间,需要忽略元数据部分,特别是 IPFS 哈希,并且只应比较可执行字节码。由于 pre-EOF 字节码不是结构化的,因此无法轻松地区分元数据部分与可执行字节码。在具有多个嵌套字节码的工厂合约中,每个字节码都有自己的元数据部分,这种情况变得更加棘手。验证器需要实现自己的启发式方法和解决方法来查找元数据部分并忽略它。
EOF 通过将代码与数据分离,并将每个合约的代码放置在各自的容器中,从而为字节码带来结构。就目前的形式而言,这使得查找数据比 pre-EOF 字节码更容易。但是,当前的规范也没有描述元数据部分。编译器目前需要将合约元数据放置在数据部分中,这带来以下几个问题:
- 无法直接区分
data_section
中的元数据部分,这会带来与 pre-EOF 字节码相同的问题。 - 对数据部分中元数据大小的任何更改都将更改可执行字节码,例如,通过移动
DATALOADN
偏移量。这样,具有不同元数据大小的两个相同合约在源代码期间将不匹配,因为代码将不同。 - 理论上,代码可以通过操作
DATALOADN
指令来访问元数据。
规范
本文档中的关键词 “MUST”,“MUST NOT”,“REQUIRED”,“SHALL”,“SHALL NOT”,“SHOULD”,“SHOULD NOT”,“RECOMMENDED”,“NOT RECOMMENDED”,“MAY” 和 “OPTIONAL” 应按照 RFC 2119 和 RFC 8174 中的描述进行解释。
扩展 EIP-3540 中介绍的格式,本 EIP 建议在 data_section
之前在正文中添加一个新的 OPTIONAL 部分,称为 metadata_section
,并在 kind_data
和 data_size
字段之前的标头中添加两个新的 OPTIONAL 字段 kind_metadata
(值:0x05
) 和 metadata_size
。
container := header, body
header :=
magic, version,
kind_type, type_size,
kind_code, num_code_sections, code_size+,
[kind_container, num_container_sections, container_size+,]
[kind_metadata, metadata_size,]
kind_data, data_size,
terminator
body := types_section, code_section+, container_section*, [metadata_section], data_section
types_section := (inputs, outputs, max_stack_height)+
标头
name | length | value | description |
---|---|---|---|
… | … | … | … |
kind_metadata | 1 byte | 0x05 | 元数据大小部分的类型标记 |
metadata_size | 2 bytes | 0x0001-0xFFFF | 16 位无符号大端整数,表示元数据部分内容的长度 |
kind_data | 1 byte | 0xff | 数据大小部分的类型标记 |
data_size | 2 bytes | 0x0000-0xFFFF | 16 位无符号大端整数,表示数据部分内容的长度 (*) |
terminator | 1 byte | 0x00 | 标记标头的结尾 |
主体
name | length | value | description |
---|---|---|---|
… | … | … | … |
metadata_section | variable | n/a | 任意字节序列 |
data_section | variable | n/a | 任意字节序列 |
metadata_section
的结构和编码不由本 EIP 定义。它留给编译器、工具或合约开发者来定义编码和内容。Solidity 和 Vyper 编译器目前的做法是使用 CBOR 编码。
原理
body
中的 metadata_section
以及 header
中的 kind_metadata
和 metadata_size
字段都是 OPTIONAL 的。这样,如果编译器不想写入任何元数据,则可以避免容器中出现额外的字节。data_section
的大小和内容可能会在部署期间发生变化,因此即使数据为空,也需要将其设置为 REQUIRED。预计 metadata_section
在部署期间不会发生变化。
将 metadata_section
放置在 data_section
之前,并将 kind_metadata
的值分配为 0x05
(而不是 0x04
)的原因是为了使现有的 EOF 工具更容易适应更改。此外,如果将 metadata_section
放置在 data_section
之后,则在部署时对 data_section
的更改将导致 metadata_section
移动。通过将 metadata_section
放置在前面,可以缓解这种情况。
向后兼容性
预计不会出现向后兼容性问题,因为 EIP-3540 尚未实现。
安全考虑
没有安全方面的考虑,因为该部分不打算执行。
版权
在 CC0 下放弃版权及相关权利。
Citation
Please cite this document as:
Kaan Uzdogan (@kuzdogan), Marco Castignoli (@marcocastignoli), Manuel Wedler (@manuelwedler), "EIP-7834: 为 EOF 分离元数据部分 [DRAFT]," Ethereum Improvement Proposals, no. 7834, December 2024. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-7834.