LOADING...
LOADING...
LOADING...
当前位置: 玩币族首页 > 币圈百科 > eosio.token合约源码分析

eosio.token合约源码分析

2020-06-19 松果 来源:区块链网络

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的记录则无法删除。

—-

编译者/作者:松果

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

LOADING...
LOADING...