4-從零開發(fā)EOS區(qū)塊鏈小游戲系列 - 加入Token體系

目錄

?Token并非區(qū)塊鏈獨有,在區(qū)塊鏈世界體現(xiàn)為一種權益證明。而且在不同的應用場景也叫法也可以不同,可以是票證、股份、代幣。而在EOS中,代幣符號就叫做EOS,代幣可以交易,可以購買內存、和計算網(wǎng)絡資源,如果次有代幣還可以擁有投票權。
?本章我們?yōu)樽约旱男∮螒蚪⒆约旱腡oken,下面我們稱為代幣,而代幣的符號(symbol)就叫SJ。上面說了EOS(后面紅字EOS統(tǒng)一指代幣)就是代幣,從技術角度來看,代幣沒有什么特殊的,他就是在一個智能合約里面一張表的記錄而已,所以要有代幣必先有合約賬號,EOS也不例外,他的合約賬號是:eosio.token;如果你擁有EOS,那么在eosio.tokenaccounts這張表就會有你的記錄,balance字段記錄了你擁有多少EOS??梢渣c擊https://eospark.com/contract/eosio.token 看看這個合約的信息??赡苣銜枺喝绻蠹业拇鷰欧柖冀?code>EOS怎么辦?代幣的符號是可以重復的,是合法的。那出現(xiàn)多個豈不是就區(qū)分不了?所以在校驗的時候必須加上合約賬號一起校驗,去年很多項目被攻擊就是因為只校驗了EOS,沒有校驗合約賬號。
?如果你只是想單純的發(fā)布一種屬于自己的代幣,那么其實你一句代碼都不需要寫,EOS對應的智能合約源代碼,官方是開源了的,拿到智能合約,編譯一下,生成wasm和abi文件,就可以直接部署了。然后調用簽名部署好的合約,通過create(創(chuàng)建token)的ACTION,就可以了。整個過程一分鐘之內就可以完成。
?但我們這里需要聯(lián)動,結合到我們的小游戲來提現(xiàn)代幣的價值,所以還是需要做一些增量改造,首先看下EOS的智能合約源代碼:https://app.eosstudio.io/eosio/eosio.token,注意這份合約代碼是1.6版本的,我們整個小游戲都是基于v1.3x+版本。源碼有幾個重要的ACTION:

         ...
         /**
           * 創(chuàng)建代幣
           * @param isuser 發(fā)行人賬號,當敏感操作需要使用該賬號權限
           * @param maximum_supply 代幣最大發(fā)行量,EOS是10000000000
         */
         [[eosio::action]]
         void create( const name&   issuer,
                      const asset&  maximum_supply);
      
          /**
           * 發(fā)行代幣,一般“挖礦”操作就是使用此接口,前提必先執(zhí)行create
           * @param to 代幣接收人賬號
           * @param quantity 發(fā)行的代幣金額
           * @param memo 備注
         */
         [[eosio::action]]
         void issue( const name& to, const asset& quantity, const string& memo );

         ...

         /** 轉賬,前提必先執(zhí)行issue
           * @param from 轉賬發(fā)起人賬號
           * @param to 代幣接收人賬號
           * @param quantity 轉賬的代幣金額
           * @param memo 備注
         */
         [[eosio::action]]
         void transfer( const name&    from,
                        const name&    to,
                        const asset&   quantity,
                        const string&  memo );

?上面的幾個對外接口都有說明了,現(xiàn)在我們新增一個miner代碼如下:

void token::miner( const name& to, const asset& quantity, const string& memo ){
   //1. 必須由小游戲合約權限調用
   require_auth("kingofighter"_n);

   //2. 先發(fā)行代幣給合約自身
   auto sym = quantity.symbol;
   stats statstable( _self, sym.code().raw() );
   auto existing = statstable.find( sym.code().raw() );
   const auto& st = *existing;
   statstable.modify(st, same_payer, [&](auto &s) {
      s.supply += quantity;
   });

   //3. 再由合約轉賬給接收人‘to’
   if (to != st.issuer) {
      add_balance(st.issuer, quantity, st.issuer);
      SEND_INLINE_ACTION(*this, transfer, { st.issuer, "active"_n }, { st.issuer, to, quantity, memo });
   }
}

?注意此action只能被小游戲的合約賬號來調用,調用流程會在下面給出;上面代碼其實分了兩個步驟:

  • 首先token合約發(fā)行代幣,代幣的持有人這時候是token合約自身(注釋2部分)
  • 然后token合約再將自己的代幣轉賬給入?yún)⒌?code>to接收者,SEND_INLINE_ACTION是一個行內操作,是事務性的,所以整個action操作要么成功要么失敗。

