EOS智能合约开发(十)EOS中eosio.token合约分析

前面文章里,我们部署过eosio.token合约,今天我们就分析一下这个合约。
首先,我们部署eoiso.token合约,通过这个合约,可以创建不同的token,可以由不同的账户部署管理这个合约。所有的token都用这个合约来运行。

在我们部署这个合约之前,我们首先需要创建一个账户来管理这个合约,我们就创建一个eosio.token这个账户来管理这个合约吧。

cleos create account eosio eosio.token EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4

然后我们可以部署可以找到的合同 ${EOSIO_SOURCE}/build/contracts/eosio.token

$ cleos set contract eosio.token build/contracts/eosio.token -p eosio.token
Reading WAST...
Assembling WASM...
Publishing contract...
executed transaction: 528bdbce1181dc5fd72a24e4181e6587dace8ab43b2d7ac9b22b2017992a07ad  8708 bytes  10000 cycles
#         eosio <= eosio::setcode               {"account":"eosio.token","vmtype":0,"vmversion":0,"code":"0061736d0100000001ce011d60067f7e7f7f7f7f00...
#         eosio <= eosio::setabi                {"account":"eosio.token","abi":{"types":[],"structs":[{"name":"transfer","base":"","fields":[{"name"...

合约已经创建了,您可以按以下定义查看接口contracts/eosio.token/eosio.token.hpp

        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 ); //备注信息

从上面接口可以看到,要创建Token,需要调用create(…)方法,使用正确的参数调用该操作。此命令可以按你最大需要去创建一个你定义的一个币种,来创建这个Token,发行人将有权发出问题并执行其他操作,例如冻结,召回和列入所有者白名单。

使用位置参数调用此方法的简明方法:

$ cleos push action eosio.token create '[ "eosio", "1000000000.0000 SYS"]' -p eosio.token
executed transaction: 0e49a421f6e75f4c5e09dd738a02d3f51bd18a0cf31894f68d335cd70d9c0e12  120 bytes  1000 cycles
#   eosio.token <= eosio.token::create          {"issuer":"eosio","maximum_supply":"1000000000.0000 SYS"}

或者,使用命名参数调用此方法的更详细的方法:

$ cleos push action eosio.token create '{"issuer":"eosio", "maximum_supply":"1000000000.0000 SYS"}' -p eosio.token
executed transaction: 0e49a421f6e75f4c5e09dd738a02d3f51bd18a0cf31894f68d335cd70d9c0e12  120 bytes  1000 cycles
#   eosio.token <= eosio.token::create          {"issuer":"eosio","maximum_supply":"1000000000.0000 SYS"}

上面命令是不同的两种写法,都是创建在eosio.token账户上数量"1000000000.0000 SYS"的代币,这笔代币是由eosio.token创建的,也是由eosio.token签名的。
从返回结果分析:
1、这笔交易消耗了120 bytes 1000 cycles.
2、eosio.token <= eosio.token::create 说明,这个代币是由eosio.token创建在eosio.token账户上。
3、{“issuer”:“eosio”,“maximum_supply”:“1000000000.0000 SYS”}说明,资产发布在eosio账户上,数量"1000000000.0000 SYS"。这一点需要注意,下面调用issue接口的时候,需要用eosio账户签名,因为资产发行在eosio账户上。


1、此命令创建了一个新令牌SYS,其中包含4个十分位数,最大供应量为1000000000.0000个SYS。
2、为了创建这个token,我们需要将这个代币创建在eosio.token账户上,因为eosio.token拥有“SYS"这个币种。可以允许其他用户购买“SYS”这个币种。出于这个原因,我们需要用eosio.token对这个币种做签名(-p eosio.token)。

void token::create( account_name issuer,
                    asset        maximum_supply )
{
    //获取授权,如果没有授权,Action调用会中止,事务会回滚
    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;
    }

下面我们分析一下资产发行接口,使用issue( account_name to, asset quantity, string memo )接口,发行者(eosio)可以向useraccount1我们之前创建的帐户发放新token。

命令如下:

$ cleos push action eosio.token issue '[ "useraccount1", "100.0000 SYS", "memo" ]' -p eosio
executed transaction: 822a607a9196112831ecc2dc14ffb1722634f1749f3ac18b73ffacd41160b019  268 bytes  1000 cycles
#   eosio.token <= eosio.token::issue           {"to":"useraccount1","quantity":"100.0000 SYS","memo":"memo"}
>> issue
#   eosio.token <= eosio.token::transfer        {"from":"eosio","to":"useraccount1","quantity":"100.0000 SYS","memo":"memo"}
>> transfer
#         eosio <= eosio.token::transfer        {"from":"eosio","to":"useraccount1","quantity":"100.0000 SYS","memo":"memo"}
#          useraccount1<= eosio.token::transfer        {"from":"eosio","to":"useraccount1","quantity":"100.0000 SYS","memo":"memo"}

