LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 区块链资讯 > 【Substrate开发教程】19 - Substrate Runtime模块编程的四大宏的整合使用

【Substrate开发教程】19 - Substrate Runtime模块编程的四大宏的整合使用

2020-11-03 松果 来源:区块链网络


这篇文章介绍如何基于vector实现存储集合,并将runtime模块编程的四大宏:decl_storage! decl_event! decl_error! decl_module!整合使用。

Rust标准库的Vec

Vec使用示例:

let mut vec = Vec::new(); vec.push(1); vec.push(2); vec[0] = 7; for x in &vec { println!("{}", x); } //输出:7 2

编写模块代码

集合(Set)是一种数据结构,用于存储没有重复项的数据。

Substrate的存储API没有提供显式声明集合的方法,但可以通过vector或map来实现它们。

Substrate可使用Rust标准库的Vec(Struct std::vec::Vec)作为连续的可增长数组类型,写作Vec<T>。

修改pallets/template/src/lib.rs的代码如下:

#![cfg_attr(not(feature = "std"), no_std)] use frame_support::{decl_error, decl_event, decl_module, decl_storage, dispatch::DispatchResult, ensure}; use frame_system::{self as system, ensure_signed}; use sp_std::prelude::*; pub const MAX_MEMBERS: usize = 16; pub trait Trait: system::Trait { type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; } decl_storage! { trait Store for Module<T: Trait> as VecSet { Members get(fn members): Vec<T::AccountId>; } } decl_event!( pub enum Event<T> where AccountId = <T as system::Trait>::AccountId { MemberAdded(AccountId), MemberRemoved(AccountId), } ); decl_error! { pub enum Error for Module<T: Trait> { AlreadyMember, NotMember, MembershipLimitReached, } } decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { fn deposit_event() = default; type Error = Error<T>; #[weight = 10_000] pub fn add_member(origin) -> DispatchResult { let new_member = ensure_signed(origin)?; let mut members = Members::<T>::get(); ensure!(members.len() < MAX_MEMBERS, Error::<T>::MembershipLimitReached); match members.binary_search(&new_member) { Ok(_) => Err(Error::<T>::AlreadyMember.into()), Err(index) => { members.insert(index, new_member.clone()); Members::<T>::put(members); Self::deposit_event(RawEvent::MemberAdded(new_member)); Ok(()) } } } #[weight = 10_000] fn remove_member(origin) -> DispatchResult { let old_member = ensure_signed(origin)?; let mut members = Members::<T>::get(); match members.binary_search(&old_member) { Ok(index) => { members.remove(index); Members::<T>::put(members); Self::deposit_event(RawEvent::MemberRemoved(old_member)); Ok(()) }, Err(_) => Err(Error::<T>::NotMember.into()), } } } }

下面详细分析模块代码。

配置依赖库

模块代码使用了Rust标准库的Vec,需要在Cargo.toml中配置依赖库,在[dependencies]下添加:

sp-std = { version = '2.0.0', default-features = false }

在模块代码中导入:

use sp_std::prelude::*;

sp_std::prelude模块包含了一些Rust常用的组件:


设置集合最大成员数量

pub const MAX_MEMBERS: usize = 16;

这里使用到了usize类型,是自适应无符号数字类型,大小依赖运行程序的计算机架构,64位架构为64位,32位架构为32位。

声明事件

pub trait Trait: system::Trait { type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>; }

声明事件的语法是固定的,不同的pallet只需要复制这部分代码即可,这里声明一个通用事件。

定义存储

decl_storage! { trait Store for Module<T: Trait> as VecSet { Members get(fn members): Vec<T::AccountId>; } }

定义Vec集合名称为Members,Vec中保存AccountId。

定义事件

decl_event!( pub enum Event<T> where AccountId = <T as system::Trait>::AccountId { MemberAdded(AccountId), MemberRemoved(AccountId), } );

使用枚举(enum)定义事件类型:

MemberAdded:添加成员后触发;MemberRemoved:删除成员后触发;

定义错误处理机制

decl_error! { pub enum Error for Module<T: Trait> { AlreadyMember, NotMember, MembershipLimitReached, } }

使用枚举(enum)定义可能出现的错误类型:

AlreadyMember:无法新增成员,因为已经是;NotMember:无法删除成员,因为还不是;MembershipLimitReached:无法新增成员,因为已达到成员数量上限;

新增成员

模块中定义了一个add_member函数来新增成员到Members集合,添加成员时会做一些条件检查以确保安全。

add_member函数的定义如下:

pub fn add_member(origin) -> DispatchResult { let new_member = ensure_signed(origin)?; let mut members = Members::<T>::get(); ensure!(members.len() < MAX_MEMBERS, Error::<T>::MembershipLimitReached); match members.binary_search(&new_member) { Ok(_) => Err(Error::<T>::AlreadyMember.into()), Err(index) => { members.insert(index, new_member.clone()); Members::<T>::put(members); Self::deposit_event(RawEvent::MemberAdded(new_member)); Ok(()) } } }

这里使用了Rust的match语法,检查列表是否已存在潜在的新成员,以避免添加重复成员。

Vec列表始终是有序的,可以使用二分搜索算法(binary_search)来查找成员,如果查找到该成员已存在则返回错误,如果查找到该成员不存在则调用insert方法插入该成员到Vec列表,然后触发一个事件。

删除成员

删除成员时先在列表中查找调用者,如果不存在则不做任何事,如果存在则二分搜索算法会返回其索引,可将其删除。

fn remove_member(origin) -> DispatchResult { let old_member = ensure_signed(origin)?; let mut members = Members::<T>::get(); match members.binary_search(&old_member) { Ok(index) => { members.remove(index); Members::<T>::put(members); Self::deposit_event(RawEvent::MemberRemoved(old_member)); Ok(()) }, Err(_) => Err(Error::<T>::NotMember.into()), } }

编译

编译命令如下:

cd substrate-node-template cargo +nightly-2020-08-23 build --release

使用了nightly-2020-08-23这个较稳定的cargo nightly版本以避免编译过程中出现bug。


测试

启动node-template

./target/release/node-template --dev

打开https://polkadot.js.org/apps,切换网络为DEVELOPMENT-Local Node:


选项卡选择开发者-交易,“提交下面的外部信息”选择templateModule,会自动获取到定义的函数addMember()和removeMember():


点击右下角“提交交易”按钮,点击“签名并提交”:


就会调用decl_module!中定义的add_member函数,并触发事件MemberAdded。

选项卡选择网络-浏览-链信息,在右侧可以看到最新触发的事件:


可以看到触发的事件是模块代码中定义的templateModule.MemberAdded。

选项卡选择开发者-链状态-存储,“查询所选状态”选择templateModule,会自动出现存储对象members(): Vec<AccountId>,点击右侧加号,可以看到ALICE账户的AccountId已被添加进Vec集合。

再使用BOB账户进行相同操作,Vec集合的成员如下:


还有更多操作如删除成员、触发错误处理机制等,读者可自行探索。

—-

编译者/作者:松果

玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。

LOADING...
LOADING...