瞬态存储最容易被误解成“更便宜的storage”。真正的分界不只在费用,而在生命周期:storage写入永久状态,transient storage只陪伴当前交易。一次外部交易触发的多层调用可以共享临时值,交易完成后这些值整体归零。
协议先确认四件事
- TLOAD和TSTORE访问独立的瞬态存储;值在同一交易的各次内部调用间可见,但交易结束后会被清空。
- 瞬态存储不进入永久状态;TSTORE成本与脏槽位的warm SSTORE相同,TLOAD成本与hot SLOAD相同,规范当前均为100 Gas。
- DELEGATECALL/CALLCODE下瞬态存储的所有者语义跟随调用上下文,因此代理和库合约必须核对谁持有临时槽位。
- 调用帧revert会回滚该帧及其内部调用写入的瞬态状态;STATICCALL中执行TSTORE会异常,而TLOAD允许使用。
| 维度 | storage | transient storage | memory |
|---|---|---|---|
| 生命周期 | 跨交易保留 | 仅当前交易 | 当前调用帧 |
| 访问方式 | SLOAD/SSTORE | TLOAD/TSTORE | 编译器内存操作 |
| 典型用途 | 余额、配置、权限 | 交易级锁、临时授权 | 参数、计算中间值 |
先按交易边界理解
TSTORE写入的槽位不会因为一次内部函数正常返回就消失。合约A调用合约B,再回到A时,A仍可读取自己的瞬态槽位;但调用帧若revert,该帧及内部调用产生的瞬态写入会一起回滚,下一笔交易也从空值开始。它适合表达“这笔交易已经做过某事”,不适合保存用户余额、累计次数或跨区块授权。
代理场景要确认槽位属于谁
EIP-1153沿用调用上下文规则。普通CALL进入被调用合约时,各合约使用自己的瞬态存储;DELEGATECALL执行实现代码时,状态上下文仍属于代理。代理、库和模块共用槽位编号时必须像设计storage slot一样命名隔离,否则一个模块可能覆盖另一个模块的交易级标记。
重入锁是例子,不是万能答案
进入敏感函数时TSTORE写入锁,退出前清除,能省去永久存储锁的状态写入成本。但所有正常和异常路径都要保持不变量,回调入口也要位于同一保护域。只给一个函数加锁,不能自动阻止跨函数重入;把锁留给调用方清理,还可能破坏合约可组合性。
上线前的瞬态状态测试
- 确认目标网络已启用EIP-1153语义
- 列出值是否需要跨交易保留
- 为代理与模块分配不冲突的瞬态槽位
- 对CALL与DELEGATECALL分别画调用上下文
- 测试revert回滚,并确认STATICCALL只能TLOAD不能TSTORE
自动清空仍有同交易风险
“交易结束自动清空”并不等于“无需清理”。同一交易内后续调用仍会读到旧值,函数若希望在交易剩余阶段恢复可调用,就要在正确时点显式复位。反过来,为了强制整笔交易只能执行一次,也可以故意不清理。选择取决于业务不变量,而不是一条统一模板。
理解代理上下文可先看CREATE2与合约部署地址,再结合eth_getProof账户与存储证明核对永久状态。交易类型与升级背景可对照以太坊Type 0到4。
发表评论
还没有评论,来说两句吧。
评论区为展示样式,提交不会被处理。