?token合約的代碼編寫好之后,先為代幣創(chuàng)建一個EOS賬號:


使用EOS studio創(chuàng)建賬號

?然后創(chuàng)建一個項目(不清楚可以調到第一、二章),直接把上面eosio.tokenhpp和cpp文件的代碼復制復制到你的合約里面,這時候目錄應該是這樣:


?然后打開你的.hpp文件,把以下這一行修改一下:

class [[eosio::contract("改為你的合約賬號")]] token : public contract 

?接著打開.cpp文件,

#include <eosio.token.hpp>
改為
#include <你的合約賬號.hpp> 
//其實就是上面目錄結構圖include文件里面的hpp文件名

?最后就是編譯-》部署到麒麟測試鏈,你還需要為合約購買內存和CPU,這些在第一章都有說,這里就不重復了。
?現(xiàn)在我們已經(jīng)將合約代碼部署到區(qū)塊鏈了,接著就是創(chuàng)建我們的代幣,打開EOS studio,切換到代幣的合約界面,選擇create action:

創(chuàng)建token

?入?yún)ⅲ?p>

  • issuser:指定代幣發(fā)行人賬號,為什么這里指定游戲的合約賬號呢?我們下面會說。
  • maximum_supply:最大發(fā)行量為100萬,EOS發(fā)行量是100億,表示我們的代幣還是比較稀有的。
    ?第三個參入是調用權限,需要填寫本合約的賬號,因為create校驗了必須本合約權限調用,這是代碼確定的:
void token::create( const name&   issuer,
                    const asset&  maximum_supply )
{
    require_auth( _self ); //必須本合約賬號權限
   ...
}

?完成后,可以在界面右邊看到數(shù)據(jù),SUPPLY值為0,表示目前還沒有發(fā)行任何代幣:

代幣創(chuàng)建成功

?以上,我們的代幣已經(jīng)就緒,但是什么時候應該發(fā)行代幣?不發(fā)行就沒有交易,沒有流通也就沒有價值?;氐轿覀兊男∮螒?,還記得游戲邏輯是如果玩家勝利了,會獎勵一定的SJ,這里其實就是一個發(fā)行的過程,也可以叫做挖礦。所以我們需要修改一下小游戲合約的代碼,讓他和我們的代幣結合起來使用。

加入token后的完整流程圖

?留意上圖,對比上一章的流程,新增了兩個功能,一個是第三步的可支持氪金以及發(fā)行代幣;氪金邏輯:我們決定10個SJ(代幣的符號,在游戲里代表水晶。無特殊說明下面SJ均表示代幣)可以提高1點攻擊力,這樣就可以提高玩家的勝率。那這里就涉及到轉賬需求了,如果想氪金,玩家就要從自己的賬號轉賬給小游戲的賬號,回想上一章,開始一局游戲時,玩家調用了合約的newgameaction,最開始的想法是可不可以調用的同時,附帶轉賬功能呢?ETH就是這樣設計的,但EOS這里不行。
?至于EOS為什么不行?我個人的理解是:ETHEOS的設計不同,ETH轉賬是一個特殊的操作,和普通的調用操作不一樣;但EOS本質上轉賬其實也是action,一個在合約賬號eosio.token上名稱為transfer的action,和普通的action并無區(qū)別,所以你想想如果你想調用newgame同時轉賬,實際就是想同時調用兩個action了,所以不被允許。
?但反過來:轉賬同時附加執(zhí)行邏輯卻是可以的,我們利用一種比較巧妙的方法,不過實現(xiàn)起來似乎有點別扭,在這之前我們講講轉賬:EOS所有的賬號包含智能合約賬號都可以接受轉賬,標準的轉賬action有4個參數(shù):發(fā)起人、接收人、轉賬金額、備注。所以如果“接收人”填的是一個智能合約的賬號,表示給這個合約轉賬。還有一點,EOS在執(zhí)行轉賬的時候,會通知到“接收人”,“接收人”可以在自己的合約代碼捕獲這個通知。
?到這里其實比較清楚了:智能合約可以通過轉賬通知,知道有人給我轉賬了,再根據(jù)轉賬時填寫的“備注”,就可以知道需要做哪些操作,比如我們可以在轉賬備注填寫:action:newgame,param1:xx,param2:xx...,這樣合約就可以知道需要執(zhí)行哪些操作,入?yún)⑹鞘裁?。是不是很巧妙呢:?br> ?切換回到小游戲的合約代碼.hpp文件,有兩處需要新增代碼,一處是新增一個交易的action,另一處在代碼的最最底部新增一段:

