提前假設:看這篇文章的讀者之前已經看過 EOS合約源碼分析:eosio.token

合約源碼下載:

git clone https://github.com/NedAmarril/eosiopowcoin cd eosiopowcoin eosio-cpp -I . eosiopowcoin.cpp –abigen

接下來的就是按源碼註釋來走一遍:

eosiopowcoin.hpp

#pragma once

#include <eosio/asset.hpp>
#include <eosio/eosio.hpp>

#include <string>

namespace eosiosystem {
   class system_contract;
}

namespace eosio {

   using std::string;

   class [[eosio::contract("eosiopowcoin")]] token : public contract {
      public:
         using contract::contract;

         [[eosio::action]]
         void create( const name&   issuer,
                      const asset&  maximum_supply);
         [[eosio::action]]
         void issue( const name& to, const asset& quantity, const string& memo );

         [[eosio::action]]
         void retire( const asset& quantity, const string& memo );

         [[eosio::action]]
         void transfer( const name&    from,
                        const name&    to,
                        const asset&   quantity,
                        const string&  memo );
         [[eosio::action]]
         void open( const name& owner, const symbol& symbol, const name& ram_payer );

         [[eosio::action]]
         void close( const name& owner, const symbol& symbol );

         [[eosio::action]]
         void setupminer(const name& user, const symbol& symbol);

         [[eosio::on_notify("eosio.token::transfer")]]
         void claim(name from, name to, eosio::asset quantity, std::string memo);

         static asset get_supply( const name& token_contract_account, const symbol_code& sym_code )
         {
            stats statstable( token_contract_account, sym_code.raw() );
            const auto& st = statstable.get( sym_code.raw() );
            return st.supply;
         }

         static int get_last_mine( const name& token_contract_account, const symbol_code& sym_code )
         {
            stats statstable( token_contract_account, sym_code.raw() );
            const auto& st = statstable.get( sym_code.raw() );
            return st.minetime;
         }

         static asset get_balance( const name& token_contract_account, const name& owner, const symbol_code& sym_code )
         {
            accounts accountstable( token_contract_account, owner.value );
            const auto& ac = accountstable.get( sym_code.raw() );
            return ac.balance;
         }