这次输出包含几个不同的操作:
一个问题和三个传输。虽然我们签署的唯一行动是issue,issue行动执行“内联转移”,“内联转移”通知发件人账户和收件人帐户。输出指令执行了所有操作,调用它们的顺序以及操作是否生成的一些输出。
为了完成这笔交易总共做了三笔确认:
1、在执行issue函数下,由eosio.token做了一次确认;
2、在执行inline transfer函数下,首先由资产的发行者(eosio)做了一次确认;
3、最后由资产的接收者做一次确认(useraccount1),完成这笔资产分发。

如下图:
EOS智能合约开发(十)EOS中eosio.token合约分析_第1张图片
从技术上讲,eosio.token合约,可以跳过inline transfer,直接修改余额。但是,在这种情况下,eosio.token遵循我们合约约定,该约定要求所有帐户余额可以通过引用它们的转移操作的总和来推导。它还要求通知资金的发送方和接收方,以便它们可以自动处理存款和取款。

我们分析一下issue函数源代码,看看是怎么执行的

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 );
    //检查转给标志的币种是否存在,如果不存在,Action调用会中止,事务会回滚
    eosio_assert( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
    const auto& st = *existing;
    //获取授权,如果没有授权,Action调用会中止,事务会回滚
    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函数,修改状态机信息
    add_balance( st.issuer, quantity, st.issuer );
    //通过调用inline transfer函数后,将交易写入区块中,完成这笔交易。
    if( to != st.issuer ) {
       SEND_INLINE_ACTION( *this, transfer, {st.issuer,N(active)}, {st.issuer, to, quantity, memo} );
    }
}

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;
      });
   }
}

如果要查看广播的实际事务,可以使用-d -j选项指示“不广播”和“将事务返回为json”。

$ cleos push action eosio.token issue '["user", "100.0000 SYS", "memo"]' -p eosio -d -j
{
  "expiration": "2018-08-01T15:20:44",
  "region": 0,
  "ref_block_num": 42580,
  "ref_block_prefix": 3987474256,
  "net_usage_words": 21,
  "kcpu_usage": 1000,
  "delay_sec": 0,
  "context_free_actions": [],
  "actions": [{
      "account": "eosio.token",
      "name": "issue",
      "authorization": [{
          "actor": "eosio",
          "permission": "active"
        }
      ],
      "data": "00000000007015d640420f000000000004454f5300000000046d656d6f"
    }
  ],
  "signatures": [
    "EOSJzPywCKsgBitRh9kxFNeMJc8BeD6QZLagtXzmdS2ib5gKTeELiVxXvcnrdRUiY3ExP9saVkdkzvUNyRZSXj2CLJnj7U42H"
  ],
  "context_free_data": []
}

将代币转移到帐户“Tester”
现在该帐户useraccount1有令牌,我们会转移一些帐户tester。我们useraccount1使用permission参数指示授权此操作-p useraccount1。

$ cleos push action eosio.token transfer '[ "useraccount1", "tester", "25.0000 SYS", "m" ]' -p useraccount1
executed transaction: 06d0a99652c11637230d08a207520bf38066b8817ef7cafaab2f0344aafd7018  268 bytes  1000 cycles
#   eosio.token <= eosio.token::transfer        {"from":"useraccount1","to":"tester","quantity":"25.0000 SYS","memo":"m"}

>> transfer
#          useraccount1<= eosio.token::transfer        {"from":"user","to":"tester","quantity":"25.0000 SYS","memo":"m"}
#        tester <= eosio.token::transfer        {"from":"user","to":"tester","quantity":"25.0000 SYS","memo":"m"}

为了完成这笔交易总共做了两笔确认:
1、在执行transfer 函数下,由useraccount1做了一次确认,并签名;
2、再由资产的接收者(tester )做了一次确认,这笔资产才转移成功;

下面我们分析一下transfer这个函数的源代码

void token::transfer( account_name from,
                      account_name to,
                      asset        quantity,
                      string       memo )
{
    eosio_assert( from != to, "cannot transfer to self" );
    //获取授权,如果没有授权,Action调用会中止,事务会回滚
    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" );

    //调用减掉资产的函数,如果不成功,Action调用会中止,事务会回滚
    sub_balance( from, quantity );
    //调用增加资产的函数,如果不成功,Action调用会中止,事务会回滚
    add_balance( to, quantity, from );
}

这里我们已经分析了eosio.token这个合约。

2018年8月1日整理于深圳

你可能感兴趣的:(区块链,技术篇,区块链开发)