我必须承认,学习eosio一直没有闲庭信步的感觉,我可以看到为什么很多人说它有一个陡峭的学习曲线。随着eosio软件继续经历大量快速发展,文档数量有限,很少有工作实例可供参考。我已经被困了好几次,也希望帮助改善下一个开发人员的体验。在本文中,我将通过将其分解为单独的部分来讨论eosio.token
合约。
eosio.token合约允许创建许多不同的代币。这使任何人都能够创建和发送代币。每个代币必须由issuer
帐户发行。由于帐户可以包含多方,因此你可以使用具有所有者和活动权限的普通帐户或自定义配置帐户来创建和管理代币。每个代币都是asset
类型,如下所示:
1000000000.0000 SYS
1.0000 SYMBOL
0.10 SYS
asset
类型是一个数字(如果我没记错的话可以达到18位小数)和一个可以在1-7个大写字母之间的符号。此合约有三个操作可用于与之交互。它们是:创建,发布和转账。
创建用于定义新代币的特征。这包括代币asset
符号,最大供应量以及允许发出代币的帐户。创建还会将新代币配置保留在区块链上。这意味着新代币配置的存储必须由某人放置。正如你稍后将看到的,部署此合约的帐户(在我们的案例中为'eosio.token')也将支付代币配置存储。
发布用于增加代币的有效供应。可以持续发出代币,直到达到最大供应量。在代币创建期间定义的issuer
必须批准此操作才能使其成功。
转账让一个帐户将代币转移到另一个帐户。
你应该知道的第一件事是每个eosio智能合约都属于一个eosio帐户。合约基本上是其他帐户可以与之交互的对象。合约包含在区块链上执行代码的操作actions
。合约可以直接访问区块链上的存储,删除和更新数据。
将一个action推送到合约需要至少一个帐户的授权。根据合约的复杂性,可能需要进一步的帐户和权限。帐户可以由基于权限的配置中设置的单个或多个个人组成。智能合约只能由一个帐户运行,而一个帐户只能拥有一个智能合约。最佳做法是为帐户和合约提供相同(小写)的名称。
在你与eosio.token合约进行交互之前,你需要创建一个具有相同名称的帐户,并将合约部署到该帐户。
首先创建一个帐户
$cleos create account eosio eosio.token
然后编译合约
$cd eos/contract/eosio.token
$eosiocpp -o eosio.token.wast eosio.token.cpp
最后将合约部署到帐户上
$cleos set contract eosio.token ../eosio.token
你可以验证合约是否已部署
$cleos get code eosio.token
合约分为两个文件eosio.token.cpp
和eosio.token.hpp
。.hpp
文件定义合约类,操作和表,而.cpp
文件实现操作逻辑。让我们首先看一下将用于实例化合约对象的合约类。(我从eosio.token.hpp
中删除了一些遗留代码)
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#pragma once
#include
#include
#include
namespace eosiosystem {
class system_contract;
}
namespace eosio {
using std::string;
class token : public contract {
public:
token( account_name self ):contract(self){}
void create( account_name issuer,
asset maximum_supply);
void issue( account_name to, asset quantity, string memo );
void transfer( account_name from,
account_name to,
asset quantity,
string memo );
private:
friend eosiosystem::system_contract;
inline asset get_supply( symbol_name sym )const;
inline asset get_balance( account_name owner, symbol_name sym )const;
struct account {
asset balance;
uint64_t primary_key()const { return balance.symbol.name(); }
};
struct currency_stats {
asset supply;
asset max_supply;
account_name issuer;
uint64_t primary_key()const { return supply.symbol.name(); }
};
typedef eosio::multi_index accounts;
typedef eosio::multi_index stats;
void sub_balance( account_name owner, asset value );
void add_balance( account_name owner, asset value, account_name ram_payer );
};
asset token::get_supply( symbol_name sym )const
{
stats statstable( _self, sym );
const auto& st = statstable.get( sym );
return st.supply;
}
asset token::get_balance( account_name owner, symbol_name sym )const
{
accounts accountstable( _self, owner );
const auto& ac = accountstable.get( sym );
return ac.balance;
}
} /// namespace eosio
构造函数和操作被定义为公共成员函数。构造函数采用帐户名称(将是部署合约的帐户,也就是eosio.token)并将其设置为contract变量。请注意,此类继承自eosio::contract
。
表和helper函数作为私有成员提供。两个内联函数在底部定义但从未使用过。这给我们留下了重要的函数sub_balance()
和add_balance()
。这些将由转移操作调用。
定义的两个表是accounts
和stat
。accounts
表由不同的account
对象组成,每个account
对象持有不同代币的余额。stat
表由持有供应,max_supply
和发行者的currency_stats
对象(由struct currency_stats
定义)组成。在继续之前,重要的是要知道该合约将数据保存到两个不同的范围。accounts
表的范围限定为eosio帐户,stat
表的范围限定为代币符号名称。
根据eosio::multi_index
定义,code
是具有写权限的帐户的名称,scope
是存储数据的帐户。
范围本质上是一种在合约中划分数据的方法,以便只能在定义的空间内访问。在代币合约中,每个eosio帐户都用作accounts
表的范围。accounts
表是一个包含多个account
对象的多索引容器。每个account
对象都由其代币符号编制索引,并包含代币余额。使用其范围查询用户的accounts
表时,将返回用户具有现有余额的所有代币的列表。
这是我如何想象它。
在上图中,有一个名为tom的eosio帐户,他有自己的范围。在他的范围内是一个名为accounts
的表。在该表中是一个单独的account
对象,用于他持有的每个代币,SYS
和EOS
。
还有一个名为stat
的第二个表。此表将包含现有代币的状态。新标记在其自己的符号名称范围内创建。范围内是一个包含currency_stats
对象的stat
表。与包含许多不同account
对象的accounts
表不同,stat
表仅包含给定标记符号的单个currency_stats
对象。
操作在.cpp
文件中实现。要创建新代币,必须发送创建操作。Create
有两个参数:发行者,以及新代币的最大供应量。issuer
是唯一允许增加新代币供应的人。issuer
不能超过最高供应量发行。
第一行代码只需要合约帐户本身的授权。这可以在推动操作时使用命令行标志-p eosio.token
给出。
接下来的几行提取传入的maximum_supply
资产的符号并执行一些错误处理。如果任何eosio_assert
失败,那么所有代码都将被回滚,并且交易不会被推送到区块链。
一个stat
表使用符号名称(标记符号)作为其范围构造为statstable
。代码检查代币是否已存在。如果没有,则创建新代币状态并将其保存到区块链中。emplace
函数中的第一个参数_self
意味着此合约帐户eosio.token
将支付投注存储。
请注意,保存supply
的符号,因为它用作定位表行的键,但供应量尚未发出。
你现在可以执行下一个操作,发布。发布将采用将收到已发行代币的帐户,发出的代币数量和备忘录。发布操作在一个中执行两个操作,因为它将修改创建的代币供应并调用转账操作以发送已发布的代币。同样,前几行提取代币符号并执行错误检查。
以下代码部分将使用符号名称作为范围构造stat
表。这用作查找先前使用create action
创建的代币的键。
请注意,从statstable.find()
返回的existing currency_stat
是一个指向找到的对象的迭代器。为简洁起见,声明了一个名为st
的新变量,并将其设置为existing
迭代器指向的实际对象。这让我们可以使用.
运算符访问成员变量而不是指针表示法->
。
创建代币的issuer
者需要签署此交易,并执行更多错误处理。
最后,修改现有代币的currency_stats st
,并将已发放的quantity
添加到supply
。issuer
也将此supply
添加到他们的余额中,以便初始供应可以追溯到他们的帐户。
紧接着,通过SEND_INLINE_ACTION()
调用transfer
函数,这会将资金进行转移。论点如下:
*this
是行动所属的合约代码。transfer
操作的名称。{st.issuer, N(active)}
操作所需的权限。{st.issuer, to, quantity, memo}
操作本身的参数。这将我们带到最后的转账操作。转账操作将从from
,to
,quantity
和memo
获取四个输入参数。from
是谁将发送代币,因此quantity
将从他们的余额中减去。to
是谁将收到代币,因此quantity
将被添加到他们的余额。quantity
是要发送的代币数量,memo
是一个可以与交易一起发送的字符串。memo
未在本合约中使用或存储。
该操作首先要求from
accounts帐户权限并对from
和to
帐户执行发布处理。该符号从quantity
提取并用于获取代币的currency_stats
。
require_recipient()
函数将在操作完成时通知发送方和接收方。
完成更多发布处理,最后调用两个私有函数sub_balance()
和add_balance()
以从发送add_balance()
减去代币余额并增加接收方的代币余额。
这是完整的'eosio.token.cpp'文件。
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#include "eosio.token.hpp"
namespace eosio {
void token::create( account_name issuer,
asset maximum_supply )
{
require_auth( _self );
auto sym = maximum_supply.symbol;
eosio_assert( sym.is_valid(), "invalid symbol name" );
eosio_assert( maximum_supply.is_valid(), "invalid supply");
eosio_assert( maximum_supply.amount > 0, "max-supply must be positive");
stats statstable( _self, sym.name() );
auto existing = statstable.find( sym.name() );
eosio_assert( existing == statstable.end(), "token with symbol already exists" );
statstable.emplace( _self, [&]( auto& s ) {
s.supply.symbol = maximum_supply.symbol;
s.max_supply = maximum_supply;
s.issuer = issuer;
});
}
void token::issue( account_name to, asset quantity, string memo )
{
auto sym = quantity.symbol;
eosio_assert( sym.is_valid(), "invalid symbol name" );
eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );
auto sym_name = sym.name();
stats statstable( _self, sym_name );
auto existing = statstable.find( sym_name );
eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
const auto& st = *existing;
require_auth( st.issuer );
eosio_assert( quantity.is_valid(), "invalid quantity" );
eosio_assert( quantity.amount > 0, "must issue positive quantity" );
eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
eosio_assert( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply");
statstable.modify( st, 0, [&]( auto& s ) {
s.supply += quantity;
});
add_balance( st.issuer, quantity, st.issuer );
if( to != st.issuer ) {
SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
}
}
void token::transfer( account_name from,
account_name to,
asset quantity,
string memo )
{
eosio_assert( from != to, "cannot transfer to self" );
require_auth( from );
eosio_assert( is_account( to ), "to account does not exist");
auto sym = quantity.symbol.name();
stats statstable( _self, sym );
const auto& st = statstable.get( sym );
require_recipient( from );
require_recipient( to );
eosio_assert( quantity.is_valid(), "invalid quantity" );
eosio_assert( quantity.amount > 0, "must transfer positive quantity" );
eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" );
sub_balance( from, quantity );
add_balance( to, quantity, from );
}
void token::sub_balance( account_name owner, asset value ) {
accounts from_acnts( _self, owner );
const auto& from = from_acnts.get( value.symbol.name(), "no balance object found" );
eosio_assert( from.balance.amount >= value.amount, "overdrawn balance" );
if( from.balance.amount == value.amount ) {
from_acnts.erase( from );
} else {
from_acnts.modify( from, owner, [&]( auto& a ) {
a.balance -= value;
});
}
}
void token::add_balance( account_name owner, asset value, account_name ram_payer )
{
accounts to_acnts( _self, owner );
auto to = to_acnts.find( value.symbol.name() );
if( to == to_acnts.end() ) {
to_acnts.emplace( ram_payer, [&]( auto& a ){
a.balance = value;
});
} else {
to_acnts.modify( to, 0, [&]( auto& a ) {
a.balance += value;
});
}
}
} /// namespace eosio
EOSIO_ABI( eosio::token, (create)(issue)(transfer) )
示例命令:
$cleos push action eosio.token create '["usera","21000000.0000 DEMO"]' -p eosio.token usera
$cleos push action eosio.token issue '["usera","21000000.0000 DEMO","issuance"]' -p usera
$cleos push action eosio.token tranfser '["usera","userb","1000000.0000 DEMO","here you go"]' -p usera
表命令:
$cleos get table eosio.token DEMO stat
{
"rows": [{
"supply": "21000000.0000 DEMO"
"max_supply": "2100000000.0000 DEMO"
"issuer": "usera"
}
],
"more": false
}
$cleos get table eosio.token usera accounts
{
"rows": [{
"balance": "20000000.0000 DEMO"
}
],
"more": false
}
$cleos get table eosio.token userb accounts
{
"rows": [{
"balance": "10000000.0000 DEMO"
}
],
"more": false
}
注意:本文是在Dawn4.1代码发布时编写的。
======================================================================
分享一些以太坊、EOS、比特币等区块链相关的交互式在线编程实战教程:
- EOS教程,本课程帮助你快速入门EOS区块链去中心化应用的开发,内容涵盖EOS工具链、账户与钱包、发行代币、智能合约开发与部署、使用代码与智能合约交互等核心知识点,最后综合运用各知识点完成一个便签DApp的开发。
- java以太坊开发教程,主要是针对java和android程序员进行区块链以太坊开发的web3j详解。
- python以太坊,主要是针对python工程师使用web3.py进行区块链以太坊开发的详解。
- php以太坊,主要是介绍使用php进行智能合约开发交互,进行账号创建、交易、转账、代币开发以及过滤器和交易等内容。
- 以太坊入门教程,主要介绍智能合约与dapp应用开发,适合入门。
- 以太坊开发进阶教程,主要是介绍使用node.js、mongodb、区块链、ipfs实现去中心化电商DApp实战,适合进阶。
- C#以太坊,主要讲解如何使用C#开发基于.Net的以太坊应用,包括账户管理、状态与交易、智能合约开发与交互、过滤器和交易等。
- java比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Java代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Java工程师不可多得的比特币开发学习课程。
- php比特币开发教程,本课程面向初学者,内容即涵盖比特币的核心概念,例如区块链存储、去中心化共识机制、密钥与脚本、交易与UTXO等,同时也详细讲解如何在Php代码中集成比特币支持功能,例如创建地址、管理钱包、构造裸交易等,是Php工程师不可多得的比特币开发学习课程。
- tendermint区块链开发详解,本课程适合希望使用tendermint进行区块链开发的工程师,课程内容即包括tendermint应用开发模型中的核心概念,例如ABCI接口、默克尔树、多版本状态库等,也包括代币发行等丰富的实操代码,是go语言工程师快速入门区块链开发的最佳选择。
汇智网原创翻译,转载请标明出处。这里是原文理解eosio.token合约