EIP-1052 在 2020 年初随伊斯坦布尔升级激活,给 EVM 添了一条读取任意地址代码哈希的指令 EXTCODEHASH,合约从此可以便宜地探问你是什么。四年后的 2024 年 2 月 26 日,一份署名 Jame 的提案 EIP-7637 指出这条指令埋着一个反直觉的角落:一个没有代码但有余额的普通地址,返回的不是零,而是空代码的哈希值。提案要求把这种情况改回 0x,依赖 EIP-1052,今天状态 Stagnant。
三条线拼出的现行语义
理解这份提案,先摆清规则。EXTCODEHASH 对完全不在状态里的地址返回 0;对存在但代码为空的地址——包括拿到过余额的普通钱包地址、以及只收到过钱还没部署的预派生合约地址——返回对空字节串做 keccak256 得到的那个固定值;对合约则返回各自代码的哈希。设计意图很明确:用一个 32 字节的读数区分不存在、空壳与有码三种状态,省掉读取整段代码的开销。在此之前,开发者习惯用代码长度或余额字段拼出同样的判断。

作者为什么认为这是个坑
提案的论证有两层。实用层面:正因为空壳地址返回的是非零哈希,链上代码很难放心用代码哈希做存在性判断——作者举了 Uniswap V2 的例子,这类合约用存储里登记的地址做校验,按现行规则只能绕开 EXTCODEHASH 的判断路径。安全层面:不少开发者凭直觉写判断,以为哈希为零等于不是合约,却不一定知道给一个预派生地址转入任意小额资金,就足以把它的返回值从 0 翻成空串哈希——两种状态之间的切换只需要一笔转账,任何人都能按这个开关。提案进一步主张,正因为这个坑,真实世界里大家只敢用代码长度为零来判断合约与否,EIP-1052 想省的开销一点没省下来,初心落空。
改动本身的形状很小:当地址无代码但余额非零时,返回值强制为 0。兼容性一栏写得坦白:用代码哈希间接判断一个地址有没有余额,这种用法从此不可用。CREATE 与 CREATE2 的地址可以提前算出,是地址预派生玩法的根基;一个还没部署却先收到零钱的地址,恰好站在 0 与空串哈希的裂缝上,这份提案想填平它。
为什么它只是 Stagnant
提案没有正面回答一个更硬的问题:归零之后,合约世界用什么便宜地回答这个地址存在吗?余额与代码两个维度的信息会被压回同一个读数,状态区分度反而变少。文本的参考实现只贴了一段执行层规范的改法草图,gas 影响、对状态树编码的连锁讨论都缺席;而一条改返回值的指令,牵动的是每个依赖账户状态承诺的客户端与索引工具。区分度的损失换便利的收益,账算不平,提案就停在原地。
快速问答
问:现在真实跑起来,一个有余额的 EOA 的 codehash 是什么? 答:按现行语义,它是空代码哈希那个固定值;从未存在过的地址才是 0。
问:给未部署的 CREATE2 地址转一笔钱,会改变它的 codehash 吗? 答:在现行规则下会——地址从不存在变为存在且无代码,返回值随之翻转。这正是提案瞄准的行为。
问:7637 动的是 gas 吗? 答:不是,它只改返回值语义,读取费用不变。
一条判断线
评估语义修订类提案,先数它动了谁的依赖:字段含义变化、边界条件翻转,还是新增状态区分;再对照客户端的状态模型看传播半径。EIP-7637 的改动看似一行条件分支,传播半径却覆盖所有账户读取路径——这类提案的评审成本从来不在代码量上。
常见误区
一是把空代码的 keccak 哈希当成零哈希,两者在不少判断里效果近似,语义完全不同;二是以为 1052 之前没有存在性检查,读代码长度一直是常规武器;三是把作者的攻击叙事读成已发生的事故——提案没有援引任何具体损失案例,那是设计推理而非事件报告。
风险提示:本文讨论处于搁置状态的提案及其作者的论证,不构成投资建议;现行行为以执行层规范与客户端文档为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。