         // 獲取當前的挖礦獎勵數值,這個獎勵邏輯是比特幣一樣,每四年產量減半。
         // 第一階段是每次獎勵 50.00000000 個 POW,每次獎勵的產生的間隔是 10 分鐘。
         // 所以可以算出要產完 10500000 個 POW,大概要 4 年的時間。
         // 然後第二階段產量減半,以此類推,所以整體挖礦獎勵是致敬了比特幣。
         asset get_reward( asset currentsupply){

         asset reward;
         if (currentsupply.amount/10000/10000 <= 10500000){ //halvening 0
            reward =  asset(5000000000, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 15750000) {//halvening 1
            reward =  asset(2500000000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 18375000) {//halvening 2

            reward =  asset(1250000000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 19687500) {//halvening 3

            reward =  asset(625000000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20343750) { //halvening 4

            reward =  asset(312500000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20671875) { //halvening 5

            reward =  asset(156250000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20835938) { //halvening 6

           reward =  asset(78125000, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20917969) { //halvening 7

            reward =  asset(39062500, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20958984) { //halvening 8

            reward =  asset(19531250, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20979492) { //halvening 9

            reward =  asset(9765625, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20989746) { //halvening 10

            reward =  asset(4882813, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20994873) { //halvening 11

            reward =  asset(2441406, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20997437) { //halvening 12

            reward =  asset(1220703, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20998718) { //halvening 13

            reward =  asset(610352, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20999359) { //halvening 14

            reward =  asset(305176, symbol("POW", 8));
         }
          else if (currentsupply.amount/10000/10000 <= 20999680) { //halvening 15

           reward =  asset(152588, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999840) { //halvening 16

            reward =  asset(76294, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999920) { //halvening 17

            reward =  asset(38147, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999960) { //halvening 18

            reward =  asset(19073, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999980) { //halvening 19

            reward =  asset(9537, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999990) { //halvening 20

            reward =  asset(4768, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999995) { //halvening 21

            reward =  asset(2384, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 <= 20999998) { //halvening 22

            reward =  asset(1192, symbol("POW", 8));
         }
         else if (currentsupply.amount/10000/10000 < 21000000){ //halvening 23

            reward =  asset(596, symbol("POW", 8));
         }
         else {

            reward =  asset(0, symbol("POW", 8));
         }
         return reward;
         }


         using create_action = eosio::action_wrapper<"create"_n, &token::create>;
         using issue_action = eosio::action_wrapper<"issue"_n, &token::issue>;
         using retire_action = eosio::action_wrapper<"retire"_n, &token::retire>;
         using transfer_action = eosio::action_wrapper<"transfer"_n, &token::transfer>;
         using open_action = eosio::action_wrapper<"open"_n, &token::open>;
         using close_action = eosio::action_wrapper<"close"_n, &token::close>;
      private:
         struct [[eosio::table]] account {
            asset    balance;

            uint64_t primary_key()const { return balance.symbol.code().raw(); }
         };

         struct [[eosio::table]] currency_stats {
            asset    supply;
            asset    max_supply;
            name     issuer;
            int      starttime;
            int      minetime;


            uint64_t primary_key()const { return supply.symbol.code().raw(); }
         };

         typedef eosio::multi_index< "accounts"_n, account > accounts;
         typedef eosio::multi_index< "stat"_n, currency_stats > stats;

         void sub_balance( const name& owner, const asset& value );
         void add_balance( const name& owner, const asset& value, const name& ram_payer );
   };
}

eosiopowcoin.cpp

#include <eosiopowcoin.hpp>
#include <eosio/system.hpp>

namespace eosio {

// 基本上和 eosio.token 一樣,只不過創建的token增加了starttime, minetime兩個屬性,這兩個屬性很重要,後面在挖礦環節就可以理解。
void token::create( const name&   issuer,
                    const asset&  maximum_supply )
{
    require_auth( get_self() );

    auto sym = maximum_supply.symbol;
    check( sym.is_valid(), "invalid symbol name" );
    check( maximum_supply.is_valid(), "invalid supply");
    check( maximum_supply.amount > 0, "max-supply must be positive");

    stats statstable( get_self(), sym.code().raw() );
    auto existing = statstable.find( sym.code().raw() );
    check( existing == statstable.end(), "token with symbol already exists" );

    // 其他都和 eosio.token 一樣
    // 只不過增加了 starttime, minetime
    statstable.emplace( get_self(), [&]( auto& s ) {
       s.supply.symbol = maximum_supply.symbol;
       s.max_supply    = maximum_supply;
       s.issuer        = issuer;
       s.starttime     = current_time_point().sec_since_epoch();
       s.minetime      = s.starttime;
    });
}

// 基本上和 eosio.token 一樣,略過
void token::issue( const name& to, const asset& quantity, const string& memo )
{
    auto sym = quantity.symbol;
    check( sym.is_valid(), "invalid symbol name" );
    check( memo.size() <= 256, "memo has more than 256 bytes" );

    stats statstable( get_self(), sym.code().raw() );
    auto existing = statstable.find( sym.code().raw() );
    check( existing != statstable.end(), "token with symbol does not exist, create token before issue" );
    const auto& st = *existing;
    check( to == st.issuer, "tokens can only be issued to issuer account" );

    require_auth( st.issuer );
    check( quantity.is_valid(), "invalid quantity" );
    check( quantity.amount > 0, "must issue positive quantity" );

    check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
    check( quantity.amount <= st.max_supply.amount - st.supply.amount, "quantity exceeds available supply");

    statstable.modify( st, same_payer, [&]( auto& s ) {
       s.supply += quantity;
    });

    add_balance( st.issuer, quantity, st.issuer );
}

// 基本上和 eosio.token 一樣,略過
void token::retire( const asset& quantity, const string& memo )
{
    auto sym = quantity.symbol;
    check( sym.is_valid(), "invalid symbol name" );
    check( memo.size() <= 256, "memo has more than 256 bytes" );

    stats statstable( get_self(), sym.code().raw() );
    auto existing = statstable.find( sym.code().raw() );
    check( existing != statstable.end(), "token with symbol does not exist" );
    const auto& st = *existing;

    require_auth( st.issuer );
    check( quantity.is_valid(), "invalid quantity" );
    check( quantity.amount > 0, "must retire positive quantity" );

    check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );

    statstable.modify( st, same_payer, [&]( auto& s ) {
       s.supply -= quantity;
    });

    sub_balance( st.issuer, quantity );
}

// 基本上和 eosio.token 一樣,略過
void token::transfer( const name&    from,
                      const name&    to,
                      const asset&   quantity,
                      const string&  memo )
{
    check( from != to, "cannot transfer to self" );
    require_auth( from );
    check( is_account( to ), "to account does not exist");
    auto sym = quantity.symbol.code();
    stats statstable( get_self(), sym.raw() );
    const auto& st = statstable.get( sym.raw() );

    require_recipient( from );
    require_recipient( to );

    check( quantity.is_valid(), "invalid quantity" );
    check( quantity.amount > 0, "must transfer positive quantity" );
    check( quantity.symbol == st.supply.symbol, "symbol precision mismatch" );
    check( memo.size() <= 256, "memo has more than 256 bytes" );

    auto payer = has_auth( to ) ? to : from;

    sub_balance( from, quantity );
    add_balance( to, quantity, payer );
}

// 基本上和 eosio.token 一樣,略過
void token::sub_balance( const name& owner, const asset& value ) {
   accounts from_acnts( get_self(), owner.value );

   const auto& from = from_acnts.get( value.symbol.code().raw(), "no balance object found" );
   check( from.balance.amount >= value.amount, "overdrawn balance" );

   from_acnts.modify( from, owner, [&]( auto& a ) {


a.balance -= value;
      });
}

// 基本上和 eosio.token 一樣,略過
void token::add_balance( const name& owner, const asset& value, const name& ram_payer )
{
   accounts to_acnts( get_self(), owner.value );
   auto to = to_acnts.find( value.symbol.code().raw() );
   if( to == to_acnts.end() ) {
      to_acnts.emplace( ram_payer, [&]( auto& a ){
        a.balance = value;
      });
   } else {
      to_acnts.modify( to, same_payer, [&]( auto& a ) {
        a.balance += value;
      });
   }
}

// 基本上和 eosio.token 一樣,略過
void token::open( const name& owner, const symbol& symbol, const name& ram_payer )
{
   require_auth( ram_payer );

   check( is_account( owner ), "owner account does not exist" );

   auto sym_code_raw = symbol.code().raw();
   stats statstable( get_self(), sym_code_raw );
   const auto& st = statstable.get( sym_code_raw, "symbol does not exist" );
   check( st.supply.symbol == symbol, "symbol precision mismatch" );

   accounts acnts( get_self(), owner.value );
   auto it = acnts.find( sym_code_raw );
   if( it == acnts.end() ) {
      acnts.emplace( ram_payer, [&]( auto& a ){
        a.balance = asset{0, symbol};
      });
   }
}

// 基本上和 eosio.token 一樣,略過
void token::close( const name& owner, const symbol& symbol )
{
   require_auth( owner );
   accounts acnts( get_self(), owner.value );
   auto it = acnts.find( symbol.code().raw() );
   check( it != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." );
   check( it->balance.amount == 0, "Cannot close because the balance is not zero." );
   acnts.erase( it );
}

// 這個是搞什麼,基本上和 open 一樣的函數,略過。
void token::setupminer(const name& user, const symbol& symbol){

   require_auth( user );

   auto sym_code_raw = symbol.code().raw();
   stats statstable( get_self(), sym_code_raw );
   const auto& st = statstable.get( sym_code_raw, "symbol does not exist" );
   check( st.supply.symbol == symbol, "symbol precision mismatch" );

   accounts acnts( get_self(), user.value );
   auto it = acnts.find( sym_code_raw );
   if( it == acnts.end() ) {
      acnts.emplace( user, [&]( auto& a ){
        a.balance = asset{0, symbol};
      });
   }


}

// 整個挖礦邏輯就靠這個函數撐着,詳細讀一下
// 在這個函數的頭文件聲明裏可以看到 
// [[eosio::on_notify("eosio.token::transfer")]]
// 這個clean函數是在 eosio.token::transfer 調用的時候會被觸發。
// 實際上就會表現爲,當轉賬給這個合約賬號的時候,這個claim函數就會被觸發。
// 
// cleos -u https://eospush.tokenpocket.pro transfer practicetest eosiopowcoin '0.0001 EOS'

// executed transaction: 2c40caf8ef17af1c8d13e11f0b58a28cb89295456f7c002a9bb06ac1f8b36a0a  128 bytes  628 us
// 給 eosiopowcoin 轉賬時,先調用了 eosio.token::transfer 合約函數
// #   eosio.token <= eosio.token::transfer        {"from":"practicetest","to":"eosiopowcoin","quantity":"0.0001 EOS","memo":""}
// #  practicetest <= eosio.token::transfer        {"from":"practicetest","to":"eosiopowcoin","quantity":"0.0001 EOS","memo":""}
// #  eosiopowcoin <= eosio.token::transfer        {"from":"practicetest","to":"eosiopowcoin","quantity":"0.0001 EOS","memo":""}
// 以上三行日誌是正常調用 eosio.token::transfer 都會出現的三行日誌。

// 以下三行日誌是在 claim 函數里,再一次觸發了 eosio.token::transfer 函數後出現的三行日誌。只不過frome和to反過來,
// 從而做到把轉給 eosiopowcoin 合約的代幣原封不動的轉回去。
// #   eosio.token <= eosio.token::transfer        {"from":"eosiopowcoin","to":"practicetest","quantity":"0.0001 EOS","memo":"Refund EOS"}
// #  eosiopowcoin <= eosio.token::transfer        {"from":"eosiopowcoin","to":"practicetest","quantity":"0.0001 EOS","memo":"Refund EOS"}
// #  practicetest <= eosio.token::transfer        {"from":"eosiopowcoin","to":"practicetest","quantity":"0.0001 EOS","memo":"Refund EOS"}

// 這個就是
// #  eosiopowcoin <= eosiopowcoin::transfer       {"from":"eosiopowcoin","to":"practicetest","quantity":"0.00020770 POW","memo":"Mine POW"}
// #  practicetest <= eosiopowcoin::transfer       {"from":"eosiopowcoin","to":"practicetest","quantity":"0.00020770 POW","memo":"Mine POW"}

void token::claim(name from, name to, eosio::asset quantity, std::string memo)
{
    // 必須是轉入方是本合約賬號,且轉出方不是本合約賬號。
   if (to != get_self() || from == get_self()) 
         return;


   accounts to_acnts( get_self(), from.value );
   auto tor = to_acnts.find( symbol_code("POW").raw() );
   check(tor != to_acnts.end(), "Must initialize POW before mining. Please use setupminer action to enable mining"); 

   // 重新觸發一次 eosio.token::transfer 函數,把 from, to 反過來
   // 從而實現把轉來的代幣原路轉回去。
   action{
         permission_level{get_self(), "active"_n},
         "eosio.token"_n,
         "transfer"_n,
         std::make_tuple(get_self(), from, quantity, std::string("Refund EOS"))
   }.send();              

   // 獲取POW的上一次挖礦時間。
   int minetime = get_last_mine(get_self(), symbol_code("POW"));
   // 當前時間,注意區塊鏈編程當前時間是和區塊有關,而不是機器的本機時間。
   int currenttime = current_time_point().sec_since_epoch();
   // 當前時間距離上一次挖礦時間的時間間隔。
   int timepassed = (currenttime - minetime);

   // 獲取POW當前發行量
   asset supply = eosio::token::get_supply(get_self(), symbol_code("POW"));    
   // 根據當前發行量算出獎勵值,這個獎勵值就是本次合約調用要獎勵給調用方的數量
   asset reward = get_reward(supply);
   // 獲取當前此合約賬號的POW代幣餘額
   asset balance = eosio::token::get_balance(get_self(), get_self(), symbol_code("POW"));

   // 上一次挖礦間隔是否已經超過了10分鐘,可以理解爲 10 分鐘出一個新塊。
   if (timepassed >= 600){
       // 如果超過10分鐘的話,意味着我們需要產生一次新的貨幣發行,也就是 issue 。
       // 這裏比較有意思的是,之前寫的代幣,基本上都是創建代幣的人去手動操作 issue。而這裏的 issue 是自動的,是根據時間來的。
       // rewardcount 大部分情況下會=1,除非這個貨幣冷門到幾乎沒人來挖,否則大概率兩次觸發的間隔都是小於 10 分鐘的。
       // 所以也就是說這裏的 issuereward 大部分情況下會等於 reward
      int rewardcount = timepassed / 600;
      asset issuereward = reward * rewardcount;

      // 調用一次自身賬號合約的 issue 操作,發行 issuereward 數量的代幣。
      action{
            permission_level{get_self(), "active"_n}, 
            get_self(),
            "issue"_n,
            std::make_tuple(get_self(), issuereward, std::string("Issue POW"))
         }.send(); 

      // 餘額加上剛發行的數量
      balance += issuereward;


      // 檢查POW是否存在
      stats statstable( get_self(), symbol_code("POW").raw() );
      auto existing = statstable.find( symbol_code("POW").raw() );
      check( existing != statstable.end(), "token with symbol does not exist" );
      const auto& st = *existing;

      // 把挖礦時間更新到當前時間
         statstable.modify( st, same_payer, [&]( auto& s ) {
            s.minetime = currenttime;
         });

      } 

      // 把當前餘額分成40000多份,每次只轉出去一份。否則如果這裏不分多份的話,可以試想一下會出現什麼情況?
      // 就是剛增發出來的代幣,被第一個觸發的交易的賬號全部轉走了。
      // 那顯然有點太不雨露均霑了。所以分成多份。
      // 儘量讓每一個挖礦的人都能拿到好處,至於多少好處,要靠挖礦的cpu來增加交易頻次,從而挖更多的份額。
      // 也就是變相的 cpu 挖礦了。
      balance /= 40000;

      if (balance > asset(0, symbol("POW", 8))){
          // 只要餘額分成多份後的結果,每一個的數值仍然大於0 ,則把這個份額的代幣數量轉回觸發這次挖礦的人的賬號。
          // 這就是挖礦的最後一步。
         action{
           permission_level{get_self(), "active"_n},
           get_self(),
           "transfer"_n,
           std::make_tuple(get_self(), from, balance, std::string("Mine POW"))
           }.send();

      }              

   }

}

小結

讀懂 EOS合約源碼分析:eosio.token 之後其實再讀這個合約代碼的時候, 基本上不會有什麼難點了。 都在源碼裏了。

相關文章