【Substrate Collectables教程】【第1章基础】6. 存储Structure
存储结构体
如果你认为每个人都有自己的 number 是一件很酷的事,那让我们试着给每个人添加虚拟 kitties。
首先,我们需要以 struct
的形式定义我们的 kitties 具有哪些属性,然后我们需要学习如何在 runtime 存储中存储这些自定义结构。
5.1 定义一个自定义 Struct
你可以为 runtime 定义一个自定义结构,如下所示:
#[derive(Encode, Decode, Default, Clone, PartialEq)] #[cfg_attr(feature = "std", derive(Debug))] pub struct MyStruct<A, B> { some_number: u32, some_generic: A, some_other_generic: B, }
要使用自定义的 Encode
和 Decode
traits,你需要从 parity_codec
crate 中导入它们:
use parity_codec::{Encode, Decode};
与在其他语言中定义结构体相比,这应该看起来很正常。但是,你会在 runtime 开发中注意到这个声明的两个奇怪之处...
5.2 使用 Generics
你会注意到我们使用泛型定义了我们的示例结构来作为我们的一个存储类型。当我们尝试在结构中使用 Substrate
中的类型(如 AccountId
或 Balances
)时,这显得非常重要,因为每次我们在使用我们的结构体时都需要传递这些类型。
因此,如果我们想存储 Balance
和 Hash
类型的数据,我们需要像这样定义我们的存储项:
decl_storage! { trait Store for Module<T: Trait> as Example { MyItem: map T::AccountId => MyStruct<T::Balance, T::Hash>; } }
为了清楚起见,我们将 T::AccountId
的泛型类型命名为 AccountId
,将 T::Balance
的泛型类型命名为 Balance
。你可以使用逗号分隔,并根据需要在此模式后添加更多泛型。
5.3 Derive Macro
你会注意到的另一件事是顶部的 #[derive(...)]
。这是 Rust 编译器提供的属性,提供了某些 trait 的基本实现。第二行,#[cfg_attr(feature = "std", derive(Debug))]
对 Debug
trait 做了同样的事情,但仅在使用“标准”库时启用,即在编译本机二进制文件而不是 Wasm 的时候。你可以在这里了解更多相关信息。出于本教程的目的,你可以将其视为 magic。
5.4 Module 函数中的自定义 Struct
现在我们已经在 runtime 存储中初始化了自定义结构,接着我们可以存储具体值并对其进行修改。
以下是使用 module 函数创建结构并将其插入到存储的示例:
decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { fn create_struct(origin, value: u32, balance: T::Balance, hash: T::Hash) -> Result { let sender = ensure_signed(origin)?; let new_struct = MyStruct { some_number: value, some_generic: balance, some_other_generic: hash, }; <MyItem<T>>::insert(sender, new_struct); Ok(()) } } }
5.5 更新你 runtime 中的 storage map 以存储 Kitty
结构体而不是 u64
一个 Kitty
应该具有以下属性:
id
:Hash
dna
:Hash
price
:Balance
gen
:u64
我们已经为你创建了 create_kitty()
函数的框架,但你需要添加逻辑。具体逻辑包含使用 Kitty
object 创建 new_kitty
,并将该 object 存储到 runtime 存储中。
要初始化 T::Hash
和 T::Balance
,你可以使用:
let hash_of_zero = <T as system::Trait>::Hashing::hash_of(&0); let my_zero_balance = <T::Balance as As<u64>>::sa(0);
代码实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | use support::{decl_storage, decl_module,StorageValue,StorageMap}; // add this line use support::{dispatch::Result}; use system::ensure_signed; use runtime_primitives::traits::{As, Hash}; use parity_codec::{Encode, Decode}; // pub trait Trait: system::Trait {} // NOTE: We have added this struct template for you #[derive(Encode, Decode, Default, Clone, PartialEq)] #[cfg_attr(feature = "std" , derive(Debug))] pub struct Kitty<Hash, Balance> { // ACTION: Define the properties of your kitty struct here // - `id` as a `Hash` // - `dna` as a `Hash` // - `price` as a `Balance` // - `gen` as a `u64` id: Hash, dna: Hash, price: Balance, gen: u64, } // ACTION: Update this to use `balances::Trait` to access T::AccountId pub trait Trait: balances::Trait {} decl_storage! { trait Store for Module<T: Trait> as KittyStorage { // Declare storage and getter functions here MyU32: u32; MyBool get(my_bool_getter): bool; // 创建一个value的存储值,用于存储u64类型 Value: u64; SomeValue get(som_value_getter): map u32 => u32; MyValue: map T::AccountId => u32; // ACTION: Update this storage item to be a `map` from `T::AccountId` to `u64` Valuemap: map T::AccountId => u64; // ACTION: Update this variable to be named `OwnedKitty` // ACTION: Add a getter function named `kitty_of_owner` // ACTION: Update this storage item to store a `Kitty<T::Hash, T::Balance>` OwnedKitty get(kitty_of_owner): map T::AccountId => Kitty<T::Hash,T::Balance>; } } decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { // Declare public functions here fn my_function(origin,input_bool: bool) -> Result { // 检验,"Ensure" that the transaction is signed let _sender = ensure_signed(origin)?; <MyBool<T>>::put(input_bool); Ok(()) } fn set_value(origin,value: u64) -> Result{ // "Ensure" that the transaction is signed let sender = ensure_signed(origin)?; // "Put" the value into storage <Value<T>>::put(value); Ok(()) } fn set_valuemap(origin,valuemap: u64) -> Result{ // "Ensure" that the transaction is signed let sender = ensure_signed(origin)?; // "Put" the value into storage <Valuemap<T>>::insert(sender,valuemap); Ok(()) } // NOTE: This function template has changed from the previous section fn create_kitty(origin) -> Result { let sender = ensure_signed(origin)?; // ACTION: Create a `Kitty` object named `new_kitty` here // HINT: You can generate a hash with `<T as system::Trait>::Hashing::hash_of(&0)` // and you can generate a `0` balance with `<T::Balance as As<u64>>::sa(0)` let new_kitty = Kitty{ id: <T as system::Trait>::Hashing::hash_of(& 0 ), dna: <T as system::Trait>::Hashing::hash_of(& 0 ), price: <T::Balance as As<u64>>::sa( 0 ), gen: 0 , }; // ACTION: Store your `new_kitty` into the runtime storage <OwnedKitty<T>>::insert(&sender, new_kitty); Ok(()) } } }<br><br> |
更新runtime
5.6 Substrate 中的 Strings
你可能希望我们为 kitties 添加一个名称属性!毕竟,谁不会给他们喜欢的东西取名呢?
Substrate 不直接支持字符串。Runtime 存储用于存储 runtime 运行的业务逻辑的状态。它不是存储 UI 所需的一般数据。如果你确实需要将一些任意数据存储到 runtime,你总是可以创建一个 字节数组(Vec<u8>
),但更合乎逻辑的做法是将哈希值存储到 IPFS 之类的服务中,然后获取数据用于 UI 展示。这超出了本次研讨会的范围,但可能会在以后为你的 kitty 支持其他元数据时添加。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现