代码和数据分家之后:EIP-7480给EOF容器配的读数指令 图 1
代码和数据分家之后:EIP-7480给EOF容器配的读数指令 · 图 1

以太坊的合约字节码长期是一段”裸奔”的字节流:代码里可以夹数据,运行时想读什么就 CODECOPY 什么,代码和数据在物理上从没分家。EOF(EVM Object Format)想结束这种状态——把容器分成正式代码段和数据段,加载时先做结构验证。EIP-7480在2023年8月提出的是这套新格式落到实处的关键一环:四条读数据段的新指令。它依赖EIP-3540和EIP-3670定义的容器与验证框架,整体随EOF系列提案停在Stagnant状态,从未上主网。

代码段与数据段为什么要分

EOF1的核心卖点之一是”代码即代码、数据即数据”。编译器可以把元数据、查找表、常量表放进数据段,代码段里只剩指令流,结构验证才敢做得更狠——跳转表可以静态完备、栈高度可以预计算。反过来,旧的 CODESIZECODECOPY 这类读自身字节码的指令在EOF1里被弃用,那么数据段就得有自己的读数工具,这就是7480的存在理由。指令命名延续了 RETURNDATACALLDATA 体系的四件套格式:读一个词、读一个编译期已知位置的词、问总长、拷贝一段。

四条指令的账本

DATALOAD0xd0)从数据段按动态偏移读32字节入栈,收4 Gas;越界部分按零填充。DATALOADN0xd1)不弹栈,偏移是编码在指令里的16位大端立即数,因为位置在部署时就固定、由代码验证保证不越界,运行时连边界检查都省了,只收3 Gas。DATASIZE0xd2)压入数据段长度,收2 Gas。DATACOPY0xd3)弹三个参数,把一段数据搬进内存,收3 Gas加每32字节3 Gas的搬运费,另按规则付内存扩张费,越界同样补零。对旧格式(历史字节码)执行这四条指令的结果是即时异常中止——也就是行为上和今天完全一样,不给老合约添任何变数。

这套设计的取舍

最值得注意的是DATALOADN的存在:它赌的是绝大多数数据读取的位置是编译器写死在代码里的,那就让验证器在部署时把关一次,运行期每条省下边界检查。这是EOF系列反复出现的思路——把运行时的不确定性提前到加载时消化。四件套的定价则刻意贴近calldata读数指令的既有档位,避免给某一类数据访问开出体系性的便宜。

快速问答

问:EOF到底装到主网了吗? 答:没有。EIP-3540定义的格式与后续系列提案一直停留在提案阶段,主网合约字节码仍是旧格式,本文四条指令不存在于任何已激活网络。 问:越界读取为什么补零而不是报错? 答:与calldata越界读同构,让编译器不必处处写边界分支;DATALOADN因为偏移已被静态验证,规格干脆禁止其越界。 问:这跟合约”源码”有什么关系? 答:数据段存的是编译产物的一部分,不是人类可读源码;验证合约逻辑仍以反编译与审计为准。

顺带一提四件套的命名法本身也是信息:EVM历史上凡是成组的读数需求——calldata、returndata、memory——都长成”动态读、静态读、问长度、拷一段”这四步,7480没有发明新范式,只是让数据段加入这个俱乐部。评审讨论里最常被问的一句”为什么不加一条跨容器读别人数据段的指令”,答案是当时的设计只保证读自己:跨容器读牵涉容器发现与验证语义,被有意留给后续提案。

一条直觉线

把两条路线摆在一起看更清楚。旧格式里,编译器把常量表藏在字节码尾部,运行时用 CODECOPY 把”代码中间那段其实是数据”的区域拷进内存再读——机器要区分”这串字节此刻是指令还是数据”全靠约定,静态分析器看见的永远是一锅粥。EOF路线则是先分家再装盒:数据住在专属数据段,读它必须走DATALOAD家族的正门,验证器在部署时就把”这段字节是指令、那段是数据”钉死。代价是旧指令被弃用、存量合约永远走不进新盒子;收益是所有工具第一次可以在执行之前回答”这个合约总共会读多少数据”这种问题。7480表面是四条读数指令,实际是这场分家运动的收银台——没有读数工具,分家就只是把仓库隔了堵墙却忘了开门。

风险提示:本文介绍未上线的字节码格式提案,不构成投资建议;不要因为读到某条”指令”就假设你的链上环境里存在它。