diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 212ff67e..3974da10 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -222,7 +222,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.player_account_id ); } - + void operator()( const tournament_payout_operation& op ) + { + _impacted.insert( op.payout_account_id ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 77a3e9fd..ebd84db9 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -371,12 +371,12 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.options.payout_interval = 7*24*60*60; a.dividend_distribution_account = TOURNAMENT_RAKE_FEE_ACCOUNT_ID; }); - const asset_bitasset_data_object& bit_asset = - create([&](asset_bitasset_data_object& a) { - a.current_feed.maintenance_collateral_ratio = 1750; - a.current_feed.maximum_short_squeeze_ratio = 1500; - a.current_feed_publication_time = genesis_state.initial_timestamp + fc::hours(1); - }); +// const asset_bitasset_data_object& bit_asset = +// create([&](asset_bitasset_data_object& a) { +// a.current_feed.maintenance_collateral_ratio = 1750; +// a.current_feed.maximum_short_squeeze_ratio = 1500; +// a.current_feed_publication_time = genesis_state.initial_timestamp + fc::hours(1); +// }); const asset_object& core_asset = create( [&]( asset_object& a ) { @@ -392,7 +392,7 @@ void database::init_genesis(const genesis_state_type& genesis_state) a.options.core_exchange_rate.quote.asset_id = asset_id_type(0); a.dynamic_asset_data_id = dyn_asset.id; a.dividend_data_id = div_asset.id; - a.bitasset_data_id = bit_asset.id; +// a.bitasset_data_id = bit_asset.id; }); assert( asset_id_type(core_asset.id) == asset().asset_id ); assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) ); diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index ecda3646..6d733e70 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -47,8 +47,6 @@ #include #include -#define USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // vesting_balance_object by_asset_balance index needed - namespace graphene { namespace chain { template @@ -756,7 +754,6 @@ void schedule_pending_dividend_balances(database& db, uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder; std::map vesting_amounts; -#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // get only once a collection of accounts that hold nonzero vesting balances of the dividend asset auto vesting_balances_begin = vesting_index.indices().get().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id)); @@ -769,20 +766,6 @@ void schedule_pending_dividend_balances(database& db, ("owner", vesting_balance_obj.owner(db).name) ("amount", vesting_balance_obj.balance.amount)); } -#else - // get only once a collection of accounts that hold nonzero vesting balances of the dividend asset - const auto& vesting_balances = vesting_index.indices().get(); - for (const vesting_balance_object& vesting_balance_obj : vesting_balances) - { - if (vesting_balance_obj.balance.asset_id == dividend_holder_asset_obj.id && vesting_balance_obj.balance.amount) - { - vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount; - dlog("Vesting balance for account: ${owner}, amount: ${amount}", - ("owner", vesting_balance_obj.owner(db).name) - ("amount", vesting_balance_obj.balance.amount)); - } - } -#endif auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first; auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first; @@ -801,18 +784,6 @@ void schedule_pending_dividend_balances(database& db, auto itr = vesting_amounts.find(holder_balance_object.owner); if (itr != vesting_amounts.end()) total_balance_of_dividend_asset += itr->second; -// // working, but potential performance gap? -// auto vesting_range = vesting_index.indices().get().equal_range(holder_balance_object.owner); -// for (const vesting_balance_object& vesting_balance : boost::make_iterator_range(vesting_range.first, vesting_range.second) -// { -// if (vesting_balance.balance.asset_id == dividend_holder_asset_obj.id) -// { -// total_balance_of_dividend_asset += vesting_balance.balance.amount; -// dlog("Vesting balances for account: ${owner}, amount: ${amount}", -// ("owner", vesting_balance.owner(db).name) -// ("amount", vesting_balance.balance.amount)); -// } -// } } // loop through all of the assets currently or previously held in the distribution account while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second || @@ -940,7 +911,6 @@ void schedule_pending_dividend_balances(database& db, // credit each account with their portion, don't send any back to the dividend distribution account for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end)) { - //if (holder_balance_object.owner != dividend_data.dividend_distribution_account && holder_balance_object.balance.value) if (holder_balance_object.owner == dividend_data.dividend_distribution_account) continue; auto holder_balance = holder_balance_object.balance; @@ -948,25 +918,14 @@ void schedule_pending_dividend_balances(database& db, auto itr = vesting_amounts.find(holder_balance_object.owner); if (itr != vesting_amounts.end()) holder_balance += itr->second; -// // working, but potential performance gap? -// auto vesting_range = vesting_index.indices().get().equal_range(holder_balance_object.owner); -// for (const vesting_balance_object& vesting_balance : boost::make_iterator_range(vesting_range.first, vesting_range.second)) -// { -// if (vesting_balance.balance.asset_id == dividend_holder_asset_obj.id) -// { -// holder_balance += vesting_balance.balance.amount; -// dlog("Vesting balances for account: ${owner}, amount: ${amount}", -// ("owner", vesting_balance.owner(db).name) -// ("amount", vesting_balance.balance.amount)); -// } -// } - if (holder_balance.value) + + fc::uint128_t amount_to_credit(delta_balance.value); + amount_to_credit *= holder_balance.value; + amount_to_credit /= total_balance_of_dividend_asset.value; + share_type shares_to_credit((int64_t)amount_to_credit.to_uint64()); + if (shares_to_credit.value) { - fc::uint128_t amount_to_credit(delta_balance.value); - amount_to_credit *= holder_balance.value; - amount_to_credit /= total_balance_of_dividend_asset.value; wdump((delta_balance.value)(holder_balance)(total_balance_of_dividend_asset)); - share_type shares_to_credit((int64_t)amount_to_credit.to_uint64()); remaining_amount_to_distribute -= shares_to_credit; @@ -990,9 +949,10 @@ void schedule_pending_dividend_balances(database& db, } for (const auto& pending_payout : pending_payout_balance_index.indices()) - dlog("Pending payout: ${account_name} -> ${amount}", - ("account_name", pending_payout.owner(db).name) - ("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type))); + if (pending_payout.pending_balance.value) + dlog("Pending payout: ${account_name} -> ${amount}", + ("account_name", pending_payout.owner(db).name) + ("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type))); dlog("Remaining balance not paid out: ${amount}", ("amount", asset(remaining_amount_to_distribute, payout_asset_type))); @@ -1144,7 +1104,7 @@ void process_dividend_assets(database& db) { const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter; - if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner) + if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, @@ -1156,14 +1116,15 @@ void process_dividend_assets(database& db) } - if (is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && + if (pending_balance_object.pending_balance.value && + is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) { dlog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); - db.adjust_balance(pending_balance_object.owner, + db.adjust_balance(pending_balance_object.owner, asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance, @@ -1179,7 +1140,7 @@ void process_dividend_assets(database& db) ++pending_balance_object_iter; } // we will always be left with the last holder's data, generate the virtual op for it now. - if (last_holder_account_id) + if (last_holder_account_id && payouts_for_this_holder.size()) { // we've moved on to a new account, generate the dividend payment virtual op for the previous one db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, @@ -1259,7 +1220,6 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g d._total_voting_stake = 0; const vesting_balance_index& vesting_index = d.get_index_type(); -#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX auto vesting_balances_begin = vesting_index.indices().get().lower_bound(boost::make_tuple(asset_id_type())); auto vesting_balances_end = @@ -1271,19 +1231,6 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g ("owner", vesting_balance_obj.owner(d).name) ("amount", vesting_balance_obj.balance.amount)); } -#else - const auto& vesting_balances = vesting_index.indices().get(); - for (const vesting_balance_object& vesting_balance_obj : vesting_balances) - { - if (vesting_balance_obj.balance.asset_id == asset_id_type() && vesting_balance_obj.balance.amount) - { - vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount; - dlog("Vesting balance for account: ${owner}, amount: ${amount}", - ("owner", vesting_balance_obj.owner(d).name) - ("amount", vesting_balance_obj.balance.amount)); - } - } -#endif } void operator()(const account_object& stake_account) { @@ -1305,18 +1252,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g auto itr = vesting_amounts.find(stake_account.id); if (itr != vesting_amounts.end()) voting_stake += itr->second.value; -// // working, but potential performance gap? -// auto vesting_range = d.get_index_type().indices().get().equal_range(stake_account.id); -// for (const vesting_balance_object& vesting_balance : boost::make_iterator_range(vesting_range.first, vesting_range.second)) -// { -// if (vesting_balance.balance.asset_id == asset_id_type()) -// { -// voting_stake += vesting_balance.balance.amount.value; -// dlog("Vote_tally_helper vesting balances for account: ${owner}, amount: ${amount}", -// ("owner", vesting_balance.owner(d).name) -// ("amount", vesting_balance.balance.amount)); -// } -// } + for( vote_id_type id : opinion_account.options.votes ) { uint32_t offset = id.instance(); diff --git a/libraries/chain/game_object.cpp b/libraries/chain/game_object.cpp index bc205f20..2bbcdd92 100644 --- a/libraries/chain/game_object.cpp +++ b/libraries/chain/game_object.cpp @@ -470,7 +470,8 @@ namespace graphene { namespace chain { { // we now know who played what, figure out if we have a winner const rock_paper_scissors_game_details& rps_game_details = game_details.get(); - if (rps_game_details.reveal_moves[0]->gesture == rps_game_details.reveal_moves[1]->gesture) + if (rps_game_details.reveal_moves[0] && rps_game_details.reveal_moves[1] && + rps_game_details.reveal_moves[0]->gesture == rps_game_details.reveal_moves[1]->gesture) ilog("The game was a tie, both players threw ${gesture}", ("gesture", rps_game_details.reveal_moves[0]->gesture)); else { diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 6e8cd61b..f96f8658 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -97,7 +97,8 @@ namespace graphene { namespace chain { tournament_join_operation, game_move_operation, asset_update_dividend_operation, - asset_dividend_distribution_operation // VIRTUAL + asset_dividend_distribution_operation, // VIRTUAL + tournament_payout_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/tournament.hpp b/libraries/chain/include/graphene/chain/protocol/tournament.hpp index 6c5299d4..b9a71523 100644 --- a/libraries/chain/include/graphene/chain/protocol/tournament.hpp +++ b/libraries/chain/include/graphene/chain/protocol/tournament.hpp @@ -36,6 +36,13 @@ namespace graphene { namespace chain { + enum class payout_type + { + prize_award, + buyin_refund, + rake_fee + }; + typedef fc::static_variant game_specific_options; /** @@ -163,8 +170,38 @@ namespace graphene { namespace chain { void validate()const; }; + struct tournament_payout_operation : public base_operation + { + struct fee_parameters_type {}; + + asset fee; + + /// The account received payout + account_id_type payout_account_id; + + /// The tournament generated payout + tournament_id_type tournament_id; + + /// The payout amount + asset payout_amount; + + payout_type type; + + extensions_type extensions; + + account_id_type fee_payer()const { return payout_account_id; } + share_type calculate_fee(const fee_parameters_type&)const { return 0; } + void validate()const {} + }; + } } +FC_REFLECT_ENUM(graphene::chain::payout_type, + (prize_award) + (buyin_refund) + (rake_fee) + ) + FC_REFLECT_TYPENAME( graphene::chain::game_specific_options ) FC_REFLECT_TYPENAME( graphene::chain::game_specific_moves ) FC_REFLECT( graphene::chain::tournament_options, @@ -196,7 +233,16 @@ FC_REFLECT( graphene::chain::game_move_operation, (player_account_id) (move) (extensions)) +FC_REFLECT( graphene::chain::tournament_payout_operation, + (fee) + (payout_account_id) + (tournament_id) + (payout_amount) + (type) + (extensions)) + FC_REFLECT( graphene::chain::tournament_create_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::tournament_join_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::game_move_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::tournament_payout_operation::fee_parameters_type, ) diff --git a/libraries/chain/protocol/tournament.cpp b/libraries/chain/protocol/tournament.cpp index f7faa93b..167072c5 100644 --- a/libraries/chain/protocol/tournament.cpp +++ b/libraries/chain/protocol/tournament.cpp @@ -61,5 +61,4 @@ void game_move_operation::validate()const { } - } } // namespace graphene::chain diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index bc7130c1..0ef5b364 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -180,11 +180,11 @@ namespace graphene { namespace chain { tournament_details_obj.matches = matches; }); - // OLEK + // find "bye" matches, complete missing player in the next match for (unsigned i = 0; i < num_matches_in_first_round; ++i) { const match_object& match = matches[i](event.db); - if (match.players.size() == 1) // is bye + if (match.players.size() == 1) // is "bye" { unsigned tournament_num_matches = tournament_details_obj.matches.size(); unsigned next_round_match_index = (i + tournament_num_matches + 1) / 2; @@ -192,21 +192,19 @@ namespace graphene { namespace chain { const match_object& next_round_match = tournament_details_obj.matches[next_round_match_index](event.db); event.db.modify(next_round_match, [&](match_object& next_match) { next_match.players.emplace_back(match.players[0]); - if (next_match.players.size() > 1) // bye + bye + if (next_match.players.size() > 1) // both previous matches were "bye" next_match.on_initiate_match(event.db); }); } } - // OLEK +#ifdef HELPFULL_DUMP_WHEN_SOLVING_BYE_MATCH_PROBLEM + wlog("###"); wdump((tournament_details_obj.matches[tournament_details_obj.matches.size() - 1])); - for( match_id_type mid : tournament_details_obj.matches ) - { wdump((mid(event.db))); - } - +#endif } void on_entry(const match_completed& event, tournament_state_machine_& fsm) @@ -243,13 +241,13 @@ namespace graphene { namespace chain { event.db.modify(next_round_match, [&](match_object& next_match_obj) { - // OLEK +#ifdef HELPFULL_DUMP_WHEN_SOLVING_BYE_MATCH_PROBLEM wdump((event.match.get_state())); wdump((event.match)); wdump((other_match.get_state())); wdump((other_match)); - - if (!event.match.match_winners.empty()) // if there is a winner +#endif + if (!event.match.match_winners.empty()) // if there is a winner { if (winner_index_in_next_match == 0) next_match_obj.players.insert(next_match_obj.players.begin(), *event.match.match_winners.begin()); @@ -257,22 +255,12 @@ namespace graphene { namespace chain { next_match_obj.players.push_back(*event.match.match_winners.begin()); } - //if (other_match.get_state() == match_state::match_complete) - // OLEK - if (!other_match.match_winners.empty()) + if (other_match.get_state() == match_state::match_complete) { -// // if other match was buy -// if (other_match.games.size() == 0 /*&& next_match_obj.players.size() < 2*/) -// { -// if (winner_index_in_next_match != 0) -// next_match_obj.players.insert(next_match_obj.players.begin(), *other_match.match_winners.begin()); -// else -// next_match_obj.players.push_back(*other_match.match_winners.begin()); -// } - // OLEK +#ifdef HELPFULL_DUMP_WHEN_SOLVING_BYE_MATCH_PROBLEM wdump((next_match_obj.get_state())); wdump((next_match_obj)); - +#endif next_match_obj.on_initiate_match(event.db); } @@ -296,7 +284,16 @@ namespace graphene { namespace chain { // for a period of time, not as a transfer back to the user; it doesn't matter // if they are currently authorized to transfer this asset, they never really // transferred it in the first place - event.db.adjust_balance(payer_pair.first, asset(payer_pair.second, fsm.tournament_obj->options.buy_in.asset_id)); + asset amount(payer_pair.second, fsm.tournament_obj->options.buy_in.asset_id); + event.db.adjust_balance(payer_pair.first, amount); + + // Generating a virtual operation that shows the payment + tournament_payout_operation op; + op.tournament_id = fsm.tournament_obj->id; + op.payout_amount = amount; + op.payout_account_id = payer_pair.first; + op.type = payout_type::buyin_refund; + event.db.push_applied_operation(op); } } }; @@ -305,29 +302,65 @@ namespace graphene { namespace chain { { void on_entry(const match_completed& event, tournament_state_machine_& fsm) { + tournament_object& tournament_obj = *fsm.tournament_obj; fc_ilog(fc::logger::get("tournament"), "Tournament ${id} is complete", - ("id", fsm.tournament_obj->id)); + ("id", tournament_obj.id)); + tournament_obj.end_time = event.db.head_block_time(); // Distribute prize money when a tournament ends #ifndef NDEBUG - const tournament_details_object& details = fsm.tournament_obj->tournament_details_id(event.db); + const tournament_details_object& details = tournament_obj.tournament_details_id(event.db); share_type total_prize = 0; for (const auto& payer_pair : details.payers) { total_prize += payer_pair.second; } - assert(total_prize == fsm.tournament_obj->prize_pool); + assert(total_prize == tournament_obj.prize_pool); #endif assert(event.match.match_winners.size() == 1); const account_id_type& winner = *event.match.match_winners.begin(); uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage; - share_type rake_amount = (fc::uint128_t(fsm.tournament_obj->prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); - event.db.adjust_balance(account_id_type(TOURNAMENT_RAKE_FEE_ACCOUNT_ID), - asset(rake_amount, fsm.tournament_obj->options.buy_in.asset_id)); - event.db.adjust_balance(winner, asset(fsm.tournament_obj->prize_pool - rake_amount, - fsm.tournament_obj->options.buy_in.asset_id)); - fsm.tournament_obj->end_time = event.db.head_block_time(); + share_type rake_amount = 0; + + const asset_object & asset_obj = tournament_obj.options.buy_in.asset_id(event.db); + optional dividend_id = asset_obj.dividend_data_id; + + if (dividend_id.valid()) + { + rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + } + asset won_prize(tournament_obj.prize_pool - rake_amount, tournament_obj.options.buy_in.asset_id); + tournament_payout_operation op; + + if (won_prize.amount.value) + { + // Adjusting balance of winner + event.db.adjust_balance(winner, won_prize); + + // Generating a virtual operation that shows the payment + op.tournament_id = tournament_obj.id; + op.payout_amount = won_prize; + op.payout_account_id = winner; + op.type = payout_type::prize_award; + event.db.push_applied_operation(op); + } + + if (dividend_id.valid() && rake_amount.value) + { + // Adjusting balance of dividend_distribution_account + const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id; + const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db); + const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account; + asset rake(rake_amount, tournament_obj.options.buy_in.asset_id); + event.db.adjust_balance(rake_account_id, rake); + + // Generating a virtual operation that shows the payment + op.payout_amount = rake; + op.payout_account_id = rake_account_id; + op.type = payout_type::rake_fee; + event.db.push_applied_operation(op); + } } }; @@ -350,45 +383,16 @@ namespace graphene { namespace chain { fc_ilog(fc::logger::get("tournament"), "In was_final_match guard, returning ${value}", ("value", event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size()])); - - // OLEK +#ifdef HELPFULL_DUMP_WHEN_SOLVING_BYE_MATCH_PROBLEM + wlog("###"); wdump((event.match.id)); wdump((tournament_details_obj.matches[tournament_details_obj.matches.size() - 1])); - for( match_id_type mid : tournament_details_obj.matches ) - { wdump((mid(event.db))); - } - - return event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size() - 1]; - } - -#if 0 - // OLEK - bool was_buy_match(const match_completed& event) - { - const tournament_details_object& tournament_details_obj = tournament_obj->tournament_details_id(event.db); - fc_ilog(fc::logger::get("tournament"), - "In was_buy_match guard, returning ${value}", - ("value", event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size()])); - - // OLEK - wdump((event.match.id)); - wdump((event.match)); - - /* - wdump((tournament_details_obj.matches[tournament_details_obj.matches.size() - 1])); - - for( match_id_type mid : tournament_details_obj.matches ) - { - wdump((mid(event.db))); - } - - return event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size() - 1]; - */ - return true; - } #endif + return event.match.id == tournament_details_obj.matches[tournament_details_obj.matches.size() - 1]; + } + void register_player(const player_registered& event) { fc_ilog(fc::logger::get("tournament"), @@ -416,7 +420,6 @@ namespace graphene { namespace chain { _row < awaiting_start, start_time_arrived, in_progress >, // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ _row < in_progress, match_completed, in_progress >, - //g_row < in_progress, match_completed, in_progress, &x::was_buy_match >, g_row < in_progress, match_completed, concluded, &x::was_final_match > // +---------------------------+-----------------------------+----------------------------+---------------------+----------------------+ > {}; diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index c31beed2..c0caf4fa 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -134,6 +134,7 @@ public: std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; std::string operator()(const asset_dividend_distribution_operation& op)const; + std::string operator()(const tournament_payout_operation& op)const; }; template @@ -3205,6 +3206,18 @@ std::string operation_printer::operator()(const asset_dividend_distribution_oper return ""; } +std::string operation_printer::operator()(const tournament_payout_operation& op)const +{ + asset_object payout_asset = wallet.get_asset(op.payout_amount.asset_id); + + out << "Tournament #" << std::string(object_id_type(op.tournament_id)) << " Payout : " + << "Account '" << wallet.get_account(op.payout_account_id).name + << "', Amount " << payout_asset.amount_to_pretty_string(op.payout_amount) << ", Type " + << (op.type == payout_type::buyin_refund ? "buyin refund" : (op.type == payout_type::rake_fee ? "rake fee" : "prize award")) + << "."; + return ""; +} + std::string operation_result_printer::operator()(const void_result& x) const { return ""; diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index f1f971b7..0995899f 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -75,7 +75,7 @@ int main(int argc, char** argv) { auto witness_plug = node->register_plugin(); auto history_plug = node->register_plugin(); auto market_history_plug = node->register_plugin(); - auto generate_genesis_plug = node->register_plugin(); + //auto generate_genesis_plug = node->register_plugin(); try { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b03d58a8..c1eced4e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -17,6 +17,10 @@ file(GLOB PERFORMANCE_TESTS "performance/*.cpp") add_executable( performance_test ${PERFORMANCE_TESTS} ${COMMON_SOURCES} ) target_link_libraries( performance_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB TOURNAMENT_TESTS "tournament/*.cpp") +add_executable( tournament_test ${TOURNAMENT_TESTS} ${COMMON_SOURCES} ) +target_link_libraries( tournament_test graphene_chain graphene_app graphene_account_history graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) + file(GLOB BENCH_MARKS "benchmarks/*.cpp") add_executable( chain_bench ${BENCH_MARKS} ${COMMON_SOURCES} ) target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_history graphene_time graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 1bd48bc6..f4ae577b 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -72,6 +72,7 @@ database_fixture::database_fixture() if( arg == "--show-test-names" ) std::cout << "running test " << boost::unit_test::framework::current_test_case().p_name << std::endl; } + auto ahplugin = app.register_plugin(); auto mhplugin = app.register_plugin(); init_account_pub_key = init_account_priv_key.get_public_key(); @@ -163,7 +164,7 @@ void database_fixture::verify_asset_supplies( const database& db ) share_type reported_core_in_orders; for( const tournament_object& t : tournaments_index ) - if (t.get_state() != tournament_state::concluded) + if (t.get_state() != tournament_state::concluded && t.get_state() != tournament_state::registration_period_expired) total_balances[t.options.buy_in.asset_id] += t.prize_pool; for( const account_balance_object& b : balance_index ) diff --git a/tests/tournament/tournament_tests.cpp b/tests/tournament/tournament_tests.cpp new file mode 100644 index 00000000..63f17999 --- /dev/null +++ b/tests/tournament/tournament_tests.cpp @@ -0,0 +1,1062 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include + + +#include +#include +#include +#include "../common/database_fixture.hpp" +#include + +using namespace graphene::chain; + +// defined if "bye" matches fix available +#define BYE_MATCHES_FIXED + +#define RAND_MAX_MIN(MAX, MIN) (std::rand() % ((MAX) - (MIN) + 1) + (MIN)) + +BOOST_AUTO_TEST_SUITE(tournament_tests) + +// class performing operations necessary for creating tournaments, +// having players join the tournaments and playing tournaments to completion. +class tournaments_helper +{ +public: + + tournaments_helper(database_fixture& df) : df(df) + { + assets.insert(asset_id_type()); + current_asset_idx = 0; + optional dividend_account = get_asset_dividend_account(asset_id_type()); + if (dividend_account.valid()) + players.insert(*dividend_account); + } + + const std::set& list_tournaments() + { + return tournaments; + } + + std::map> list_players_balances() + { + std::map> result; + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + asset a = df.db.get_balance(player_id, asset_id); + result[player_id][a.asset_id] = a.amount; + } + } + return result; + } + + std::map> get_players_fees() + { + return players_fees; + } + + void reset_players_fees() + { + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + players_fees[player_id][asset_id] = 0; + } + } + } + + void create_asset(const account_id_type& issuer_account_id, + const string& symbol, + uint8_t precision, + asset_options& common, + const fc::ecc::private_key& sig_priv_key) + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + asset_create_operation op; + op.issuer = issuer_account_id; + op.symbol = symbol; + op.precision = precision; + op.common_options = common; + + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + assets.insert(asset_id_type(++current_asset_idx)); + } + + void update_dividend_asset(const asset_id_type asset_to_update_id, + dividend_asset_options new_options, + const fc::ecc::private_key& sig_priv_key) + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + asset_update_dividend_operation update_op; + + update_op.issuer = asset_to_update_id(db).issuer; + update_op.asset_to_update = asset_to_update_id; + update_op.new_options = new_options; + + tx.operations = {update_op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + optional dividend_account = get_asset_dividend_account(asset_to_update_id); + if (dividend_account.valid()) + players.insert(*dividend_account); + } + + optional get_asset_dividend_account(const asset_id_type& asset_id) + { + graphene::chain::database& db = df.db; + optional result; + const asset_object& asset_obj = asset_id(db); + + if (asset_obj.dividend_data_id.valid()) + { + const asset_dividend_data_object& dividend_data = (*asset_obj.dividend_data_id)(db); + result = dividend_data.dividend_distribution_account; + } + return result; + } + + const tournament_id_type create_tournament (const account_id_type& creator, + const fc::ecc::private_key& sig_priv_key, + asset buy_in, + uint32_t number_of_players = 2, + uint32_t time_per_commit_move = 3, + uint32_t time_per_reveal_move = 1, + uint32_t number_of_wins = 3, + uint32_t registration_deadline = 3600, + uint32_t start_delay = 3, + uint32_t round_delay = 3, + bool insurance_enabled = false + ) + { + if (current_tournament_idx.valid()) + current_tournament_idx = *current_tournament_idx + 1; + else + current_tournament_idx = 0; + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction trx; + tournament_options options; + tournament_create_operation op; + rock_paper_scissors_game_options& game_options = options.game_options.get(); + + game_options.number_of_gestures = 3; + game_options.time_per_commit_move = time_per_commit_move; + game_options.time_per_reveal_move = time_per_reveal_move; + game_options.insurance_enabled = insurance_enabled; + + options.registration_deadline = db.head_block_time() + fc::seconds(registration_deadline + *current_tournament_idx); + options.buy_in = buy_in; + options.number_of_players = number_of_players; + options.start_delay = start_delay; + options.round_delay = round_delay; + options.number_of_wins = number_of_wins; + + op.creator = creator; + op.options = options; + trx.operations = {op}; + for( auto& op : trx.operations ) + db.current_fee_schedule().set_fee(op); + trx.validate(); + trx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(trx, sig_priv_key); + PUSH_TX(db, trx); + + tournament_id_type tournament_id = tournament_id_type(*current_tournament_idx); + tournaments.insert(tournament_id); + return tournament_id; + } + + void join_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& payer_id, + const fc::ecc::private_key& sig_priv_key, + asset buy_in + ) + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + tournament_join_operation op; + + op.payer_account_id = payer_id; + op.buy_in = buy_in; + op.player_account_id = player_id; + op.tournament_id = tournament_id; + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + players.insert(player_id); + players_keys[player_id] = sig_priv_key; + } + + // stolen from cli_wallet + void rps_throw(const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key + ) + { + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + // check whether the gesture is appropriate for the game we're playing + game_object game_obj = game_id(db); + match_object match_obj = game_obj.match_id(db); + tournament_object tournament_obj = match_obj.tournament_id(db); + rock_paper_scissors_game_options game_options = tournament_obj.options.game_options.get(); + assert((int)gesture < game_options.number_of_gestures); + + account_object player_account_obj = player_id(db); + + // construct the complete throw, the commit, and reveal + rock_paper_scissors_throw full_throw; + rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1)); + rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2)); + full_throw.gesture = gesture; + + rock_paper_scissors_throw_commit commit_throw; + commit_throw.nonce1 = full_throw.nonce1; + std::vector full_throw_packed(fc::raw::pack(full_throw)); + commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); + + rock_paper_scissors_throw_reveal reveal_throw; + reveal_throw.nonce2 = full_throw.nonce2; + reveal_throw.gesture = full_throw.gesture; + + // store off the reveal for applying after both players commit + committed_game_moves[commit_throw] = reveal_throw; + + signed_transaction tx; + game_move_operation move_operation; + move_operation.game_id = game_id; + move_operation.player_account_id = player_account_obj.id; + move_operation.move = commit_throw; + tx.operations = {move_operation}; + for( operation& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + } + + // spaghetti programming + // walking through all tournaments, matches and games and throwing random moves + void play_games() + { + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + rock_paper_scissors_game_options game_options = tournament.options.game_options.get(); + for(const auto& match_id: tournament_details.matches) + { + const match_object& match = match_id(db); + for(const auto& game_id: match.games ) + { + const game_object& game = game_id(db); + if (game.get_state() == game_state::expecting_commit_moves) + { + for(const auto& player_id: game.players) + { + if (players_keys.find(player_id) != players_keys.end()) + { + rps_throw(game_id, player_id, (rock_paper_scissors_gesture) (std::rand() % game_options.number_of_gestures), players_keys[player_id]); + } + } + } + else if (game.get_state() == game_state::expecting_reveal_moves) + { + const rock_paper_scissors_game_details& rps_details = game.game_details.get(); + + for (unsigned i = 0; i < 2; ++i) + { + if (rps_details.commit_moves.at(i) && + !rps_details.reveal_moves.at(i)) + { + const account_id_type& player_id = game.players[i]; + if (players_keys.find(player_id) != players_keys.end()) + { + { + auto iter = committed_game_moves.find(*rps_details.commit_moves.at(i)); + if (iter != committed_game_moves.end()) + { + const rock_paper_scissors_throw_reveal& reveal = iter->second; + + game_move_operation move_operation; + move_operation.game_id = game.id; + move_operation.player_account_id = player_id; + move_operation.move = reveal; + + signed_transaction tx; + tx.operations = {move_operation}; + for( auto& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, players_keys[player_id]); + PUSH_TX(db, tx); + } + } + } + } + } + } + } + } + } + } + +private: + database_fixture& df; + // index of last created tournament + fc::optional current_tournament_idx; + // index of last asset + uint64_t current_asset_idx; + // assets : core and maybe others + std::set assets; + // tournaments to be played + std::set tournaments; + // all players registered in tournaments + std::set players; + // players' private keys + std::map players_keys; + // total charges for moves made by every player + std::map> players_fees; + // store of commits and reveals + std::map committed_game_moves; + + // taken from rand.cpp + void rand_bytes(char* buf, int count) + { + fc::init_openssl(); + + int result = RAND_bytes((unsigned char*)buf, count); + if (result != 1) + FC_THROW("Error calling OpenSSL's RAND_bytes(): ${code}", ("code", (uint32_t)ERR_get_error())); + } +}; + +// Test of basic functionality creating two tournamenst, joinig players, +// playing tournaments to completion, distributing prize. +// Testing of "bye" matches handling can be performed if "bye" matches fix is available. +// Numbers of players 2+1 4+1 8+1 ... seem to be most critical for handling of "bye" matches. +// Moves are generated automatically. +BOOST_FIXTURE_TEST_CASE( simple, database_fixture ) +{ + try + { +#ifdef BYE_MATCHES_FIXED + #define TEST1_NR_OF_PLAYERS_NUMBER 3 + #define TEST2_NR_OF_PLAYERS_NUMBER 5 +#else + #define TEST1_NR_OF_PLAYERS_NUMBER 2 + #define TEST2_NR_OF_PLAYERS_NUMBER 4 +#endif + BOOST_TEST_MESSAGE("Hello simple tournament test"); + ACTORS((nathan)(alice)(bob)(carol)(dave)(ed)(frank)(george)(harry)(ike)); + + tournaments_helper tournament_helper(*this); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + + BOOST_TEST_MESSAGE( "Giving folks some money" ); + transfer(committee_account, nathan_id, asset(1000000000)); + transfer(committee_account, alice_id, asset(2000000)); + transfer(committee_account, bob_id, asset(3000000)); + transfer(committee_account, carol_id, asset(4000000)); + transfer(committee_account, dave_id, asset(5000000)); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + transfer(committee_account, ed_id, asset(6000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + transfer(committee_account, frank_id, asset(7000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + transfer(committee_account, george_id, asset(8000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + transfer(committee_account, harry_id, asset(9000000)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + transfer(committee_account, ike_id, asset(1000000)); +#endif + + BOOST_TEST_MESSAGE( "Preparing nathan" ); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + uint16_t tournaments_to_complete = 0; + asset buy_in = asset(12000); + tournament_id_type tournament_id; + + BOOST_TEST_MESSAGE( "Preparing a tournament" ); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST1_NR_OF_PLAYERS_NUMBER); + BOOST_REQUIRE(tournament_id == tournament_id_type()); + + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); +#if TEST1_NR_OF_PLAYERS_NUMBER > 2 + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); +#endif + ++tournaments_to_complete; + + BOOST_TEST_MESSAGE( "Preparing another one" ); + buy_in = asset(13000); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, TEST2_NR_OF_PLAYERS_NUMBER); + BOOST_REQUIRE(tournament_id == tournament_id_type(1)); + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); + tournament_helper.join_tournament(tournament_id, carol_id, carol_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("carol"))), buy_in); + tournament_helper.join_tournament(tournament_id, dave_id, dave_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("dave"))), buy_in); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + tournament_helper.join_tournament(tournament_id, ed_id, ed_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ed"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + tournament_helper.join_tournament(tournament_id, frank_id, frank_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("frank"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + tournament_helper.join_tournament(tournament_id, george_id, george_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("george"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + tournament_helper.join_tournament(tournament_id, harry_id, harry_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("harry"))), buy_in); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + tournament_helper.join_tournament(tournament_id, ike_id, ike_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("ike"))), buy_in); +#endif + ++tournaments_to_complete; + + auto abc = [&] (string s) + { +#if 0 + wlog(s); + auto a = db.get_balance(alice_id, asset_id_type()); wdump(("# alice's balance") (a)); + auto b = db.get_balance(bob_id, asset_id_type()); wdump(("# bob's balance") (b)); + auto c = db.get_balance(carol_id, asset_id_type()); wdump(("# carol's balance") (c)); + auto d = db.get_balance(dave_id, asset_id_type()); wdump(("# dave's balance") (d)); +#if TEST2_NR_OF_PLAYERS_NUMBER > 4 + auto e = db.get_balance(ed_id, asset_id_type()); wdump(("# ed's balance") (e)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 5 + auto f = db.get_balance(frank_id, asset_id_type()); wdump(("# frank's balance") (f)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 6 + auto g = db.get_balance(george_id, asset_id_type()); wdump(("# george's balance") (g)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 7 + auto h = db.get_balance(harry_id, asset_id_type()); wdump(("# harry's balance") (f)); +#endif +#if TEST2_NR_OF_PLAYERS_NUMBER > 8 + auto i = db.get_balance(ike_id, asset_id_type()); wdump(("# ike's balance") (i)); +#endif + + auto n = db.get_balance(nathan_id, asset_id_type()); wdump(("# nathan's balance") (n)); + auto r = db.get_balance(TOURNAMENT_RAKE_FEE_ACCOUNT_ID, asset_id_type()); wdump(("# rake's balance") (r)); +#endif + }; + +#if 0 + // trying to randomize automatic moves ? + auto n = std::rand() % 100; + for(int i = 0; i < n ; ++i) + db.get_random_bits(3); +#endif + abc("@ tournament awaiting start"); + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + generate_block(); + abc("@ after first generated block"); + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + while(tournaments_to_complete > 0) + { + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + + const account_object winner = winner_id(db); + BOOST_TEST_MESSAGE( "The winner is " + winner.name ); + + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + optional dividend_account = tournament_helper.get_asset_dividend_account(tournament.options.buy_in.asset_id); + if (dividend_account.valid()) + players_balances[*dividend_account][tournament.options.buy_in.asset_id] += rake_amount; + players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + generate_block(); + sleep(1); + } + + abc("@ tournament concluded"); + // checking if prizes were distributed correctly + BOOST_CHECK(tournaments.size() == 0); + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + BOOST_TEST_MESSAGE("Bye simple tournament test\n"); + + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} + +// Test of canceled tournament +// Checking buyin refund. +BOOST_FIXTURE_TEST_CASE( canceled, database_fixture ) +{ + try + { + BOOST_TEST_MESSAGE("Hello canceled tournament test"); + ACTORS((nathan)(alice)(bob)); + + tournaments_helper tournament_helper(*this); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + + BOOST_TEST_MESSAGE( "Giving folks some money" ); + transfer(committee_account, nathan_id, asset(1000000000)); + transfer(committee_account, alice_id, asset(2000000)); + transfer(committee_account, bob_id, asset(3000000)); + + BOOST_TEST_MESSAGE( "Preparing nathan" ); + upgrade_to_lifetime_member(nathan); + + uint16_t tournaments_to_complete = 0; + asset buy_in = asset(12340); + tournament_id_type tournament_id; + + BOOST_TEST_MESSAGE( "Preparing a tournament" ); + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, 3, 30, 30, 3, 5); + BOOST_REQUIRE(tournament_id == tournament_id_type()); + + tournament_helper.join_tournament(tournament_id, alice_id, alice_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("alice"))), buy_in); + tournament_helper.join_tournament(tournament_id, bob_id, bob_id, fc::ecc::private_key::regenerate(fc::sha256::hash(string("bob"))), buy_in); + ++tournaments_to_complete; + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournament's completion"); + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + + while(tournaments_to_complete > 0) + { + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + BOOST_REQUIRE(tournament.get_state() != tournament_state::concluded); + + if (tournament.get_state() == tournament_state::registration_period_expired) { + + + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + for(auto payer : tournament_details.payers) + { + players_balances[payer.first][tournament.options.buy_in.asset_id] += payer.second; + } + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + generate_block(); + sleep(1); + } + + // checking if buyins were refunded correctly + BOOST_CHECK(tournaments.size() == 0); + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + BOOST_TEST_MESSAGE("Bye canceled tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} + +// Test of few concurrently played tournaments having the same constant number of players. +// Tournament/s having even number use the core asset. +// Tournament/s having odd number use another asset. +// Moves are generated randomly. +// Checking prizes distribution for both assets is performed. +BOOST_FIXTURE_TEST_CASE( assets, database_fixture ) +{ + try + { + #define PLAYERS_NUMBER 8 + #define TOURNAMENTS_NUMBER 3 + #define DEF_SYMBOL "NEXT" + + BOOST_TEST_MESSAGE("Hello two assets tournament test"); + + ACTORS((nathan)); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + transfer(committee_account, nathan_id, asset(1000000000)); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + tournaments_helper tournament_helper(*this); + // creating new asset + asset_options aoptions; + aoptions.max_market_fee = aoptions.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + aoptions.flags = 0; + aoptions.issuer_permissions = 79; + aoptions.core_exchange_rate.base.amount = 1; + aoptions.core_exchange_rate.base.asset_id = asset_id_type(0); + aoptions.core_exchange_rate.quote.amount = 1; + aoptions.core_exchange_rate.quote.asset_id = asset_id_type(1); + tournament_helper.create_asset(nathan_id, DEF_SYMBOL, 5, aoptions, nathan_priv_key); + + issue_uia(nathan_id, asset(GRAPHENE_MAX_SHARE_SUPPLY/2, asset_id_type(1))); + + dividend_asset_options doptions; + doptions.minimum_distribution_interval = 3*24*60*60; + doptions.minimum_fee_percentage = 10*GRAPHENE_1_PERCENT; + doptions.next_payout_time = db.head_block_time() + fc::hours(1); + doptions.payout_interval = 7*24*60*60; + tournament_helper.update_dividend_asset(asset_id_type(1), doptions, nathan_priv_key); + +#if 0 + auto tas = get_asset(GRAPHENE_SYMBOL); wdump((tas)); + auto das = get_asset(DEF_SYMBOL); wdump((das)); + + if (das.dividend_data_id.valid()) + { + auto div = (*das.dividend_data_id)(db); wdump((div)); + auto dda = div.dividend_distribution_account(db); wdump((dda)); + } + + auto nac = nathan_id(db); wdump(("# nathan's account") (nac)); + + auto nab0 = db.get_balance(nathan_id, asset_id_type(0)); wdump(("# nathans's balance 0") (nab0)); + auto nab1 = db.get_balance(nathan_id, asset_id_type(1)); wdump(("# nathans's balance 1") (nab1)); +#endif + + // creating actors + std::vector> actors; + for(unsigned i = 0; i < PLAYERS_NUMBER; ++i) + { + std::string name = "account" + std::to_string(i); + auto priv_key = generate_private_key(name); + const auto& account = create_account(name, priv_key.get_public_key()); + actors.emplace_back(name, account.id, priv_key); + transfer(committee_account, account.id, asset((uint64_t)100000000 * PLAYERS_NUMBER + 10000000 * (i+1))); + transfer(nathan_id, account.id, asset((uint64_t)200000000 * PLAYERS_NUMBER + 20000000 * (i+1), asset_id_type(1))); + } + + // creating tournaments, registering players + for(unsigned i = 0; i < TOURNAMENTS_NUMBER; ++i) + { + asset buy_in = asset(1000 * PLAYERS_NUMBER + 100 * i, asset_id_type(i%2)); + tournament_id_type tournament_id; + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, PLAYERS_NUMBER, 30, 30); + + for (unsigned j = 0; j < PLAYERS_NUMBER; ++j) + { + auto a = actors[j]; + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } + } + + uint16_t tournaments_to_complete = TOURNAMENTS_NUMBER; + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + while(tournaments_to_complete > 0) + { + generate_block(); + tournament_helper.play_games(); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + optional dividend_account = tournament_helper.get_asset_dividend_account(tournament.options.buy_in.asset_id); + if (dividend_account.valid()) + players_balances[*dividend_account][tournament.options.buy_in.asset_id] += rake_amount; players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + BOOST_CHECK(tournaments.size() == 0); + // checking if prizes were distributed correctly + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s " + GRAPHENE_SYMBOL + " balance " + std::to_string((uint64_t) (a.second[asset_id_type()].value))); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s " + DEF_SYMBOL + " balance " + std::to_string((uint64_t) (a.second[asset_id_type(1)].value))); + BOOST_CHECK(a.second[asset_id_type(1)] == players_balances[a.first][asset_id_type(1)]); + } + + BOOST_TEST_MESSAGE("Bye two assets tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} + +// Test of concurrently played tournaments having +// 2, 4, 8 ... 64 players randomly registered from global pool +// and randomized number of wins, +// generates random moves, +// checks prizes distribution and fees calculation. +// No "bye" matches. +BOOST_FIXTURE_TEST_CASE( basic, database_fixture ) +{ + try + { + #define MIN_PLAYERS_NUMBER 2 + #define MAX_PLAYERS_NUMBER 64 + + #define MIN_WINS_NUMBER 2 + #define MAX_WINS_NUMBER 5 + + BOOST_TEST_MESSAGE("Hello basic tournament test"); + + ACTORS((nathan)); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + transfer(committee_account, nathan_id, asset(1000000000)); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + // creating random order of numbers of players joining tournaments + std::vector players; + for(unsigned i = MIN_PLAYERS_NUMBER; i <= MAX_PLAYERS_NUMBER; i*=2) + { + players.emplace_back(i); + } + for (unsigned i = players.size() - 1; i >= 1; --i) + { + if (std::rand() % 2 == 0) continue; + unsigned j = std::rand() % i; + std::swap(players[i], players[j]); + } + + // creating a pool of actors + std::vector> actors; + for(unsigned i = 0; i < 3 * MAX_PLAYERS_NUMBER; ++i) + { + std::string name = "account" + std::to_string(i); + auto priv_key = generate_private_key(name); + const auto& account = create_account(name, priv_key.get_public_key()); + actors.emplace_back(name, account.id, priv_key); + transfer(committee_account, account.id, asset((uint64_t)1000000000 * players.size() + 100000000 * (i+1))); + } +#if 0 + enable_fees(); + wdump((db.get_global_properties())); +#endif + // creating tournaments, registering players + tournaments_helper tournament_helper(*this); + for (unsigned i = 0; i < players.size(); ++i) + { + auto number_of_players = players[i]; + auto number_of_wins = RAND_MAX_MIN(MAX_WINS_NUMBER, MIN_WINS_NUMBER); + BOOST_TEST_MESSAGE( "Preparing tournament with " + std::to_string(number_of_players) + " players and " + std::to_string(number_of_wins) + " wins" ); + + asset buy_in = asset(1000 * number_of_players + 100 * i); + tournament_id_type tournament_id; + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, number_of_players, 30, 30, number_of_wins); + + for (unsigned j = 0; j < actors.size() && number_of_players > 0; ++j) + { + if (number_of_players < actors.size() - j && std::rand() % 2 == 0) continue; + auto a = actors[j]; + --number_of_players; + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } + } + + uint16_t tournaments_to_complete = players.size(); + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_initial_balances = tournament_helper.list_players_balances(); + tournament_helper.reset_players_fees(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; +#if 0 + wlog( "Prepared tournaments:"); + for(const tournament_id_type& tid: tournament_helper.list_tournaments()) + { + const tournament_object tournament = tid(db); + //const tournament_details_object details = tournament.tournament_details_id(db); + wlog(" # ${i}, players count ${c}, wins number ${w}", ("i", tid.instance) ("c", tournament.registered_players) ("w", tournament.options.number_of_wins )); + } + +#endif + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + tournament_helper.reset_players_fees(); + while(tournaments_to_complete > 0) + { + generate_block(); + enable_fees(); + tournament_helper.play_games(); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + optional dividend_account = tournament_helper.get_asset_dividend_account(tournament.options.buy_in.asset_id); + if (dividend_account.valid()) + players_initial_balances[*dividend_account][tournament.options.buy_in.asset_id] += rake_amount; + players_initial_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + BOOST_CHECK(tournaments.size() == 0); + + // checking if prizes were distributed correctly and fees calculated properly + std::map> current_players_balances = tournament_helper.list_players_balances(); + std::map> players_paid_fees = tournament_helper.get_players_fees(); + for (auto a: current_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_initial_balances[a.first][asset_id_type()] + players_paid_fees[a.first][asset_id_type()]); + } + + BOOST_TEST_MESSAGE("Bye basic tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } + +} + +#ifdef BYE_MATCHES_FIXED +// Test of several concurrently played tournaments having +// randomized number of players registered from global pool, +// and randomized number of wins, +// generates random moves, +// checks prizes distribution. +// "bye" matches fix is required. +BOOST_FIXTURE_TEST_CASE( massive, database_fixture ) +{ + try + { + #define MIN_TOURNAMENTS_NUMBER 1 + #define MAX_TOURNAMENTS_NUMBER 10 + + #define MIN_PLAYERS_NUMBER 2 + #define MAX_PLAYERS_NUMBER 64 + + #define MIN_WINS_NUMBER 2 + #define MAX_WINS_NUMBER 5 + + BOOST_TEST_MESSAGE("Hello massive tournament test"); + + ACTORS((nathan)); + fc::ecc::private_key nathan_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + transfer(committee_account, nathan_id, asset(1000000000)); + upgrade_to_lifetime_member(nathan); + BOOST_CHECK(nathan.is_lifetime_member()); + + // creating a pool of actors + std::vector> actors; + for(unsigned i = 0; i < 2 * MAX_PLAYERS_NUMBER; ++i) + { + std::string name = "account" + std::to_string(i); + auto priv_key = generate_private_key(name); + const auto& account = create_account(name, priv_key.get_public_key()); + actors.emplace_back(name, account.id, priv_key); + transfer(committee_account, account.id, asset((uint64_t)1000000 * MAX_TOURNAMENTS_NUMBER + 100000 * (i+1))); + } + + // creating tournaments, registering players + tournaments_helper tournament_helper(*this); + unsigned number_of_tournaments = RAND_MAX_MIN(MAX_TOURNAMENTS_NUMBER, MIN_TOURNAMENTS_NUMBER); + for(unsigned i = 0; i < number_of_tournaments; ++i) + { + unsigned number_of_players = RAND_MAX_MIN(MAX_PLAYERS_NUMBER, MIN_PLAYERS_NUMBER); + unsigned number_of_wins = RAND_MAX_MIN(MAX_WINS_NUMBER, MIN_WINS_NUMBER); + BOOST_TEST_MESSAGE( "Preparing tournament with " + std::to_string(number_of_players) + " players and " + std::to_string(number_of_wins) + " wins" ); + + asset buy_in = asset(1000 * number_of_players + 100 * i); + tournament_id_type tournament_id; + tournament_id = tournament_helper.create_tournament (nathan_id, nathan_priv_key, buy_in, number_of_players, 30, 30, number_of_wins); + + for (unsigned j = 0; j < actors.size() && number_of_players > 0; ++j) + { + if (number_of_players < actors.size() - j && std::rand() % 2 == 0) continue; + auto a = actors[j]; + --number_of_players; + tournament_helper.join_tournament(tournament_id, std::get<1>(a), std::get<1>(a), std::get<2>(a), buy_in); + } + } + + uint16_t tournaments_to_complete = number_of_tournaments; + std::set tournaments = tournament_helper.list_tournaments(); + std::map> players_balances = tournament_helper.list_players_balances(); + uint16_t rake_fee_percentage = db.get_global_properties().parameters.rake_fee_percentage; + + BOOST_TEST_MESSAGE( "Generating blocks, waiting for tournaments' completion"); + while(tournaments_to_complete > 0) + { + generate_block(); + tournament_helper.play_games(); + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + if (tournament.get_state() == tournament_state::concluded) { + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + const match_object& final_match = (tournament_details.matches[tournament_details.matches.size() - 1])(db); + + assert(final_match.match_winners.size() == 1); + const account_id_type& winner_id = *final_match.match_winners.begin(); + BOOST_TEST_MESSAGE( "The winner of " + std::string(object_id_type(tournament_id)) + " is " + winner_id(db).name + " " + std::string(object_id_type(winner_id))); + share_type rake_amount = (fc::uint128_t(tournament.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64(); + optional dividend_account = tournament_helper.get_asset_dividend_account(tournament.options.buy_in.asset_id); + if (dividend_account.valid()) + players_balances[*dividend_account][tournament.options.buy_in.asset_id] += rake_amount; + players_balances[winner_id][tournament.options.buy_in.asset_id] += tournament.prize_pool - rake_amount; + + tournaments.erase(tournament_id); + --tournaments_to_complete; + break; + } + } + sleep(1); + } + BOOST_CHECK(tournaments.size() == 0); +#if 0 + wlog( "Performed tournaments:"); + for(const tournament_id_type& tid: tournament_helper.list_tournaments()) + { + const tournament_object tournament = tid(db); + //const tournament_details_object details = tournament.tournament_details_id(db); + wlog(" # ${i}, players count ${c}, wins number ${w}", ("i", tid.instance) ("c", tournament.registered_players) ("w", tournament.options.number_of_wins )); + } +#endif + // checking if prizes were distributed correctly + std::map> last_players_balances = tournament_helper.list_players_balances(); + for (auto a: last_players_balances) + { + BOOST_TEST_MESSAGE( "Checking " + a.first(db).name + "'s balance " + std::to_string((uint64_t)(a.second[asset_id_type()].value)) ); + BOOST_CHECK(a.second[asset_id_type()] == players_balances[a.first][asset_id_type()]); + } + + BOOST_TEST_MESSAGE("Bye massive tournament test\n"); + } + catch (fc::exception& e) + { + edump((e.to_detail_string())); + throw; + } +} +#endif + +BOOST_AUTO_TEST_SUITE_END() + +//#define BOOST_TEST_MODULE "C++ Unit Tests for Graphene Blockchain Database" +#include +#include +#include + +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; + return nullptr; +}