...
//注意,這里交易的action沒有聲明為[[eosio::action]]
//所以不會出現(xiàn)在abi文件中,即表示這是非公開的
void transfer(const name from,const name to,const asset &quantity, const string memo);
...
extern "C"
{
  //由于EOS有類似通知的功能,執(zhí)行某些操作時,你可以指定通知給其他賬號
  //這里能接收所有的消息,入?yún)?  //`receiver`表示接收通知的賬號
  //`code`表示發(fā)出通知的合約賬號
  //`actin`表示發(fā)出通知的合約賬號所被調用的action
  void apply(uint64_t receiver, uint64_t code, uint64_t action) {
      //校驗,必須是`kofgametoken`合約的交易操作,才能執(zhí)行以下的邏輯
      if (code == name("kofgametoken").value && action == name("transfer").value) {
          //把接收到的參數(shù)透傳到我們自己定義的`transfer` action
          //等價于捕捉到轉賬后,執(zhí)行我們自己的`tranfer`
          //就是我們上面定義的tranfer
          execute_action(name(receiver), name(code), &kingofighter::transfer);
          return;
      }
      if (code != receiver)
          return;
  
      switch (action) {
          EOSIO_DISPATCH_HELPER(kingofighter, (signup)(battle)(newgame))
      }
    eosio_exit(0);
  }
}

?接下來就是編寫捕捉到轉賬后,需要執(zhí)行的邏輯,我們把這塊代碼放在一個transfer的action,其實就是把上一章的newgame里面的代碼,只是需要作一點修改:

