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 此命令需要指定三部分内容 查询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的记录则无法删除。 —- 编译者/作者:松果 玩币族申明:玩币族作为开放的资讯翻译/分享平台,所提供的所有资讯仅代表作者个人观点,与玩币族平台立场无关,且不构成任何投资理财建议。文章版权归原作者所有。 |
eosio.token合约源码分析
2020-06-19 松果 来源:区块链网络
LOADING...
相关阅读:
- 汹涌的后浪,借父母15万进场,各种骚操作做波段,然后赚了120万2020-08-05
- BetaEX交易所凉凉,原班人马出手打造的一个镰刀交易所,果然镰刀就是2020-08-05
- 尽量远离小交易所,小交易所都不知道已经凉了多少个了,不要跟自己2020-08-05
- 六月份跨链重大进展总结2020-08-05
- DeFi界的狗狗币Tendies来踢馆2020-08-05