eosio.token合约源码的Github地址为:https://github.com/EOSIO/eosio.contracts/tree/master/contracts/eosio.token。
eosio.token合约源码由头文件eosio.token.hpp和源文件eosio.token.cpp构成,此外还有李嘉图合约文件eosio.token.contracts.md.in。
李嘉图合约(Ricardian Contract)是数字文档,用于定义两方或多方之间交互的条款和条件,指明智能合约的意图,李嘉图合约是人类可读的,同时机器可读的合约。
eosio.token.hpp
eosio.token.hpp头文件定义了eosio.token合约的结构,这里介绍合约类中定义的multi-index数据表和用于创建、发行、管理token的各种Action,完整代码可到Github查看。
accounts和stat数据表
eosio.token合约定义的是EOS主网代币(EOS)的运行机制,数据保存在以下两张表中:
- accounts(账户表):保存EOS账户的代币余额
- stat(代币表):保存代币的统计数据(发行人、最大供应量、现供应量)
eosio.token合约主要就是对这两张数据表的管理
这两张表在eosio.token.hpp中的定义如下
struct [[eosio::table]] account {
asset balance;
uint64_t primary_key()const { return balance.symbol.code().raw(); }
};
struct [[eosio::table]] currency_stats {
asset supply;
asset max_supply;
name issuer;
uint64_t primary_key()const { return supply.symbol.code().raw(); }
};
typedef eosio::multi_index< "accounts"_n, account > accounts;
typedef eosio::multi_index< "stat"_n, currency_stats > stats;
使用cleos get table命令可以查询存储在Multi-index表中的数据
cleos get table [OPTIONS] account scope table
此命令需要指定三部分内容
- account:合约账户名
- scope:数据域
- table:表名
查询accounts表数据的命令如下
cleos -u http://eospush.tokenpocket.pro get table eosio.token vuniyuoxoeub accounts
-u选项指定RPC API接口,这里是tokenpocket为EOS主网提供的接口,scope是要查询的EOS账户名,返回结果如下
{
"rows": [{
"balance": "54517351.9376 EOS"
}
],
"more": false,
"next_key": ""
}
查询stat表数据的命令如下
cleos -u http://eospush.tokenpocket.pro get table eosio.token EOS stat
scope是要查询的代币名,返回结果如下
{
"rows": [{
"supply": "1017746571.6137 EOS",
"max_supply": "10000000000.0000 EOS",
"issuer": "eosio"
}
],
"more": false,
"next_key": ""
}
除了使用cleos get table命令外,还可以使用cleos get currency命令查询accounts表和stat表的数据,命令如下
cleos -u http://eospush.tokenpocket.pro get currency balance eosio.token vuniyuoxoeub
cleos -u http://eospush.tokenpocket.pro get currency stats eosio.token EOS
eosio.token.hpp中声明的函数
eosio.token.hpp中声明了8个函数,其中有2个私有函数,6个Action函数。私有函数只能被合约内部调用,Action是所有EOS账户都可以公开访问的函数。
2个私有函数
//增加资产余额
void add_balance( const name& owner, const asset& value, const name& ram_payer );
//减少资产余额
void sub_balance( const name& owner, const asset& value );
6个Action
//创建代币
[[eosio::action]]
void create( const name& issuer, const asset& maximum_supply);
//发行代币
[[eosio::action]]
void issue( const name& to, const asset& quantity, const string& memo );
//销毁代币
[[eosio::action]]
void retire( const asset& quantity, const string& memo );
//转账
[[eosio::action]]
void transfer( const name& from, const name& to, const asset& quantity, const string& memo );
//添加账户
[[eosio::action]]
void open( const name& owner, const symbol& symbol, const name& ram_payer );
//删除账户
[[eosio::action]]
void close( const name& owner, const symbol& symbol );
eosio.token.cpp
eosio.token.cpp中的代码是对eosio.token.hpp中声明的8个函数的实现。
add_balance(增加资产余额)
void token::add_balance( const name& owner, const asset& value, const name& ram_payer )
{
accounts to_acnts( get_self(), owner.value );
auto to = to_acnts.find( value.symbol.code().raw() );
if( to == to_acnts.end() ) {
to_acnts.emplace( ram_payer, [&]( auto& a ){
a.balance = value;
});
} else {
to_acnts.modify( to, same_payer, [&]( auto& a ) {
a.balance += value;
});
}
}
add_balance首先创建一个accounts类型的实例,accounts是multi_index类型的别名
typedef eosio::multi_index< "accounts"_n, account > accounts;
构造一个multi_index类型需要code和scope两个参数
multi_index( name code, uint64_t scope )
code通过get_self()函数返回,即eosio.token账户;
scope是需要修改余额的EOS账户,需要的是uint64_t类型,传递的是owner.value,因为name类型持有一个uint64_t类型的成员value,用来唯一区分账户。
multi_index的find函数使用主键(primary key)检索数据,这里的主键是代币名(EOS)的uint64_t类型值。
to == to_acnts.end()表示没有检索到数据,使用multi_index的emplace函数增加一条数据;
如果检索到了数据,则使用multi_index的modify函数修改数据,具体是增加了balance的值。
same_payer是一个name类型的实例,value值为0
constexpr static inline name same_payer{};
新增数据到multi_index表会消耗RAM,multi_index的modify函数修改数据时检测到value值为0的name是RAM支付者时,会自动使用创建数据时的账户支付RAM费用(仅修改数据时一般不消耗RAM)。
sub_balance(减少资产余额)
void token::sub_balance( const name& owner, const asset& value ) {
accounts from_acnts( get_self(), owner.value );
const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" );
check( from.balance.amount >= value.amount, "overdrawn balance" );
from_acnts.modify( from, owner, [&]( auto& a ) {
a.balance -= value;
});
}
sub_balance检查输入数据后,使用multi_index的modify函数减少balance的值。
create(创建代币)
void token::create( const name& issuer,
const asset& maximum_supply )
{
require_auth( get_self() );
auto sym = maximum_supply.symbol;
check( sym.is_valid(), "invalid symbol name" );
check( maximum_supply.is_valid(), "invalid supply");
check( maximum_supply.amount > 0, "max-supply must be positive");
stats statstable( get_self(), sym.code().raw() );
auto existing = statstable.find( sym.code().raw() );
check( existing == statstable.end(), "token with symbol already exists" );
statstable.emplace( get_self(), [&]( auto& s ) {
s.supply.symbol = maximum_supply.symbol;
s.max_supply = maximum_supply;
s.issuer = issuer;
});
}
创建代币操作首先使用require_auth检查此Action的调用权限,get_self()定义如下
inline name get_self()const { return _self; }
返回的_self表示合约账户本身,这里就是eosio.token账户,因此create Action只允许eosio.token账户来调用。
然后检验代币名称(由大写字母A-Z组成,不超过7个字符)、发行量是否超过系统最大发行量:(1LL << 62) - 1、代币名称是否重复。
注意,同一个智能合约可以发行多个代币,这里的代币名重复检测只限于合约内,不同合约可以有相同的代币名。
stats是multi_index类型的别名
typedef eosio::multi_index< "stat"_n, currency_stats > stats;
最后调用multi_index的emplace函数在代币表stat中增加一条数据。
issue(发行代币)
void token::issue( const name& to, const asset& quantity, const string& memo )
{
auto sym = quantity.symbol;
check( sym.is_valid(), "invalid symbol name" );
check( memo.size() <= 256, "memo has more than 256 bytes" );
stats statstable( get_self(), sym.code().raw() );
auto existing = statstable.find( sym.code().raw() );
check( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
const auto& st = *existing;
check( to == st.issuer, "tokens can only be issued to issuer account" );
require_auth( st.issuer );
check( quantity.is_valid(), "invalid quantity" );
check( quantity.amount > 0, "must issue positive quantity" );
check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
check( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply");
statstable.modify( st, same_payer, [&]( auto& s ) {
s.supply += quantity;
});
add_balance( st.issuer, quantity, st.issuer );
}
发行代币操作首先进行代币名、附加消息容量、代币存在性的检查。注意这句代码
check( to == st.issuer, "tokens can only be issued to issuer account" );
这里要求代币只能发行给创建时指定的issuer账户,早期的合约代码没有这个限制,在这个issue中进行了修改。
使用require_auth( st.issuer )检测权限,必须是创建代币时指定的issuer账户才能调用此Action。
然后检测此次代币发行量,必须是已发行量和最大发行量之间的值,最后修改stat表和accounts表的数据。
retire(销毁代币)
void token::retire( const asset& quantity, const string& memo )
{
auto sym = quantity.symbol;
check( sym.is_valid(), "invalid symbol name" );
check( memo.size() <= 256, "memo has more than 256 bytes" );
stats statstable( get_self(), sym.code().raw() );
auto existing = statstable.find( sym.code().raw() );
check( existing != statstable.end(), "token with symbol does not exist" );
const auto& st = *existing;
require_auth( st.issuer );
check( quantity.is_valid(), "invalid quantity" );
check( quantity.amount > 0, "must retire positive quantity" );
check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
statstable.modify( st, same_payer, [&]( auto& s ) {
s.supply -= quantity;
});
sub_balance( st.issuer, quantity );
}
销毁代币是和发行代币相反的操作,会使stat表的supply字段值减少,即代币的现供应量减少。
销毁代币的操作也只能由创建代币时指定的issuer账户执行。
transfer(转账)
void token::transfer( const name& from,
const name& to,
const asset& quantity,
const string& memo )
{
check( from != to, "cannot transfer to self" );
require_auth( from );
check( is_account( to ), "to account does not exist");
auto sym = quantity.symbol.code();
stats statstable( get_self(), sym.raw() );
const auto& st = statstable.get( sym.raw() );
require_recipient( from );
require_recipient( to );
check( quantity.is_valid(), "invalid quantity" );
check( quantity.amount > 0, "must transfer positive quantity" );
check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
check( memo.size() <= 256, "memo has more than 256 bytes" );
auto payer = has_auth( to ) ? to : from;
sub_balance( from, quantity );
add_balance( to, quantity, payer );
}
转账操作是EOS用户使用得最多的Action,由发送代币者执行操作,经过一系列数据检查后,使用sub_balance减少发送者余额,使用add_balance增加接收者余额。
转账操作(transfer Action)中有两个比较重要的地方,一是require_recipient
require_recipient( from );
require_recipient( to );
这两句代码把from和to两个账户添加到待通知列表中,通过notify机制,转账双方都会收到交易通知。
二是对转账双方谁支付RAM的判定
auto payer = has_auth( to ) ? to : from;
RAM费用默认由发送方(from)支付,通过has_auth可以指定由谁支付RAM费用,具体实现函数如下
bool apply_context::has_authorization( const account_name& account )const {
for( const auto& auth : act->authorization )
if( auth.actor == account )
return true;
return false;
}
系统会遍历Action的授权(authorization),如果授权中有接收方账户(to)的授权,has_auth就会返回true,RAM费用由接收方支付。
open(添加账户)
void token::open( const name& owner, const symbol& symbol, const name& ram_payer )
{
require_auth( ram_payer );
check( is_account( owner ), "owner account does not exist" );
auto sym_code_raw = symbol.code().raw();
stats statstable( get_self(), sym_code_raw );
const auto& st = statstable.get( sym_code_raw, "symbol does not exist" );
check( st.supply.symbol == symbol, "symbol precision mismatch" );
accounts acnts( get_self(), owner.value );
auto it = acnts.find( sym_code_raw );
if( it == acnts.end() ) {
acnts.emplace( ram_payer, [&]( auto& a ){
a.balance = asset{0, symbol};
});
}
}
open操作用于在accounts表中添加一条某代币余额为0的记录,主要用于项目方空投(Airdrop)时登记。
close(删除账户)
void token::close( const name& owner, const symbol& symbol )
{
require_auth( owner );
accounts acnts( get_self(), owner.value );
auto it = acnts.find( symbol.code().raw() );
check( it != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." );
check( it->balance.amount == 0, "Cannot close because the balance is not zero." );
acnts.erase( it );
}
close操作是open操作的逆操作,用于删除open操作添加的记录,但对于代币余额不为0的记录则无法删除。
版权声明:项目均采集于互联网, 空投币 无法审核全面,且希望大家能赚钱,请谨慎切勿上当受骗!
温馨提示:★★★天上真会掉馅饼!天道酬勤,都是机会!不错过每个空投糖果!真假难以辨认,尽量0撸!