EIP-8246 深度解析:从 EVM 中彻底移除 SELFDESTRUCT 的 ETH 销毁语义
【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs
导读
本文以以太坊改进提案 EIP-8246(Remove SELFDESTRUCT Burn) 为唯一主题,全面拆解该提案如何在 EIP-6780 的基础上,清除SELFDESTRUCT指令残存的最后一类 ETH 销毁路径,使 ETH 不再存在任何离开总供应量的 EVM 机制。读完本文,你将理解同交易内自销毁(selfdestruct-to-self)与交易终结时余额保留的精确状态变更规则、主网回放数据支撑的动机论证、完整的规范与测试矩阵,以及CREATE2重部署、空账户清理(EIP-161)与地址碰撞(EIP-684)等周边共识规则之间的相互作用。
提案背景:SELFDESTRUCT 的两次语义收缩
SELFDESTRUCT(操作码0xff)是 EVM 中历史最悠久、语义最重的指令之一。它一次性地执行"转走全部余额 + 清除代码与存储 + 重置 nonce + 删除账户"的复合操作,因此也是 EVM 实现、共识规范和测试套件中长期需要特判处理的来源。
该指令的收敛经历了三个阶段,EIP-8246 正是这条演进路径的终点:
- EIP-6049(弃用声明):在 eip-6049.md 中,社区正式弃用该操作码,明确警告其行为可能在未来发生破坏性变更,任何新部署合约都强烈不建议使用。
- EIP-6780(同交易限定):eip-6780.md(Final)规定:只有当合约在创建它的同一笔交易内执行
SELFDESTRUCT时,才保留"删除账户"的旧行为;其他情况下指令只负责把余额转给受益人,不再删除任何数据。同时,由于 EIP-3529 已移除SELFDESTRUCT的 gas 返还,EIP-2929 则规定受益人不在accessed_addresses集合中时需额外收取COLD_ACCOUNT_ACCESS_COST,其冷/热地址计价规则保持适用。 - EIP-8246(移除销毁):即本文主角。它把 EIP-6780 残留的"同交易创建 + 自销毁"场景下的 ETH 销毁行为一并移除,让
SELFDESTRUCT从此不再销毁任何 ETH。
动机:残留销毁行为几乎无人使用,却持续带来特判成本
EIP-6780 落地后,ETH 仍能被销毁的场景被压缩到极窄的两类,且都要求合约在创建它的同一笔交易内执行SELFDESTRUCT:
- 受益人就是执行账户自身(selfdestruct-to-self,自销毁);
- 合约执行
SELFDESTRUCT后,在同一笔交易内又通过CALL(或再次SELFDESTRUCT,可多次)收到额外 ETH,在交易终结时这部分余额被销毁。
提案作者给出的主网全量回放数据极具说服力(来源:eip-8246.md):
| 区间 | 自销毁/销毁事件数 |
|---|---|
| 创世 ~ 约 25M 区块(全量回放) | 通过同交易路径的销毁仅2 次(Cancun 之后),交易终结时销毁余额0 次 |
| Cancun(EIP-6780 生效)之前的历史 | 自销毁合计54 次 |
也就是说,这次要移除的残留行为,比 EIP-6780 已经移除的行为还要罕见。既然连"部分移除"都只影响了极少数交易,这里提出的"完整移除"所影响的交易只会更少。与此同时,保留这一残存特例却迫使 EVM 实现、规范和测试持续为其维护特殊分支,EIP-8246 主张直接从源头删掉它。
一个具有象征意义的推论随之而来:ETH 从此失去最后一个可离开总供应量的 EVM 机制——销毁只可能发生在协议层以外的场景(如地址误用),EVM 内不再有"烧币"指令。
规范:余额保留 + 状态清空的两段式设计
EIP-8246 对SELFDESTRUCT的行为修改精确分为两条:
1. 同交易自销毁不再销毁余额。当SELFDESTRUCT在合约创建的同笔交易内执行,且受益人地址就是执行账户自身时,该账户的余额保持不变。
2. 交易终结时,被标记自销毁的账户不再被删除,而是被改写为:
- nonce 重置为 0;
- 余额保持不变;
- 代码被清除;
- 全部存储被清除。
两条关键注释约束了边界:
- 其余行为一律不变:所有未被本条覆盖的
SELFDESTRUCT行为(如受益人转账、gas 规则、EIP-2929 的冷地址附加费)维持现状。 - 空账户自动回收:若改写后余额恰好为 0,则该账户符合 EIP-161 定义的empty条件(无代码、零 nonce、零余额),会在交易终结时被从状态中删除。
与 EIP-161 空账户规则的衔接
理解第二条必须回到 eip-161.md(Spurious Dragon 硬分叉,随 eip-607.md 引入)。该提案确立了三条核心不变量:
- 空账户(无代码、零 nonce、零余额)被视为dead,
CALL向其转账不再收取 25000 gas 的新建账户费用; - 任何操作不得将账户从"不存在"变更为"存在但为空";
- 交易结束时,所有被touched且为空的账户必须被删除。
EIP-8246 的终结时改写流程正是设计为与这套规则无缝协作:被改写后余额为零的账户是"空"的,由 EIP-161 的既有机制回收,不需要新增删除逻辑;余额非零的账户则作为"仅余额账户(balance-only account)"保留下来。
设计理由:在源头移除销毁,而非四处打补丁
EIP-8246 的设计哲学是在行为源头移除销毁,而不是在交易终结时对"销毁"这个结果再做一层专门处理。具体到 Rationale(eip-8246.md):
- 保留状态清空效果:对同交易创建的合约,
SELFDESTRUCT仍然执行"清代码、清存储、重置 nonce"的效果,账户最多以"仅余额"形态存活。这让"ETH 凭空从状态中消失"的特例被消除,同时账户在交易终结后依然不可执行。 - nonce 归零保证 CREATE2 可用:重置 nonce 为 0,确保未来在相同地址执行
CREATE2不会被一个"仅余额账户"挡住。 - 克制的最小变更:曾考虑过"完整保留账户(含 nonce、代码、存储)"的替代方案——它同样能消除销毁,但等于对整个
SELFDESTRUCT进行"缴械",语义变更幅度过大。EIP-8246 只移除销毁行为,不触碰其余语义。
向后兼容性:硬分叉级变更与生态影响
由于修改的是共识规则,本 EIP 需要一次硬分叉。兼容性影响在 eip-8246.md 中逐层展开:
对部署合约的影响
- 过去,在同笔交易内创建后立即自销毁的合约,其余额(无论是自销毁时锁定,还是之后被
CALL转入)会被销毁。本 EIP 生效后这两种情况都不再销毁 ETH。 - 过去这类合约在交易终结时必定被删除;现在,终结余额为零的合约仍被删除,终结余额非零的合约则以"仅余额账户"(空代码、空存储、nonce 0)的形式留在状态中。
- 对"不在创建同笔交易内执行
SELFDESTRUCT"的合约,行为与 EIP-6780 完全一致,不受影响。 - 对由此产生的仅余额账户,后续交易仍可通过
CREATE2在同一地址重新部署合约,相关使用模式保持可用。
L2 的专项风险:OP Stack 的 burn() 依赖
文档特别警告:部分 L2 链会定期依赖SELFDESTRUCT的销毁功能,需要单独的迁移方案。
以 OP Stack 为例(eip-8246.md 中的详细描述):从 L2 提款至 L1 的 ETH 会累积在L2ToL1MessagePasser预部署合约(地址0x4200000000000000000000000000000000000016)中。该合约提供一个无需权限的burn()函数:它通过创建"构造函数内执行以自身为受益人的SELFDESTRUCT"的合约来销毁余额,从而防止 L2 ETH 供应量膨胀。任何继承该代码库的链(包括许多并不被普遍认为是 Optimism 系的项目)都会继承这一依赖,除非修改该预部署合约。值得注意的细节是:这类合约通过CREATE部署,因此它们留下的"仅余额账户"无法用上文所述的CREATE2模式重新创建。
测试矩阵:一个覆盖多维因子的完整用例集
EIP-8246 的 Test Cases 章节给出了结构化的测试策略,是理解行为边界的绝佳地图。
未修改行为:复用既有覆盖
SELFDESTRUCT历史上被多次修改,指令级的通用测试覆盖预期已经非常完善。因此只需将现有测试用例按新语义对齐,即可获得所有未修改特性的良好覆盖。
修改行为:因子组合矩阵
每个测试用例都应在物理上有意义的前提下,覆盖以下因子的组合:
| 因子 | 取值 |
|---|---|
| level | 顶层交易 / 内部调用 |
| call type | CALL(含顶层交易)/ CREATE(含顶层交易)/ CREATE2 |
| code state | initcode 内(部署前)执行 / 已部署代码执行 |
| status | SELFDESTRUCT成功 / 因 OOG 失败 |
| revert | 效果提交到 post-state / 效果被回滚(仅包装型内部调用) |
| balance | 执行SELFDESTRUCT时账户余额为零 / 非零 |
指令级用例(1–4)
- 同交易自销毁到自身。
- 同交易自销毁到自身,随后再次自销毁到自身。
- 同交易自销毁到自身,随后自销毁到其他地址。
- 同交易自销毁到其他地址,随后自销毁到自身。
这组用例验证多次SELFDESTRUCT交错执行时余额与状态的一致性。
交易终结级用例(5–16)
聚焦"自销毁之后又被转入 ETH"这一销毁残留路径:
- 同交易自销毁到其他地址,随后向已自销毁账户
CALL转账。 - 同交易自销毁到其他地址,随后多次
CALL转账到已自销毁账户。 - 自销毁到其他地址 →
CALL转账 → 再次自销毁到其他地址。 - 自销毁到其他地址 →
CALL转账 → 自销毁到自身。 - 自销毁到自身 →
CALL转账。 - 自销毁到自身 → 多次
CALL转账。 - 自销毁到自身 →
CALL转账 → 自销毁到其他地址。 - 自销毁到自身 →
CALL转账 → 自销毁到自身。 - 创建新账户,新账户再创建其他账户以抬高自身 nonce,随后自销毁到自身。
- 创建新账户,新账户再创建其他账户以抬高自身 nonce,随后自销毁到其他地址。
- 创建新账户,新账户写入新存储,随后自销毁到自身。
- 创建新账户,新账户写入新存储,随后自销毁到其他地址。
用例 13–16 专门验证"非零 nonce / 非零存储"对终结时改写与空账户判定的影响。
多交易用例(17)
- Tx-1:
CREATE2创建带非零余额的新账户,新账户自销毁到自身;Tx-2:重复 Tx-1。
该用例直接验证提案声称的"仅余额账户可被后续CREATE2重新部署"这一关键承诺。
安全考量:三大要点
EIP-8246 的 Security Considerations 指出:
- 组合爆炸式的测试负担:
SELFDESTRUCT行为修改对大量涉及多调用/多创建交互的测试场景产生组合影响,需要显著测试投入;好在大量 EIP-6780 的既有测试用例可以直接适配到新行为。 - 灰尘账户不是免费 DoS 向量:当某些用例依赖 ETH 销毁时,交易会留下"带有潜在锁定非零余额的灰尘账户"。但新建账户的 gas 成本由交易方承担,因此灰尘账户无法构成免费的 DoS 攻击。主网(Cancun 至约 25M 区块)迄今只记录到 2 次此类事件。
- 仅余额账户可被抢先接管:保留余额会产生一个"nonce、代码、存储均被清空但仍有钱"的账户,而该地址重新成为合法的
CREATE2目标——因为根据 EIP-684,仅非零余额本身永远不会触发创建碰撞(eip-684.md 规定只有目标地址存在非零 nonce 或非零代码长度时创建才必须失败)。(sender, salt, initcode)是公开数据,因此当sender是无需权限的工厂合约时,任何人都可以重新部署该账户并取走保留的余额。
总结:EVM 烧币时代的终结
EIP-8246 是SELFDESTRUCT语义收敛之路的最后一步:EIP-6049 声明弃用,EIP-6780 限定同交易删除,EIP-8246 移除最后的销毁路径。它用一个克制的两段式设计(指令级"自销毁不再烧币" + 终结时"余额保留、其余清空"),在保持状态清理效果、兼容 EIP-161 空账户回收、保留 CREATE2 重部署能力的同时,彻底终结了 ETH 经由 EVM 离开总供应的历史。对于以太坊核心开发者,它是共识规范与执行客户端必须对齐的新语义;对于 L2 团队,OP Stack 的L2ToL1MessagePasser.burn()案例则是一份必须尽早规划的迁移警示。
本文所有规范、数据与用例均直接引用自仓库内的 eip-8246.md,相关依赖提案见 eip-6780.md、eip-161.md、eip-684.md、eip-6049.md、eip-3529.md 与 eip-2929.md,版权遵循 CC0 声明。
【免费下载链接】EIPsThe Ethereum Improvement Proposal repository项目地址: https://gitcode.com/GitHub_Trending/ei/EIPs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考