返回值必须先进内存吗?EIP-7877 的四种 RETURN 图 1
返回值必须先进内存吗?EIP-7877 的四种 RETURN · 图 1

EVM 里有一处细节长期被合约开发者当成“过路费”:函数要返回一段数据,这段数据必须先被写到内存(更准确说是 EVM 的易失性内存区),RETURN 才能把它交出去。如果数据本来就躺在内存里,没问题;但如果它其实在持久存储、在 EIP-1153 引入的瞬态存储、或者在上一条外部调用留下的返回缓冲区里,合约就得先跑一段循环,把数据一格一格搬进内存,白白付出内存扩容费和逐条指令费。EIP-7877 想把这笔搬运费省掉。

方案:给 RETURN 分地点

提案引入三条新指令并把现有指令改名。MRETURN(操作码沿用原 RETURN 的 0xf3)语义完全不变,从内存返回,行为与 Cancun 时点一致。SRETURN(0xf6)从持久存储返回:弹出起始槽号和槽数,直接返回这些槽里的数据;按每槽 32 字节的粒度取数,和 SLOAD 的粒度对齐,编译器和内联汇编都好处理;长度参数为零时只返回起始槽那一个值。TRETURN(0xf7)对瞬态存储做同样的事。RRETURN(0xf8)从已有的返回缓冲区取数——典型场景是把上一次调用的返回数据原样或截断转发,即代理与包装合约最常见的转发路径。Gas 计价的思路是“等于你手动搬运的账面成本再优化掉循环”:以 SRETURN 为例,按触及槽的冷热计价,冷槽 2100、热槽 100,这正是 EIP-2929 体系下 SLOAD 的价格结构。

返回值必须先进内存吗?EIP-7877 的四种 RETURN 图 2
返回值必须先进内存吗?EIP-7877 的四种 RETURN · 图 2

动机里藏着的账

最典型的收益来自回调模式:合约把回调要用的数据临时放在瞬态存储(因为它便宜且不写状态),回调发生时旧写法要把这些字节搬回内存。搬运本身要动内存扩容费加上循环里的读写指令,数据一大就很可观。有了 TRETURN,一次指令直接按槽交出去。存储路径同理,只是价格更高、场景更少——多数返回数据来自计算结果而非持久存储。

状态与判断姿势

EIP-7877 创建于 2025 年 1 月 31 日,依赖 EIP-6、EIP-1153、EIP-3855(分别对应退栈字节、瞬态存储与 PUSH0 一族上下文),当前状态是 Stagnant——推进停滞、等待推动者接手,它不是任何一次已激活升级的一部分,主网上你仍然只能用从内存返回的 RETURN。读这类提案,把它记成“设计已成型但无人推进”最准确;如果哪天实现进展改变,以 EIP 仓库的状态字段为准,而不是任何二手文章。

一段字节账

拿一次 64 字节的回调返回举例:旧写法里,数据在瞬态存储占两个槽,返回前要两条 TLOAD 加两条 MSTORE 把内容推进内存,再付一次内存扩容,循环展开后指令数是六条量级、还随长度线性增长;新写法里一条 TRETURN 弹出槽号与长度就结束,指令数恒定两条以内。差别在小金额时是噪音,在每笔交易被调用几百次的热点合约里就是每天真金白银的 Gas 账单。这也是为什么该提案的受益面集中在回调、代理与大批量结算脚本,而非常规的读写页面逻辑。

与相邻提案的关系

这类“给指令开新入口”的提案不是孤例:同一年里还有想把返回数据缓冲区复用的其他条目,以及把存储读取直接并入跳转判断的思路,它们共享同一套成本核算语言(冷热槽价格、内存扩容公式),也因此常常在评审中互相引用、互相竞争优先级。理解这一点有助于读路线图:EVM 的效率路线图近年明显偏向“让编译器少搬数据”而非“让单条指令更猛”,从 PUSH0、瞬态存储到这批 RETURN 变体,都是同一条思路的延长线。

快速问答

问:现有 RETURN 会被删除吗? 答:提案将其重命名为 MRETURN 并保持语义与价格不变,属于别名化处理。

问:代理合约最受益的是哪条? 答:RRETURN——转发调用返回数据是代理的日常,现在这步也要先经内存。

问:为什么按 32 字节槽为单位? 答:与存储读写的原生粒度一致,避免按字节寻址带来的部分读写计价难题。

风险提示:本文仅解释虚拟机指令与提案状态,不构成投资建议;Gas 相关表述以你实际使用的客户端与升级版本为准。