1 说明
本节分两部分,一是介绍如何构建kitties pallet,包括创建与kittes交互的功能;另一部分是介绍开发前端UI,与我们第一部分的链进行交互。
本课程内容较长,可以分几次来学习。
2 本节目标
本节涉及的内容主要如下:
- 学习构建和运行substrate节点的基本模式。
- 编写自定义框架pallet并集成到运行时。
- 了解如何创建和更新存储项。
- 编写pellet相关辅助函数。
- 使用PolkadotJs API将substrate节点连接到自定义前端。
2.1 kitties功能
为了方便学习,我们的kitties subside chain只能做以下事情:
- 可以通过一些原始来源或者通过使用现有小猫进行繁殖创造。
- 以其所有者设定的价格出售。
- 从一个所有者转移到另一个所有者。
2.2 未完善的工作
本教程中不会考虑以下方面:
- 为pallet写测试。
- 使用正确的weight值。
3基本步骤
3.1 安装template-node
- 安装kickstart
kickstart是一个命令行工具,可以用来方便的命名我们的node template,安装命令如下:
cargo install kickstart
然后运行如下命令:
kickstart https://github.com/sacha-l/kickstart-substrate
敲下回车,当出现如下提示时:
Tmp dir: "/tmp"
What are you calling your node? [default: template]:此处请输入kitties
What are you calling your pallet? [default: template]: 此处请输入kitties
这样我们就成功的将node和pallet的名字分别改为了node-kitties和pallet-kitties。
- kickstart命令修改的目录如下:
1、node: 包含节点和runtime以及RPC client交互的所有逻辑;
2、pallets:自定义pallet放置的位置;
3、runtime: 集成pallet实现链的runtime
* 修改runtime/src/lib.rs:
找到:
construct_runtime!(
pub enum Runtime where
Block = Block,
NodeBlock = opaque::Block,
UncheckedExtrinsic = UncheckedExtrinsic
{
System: frame_system::{Pallet, Call, Config, Storage, Event},
RandomnessCollectiveFlip: pallet_randomness_collective_flip::{Pallet, Storage},
Timestamp: pallet_timestamp::{Pallet, Call, Storage, Inherent},
Aura: pallet_aura::{Pallet, Config},
Grandpa: pallet_grandpa::{Pallet, Call, Storage, Config, Event},
Balances: pallet_balances::{Pallet, Call, Storage, Config, Event},
TransactionPayment: pallet_transaction_payment::{Pallet, Storage},
Sudo: pallet_sudo::{Pallet, Call, Config, Storage, Event},
// Include the custom logic from the pallet-kitties in the runtime.
TemplateModule: pallet_kitties::{Pallet, Call, Storage, Event},
}
);
将其中的:
TemplateModule: pallet_kitties::{Pallet, Call, Storage, Event},
改为:
SubstrateKitties: pallet_kitties::{Pallet, Call, Config, Storage, Event},
# 3.2 写pallet_kitties的脚手架
substrate中的pallets是用来定义runtime的逻辑的。在本例子中,我们将定义一个简单的pallet来管理substrate kitties应用的逻辑。
每个FRAME pallet都会有:
* frame_support 和 frame_system依赖的集合;
* 要求的属性宏。
对于pallets中的内容,我们先做如下操作:
+– pallets
| |
| +– kitties
| |
| +– Cargo.toml
| |
| +– src
| |
| +– benchmarking.rs <– Remove file
| |
| +– lib.rs <– Remove contents
| |
| +– mock.rs <– Remove file
| |
| +– tests.rs <– Remove file
|
在/pallets/kitties/src/lib.rs中粘贴如下内容:
#![cfg_attr(not(feature = “std”), no_std)]
pub use pallet::*;
#[frame_support::pallet]
pub mod pallet {
use frame_support::{sp_runtime:
:{Hash, Zero},
dispatch::{DispatchResultWithPostInfo, DispatchResult},
traits::{Currency, ExistenceRequirement, Randomness},
pallet_prelude::};
use frame_system:
:;
use sp_io:
:blake2_128;
// TODO Part II: Struct for holding Kitty information.
// TODO Part II: Enum and implementation to handle Gender type in Kitty struct.
#[pallet::pallet]
#[pallet::generate_store(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>;
// TODO Part II: Specify the custom types for our runtime.
}
// Errors.
#[pallet::error]
pub enum Error<T> {
// TODO Part III
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
// TODO Part III
}
// ACTION: Storage item to keep a count of all existing Kitties.
// TODO Part II: Remaining storage items.
// TODO Part III: Our pallet's genesis configuration.
#[pallet::call]
impl<T: Config> Pallet<T> {
// TODO Part III: create_kitty
// TODO Part III: set_price
// TODO Part III: transfer
// TODO Part III: buy_kitty
// TODO Part III: breed_kitty
}
// TODO Parts II: helper function for Kitty struct
impl<T: Config> Pallet<T> {
// TODO Part III: helper functions for dispatchable functions
// TODO: increment_nonce, random_hash, mint, transfer_from
}
}
运行下面的命令先编译一下:
cargo build -p pallet-kitties
编译后,我们得到如下错误:
failed to resolve: use of undeclared crate or module sp_io
解决此问题需要在/pallets/kitties/Cargo.toml中添加:
[dependencies]
sp-io = { default-features = false, version = ‘4.0.0-dev’, git = ‘github.com/paritytech/substrate.gi..., tag = ‘monthly-2021-10’ }
# 3.3 添加存储
此处我们使用StorageValue。
在pallets/kitties/src/lib.rs中, 我们替换如下部分:
// ACTION: Storage item to keep a count of all existing Kitties.
替换成如下代码:
#[pallet::storage]
#[pallet::getter(fn kitty_cnt)]
/// Keeps track of the number of Kitties in existence.
pub(super) type KittyCnt<T: Config> = StorageValue<_, u64, ValueQuery>;
## 3.4 添加余额实现
在runtime/src/lib.rs添加如下代码:
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances; // <– Add this line
}
编译一下检查是否有错:
cargo build –release
# 4 相关数据结构与存储
## 4.1 定义kitties struct
定义我们的kitties结构体如下(用如下代码替换pallets/kitties/src/lib.rs中的注释“TODO Part II”):
type AccountOf = ::AccountId;
type BalanceOf =
<::Currency as Currency<::AccountId>>::Balance;
// Struct for holding Kitty information.
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct Kitty<T: Config> {
pub dna: [u8; 16],
pub price: Option<BalanceOf>,
pub gender: Gender,
pub owner: AccountOf,
}
同时还需要在pallet顶部添加:
use scale_info::TypeInfo;
此时我们的代码还编译不过,因为Gender类型还未定义。
## 4.2 自定义类型Gender
Gender用来生成kitty,包含两部分:
* 定义枚举类型,表示性别;
* 实现一个helper函数。
### 4.2.1 定义类型
用如下代码
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(T))]
#[cfg_attr(feature = “std”, derive(Serialize, Deserialize))]
pub enum Gender {
Male,
Female,
}
写在pallets/kitties/src/lib.rs中的注释“TODO Part II: Enum and implementation”之下。
因为使用了反序列化的库,因此我们需要在pallets/kitties/Cargo.toml中添加如下:
[dependencies.serde]
default-features = false
version = ‘1.0.119’
在pallet顶部添加:
#[cfg(feature = “std”)]
use serde::{Deserialize, Serialize};
### 4.2.2 实现函数
在注释“TODO Part III: helper functions for dispatchable functions”下面加入如下代码:
//pallets/kitties/src/lib.rs
impl<T: Config> Pallet {
// ACTION #4: helper function for Kitty struct
fn gen_gender() -> Gender {
let random = T::KittyRandomness::random(&b"gender"[..]).0;
match random.as_ref()[0] % 2 {
0 => Gender::Male,
_ => Gender::Female,
}
}
}
## 4.3 实现链上随机数
下面我们要定义KittyRandomness。我们将使用frame_support的 Randomness trait来实现。为实现Randomness trait,我们将:
* 在pallet的config trait中添加:
//pallets/kitties/src/lib.rs
#[pallet::config]
pub trait Config: frame_system::Config {
...
//添加这一行
type KittyRandomness: Randomness<Self::Hash, Self::BlockNumber>;
...
}
* 在runtime中添加:
//runtime/src/lib.rs
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip; // <– ACTION: add this line.
}
* 生成随机DNA
在pallet中添加如下, 在gen_gender下添加:
//pallets/kitties/src/lib.rs
fn gen_dna() -> [u8; 16] {
let payload = (
T:
:random(&b”dna”[..]).0,
<frame_system::Pallet>::block_number(),
);
payload.using_encoded(blake2_128)
}
# 4.4 实现storage
## 4.4.1 理解逻辑
我们使用使用一个唯一的ID作为存储项目的全局密钥,也就是说有一个唯一的key指向我们的kitty对象。为了保证新小猫的ID始终是唯一的,我们可以定义一个存储项来存储从ID到kitty对象的映射。从调度函数内部,我们可以使用以下代码进行冲突检查:
ensure!(!<Kitties>::exists(new_id), “This new id already exists”);
我们的runtime需要注意以下两点:
* 特定的资产、如通证或者kitties,这些将由名为kitties的storage map维护。
* 资产的所有权,如账户ID,这些将由storage map KittiesOwned来处理。
### 4.4.2 使用StorageMap
Kitties定义如下:
#[pallet::storage]
#[pallet::getter(fn kitties)]
pub(super) type Kitties<T: Config> = StorageMap<
_,
Twox64Concat,
T::Hash,
Kitty
;
KittiesOwned定义如下:
#[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
;
将上述两段代码放在pallets/kitties/src/lib.rs中的注释“TODO Part II: Remaining storage items.”下面。
我们还需要在config trait中添加一个新类型MAxKittyOwned,因此我们在添加如下代码:
//pallets/kitties/sec/lib.rs
...
// TODO Part II: Specify the custom types for our runtime.
type KittyRandomness: Randomness<Self::Hash, Self::BlockNumber>;
#[pallet::constant]
type MaxKittyOwned: Get<u32>;
...
接下来,我们在runtime使用我们定义的类型,修改代码如下:
//runtime/src/lib.rs
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
}
然后,我们可以编译一下来检查是否有错:
cargo build --release
我们发现会报如下错误:
error: `SubstrateKitties` does not have #[pallet::genesis_config] defined, perhaps you should remove `Config` from construct_runtime?
5 调度函数,事件和error
前面我们定义了相关的数据结构和存储,下面我们就可以使用这些结构来实现我们的相关函数。主要如下:
- create_kitty(): 允许一个账户创建一个kitty的函数
- mint():更新我们的pallet的存储和error检查的函数,会被create_kitty调用。
- pallet Events:使用FRAME的#[pallet::event]属性
5.1 公有和私有函数
此处create_kitty为公有函数,会调用私有函数mint。
5.2 写create_kitty调度函数
在substrate中的调度函数都是一种结构,就是在#[pallet::call]宏下面的implement<T: Config> Pallet {}代码块中。
5.2.1 weights(权重)
权重是substrate开发中很重要的一部分,它会强制开发者思考自己的函数的复杂度(所以,从这句话其实可以推测出weight实际上是类似于以太坊中gas相关的东东)。关于weight相关资料此处我们不展开,建议看下substrate官方文档。此处我们所有的调度函数都将weight设置成100.
在pallets/kitties/src/lib.rs文件中注释“TODO Part III: create_kitty”添加如下代码:
#[pallet::weight(100)]
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?; // <- add this line
let kitty_id = Self::mint(&sender, None, None)?; // <- add this line
// Logging to the console
log::info!("A kitty is born with ID: {:?}.", kitty_id); // <- add this line
// ACTION #4: Deposit `Created` event
Ok(())
}
因为我们此处使用了log,所以要添加log相关的依赖:
//pallets/kitties/Cargo.toml
[dependencies.log]
default-features = false
version = '0.4.14'
5.3 创建mint函数
我们在注释“TODO: increment_nonce, random_hash, mint, transfer_from”下添加如下函数:
//pallets/kitties/sec/lib.rs
// Helper to mint a Kitty.
pub fn mint(
owner: &T::AccountId,
dna: Option<[u8; 16]>,
gender: Option<Gender>,
) -> Result<T::Hash, Error<T>> {
let kitty = Kitty::<T> {
dna: dna.unwrap_or_else(Self::gen_dna),
price: None,
gender: gender.unwrap_or_else(Self::gen_gender),
owner: owner.clone(),
};
let kitty_id = T::Hashing::hash_of(&kitty);
// Performs this operation first as it may fail
let new_cnt = Self::kitty_cnt().checked_add(1)
.ok_or(<Error<T>>::KittyCntOverflow)?;
// Performs this operation first because as it may fail
<KittiesOwned<T>>::try_mutate(&owner, |kitty_vec| {
kitty_vec.try_push(kitty_id)
}).map_err(|_| <Error<T>>::ExceedMaxKittyOwned)?;
<Kitties<T>>::insert(kitty_id, kitty);
<KittyCnt<T>>::put(new_cnt);
Ok(kitty_id)
}
5.4 实现pallet事件
在我们的代码中给Event的定义添加如下代码:
//pallets/kitties/sec/lib.rs
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config>{
//添加下面这几行
/// A new Kitty was sucessfully created. \[sender, kitty_id\]
Created(T::AccountId, T::Hash),
/// Kitty price was sucessfully set. \[sender, kitty_id, new_price\]
PriceSet(T::AccountId, T::Hash, Option<BalanceOf<T>>),
/// A Kitty was sucessfully transferred. \[from, to, kitty_id\]
Transferred(T::AccountId, T::AccountId, T::Hash),
/// A Kitty was sucessfully bought. \[buyer, seller, kitty_id, bid_price\]
Bought(T::AccountId, T::AccountId, T::Hash, BalanceOf<T>),
}
此时,我们在前面创建的creat_kitty函数中添加调用该事件的代码:
//pallets/kitties/sec/lib.rs
#[pallet::weight(100)]
pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult {
let sender = ensure_signed(origin)?; // <- add this line
let kitty_id = Self::mint(&sender, None, None)?; // <- add this line
// Logging to the console
log::info!("A kitty is born with ID: {:?}.", kitty_id); // <- add this line
// ACTION #4: Deposit `Created` event
Self::deposit_event(Event::Created(sender, kitty_id)); //添加这一行
Ok(())
}
5.5 错误处理
substrate使用#[pallet::error]定义错误,我们在Error中添加如下代码:
//pallets/kitties/sec/lib.rs
#[pallet::error]
pub enum Error<T> {
//添加如下行
/// Handles arithemtic overflow when incrementing the Kitty counter.
KittyCntOverflow,
/// An account cannot own more Kitties than `MaxKittyCount`.
ExceedMaxKittyOwned,
/// Buyer cannot be the owner.
BuyerIsKittyOwner,
/// Cannot transfer a kitty to its owner.
TransferToSelf,
/// Handles checking whether the Kitty exists.
KittyNotExist,
/// Handles checking that the Kitty is owned by the account transferring, buying or setting a price for it.
NotKittyOwner,
/// Ensures the Kitty is for sale.
KittyNotForSale,
/// Ensures that the buying price is greater than the asking price.
KittyBidPriceTooLow,
/// Ensures that an account has enough funds to purchase a Kitty.
NotEnoughBalance,
}
现在,我们可以编译检查一下是否存在错误:
cargo build --release
发现还是会报错,因为我们还差很重要的一部分,就是configuration。
6 和kitties进行交互
下面我们将实现和kitties交互相关的函数。
6.1 为kitty设置价格
为kitty设置价格分为两步,分别如下:
- 检查Kitty的所有权
- 我们用如下代码实现所有权检查:
//pallets/kitties/sec/lib.rs
ensure!(Self::is_kitty_owner(&kitty_id, &sender)?, <Error<T>>::NotKittyOwner);
- is_kitty_owner函数代码如下:
pub fn is_kitty_owner(kitty_id: &T::Hash, acct: &T::AccountId) -> Result<bool, Error<T>> {
match Self::kitties(kitty_id) {
Some(kitty) => Ok(kitty.owner == *acct),
None => Err(<Error<T>>::KittyNotExist)
}
}
- 更新kitty对象的价格:
- 在mint函数中,我们将kitty的价格字段(price)设置为0.因此,我们在set_price函数中要更新此值。因此,我们需要在set_price函数中添加如下代码:
kitty.price = new_price.clone();
<Kitties<T>>::insert(&kitty_id, kitty);
- 发出事件
- 做完动作后要发出事件提醒用户,因此还需在函数中加上如下代码:
// Deposit a "PriceSet" event.
Self::deposit_event(Event::PriceSet(sender, kitty_id, new_price));
所以设置价格的完整代码如下:
//pallets/kitties/sec/lib.rs 将下面的代码放在注释TODO Part III: set_price下方
#[pallet::weight(100)]
pub fn set_price(
origin: OriginFor<T>,
kitty_id: T::Hash,
new_price: Option<BalanceOf<T>>
) -> DispatchResult {
let sender = ensure_signed(origin)?;
// Ensure the kitty exists and is called by the kitty owner
ensure!(Self::is_kitty_owner(&kitty_id, &sender)?, <Error<T>>::NotKittyOwner);
let mut kitty = Self::kitties(&kitty_id).ok_or(<Error<T>>::KittyNotExist)?;
kitty.price = new_price.clone();
<Kitties<T>>::insert(&kitty_id, kitty);
// Deposit a "PriceSet" event.
Self::deposit_event(Event::PriceSet(sender, kitty_id, new_price));
Ok(())
}
//...
//将is_kitty_owner函数的代码放在mint函数下方
pub fn is_kitty_owner(kitty_id: &T::Hash, acct: &T::AccountId) -> Result<bool, Error<T>> {
match Self::kitties(kitty_id) {
Some(kitty) => Ok(kitty.owner == *acct),
None => Err(<Error<T>>::KittyNotExist)
}
}
6.2 交易kitty
交易kitty的函数分为两部分实现,一部分是transfer函数,一部分是transfer_kitty_to函数。
transfer函数的代码如下:
//pallets/kitties/sec/lib.rs 放在// TODO Part III: transfer下面
#[pallet::weight(100)]
pub fn transfer(
origin: OriginFor<T>,
to: T::AccountId,
kitty_id: T::Hash
) -> DispatchResult {
let from = ensure_signed(origin)?;
// Ensure the kitty exists and is called by the kitty owner
ensure!(Self::is_kitty_owner(&kitty_id, &from)?, <Error<T>>::NotKittyOwner);
// Verify the kitty is not transferring back to its owner.
ensure!(from != to, <Error<T>>::TransferToSelf);
// Verify the recipient has the capacity to receive one more kitty
let to_owned = <KittiesOwned<T>>::get(&to);
ensure!((to_owned.len() as u32) < T::MaxKittyOwned::get(), <Error<T>>::ExceedMaxKittyOwned);
Self::transfer_kitty_to(&kitty_id, &to)?;
Self::deposit_event(Event::Transferred(from, to, kitty_id));
Ok(())
}
transfer_kitty_to的代码如下:
//放在is_kitty_owner函数下面
#[transactional]
pub fn transfer_kitty_to(
kitty_id: &T::Hash,
to: &T::AccountId,
) -> Result<(), Error<T>> {
let mut kitty = Self::kitties(&kitty_id).ok_or(<Error<T>>::KittyNotExist)?;
let prev_owner = kitty.owner.clone();
// Remove `kitty_id` from the KittyOwned vector of `prev_kitty_owner`
<KittiesOwned<T>>::try_mutate(&prev_owner, |owned| {
if let Some(ind) = owned.iter().position(|&id| id == *kitty_id) {
owned.swap_remove(ind);
return Ok(());
}
Err(())
}).map_err(|_| <Error<T>>::KittyNotExist)?;
// Update the kitty owner
kitty.owner = to.clone();
// Reset the ask price so the kitty is not for sale until `set_price()` is called
// by the current owner.
kitty.price = None;
<Kitties<T>>::insert(kitty_id, kitty);
<KittiesOwned<T>>::try_mutate(to, |vec| {
vec.try_push(*kitty_id)
}).map_err(|_| <Error<T>>::ExceedMaxKittyOwned)?;
Ok(())
}
6.3 购买kitty
购买kitty分为两步,一是检查是否可以购买,二是支付
- 检查kitty是否可以购买
- 购买kitty时我们需要从两个方面确认可以购买:
- 1、这只kitty的状态是要等待购买;
- 2、当前这只kitty是否在用户的预算之类,并且用户有足够的余额
- 支付
- 支付时直接使用Currency::transfer进行,完了后转移kitty的所有权到买家,最后发出事件。
所以整个buy_kitty函数的完整代码如下:
//pallets/kitties/sec/lib.rs
// TODO Part III: buy_kitty下添加
#[transactional]
#[pallet::weight(100)]
pub fn buy_kitty(
origin: OriginFor<T>,
kitty_id: T::Hash,
bid_price: BalanceOf<T>
) -> DispatchResult {
let buyer = ensure_signed(origin)?;
// Check the kitty exists and buyer is not the current kitty owner
let kitty = Self::kitties(&kitty_id).ok_or(<Error<T>>::KittyNotExist)?;
ensure!(kitty.owner != buyer, <Error<T>>::BuyerIsKittyOwner);
// Check the kitty is for sale and the kitty ask price <= bid_price
if let Some(ask_price) = kitty.price {
ensure!(ask_price <= bid_price, <Error<T>>::KittyBidPriceTooLow);
} else {
Err(<Error<T>>::KittyNotForSale)?;
}
// Check the buyer has enough free balance
ensure!(T::Currency::free_balance(&buyer) >= bid_price, <Error<T>>::NotEnoughBalance);
// Verify the buyer has the capacity to receive one more kitty
let to_owned = <KittiesOwned<T>>::get(&buyer);
ensure!((to_owned.len() as u32) < T::MaxKittyOwned::get(), <Error<T>>::ExceedMaxKittyOwned);
let seller = kitty.owner.clone();
// Transfer the amount from buyer to seller
T::Currency::transfer(&buyer, &seller, bid_price, ExistenceRequirement::KeepAlive)?;
// Transfer the kitty from seller to buyer
Self::transfer_kitty_to(&kitty_id, &buyer)?;
Self::deposit_event(Event::Bought(buyer, seller, kitty_id, bid_price));
Ok(())
}
同时,我们还需要在顶部引入“transactional”,如下:
use frame_support::{
dispatch::{DispatchResult, DispatchResultWithPostInfo},
pallet_prelude::*,
sp_runtime::traits::{Hash, Zero},
traits::{Currency, ExistenceRequirement, Randomness},
transactional, //添加此行
};
6.4 繁殖小猫
繁殖小猫的函数比较简单,代码如下:
//pallets/kitties/sec/lib.rs
// TODO Part III: breed_kitty下添加
#[pallet::weight(100)]
pub fn breed_kitty(
origin: OriginFor<T>,
parent1: T::Hash,
parent2: T::Hash
) -> DispatchResult {
let sender = ensure_signed(origin)?;
// Check: Verify `sender` owns both kitties (and both kitties exist).
ensure!(Self::is_kitty_owner(&parent1, &sender)?, <Error<T>>::NotKittyOwner);
ensure!(Self::is_kitty_owner(&parent2, &sender)?, <Error<T>>::NotKittyOwner);
let new_dna = Self::breed_dna(&parent1, &parent2)?;
Self::mint(&sender, Some(new_dna), None)?;
Ok(())
}
在transfer_kitty_to函数下面添加如下代码:
pub fn breed_dna(parent1: &T::Hash, parent2: &T::Hash) -> Result<[u8; 16], Error<T>> {
let dna1 = Self::kitties(parent1).ok_or(<Error<T>>::KittyNotExist)?.dna;
let dna2 = Self::kitties(parent2).ok_or(<Error<T>>::KittyNotExist)?.dna;
let mut new_dna = Self::gen_dna();
for i in 0..new_dna.len() {
new_dna[i] = (new_dna[i] & dna1[i]) | (!new_dna[i] & dna2[i]);
}
Ok(new_dna)
}
6.5 初始化配置
substrate frame中使用#[pallet::genesis_config]来进行初始化配置:
//pallets/kitties/sec/lib.rs 在注释// TODO Part III: Our pallet's genesis configuration.下面添加
// Our pallet's genesis configuration.
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
pub kitties: Vec<(T::AccountId, [u8; 16], Gender)>,
}
// Required to implement default for GenesisConfig.
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
fn default() -> GenesisConfig<T> {
GenesisConfig { kitties: vec![] }
}
}
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
fn build(&self) {
// When building a kitty from genesis config, we require the dna and gender to be supplied.
for (acct, dna, gender) in &self.kitties {
let _ = <Pallet<T>>::mint(acct, Some(dna.clone()), Some(gender.clone()));
}
}
}
为了让我们的genesisConfig生效,我们需要修改node/src/chain_spec.rs,我们需要添加如下代码:
fn testnet_genesis(
wasm_binary: &[u8],
initial_authorities: Vec<(AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool,
) -> GenesisConfig {
GenesisConfig {
system: SystemConfig {
// Add Wasm runtime to storage.
code: wasm_binary.to_vec(),
changes_trie_config: Default::default(),
},
...
sudo: SudoConfig {
// Assign network admin rights.
key: root_key,
},
//添加下面这几行
substrate_kitties: SubstrateKittiesConfig {
kitties: vec![],
},
}
}
我们还需要在chain_sepc.rs顶部添加如下:
use node_kitties_runtime::SubstrateKittiesConfig;
6.6 编译运行
使用如下命令:
cargo build --release
./target/release/node-kitties --dev --tmp
7 使用Polkadot-JS Apps UI进行测试
- 使用6.6运行我们的链
- 运行Polkadot-js Apps UI
- 使用如下地址:
https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/rpc
- 在界面上,我们找到 “Settings” -> “Developer”,然后输入如下json:
{
"Gender": {
"_enum": [ "Male", "Female"]
},
"Kitty": {
"dna": "[u8; 16]",
"price": "Option<Balance>",
"gender": "Gender",
"ownder": "AccountId"
}
}
- 在界面上,我们可以对小猫进行一些列的操作。
8 完整源码地址
完整的实验源码地址:github.com/anonymousGiga/substrate...