编写有数据存储的智能合约

如何存储数据

EOS通过table的概念来存储智能合约运行中所需的数据。

数据储存在区块链上。

部署到区块链后,如果有数据存储,修改wasm编码后,会产生原始储存数据不可读的问题

我们看下官方例子tic_tac_toe

其中

hpp+cpp文件,主要实现了数据结构定义、业务逻辑、权限控制、数据存储

abi文件,定义了合约的types、structs、actions、tables和ricardian_clauses

hpp文件

#include <eosiolib/eosio.hpp>

/**
 *  圆叉旗:
 *  在一个3*3的棋盘里,一人用x(先手host),一人用o(后手challenger),先连成三点一线的获胜。
 *  棋盘如下所示:
 *
 *  (0,0)坐标表示左上角
 *  (2,2)坐标表示右下角
 *
 *                 (0,2)
 *  (0,0) -  |  o  |  x            - = 空
 *        -  |  x  |  -            x = 先手
 *  (2,0) x  |  o  |  o            o = 后手
 * 
 *  为了让存储变得简单,在本例中我们用一个9个长度的数组,表示上面3*3的矩阵
 *  比如,上面的棋盘存储后就是,[0, 2, 1, 0, 1, 0, 1, 2, 2]
 *  
 *  另外,为了让智能合约变的简单,我们对每两对用户A和B,只允许开启两个旗局,一个是A为先手,一个是B为先手
 *  数据存储在先手账户中,并用后手作为key
 * 
 *  create创建游戏
 *  restart重开游戏
 *  close关闭游戏
 *  move下棋,先手后手交替进行
 */

namespace neo_tictactoe {
   //游戏开启账户,先手
   static const account_name games_account = N(games);
   //合约账户
   static const account_name code_account = N(tic.tac.toe);

   /**
    * @brief 用于保存棋盘信息的结构
    */
   static const uint32_t board_len = 9;
   struct game {
      game() { initialize_board(); }

      game(account_name challenger, account_name host):challenger(challenger), host(host), turn(host) {
         initialize_board();
      }

      account_name     challenger; //后手
      account_name     host; //先手
      account_name     turn; // 谁的回合
      account_name     winner = N(none); // 谁赢了 none/ draw/ host/ challenger
      uint8_t          board[board_len]; // 棋盘信息

      // 初始化游戏
      void initialize_board() {
         for (uint8_t i = 0; i < board_len ; i++) {
            board[i] = 0;
         }
      }

      // 重置游戏
      void reset_game() {
         initialize_board();
         turn = host;
         winner = N(none);
      }

      auto primary_key() const { return challenger; }

      EOSLIB_SERIALIZE( game, (challenger)(host)(turn)(winner)(board) )
   };

   /**
    * @brief 新建游戏
    */
   struct create {
      account_name   challenger;
      account_name   host;

      EOSLIB_SERIALIZE( create, (challenger)(host) )
   };

   /**
    * @brief 重置游戏
    */
   struct restart {
      account_name   challenger;
      account_name   host;
      account_name   by; // the account who wants to restart the game

      EOSLIB_SERIALIZE( restart, (challenger)(host)(by) )
   };

   /**
    * @brief 关闭游戏
    */
   struct close {
      account_name   challenger;
      account_name   host;

      EOSLIB_SERIALIZE( close, (challenger)(host) )
   };

   /**
    * @brief 下棋坐标
    */
   struct movement {
      uint32_t    row;
      uint32_t    column;

      EOSLIB_SERIALIZE( movement, (row)(column) )
   };

   /**
    * @brief 下棋
    */
   struct move {
      account_name   challenger;
      account_name   host;
      account_name   by; // the account who wants to make the move
      movement       mvt;

      EOSLIB_SERIALIZE( move, (challenger)(host)(by)(mvt) )
   };

   /**
    * @brief 新建表,保存账号和游戏信息
    */
   typedef eosio::multi_index< games_account, game> games;
}

cpp文件

#include "neo_tictactoe.hpp"

using namespace eosio;
namespace neo_tictactoe {
struct impl {
   /**
    * @brief Check if cell is empty
    * @param cell - value of the cell (should be either 0, 1, or 2)
    * @return true if cell is empty
    */
   bool is_empty_cell(const uint8_t& cell) {
      return cell == 0;
   }

   /**
    * @brief Check for valid movement
    * @detail Movement is considered valid if it is inside the board and done on empty cell
    * @param movement - the movement made by the player
    * @param game - the game on which the movement is being made
    * @return true if movement is valid
    */
   bool is_valid_movement(const movement& mvt, const game& game_for_movement) {
      uint32_t movement_location = mvt.row * 3 + mvt.column;
      bool is_valid = movement_location < board_len && is_empty_cell(game_for_movement.board[movement_location]);
      return is_valid;
   }


