冷槽与热槽:EVM 为什么给“第一次碰某个存储”额外收费 图 1
冷槽与热槽:EVM 为什么给“第一次碰某个存储”额外收费 · 图 1

同样一条 SLOAD,在有的位置只花一百出头的 gas,在有的位置要两千多——这不是计价错误,而是伦敦升级引入的定价规则:你这条交易第一次触碰某个账户的存储槽,付“冷价”;本轮之内再碰,只付“热价”。规则由 EIP-2929 定义,随 2021 年 8 月伦敦硬分叉进入以太坊主网。它不改变读到的值,改变的是“状态访问的账单从哪来”。要理解为什么值得这么设计,得先回到EVM 对状态的定义。

状态访问的隐形成本

以太坊状态是一张巨表:地址、余额、每个合约的每个存储槽。节点必须随时答得出“任意槽位此刻的值”(见 Patricia Trie 的树结构),代价是所有节点保存全量状态,状态膨胀被视作去中心化的头号慢性毒药。社区给这条路线的终点画过一张图——无状态以太坊:验证者不再随身带账本,每笔交易自带它触碰状态的凭证(witness)。既然终局要求“按被访问状态计价”,过渡期先把定价做粗:一条交易第一次碰某存储槽多收(因为验证者大概率要为此做真实工作),本轮再碰同一槽位边际成本接近零,就按热价。冷价大约相当于把“取一次外部数据”的服务费显性化。同批生效的还有对外部账户与代码的首访定价,思路同源。

预加热与可预测清单

执行前“算 warm 清单”的动作叫预加热:发送者自带的交易字段、目标账户与其代码,天然进清单。EIP-2930 访问列表 更进一步,允许发送者申报“我这笔会碰这些地址和槽位”,申报可享比现场冷价更低的费率,执行中确实访问就免重复费。这让 gas 从“靠经验估”向“按清单算”挪了一步,也解释了区块级访问清单 BAL 这类并行执行研究为何围绕清单做文章。对普通转账几乎无感,对重读写合约则是账单结构的变化:一次调用里“碰多少个不同槽位”开始比“读写多少次”更贵。

写给合约与用户的具体影响

合约侧:把常用变量塞进同几个槽、循环里避免对大数组全量 SLOAD、用连续槽位改善缓存局部性,都是被这条规则强化的老手艺;嵌套映射的每次“下钻”都要为新路径付冷费,所以把派生值算好存起来有时比现场重算便宜。用户侧:合约调用 gas 估算里开始出现“看似没多复杂却很贵”的账单,先分清是冷访问密集还是计算密集,工具上debug_traceCall 可以看到每条指令的 gas 去向;同一次故障里失败原因判读另见 交易失败提示。还要注意,同一条交易内首次与再次访问的价差,会让“重复调用同一函数”便宜得反直觉,别按第一次的价格外推长期使用成本。

快速问答

问:冷热是按区块还是按交易算?按交易:每条交易的 warm 清单重新初始化,上一条交易热过的事实不继承。问:合约代码第一次执行算冷吗?对,目标合约代码首访同样收冷费,外部库调用(delegatecall 到新地址)也是。问:为什么要给“重复访问”分价而不是一刀切?一刀切会惩罚循环读的正确写法,分价让 gas 更接近真实工作状态。问:和 Gas 退款机制怎么叠加?退款规则(Gas 退款)管存储清零的返还,与本条互不冲抵,只是各自计入总额。

常见误区

一是把“冷”理解成链下存储或某个节点没缓存——它是执行期记账状态,与具体硬件无关。二是以为冷费是为给 MEV 基础设施买单,它的动机是状态模型,与MEV 无关。三是把访问列表当作必选项,多数钱包和普通用户根本不需要碰它,它是给高频与批量场景的选项。

小结

EIP-2929 是“按状态访问量付账”原则第一次写进 EVM 账本:冷槽贵、热槽贱、清单可预申报。它让合约把存储布局当成性能设计,也让路线图多了一个可计价的路标——通往验证者不再背着全量状态的那一天。对今天的用户,它只是把账单变得更可解释:贵的不是操作次数,而是你惊动了多少沉睡的状态。

风险提示:本文只解释协议机制,不构成投资建议;具体 gas 数值随升级调整,以当期规范为准。