eosio.token.hpp public actions
#include <eosio/asset.hpp>�#include <eosio/eosio.hpp>��CONTRACT token : public contract {� public:� using contract::contract;�� ACTION create( const name& issuer, const asset& maximum_supply);� ACTION issue( const name& to, const asset& quantity, const string& memo );� ACTION transfer( const name& from, const name& to, const asset& quantity, const string& memo );�
static asset get_balance( const name& token_contract_account, const name& owner, const symbol_code& sym_code )� {� accounts accountstable( token_contract_account, owner.value );� const auto& ac = accountstable.get( sym_code.raw() );� return ac.balance;� }�
using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;
Go to:
static asset get_balance(
const name& token_contract_account,
const name& owner,
const symbol_code& sym_code )
Contracts cannot call a getter function on another contract
If another contract wishes to read another, it can use the table API to search the data. Or, for more convenience, a contract can write a function that others can borrow to perform common operations. E.g.
#include “eosio.token.hpp”
...
asset balance = token::get_balance("eosio.token"_n, user, symbol_code("EOS"));
using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>
Instead of
action( permission_level{user,"active"_n}, // permission� "eosio.token"_n, // account "code"� "notify"_n, // action� std::make_tuple(get_self(), user, quantity, memo)
).send();
Use
token::transfer_action eosio_token_transfer("eosio.token"_n, {get_self(), "active"_n});�...
eosio_token_transfer.send(get_self(), user, quantity, memo);
.code permission
To send a code to another contract, it must have a permission set with the actor = the account to send to, and permission = code
https://www.google.com/search?q=eosio.code+permission
eosio.token.hpp private tables and functions
private:� ...
TABLE account {� asset balance;� uint64_t primary_key()const { return balance.symbol.code().raw(); }� };�� typedef eosio::multi_index< "accounts"_n, account > accounts;� ...
� void sub_balance( const name& owner, const asset& value );� void add_balance( const name& owner, const asset& value, const name& ram_payer );� };
Asset class
eosio::asset
amount: int64_t
eosio::symbol
precision: uint8_t
eosio::asset
amount: int64_t 1000.0
precision: uint8_t 4
code: uint64_t "EOS"
eosio::symbol_code
code: uint64_t
symbol sym = symbol("EOS", 4);�asset myAsset = asset(1000.0, sym);�myAsset.is_valid();
TABLE account {� asset balance;� uint64_t primary_key()const { return balance.symbol.code().raw(); }� };
The account name is used as the scope of the table. And the asset symbol as the search index. This means account name need not exist in RAM.
eosio.token.cpp 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 );�}
check( from != to, "cannot transfer to self" );
Check a predicate is true, if not throw an error with a message
auto sym = quantity.symbol.code();
const auto& st = statstable.get( sym.raw() );
...
check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
Use the symbol code (e.g. “EOS”) to find the token in the contracts statistics table.
Make sure the precision (number of decimal places) provided in the argument matches the token. E.g.
“1.000 EOS” as the argument it will fail, because the EOS token has 4 decimal places. It should be “1.0000 EOS”
require_recipient( to );
This is a special wrapper to send an action to another contract with the same action and arguments as the action it was sent. This notification can then be received by another contract. It is the same as writing this:
The to account can receive this by writing a receiver, and execute some code if it wants.
Receiving a notification
[[eosio::on_notify("eosio.token::transfer")]]
void deposit(name from, name to, asset quantity, std::string memo);
This is different from using an ACTION macro. It tells the compiler to trigger this function if and only if the action is sent from the eosio.token account, and wants to execute the transfer() transaction.
The arguments of the function must match the arguments of the sending action. Wildcards supported e.g.
[[eosio::on_notify("*::*")]]
check( quantity.is_valid(), "invalid quantity" );�check( quantity.amount > 0, "must transfer positive quantity" );�...
check( memo.size() <= 256, "memo has more than 256 bytes" );
Check the asset is valid
Check the amount is valid
Check the memo is not too long
auto payer = has_auth( to ) ? to : from;
Check is to account has authorized transaction (returns bool)
Figure out who should pay for RAM
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;� });�}
Code: eosio.token (account with write access / the account the contract is deployed to)
Scope: from
Search with: symbol code
const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" );�check( from.balance.amount >= value.amount, "overdrawn balance" );
Use the muti_index_table API to search for a value and if none is found throws an error.
Searches using the symbol code.
Check that the balance is greater than the amount to be sent.
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;� });� }�}
to_acnts.modify( to, same_payer, [&]( auto& a ) {� a.balance += value;� });
same_payer
Is used to tell the API to charge the same account that was previously charged for RAM for this row.
Extra topics
Debugging with print()
Using containers in tables (vector, map, list etc) is possible
Understanding the dispatcher,
Creating secondary indexes
Singleton tables - use when only on variable (row) is needed
Error handling (note that deferred transactions may be depreciated)
More examples
Contract writing tools
Developer documentation� https://developers.eos.io/
Contact API - guide to the eosio library� https://eosio.github.io/eosio.cdt/latest
eosio.contracts - system contracts� https://github.com/EOSIO/eosio.contracts/tree/master/contracts
Eoslib - eosio library implementations (advanced)� https://github.com/EOSIO/eosio.cdt/tree/master/libraries/eosiolib
Chain libraries - used my nodeos core (advanced)� https://github.com/EOSIO/eos/tree/master/libraries/chain