「合约代码不可变」是智能合约最被反复引用的一句话,但它曾是靠默认行为维持的惯例,而非白纸黑字的规则。EIP-684 把它变成硬约束:不论通过创建交易、CREATE、CREATE2 还是任何其他方式发起合约创建,只要目标地址已经有非零 nonce 或非零代码长度,这次创建必须抛错,效果等同于 initcode 的第一个字节就是非法操作码;并且这条规则追溯适用于所有既有区块。提案 2023 年 3 月 20 日创建,作者列名 Vitalik Buterin 与 Renan Rodrigues de Souza,现为 Final 状态。
漏洞长什么样
在规则明确之前,规范文字没有禁止「在已有代码的地址上创建合约」。如果实现真的允许,剧本是这样的:攻击者诱导某个工厂把合约部署到一个恰好已有代码的地址,按「创建覆盖」的读法,原地址上的字节码会被新 initcode 的产物直接替换——一个已经收储、签名、绑定过一堆约定的地址,从此执行另一套逻辑。Rationale 部分直指核心:智能合约的基本信条是代码不随时间改变,若算力足够者能以「创建」名义改写任意地址的代码,余额可以被转走,预言机可以被换脑。现实里主流实现早已各自拦截这种情形,EIP-684 的工作是把这个共识写进规范,堵住「按规范实现」的理论分支。
追溯适用为什么可能
追溯条款是这条提案的技术支点。EIP-161 状态清理之后,被创建合约账户的 nonce 从一起步,于是「nonce 为零且代码为空」几乎不可能出现在有历史的地址上,碰撞检查因此不可能误伤既有合约。提案特意强调「追溯适用于所有既有区块」,就是在宣示:这不是某个区块号之后的新行为,而是对协议一直应有的行为的澄清——任何历史块重放这条规则都应当通过。后一个担心来自真实的边角:EIP-161 之前部署的少数合约 nonce 停在零,2024 年的后继提案 EIP-7610 因此把「存储非空」追加为第三个碰撞条件,补上「nonce 零、代码空、存储却有值」这一类历史遗物,两者叠加后规则才算密不透风。
对部署工具链的实际含义
规则落地后的世界很好辨认:撞地址的部署不会「静默换壳」,而是一开始就 revert,工厂交易失败、gas 照烧。对开发者,它把地址推导(CREATE 用发送方地址加 nonce、CREATE2 用盐值)变成可以安心依赖的承诺:只要按规则推导,预测地址与实际代码一一对应。安全侧的启示同样直接——合约的不可变性来自协议,而不是来自部署者的善意;审计时不需要再问「这个地址会不会被覆盖」,但需要问「工厂用哪套推导、nonce 状态如何」,因为地址计算规则本身(以及 EIP-161 的历史差异)才是撞车风险真正所在。
一次算错的地址推演
用一组具体参数理解碰撞检查怎么工作。普通账户 A 首次部署合约:新地址由 A 的地址与 nonce 零共同推导;部署成功后 A 的 nonce 变一,第二次部署得到另一个地址——同一路径不可能产出重复目标。真正的边角在合约派合约:工厂合约用 CREATE 派生子合约时用的是工厂自身 nonce,若某个工厂在创世预置时 nonce 恰好为零(EIP-161 之前的历史遗留),理论上存在「不同派生链撞进同一地址」的理论剧本。684 与 7610 叠加后,这些剧本统一收敛为一次 revert:宁可部署失败,不许地址换壳。审计清单上因此多一条确定性检查:任何声称「地址可预测且不可变」的设计,隐含依赖的就是这两条规则。
快速问答
问:它和 EIP-7610 是什么关系? 答:7610 在 684 的两条件(nonce 为零、代码为空)上追加「存储必须为空」,把 EIP-161 之前的历史合约遗物也纳入保护;两者都要求以硬分叉/协商一致的方式落地。
问:追溯适用意味着要重新同步全链吗? 答:不是。追溯说的是语义上规则对历史块的判定成立,实践上主流实现早已如此行为,对正常交易不产生可见变化。
问:普通用户什么时候会碰到这条规则? 答:几乎只在部署阶段:合约工厂返回地址预测却部署 revert 时,第一嫌疑人就是目标地址已被占用的碰撞检查。
风险提示
本文为协议规则解读,不构成投资建议,也不构成对任何合约安全性的保证。条款细节以提案仓库与客户端实现为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。