自毁是合约的逃生舱,也是别人的定时炸弹
SELFDESTRUCT(编号 0xFF)是以太坊最早期的操作码之一:合约执行它即可注销自身。对单个合约这是升级与止损工具,但对被众多合约共用的公共库来说,“它随时可能消失”本身就是系统性风险——调用方无法验证”这段代码将永远在这里”。2020 年代初期,围绕怎么约束这条指令的提案排成一列,有主张直接停用或改名的,有主张加税的。2020 年 9 月 4 日维塔利克独自署名的 EIP-2937 走了一条相反的路:不碰这条指令分毫,给合约一个自愿焊死自己逃生舱的选项。

一个操作码加一个交易级名单
2937 的设计只有几行:新增 SET_INDESTRUCTIBLE 操作码,编号 0xA8,基础 Gas 成本;执行时把当前被调用合约加进一个交易范围内生效的全局名单;此后在这笔交易的任何执行帧里,名单内合约再调 SELFDESTRUCT 直接抛异常。想表忠心的合约只需把自己的第一条字节码放成 0xA8——链上 anyone 都能验证这一事实,等于对全体依赖方签了”代码永存”的保证书。提案特意强调:它不改变任何现存合约的行为,只新增一个自愿选项。
为什么不禁令而选自愿
提案的 Rationale 记录了两次路线分歧。一是为什么不直接全面封杀 SELFDESTRUCT:那是全网级破坏性改动,要替所有存量合约的升级路径负责,牵连太广。二是为什么名单不做成合约内部变量而是交易级全局变量:内部状态会被 DELEGATECALL 击穿——代理调用时执行的是别人的存储,合约自己的布尔锁形同虚设,交易级名单则跟着执行上下文走,绕不开。这个细节后来被反复引用,说明”自愿不可毁”要经得起代理模式的检验。
事故、现状与提案的尾声
库合约的自毁能力酿成过大事故:2017 年 Parity 多签钱包库合约因初始化漏洞被夺取所有权后自毁,一批钱包当场冻结,完整复盘见 一次自毁冻住一整批钱包:EIP-999 与 Parity 库合约事故。社区对自毁的整体处置走的是另一条线:EIP-6780 在 2024 年 3 月的 Dencun 升级生效,自毁不再转移资产,只有在同一笔交易内部署的合约才真正注销代码,历史沿革可看 EIP-6780后合约还能自毁吗?。2937 的 Security Considerations 里那句”如果未来 SELFDESTRUCT 被移除,本提案自然作废”如今听起来像预言——它本身则停在 Stagnant 状态,从未激活。
它和状态租金的隐性合同
2937 还留了一段今天读仍有趣的权衡:不可毁承诺会与某些状态治理方案冲突。若以太坊将来用”状态租金”之类机制定期清理年久失修的账户,焊死自毁的合约在规则上多占了一层便宜——提案明确说这会与”部分”此类方案的前向兼容性相抵,但对另一些方案(提案点名了 ReGenesis 思路)无碍。换句话说,自愿锁死逃生舱的合约同时也替全网锁掉了一点未来清理状态的自由度。选不选这个选项,本质是”可用性承诺”与”可演进性”的对价。
代理升级与”不可变”宣称的错位
2937 想锁的那扇门,现实里最常见的开法其实更隐蔽。多数现代合约的”可升级”不靠自毁,而靠代理模式:用户地址永远指向一层薄代理,业务逻辑放在后面的实现合约里,换实现就是换大脑。这类合约在语义上从没自毁过,SET_INDESTRUCTIBLE 对它们毫无约束力——焊死逃生舱的前提是逃生舱存在且用的是那扇门。于是出现一种宣传与事实的错位:项目方说”合约不可篡改”,实际指的是”不会自毁”,而代码地址随时可能在文档更新日悄悄换掉。这也是为什么 2937 即使落地,能担保的范围也远小于用户以为的”这个地址的代码永远不变”。想验证这一层,普通用户可行的办法是查该地址的代理特征与官方文档声明的升级流程,而不是相信任何一句口号。风险提示:本文为提案与机制分析,不构成投资建议;具体合约行为以链上字节码为准。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。