合约能不能知道自己是不是只读现场:EIP-2970的IS_STATIC 图 1
合约能不能知道自己是不是只读现场:EIP-2970的IS_STATIC · 图 1

EVM里有一种被叫作静态执行模式的规则:当合约被STATICCALL调进来时,整棵调用树里任何修改状态的动作都会立刻炸掉——写存储、发币、建合约、记日志,一律无效。这条规则服务于只读查询场景,比如估计一笔交易要花多少Gas。EIP-2970在2020年9月由Vitalik Buterin提出,想让合约有机会问一句:我此刻是在静态模式里吗?指令名字就叫IS_STATIC。提案停在Stagnant,动机段落却精准预言了两年后账户抽象要面对的麻烦。

静态模式的来历和边界

静态模式由EIP-2929之前的EIP-214与2929等规则逐步定型,核心逻辑是只读承诺:既然你答应不改状态,任何试图改状态的操作都按违规处理,而不是静悄悄跳过。它拦得住写操作,但拦不住合约知道自己在被模拟这一事实的反面——合约本身没有仪器可查。IS_STATIC就是补这台仪器:执行到这条指令时,静态上下文压1,否则压0。

账户抽象为什么需要区分现场

提案给出的主用例是把当时的EIP-2938(原生账户抽象草稿)往前推一步。设想普通地址也挂了一段验证代码,每个发给它的交易都要先跑一遍验证签名。麻烦在于:钱包和节点经常要在真实执行之前用静态调用预演一遍交易。预演环境里,验证代码里依赖链上即时状态的动作可能天然失败——比如签名校验恰好需要读一个预演中不存在的东西。如果合约能识别IS_STATIC,就可以在纯模拟场景放宽某类检查、在真实执行时严格把关,安全性一分不让,预演也不再误伤。

反对声音与替代路线

这条指令的争议点很直白:给合约开了一个知道自己在被观察的开关。一旦合法,静态模拟的结果就和真实执行系统性分叉,Gas估计的可靠性首当其冲;更微妙的是,恶意合约可以专门针对估计器行为做文章。所以支持这条指令的人提得很克制——提案把它限定为只影响外部调用的可见性,不改变静态调用本身能改什么。后来行业的主流路线走向别处:4337把预验证拆成独立的验证合约阶段,Gas估计阶段允许验证者返回宽限结果,等于在协议外围解决了同一道题。IS_STATIC因此更像一件留在工具箱里没上墙的扳手。

一条对照线

把三种执行场景摆在一起看得更清楚。普通外部调用:合约所见即生产现实,所有检查都该硬。STATICCALL链内的合约:写路径被焊死,但合约仍不知道自己在被读,只能凭经验猜测来者是不是估计器。装了IS_STATIC的合约:三种场景变成两分法——能改事的现场和只能看的现场,检查策略可以明写。缺失的其实不是能力,是一个标准化、无法伪造的现场标识。

一次估计的完整旅程

估计一笔交易时,钱包会构造一个仿真现场:从最近一个区块状态出发,用静态或近似静态的方式把交易跑一遍,数它烧掉的Gas。IS_STATIC要介入的正是这条流水线的中间环节——合约在被仿真时若无法自知,验证逻辑只能盲跑:链上缺一个仿真现场没有的字段,验证失败,钱包据此告诉你这笔交易上链会 revert,而真实世界里它其实能成。反方向更糟:仿真通过、真实执行因依赖了只有现场才有的信号而失败。一台可靠的现场标识仪能把这类假阴性与假阳性从估计误差表里整列划掉,这就是提案动机段说的:静态调用对外部账户抽象是安全的,但前提是账户代码知道自己在被静态对待。

快速问答

问:静态调用会执行收款逻辑吗? 答:外部合约收到静态调用时,它的代码照常跑,只是写状态的部分一触发就回滚。 问:和预验证有什么关系? 答:账户抽象的预验证阶段广泛使用静态语义,IS_STATIC的提案动机几乎全部围绕这一场景。

风险提示:本文描述技术提案,不构成任何投资建议。提案状态以EIP官网为准。