diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 00f45b4a..aa6d9b5c 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -13,7 +13,7 @@ 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 + adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake + fees //TODO: do special fee accounting as required if (create_virtual_op) push_applied_operation(bet_canceled_operation(bet.bettor_id, bet.id, @@ -157,7 +157,10 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett } FC_CAPTURE_AND_RETHROW((bettor_id)(betting_market_id)(bet_amount)) } -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) +// 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) { // calculate the percentage fee paid fc::uint128_t percentage_fee_128 = bet.amount_reserved_for_fees.value; @@ -191,6 +194,8 @@ bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet, 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; @@ -202,10 +207,10 @@ bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet, * * @return a bit field indicating which orders were filled (and thus removed) * - * 0 - no orders were matched - * 1 - bid was filled - * 2 - ask was filled - * 3 - both were filled + * 0 - no bet was matched (this will never happen) + * 1 - taker_bet was filled and removed from the books + * 2 - maker_bet was filled and removed from the books + * 3 - both were filled and removed from the books */ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker_bet ) { @@ -244,11 +249,6 @@ bool database::place_bet(const bet_object& new_bet_object) const auto& bet_odds_idx = get_index_type().indices().get(); - // TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to - // determine whether or not this order has "changed the book" in a way that requires us to - // check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs - // constant time check. Potential optimization. - 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)); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 9fa63545..32918962 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -37,6 +37,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -1067,6 +1072,123 @@ vector< operation_history_object > database_fixture::get_operation_history( acco return result; } +void database_fixture::process_operation_by_witnesses(operation op) +{ + const flat_set& active_witnesses = db.get_global_properties().active_witnesses; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = (*active_witnesses.begin())(db).witness_account; + proposal_op.proposed_ops.emplace_back(op); + proposal_op.expiration_time = db.head_block_time() + fc::days(1); + + signed_transaction tx; + tx.operations.push_back(proposal_op); + set_expiration(db, tx); + sign(tx, init_account_priv_key); + + processed_transaction processed_tx = db.push_transaction(tx); + proposal_id_type proposal_id = processed_tx.operation_results[0].get(); + + for (const witness_id_type& witness_id : active_witnesses) + { + const witness_object& witness = witness_id(db); + const account_object& witness_account = witness.witness_account(db); + + proposal_update_operation pup; + pup.proposal = proposal_id; + pup.fee_paying_account = witness_account.id; + pup.active_approvals_to_add.insert(witness_account.id); + + signed_transaction tx; + tx.operations.push_back( pup ); + set_expiration( db, tx ); + sign(tx, init_account_priv_key); + + db.push_transaction(tx, ~0); + + const auto& proposal_idx = db.get_index_type().indices().get(); + if (proposal_idx.find(proposal_id) == proposal_idx.end()) + break; + } +} + +const sport_object& database_fixture::create_sport(internationalized_string_type name) +{ try { + sport_create_operation sport_create_op; + sport_create_op.name = name; + process_operation_by_witnesses(sport_create_op); + const auto& sport_index = db.get_index_type().indices().get(); + return *sport_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (name) ) } + +const competitor_object& database_fixture::create_competitor(internationalized_string_type name, sport_id_type sport_id) +{ try { + competitor_create_operation competitor_create_op; + competitor_create_op.name = name; + competitor_create_op.sport_id = sport_id; + process_operation_by_witnesses(competitor_create_op); + const auto& competitor_index = db.get_index_type().indices().get(); + return *competitor_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (name) ) } + +const event_group_object& database_fixture::create_event_group(internationalized_string_type name, sport_id_type sport_id) +{ try { + event_group_create_operation event_group_create_op; + event_group_create_op.name = name; + event_group_create_op.sport_id = sport_id; + process_operation_by_witnesses(event_group_create_op); + const auto& event_group_index = db.get_index_type().indices().get(); + return *event_group_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (name) ) } + +const event_object& database_fixture::create_event(internationalized_string_type season, event_group_id_type event_group_id, vector competitors) +{ try { + event_create_operation event_create_op; + event_create_op.season = season; + event_create_op.event_group_id = event_group_id; + event_create_op.competitors.assign(competitors.begin(), competitors.end()); + process_operation_by_witnesses(event_create_op); + const auto& event_index = db.get_index_type().indices().get(); + return *event_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (event_group_id) ) } + +const betting_market_group_object& database_fixture::create_betting_market_group(event_id_type event_id, betting_market_options_type options) +{ try { + betting_market_group_create_operation betting_market_group_create_op; + betting_market_group_create_op.event_id = event_id; + betting_market_group_create_op.options = options; + process_operation_by_witnesses(betting_market_group_create_op); + const auto& betting_market_group_index = db.get_index_type().indices().get(); + return *betting_market_group_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (event_id) ) } + +const betting_market_object& database_fixture::create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id) +{ try { + betting_market_create_operation betting_market_create_op; + betting_market_create_op.group_id = group_id; + betting_market_create_op.payout_condition = payout_condition; + betting_market_create_op.asset_id = asset_id; + process_operation_by_witnesses(betting_market_create_op); + const auto& betting_market_index = db.get_index_type().indices().get(); + return *betting_market_index.rbegin(); +} FC_CAPTURE_AND_RETHROW( (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) +{ 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); + trx.validate(); + processed_transaction ptx = db.push_transaction(trx, ~0); + trx.operations.clear(); +} FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) } + namespace test { void set_expiration( const database& db, transaction& tx ) @@ -1074,7 +1196,6 @@ void set_expiration( const database& db, transaction& tx ) const chain_parameters& params = db.get_global_properties().parameters; tx.set_reference_block(db.head_block_id()); tx.set_expiration( db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 ) ); - return; } bool _push_block( database& db, const signed_block& b, uint32_t skip_flags /* = 0 */ ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index ec5e9bd7..550d20e5 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -281,6 +281,15 @@ struct database_fixture { int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; vector< operation_history_object > get_operation_history( account_id_type account_id )const; + void process_operation_by_witnesses(operation op); + const sport_object& create_sport(internationalized_string_type name); + const competitor_object& create_competitor(internationalized_string_type name, sport_id_type sport_id); + const event_group_object& create_event_group(internationalized_string_type name, sport_id_type sport_id); + const event_object& create_event(internationalized_string_type season, event_group_id_type event_group_id, vector competitors); + const betting_market_group_object& create_betting_market_group(event_id_type event_id, betting_market_options_type options); + const betting_market_object& create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id); + + 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); }; namespace test { diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index ee6aee94..23ed94bd 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include #include #include @@ -1624,7 +1626,52 @@ BOOST_AUTO_TEST_CASE( buyback ) } FC_LOG_AND_RETHROW() } + +#define CREATE_ICE_HOCKEY_BETTING_MARKET() \ + const sport_object& ice_hockey = create_sport({{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \ + const competitor_object& capitals = create_competitor({{"en", "Washington Capitals"}, {"zh_Hans", "華盛頓首都隊"}, {"ja", "ワシントン・キャピタルズ"}}, ice_hockey.id); \ + const competitor_object& blackhawks = create_competitor({{"en", "Chicago Blackhawks"}, {"zh_Hans", "芝加哥黑鷹"}, {"ja", "シカゴ・ブラックホークス"}}, ice_hockey.id); \ + const event_group_object& nhl = create_event_group({{"en", "NHL"}, {"zh_Hans", "國家冰球聯盟"}, {"ja", "ナショナルホッケーリーグ"}}, ice_hockey.id); \ + const event_object& capitals_vs_blackhawks = create_event({{"en", "2016-17"}}, nhl.id, {capitals.id, blackhawks.id}); \ + const betting_market_group_object& moneyline_betting_markets = create_betting_market_group(capitals_vs_blackhawks.id, moneyline_market_options{}); \ + const betting_market_object& capitals_win_market = create_betting_market(moneyline_betting_markets.id, {{"en", "Washington Capitals win"}}, asset_id_type()); \ + const betting_market_object& blackhawks_win_market = create_betting_market(moneyline_betting_markets.id, {{"en", "Chicago Blackhawks win"}}, asset_id_type()); + + BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) +{ + try + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + + // give alice and bob 10M each + transfer(account_id_type(), alice_id, asset(10000000)); + 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 */); + // 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 */); + + 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); + + // caps win + { + betting_market_resolve_operation betting_market_resolve_op; + betting_market_resolve_op.betting_market_id = capitals_win_market.id; + betting_market_resolve_op.resolution = betting_market_resolution_type::win; + process_operation_by_witnesses(betting_market_resolve_op); + } + + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( chained_market_create_test ) { ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) ); @@ -1801,96 +1848,6 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) } } - // give alice and bob 10M each - transfer(account_id_type(), alice_id, asset(10000000)); - transfer(account_id_type(), bob_id, asset(10000000)); - { - // get a betting market to run tests in. It doesn't relly matter what it is, but in this test it will be "caps win". - const betting_market_object& market = *db.get_index_type().indices().begin(); - - { - // have bob lay a bet for 1M (+20k fees) at 1:1 odds - signed_transaction tx; - bet_place_operation bet_op; - bet_op.bettor_id = bob_id; - bet_op.betting_market_id = market.id; - bet_op.amount_to_bet = asset(1000000, asset_id_type()); - bet_op.backer_multiplier = 2 * GRAPHENE_BETTING_ODDS_PRECISION; - bet_op.amount_reserved_for_fees = 1000000 / 50; // chain defaults to 2% fees - bet_op.back_or_lay = bet_type::lay; - tx.operations.push_back(bet_op); - db.current_fee_schedule().set_fee(tx.operations.back()); - set_expiration(db, tx); - sign(tx, bob_private_key); - db.push_transaction(tx); - } - - { - // have alice back a matching bet at 1:1 odds (also costing 1.02M) - signed_transaction tx; - bet_place_operation bet_op; - bet_op.bettor_id = alice_id; - bet_op.betting_market_id = market.id; - bet_op.amount_to_bet = asset(1000000, asset_id_type()); - bet_op.backer_multiplier = 2 * GRAPHENE_BETTING_ODDS_PRECISION; - bet_op.amount_reserved_for_fees = 1000000 / 50; // chain defaults to 2% fees - bet_op.back_or_lay = bet_type::back; - tx.operations.push_back(bet_op); - db.current_fee_schedule().set_fee(tx.operations.back()); - set_expiration(db, tx); - sign(tx, alice_private_key); - db.push_transaction(tx); - } - - 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); - // caps win - { - proposal_create_operation proposal_op; - proposal_op.fee_paying_account = (*active_witnesses.begin())(db).witness_account; - betting_market_resolve_operation betting_market_resolve_op; - betting_market_resolve_op.betting_market_id = market.id; - betting_market_resolve_op.resolution = betting_market_resolution_type::win; - proposal_op.proposed_ops.emplace_back(betting_market_resolve_op); - proposal_op.expiration_time = db.head_block_time() + fc::days(1); - signed_transaction tx; - tx.operations.push_back(proposal_op); - set_expiration(db, tx); - sign(tx, init_account_priv_key); - - db.push_transaction(tx); - } - - BOOST_REQUIRE_EQUAL(db.get_index_type().indices().size(), 1); - { - const proposal_object& prop = *db.get_index_type().indices().begin(); - - for (const witness_id_type& witness_id : active_witnesses) - { - BOOST_TEST_MESSAGE("Approving market resolve witness " << fc::variant(witness_id).as()); - const witness_object& witness = witness_id(db); - const account_object& witness_account = witness.witness_account(db); - - proposal_update_operation pup; - pup.proposal = prop.id; - pup.fee_paying_account = witness_account.id; - pup.active_approvals_to_add.insert(witness_account.id); - - signed_transaction tx; - tx.operations.push_back( pup ); - set_expiration( db, tx ); - sign(tx, init_account_priv_key); - - db.push_transaction(tx, ~0); - if (db.get_index_type().indices().size() == 0) - break; - } - } - - BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000); - BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); - } - } } FC_LOG_AND_RETHROW()