ACTION kingofighter::transfer(const name from,const name to, const asset &quantity,const string memo) {
    //這一句很重要,涉及到安全問題
    //from == get_self() 的時候 return;表示當轉賬發(fā)起人是合約自身時,跳過
    //to != get_self() 的時候return;表示接受這并不是合約自身時,跳過
    //什么情況會出現(xiàn)to != get_self()?當其他合約發(fā)起通知的時候即:require_recipient操作,有興趣可以查一下,這里不展開
    if (from == get_self() || to != get_self()) return;

    //2. 普通轉賬,無需執(zhí)行邏輯
    if (memo.empty()) return;

    //3. 只接受SJ的代幣
    const symbol SJ = symbol(symbol_code("SJ"), 4);
    check(quantity.symbol == SJ, "only SJ token allowed");
    check(quantity.is_valid(), "quantity invalid");
    check(quantity.amount >=10*1000,"quantity at least 10 SJ");
    
    //4. 解析備注
    //   入?yún)⒁还?個:action、user_seed、house_seed_hash、expire_timestamp、sig
    vector<string> vec;
    split_memo(vec, memo, ',');                         
    if(vec.size() != 5)
        return;
     //5. 入?yún)㈩愋娃D換
     const string action = split_val(vec,"action");
     check("imrich"==action,"action invalid");
     string user_seed = split_val(vec,"us");
     string house_seed_hash_str = split_val(vec,"ush");
     checksum256 house_seed_hash = hex_to_sha256(house_seed_hash_str);
     uint64_t expire_timestamp = stoll(split_val(vec,"et"));
     signature sig = str_to_sig(split_val(vec,"sig"));
      ...

      g_tb.emplace(get_self(), [&](auto &r) {
        ...
        r.coin = quantity; //記錄玩家氪金的金額
        ...
    });


?這里需要強調,上面return,并不是拒絕調用者的請求;而是不執(zhí)行下面的邏輯而已,轉賬依然是能成功的,但如果check檢查不通過,拋出了異常,轉賬也會失敗。代碼邏輯都寫了注釋這里不多說。
?然后是修改battle,需要新增挖礦的邏輯:

ACTION kingofighter::battle(const uint64_t& game_id,const string &house_seed) {
    require_auth(get_self());
     ...
    if (i & 1) {
            //i為奇數(shù),BOSS攻擊
         ...
        } else {
           //i為偶數(shù),玩家攻擊
            uint32_t hero_max_atk = hero->max_atk;
            uint32_t hero_min_atk = hero->min_atk;
            if(itr->coin.amount > 0){
                //玩家已氪金 10SJ=1攻擊力
                //最高只能增加20點攻擊力
                uint32_t append_atk = itr->coin.amount /1000 / 10;
                if(append_atk > 20)
                    append_atk = 20;
                hero_max_atk += append_atk;
                hero_min_atk += append_atk;
            }
            damage = hash_val % (hero_max_atk - hero_min_atk + 1) + hero_min_atk;
     ...
   if (hero_hp > 0) {
      ...
        //并轉賬代幣給玩家
        const asset reward_coin = asset(100 * 10000, symbol(symbol_code("SJ"), 4));
        action(permission_level{get_self(), "active"_n},
        "kofgametoken"_n, "miner"_n,
        std::make_tuple(player,reward_coin,"Reward SJ.")).send();

    }

?主要修改了兩處地方:一處是當玩家進行攻擊,且玩家已氪金,攻擊力需要增加,但最高只能增加20點;另外一處是當玩家獲勝,需要轉賬代幣給玩家(挖礦)。
if (hero_hp > 0) {...}這塊代碼是使用本合約賬號的權限,調用kofgametoken合約的miner action。
?現(xiàn)在,代碼都已經(jīng)編寫完畢,但在開始對戰(zhàn)之前,如果想要氪金,玩家還需要有SJ,但目前玩家并沒有任何SJ代幣,我們可以贈送一些給他用于內部測試,打開EOS studio,切換到Token智能合約界面,選擇miner,決定贈送500個SJ給玩家:

贈送代幣給玩家

?上面注意需要使用小游戲的智能合約的賬號權限來調用,執(zhí)行成功后,可以看到右邊stat表的發(fā)行量數(shù)據(jù)變?yōu)?code>500了。我們可以查看玩家這時候的 SJ余額:
玩家代幣余額

?一切準備就緒,我們來看看執(zhí)行的效果。先啟動服務端,不清楚的朋友請回到第三章。獲取種子信息:

{
    "house_seed_hash":"6e64e182e42920b689368ebc732188dbe7ac7c63939495f2671ad7d64937b0a9",
    "expire_timestamp":1579072709,
    "sig":"SIG_K1_Jx3ahMXUUxyEm2wYSXaJoXTsXhMGTC4g76dPxrEm58AF6gLEh3GVGSvWn4hDTq1bsKLeY1RfRAhfkzYz1wM6REChrWVLZi"
}

?打開EOS studio,依然是切換到Token合約,選擇transfer action:

transfer調用

  1. 輸入玩家的賬號
  2. 輸入小游戲的智能合約賬號
  3. 輸入氪金金額(注意格式)
  4. 備注,需要把入?yún)⑵唇?/li>

根據(jù)我們獲取到的種子信息,我們這里拼接到的數(shù)據(jù)是:

action:imrich,us:9a114079014a,ush:6e64e182e42920b689368ebc732188dbe7ac7c63939495f2671ad7d64937b0a9,et:1579072709,sig:SIG_K1_Jx3ahMXUUxyEm2wYSXaJoXTsXhMGTC4g76dPxrEm58AF6gLEh3GVGSvWn4hDTq1bsKLeY1RfRAhfkzYz1wM6REChrWVLZi

點擊執(zhí)行后,等一會服務端自動調用對戰(zhàn)后,再看看gamerecords表的對戰(zhàn)結果,顯示勝利。仔細查看每一次對BOSS造成的傷害明顯增加了不少:


?如果這時候你查看玩家的余額會發(fā)現(xiàn)還有400=500-200+100,表示挖礦成功?;蛘吣阋部梢酝ㄟ^日志查看整個執(zhí)行過程,不過之前的日志是依賴history_plugin插件,好像是EOS1.3x后已經(jīng)不建議使用,現(xiàn)在基本所有超級節(jié)點也都停用了,好在有一些平臺提供了日志的服務,我自己使用的就是dfuse平臺提供的接口。
?到這里,我們的小游戲全部邏輯已經(jīng)搭建完成。一般會提供UI界面去給玩家操作,然后將攻擊特效做得很炫酷,這時候玩家調用合約就沒有那么方便,因為涉及到私鑰簽名,需要借助官方的eosjsjs庫,以及依賴scatter。
?下一章是本系列的最終章,將會講解如何實現(xiàn)EOS1.8版本的一個新型功能:ONLY_BILL_FIRST_AUTHORIZER,即如何讓玩家不需要支付CPU和NET,就可以玩我們的小游戲。敬請期待:)

本章節(jié)源代碼地址:https://github.com/jan-gogogo/kof-chapter4

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯(lián)系作者。

相關閱讀更多精彩內容

友情鏈接更多精彩內容