定位状态变量的写入来源
从最终 Storage 差异追溯到交易内部每次 SSTORE、对应 Trace 和执行代码。
案例目标
识别目标 Storage Slot 在交易中被修改了几次、每次写入发生在哪个 Trace,以及哪次写入形成最终值。
示例交易与背景
本例使用 Ethereum 区块 17806056 中的交易:
0xa84aa065ce61dbb1eb50ab6ae67fc31a9da50dd2c74eefd561661bfce2f1620c目标是 Curve pETH-ETH-f 池代理 0x9848482da3Ee3076165ce6497eDA906E66bB85C5 的 slot 8。该交易通过 implementation 0x6326DEbBAa15bCFE603d831e7D75f4fc10d9B43E 的代码多次写入代理状态。
预期回答的问题
- slot
8的交易前后值分别是什么? - 中间发生了多少次写入?
- 为什么代码地址和状态地址不同?
分析步骤
- 在 State Changes 中展开代理地址,记录 slot
8的 Before 与 After 原始值。 - 打开该 Slot 的 Variable History,按 Trace 顺序检查每次写入。
- 跳转到 Execution Trace,开启 SSTORE 过滤,核对执行地址、Program Counter 和父调用。
- 使用 Set Breakpoints 把五次写入加入断点组,再在 Source Debugger 中比较 Call Stack 和变量上下文。
- 将 implementation 视为代码和 Storage Layout 来源,将 proxy 保留为实际状态地址。
关键证据
目标变量在当前布局中未可靠解码,因此本案例使用“代理地址 + slot + 原始值”作为稳定标识。
| Trace | PC | 写入前 | 写入后 |
|---|---|---|---|
69 | 1046 | 0x14f4ef71451a6d37997 | 0x9c797cba0428105ecc1 |
87 | 7190 | 0x9c797cba0428105ecc1 | 0x283517e49e6b3069518 |
105 | 1046 | 0x283517e49e6b3069518 | 0xafba082befb269343e8 |
144 | 7190 | 0xafba082befb269343e8 | 0xec4ae2b62e2a888e17 |
194 | 3150 | 0xec4ae2b62e2a888e17 | 0x3f3278e0363aaefcf |
State Changes 中的完整最终差异为:
Address: 0x9848482da3Ee3076165ce6497eDA906E66bB85C5
Slot: 0x0000000000000000000000000000000000000000000000000000000000000008
Before: 0x00000000000000000000000000000000000000000000014f4ef71451a6d37997
After: 0x00000000000000000000000000000000000000000000003f3278e0363aaefcf五次 SSTORE 的首值和末值与 State Changes 完全一致,证明中间历史没有断点。
案例结论
代理地址的 slot
8在一笔交易中连续写入五次。Trace69读取交易前值,Trace194写出最终值;中间三次变化虽然不会单独出现在最终差异中,但可由 Variable History 和 SSTORE Trace 完整复原。执行代码来自 implementation,实际状态始终属于 proxy。
注意事项
- 未确认 Storage Layout 时,不要为原始 Slot 强行命名变量。
- Mapping 和动态数组的 Slot 通常经过
KECCAK256计算。 - 中间写入可能被覆盖,或随子调用 Revert 一起回滚。
- 在
DELEGATECALL场景中,Editors 显示 implementation 源码并不表示状态存储在 implementation。