   /**
    * @brief Get winner of the game
    * @detail Winner of the game is the first player who made three consecutive aligned movement
    * @param game - the game which we want to determine the winner of
    * @return winner of the game (can be either none/ draw/ account name of host/ account name of challenger)
    */
   account_name get_winner(const game& current_game) {
      if((current_game.board[0] == current_game.board[4] && current_game.board[4] == current_game.board[8]) ||
         (current_game.board[1] == current_game.board[4] && current_game.board[4] == current_game.board[7]) ||
         (current_game.board[2] == current_game.board[4] && current_game.board[4] == current_game.board[6]) ||
         (current_game.board[3] == current_game.board[4] && current_game.board[4] == current_game.board[5])) {
         //  - | - | x    x | - | -    - | - | -    - | x | -
         //  - | x | -    - | x | -    x | x | x    - | x | -
         //  x | - | -    - | - | x    - | - | -    - | x | -
         if (current_game.board[4] == 1) {
            return current_game.host;
         } else if (current_game.board[4] == 2) {
            return current_game.challenger;
         }
      } else if ((current_game.board[0] == current_game.board[1] && current_game.board[1] == current_game.board[2]) ||
                 (current_game.board[0] == current_game.board[3] && current_game.board[3] == current_game.board[6])) {
         //  x | x | x       x | - | -
         //  - | - | -       x | - | -
         //  - | - | -       x | - | -
         if (current_game.board[0] == 1) {
            return current_game.host;
         } else if (current_game.board[0] == 2) {
            return current_game.challenger;
         }
      } else if ((current_game.board[2] == current_game.board[5] && current_game.board[5] == current_game.board[8]) ||
                 (current_game.board[6] == current_game.board[7] && current_game.board[7] == current_game.board[8])) {
         //  - | - | -       - | - | x
         //  - | - | -       - | - | x
         //  x | x | x       - | - | x
         if (current_game.board[8] == 1) {
            return current_game.host;
         } else if (current_game.board[8] == 2) {
            return current_game.challenger;
         }
      } else {
         bool is_board_full = true;
         for (uint8_t i = 0; i < board_len; i++) {
            if (is_empty_cell(current_game.board[i])) {
               is_board_full = false;
               break;
            }
         }
         if (is_board_full) {
            return N(draw);
         }
      }
      return N(none);
   }

   /**
    * @brief Apply create action
    * @param create - action to be applied
    */
   void on(const create& c) {
      require_auth(c.host);
      eosio_assert(c.challenger != c.host, "challenger shouldn't be the same as host");

      // Check if game already exists
      games existing_host_games(code_account, c.host);
      auto itr = existing_host_games.find( c.challenger );
      eosio_assert(itr == existing_host_games.end(), "game already exists");

      existing_host_games.emplace(c.host, [&]( auto& g ) {
         g.challenger = c.challenger;
         g.host = c.host;
         g.turn = c.host;
      });
   }

   /**
    * @brief Apply restart action
    * @param restart - action to be applied
    */
   void on(const restart& r) {
      require_auth(r.by);

      // Check if game exists
      games existing_host_games(code_account, r.host);
      auto itr = existing_host_games.find( r.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Check if this game belongs to the action sender
      eosio_assert(r.by == itr->host || r.by == itr->challenger, "this is not your game!");

      // Reset game
      existing_host_games.modify(itr, itr->host, []( auto& g ) {
         g.reset_game();
      });
   }

   /**
    * @brief Apply close action
    * @param close - action to be applied
    */
   void on(const close& c) {
      require_auth(c.host);

      // Check if game exists
      games existing_host_games(code_account, c.host);
      auto itr = existing_host_games.find( c.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Remove game
      existing_host_games.erase(itr);
   }

   /**
    * @brief Apply move action
    * @param move - action to be applied
    */
   void on(const move& m) {
      require_auth(m.by);

      // Check if game exists
      games existing_host_games(code_account, m.host);
      auto itr = existing_host_games.find( m.challenger );
      eosio_assert(itr != existing_host_games.end(), "game doesn't exists");

      // Check if this game hasn't ended yet
      eosio_assert(itr->winner == N(none), "the game has ended!");
      // Check if this game belongs to the action sender
      eosio_assert(m.by == itr->host || m.by == itr->challenger, "this is not your game!");
      // Check if this is the  action sender's turn
      eosio_assert(m.by == itr->turn, "it's not your turn yet!");


      // Check if user makes a valid movement
      eosio_assert(is_valid_movement(m.mvt, *itr), "not a valid movement!");

      // Fill the cell, 1 for host, 2 for challenger
      const auto cell_value = itr->turn == itr->host ? 1 : 2;
      const auto turn = itr->turn == itr->host ? itr->challenger : itr->host;
      existing_host_games.modify(itr, itr->host, [&]( auto& g ) {
         g.board[m.mvt.row * 3 + m.mvt.column] = cell_value;
         g.turn = turn;

         //check to see if we have a winner
         g.winner = get_winner(g);
      });
   }

   /// The apply method implements the dispatch of events to this contract
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) {

      if (code == code_account) {
         if (action == N(create)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::create>());
         } else if (action == N(restart)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::restart>());
         } else if (action == N(close)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::close>());
         } else if (action == N(move)) {
            impl::on(eosio::unpack_action_data<tic_tac_toe::move>());
         }
      }
   }

};
}

