当前位置: 首页 > news >正文

Transient Storage

本节是《Solidity by Example》的中文翻译与深入讲解,专为零基础或刚接触区块链开发的小白朋友打造。我们将通过“示例 + 解说 + 提示”的方式,带你逐步理解每一段 Solidity 代码的实际用途与背后的逻辑。

Solidity 是以太坊等智能合约平台使用的主要编程语言,就像写网页要用 HTML 和 JavaScript,写智能合约就需要会 Solidity。

如果你从没写过区块链代码也没关系,只要你了解一点点编程概念,比如“变量”“函数”“条件判断”,我们就能从最简单的例子开始,一步步建立你的 Solidity 编程思维。

Transient Storage

瞬态存储
瞬态存储中的数据在交易结束后会被清除。

  • 什么是瞬态存储(Transient Storage)?

    瞬态存储(Transient Storage)是 Solidity 在以太坊 Cancun 升级(EIP-1153)中引入的一种新数据存储位置,介于storage(永久存储)和memory(函数级临时存储)之间。

    • 特点:
      • 数据存储在交易(transaction)期间,交易结束后自动清除。
      • 使用 tstore(存储)和 tload(读取)操作,通过内联汇编(assembly)访问。
      • storage 操作(如 sstore/sload)更节省 Gas,因为数据不持久化到区块链。
    • 比喻:
      • 瞬态存储像会议室的临时白板,会议(交易)结束后自动擦除。
      • 相比之下,storage 像刻在石碑上的记录(永久且昂贵),memory 像便签纸(函数结束即丢弃)。
  • 用途:

    • 临时状态:适合需要跨函数调用但仅在交易内有效的状态(如锁状态)。
    • 重入保护:在重入保护(reentrancy guard)中替代 storage,降低 Gas 成本。
    • 优化 Gas:瞬态存储的操作(tstore/tload)比 storage 操作便宜,适合高频临时数据操作。
