From a8d5fded265487e2c5cb51d3b14d70b1933f2ad9 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Wed, 9 Aug 2017 11:12:59 -0400 Subject: [PATCH] Remove the code for charging beting fees up-front now that rake fees are taken from your net winnings. Change the bet matching algorithm to only match at exactly the maker's odds, no rounding. Implement binned order books in the betting market plugin. Keep betting market groups, betting markets, and bet objects around forever in the plugin (not yet exposed to the api). --- libraries/app/impacted.cpp | 4 + libraries/chain/betting_market_evaluator.cpp | 23 +- libraries/chain/db_bet.cpp | 121 +++++---- libraries/chain/db_notify.cpp | 1 + .../graphene/chain/betting_market_object.hpp | 23 +- .../chain/protocol/betting_market.hpp | 58 +++-- .../graphene/chain/protocol/operations.hpp | 3 +- libraries/plugins/bookie/bookie_api.cpp | 24 +- libraries/plugins/bookie/bookie_plugin.cpp | 235 +++++++++++++++-- .../include/graphene/bookie/bookie_plugin.hpp | 10 +- .../wallet/include/graphene/wallet/wallet.hpp | 1 - libraries/wallet/wallet.cpp | 2 - tests/betting/betting_tests.cpp | 243 ++++++++++-------- tests/common/database_fixture.cpp | 4 +- tests/common/database_fixture.hpp | 2 +- 15 files changed, 511 insertions(+), 243 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 353810e4..ebcd5c2b 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -236,6 +236,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.bettor_id ); } + void operator()( const bet_adjusted_operation& op ) + { + _impacted.insert( op.bettor_id ); + } void operator()( const bet_matched_operation& op ) { _impacted.insert( op.bettor_id ); diff --git a/libraries/chain/betting_market_evaluator.cpp b/libraries/chain/betting_market_evaluator.cpp index 1cf73d8a..8cbb8bfb 100644 --- a/libraries/chain/betting_market_evaluator.cpp +++ b/libraries/chain/betting_market_evaluator.cpp @@ -291,26 +291,14 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op) simulated_bet.betting_market_id = op.betting_market_id; simulated_bet.amount_to_bet = op.amount_to_bet; simulated_bet.backer_multiplier = op.backer_multiplier; - simulated_bet.amount_reserved_for_fees = op.amount_reserved_for_fees; simulated_bet.back_or_lay = op.back_or_lay; share_type required_deposit = get_required_deposit_for_bet(simulated_bet); #endif - // verify they reserved enough to cover the percentage fee - uint16_t percentage_fee = current_params.current_fees->get().percentage_fee; - fc::uint128_t minimum_percentage_fee_calculation = op.amount_to_bet.amount.value; - minimum_percentage_fee_calculation *= percentage_fee; - minimum_percentage_fee_calculation += GRAPHENE_100_PERCENT - 1; // round up - minimum_percentage_fee_calculation /= GRAPHENE_100_PERCENT; - share_type minimum_percentage_fee = minimum_percentage_fee_calculation.to_uint64(); - FC_ASSERT(op.amount_reserved_for_fees >= minimum_percentage_fee, "insufficient fees", - ("fee_provided", op.amount_reserved_for_fees)("fee_required", minimum_percentage_fee)); - // do they have enough in their account to place the bet - _stake_plus_fees = op.amount_to_bet.amount + op.amount_reserved_for_fees; - FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= _stake_plus_fees, "insufficient balance", - ("balance", d.get_balance(*fee_paying_account, *_asset))("stake_plus_fees", _stake_plus_fees) ); + FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= op.amount_to_bet.amount, "insufficient balance", + ("balance", d.get_balance(*fee_paying_account, *_asset))("amount_to_bet", op.amount_to_bet.amount) ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -324,15 +312,16 @@ object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op) bet_obj.betting_market_id = op.betting_market_id; bet_obj.amount_to_bet = op.amount_to_bet; bet_obj.backer_multiplier = op.backer_multiplier; - bet_obj.amount_reserved_for_fees = op.amount_reserved_for_fees; bet_obj.back_or_lay = op.back_or_lay; }); - d.adjust_balance(fee_paying_account->id, asset(-_stake_plus_fees, _betting_market_group->asset_id)); + bet_id_type new_bet_id = new_bet.id; // save the bet id here, new_bet may be deleted during place_bet() + + d.adjust_balance(fee_paying_account->id, -op.amount_to_bet); bool bet_matched = d.place_bet(new_bet); - return new_bet.id; + return new_bet_id; } FC_CAPTURE_AND_RETHROW( (op) ) } void_result bet_cancel_evaluator::do_evaluate(const bet_cancel_operation& op) diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index e0918f5e..dd9d85e9 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -11,15 +11,12 @@ namespace graphene { namespace chain { void database::cancel_bet( const bet_object& bet, bool create_virtual_op ) { asset amount_to_refund = bet.amount_to_bet; - amount_to_refund += bet.amount_reserved_for_fees; //TODO: update global statistics - adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake + fees - //TODO: do special fee accounting as required + adjust_balance(bet.bettor_id, amount_to_refund); if (create_virtual_op) { bet_canceled_operation bet_canceled_virtual_op(bet.bettor_id, bet.id, - bet.amount_to_bet, - bet.amount_reserved_for_fees); + bet.amount_to_bet); //idump((bet_canceled_virtual_op)); push_applied_operation(std::move(bet_canceled_virtual_op)); } @@ -127,7 +124,6 @@ void database::resolve_betting_market_group(const betting_market_group_object& b uint16_t rake_fee_percentage = get_global_properties().parameters.betting_rake_fee_percentage; share_type net_profits; share_type payout_amounts; - share_type fees_collected; account_id_type bettor_id = bettor_positions_pair.first; const std::vector& bettor_positions = bettor_positions_pair.second; @@ -149,7 +145,6 @@ void database::resolve_betting_market_group(const betting_market_group_object& b { share_type total_payout = position->pay_if_payout_condition + position->pay_if_not_canceled; payout_amounts += total_payout; - fees_collected += position->fees_collected; net_profits += total_payout - position->pay_if_canceled; break; } @@ -157,12 +152,11 @@ void database::resolve_betting_market_group(const betting_market_group_object& b { share_type total_payout = position->pay_if_not_payout_condition + position->pay_if_not_canceled; payout_amounts += total_payout; - fees_collected += position->fees_collected; net_profits += total_payout - position->pay_if_canceled; break; } case betting_market_resolution_type::cancel: - payout_amounts += position->pay_if_canceled + position->fees_collected; + payout_amounts += position->pay_if_canceled; break; default: continue; @@ -179,9 +173,6 @@ void database::resolve_betting_market_group(const betting_market_group_object& b adjust_balance(*rake_account_id, asset(rake_amount, betting_market_group.asset_id)); } - if (fees_collected.value) - adjust_balance(*rake_account_id, asset(fees_collected, betting_market_group.asset_id)); - // pay winning - rake adjust_balance(bettor_id, asset(payout_amounts - rake_amount, betting_market_group.asset_id)); // [ROL] @@ -191,7 +182,7 @@ void database::resolve_betting_market_group(const betting_market_group_object& b betting_market_group.id, resolutions, payout_amounts, - fees_collected)); + rake_amount)); } betting_market_itr = betting_market_index.lower_bound(betting_market_group.id); @@ -213,27 +204,7 @@ void database::get_required_deposit_for_bet(const betting_market_object& betting } #endif -bool maybe_cull_small_bet( database& db, const bet_object& bet_object_to_cull ) -{ - /** - * There are times when this bet can't be even partially matched at its stated odds - * For example, say it's a back bet for 20 satoshis at 1.92 odds (which comes out to 25:23 - * odds). It's not possible for it to match at exact odds since it's < 25 - * - * Remove these bets from the books to reduce clutter. - */ - share_type minimum_matchable_amount = bet_object_to_cull.get_minimum_matchable_amount(); - if (bet_object_to_cull.amount_to_bet.amount < minimum_matchable_amount) - { - dlog("culling small bet of ${amount}, smaller than the minimum ${minimum_matchable_amount} at odds ${odds}", - ("amount", bet_object_to_cull.amount_to_bet.amount)("minimum_matchable_amount", minimum_matchable_amount)("odds", bet_object_to_cull.backer_multiplier)); - db.cancel_bet(bet_object_to_cull); - return true; - } - return false; -} - -share_type adjust_betting_position(database& db, account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, share_type bet_amount, share_type matched_amount, share_type fees_collected) +share_type adjust_betting_position(database& db, account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, share_type bet_amount, share_type matched_amount) { try { assert(bet_amount >= 0); @@ -253,7 +224,6 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett position.pay_if_not_payout_condition = back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0; position.pay_if_canceled = bet_amount; position.pay_if_not_canceled = 0; - position.fees_collected = fees_collected; // this should not be reducible }); } else { @@ -263,7 +233,6 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett position.pay_if_payout_condition += back_or_lay == bet_type::back ? bet_amount + matched_amount : 0; position.pay_if_not_payout_condition += back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0; position.pay_if_canceled += bet_amount; - position.fees_collected += fees_collected; guaranteed_winnings_returned = position.reduce(); }); @@ -275,29 +244,21 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett // called twice when a bet is matched, once for the taker, once for the maker bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet, share_type amount_matched, - bet_multiplier_type actual_multiplier, bool cull_if_small) + bet_multiplier_type actual_multiplier) { - // calculate the percentage fee paid - fc::uint128_t percentage_fee_128 = bet.amount_reserved_for_fees.value; - percentage_fee_128 *= amount_bet.value; - percentage_fee_128 += bet.amount_to_bet.amount.value - 1; - percentage_fee_128 /= bet.amount_to_bet.amount.value; - share_type fee_paid = percentage_fee_128.to_uint64(); - // record their bet, modifying their position, and return any winnings share_type guaranteed_winnings_returned = adjust_betting_position(db, bet.bettor_id, bet.betting_market_id, - bet.back_or_lay, amount_bet, amount_matched, fee_paid); + bet.back_or_lay, amount_bet, amount_matched); db.adjust_balance(bet.bettor_id, asset(guaranteed_winnings_returned, bet.amount_to_bet.asset_id)); // generate a virtual "match" op asset asset_amount_bet(amount_bet, bet.amount_to_bet.asset_id); - bet_matched_operation bet_matched_virtual_op(bet.bettor_id, bet.id, bet.betting_market_id, + bet_matched_operation bet_matched_virtual_op(bet.bettor_id, bet.id, asset_amount_bet, - fee_paid, actual_multiplier, guaranteed_winnings_returned); - //idump((bet_matched_virtual_op)); + //edump((bet_matched_virtual_op)); db.push_applied_operation(std::move(bet_matched_virtual_op)); // update the bet on the books @@ -310,12 +271,7 @@ bool bet_was_matched(database& db, const bet_object& bet, { db.modify(bet, [&](bet_object& bet_obj) { bet_obj.amount_to_bet -= asset_amount_bet; - bet_obj.amount_reserved_for_fees -= fee_paid; }); - //TODO: cull_if_small is currently always true, remove the parameter if we don't find a - // need for it soon - if (cull_if_small) - return maybe_cull_small_bet(db, bet); return false; } } @@ -334,7 +290,7 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker { assert(taker_bet.amount_to_bet.asset_id == maker_bet.amount_to_bet.asset_id); assert(taker_bet.amount_to_bet.amount > 0 && maker_bet.amount_to_bet.amount > 0); - assert(taker_bet.backer_multiplier >= maker_bet.backer_multiplier); + assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier : taker_bet.backer_multiplier >= maker_bet.backer_multiplier); assert(taker_bet.back_or_lay != maker_bet.back_or_lay); int result = 0; @@ -381,37 +337,80 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker //idump((taker_amount_to_match)(maker_amount_to_match)); - result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier, true); - result |= bet_was_matched(db, maker_bet, maker_amount_to_match, taker_amount_to_match, maker_bet.backer_multiplier, true) << 1; + result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier); + result |= bet_was_matched(db, maker_bet, maker_amount_to_match, taker_amount_to_match, maker_bet.backer_multiplier) << 1; assert(result != 0); return result; } + +// called from the bet_place_evaluator bool database::place_bet(const bet_object& new_bet_object) { - bet_id_type bet_id = new_bet_object.id; - const asset_object& bet_asset = get(new_bet_object.amount_to_bet.asset_id); - const auto& bet_odds_idx = get_index_type().indices().get(); bet_type bet_type_to_match = new_bet_object.back_or_lay == bet_type::back ? bet_type::lay : bet_type::back; auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match)); auto book_end = bet_odds_idx.upper_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match, new_bet_object.backer_multiplier)); + // ilog(""); + // ilog("------------ order book ------------------"); + // for (auto itr = book_itr; itr != book_end; ++itr) + // idump((*itr)); + // ilog("------------ order book ------------------"); + int orders_matched_flags = 0; bool finished = false; while (!finished && book_itr != book_end) { auto old_book_itr = book_itr; ++book_itr; - // match returns 2 when only the old order was fully filled. In this case, we keep matching; otherwise, we stop. orders_matched_flags = match_bet(*this, new_bet_object, *old_book_itr); + + // we continue if the maker bet was completely consumed AND the taker bet was not finished = orders_matched_flags != 2; } - return (orders_matched_flags & 1) != 0; + if (!(orders_matched_flags & 1)) + { + // if the new (taker) bet was not completely consumed, we need to put whatever remains + // of it on the books. But we only allow bets that can be exactly matched + // on the books, so round the amount down if necessary + share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount(); + share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount; + share_type rounded_bet_amount = scale_factor * minimum_matchable_amount; + //idump((new_bet_object.amount_to_bet.amount)(rounded_bet_amount)(minimum_matchable_amount)(scale_factor)); + + if (rounded_bet_amount == share_type()) + { + // the remainder of the bet was too small to match, cancel the bet + cancel_bet(new_bet_object, true); + return true; + } + else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount) + { + asset stake_returned = new_bet_object.amount_to_bet; + stake_returned.amount -= rounded_bet_amount; + + modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) { + modified_bet_object.amount_to_bet.amount = rounded_bet_amount; + }); + + adjust_balance(new_bet_object.bettor_id, stake_returned); + // TODO: update global statistics + bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id, + stake_returned); + // idump((bet_adjusted_op)(new_bet_object)); + push_applied_operation(std::move(bet_adjusted_op)); + return false; + } + else + return false; + } + else + return true; } } } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 34810391..906d23a6 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -210,6 +210,7 @@ struct get_impacted_account_visitor void operator()(const bet_matched_operation &){} void operator()(const bet_cancel_operation&){} void operator()(const bet_canceled_operation &){} + void operator()(const bet_adjusted_operation &){} void operator()( const tournament_create_operation& op ) { diff --git a/libraries/chain/include/graphene/chain/betting_market_object.hpp b/libraries/chain/include/graphene/chain/betting_market_object.hpp index 4e944871..2f8c97c5 100644 --- a/libraries/chain/include/graphene/chain/betting_market_object.hpp +++ b/libraries/chain/include/graphene/chain/betting_market_object.hpp @@ -94,8 +94,6 @@ class bet_object : public graphene::db::abstract_object< bet_object > bet_multiplier_type backer_multiplier; - share_type amount_reserved_for_fees; // same asset type as amount_to_bet - bet_type back_or_lay; static share_type get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up = false); @@ -241,7 +239,10 @@ struct compare_bet_by_odds { return true; if (lhs_bet_type > rhs_bet_type) return false; - return lhs_backer_multiplier < rhs_backer_multiplier; + if (lhs_bet_type == bet_type::back) + return lhs_backer_multiplier < rhs_backer_multiplier; + else + return lhs_backer_multiplier > rhs_backer_multiplier; } bool compare(const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type, bet_multiplier_type lhs_backer_multiplier, const bet_id_type& lhs_bet_id, @@ -257,9 +258,9 @@ struct compare_bet_by_odds { if (lhs_bet_type > rhs_bet_type) return false; if (lhs_backer_multiplier < rhs_backer_multiplier) - return true; + return lhs_bet_type == bet_type::back; if (lhs_backer_multiplier > rhs_backer_multiplier) - return false; + return lhs_bet_type == bet_type::lay; return lhs_bet_id < rhs_bet_id; } }; @@ -381,7 +382,10 @@ struct compare_bet_by_bettor_then_odds { return true; if (lhs_bet_type > rhs_bet_type) return false; - return lhs_backer_multiplier < rhs_backer_multiplier; + if (lhs_bet_type == bet_type::back) + return lhs_backer_multiplier < rhs_backer_multiplier; + else + return lhs_backer_multiplier > rhs_backer_multiplier; } bool compare(const betting_market_id_type& lhs_betting_market_id, const account_id_type& lhs_bettor_id, bet_type lhs_bet_type, @@ -403,9 +407,10 @@ struct compare_bet_by_bettor_then_odds { if (lhs_bet_type > rhs_bet_type) return false; if (lhs_backer_multiplier < rhs_backer_multiplier) - return true; + return lhs_bet_type == bet_type::back; if (lhs_backer_multiplier > rhs_backer_multiplier) - return false; + return lhs_bet_type == bet_type::lay; + return lhs_bet_id < rhs_bet_id; } }; @@ -444,6 +449,6 @@ typedef generic_index operation; /// @} // operations group diff --git a/libraries/plugins/bookie/bookie_api.cpp b/libraries/plugins/bookie/bookie_api.cpp index 375cbbc5..1af4fc5b 100644 --- a/libraries/plugins/bookie/bookie_api.cpp +++ b/libraries/plugins/bookie/bookie_api.cpp @@ -80,13 +80,15 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin } }; + // iterate through both sides of the order book (backs at increasing odds then lays at decreasing odds) for (auto bet_odds_iter = bet_odds_idx.lower_bound(std::make_tuple(betting_market_id)); bet_odds_iter != bet_odds_idx.end() && betting_market_id == bet_odds_iter->betting_market_id; ++bet_odds_iter) { if (current_bin && - (bet_odds_iter->back_or_lay == current_bin->back_or_lay || - bet_odds_iter->backer_multiplier > current_bin->backer_multiplier)) + (bet_odds_iter->back_or_lay == current_bin->back_or_lay /* we have switched from back to lay bets */ || + (bet_odds_iter->back_or_lay == bet_type::back ? bet_odds_iter->backer_multiplier > current_bin->backer_multiplier : + bet_odds_iter->backer_multiplier < current_bin->backer_multiplier))) flush_current_bin(); if (!current_bin) @@ -94,9 +96,21 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin // if there is no current bin, create one appropriate for the bet we're processing current_bin = graphene::chain::bet_object(); - current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size; - current_bin->backer_multiplier = std::min(current_bin->backer_multiplier, current_params.max_bet_multiplier); - current_bin->back_or_lay = bet_odds_iter->back_or_lay == bet_type::back ? bet_type::lay : bet_type::back; + // for back bets, we want to group all bets with odds from 3.0001 to 4 into the "4" bin + // for lay bets, we want to group all bets with odds from 3 to 3.9999 into the "3" bin + if (bet_odds_iter->back_or_lay == bet_type::back) + { + current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size; + current_bin->backer_multiplier = std::min(current_bin->backer_multiplier, current_params.max_bet_multiplier); + current_bin->back_or_lay = bet_type::lay; + } + else + { + current_bin->backer_multiplier = bet_odds_iter->backer_multiplier / bin_size * bin_size; + current_bin->backer_multiplier = std::max(current_bin->backer_multiplier, current_params.min_bet_multiplier); + current_bin->back_or_lay = bet_type::back; + } + current_bin->amount_to_bet.amount = 0; } diff --git a/libraries/plugins/bookie/bookie_plugin.cpp b/libraries/plugins/bookie/bookie_plugin.cpp index 70cee06e..681f18a2 100644 --- a/libraries/plugins/bookie/bookie_plugin.cpp +++ b/libraries/plugins/bookie/bookie_plugin.cpp @@ -55,7 +55,7 @@ namespace graphene { namespace bookie { namespace detail { -class persistent_event_object : public graphene::db::abstract_object +class persistent_event_object : public graphene::db::abstract_object { public: static const uint8_t space_id = bookie_objects; @@ -124,6 +124,117 @@ void events_by_competitor_index::object_modified( const object& after ) } #endif +//////////// betting_market_group_object ////////////////// +class persistent_betting_market_group_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = bookie_objects; + static const uint8_t type_id = persistent_betting_market_group_object_type; + + betting_market_group_object ephemeral_betting_market_group_object; + + share_type total_matched_bets_amount; + + betting_market_group_id_type get_betting_market_group_id() const { return ephemeral_betting_market_group_object.id; } +}; + +struct by_betting_market_group_id; +typedef multi_index_container< + persistent_betting_market_group_object, + indexed_by< + ordered_unique, member >, + ordered_unique, const_mem_fun > > > persistent_betting_market_group_multi_index_type; + +typedef generic_index persistent_betting_market_group_index; + +//////////// betting_market_object ////////////////// +class persistent_betting_market_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = bookie_objects; + static const uint8_t type_id = persistent_betting_market_object_type; + + betting_market_object ephemeral_betting_market_object; + + share_type total_matched_bets_amount; + + betting_market_id_type get_betting_market_id() const { return ephemeral_betting_market_object.id; } +}; + +struct by_betting_market_id; +typedef multi_index_container< + persistent_betting_market_object, + indexed_by< + ordered_unique, member >, + ordered_unique, const_mem_fun > > > persistent_betting_market_multi_index_type; + +typedef generic_index persistent_betting_market_index; + +//////////// bet_object ////////////////// +class persistent_bet_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = bookie_objects; + static const uint8_t type_id = persistent_bet_object_type; + + bet_object ephemeral_bet_object; + + bet_id_type get_bet_id() const { return ephemeral_bet_object.id; } +}; + +struct by_bet_id; +typedef multi_index_container< + persistent_bet_object, + indexed_by< + ordered_unique, member >, + ordered_unique, const_mem_fun > > > persistent_bet_multi_index_type; + + +typedef generic_index persistent_bet_index; + +/* As a plugin, we get notified of new/changed objects at the end of every block processed. + * For most objects, that's fine, because we expect them to always be around until the end of + * the block. However, with bet objects, it's possible that the user places a bet and it fills + * and is removed during the same block, so need another strategy to detect them immediately after + * they are created. + * We do this by creating a secondary index on bet_object. We don't actually use it + * to index any property of the bet, we just use it to register for callbacks. + */ +class persistent_bet_object_helper : public secondary_index +{ + public: + virtual ~persistent_bet_object_helper() {} + + virtual void object_inserted(const object& obj) override; + //virtual void object_removed( const object& obj ) override; + //virtual void about_to_modify( const object& before ) override; + virtual void object_modified(const object& after) override; + void set_plugin_instance(bookie_plugin* instance) { _bookie_plugin = instance; } + private: + bookie_plugin* _bookie_plugin; +}; + +void persistent_bet_object_helper::object_inserted(const object& obj) +{ + const bet_object& bet_obj = *boost::polymorphic_downcast(&obj); + _bookie_plugin->database().create([&](persistent_bet_object& saved_bet_obj) { + saved_bet_obj.ephemeral_bet_object = bet_obj; + }); +} +void persistent_bet_object_helper::object_modified(const object& after) +{ + database& db = _bookie_plugin->database(); + auto& persistent_bets_by_bet_id = db.get_index_type().indices().get(); + const bet_object& bet_obj = *boost::polymorphic_downcast(&after); + auto iter = persistent_bets_by_bet_id.find(bet_obj.id); + assert (iter != persistent_bets_by_bet_id.end()); + if (iter != persistent_bets_by_bet_id.end()) + db.modify(*iter, [&](persistent_bet_object& saved_bet_obj) { + saved_bet_obj.ephemeral_bet_object = bet_obj; + }); +} + +//////////// end bet_object /////////////////// class bookie_plugin_impl { public: @@ -175,13 +286,14 @@ class bookie_plugin_impl bookie_plugin_impl::~bookie_plugin_impl() { - return; } void bookie_plugin_impl::on_objects_changed(const vector& changed_object_ids) { graphene::chain::database& db = database(); auto& event_id_index = db.get_index_type().indices().get(); + auto& betting_market_group_id_index = db.get_index_type().indices().get(); + auto& betting_market_id_index = db.get_index_type().indices().get(); for (const object_id_type& changed_object_id : changed_object_ids) { @@ -232,6 +344,78 @@ void bookie_plugin_impl::on_objects_changed(const vector& change }); } } + else if (changed_object_id.space() == betting_market_group_object::space_id && + changed_object_id.type() == betting_market_group_object::type_id) + { + betting_market_group_id_type changed_betting_market_group_id = changed_object_id; + const betting_market_group_object* new_betting_market_group_obj = nullptr; + try + { + new_betting_market_group_obj = &changed_betting_market_group_id(db); + } + catch (fc::exception& e) + { + } + // new_betting_market_group_obj should point to the now-changed event_object, or null if it was removed from the database + + const persistent_betting_market_group_object* old_betting_market_group_obj = nullptr; + + auto persistent_betting_market_group_iter = betting_market_group_id_index.find(changed_betting_market_group_id); + if (persistent_betting_market_group_iter != betting_market_group_id_index.end()) + old_betting_market_group_obj = &*persistent_betting_market_group_iter; + + // and old_betting_market_group_obj is a pointer to our saved copy, or nullptr if it is a new object + if (old_betting_market_group_obj && new_betting_market_group_obj) + { + ilog("Modifying persistent betting_market_group object ${id}", ("id", changed_betting_market_group_id)); + db.modify(*old_betting_market_group_obj, [&](persistent_betting_market_group_object& saved_betting_market_group_obj) { + saved_betting_market_group_obj.ephemeral_betting_market_group_object = *new_betting_market_group_obj; + }); + } + else if (new_betting_market_group_obj) + { + ilog("Creating new persistent betting_market_group object ${id}", ("id", changed_betting_market_group_id)); + db.create([&](persistent_betting_market_group_object& saved_betting_market_group_obj) { + saved_betting_market_group_obj.ephemeral_betting_market_group_object = *new_betting_market_group_obj; + }); + } + } + else if (changed_object_id.space() == betting_market_object::space_id && + changed_object_id.type() == betting_market_object::type_id) + { + betting_market_id_type changed_betting_market_id = changed_object_id; + const betting_market_object* new_betting_market_obj = nullptr; + try + { + new_betting_market_obj = &changed_betting_market_id(db); + } + catch (fc::exception& e) + { + } + // new_betting_market_obj should point to the now-changed event_object, or null if it was removed from the database + + const persistent_betting_market_object* old_betting_market_obj = nullptr; + + auto persistent_betting_market_iter = betting_market_id_index.find(changed_betting_market_id); + if (persistent_betting_market_iter != betting_market_id_index.end()) + old_betting_market_obj = &*persistent_betting_market_iter; + + // and old_betting_market_obj is a pointer to our saved copy, or nullptr if it is a new object + if (old_betting_market_obj && new_betting_market_obj) + { + ilog("Modifying persistent betting_market object ${id}", ("id", changed_betting_market_id)); + db.modify(*old_betting_market_obj, [&](persistent_betting_market_object& saved_betting_market_obj) { + saved_betting_market_obj.ephemeral_betting_market_object = *new_betting_market_obj; + }); + } + else if (new_betting_market_obj) + { + ilog("Creating new persistent betting_market object ${id}", ("id", changed_betting_market_id)); + db.create([&](persistent_betting_market_object& saved_betting_market_obj) { + saved_betting_market_obj.ephemeral_betting_market_object = *new_betting_market_obj; + }); + } + } } } @@ -239,29 +423,36 @@ void bookie_plugin_impl::on_block_applied( const signed_block& ) { graphene::chain::database& db = database(); const vector >& hist = db.get_applied_operations(); - for( const optional< operation_history_object >& o_op : hist ) + for( const optional& o_op : hist ) { if( !o_op.valid() ) { continue; } const operation_history_object& op = *o_op; - if( op.op.which() == operation::tag< bet_matched_operation >::value ) + if( op.op.which() == operation::tag::value ) { const bet_matched_operation& bet_matched_op = op.op.get(); idump((bet_matched_op)); const asset& amount_bet = bet_matched_op.amount_bet; // object may no longer exist //const bet_object& bet = bet_matched_op.bet_id(db); - const betting_market_object& betting_market = bet_matched_op.betting_market_id(db); - const betting_market_group_object& betting_market_group = betting_market.group_id(db); - db.modify( betting_market_group, [&]( betting_market_group_object& obj ){ - obj.total_matched_bets_amount += amount_bet.amount; - }); + auto& persistent_bets_by_bet_id = db.get_index_type().indices().get(); + auto bet_iter = persistent_bets_by_bet_id.find(bet_matched_op.bet_id); + assert(bet_iter != persistent_bets_by_bet_id.end()); + if (bet_iter != persistent_bets_by_bet_id.end()) + { + const bet_object& bet_obj = bet_iter->ephemeral_bet_object; + const betting_market_object& betting_market = bet_obj.betting_market_id(db); // TODO: this needs to look at the persistent version + const betting_market_group_object& betting_market_group = betting_market.group_id(db); // TODO: as does this + db.modify( betting_market_group, [&]( betting_market_group_object& obj ){ + obj.total_matched_bets_amount += amount_bet.amount; + }); + } } - else if( op.op.which() == operation::tag< event_create_operation >::value ) + else if( op.op.which() == operation::tag::value ) { - FC_ASSERT(op.result.which() == operation_result::tag< object_id_type >::value); + FC_ASSERT(op.result.which() == operation_result::tag::value); //object_id_type object_id = op.result.get(); event_id_type object_id = op.result.get(); FC_ASSERT( db.find_object(object_id), "invalid event specified" ); @@ -271,7 +462,7 @@ void bookie_plugin_impl::on_block_applied( const signed_block& ) localized_event_strings[pair.first].insert(event_string(object_id, pair.second)); } } - else if( op.op.which() == operation::tag< event_update_operation >::value ) + else if( op.op.which() == operation::tag::value ) { const event_update_operation& event_create_op = op.op.get(); if (!event_create_op.new_name.valid()) @@ -345,10 +536,8 @@ std::string bookie_plugin::plugin_name()const return "bookie"; } -void bookie_plugin::plugin_set_program_options( - boost::program_options::options_description& cli, - boost::program_options::options_description& cfg - ) +void bookie_plugin::plugin_set_program_options(boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) { //cli.add_options() // ("track-account", boost::program_options::value>()->composing()->multitoken(), "Account ID to track history for (may specify multiple times)") @@ -363,8 +552,13 @@ void bookie_plugin::plugin_initialize(const boost::program_options::variables_ma database().changed_objects.connect([&](const vector& changed_object_ids, const fc::flat_set& impacted_accounts){ my->on_objects_changed(changed_object_ids); }); //auto event_index = database().add_index >(); - //event_index->add_secondary_index(); - //LOAD_VALUE_SET(options, "tracked-accounts", my->_tracked_accounts, graphene::chain::account_id_type); + database().add_index >(); + database().add_index >(); + database().add_index >(); + const primary_index& bet_object_idx = database().get_index_type >(); + primary_index& nonconst_bet_object_idx = const_cast&>(bet_object_idx); + detail::persistent_bet_object_helper* persistent_bet_object_helper_index = nonconst_bet_object_idx.add_secondary_index(); + persistent_bet_object_helper_index->set_plugin_instance(this); ilog("bookie plugin: plugin_startup() end"); } @@ -392,5 +586,8 @@ void bookie_plugin::get_events_containing_sub_string(std::vector& } } } -FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_event_object, (graphene::db::object), (event_object_id)(name)(season)(start_time)(event_group_id)(status)(scores) ) +FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_event_object, (graphene::db::object), (event_object_id)(name)(season)(start_time)(event_group_id)(status)(scores) ) +FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_betting_market_group_object, (graphene::db::object), (ephemeral_betting_market_group_object)(total_matched_bets_amount) ) +FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_betting_market_object, (graphene::db::object), (ephemeral_betting_market_object) ) +FC_REFLECT_DERIVED( graphene::bookie::detail::persistent_bet_object, (graphene::db::object), (ephemeral_bet_object) ) diff --git a/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp b/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp index c5a0c36b..0bd25a2b 100644 --- a/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp @@ -47,13 +47,16 @@ enum spaces { enum bookie_object_type { persistent_event_object_type, + persistent_betting_market_group_object_type, + persistent_betting_market_object_type, + persistent_bet_object_type, BOOKIE_OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; namespace detail { - class bookie_plugin_impl; + class bookie_plugin_impl; } class bookie_plugin : public graphene::app::plugin @@ -63,9 +66,8 @@ class bookie_plugin : public graphene::app::plugin virtual ~bookie_plugin(); std::string plugin_name()const override; - virtual void plugin_set_program_options( - boost::program_options::options_description& cli, - boost::program_options::options_description& cfg) override; + virtual void plugin_set_program_options(boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; virtual void plugin_initialize(const boost::program_options::variables_map& options) override; virtual void plugin_startup() override; diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index fec081cb..f7d41f30 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1685,7 +1685,6 @@ class wallet_api bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, - share_type amount_reserved_for_fees, bool broadcast = false); signed_transaction propose_resolve_betting_market_group( diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 08459abc..2cd1ce69 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -5406,7 +5406,6 @@ signed_transaction wallet_api::place_bet( bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, - share_type amount_reserved_for_fees, bool broadcast /*= false*/) { FC_ASSERT( !is_locked() ); @@ -5417,7 +5416,6 @@ signed_transaction wallet_api::place_bet( bet_place_op.betting_market_id = betting_market_id; bet_place_op.amount_to_bet = amount_to_bet; bet_place_op.backer_multiplier = backer_multiplier; - bet_place_op.amount_reserved_for_fees = amount_reserved_for_fees; bet_place_op.back_or_lay = back_or_lay; signed_transaction tx; diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index 49053ceb..764be8f3 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -135,12 +135,12 @@ BOOST_AUTO_TEST_CASE(simple_bet_win) transfer(account_id_type(), bob_id, asset(10000)); // place bets at 10:1 - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION, 2); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION, 20); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); // reverse positions at 1:1 - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22); - place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); } FC_LOG_AND_RETHROW() } @@ -159,11 +159,11 @@ BOOST_AUTO_TEST_CASE(binned_order_books) transfer(account_id_type(), bob_id, asset(10000)); // place back bets at decimal odds of 1.55, 1.6, 1.65, 1.66, and 1.67 - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100); const auto& bet_odds_idx = db.get_index_type().indices().get(); @@ -187,7 +187,7 @@ BOOST_AUTO_TEST_CASE(binned_order_books) // compute the matching lay order share_type lay_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::back, false /* round down */); ilog("Alice is laying with ${lay_amount} at odds ${odds} to match the binned back amount ${back_amount}", ("lay_amount", lay_amount)("odds", binned_order.backer_multiplier)("back_amount", binned_order.amount_to_bet)); - place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(lay_amount, asset_id_type()), binned_order.backer_multiplier, 2); + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(lay_amount, asset_id_type()), binned_order.backer_multiplier); } bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)); @@ -201,11 +201,11 @@ BOOST_AUTO_TEST_CASE(binned_order_books) BOOST_CHECK(bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)) == bet_odds_idx.end()); // place lay bets at decimal odds of 1.55, 1.6, 1.65, 1.66, and 1.67 - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100, 2); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 166 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 167 * GRAPHENE_BETTING_ODDS_PRECISION / 100); binned_orders_point_one = bookie_api.get_binned_order_book(capitals_win_market.id, 1); idump((binned_orders_point_one)); @@ -219,7 +219,7 @@ BOOST_AUTO_TEST_CASE(binned_order_books) // compute the matching lay order share_type back_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::lay, false /* round down */); ilog("Alice is backing with ${back_amount} at odds ${odds} to match the binned lay amount ${lay_amount}", ("back_amount", back_amount)("odds", binned_order.backer_multiplier)("lay_amount", binned_order.amount_to_bet)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(back_amount, asset_id_type()), binned_order.backer_multiplier, 2); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(back_amount, asset_id_type()), binned_order.backer_multiplier); } bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)); @@ -247,12 +247,12 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) transfer(account_id_type(), bob_id, asset(10000000)); // have bob lay a bet for 1M (+20k fees) at 1:1 odds - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // have alice back a matching bet at 1:1 odds (also costing 1.02M) - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(moneyline_betting_markets.id, @@ -263,8 +263,8 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -281,25 +281,66 @@ BOOST_AUTO_TEST_CASE( cancel_unmatched_in_betting_group_test ) transfer(account_id_type(), bob_id, asset(10000000)); // have bob lay a bet for 1M (+20k fees) at 1:1 odds - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // have alice back a matching bet at 1:1 odds (also costing 1.02M) - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // place unmatched - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 10); - place_bet(bob_id, blackhawks_win_market.id, bet_type::lay, asset(600, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 20); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, blackhawks_win_market.id, bet_type::lay, asset(600, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 - 500 - 10); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000 - 600 - 20); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 500); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 600); // cancel unmatched cancel_unmatched_bets(moneyline_betting_markets.id); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE(inexact_odds) +{ + try + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + + transfer(account_id_type(), alice_id, asset(10000000)); + share_type alice_expected_balance = 10000000; + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value); + + // lay 47 at 1.94 odds (50:47) -- this is an exact amount, nothing surprising should happen here + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(47, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + alice_expected_balance -= 47; + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value); + + // lay 100 at 1.91 odds (100:91) -- this is an inexact match, we should get refunded 9 and leave a bet for 91 on the books + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 191 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + alice_expected_balance -= 91; + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value); + + + transfer(account_id_type(), bob_id, asset(10000000)); + share_type bob_expected_balance = 10000000; + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value); + + // now have bob match it with a back of 300 at 1.91 + // This should: + // match the full 47 @ 1.94 with 50 + // match the full 91 @ 1.91 with 100 + // leaving 150 + // back bets at 100:91 must be a multiple of 100, so refund 50 + // leaves a back bet of 100 @ 1.91 on the books + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(300, asset_id_type()), 191 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + bob_expected_balance -= 250; + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value); + } + FC_LOG_AND_RETHROW() +} + + BOOST_AUTO_TEST_CASE( chained_market_create_test ) { // Often you will want to create several objects that reference each other at the same time. @@ -430,12 +471,12 @@ struct simple_bet_test_fixture : database_fixture { transfer(account_id_type(), bob_id, asset(10000)); // place bets at 10:1 - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION, 2); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION, 20); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); // reverse positions at 1:1 - place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22); + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); capitals_win_betting_market_id = capitals_win_market.id; blackhawks_win_betting_market_id = blackhawks_win_market.id; @@ -459,13 +500,13 @@ BOOST_AUTO_TEST_CASE( win ) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value; //rake_value = (-100 + 1100 - 1100) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; - // alice starts with 10000, pays 100 (bet) + 2 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0 - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 1100 - 1100 - 22 + 0); + // alice starts with 10000, pays 100 (bet), wins 1100, then pays 1100 (bet), wins 0 + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 + 1100 - 1100 + 0); rake_value = (-1000 - 1100 + 2200) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; - // bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200 + // bob starts with 10000, pays 1000 (bet), wins 0, then pays 1100 (bet), wins 2200 BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 0 - 1100 - 22 + 2200 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 + 0 - 1100 + 2200 - rake_value); } FC_LOG_AND_RETHROW() } @@ -482,13 +523,13 @@ BOOST_AUTO_TEST_CASE( not_win ) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-100 - 1100 + 2200) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; - // alice starts with 10000, pays 100 (bet) + 2 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200 + // alice starts with 10000, pays 100 (bet), wins 0, then pays 1100 (bet), wins 2200 BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 0 - 1100 - 22 + 2200 - rake_value); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 + 0 - 1100 + 2200 - rake_value); //rake_value = (-1000 + 1100 - 1100) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; - // bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0 - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 1100 - 1100 - 22 + 0); + // bob starts with 10000, pays 1000 (bet), wins 1100, then pays 1100 (bet), wins 0 + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 + 1100 - 1100 + 0); } FC_LOG_AND_RETHROW() } @@ -523,21 +564,21 @@ struct simple_bet_test_fixture_2 : database_fixture { transfer(account_id_type(), bob_id, asset(10000)); // alice backs 1000 at 1:1, matches - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 20); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 20); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // now alice lays at 2500 at 1:1. This should require a deposit of 500, with the remaining 200 being funded from exposure - place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(2500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 50); + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(2500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // match the bet bit by bit. bob matches 500 of alice's 2500 bet. This effectively cancels half of bob's lay position // so he immediately gets 500 back. It reduces alice's back position, but doesn't return any money to her (all 2000 of her exposure // was already "promised" to her lay bet, so the 500 she would have received is placed in her refundable_unmatched_bets) - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 10); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); // match another 500, which will fully cancel bob's lay position and return the other 500 he had locked up in his position. // alice's back position is now canceled, 1500 remains of her unmatched lay bet, and the 500 from canceling her position has // been moved to her refundable_unmatched_bets - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 10); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(500, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); capitals_win_betting_market_id = capitals_win_market.id; } @@ -554,9 +595,9 @@ BOOST_AUTO_TEST_CASE(sport_update_test) update_sport(ice_hockey.id, {{"en", "Hockey on Ice"}, {"zh_Hans", "冰"}, {"ja", "アイスホッケ"}}); transfer(account_id_type(), alice_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -571,7 +612,7 @@ BOOST_AUTO_TEST_CASE(event_group_update_test) transfer(account_id_type(), alice_id, asset(10000000)); transfer(account_id_type(), bob_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); const sport_object& ice_on_hockey = create_sport({{"en", "Hockey on Ice"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \ fc::optional sport_id = ice_on_hockey.id; @@ -582,10 +623,10 @@ BOOST_AUTO_TEST_CASE(event_group_update_test) update_event_group(nhl.id, sport_id, fc::optional()); update_event_group(nhl.id, sport_id, name); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(moneyline_betting_markets.id, @@ -596,8 +637,8 @@ BOOST_AUTO_TEST_CASE(event_group_update_test) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -613,7 +654,7 @@ BOOST_AUTO_TEST_CASE(event_update_test) transfer(account_id_type(), alice_id, asset(10000000)); transfer(account_id_type(), bob_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); fc::optional empty; fc::optional name = internationalized_string_type({{"en", "Washington Capitals vs. Chicago Blackhawks"}, {"zh_Hans", "華盛頓首都隊/芝加哥黑"}, {"ja", "ワシントン・キャピタルズ/シカゴ・ブラックホーク"}}); @@ -630,10 +671,10 @@ BOOST_AUTO_TEST_CASE(event_update_test) update_event(capitals_vs_blackhawks.id, event_group_id , empty, empty); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(moneyline_betting_markets.id, @@ -644,8 +685,8 @@ BOOST_AUTO_TEST_CASE(event_update_test) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -666,9 +707,9 @@ BOOST_AUTO_TEST_CASE(betting_market_rules_update_test) update_betting_market_rules(betting_market_rules.id, name, desc); transfer(account_id_type(), alice_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -681,7 +722,7 @@ BOOST_AUTO_TEST_CASE(betting_market_group_update_test) CREATE_ICE_HOCKEY_BETTING_MARKET(); transfer(account_id_type(), alice_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); fc::optional dempty; fc::optional empty_object_id; @@ -701,10 +742,10 @@ BOOST_AUTO_TEST_CASE(betting_market_group_update_test) update_betting_market_group(moneyline_betting_markets.id, new_desc, new_event, new_rule, freeze); transfer(account_id_type(), bob_id, asset(10000000)); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(moneyline_betting_markets.id, @@ -715,8 +756,8 @@ BOOST_AUTO_TEST_CASE(betting_market_group_update_test) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -729,7 +770,7 @@ BOOST_AUTO_TEST_CASE(betting_market_update_test) CREATE_ICE_HOCKEY_BETTING_MARKET(); transfer(account_id_type(), alice_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); const betting_market_group_object& new_moneyline_betting_markets = create_betting_market_group({{"en", "New Moneyline"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type()); \ fc::optional betting_market_group = new_moneyline_betting_markets.id; @@ -743,10 +784,10 @@ BOOST_AUTO_TEST_CASE(betting_market_update_test) update_betting_market(blackhawks_win_market.id, betting_market_group, fc::optional()); transfer(account_id_type(), bob_id, asset(10000000)); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(new_moneyline_betting_markets.id, @@ -757,8 +798,8 @@ BOOST_AUTO_TEST_CASE(betting_market_update_test) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -779,9 +820,9 @@ BOOST_AUTO_TEST_SUITE(other_betting_tests) { \ if (1) \ { \ - edump(("###")); \ + elog("###"); \ edump((e.to_detail_string())); \ - edump(("###")); \ + elog("###"); \ } \ FC_ASSERT(e.to_detail_string().find(reason) != \ std::string::npos, "expected error hasn't occured");\ @@ -802,7 +843,7 @@ BOOST_FIXTURE_TEST_CASE( another_event_group_update_test, database_fixture) transfer(account_id_type(), alice_id, asset(10000000)); transfer(account_id_type(), bob_id, asset(10000000)); - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); fc::optional name = internationalized_string_type({{"en", "IBM"}, {"zh_Hans", "國家冰球聯"}, {"ja", "ナショナルホッケーリー"}}); @@ -831,10 +872,10 @@ BOOST_FIXTURE_TEST_CASE( another_event_group_update_test, database_fixture) //GRAPHENE_REQUIRE_THROW(try_update_event_group(nhl.id, sport_id, fc::optional()), fc::exception); TRY_EXPECT_THROW(try_update_event_group(nhl.id, sport_id, fc::optional()), fc::exception, "invalid sport specified"); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // caps win resolve_betting_market_group(moneyline_betting_markets.id, @@ -845,8 +886,8 @@ BOOST_FIXTURE_TEST_CASE( another_event_group_update_test, database_fixture) uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 + 2000000 - rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); } FC_LOG_AND_RETHROW() } @@ -894,17 +935,17 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_sf_test ) BOOST_TEST_MESSAGE("cilic_wins_market " << fc::variant(cilic_wins_market.id).as()); BOOST_TEST_MESSAGE("querrey_wins_market " << fc::variant(querrey_wins_market.id).as()); - place_bet(alice_id, berdych_wins_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); - place_bet(bob_id, berdych_wins_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, berdych_wins_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, berdych_wins_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); - place_bet(alice_id, cilic_wins_market.id, bet_type::back, asset(100000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 100000 / 50 /* chain defaults to 2% fees */); - place_bet(bob_id, cilic_wins_market.id, bet_type::lay, asset(100000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 100000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, cilic_wins_market.id, bet_type::back, asset(100000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, cilic_wins_market.id, bet_type::lay, asset(100000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 100000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 100000); // federer wins resolve_betting_market_group(moneyline_berdych_vs_federer.id, @@ -914,8 +955,8 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_sf_test ) uint32_t bob_rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Bob's rake value " + std::to_string(bob_rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000 + 2000000 - bob_rake_value); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 100000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 100000 + 2000000 - bob_rake_value); // cilic wins resolve_betting_market_group(moneyline_cilic_vs_querrey.id, @@ -925,8 +966,8 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_sf_test ) uint32_t alice_rake_value = (-100000 + 200000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Alice rake value " + std::to_string(alice_rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000 + 200000 - alice_rake_value); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000 - 100000 - 2000 + 2000000 - bob_rake_value); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 100000 + 200000 - alice_rake_value); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 100000 + 2000000 - bob_rake_value); } FC_LOG_AND_RETHROW() } @@ -948,8 +989,8 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_final_test ) BOOST_TEST_MESSAGE("federer_wins_final_market " << fc::variant(federer_wins_final_market.id).as()); BOOST_TEST_MESSAGE("cilic_wins_final_market " << fc::variant(cilic_wins_final_market.id).as()); - place_bet(alice_id, cilic_wins_final_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); - place_bet(bob_id, cilic_wins_final_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + place_bet(alice_id, cilic_wins_final_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, cilic_wins_final_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); betting_market_group_id_type moneyline_cilic_vs_federer_id = moneyline_cilic_vs_federer.id; auto cilic_wins_final_market_id = cilic_wins_final_market.id; @@ -965,8 +1006,8 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_final_test ) const betting_market_group_object& betting_market_group = moneyline_cilic_vs_federer_id(db); BOOST_CHECK_EQUAL(betting_market_group.total_matched_bets_amount.value, 2000000); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000); // federer wins resolve_betting_market_group(moneyline_cilic_vs_federer_id, @@ -976,8 +1017,8 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_final_test ) uint32_t bob_rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; BOOST_TEST_MESSAGE("Bob's rake value " + std::to_string(bob_rake_value)); - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - bob_rake_value); + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 + 2000000 - bob_rake_value); } FC_LOG_AND_RETHROW() } diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index f6881f36..7ac43736 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -225,7 +225,6 @@ void database_fixture::verify_asset_supplies( const database& db ) for (const bet_object& o : db.get_index_type().indices()) { total_balances[o.amount_to_bet.asset_id] += o.amount_to_bet.amount; - total_balances[o.amount_to_bet.asset_id] += o.amount_reserved_for_fees; } for (const betting_market_position_object& o : db.get_index_type().indices()) { @@ -1394,14 +1393,13 @@ void database_fixture::update_betting_market(betting_market_id_type betting_mark } FC_CAPTURE_AND_RETHROW( (betting_market_id) (group_id) (payout_condition) ) } - void database_fixture::place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees) + void database_fixture::place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier) { try { bet_place_operation bet_place_op; bet_place_op.bettor_id = bettor_id; bet_place_op.betting_market_id = betting_market_id; bet_place_op.amount_to_bet = amount_to_bet; bet_place_op.backer_multiplier = backer_multiplier; - bet_place_op.amount_reserved_for_fees = amount_reserved_for_fees; bet_place_op.back_or_lay = back_or_lay; trx.operations.push_back(bet_place_op); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index a8241a96..aedb2dab 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -318,7 +318,7 @@ struct database_fixture { /*fc::optional description,*/ fc::optional payout_condition); - void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees); + void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier); void resolve_betting_market_group(betting_market_group_id_type betting_market_group_id, std::map resolutions); void cancel_unmatched_bets(betting_market_group_id_type betting_market_group_id);