substrate学习笔记6:使用substrate构建kitties链

编程/开发
456
0
0
2022-09-20

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...

9 参考文档

docs.substrate.io/tutorials/v3/kit...