/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
#include <utility>
#include <vector>
#include <string>
#include <eosiolib/eosio.hpp> //eosio核心库
#include <eosiolib/time.hpp> //eosio时间管理库
#include <eosiolib/asset.hpp>
#include <eosiolib/contract.hpp>
#include <eosiolib/crypto.h>
using eosio::key256;
using eosio::indexed_by;
using eosio::const_mem_fun;
using eosio::asset;
using eosio::permission_level;
using eosio::action;
using eosio::print;
using eosio::name;
class dice : public eosio::contract { //继承contract基类
private:
//@abi table offer i64
struct offer //定义数据库表 offer
{
uint64_t id; //uint64_t通常用于定义ID
account_name owner; //账户数据类型 account_name,参考 https://developers.eos.io/eosio-cpp/reference#account_name
asset bet; //资产数据类型,参考 https://developers.eos.io/eosio-cpp/reference#asset
checksum256 commitment; //
uint64_t gameid = 0; //游戏ID
uint64_t primary_key() const { return id; } //定义主键
uint64_t by_bet() const { return (uint64_t)bet.amount; } //定义下注金额,asset数据类型的属性 amount
key256 by_commitment() const { return get_commitment(commitment); }
static key256 get_commitment(const checksum256 &commitment)
{
const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&commitment);
return key256::make_from_word_sequence<uint64_t>(p64[0], p64[1], p64[2], p64[3]);
}
EOSLIB_SERIALIZE(offer, (id)(owner)(bet)(commitment)(gameid))
};
typedef eosio::multi_index<N(offer), offer,
indexed_by<N(bet), const_mem_fun<offer, uint64_t, &offer::by_bet>>,
indexed_by<N(commitment), const_mem_fun<offer, key256, &offer::by_commitment>>>
offer_index; //定义一个多索引数据类型 offer_index,该实例以id为主键,bet.amount和commitment为索引键
struct player //玩家数据结构,这个不用做成数据表,不需要 @abi table
{
checksum256 commitment; //玩家的SHA256
checksum256 reveal; //玩家的随机密钥,这个密钥用来生成上面的SHA256
EOSLIB_SERIALIZE(player, (commitment)(reveal))
};
//@abi table game i64
struct game
{
uint64_t id; //游戏ID
asset bet; //下注树
eosio::time_point_sec deadline; //到期时间,加5分钟
player player1; //玩家1
player player2; //玩家2
uint64_t primary_key() const { return id; } //设置主键
EOSLIB_SERIALIZE(game, (id)(bet)(deadline)(player1)(player2))
};
typedef eosio::multi_index<N(game), game> game_index; //定义一个game类型:game_index
//@abi table global i64
struct global_dice
{
uint64_t id = 0;
uint64_t nextgameid = 0;
uint64_t primary_key() const { return id; }
EOSLIB_SERIALIZE(global_dice, (id)(nextgameid))
};
typedef eosio::multi_index<N(global), global_dice> global_dice_index;
//@abi table account i64
struct account //参与账户类
{
account(account_name o = account_name()) : owner(o) {}
account_name owner; //账户名
asset eos_balance; //充值的代币余额
uint32_t open_offers = 0; //尚未下注的游戏
uint32_t open_games = 0; //尚未结束的游戏
bool is_empty() const { return !(eos_balance.amount | open_offers | open_games); } //如果充值额为0,或者没有尚未下注的游戏,或者没有尚未结束的游戏,则说明是空
uint64_t primary_key() const { return owner; } //用户名为主键,不能同时进行两个游戏
EOSLIB_SERIALIZE(account, (owner)(eos_balance)(open_offers)(open_games))
};
typedef eosio::multi_index<N(account), account> account_index; //定义一个账户类型:account_index
//实例化数据表
offer_index offers;
game_index games;
global_dice_index global_dices;
account_index accounts;
//内部函数
bool has_offer(const checksum256 &commitment) const
{
//是否已经下注
auto idx = offers.template get_index<N(commitment)>(); //在多索引数据表中查找非主键数据的方法
auto itr = idx.find(offer::get_commitment(commitment));
return itr != idx.end();
}
bool is_equal(const checksum256 &a, const checksum256 &b) const
{
return memcmp((void *)&a, (const void *)&b, sizeof(checksum256)) == 0;
}
bool is_zero(const checksum256 &a) const
{
const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&a);
return p64[0] == 0 && p64[1] == 0 && p64[2] == 0 && p64[3] == 0;
}
void pay_and_clean(const game &g, const offer &winner_offer,
const offer &loser_offer)
{
// Update winner account balance and game count
auto winner_account = accounts.find(winner_offer.owner);
accounts.modify(winner_account, 0, [&](auto &acnt) {
acnt.eos_balance += 2 * g.bet;
acnt.open_games--;
});
// Update losser account game count
auto loser_account = accounts.find(loser_offer.owner);
accounts.modify(loser_account, 0, [&](auto &acnt) {
acnt.open_games--;
});
if (loser_account->is_empty())
{
accounts.erase(loser_account);
}
games.erase(g);
offers.erase(winner_offer);
offers.erase(loser_offer);
}
public:
const uint32_t FIVE_MINUTES = 5 * 60;
dice(account_name self)
: eosio::contract(self),
offers(_self, _self),
games(_self, _self),
global_dices(_self, _self),
accounts(_self, _self)
{}
//@abi action
void deposit(const account_name from, const asset &quantity)
{
//充值
eosio_assert(quantity.is_valid(), "invalid quantity"); //验证资产数据格式是否合法
eosio_assert(quantity.amount > 0, "must deposit positive quantity");
//操作accounts数据表,如果没有,则添加新用户
auto itr = accounts.find(from);
if (itr == accounts.end())
{
itr = accounts.emplace(_self, [&](auto &acnt) {
acnt.owner = from;
});
}
//转账动作组合
//相当于:cleos push action eosio.token transfer '[from, _self, quantity]' -p from@active
//_self是合约账户
action(
permission_level{from, N(active)},
N(eosio.token), N(transfer),
std::make_tuple(from, _self, quantity, std::string("")))
.send();
//如果转账成功,则账户余额添加
accounts.modify(itr, 0, [&](auto &acnt) {
acnt.eos_balance += quantity;
});
}
//@abi action
void withdraw(const account_name to, const asset &quantity)
{
require_auth(to);
eosio_assert(quantity.is_valid(), "invalid quantity");
eosio_assert(quantity.amount > 0, "must withdraw positive quantity");
auto itr = accounts.find(to);
eosio_assert(itr != accounts.end(), "unknown account");
accounts.modify(itr, 0, [&](auto &acnt) {
eosio_assert(acnt.eos_balance >= quantity, "insufficient balance");
acnt.eos_balance -= quantity;
});
//提现动作
//相当于:cleos push action eosio.token transfer '[_self, from, quantity]' -p _self@active
action(
permission_level{_self, N(active)}, //BUG:应该改为 permission_level{_self, N(eosio.code)}
N(eosio.token), N(transfer),
std::make_tuple(_self, to, quantity, std::string("")))
.send();
if (itr->is_empty())
{
accounts.erase(itr);
}
}
//@abi action
void offerbet(const asset &bet, const account_name player, const checksum256 &commitment)
{
//下注
eosio_assert(bet.symbol == CORE_SYMBOL, "only core token allowed"); //eosio_assert 参考 https://developers.eos.io/eosio-cpp/reference#eosio_assert
eosio_assert(bet.is_valid(), "invalid bet");
eosio_assert(bet.amount > 0, "must bet positive quantity");
eosio_assert(!has_offer(commitment), "offer with this commitment already exist"); //如果下注已经存在,则退出
require_auth(player); //验证账户是否合法,参考 https://developers.eos.io/eosio-cpp/reference#require_auth
auto cur_player_itr = accounts.find(player); //查找用户,accounts是账户数据表
eosio_assert(cur_player_itr != accounts.end(), "unknown account"); //判断用户是否存在
// Store new offer
auto new_offer_itr = offers.emplace(_self, [&](auto &offer) { //在下注的数据表中,用emplace添加数据
offer.id = offers.available_primary_key(); //使用自增型主键
offer.bet = bet;
offer.owner = player;
offer.commitment = commitment;
offer.gameid = 0;
});
// Try to find a matching bet
// 寻找是否相同金额的下注方
auto idx = offers.template get_index<N(bet)>();
auto matched_offer_itr = idx.lower_bound((uint64_t)new_offer_itr->bet.amount);
if (matched_offer_itr == idx.end() || matched_offer_itr->bet != new_offer_itr->bet || matched_offer_itr->owner == new_offer_itr->owner)
{
// No matching bet found, update player's account
accounts.modify( cur_player_itr, 0, [&](auto& acnt) {
eosio_assert( acnt.eos_balance >= bet, "insufficient balance" );//充值额不够
acnt.eos_balance -= bet; //扣除余额
acnt.open_offers++; //下注次数+1
});
} else {
// Create global game counter if not exists
auto gdice_itr = global_dices.begin();
if( gdice_itr == global_dices.end() ) {
gdice_itr = global_dices.emplace(_self, [&](auto& gdice){
gdice.nextgameid=0;
});
}
// Increment global game counter
global_dices.modify(gdice_itr, 0, [&](auto& gdice){
gdice.nextgameid++;
});
// Create a new game
auto game_itr = games.emplace(_self, [&](auto& new_game){
new_game.id = gdice_itr->nextgameid;
new_game.bet = new_offer_itr->bet;
new_game.deadline = eosio::time_point_sec(0);
new_game.player1.commitment = matched_offer_itr->commitment;
memset(&new_game.player1.reveal, 0, sizeof(checksum256));
new_game.player2.commitment = new_offer_itr->commitment;
memset(&new_game.player2.reveal, 0, sizeof(checksum256));
});
// Update player's offers
idx.modify(matched_offer_itr, 0, [&](auto& offer){
offer.bet.amount = 0;
offer.gameid = game_itr->id;
});
offers.modify(new_offer_itr, 0, [&](auto& offer){
offer.bet.amount = 0;
offer.gameid = game_itr->id;
});
// Update player's accounts
accounts.modify( accounts.find( matched_offer_itr->owner ), 0, [&](auto& acnt) {
acnt.open_offers--;
acnt.open_games++;
});
accounts.modify( cur_player_itr, 0, [&](auto& acnt) {
eosio_assert( acnt.eos_balance >= bet, "insufficient balance" );
acnt.eos_balance -= bet;
acnt.open_games++;
});
}
}
//@abi action
void canceloffer( const checksum256& commitment ) {
auto idx = offers.template get_index<N(commitment)>();
auto offer_itr = idx.find( offer::get_commitment(commitment) );
eosio_assert( offer_itr != idx.end(), "offer does not exists" );
eosio_assert( offer_itr->gameid == 0, "unable to cancel offer" );
require_auth( offer_itr->owner );
auto acnt_itr = accounts.find(offer_itr->owner);
accounts.modify(acnt_itr, 0, [&](auto& acnt){
acnt.open_offers--;
acnt.eos_balance += offer_itr->bet;
});
idx.erase(offer_itr);
}
//@abi action
void reveal( const checksum256& commitment, const checksum256& source ) {
//开奖
assert_sha256( (char *)&source, sizeof(source), (const checksum256 *)&commitment );
//获取当前执行reveal的人的SHA256
auto idx = offers.template get_index<N(commitment)>();
auto curr_revealer_offer = idx.find( offer::get_commitment(commitment) );
eosio_assert(curr_revealer_offer != idx.end(), "offer not found");
eosio_assert(curr_revealer_offer->gameid > 0, "unable to reveal"); //如果已经开过
auto game_itr = games.find( curr_revealer_offer->gameid ); //查询对应的game
player curr_reveal = game_itr->player1; //获取玩家1
player prev_reveal = game_itr->player2; //获取玩家2
if( !is_equal(curr_reveal.commitment, commitment) ) {
std::swap(curr_reveal, prev_reveal);
}
eosio_assert( is_zero(curr_reveal.reveal) == true, "player already revealed");//判断玩家是否已经开奖
if( !is_zero(prev_reveal.reveal) ) {
//比较双方随机数的大小
checksum256 result;
sha256( (char *)&game_itr->player1, sizeof(player)*2, &result);
auto prev_revealer_offer = idx.find( offer::get_commitment(prev_reveal.commitment) );
int winner = result.hash[1] < result.hash[0] ? 0 : 1;
if( winner ) {
pay_and_clean(*game_itr, *curr_revealer_offer, *prev_revealer_offer);
} else {
pay_and_clean(*game_itr, *prev_revealer_offer, *curr_revealer_offer);
}
} else {
games.modify(game_itr, 0, [&](auto& game){
if( is_equal(curr_reveal.commitment, game.player1.commitment) )
game.player1.reveal = source;
else
game.player2.reveal = source;
game.deadline = eosio::time_point_sec(now() + FIVE_MINUTES);
});
}
}
//@abi action
void claimexpired( const uint64_t gameid ) {
auto game_itr = games.find(gameid);
eosio_assert(game_itr != games.end(), "game not found");
eosio_assert(game_itr->deadline != eosio::time_point_sec(0) && eosio::time_point_sec(now()) > game_itr->deadline, "game not expired");
auto idx = offers.template get_index<N(commitment)>();
auto player1_offer = idx.find( offer::get_commitment(game_itr->player1.commitment) );
auto player2_offer = idx.find( offer::get_commitment(game_itr->player2.commitment) );
if( !is_zero(game_itr->player1.reveal) ) {
eosio_assert( is_zero(game_itr->player2.reveal), "game error");
pay_and_clean(*game_itr, *player1_offer, *player2_offer);
} else {
eosio_assert( is_zero(game_itr->player1.reveal), "game error");
pay_and_clean(*game_itr, *player2_offer, *player1_offer);
}
}
};
EOSIO_ABI( dice, (offerbet)(canceloffer)(reveal)(claimexpired)(deposit)(withdraw) )
更多高级菠菜游戏合约,请添加微信交流学习