
Sway 智能合約 StorageMap 存儲映射完全指南從聲明、讀寫到嵌套與底層槽位原理【免費下載鏈接】sway Empowering everyone to build reliable and efficient smart contracts.項目地址: https://gitcode.com/GitHub_Trending/sw/sway導讀StorageMapK, V是 Sway 標準庫提供的持久化鍵值對存儲集合它通過哈希函數把任意類型的鍵K映射到對應的值V并寫入 FuelVM 的持久化存儲槽位storage slots。本指南以 Sway Book 的 Storage Maps 章節為主體結合倉庫中的可運行示例與標準庫源碼完整講解 StorageMap 的聲明、插入、讀取、刪除、元組多鍵、嵌套映射并深入解析其底層的 slot 計算與Hashtrait 約束幫助你寫出可編譯、可上鏈、gas 開銷可控的合約存儲邏輯。什么是 StorageMapStorageMapK, V是 Sway 標準庫中最重要、也最常用的持久化集合之一。與通過索引訪問的StorageVecT不同StorageMap 允許你通過任意類型的鍵來查找數據。標準庫對它的定義極其精簡——它本身就是一個空結構體pub struct StorageMapK, V {}一切有意思的行為都實現在其方法上見 sway-lib-std/src/storage/storage_map.sw。與 RustHashMapK, V的異同從類型形態上看StorageMapK, V與 Rust 標準庫中的HashMapK, V類似但存在本質區別持久化Rust 的HashMap是內存數據結構進程結束即消失Sway 的StorageMap直接寫入 FuelVM 的持久化存儲合約狀態可跨交易、跨區塊存活。槽位slotsStorageMap 通過哈希函數把鍵值對映射到 32 字節的 storage slots 中槽位是 FuelVM 存儲的最小尋址單元。僅限合約與StorageVecT一樣StorageMapK, V只能在合約contract中使用因為只有合約才有權訪問持久化存儲腳本script與謂詞predicate無法使用。典型應用場景文檔給出的經典場景是構建基于賬本的子貨幣智能合約以每個錢包的Address作為鍵以錢包余額作為值。給定一個Address即可直接檢索其余額無需遍歷任何列表// 偽代碼示意Address - 余額 storage.map.get(user_address).try_read()無需手動導入StorageMap包含在標準庫 prelude 中即開箱即用。這一點可以在源碼中得到印證sway-lib-std/src/prelude.sw 中明確寫有pub use ::storage::storage_map::*;。注意已知問題雖然StorageMap類型本身在 prelude 中但它的方法要求鍵類型實現Hashtrait。根據 storage.md 的說明使用StorageMap時仍需要顯式導入Hashtrait如use std::hash::*;這是當前版本的已知問題官方計劃在未來版本解決。聲明與初始化在 storage 塊中創建 StorageMap創建一個新的空 StorageMap 需要在合約的storage塊中聲明。完整的可運行示例位于 examples/storage_map/src/main.swcontract; use std::hash::*; storage { map: StorageMapAddress, u64 StorageMap::Address, u64 {}, }聲明 StorageMap 時有兩個必備要素與其它存儲變量完全一致類型注解StorageMapK, V指明鍵類型K與值類型V。初始化器一個空的StorageMap::K, V {}結構體實例。因為StorageMapK, V本身是空結構體它的所有能力都來自方法實現。由于 StorageMap 基于泛型實現標準庫提供的StorageMapK, V可以將任意類型K的鍵映射到任意類型V的值。上例中我們告訴編譯器map會把Address類型的鍵映射為u64類型的值。提示聲明StorageMap時無需添加mut關鍵字——所有存儲變量默認都是可變的storage 本身就是可寫狀態。底層原理StorageMap 如何計算存儲槽位要真正理解 StorageMap需要知道它的值究竟被寫到了哪里。從 storage_map.sw 的實現可以看到關鍵細節// 存儲映射域前綴用于防止與編譯器生成的存儲字段鍵沖突 const STORAGE_MAP_DOMAIN: u8 1; fn get_slot_key(self, key: K) - b256 { sha256((STORAGE_MAP_DOMAIN, key, self.field_id())) }每個鍵值對在存儲中的位置由sha256((STORAGE_MAP_DOMAIN, key, field_id))決定即對三元組做 SHA-256 哈希STORAGE_MAP_DOMAIN值為 1一個單字節前綴。它的作用是隔離命名空間保證用戶傳入的鍵參與哈希計算出的前映像pre-image永遠不會與編譯器為存儲字段自動生成的鍵前映像相同從而避免槽位沖突。key用戶提供的任意類型的鍵。field_id()當前存儲字段在存儲布局中的唯一標識。文檔源碼注釋明確說明StorageMap是零大小存儲類型zero-sized storage type可以嵌套在其他存儲類型內部如StorageMapK, StorageMap因此其方法一律使用field_id而非slot來計算槽位。得到的b256哈希值就是該鍵值對在存儲中的槽位 key。值本身通過 storage_key.sw 中的read_quads/write_quads非動態存儲模式或read_slot/write_slot動態存儲模式進行讀寫。深入閱讀StorageKeyT描述存儲中某個 32 字節槽位加上字偏移word offset處的讀寫位置詳見 sway-lib-std/src/storage/storage_key.sw。向 StorageMap 插入鍵值對insert 方法與存儲注解要向 StorageMap 寫入數據使用insert方法。示例 examples/storage_map/src/main.sw#[storage(write)] fn insert_into_storage_map() { let addr1 Address::from(0x0101010101010101010101010101010101010101010101010101010101010101); let addr2 Address::from(0x0202020202020202020202020202020202020202020202020202020202020202); storage.map.insert(addr1, 42); storage.map.insert(addr2, 77); }這里有兩個必須注意的細節通過storage關鍵字訪問調用insert前必須先用storage關鍵字訪問存儲映射即storage.map.insert(...)。#[storage(write)]注解因為insert需要寫入存儲調用它的 ABI 函數必須標注#[storage(write)]。注意存儲注解對合約內所有嘗試寫入該映射的私有函數同樣適用——任何調用insert的函數無論公開還是私有都必須有相應的#[storage(write)]必要時加上read注解否則編譯無法通過。從源碼看insert的實現非動態存儲模式storage_map.sw#[storage(read, write)] pub fn insert(self, key: K, value: V) where K: Hash, { let key self.get_slot_key(key); write_quads::V(key, 0, value); }它計算槽位后調用write_quads::V(key, 0, value)完成寫入。根據源碼注釋其存儲訪問次數為讀取0次若value恰好占滿整個槽位否則1次讀取將被部分覆蓋的舊數據寫入1次。這提醒我們當值類型小于 32 字節如u64時插入會伴隨一次額外的存儲讀取以保留同一槽位內相鄰的數據。合理設計值類型的布局可以在一定程度上節省 gas。從 StorageMap 讀取值get、try_read 與缺省值處理通過提供key調用get方法即可取回對應的值。示例 examples/storage_map/src/main.sw#[storage(read, write)] fn get_from_storage_map() { let addr1 Address::from(0x0101010101010101010101010101010101010101010101010101010101010101); let addr2 Address::from(0x0202020202020202020202020202020202020202020202020202020202020202); storage.map.insert(addr1, 42); storage.map.insert(addr2, 77); let value1 storage.map.get(addr1).try_read().unwrap_or(0); }上例中value1將得到與第一個地址關聯的值42。關于返回值需要說明get(key)返回的是StorageKeyV它描述該鍵值在存儲中的位置見 storage_map.sw而不是直接返回值通過StorageKey上的.read()可讀取值本身.try_read()返回OptionV如果映射中沒有該鍵對應的值try_read()返回Noneread()則會在槽位為空時 revert。示例程序用unwrap_or(0)處理Option將缺失鍵的value1兜底為0。這種get(key).try_read().unwrap_or(default)的寫法是 Sway 合約中處理鍵不存在場景的標準慣用法可避免不必要的 revert。刪除鍵值對remove 與 try_insert 等進階方法除了insert與get標準庫還提供了完整的增刪查 API見 storage_map.sw。這些是官方文檔的延伸補充在實戰中同樣常用方法簽名說明存儲訪問insert(self, key: K, value: V)插入或覆蓋鍵值對寫 1 次值不滿一槽時另有 1 次讀get(self, key: K) - StorageKeyV返回指向該鍵值的StorageKey無直接訪問remove(self, key: K) - bool清除鍵對應值返回該鍵是否曾存在清除 1 次try_insert(self, key: K, value: V) - ResultV, StorageMapErrorV僅當鍵不存在時插入鍵已存在則返回舊值讀 1 次插入時寫 1 次try_insert的行為值得強調當鍵已存在時它返回Result::Err(StorageMapError::OccupiedError(pre_existing_value))并把舊值返回給你當鍵不存在時插入成功并返回Result::Ok(value)。它是實現冪等寫入類邏輯的利器。錯誤類型定義于同文件pub enum StorageMapErrorV { OccupiedError: V, }多鍵 StorageMap使用元組作為鍵如果業務邏輯需要多個維度定位一條數據例如用戶 × 資產類型 → 余額可以用元組作為鍵實現多鍵映射。示例 examples/storage_map/src/main.swstorage { map_two_keys: StorageMap(b256, bool), b256 StorageMap::(b256, bool), b256 {}, }這里鍵類型是元組(b256, bool)值類型是b256。使用方式與單鍵映射完全一致storage.map_two_keys.insert((key_hash, true), value); let result storage.map_two_keys.get((key_hash, true)).try_read();只要元組中的每個元素類型都實現了Hash整個元組就可以作為StorageMap的鍵。這對復合主鍵類場景非常實用。嵌套 StorageMap映射的值仍是映射StorageMap 的值為任意類型自然也可以是另一個 StorageMap——這允許你構建二維乃至多維的存儲結構。聲明方式見 examples/storage_map/src/main.swstorage { nested_map: StorageMapu64, StorageMapu64, u64 StorageMap::u64, StorageMapu64, u64 {}, }訪問嵌套映射的方式是把外層get與內層insert/get鏈式調用。示例 examples/storage_map/src/main.sw#[storage(read, write)] fn access_nested_map() { storage.nested_map.get(0).insert(1, 42); storage.nested_map.get(2).insert(3, 24); assert(storage.nested_map.get(0).get(1).read() 42); assert(storage.nested_map.get(0).get(0).try_read().is_none()); // Nothing inserted here assert(storage.nested_map.get(2).get(3).read() 24); assert(storage.nested_map.get(2).get(2).try_read().is_none()); // Nothing inserted here }注意這里storage.nested_map.get(0)返回內層StorageMapu64, u64對應的StorageKey隨后直接調用.insert(...)寫入內層映射讀取時用.get(k).read()取值并用.get(k).try_read().is_none()驗證未插入處確實為空。該示例同時演示了assert在測試存儲邏輯時的用法。更進一步與其他存儲集合嵌套由于StorageMap是零大小存儲類型還可以與StorageVec、StorageString、StorageBytes等存儲類型自由組合嵌套。例如把StorageVecT或StorageString作為StorageMap的值相關可運行示例與講解見 advanced_storage.md 及配套示例 examples/nested_storage_variables/src/main.sw。需要注意這類嵌套要求存儲初始化并且在import存儲類型時請使用 glob 導入如use std::storage::storage_vec::*;。使用 StorageMap 的注意事項總結結合官方文檔與倉庫源碼使用StorageMapK, V時有以下幾點務必留意只能用于合約持久化存儲訪問權屬于合約腳本與謂詞中無法使用StorageMap。鍵必須實現Hashinsert、get、remove、try_insert等方法均有where K: Hash約束見 storage_map.sw。使用前需use std::hash::*;導入 Hash trait參見 storage.md 中的已知問題說明。寫操作需要存儲注解任何調用insert、remove等寫方法的函數含私有函數都必須標注#[storage(write)]同時讀寫的函數標注#[storage(read, write)]。不需要mut存儲變量默認可變。關注 gas 開銷值類型不滿 32 字節時插入伴隨額外的存儲讀取頻繁讀寫的熱點映射應合理設計值類型。可自由組合支持元組多鍵、嵌套映射以及與StorageVec/StorageString等的深層嵌套是構建復雜鏈上數據模型的基石。延伸學習完整可運行示例examples/storage_map/src/main.sw含本文全部錨點代碼片段標準庫實現sway-lib-std/src/storage/storage_map.sw存儲位置抽象sway-lib-std/src/storage/storage_key.sw標準庫 preludesway-lib-std/src/prelude.sw存儲類型總覽storage.md嵌套存儲集合進階advanced_storage.md相關集合文檔common-collections 索引結合 examples/storage_map 下的Forc.toml與src/main.sw你可以在本地用forc build直接編譯驗證上述所有代碼片段親手觀察編譯產物從而深入掌握 StorageMap 的完整使用鏈路。【免費下載鏈接】sway Empowering everyone to build reliable and efficient smart contracts.項目地址: https://gitcode.com/GitHub_Trending/sw/sway創作聲明:本文部分內容由AI輔助生成(AIGC),僅供參考