唯一性、自定义类型和存储映射
接下来几个基础步骤,使用以下代码段更新您的pallet代码(如果您不想使用模板代码,请跳过此步骤):
#![cfg_attr(not(feature = "std"), no_std)]
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
use frame_support::{
sp_runtime::traits::Hash,
traits::{ Randomness, Currency, tokens::ExistenceRequirement },
transactional
};
use sp_io::hashing::blake2_128;
#[cfg(feature = "std")]
use frame_support::serde::{Deserialize, Serialize};
// ACTION #1: Write a Struct to hold Kitty information.
// ACTION #2: Enum declaration for Gender.
// ACTION #3: Implementation to handle Gender type in Kitty struct.
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);
/// Configure the pallet by specifying the parameters and types it depends on.
#[pallet::config]
pub trait Config: frame_system::Config {
/// Because this pallet emits events, it depends on the runtime's definition of an event.
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
/// The Currency handler for the Kitties pallet.
type Currency: Currency<Self::AccountId>;
// ACTION #5: Specify the type for Randomness we want to specify for runtime.
// ACTION #9: Add MaxKittyOwned constant
}
// Errors.
#[pallet::error]
pub enum Error<T> {
// TODO Part III
}
// Events.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// TODO Part III
}
#[pallet::storage]
#[pallet::getter(fn count_for_kitties)]
pub(super) type CountForKitties<T: Config> = StorageValue<_, u64, ValueQuery>;
// ACTION #7: Remaining storage items.
// TODO Part IV: Our pallet's genesis configuration.
#[pallet::call]
impl<T: Config> Pallet<T> {
// TODO Part III: create_kitty
// TODO Part IV: set_price
// TODO Part IV: transfer
// TODO Part IV: buy_kitty
// TODO Part IV: breed_kitty
}
//** Our helper functions.**//
impl<T: Config> Pallet<T> {
// ACTION #4: helper function for Kitty struct
// TODO Part III: helper functions for dispatchable functions
// ACTION #6: function to randomly generate DNA
// TODO Part III: mint
// TODO Part IV: transfer_kitty_to
}
}
除了此代码,我们还需要导入serde
。将其添加到pallet的 Cargo.toml 文件中,使用匹配的版本作为substrate upstream。
Scaffold Kitty struct
Rust 中的结构是一个有用的构造,可帮助存储具有共同点的数据。出于我们的目的,我们的Kitty将携带多个属性,我们可以将其存储在单个结构中,而不是使用单独的存储项目。在尝试优化存储读取和写入时,这会派上用场,因此我们的runtime可以执行较少的读取/写入来更新多个值。
要包含哪些信息
让我们首先看看单个Kitty将携带哪些信息:
-
dna
:用于识别小猫DNA的哈希值,对应于其独特的特征。DNA还用于繁殖新的小猫咪,并跟踪不同的小猫代。 -
price
:这是一个balance,对应于购买Kitty所需的金额,并由其所有者设置。 -
gender
:可以是Male
或Female
的枚举。 -
owner
:指定单个所有者的帐户 ID。
结构所持有的类型
从上面看我们的结构的项目,我们可以推断出以下类型:
-
[u8; 16]
dna
- 使用 16 个字节来表示小猫的 DNA。 -
BalanceOf
price- 使用 FRAME 的自定义类型
Currency
trait. -
性别
gender
- 我们将创建!
首先,我们需要在声明结构之前添加自定义类型BalanceOf
和AccountOf
。将操作 #1 替换为以下代码段:
type AccountOf<T> = <T as frame_system::Config>::AccountId;
type BalanceOf<T> =
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
// Struct for holding Kitty information.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[scale_info(skip_type_params(T))]
#[codec(mel_bound())]
pub struct Kitty<T: Config> {
pub dna: [u8; 16],
pub price: Option<BalanceOf<T>>,
pub gender: Gender,
pub owner: AccountOf<T>,
}
请注意我们如何使用derive
宏来包含各种辅助traits用到我们的结构中。我们需要添加TypeInfo
,以便让我们的结构访问此特征。在pallet顶部添加以下行:
use scale_info::TypeInfo;
对于Gender
类型 ,我们需要构建自己的自定义枚举和帮助程序函数。
编写自定义类型Gender
我们刚刚创建了一个结构名为Gender
,此类型将处理我们定义的Kitty 性别的枚举。要创建它,您将构建以下部分:
枚举声明,指定
Male
和Female
为我们的 Kitty 结构实现一个帮助程序函数。
-
声明自定义枚举
将 ACTION 项 #2 替换为以下枚举声明:
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Gender {
Male,
Female,
}
注意derive宏必须在枚举声明之前使用。包裹着我们枚举的数据结构,需要与runtime中的其他类型进行交互。为了使用Serialize
和Deserialize
,需要将serde
添加到pallets/kitties/Cargo.toml
中。目前,我们知道如何创建自定义结构。但是,为Kitty结构提供一种分配性别的方法呢?为此,我们需要再学习一件事。
-
为我们的 Kitty 结构实现帮助程序函数
配置结构对于在结构中预定义值非常有用。例如,当设置与另一个函数返回的值相关的值时。在我们的例子中,我们有一个类似的情况,我们需要以一种根据Kitty的DNA设置的方式配置我们的Kitty的Gender
。
我们只会在creating Kittes用到这个函数。我们将创建一个名为gen_gender
的公共函数,该函数返回类型Gender
并使用随机函数在Gender
枚举值之间进行选择。
将 ACTION #4 替换为以下代码段:
fn gen_gender() -> Gender {
let random = T::KittyRandomness::random(&b"gender"[..]).0;
match random.as_ref()[0] % 2 {
0 => Gender::Male,
_ => Gender::Female,
}
}
现在,每当gen_gender()
在pallet内调用时,它都会返回 Gender
的伪随机枚举值。
实现链上随机性
如果我们希望能够区分这些小猫,我们需要赋予它们独特的属性!在上一步中,我们使用了尚未实际定义的KittyRandomness
。
我们将使用frame_support
中的Randomness trait执行此操作。它将能够生成一个随机的种子,我们将用它来创建独特的小猫,并培育新的小猫。
- 在pallet的配置特征中,定义受
Randomness
特征约束的新类型。
来自frame_support
中Randomness
trait 需要使用参数来指定Output
和BlockNumber
泛型。
将 ACTION #5 行替换为:
type KittyRandomness: Randomness<Self::Hash, Self::BlockNumber>;
- 在runtime中指定实际类型。
鉴于我们在pallet的配置中添加了一个新类型,我们需要配置runtime以设置其具体类型。如果我们想更改正在使用的算法,而无需修改pallet内的使用位置,KittyRandomness
可能会派上用场。为了展示这一点,我们将设置 KittyRandomness 类型为 FRAME 的 RandomnessCollectiveFlip 的一个实例。 方便的是,node template已经有一个 RandomnessCollectiveFlip pallet的实例。
在runtime的runtime/src/lib.rs
中设置 KittyRandomness
类型:
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip; // <-- ACTION: add this line.
}
在这里,我们从其接口(Randomness<Self::Hash, Self::BlockNumber>
)中抽象出随机性生成实现(RandomnessCollectiveFlip
)。
- 随机生成DNA
生成DNA类似于使用随机性随机分配性别类型。不同之处在于,我们将使用在上一部分中导入的blake2_128
。如果我们在同一块中多次调用此函数,我们还将使用extrinsic_index从frame_system
pallet中生成不同的哈希。将 ACTION #6 行替换为:
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip; // <-- ACTION: add this line.
}
写入剩余的存储项
为了轻松跟踪我们所有的小猫咪,我们将标准化我们的逻辑,以使用唯一的 ID 作为存储项的全局KEY。这意味着单个唯一键将指向我们的Kitty对象(即我们之前声明的结构)。
为了使其正常工作,我们需要确保新Kitty的ID始终是唯一的。我们可以使用新的存储项Kitties
来执行此操作,该存储项将从ID(哈希)映射到Kitty对象。
使用此对象,我们只需检查此存储项是否已包含使用特定 ID 的映射,即可轻松检查冲突。例如,从可调度函数内部,我们可以使用以下命令进行检查:
ensure!(!<Kitties<T>>::exists(new_id), "This new id already exists");
我们的runtime需要注意:
- 独特的资产,如货币或小猫(这将由名为
Kitties
的存储map持有)。 - 这些资产的所有权,如帐户ID(这将处理一个名为
KittiesOwned
的新存储映射)。
要为结构Kitty
创建存储实例,我们将使用StorageMap
— FRAME 提供给我们的。
存储项的外观如下:
#[pallet::storage]
#[pallet::getter(fn kitties)]
pub(super) type Kitties<T: Config> = StorageMap<
_,
Twox64Concat,
T::Hash,
Kitty<T>,
>;
KittiesOwned
存储项与此类似,只是我们将使用BoundedVec
来跟踪我们将在runtime/src/lib.s
中配置的 Kitties 的最大数量。
#[pallet::storage]
#[pallet::getter(fn kitties_owned)]
/// Keeps track of what accounts own what Kitty.
pub(super) type KittiesOwned<T: Config> = StorageMap<
_,
Twox64Concat,
T::AccountId,
BoundedVec<T::Hash, T::MaxKittyOwned>,
ValueQuery,
>;
复制上面的两个代码片段以替换操作 #7 行。
在检查pallet编译之前,我们需要在配置特征中添加一个新类型MaxKittyOwned
,这是一个pallet常量类型(类似于前面KittyRandomness
的步骤)。将操作 #9 替换为:
#[pallet::constant]
type MaxKittyOwned: Get<u32>;
最后,我们在runtime/src/lib.rs
中定义类型MaxKittyOwned
。这与我们对 Currency 和 KittyRandomness 遵循的模式相同,只是我们将使用 parameter_types ! 宏添加一个固定的 u32!
parameter_types! { // <- add this macro
// One can own at most 9,999 Kitties
pub const MaxKittyOwned: u32 = 9999;
}
/// Configure the pallet-kitties in pallets/kitties.
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip;
type MaxKittyOwned = MaxKittyOwned; // <- add this line
}
检查Kitties 区块链编译
cargo build --release