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),完成這筆資產分發。

如下圖:
這裏寫圖片描述
從技術上講,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日整理於深圳

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章