substrate加密猫

通过加密猫案例,来完整了解下substrate的整个操作。新版substrate官方文档貌似已经移除了加密猫的实现,以下是使用新版substrate代码结合旧的文档整合而成。
代码结合最新的substrate框架做了调整,已亲自完整测试,按本文操作,该项目可以成功运行。

1. 概述

加密猫现在在各大平台都已经出现了其身影,本文主要实现以下功能:

  • 通过一些原始来源或者通过使用现有小猫进行繁殖创造
  • 以其所有者设定的价格出售
  • 从一个所有者转移到另一个所有者

大体说明:substrate会涉及到一个权重(weights)的概念,会让开发者考虑函数的复杂度,类似于以太坊中gas的高低。本文我们统一weight=100

2. 根据模板template-node创建对应的kitties项目框架

kickstart这是个模板工具,通过该工具,可以将模板生成为我们需要的项目框架,安装:

1
cargo install kickstart

根据模板创建项目框架:

1
kickstart https://github.com/sacha-l/kickstart-substrate

会自动指定tmp目录,然后会提示输入节点名称以及pallet名称,这里依次输入:

1
2
kitties
kitties

然后就能生成项目框架文件了,具体各目录作用,这里就不解释了。看前面文章去。
项目根目录的名称现在应该是:kitties,全局搜索项目,将文本TemplateModule都替换为:SubstrateKitties,后续开发可读性更高一些

然后就可以编译和启动项目了:

1
2
3
4
5
6
7
# 编译
cargo build --release
# 或者使用入下编译
cargo +nightly build --release

# 启动
./target/release/node-kitties --dev

3. 实现pallet_kitties脚手架及其具体功能

也就是pallet的整体框架
pallets/kitties/src目录下,删除benchmarking.rsmock.rstests.rs这三个文件。lib.rs文件中的内容清空,然后加入如下内容:

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
#![cfg_attr(not(feature = "std"), no_std)]

/// Edit this file to define custom logic or remove it if it is not needed.
/// Learn more about FRAME and the core library of Substrate FRAME pallets:
/// <https://docs.substrate.io/v3/runtime/frame>

#[frame_support::pallet]
pub mod pallet {
use frame_support::{
dispatch::{DispatchResult},
pallet_prelude::*,
sp_runtime::traits::{Hash},
traits::{Currency, ExistenceRequirement, Randomness},
transactional,
};
use frame_system::pallet_prelude::*;
use sp_io::hashing::blake2_128;

/// Configure the pallet by specifying the parameters and types on which it depends.
#[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.
}

#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
pub struct Pallet<T>(_);

// The pallet's runtime storage items.
// https://docs.substrate.io/v3/runtime/storage
#[pallet::storage]
#[pallet::getter(fn something)]
// Learn more about declaring storage items:
// https://docs.substrate.io/v3/runtime/storage#declaring-storage-items
// ACTION: store item to keep a count of all existing Kitties.
pub type Something<T> = StorageValue<_, u32>;
// TODO Part II: Remaining sotre items.

// TODO Part III: Our pallet's genesis configuration.
// #[pallet::genesis_config]

// Pallets use events to inform users when important changes are made.
// https://docs.substrate.io/v3/runtime/events-and-errors
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event documentation should end with an array that provides descriptive names for event
// TODO Part III
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
///Error names should be descriptive.
/// Errors should have helpful documentation associated with them.
// TODO Part III
}

// Dispatchable functions allows users to interact with the pallet and invoke state changes.
// These functions materialize as "extrinsics", which are often compared to transactions.
// Dispatchable functions must be annotated with a weight and must return a DispatchResult.
#[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
//简单说,就是内部方法,用来支持上面#[pallet::call]中的方法使用
impl<T: Config> Pallet<T> {
//TODO Part III: helper functions for dispatchable functions
//TODO: increment_noce, random_hash, mint, transfer_from
}
}

其中todo内容为后续要加入的内容(即业务逻辑)

3.1 Cargo.toml添加依赖

1
2
3
4
[dependencies]
sp-io = { default-features = false, version = "6.0.0", git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.26" }
serde = { default-features = false, version = "1.0.119"}
log = { default-features = false, version = "0.4.14" }

3.1 实现存储

此时pallets中的lib.rs内容如下:

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

#[frame_support::pallet]
pub mod pallet {
use frame_support::{
dispatch::{DispatchResult},
pallet_prelude::*,
sp_runtime::traits::{Hash},
traits::{Currency, ExistenceRequirement, Randomness},
transactional,
};
use frame_system::pallet_prelude::*;
use sp_io::hashing::blake2_128;

#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};

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.
// MaxEncodedLen务必需要加上,否则编译无法知晓结构体大小
#[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>,
}
// MaxEncodedLen务必需要加上,否则编译无法知晓结构体大小
#[derive(Clone, Encode, Decode, PartialEq, RuntimeDebug, TypeInfo, MaxEncodedLen)]
#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
pub enum Gender {
Male,
Female,
}
/// Configure the pallet by specifying the parameters and types on which it depends.
#[pallet::config]
pub trait Config: frame_system::Config {
//...
//Part II: Specify the custom types for our runtime.
//新增随机数配置
type KittyRandomness: Randomness<Self::Hash, Self::BlockNumber>;
#[pallet::constant]
type MaxKittyOwned: Get<u32>;
}

// ...
#[pallet::storage]
#[pallet::getter(fn kitty_cnt)]
pub(super) type KittyCnt<T: Config> = StorageValue<_, u64, ValueQuery>;

//存储资产
#[pallet::storage]
#[pallet::getter(fn kitties)]
pub(super) type Kitties<T: Config> = StorageMap<_, Twox64Concat, T::Hash, Kitty<T>>;

//存储资产所有权
#[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>;
//...

//Parts II: helper function for Kitty struct
//简单说,就是内部方法,用来支持上面#[pallet::call]中的方法使用
impl<T: Config> Pallet<T> {
//Part III: helper functions for dispatchable functions
//increment_noce, random_hash, mint, transfer_from
//生成性别
fn gen_gender() -> Gender {
let random = T::KittyRandomness::random(&b"gender"[..]).0;
match random.as_ref()[0] % 2 {
0 => Gender::Male,
_ => Gender::Female,
}
}

//生成DNA
fn gen_dna() -> [u8; 16] {
let payload = (
T::KittyRandomness::random(&b"dna"[..]).0,
<frame_system::Pallet<T>>::block_number(),
);
payload.using_encoded(blake2_128)
}
}
}

