编写有数据存储的智能合约
如何存储数据
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
}
最后更新于