十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Sway 合约持久化向量 StorageVec 实战指南:声明、读取、写入与标准库方法全解析

Sway 合约持久化向量 StorageVec 实战指南:声明、读取、写入与标准库方法全解析 Sway 合约持久化向量 StorageVec 实战指南声明、读取、写入与标准库方法全解析【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/swayStorageVecT是 Sway 标准库提供的一种持久化集合类型它把数据永久写入合约的 storage 槽位功能与常规向量VecT一致但由于依赖哈希与泛型定位存储位置其元素并非连续存放。本文以仓库参考文档 storage-vec.md 为骨架结合 标准库实现 与配套 示例合约完整讲解StorageVec的声明、.get()读取、.push()写入、存储注解要求、越界处理、迭代及全部内置方法读完即可在合约中正确使用并理解其底层存储寻址原理。StorageVec 是什么与 Vec 的本质差异在 Sway 中VecT是堆上分配的临时向量生命周期局限于当前执行环境而StorageVecT将数据永久存储于合约的持久化 storage 中跨交易存续。两者核心差异如下存储介质不同VecT存于内存堆StorageVecT存于链上 storage。物理布局不同VecT元素在内存中连续排列StorageVecT的元素借助**哈希hashing与泛型generics**计算存储位置元素散布在不同的 storage 槽位中并不要求槽位 key 连续。使用位置限制只有合约contract才能访问持久化 storage因此StorageVec只能在合约中使用脚本script与谓词predicate无法使用。从源码结构看StorageVecV本身被实现为一个零大小的空结构体storage_vec.sw所有有趣的行为都封装在其方法实现中并且注释明确说明始终使用self.field_id作为 storage 槽位而绝不使用self.slot这是它能被嵌套进StorageMap、StorageVec等其它存储类型的关键设计。声明与初始化 StorageVec要使用StorageVec首先需要从标准库导入。参考文档的配套示例main.sw中导入语句如下use std::storage::storage_vec::*;声明位置在合约的storage块内语法遵循 初始化章节 的通用规则每个 storage 变量由「名字 类型 初始值」组成。示例声明了两个 storage 变量storage { // T u64 balance: StorageVecu64 StorageVec {}, // T (Identity, u64) user: StorageVec(Identity, u64) StorageVec {}, }要点说明balance是StorageVecu64单值向量user是StorageVec(Identity, u64)每个元素是一个 元组 (tuple)把调用者的 Identity 与某个id打包存放演示了泛型类型参数T可以是任意可存储类型。初始化器就是空结构体字面量StorageVec {}因为StorageVecT本身是空结构体不需要也不能传入初始元素。StorageVec支持任意存储类型作为元素T包括u64、b256、结构体、枚举、元组乃至嵌套的存储类型如StorageVecStorageVecu64、StorageVecStorageMapu64, b256见 storage_vec.sw 的文档注释。声明 storage 变量不需要mut关键字所有 storage 变量默认可变。Forc.toml只需声明项目与标准库依赖示例配置见 storage_vec/Forc.toml标准库以 path 形式依赖本仓库的 sway-lib-std。从 Storage 读取.get(index) 与 StorageKey读取 storage 变量通过.get(index)方法完成在括号中指定索引追加在 storage 变量之后。示例合约中的读取函数如下#[storage(read)] fn reading_from_storage(id: u64) { let balance storage.balance.get(id).unwrap(); let (user, value) storage.user.get(id).unwrap().read(); }行为拆解storage.balance.get(id)返回OptionStorageKeyu64当id越界id len时返回None不会 panic见 get 实现。因此需要配合.unwrap()本例假定索引必定有效或match处理Some/None。.read()是解引用get返回的是「存储位置」StorageKeyV而非值本身要拿到值必须调用StorageKey上的.read()。所以单值读取写为storage.balance.get(id).unwrap()就能得到u64——这里u64是内置标量类型StorageKeyu64实现了直接求值而对user这种元组元素则显式链式调用.read()解包出(Identity, u64)再通过模式匹配解构为(user, value)两个局部变量。存储注解读取函数只需标注#[storage(read)]因为get只读不写。越界行为是get的一大实用特性当索引可能偶尔超出向量范围例如索引来自外部调用参数时代码可以安全地拿到None并自行决定是 revert 还是返回带错误信息的提示。仓库示例 examples/storage_vec/src/main.sw 展示了match处理方式#[storage(read)] fn read_from_storage_vec() { let third storage.v.get(2); match third { Some(third) log(third.read()), None revert(42), } }写入 Storage.push(value) 与读写注解写入与读取流程类似区别在于使用.push(value)方法并且函数必须标注#[storage(read, write)]。为什么需要同时标注read从 push 实现 可以看出push首先会读取向量的长度长度本身也持久化存储在 storage 中据以确定新元素应写入的位置随后写入元素并把长度加一——既读又写因此两个注解缺一不可#[storage(read, write)] fn writing_to_storage(id: u64) { storage.user.push((msg_sender().unwrap(), id)); }本例向user向量插入一个元组包含调用者的Identity通过标准库的msg_sender().unwrap()获取和传入的id。参考文档特别提醒push 的实现需要读取向量长度来确定存放位置这正是它与普通读取的最大差别。push的存储访问开销为3 次读、2 次写见 push 文档注释。存储注解同样适用于合约内调用 push 的私有函数。而仅调用get的函数只需#[storage(read)]。底层存储寻址原理哈希定位与偏移计算StorageVec之所以「不连续」源于其寻址算法。以experimental_dynamic_storage false默认 quads 模式下的实现为例storage_vec.sw 注释长度存储向量长度以u64形式保存在self.field_id槽位。内容存储实际元素数据存放在sha256(self.field_id)计算出的 key 处。元素定位第index个元素的字节偏移由offset_calculator::V(index)计算实现fn offset_calculatorT(index: u64) - u64 { let size_in_bytes __size_of::T(); let size_in_bytes (size_in_bytes (8 - 1)) - ((size_in_bytes (8 - 1)) % 8); (index * size_in_bytes) / 8 }即先把类型大小向上取整到8 字节一个 word对齐不足 8 字节的如bool也按 8 字节占位再乘以索引并除以 8 得到按 8 字节为单位的偏移。这样每个元素都有确定且不重叠的存储区间配合write_quads/read_quads完成读写。当启用experimental_dynamic_storage true时标准库提供了另一套基于动态槽位的分块实现源码后半部分#[cfg(experimental_dynamic_storage true)]下的同名方法其元素布局以CHUNK_MAX_SIZE分块、每块独立槽位此处不再展开但两套实现的对外 API 完全一致。方法全集标准库 StorageVec API 一览标准库 storage_vec.sw 为StorageKeyStorageVecV实现了丰富的方法参考文档提到「除了 push 之外还有一整套方法」下表为默认模式下的公开 API行号对应 storage_vec.sw方法签名说明存储访问pushpush(self, value: V)追加元素到末尾自动读长度并写回读 3 / 写 2poppop(self) - OptionV移除并返回末尾元素空向量返回None读 3 / 写 1getget(self, index: u64) - OptionStorageKeyV取指定索引的存储位置越界返回None读 1setset(self, index: u64, value: V)覆盖指定索引的值越界 revert读 1-2 / 写 1removeremove(self, index: u64) - V删除指定索引并把后续元素前移大向量耗 gas读 32·(len-index) / 写 len-indexswap_removeswap_remove(self, index: u64) - V用末尾元素替换指定索引再删末尾O(1) 删除读 5 / 写 2insertinsert(self, index: u64, value: V)在指定索引插入并后移后续元素index len时等价push读 3 或 52·(len-index) / 写 2 或 2len-indexlenlen(self) - u64返回向量长度读 1is_emptyis_empty(self) - bool判断是否为空读 1swapswap(self, element1_index: u64, element2_index: u64)交换两个索引处的元素读 5 / 写 2firstfirst(self) - OptionStorageKeyV返回首元素存储位置空向量返回None读 1lastlast(self) - OptionStorageKeyV返回末元素存储位置空向量返回None读 1reversereverse(self)原地反转元素顺序读 13·(len/2) / 写 2·(len/2)fillfill(self, value: V)用value填充全部元素读 1len / 写 lenresizeresize(self, new_len: u64, value: V)扩容新元素填value或截断读/写视 new_len 而定store_vecstore_vec(self, vec: VecV)将堆上VecV整体写入 storage覆盖旧值写 2load_vecload_vec(self) - VecV将整个 storage 向量读回堆上VecV读 2iteriter(self) - StorageVecIterV返回迭代器逐元素产出StorageKeyV读 1其中部分方法set、remove、swap_remove、insert、swap、reverse、fill、store_vec、load_vec会在元素类型V为零大小的嵌套存储类型时revert通过assert(__size_of::V() 0)断言并在方法文档中注明pop对嵌套存储类型有特殊行为会移除并缩短向量长度但总是返回None且被移除元素的嵌套内容会残留在 storage 中——使用嵌套StorageVec时需特别注意storage_vec.sw。错误类型方面标准库定义了OutOfBounds { length, index }结构与StorageVecError::IndexOutOfBounds、StorageVecError::MethodDoesNotSupportNestedStorageTypes两个错误变体storage_vec.sw用于越界访问与不支持嵌套类型的方法调用。迭代 StorageVec迭代一个 storage 向量在概念上与迭代VecT相同唯一区别是每次拿到的是StorageKeyV需要额外调用一次.read()取出真正的值。标准库提供iter()返回StorageVecIterV实现示例 examples/storage_vec/src/main.sw 展示了三种方式// 方式一while get不推荐仅为说明 get 用法 let mut i 0; while i storage.v.len() { log(storage.v.get(i).unwrap().read()); i 1; } // 方式二for iter推荐性能最优 for elem in storage.v.iter() { log(elem.read()); } // 方式三while 精确控制遍历如反向、跳步 let mut i storage.v.len() - 1; while 0 i { log(storage.v.get(i).unwrap().read()); i - 2; }注意迭代过程中修改向量增删元素属于逻辑错误会导致未定义行为标准库并不保证其安全应避免在迭代循环内 push/pop。用枚举在 StorageVec 中存放多种类型StorageVec与Vec一样要求所有元素同型。若一个向量需要承载不同类型可以定义一个枚举把各类型封装为变体枚举整体视为单一类型。示例examples/storage_vec/src/main.swenum TableCell { Int: u64, B256: b256, Boolean: bool, } storage { row: StorageVecTableCell StorageVec {}, }然后即可向同一向量推入不同类型的变体storage.row.push(TableCell::Int(3)); storage .row .push(TableCell::B256(0x0101010101010101010101010101010101010101010101010101010101010101)); storage.row.push(TableCell::Boolean(true));嵌套 StorageVecStorageVec可以嵌套使用例如StorageVecStorageVecu64。内层向量的访问方式是先用get(index)取出内层向量的StorageKey再对它继续调用push、get、len等examples/storage_vec/src/main.swstorage.nested_vec.push(StorageVec {}); storage.nested_vec.push(StorageVec {}); let mut inner_vec0 storage.nested_vec.get(0).unwrap(); let mut inner_vec1 storage.nested_vec.get(1).unwrap(); inner_vec0.push(0); inner_vec0.push(1); inner_vec1.push(2); inner_vec1.push(3); inner_vec1.push(4); assert(inner_vec0.len() 2); assert(inner_vec0.get(2).is_none());这一能力得益于StorageVec方法始终基于self.field_id而非self.slot寻址——每个嵌套实例持有独立的field_id互不干扰。但请回顾上文警告对嵌套类型调用remove、set、insert、swap_remove、swap、fill、reverse、store_vec、load_vec等方法会 revert需按需选用。小结StorageVecT是 Sway 合约在链上维护动态大小集合的标准方案核心要点可归纳为声明在storage块中以StorageVecT StorageVec {}初始化并从std::storage::storage_vec导入。读取.get(index)返回OptionStorageKeyT越界得None取值需.read()。写入.push(value)会先读长度再写元素函数须标注#[storage(read, write)]。寻址元素存放于sha256(field_id)处偏移由offset_calculator按 8 字节对齐计算故元素不连续存储。API标准库提供 push / pop / get / set / insert / remove / swap_remove / swap / len / is_empty / first / last / reverse / fill / resize / store_vec / load_vec / iter 共 18 个方法均有明确的存储访问次数与越界/嵌套类型约束。如需深入可继续阅读仓库内 存储初始化、存储读写与注解、StorageMap 对比 与 Store Get 手动槽位操作并对照标准库源码 storage_vec.sw 与完整示例 examples/storage_vec/src/main.sw 进行验证。【免费下载链接】sway Empowering everyone to build reliable and efficient smart contracts.项目地址: https://gitcode.com/GitHub_Trending/sw/sway创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表