diff --git a/docs b/docs index 97435c1a..51c3fccd 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit 97435c1a622e41e0a5fc1be72aaadea62e1b7adb +Subproject commit 51c3fccda7cb39656d27b94c5274b2aa11c7b598 diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 47f9e659..b5b9b989 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -901,54 +901,25 @@ vector database_api::list_sports() const vector database_api_impl::list_sports() const { const auto& sport_object_idx = _db.get_index_type().indices().get(); - vector result; - for (const sport_object& sport : sport_object_idx) - { - result.emplace_back(sport); - } - return result; + return boost::copy_range >(sport_object_idx); } vector database_api_impl::list_event_groups(sport_id_type sport_id) const { - vector result; const auto& event_group_idx = _db.get_index_type().indices().get(); - auto event_group_itr = event_group_idx.lower_bound(sport_id); - while (event_group_itr != event_group_idx.end() && - event_group_itr->sport_id == sport_id) - { - result.emplace_back(*event_group_itr); - ++event_group_itr; - } - return result; + return boost::copy_range >(event_group_idx.equal_range(sport_id)); } vector database_api_impl::list_betting_market_groups(event_id_type event_id) const { - vector result; const auto& betting_market_group_idx = _db.get_index_type().indices().get(); - auto betting_market_group_itr = betting_market_group_idx.lower_bound(event_id); - while (betting_market_group_itr != betting_market_group_idx.end() && - betting_market_group_itr->event_id == event_id) - { - result.emplace_back(*betting_market_group_itr); - ++betting_market_group_itr; - } - return result; + return boost::copy_range >(betting_market_group_idx.equal_range(event_id)); } vector database_api_impl::list_betting_markets(betting_market_group_id_type betting_market_group_id) const { - vector result; const auto& betting_market_idx = _db.get_index_type().indices().get(); - auto betting_market_itr = betting_market_idx.lower_bound(betting_market_group_id); - while (betting_market_itr != betting_market_idx.end() && - betting_market_itr->group_id == betting_market_group_id) - { - result.emplace_back(*betting_market_itr); - ++betting_market_itr; - } - return result; + return boost::copy_range >(betting_market_idx.equal_range(betting_market_group_id)); } ////////////////////////////////////////////////////////////////////// diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index aa6d9b5c..971c8467 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -76,19 +76,22 @@ void database::resolve_betting_market(const betting_market_object& betting_marke case betting_market_resolution_type::win: payout_amount += position.pay_if_payout_condition; payout_amount += position.pay_if_not_canceled; + //TODO: pay the fees to the correct (dividend-distribution) account + adjust_balance(account_id_type(), asset(position.fees_collected, betting_market.asset_id)); break; case betting_market_resolution_type::not_win: payout_amount += position.pay_if_not_payout_condition; payout_amount += position.pay_if_not_canceled; + //TODO: pay the fees to the correct (dividend-distribution) account + adjust_balance(account_id_type(), asset(position.fees_collected, betting_market.asset_id)); break; case betting_market_resolution_type::cancel: payout_amount += position.pay_if_canceled; + payout_amount += position.fees_collected; break; } adjust_balance(position.bettor_id, asset(payout_amount, betting_market.asset_id)); - //TODO: pay the fees to the correct (dividend-distribution) account - adjust_balance(account_id_type(), asset(position.fees_collected, betting_market.asset_id)); push_applied_operation(betting_market_resolved_operation(position.bettor_id, betting_market.id, diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 32918962..61103c90 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1189,6 +1189,14 @@ const betting_market_object& database_fixture::create_betting_market(betting_mar trx.operations.clear(); } FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) } +void database_fixture::resolve_betting_market(betting_market_id_type betting_market_id, betting_market_resolution_type resolution) +{ try { + betting_market_resolve_operation betting_market_resolve_op; + betting_market_resolve_op.betting_market_id = betting_market_id; + betting_market_resolve_op.resolution = resolution; + process_operation_by_witnesses(betting_market_resolve_op); +} FC_CAPTURE_AND_RETHROW( (betting_market_id)(resolution) ) } + namespace test { void set_expiration( const database& db, transaction& tx ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 550d20e5..e7041eea 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -290,6 +290,7 @@ struct database_fixture { 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); + void resolve_betting_market(betting_market_id_type betting_market_id, betting_market_resolution_type resolution); }; namespace test { diff --git a/tests/tests/operation_tests2.cpp b/tests/tests/operation_tests2.cpp index 23ed94bd..cdd553cd 100644 --- a/tests/tests/operation_tests2.cpp +++ b/tests/tests/operation_tests2.cpp @@ -1638,6 +1638,29 @@ BOOST_AUTO_TEST_CASE( buyback ) 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( simple_bet_win ) +{ + try + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + + // give alice and bob 10k each + transfer(account_id_type(), alice_id, asset(10000)); + 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); + + // 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); + + } FC_LOG_AND_RETHROW() +} + + BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) { try @@ -1658,12 +1681,7 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) 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); - } + resolve_betting_market(capitals_win_market.id, betting_market_resolution_type::win); 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); @@ -1673,73 +1691,15 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) BOOST_AUTO_TEST_CASE( chained_market_create_test ) { - ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) ); - + // Often you will want to create several objects that reference each other at the same time. + // To facilitate this, many of the betting market operations allow you to use "relative" object ids, + // which let you can create, for example, an event in the 2nd operation in a transaction where the + // event group id is set to the id of an event group created in the 1st operation in a tranasction. try { { const flat_set& active_witnesses = db.get_global_properties().active_witnesses; - BOOST_TEST_MESSAGE("Propose the create_sport operation"); - { - sport_create_operation sport_create_op; - sport_create_op.name.insert(internationalized_string_type::value_type("en", "Football")); - sport_create_op.name.insert(internationalized_string_type::value_type("en_US", "Soccer")); - sport_create_op.name.insert(internationalized_string_type::value_type("zh_Hans", "足球")); - sport_create_op.name.insert(internationalized_string_type::value_type("ja", "サッカー")); - proposal_create_operation proposal_op; - proposal_op.fee_paying_account = (*active_witnesses.begin())(db).witness_account; - proposal_op.proposed_ops.emplace_back(sport_create_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_TEST_MESSAGE( "Witness account: " << fc::json::to_pretty_string(GRAPHENE_WITNESS_ACCOUNT(db))); - - BOOST_TEST_MESSAGE("There are now " << db.get_index_type().indices().size() << " proposals"); - const proposal_object& prop = *db.get_index_type().indices().begin(); - BOOST_TEST_MESSAGE("Just created sport creation proposal " << fc::variant(prop.id).as()); - - - BOOST_CHECK_EQUAL(prop.required_active_approvals.size(), 1); // should require GRAPHENE_WITNESS_ACCOUNT only - BOOST_CHECK_EQUAL(prop.required_owner_approvals.size(), 0); - BOOST_CHECK(!prop.is_authorized_to_execute(db)); - - for (const witness_id_type& witness_id : active_witnesses) - { - BOOST_TEST_MESSAGE("Approving sport creation from 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.key_approvals_to_add.insert(witness.signing_key); - 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) - { - BOOST_TEST_MESSAGE("The sport creation operation has been approved, new sport object on the blockchain is " << fc::json::to_pretty_string(*db.get_index_type().indices().begin())); - break; - } - } - } - - BOOST_REQUIRE_EQUAL(db.get_index_type().indices().size(), 1); - BOOST_TEST_MESSAGE("Creating a sport and competitors in the same proposal"); { // operation 0 in the transaction @@ -1838,7 +1798,7 @@ BOOST_AUTO_TEST_CASE( chained_market_create_test ) sign(tx, init_account_priv_key); db.push_transaction(tx, ~0); - if (db.get_index_type().indices().size() > 1) + if (db.get_index_type().indices().size() == 1) { BOOST_REQUIRE_EQUAL(db.get_index_type().indices().size(), 2); //BOOST_TEST_MESSAGE("The sport creation operation has been approved, new sport object on the blockchain is " << fc::json::to_pretty_string(*db.get_index_type().indices().rbegin())); @@ -1854,3 +1814,80 @@ BOOST_AUTO_TEST_CASE( chained_market_create_test ) } BOOST_AUTO_TEST_SUITE_END() + +// set up a fixture that places a series of two matched bets, we'll use this fixture to verify +// the result in all three possible outcomes +struct simple_bet_test_fixture : database_fixture { + betting_market_id_type capitals_win_betting_market_id; + simple_bet_test_fixture() + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + + // give alice and bob 10k each + transfer(account_id_type(), alice_id, asset(10000)); + 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); + + // 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); + + capitals_win_betting_market_id = capitals_win_market.id; + } +}; + +BOOST_FIXTURE_TEST_SUITE( simple_bet_tests, simple_bet_test_fixture ) + +BOOST_AUTO_TEST_CASE( win ) +{ + try + { + resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::win); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // 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); + // bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200 + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 0 - 1100 - 22 + 2200); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( not_win ) +{ + try + { + resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::not_win); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // alice starts with 10000, pays 100 (bet) + 2 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200 + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 0 - 1100 - 22 + 2200); + // 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); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( cancel ) +{ + try + { + resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::cancel); + + GET_ACTOR(alice); + GET_ACTOR(bob); + + // alice and bob both start with 10000, they should end with 10000 + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000); + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END() +