理解eos区块链的eosio.token合约

我必须认可,学习eosio一直没有闲庭信步的感受,我能够看到为何不少人说它有一个陡峭的学习曲线。随着eosio软件继续经历大量快速发展,文档数量有限,不多有工做实例可供参考。我已经被困了好几回,也但愿帮助改善下一个开发人员的体验。在本文中,我将经过将其分解为单独的部分来讨论eosio.token合约。php

什么是eosio.token合约?

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.cppeosio.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()。这些将由转移操做调用。

定义的两个表是accountsstataccounts表由不一样的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对象,用于他持有的每一个代币,SYSEOS

还有一个名为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添加到supplyissuer也将此supply添加到他们的余额中,以便初始供应能够追溯到他们的账户。

紧接着,经过SEND_INLINE_ACTION()调用transfer函数,这会将资金进行转移。论点以下:

  • 1.*this是行动所属的合约代码。
  • 2.transfer操做的名称。
  • 3.{st.issuer, N(active)}操做所需的权限。
  • 4.{st.issuer, to, quantity, memo}操做自己的参数。

这将咱们带到最后的转帐操做。转帐操做将从fromtoquantitymemo获取四个输入参数。from是谁将发送代币,所以quantity将从他们的余额中减去。to是谁将收到代币,所以quantity将被添加到他们的余额。quantity是要发送的代币数量,memo是一个能够与交易一块儿发送的字符串。memo未在本合约中使用或存储。

该操做首先要求from accounts账户权限并对fromto账户执行发布处理。该符号从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合约

相关文章
相关标签/搜索