这篇文章介绍如何在Substrate Runtime创建一个简单的功能齐全的通证(token,或代币)。 编写模块代码 修改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}; 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 Token { pub Balances get(fn get_balance): map hasher(blake2_128_concat) T::AccountId => u64; pub TotalSupply get(fn total_supply): u64 = 21000000; Init get(fn is_init): bool; } } decl_event!( pub enum Event<T> where AccountId = <T as system::Trait>::AccountId { Initialized(AccountId), Transfer(AccountId, AccountId, u64), } ); decl_error! { pub enum Error for Module<T: Trait> { AlreadyInitialized, InsufficientFunds, } } decl_module! { pub struct Module<T: Trait> for enum Call where origin: T::Origin { fn deposit_event() = default; #[weight = 10_000] fn init(origin) -> DispatchResult { let sender = ensure_signed(origin)?; ensure!(!Self::is_init(), <Error<T>>::AlreadyInitialized); <Balances<T>>::insert(&sender, Self::total_supply()); Init::put(true); Self::deposit_event(RawEvent::Initialized(sender)); Ok(()) } #[weight = 10_000] fn transfer(_origin, to: T::AccountId, value: u64) -> DispatchResult { let sender = ensure_signed(_origin)?; let sender_balance = Self::get_balance(&sender); let receiver_balance = Self::get_balance(&to); let updated_from_balance = sender_balance.checked_sub(value).ok_or(<Error<T>>::InsufficientFunds)?; let updated_to_balance = receiver_balance.checked_add(value).expect("Entire supply fits in u64; qed"); <Balances<T>>::insert(&sender, updated_from_balance); <Balances<T>>::insert(&to, updated_to_balance); Self::deposit_event(RawEvent::Transfer(sender, to, value)); Ok(()) } } } 下面详细分析模块代码。 创建从账户到余额的映射 映射(Mapping)在表示拥有某数据时很有用,这里使用Substrate模块内置的StorageMap创建从账户(AccountId)到余额(u64)的映射: decl_storage! { trait Store for Module<T: Trait> as Token { pub Balances get(get_balance): map hasher(blake2_128_concat) T::AccountId => u64; pub TotalSupply get(total_supply): u64 = 21000000; Init get(is_init): bool; } } 每个持有token的账户在该映射中均表示为键(key),其值(value)就是其持有的token数量。 TotalSupply设置token的总供应量,Init跟踪token是否已初始化。 定义事件 decl_event!( pub enum Event<T> where AccountId = <T as system::Trait>::AccountId{ Initialized(AccountId), Transfer(AccountId, AccountId, u64), } ); 这里定义了两个事件: Initialized:token被用户初始化;Transfer:token在账户间转账,三个参数分别代表from、to、value;定义错误处理机制 decl_error! { pub enum Error for Module<T: Trait> { AlreadyInitialized, InsufficientFunds, } } 这里定义了两个错误: AlreadyInitialized:试图初始化已经初始化过的token;InsufficientFunds:转账金额超过可用余额;初始化通证 为了使token可用,账户必须初始化它,初始化token有很多方法,如进行创世(genesis)配置、声明存储过程、lockdrop等。 这里使用一个非常简单的方法,第一个调用init函数的账户将收到所有token,类似于在EOISO区块链上发行通证时调用issue Action。 fn init(origin) -> DispatchResult { let sender = ensure_signed(origin)?; ensure!(!Self::is_init(), <Error<T>>::AlreadyInitialized); <Balances<T>>::insert(&sender, Self::total_supply()); Init::put(true); Self::deposit_event(RawEvent::Initialized(sender)); Ok(()) } 先检查执行条件,以确保token只被初始化一次,然后修改StorageMap中的相关数据并触发Initialized事件。 转账 用户要进行转账,需要持有一些token,然后调用transfer函数,并指定接收者和转账金额作为函数参数。 fn transfer(_origin, to: T::AccountId, value: u64) -> DispatchResult { let sender = ensure_signed(_origin)?; let sender_balance = Self::get_balance(&sender); let receiver_balance = Self::get_balance(&to); let updated_from_balance = sender_balance.checked_sub(value).ok_or(<Error<T>>::InsufficientFunds)?; let updated_to_balance = receiver_balance.checked_add(value).expect("Entire supply fits in u64; qed"); <Balances<T>>::insert(&sender, updated_from_balance); <Balances<T>>::insert(&to, updated_to_balance); Self::deposit_event(RawEvent::Transfer(sender, to, value)); Ok(()) } 在修改StorageMap中的相关数据之前,再次检查可能出现的错误情况,此时无需检查token是否已被初始化。 Rust数字类型自带的checked_sub、checked_add方法返回一个Option<T>,后者调用其方法检查是否存在InsufficientFunds错误,然后修改存储并触发Transfer事件。 这里用到了Rust Option<T>的方法ok_or()和expect()。 ok_or()将Option<T>类型转换成Result<T, E>,把Some(v)映射到Ok(v),把None映射到Err(err)。 expect()需要一个参数用来输出提示信息,这个消息被传递到底层的panic!,它在代码出现错误时提供一个较好的错误消息展示方式。 编译 编译命令如下: 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,会自动获取到定义的函数: init()transfer(to, value)这里选择BOB账户作为init()函数的调用者,2100万个token首先会发送到BOB账户。 点击右下角“提交交易”按钮,点击“签名并提交”: 就会调用decl_module!中定义的init函数,并触发事件Initialized。 再调用transfer(to, value),指定接收账户为ALICE,金额为8888,向ALICE账户转账。 选项卡选择网络-浏览-链信息,在右侧可以看到最新触发的事件: 可以看到触发了模块代码中定义的templateModule.Initialized和templateModule.Transfer事件。 选项卡选择开发者-链状态-存储,“查询所选状态”选择templateModule,查看balances(AccountId): u64中数据: 可以看到ALICE和BOB账户目前的余额。 —- 编译者/作者:松果 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
【Substrate开发教程】22 - 如何在基于Substrate的区块链上发行通证?
2020-11-06 松果 来源:区块链网络
LOADING...
相关阅读:
- YFI分叉YFIR官方发推文表示:YFIR即将产生场景应用!2020-11-06
- 美国大选之争比特币飙升至15700多美金上演大牛市web3.0FIL币能否突破重围2020-11-06
- YFI分叉代币YFIL发推:1/YFIL每天都可以开采2/YFILP2020-11-06
- YFI分叉代币YFIL官方发推:YFI社区期待已久的YFIL将问世2020-11-06
- 基本面向好价格却一路下跌DeFi被低估了吗?2020-11-06