定位状态变量的写入来源

从最终 Storage 差异追溯到交易内部每次 SSTORE、对应 Trace 和执行代码。

案例目标

识别目标 Storage Slot 在交易中被修改了几次、每次写入发生在哪个 Trace,以及哪次写入形成最终值。

示例交易与背景

本例使用 Ethereum 区块 17806056 中的交易:

0xa84aa065ce61dbb1eb50ab6ae67fc31a9da50dd2c74eefd561661bfce2f1620c

在 Skylens 中打开交易

目标是 Curve pETH-ETH-f 池代理 0x9848482da3Ee3076165ce6497eDA906E66bB85C5 的 slot 8。该交易通过 implementation 0x6326DEbBAa15bCFE603d831e7D75f4fc10d9B43E 的代码多次写入代理状态。

预期回答的问题

  1. slot 8 的交易前后值分别是什么?
  2. 中间发生了多少次写入?
  3. 为什么代码地址和状态地址不同?

分析步骤

  1. State Changes 中展开代理地址,记录 slot 8BeforeAfter 原始值。
  2. 打开该 Slot 的 Variable History,按 Trace 顺序检查每次写入。
  3. 跳转到 Execution Trace,开启 SSTORE 过滤,核对执行地址、Program Counter 和父调用。
  4. 使用 Set Breakpoints 把五次写入加入断点组,再在 Source Debugger 中比较 Call Stack 和变量上下文。
  5. 将 implementation 视为代码和 Storage Layout 来源,将 proxy 保留为实际状态地址。

关键证据

目标变量在当前布局中未可靠解码,因此本案例使用“代理地址 + slot + 原始值”作为稳定标识。

TracePC写入前写入后
6910460x14f4ef71451a6d379970x9c797cba0428105ecc1
8771900x9c797cba0428105ecc10x283517e49e6b3069518
10510460x283517e49e6b30695180xafba082befb269343e8
14471900xafba082befb269343e80xec4ae2b62e2a888e17
19431500xec4ae2b62e2a888e170x3f3278e0363aaefcf

State Changes 中的完整最终差异为:

Address: 0x9848482da3Ee3076165ce6497eDA906E66bB85C5
Slot:    0x0000000000000000000000000000000000000000000000000000000000000008
Before:  0x00000000000000000000000000000000000000000000014f4ef71451a6d37997
After:   0x00000000000000000000000000000000000000000000003f3278e0363aaefcf

五次 SSTORE 的首值和末值与 State Changes 完全一致,证明中间历史没有断点。

案例结论

代理地址的 slot 8 在一笔交易中连续写入五次。Trace 69 读取交易前值,Trace 194 写出最终值;中间三次变化虽然不会单独出现在最终差异中,但可由 Variable History 和 SSTORE Trace 完整复原。执行代码来自 implementation,实际状态始终属于 proxy。

注意事项

  • 未确认 Storage Layout 时,不要为原始 Slot 强行命名变量。
  • Mapping 和动态数组的 Slot 通常经过 KECCAK256 计算。
  • 中间写入可能被覆盖,或随子调用 Revert 一起回滚。
  • DELEGATECALL 场景中,Editors 显示 implementation 源码并不表示状态存储在 implementation。

相关文档