/**
*  The apply() methods must have C calling convention so that the blockchain can lookup and
*  call these methods.
*/
extern "C" {

   using namespace neo_tictactoe;
   /// The apply method implements the dispatch of events to this contract
   void apply( uint64_t receiver, uint64_t code, uint64_t action ) {
      impl().apply(receiver, code, action);
   }

} // extern "C"

abi文件

{
  "types": [],
  "structs": [{
      "name": "game",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"turn", "type":"account_name"},
        {"name":"winner", "type":"account_name"},
        {"name":"board", "type":"uint8[]"}
      ]
    },{
      "name": "create",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"}
      ]
    },{
      "name": "restart",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"by", "type":"account_name"}
      ]
    },{
      "name": "close",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"}
      ]
    },{
      "name": "movement",
      "base": "",
      "fields": [
        {"name":"row", "type":"uint32"},
        {"name":"column", "type":"uint32"}
      ]
    },{
      "name": "move",
      "base": "",
      "fields": [
        {"name":"challenger", "type":"account_name"},
        {"name":"host", "type":"account_name"},
        {"name":"by", "type":"account_name"},
        {"name":"mvt", "type":"movement"}
      ]
    }
  ],
  "actions": [{
      "name": "create",
      "type": "create",
      "ricardian_contract": ""
    },{
      "name": "restart",
      "type": "restart",
      "ricardian_contract": ""
    },{
      "name": "close",
      "type": "close",
      "ricardian_contract": ""
    },{
      "name": "move",
      "type": "move",
      "ricardian_contract": ""
    }
  ],
  "tables": [{
        "name": "games",
        "type": "game",
        "index_type": "i64",
        "key_names" : ["challenger"],
        "key_types" : ["account_name"]
      }
  ],
  "ricardian_clauses": []
}

编译并加载合约

#编译
eosiocpp -o neo.tictactoe.wast neo.tictactoe.cpp
eosiocpp -g neo.tictactoe.abi neo.tictactoe.hpp
#开启区块
nodeos -e -p eosio --plugin eosio::wallet_api_plugin --plugin eosio::chain_api_plugin --plugin  eosio::account_history_api_plugin

#加载钱包

cleos wallet open
cleos wallet unlock
"PW5JAj9qw2tQsuVajLVXsiKUD7ULQnZ83qxpaB5YVFcs6qbhQ84J1"

cleos wallet open -n hospital2
cleos wallet unlock -n hospital2
"PW5JxnbBTTbTQKUHHNvYojU1xrcjxQduz2wU6gfMEgiZjwT5S6xJ6"

cleos wallet list

#创建用户
cleos create account eosio tictactoe1 EOS8K9wqRtD8cCuHvbdzcquEhYHnSaFvyQSWm8kVxYFHavraNeue2 EOS8K9wqRtD8cCuHvbdzcquEhYHnSaFvyQSWm8kVxYFHavraNeue2

#加载合约
cleos set contract tictactoe1  neo.tictactoe -p tictactoe1

执行合约

cleos push action tictactoe1 create '{"challenger":"patient2", "host":"patient1"}' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":0, "column":0} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":0, "column":1} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":1, "column":1} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":2, "column":2} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":0, "column":2} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":1, "column":0} }' -p patient2

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient1", "mvt":{"row":2, "column":0} }' -p patient1

cleos push action tictactoe1 move '{"challenger":"patient2", "host":"patient1", "by":"patient2", "mvt":{"row":2, "column":0} }' -p patient2

查看数据

cleos get table tictactoe1 patient1 games
{
  "rows": [{
      "challenger": "patient2",
      "host": "patient1",
      "turn": "patient2",
      "winner": "patient1",
      "board": [
        1,
        2,
        1,
        2,
        1,
        0,
        1,
        0,
        2
      ]
    }
  ],
  "more": false
}

最后更新于