【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);

代码实例:

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(())
        }
    }
}

更新runtime

5.6 Substrate 中的 Strings

你可能希望我们为 kitties 添加一个名称属性!毕竟,谁不会给他们喜欢的东西取名呢?

Substrate 不直接支持字符串。Runtime 存储用于存储 runtime 运行的业务逻辑的状态。它不是存储 UI 所需的一般数据。如果你确实需要将一些任意数据存储到 runtime,你总是可以创建一个 字节数组(Vec<u8>),但更合乎逻辑的做法是将哈希值存储到 IPFS 之类的服务中,然后获取数据用于 UI 展示。这超出了本次研讨会的范围,但可能会在以后为你的 kitty 支持其他元数据时添加。

 

posted @ 2022-05-12 15:10  MintMin  阅读(33)  评论(0编辑  收藏  举报