我必须认可,学习eosio一直没有闲庭信步的感受,我能够看到为何不少人说它有一个陡峭的学习曲线。随着eosio软件继续经历大量快速发展,文档数量有限,不多有工做实例可供参考。我已经被困了好几回,也但愿帮助改善下一个开发人员的体验。在本文中,我将经过将其分解为单独的部分来讨论eosio.token
合约。php
eosio.token合约容许建立许多不一样的代币。这使任何人都可以建立和发送代币。每一个代币必须由issuer
账户发行。因为账户能够包含多方,所以你可使用具备全部者和活动权限的普通账户或自定义配置账户来建立和管理代币。每一个代币都是asset
类型,以下所示:java
1000000000.0000 SYS 1.0000 SYMBOL 0.10 SYS
asset
类型是一个数字(若是我没记错的话能够达到18位小数)和一个能够在1-7个大写字母之间的符号。此合约有三个操做可用于与之交互。它们是:建立,发布和转帐。node
建立用于定义新代币的特征。这包括代币asset
符号,最大供应量以及容许发出代币的账户。建立还会将新代币配置保留在区块链上。这意味着新代币配置的存储必须由某人放置。正如你稍后将看到的,部署此合约的账户(在咱们的案例中为'eosio.token')也将支付代币配置存储。python
发布用于增长代币的有效供应。能够持续发出代币,直到达到最大供应量。在代币建立期间定义的issuer
必须批准此操做才能使其成功。android
转帐让一个账户将代币转移到另外一个账户。ios
你应该知道的第一件事是每一个eosio智能合约都属于一个eosio账户。合约基本上是其余账户能够与之交互的对象。合约包含在区块链上执行代码的操做actions
。合约能够直接访问区块链上的存储,删除和更新数据。程序员
将一个action推送到合约须要至少一个账户的受权。根据合约的复杂性,可能须要进一步的账户和权限。账户能够由基于权限的配置中设置的单个或多个我的组成。智能合约只能由一个账户运行,而一个账户只能拥有一个智能合约。最佳作法是为账户和合约提供相同(小写)的名称。web
在你与eosio.token合约进行交互以前,你须要建立一个具备相同名称的账户,并将合约部署到该账户。mongodb
首先建立一个账户编程
$cleos create account eosio eosio.token <OWNER-KEY> <ACTIVE-KEY>
而后编译合约
$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 <eosiolib/asset.hpp> #include <eosiolib/eosio.hpp> #include <string> 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<N(accounts), account> accounts; typedef eosio::multi_index<N(stat), currency_stats> 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合约