prefetch预取合约:EIP-7650想让合约自己点名要用的数据 图 1
prefetch预取合约:EIP-7650想让合约自己点名要用的数据 · 图 1

prefetch预取合约:EIP-7650想让合约自己点名要用的数据

以太坊的Gas账单里藏着一个容易被忽略的事实:同一份链上数据,第一次碰和第二次碰的价格差二十多倍。EIP-2929把状态访问分成冷与热两类,地址冷访问两千六百Gas、热访问一百Gas,存储槽冷加载两千一百Gas、热加载一百Gas。这个设计的初衷是防止攻击者用廉价指令反复拖慢节点,代价是合约如果访问模式分散,会反复付冷价。EIP-7650(2024年3月10日创建,提案文本状态为Stagnant)给出的解法很直白:既然访问列表可以在交易层面声明,为什么合约不能在代码里自己声明?它提议增加一个叫prefetch的预编译合约,让合约在执行开头就把后面要用的地址和存储槽一次性报上去。

冷热访问的钱花在哪

先回忆EIP-2929的规则。EVM在执行每条交易时维护两个集合:已访问地址集合与已访问存储槽集合。碰到一个不在集合里的地址或槽,按冷价收费并把它加进集合;之后的访问按热价。集合在交易结束时清空,下一条交易从零开始。对多数合约来说这套规则足够合理,但有一类场景明显吃亏:一个函数要读写六七个互不相邻的存储槽,再调用两三个外部合约,每个目标都是第一次碰,全部按冷价计费。用户付的Gas里有一大块并不是计算本身,而是节点去状态库里做第一次查找的成本。EIP-2930已经在交易类型里支持携带访问列表,让外部调用者预付冷访问费、换交易内这些位置的hot待遇,但那是交易发起者的工具,合约作者控制不了。

prefetch的输入编码与计费

EIP-7650把同样的能力下沉到合约内部。预编译合约的输入编码为:先三十二字节写明本地存储槽数量n,紧跟n个三十二字节的存储槽;再三十二字节写明地址数量m,紧跟m个地址。调用发生时,提案规定按每个未访问存储槽两千一百Gas、每个未访问地址两千六百Gas计费,公式里除以一个并发度参数并向上取整,含义是鼓励客户端并行读盘、把真实磁盘成本摊薄。已经热的条目不重复收费。计费完成后这些条目直接进入两个已访问集合,函数后半段真正读写它们时按热价结算。

提案文档里用UniswapV2的swap函数做了示意:函数开头先prefetch四个存储槽(两个代币地址槽、储备量槽和两个价格累计槽),再prefetch两个代币合约地址,随后主逻辑里的所有读写都变成热访问。按示意数字粗算,光冷启动附加费一项就能省下上万Gas——五个冷项从冷价合计降到接近热价,差额就是预取的价值。这只是示意性算术,实际收益取决于函数真实的访问结构。

它和访问列表的分工

两者的差别在于谁知道未来要碰什么。外部访问列表适合发起者:调用路径可预测、发起者愿意多花一笔固定成本换执行费折扣。prefetch适合合约作者:库函数、代理转发、多资产池这些调用方看不透的内部结构,只有写代码的人最清楚接下来要读哪些槽。一个在交易层,一个在字节码层,机制上互补而非替代。对普通用户来说,能感知的变化是:支持这种写法的协议,同样一次交互的Gas估算会更低、也更稳定。

快速问答

问:prefetch现在能用吗? 答:不能。截至本文撰写时,EIP-7650在EIP仓库中的提案文本状态为Stagnant,主网不存在这个预编译地址。合约里写类似语法只是编程语言的设想语法,编译器并不会把它编译成一条真实指令。

问:预取是白拿的吗? 答:不是。每个还没热起来的条目在prefetch时就要按冷价付账,之后访问才按热价。它没有消灭冷费,只是把冷费从分散的访问点集中到函数开头,并给了并行读盘一个折价可能。

问:对Layer2有意义吗? 答:逻辑同样适用。Sequencer执行交易时一样按这套Gas规则记账,预取能降低L2上的执行费构成中的状态访问项。

一条判断线

评估这类提案的标准只有一个:省下的状态查找费,是否值得多一条指令和一个预编译地址的协议复杂度。prefetch的收益在访问模式复杂、调用方无法预判的协议里最大;对简单转账和单槽合约,预取的账单和直接访问几乎一样。看EIP时先问机制省的是哪一项成本,再看自己关心的场景里这项成本占多大比重,比看提案编号的顺序更可靠。

风险提示:本文仅解释协议提案机制,不构成任何投资建议。EIP状态以官方仓库为准,提案不等于主网激活。