尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

Sway 合约存储读写实战:storage 块、`storage.` 语法与 `[storage(read, write)]` 纯度注解

Sway 合约存储读写实战:storage 块、`storage.` 语法与 `[storage(read, write)]` 纯度注解 Sway 合约存储读写实战storage 块、storage.语法与#[storage(read, write)]纯度注解【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway本篇技术指南以 Sway 语言官方参考文档的 storage/read-write 为核心系统讲解智能合约如何声明存储变量、如何通过storage.变量名显式引用存储域、以及如何配合#[storage(read)]/#[storage(write)]/#[storage(read, write)]纯度注解完成对持久化数据的读取与写入。读完本文你将掌握在 Sway 合约中安全、规范地读写链上存储的完整技能并理解其底层 32 字节存储槽storage slot的实现原理。存储读写概览两种纯度一个统一语法在 Sway 中处理存储storage时只有两类操作从存储中读取和向存储中写入。无论执行哪一类操作都必须借助 storage 注解 声明函数对存储的纯度purity即该函数是否读取、写入、或同时读写存储抑或是完全不触碰存储纯函数。同时当在代码中引用一个存储变量时必须显式地指明该变量来自存储域storage scope而非局部作用域local scope。这一要求通过storage.variable_name语法实现例如storage.counter。存储变量首先需要通过storage关键字在合约中声明。以下是一个最小示例完整代码见 reading_writing_to_storage/src/main.swcontract; storage { counter: u64 0, }在storage块内每个变量都有三个要素变量名、类型、默认值。上例声明了一个名为counter、类型为u64、初始值为0的存储变量。关于 storage 块初始化的更多形式例如通过 associated function 构造复杂类型的默认值可参阅 storage/init。从存储读取#[storage(read)]与.read()当处理内置类型如u64、bool、b256等时可以直接读取存储变量而无需调用任何特殊方法之外的辅助逻辑#[storage(read)] fn read() { let counter storage.counter.read(); }要点拆解#[storage(read)]标注该函数只读存储不会产生任何写入。storage.counter返回一个指向存储位置的键StorageKeyu64随后调用其.read()方法取出当前值。读取到的是存储中的最新持久化值合约重启或多次交易调用之间数据保持一致。底层实现StorageKey::read与read_quads从源码看storage.counter实际上会构造一个StorageKey它由三部分描述存储位置slot32 字节存储槽的键b256offset从槽起始处的偏移非动态存储模式下以字为单位field_id用于区分可能位于同一存储位置的零尺寸存储类型的唯一标识符。其read()方法在非动态存储模式下最终调用 read_quads即storage_api::read_quadsT(slot, offset)。read_quads会先通过slot_calculator计算出值实际跨越的槽数量与槽内位置分配缓冲区后调用__state_load_quad一次性读出返回OptionT再由.unwrap()解出T。这意味着若值跨越 32 字节边界读取会自动延续到后续槽若目标槽从未被写入过read_quads返回None而StorageKey::read会直接 revert此时可使用不抛错的try_read()获得OptionT。向存储写入#[storage(write)]与.write()同样地对于内置类型更新存储变量也不需要额外的辅助方法#[storage(write)] fn write() { storage.counter.write(storage.counter.read() 1); }要点拆解#[storage(write)]标注该函数只写存储。.write(value)将新值写入storage.counter对应的存储位置。上例实现了自增语义先读取当前值加一后再写回——这是计数器类合约最典型的模式。底层实现write_quads与内存复制语义StorageKey::write在非动态存储模式下调用 write_quads即storage_api::write_quadsT(slot, offset, value)。其核心流程若值恰好占满整数个槽且offset 0直接调用__state_store_quad写入无需预读否则分配number_of_slots * 32字节缓冲先__state_load_quad读入受影响槽的既有数据将新值覆写到指定偏移处再整块写回避免破坏同一槽内其他共享数据。需要特别注意的是源码文档中强调的语义值是内存复制进存储槽的如果值内部包含指针或引用其指向的数据不会被存入存储。因此对于Vec、String、Bytes这类动态类型应使用标准库专门提供的StorageVec、StorageString、StorageBytes见 storage 模块而不是直接对普通动态类型调用.write()。同时读写#[storage(read, write)]在单个函数中既读又写时需要在注解中同时使用两个关键字#[storage(read, write)] fn read_write() { let counter storage.counter.read(); storage.counter.write(counter 1); }这是最常见的业务场景之一例如先读取余额再更新余额、先读取计数再递增计数。注解中的两个关键字可以按任意顺序排列但必须同时出现缺一不可——编译器会据此进行纯度检查防止合约在未声明权限的情况下意外访问存储。实战对照计数器合约仓库中的 examples/counter/src/main.sw 是这三种纯度注解组合的完整范例contract; abi TestContract { #[storage(write)] fn initialize_counter(value: u64) - u64; #[storage(read, write)] fn increment_counter(amount: u64) - u64; } storage { counter: u64 0, } impl TestContract for Contract { #[storage(write)] fn initialize_counter(value: u64) - u64 { storage.counter.write(value); value } #[storage(read, write)] fn increment_counter(amount: u64) - u64 { let incremented storage.counter.read() amount; storage.counter.write(incremented); incremented } }该示例展示了两个重要实践初始化函数只写不读#[storage(write)]增量函数先读后写#[storage(read, write)]。如果把increment_counter误标成#[storage(write)]编译器会因其中存在读取操作而报纯度错误。纯函数与注解省略规则根据 storage 注解文档storage 注解完整地描述了函数的纯度共四种情况纯度注解说明只读#[storage(read)]函数可从存储读取只写#[storage(write)]函数可向存储写入读写#[storage(read, write)]函数既可读也可写纯函数省略注解既不读也不写存储当函数是纯函数时注解被省略否则必须将正确的注解置于函数签名之上。这一机制在编译期保证了存储访问的显式化与安全性。存储位置的确定与高级控制默认位置由变量名哈希派生每个存储变量在链上的实际槽位并非由程序员手工指定而是通过哈希计算得出。默认情况下storage块中变量counter的存储位置由sha256(storage.counter)派生若使用了命名空间namespace则计算依据变为sha256(storage::my_namespace.foobar)。强制指定位置in关键字如果希望把某个变量存放到指定位置可以使用 storage in 关键字 覆盖默认哈希位置。例如把current_owners强制存入固定哈希键HASH_KEY而不是由变量名派生出的位置storage { current_owners: ... HASH_KEY, }这在需要与既定存储布局如升级场景或跨合约约定对齐时非常有用。更底层的替代方案store()与get()标准库还提供了不依赖storage块的直接读写方式store()与get()见 store-get。它们利用泛型按任意b256键存储与检索值其中get返回OptionT若目标槽从未被写入则返回None。该方案适合不预先声明storage块、直接管理键值对的场景。注意事项与最佳实践注解必须准确函数体内只要出现读取就必须标注read出现写入就必须标注write两者兼有则必须同时标注纯函数一律省略。显式域引用存储变量一律以storage.变量名形式访问避免与局部变量混淆。内置类型直接读写u64、bool、b256等内置类型可调用.read()/.write()直接操作动态集合类型请使用标准库的StorageVec、StorageMap、StorageString、StorageBytes。理解槽与偏移底层存储以 32 字节为槽、8 字节为字wordread_quads/write_quads的offset以字为单位非动态存储模式下StorageKey的偏移同样以字计动态存储模式experimental_dynamic_storage则以字节计。写入成本当写入值未占满整个槽时write_quads需要先读取既有数据再部分覆写会产生额外读取占满整槽且对齐时则免去预读写入更省 Gas。通过本文的三种纯度注解模式与storage.语法你可以在 Sway 合约中精确控制每一次链上数据访问写出既安全又高效的持久化逻辑。如需进一步了解存储变量的声明与初始化、命名空间、以及 StorageMap / StorageVec 等高级库可继续阅读 storage 章节索引。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表