runtime中的lib.rs

1
2
3
4
5
6
7
8
9
10
11
//...
impl pallet_kitties::Config for Runtime {
type Event = Event;
type Currency = Balances;
type KittyRandomness = RandomnessCollectiveFlip;
type MaxKittyOwned = MaxKittyOwned;
}
parameter_types! {
//...
pub const MaxKittyOwned: u32 = 9999;
}

3.2 公/私有函数以及事件的实现

其中也包括
主要实现如下内容:

  1. create_kitty(): 允许一个账户创建一个kitty的函数
  2. mint():更新我们的pallet的存储和error检查的函数,会被create_kitty调用。
  3. pallet Events:使用FRAME的#[pallet::event]属性
  4. create_kitty为公有函数,会调用私有函数mint。
  5. 其余用于交互的功能:设置交易价格、交易、购买、繁殖

公有函数是在其中实现,这是要对用户开放的:

1
2
3
4
#[pallet::call] 
impl<T: Config> Pallet<T> {
//...
}

私有函数的实现,是在其中实现,不带有宏:

1
2
3
impl<T: Config> Pallet<T> {
//...
}

新增代码如下:

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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#[pallet::event]
#[pallet::generate_deposit(pub (super) fn deposit_event)]
pub enum Event<T: Config> {
/// Event documentation should end with an array that provides descriptive names for event
// Part III
//添加下面这几行
/// 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>),
}

// Errors inform users that something went wrong.
#[pallet::error]
pub enum Error<T> {
///Error names should be descriptive.
/// Errors should have helpful documentation associated with them.
// Part III
/// 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,
}
//...
//公共方法
#[pallet::call]
impl<T: Config> Pallet<T> {
//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
//发出事件创建
Self::deposit_event(Event::Created(sender, kitty_id));
Ok(())
}
//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(())
}
//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(())
}
//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(())
}
//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(())
}
}

//私有方法:
impl<T: Config> Pallet<T> {
//Part III: helper functions for dispatchable functions
//increment_noce, random_hash, mint, transfer_from
//生成性别
fn gen_gender() -> Gender {
let random = T::KittyRandomness::random(&b"gender"[..]).0;
match random.as_ref()[0] % 2 {
0 => Gender::Male,
_ => Gender::Female,
}
}

//生成DNA
fn gen_dna() -> [u8; 16] {
let payload = (
T::KittyRandomness::random(&b"dna"[..]).0,
<frame_system::Pallet<T>>::block_number(),
);
payload.using_encoded(blake2_128)
}

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

//将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)
}
}

#[transactional]
pub fn transfer_kitty_to(
kitty_id: &T::Hash,
to: &T::AccountId,
) -> Result<(), &'static str> {
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(())
}
//繁殖
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)
}
}

4. 初始化

// TODO Part III: Our pallet's genesis configuration.位置处加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#[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()));
}
}
}

同时为了生效,需要在node/src/chain_spec.rstestnet_genesis的方法中加入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
fn testnet_genesis(
wasm_binary: &[u8],
initial_authorities: Vec<(AuraId, GrandpaId)>,
root_key: AccountId,
endowed_accounts: Vec<AccountId>,
_enable_println: bool,
) -> GenesisConfig {
GenesisConfig {
//...
//加入如下代码
substrate_kitties: SubstrateKittiesConfig {
kitties: vec![],
},
}
}

同时,在node/src/chain_spec.rs的顶部加入:

1
use node_kitties_runtime::SubstrateKittiesConfig;

5. 编译并运行

1
2
3
4
# 编译
cargo build --release
# 运行
./target/release/node-kitties --dev

6. 使用Polkadot-JS Apps UI进行测试

打开polkadot-app ,连接到本地节点,对kitties进行测试
前往"开发者"-"交易"中,调用substrateKitties,则可找到相关方法进行操作

总结

本文编辑完毕

参考

[1] Substrate Kitties - Basic Setup
[2] 使用substrate构建kitties链

  • Copyrights © 2017-2023 Jason
  • Visitors: | Views:

谢谢打赏~

微信