
这篇文章介绍如何基于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-templatecargo +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集合的成员如下:

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