// SPDX-License-Identifier: MIT
// 使用 MIT 许可证,允许自由使用、修改和分发代码。pragma solidity ^0.8.26;
// 指定 Solidity 编译器版本,必须为 0.8.26 或更高(但低于 0.9.0)。// Make sure EVM version and VM set to Cancun
// 确保 EVM 版本和虚拟机设置为 Cancun(支持 EIP-1153 的瞬态存储)。// Storage - data is stored on the blockchain
// Memory - data is cleared out after a function call
// Transient storage - data is cleared out after a transaction
// 存储(Storage) - 数据存储在区块链上,永久存在。
// 内存(Memory) - 数据在函数调用后清除。
// 瞬态存储(Transient Storage) - 数据在交易结束后清除。interface ITest {function val() external view returns (uint256);function test() external;
}
// 定义一个接口 ITest,包含两个函数:
// - val():视图函数,返回 uint256 类型的值(读取状态,免费)。
// - test():外部函数,执行测试逻辑(可能修改状态,消耗 Gas)。
// 接口用于跨合约调用,测试存储行为。contract Callback {uint256 public val;// 声明一个公共的状态变量 val,存储在 storage(区块链上),记录测试值。// public 自动生成 getter 函数(val()),消耗 Gas 修改。fallback() external {val = ITest(msg.sender).val();// 定义一个 fallback 函数,在接收未定义函数调用时触发。// 调用调用者的 val() 函数(通过 ITest 接口),获取其状态并存储到本合约的 val。// msg.sender 是触发调用的合约地址(如 TestStorage 或 TestTransientStorage)。}function test(address target) external {ITest(target).test();// 定义一个公共函数 test,接受目标合约地址 target。// 通过 ITest 接口调用目标合约的 test() 函数,触发目标合约逻辑。// 若目标合约调用本合约的 fallback,val 会被更新。}
}contract TestStorage {uint256 public val;// 声明一个公共的状态变量 val,存储在 storage(区块链上),记录测试值。function test() public {val = 123;// 将 storage 变量 val 设置为 123,修改区块链状态,消耗 Gas(约 20,000+)。bytes memory b = "";// 创建一个空的 memory 字节数组 b,临时存储,不消耗 Gas。msg.sender.call(b);// 调用 msg.sender(调用者,通常是 Callback 合约)的 fallback 函数,传递空字节数组 b。// 触发 Callback 的 fallback,Callback 会读取本合约的 val(123)。}
}contract TestTransientStorage {bytes32 constant SLOT = 0;// 定义一个常量 SLOT(值为 0),表示瞬态存储的槽位。// 瞬态存储使用固定槽位(bytes32 类型)来存储数据。function test() public {assembly {tstore(SLOT, 321)// 使用内联汇编的 tstore 操作,将值 321 存储到瞬态存储的 SLOT(槽位 0)。// 瞬态存储仅在交易期间有效,交易结束后清除。// tstore 比 sstore(storage 操作)更节省 Gas。}bytes memory b = "";// 创建一个空的 memory 字节数组 b,临时存储,不消耗 Gas。msg.sender.call(b);// 调用 msg.sender(调用者,通常是 Callback 合约)的 fallback 函数,传递空字节数组 b。// 触发 Callback 的 fallback,Callback 会读取本合约的 val(瞬态存储中的 321)。}function val() public view returns (uint256 v) {assembly {v := tload(SLOT)// 使用内联汇编的 tload 操作,读取瞬态存储 SLOT(槽位 0)的值。// 返回瞬态存储中的值(交易内为 321,交易结束后为 0)。}}
}contract MaliciousCallback {uint256 public count = 0;// 声明一个公共的状态变量 count,存储在 storage,记录重入次数。fallback() external {count++;// 在 fallback 函数中增加 count,表示被调用一次。ITest(msg.sender).test();// 尝试重新调用调用者的 test() 函数,模拟重入攻击。}function attack(address _target) external {// 定义一个公共函数 attack,接受目标合约地址 _target。ITest(_target).test();// 调用目标合约的 test() 函数,触发重入攻击。// 若目标合约未防止重入,MaliciousCallback 的 fallback 会反复调用 test()。}
}contract ReentrancyGuard {bool private locked;// 声明一个私有状态变量 locked,存储在 storage,表示锁状态(true 表示锁定,false 表示未锁定)。// private 防止外部直接访问。modifier lock() {require(!locked);// 检查 locked 是否为 false,若为 true 则抛出错误,防止重入。locked = true;// 设置 locked 为 true,锁定合约。_;// 执行函数主体。locked = false;// 函数结束后,设置 locked 为 false,释放锁。}// 27587 gasfunction test() public lock {// 定义一个公共函数 test,使用 lock 修饰符防止重入。// 注释表明此函数消耗约 27,587 Gas(因 storage 操作)。bytes memory b = "";// 创建一个空的 memory 字节数组 b,临时存储。msg.sender.call(b);// 调用 msg.sender 的 fallback 函数,传递空字节数组 b。// 若调用者是 MaliciousCallback,lock 修饰符会防止重入。}
}contract ReentrancyGuardTransient {bytes32 constant SLOT = 0;// 定义一个常量 SLOT(值为 0),表示瞬态存储的槽位。modifier lock() {assembly {if tload(SLOT) { revert(0, 0) }// 检查瞬态存储 SLOT 的值,若非 0(已锁定),抛出错误,防止重入。tstore(SLOT, 1)// 设置瞬态存储 SLOT 的值为 1,表示锁定。}_;// 执行函数主体。assembly {tstore(SLOT, 0)// 函数结束后,设置瞬态存储 SLOT 为 0,释放锁(交易结束会自动清除)。}}// 4909 gasfunction test() external lock {// 定义一个外部函数 test,使用 lock 修饰符防止重入。// 注释表明此函数消耗约 4,909 Gas(因瞬态存储操作比 storage 便宜)。bytes memory b = "";// 创建一个空的 memory 字节数组 b,临时存储。msg.sender.call(b);// 调用 msg.sender 的 fallback 函数,传递空字节数组 b。// 若调用者是 MaliciousCallback,lock 修饰符会防止重入。}
}

代码整体说明

代码包含多个合约,展示瞬态存储(transient storage)与传统存储(storage)的对比,以及在重入保护中的应用:

  1. 接口 ITest:定义测试函数签名,用于跨合约调用。
  2. 合约 Callback:测试普通存储和瞬态存储的差异,通过 fallback 捕获调用者的状态。
  3. 合约 TestStorage:使用普通存储(storage)保存数据,展示持久化行为。
  4. 合约 TestTransientStorage:使用瞬态存储(tstore/tload)保存数据,展示交易后清除行为。
  5. 合约 MaliciousCallback:模拟重入攻击,测试重入保护。
  6. 合约 ReentrancyGuard:使用 storage 实现重入保护,展示高 Gas 成本。
  7. 合约 ReentrancyGuardTransient:使用瞬态存储实现重入保护,展示低 Gas 成本。

瞬态存储的本质

  • 瞬态存储(transient storage)是一种在交易期间存储数据的机制,交易结束后数据自动清除。
  • 比喻:
    • 瞬态存储像会议室的临时白板,会议(交易)结束后自动擦除,记录成本低。
    • Storage 像刻在石碑上的记录,永久但昂贵(高 Gas)。
    • Memory 像便签纸,函数结束即丢弃,免费但无法跨函数调用。
  • 核心特点:
    • 生命周期:仅在交易内有效,结束后清除(无需手动清理)。
    • 操作方式:通过内联汇编的 tstore(存储)和 tload(读取)访问,使用槽位(bytes32)存储数据。
    • 低 Gas 成本tstoretloadsstoresloadstorage 操作)便宜,适合临时状态。
  • 优势:
    • 节省 Gas:瞬态存储操作成本低(如 ReentrancyGuardTransient 的 4,909 Gas 对比 ReentrancyGuard 的 27,587 Gas)。
    • 自动清理:交易结束后数据自动清除,无需手动重置。
    • 跨函数调用:数据可在交易内的多次调用中共享(不像 memory 局限于单函数)。

代码功能

  • 接口 ITest
    • 定义 val()test() 函数,用于跨合约调用,测试存储行为。
  • 合约 Callback
    • 接收目标合约的 val() 返回值,存储到 storage 变量 val,展示持久化存储。
  • 合约 TestStorage
    • 使用 storage 变量 val 存储 123,调用 Callbackfallback,持久保存数据。
  • 合约 TestTransientStorage
    • 使用瞬态存储(tstore/tload)存储 321,调用 Callbackfallback,数据在交易结束后清除。
  • 合约 MaliciousCallback
    • 模拟重入攻击,通过 fallback 反复调用目标合约的 test()
  • 合约 ReentrancyGuard
    • 使用 storage 变量 locked 实现重入保护,消耗高 Gas(27,587)。
  • 合约 ReentrancyGuardTransient
    • 使用瞬态存储(槽位 0)实现重入保护,消耗低 Gas(4,909)。

瞬态存储的注意事项

  • 生命周期:
    • 瞬态存储仅在交易内有效,交易结束后自动清零。
    • 不适合持久数据存储(如用户余额)。
  • 操作方式:
    • 仅通过内联汇编(tstore/tload)访问,需熟悉汇编语法。
    • 槽位(bytes32)需明确定义,避免冲突。
  • Gas 成本:
    • tstoretloadsstoresload 便宜,适合高频临时操作。
    • 仍需注意交易内多次操作的累积成本。
  • 兼容性:
    • 瞬态存储依赖 Cancun 升级(EIP-1153),需确保 EVM 支持。
    • 测试时使用 Hardhat 或 Remix 的 Cancun 环境。
  • 安全性:
    • 检查槽位状态(tload),防止逻辑错误。
    • 避免在关键逻辑中依赖瞬态存储的持久性(会丢失)。
http://www.dtcms.com/a/288855.html

相关文章:

  • Redis通用常见命令(含面试题)
  • [硬件电路-51]:晶体管既可以用于模拟电路芯片,也可以用于数字电路芯片,晶体管应用在这两个领域的相同点和本质区别?
  • Linux之dpkg--命令的用法
  • Apache基础配置
  • DOM型XSS破坏
  • gcc 和 g++ 的区别
  • Pycaita二次开发基础代码解析:材料属性管理与焊点坐标导出实战指南
  • Go实现用户登录小程序
  • WPF学习笔记(28)Interaction.Triggers的意义与使用方式
  • Java设计模式之行为型模式(备忘录模式)应用场景分析
  • HTML和CSS快速入门
  • 【算法】二分查找经典例题
  • 2025最新 PostgreSQL17 安装及配置(Windows原生版)
  • 大语言模型调用方式与函数调用
  • Node.js 与 Java 性能对比
  • 零基础 “入坑” Java--- 十三、再谈类和接口
  • 网络原理——IP
  • 【硬件】GalaxyTabPro10.1(SM-T520)刷机/TWRP/LineageOS14/安卓7升级小白向保姆教程
  • Nginx的location匹配规则
  • 【数据结构与算法】数据结构初阶:详解二叉树(一)
  • Mysql 学习总结(90)—— Mysql 8.0 25 条性能优化实战指南
  • ubuntu 24.04 xfce4 钉钉输入抢焦点问题
  • Baumer工业相机堡盟工业相机如何通过YoloV8的深度学习模型实现螺母螺丝的分类检测(C#代码,UI界面版)
  • 二、Spark 开发环境搭建 IDEA + Maven 及 WordCount 案例实战
  • PaddleOCR 与 PaddleX 调试
  • linux ps -a与-e的区别
  • Spring Cloud Gateway高危隐患
  • 社交圈子系统开源社交源码 / 小程序+H5+APP 多端互通的底层技术分析
  • [特殊字符] 小程序 vs 智能体:下一代应用开发,谁主沉浮?
  • 「Java案例」利用方法打印乘法表