From d13783a3c46c05793f618c9ae90291aa897d8239 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Wed, 9 Aug 2017 17:17:51 -0400 Subject: [PATCH] Fix bug that prevented placing bets that were too small to stay on the books, Add a get_objects() call to the bookie API to provide access to objects that have been removed from the blockchain, and a few tests to verify that they work. --- libraries/chain/betting_market_evaluator.cpp | 15 +- libraries/plugins/bookie/bookie_api.cpp | 37 +++- libraries/plugins/bookie/bookie_plugin.cpp | 189 +++--------------- .../include/graphene/bookie/bookie_api.hpp | 4 +- .../graphene/bookie/bookie_objects.hpp | 187 +++++++++++++++++ .../include/graphene/bookie/bookie_plugin.hpp | 11 +- tests/betting/betting_tests.cpp | 58 +++++- 7 files changed, 300 insertions(+), 201 deletions(-) create mode 100644 libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp diff --git a/libraries/chain/betting_market_evaluator.cpp b/libraries/chain/betting_market_evaluator.cpp index 19793969..1114ef33 100644 --- a/libraries/chain/betting_market_evaluator.cpp +++ b/libraries/chain/betting_market_evaluator.cpp @@ -281,20 +281,7 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op) FC_ASSERT(op.backer_multiplier % allowed_increment == 0, "Bet odds must be a multiple of ${allowed_increment}", ("allowed_increment", allowed_increment)); } - // is it possible to match this bet - FC_ASSERT(bet_object::get_exact_matching_amount(op.amount_to_bet.amount, op.backer_multiplier, op.back_or_lay) != 0, - "Bet cannot be matched"); - -#if 0 - bet_object simulated_bet; - simulated_bet.bettor_id = op.bettor_id; - 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.back_or_lay = op.back_or_lay; - - share_type required_deposit = get_required_deposit_for_bet(simulated_bet); -#endif + FC_ASSERT(op.amount_to_bet.amount > share_type(), "Cannot place a bet with zero amount"); // do they have enough in their account to place the bet FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= op.amount_to_bet.amount, "insufficient balance", diff --git a/libraries/plugins/bookie/bookie_api.cpp b/libraries/plugins/bookie/bookie_api.cpp index 1af4fc5b..f5c9f19f 100644 --- a/libraries/plugins/bookie/bookie_api.cpp +++ b/libraries/plugins/bookie/bookie_api.cpp @@ -15,6 +15,7 @@ #include #include +#include namespace graphene { namespace bookie { @@ -28,7 +29,8 @@ class bookie_api_impl binned_order_book get_binned_order_book(graphene::chain::betting_market_id_type betting_market_id, int32_t precision); std::shared_ptr get_plugin(); asset get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id); - void get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language); + std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); + fc::variants get_objects(const vector& ids) const; graphene::app::application& app; }; @@ -122,6 +124,27 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin return result; } +fc::variants bookie_api_impl::get_objects(const vector& ids) const +{ + std::shared_ptr db = app.chain_database(); + fc::variants result; + result.reserve(ids.size()); + + std::transform(ids.begin(), ids.end(), std::back_inserter(result), + [this, &db](object_id_type id) -> fc::variant { + if (id.type() == bet_id_type::type_id) + { + auto& persistent_bets_by_bet_id = db->get_index_type().indices().get(); + auto iter = persistent_bets_by_bet_id.find(id.as()); + if (iter != persistent_bets_by_bet_id.end()) + return iter->ephemeral_bet_object.to_variant(); + } + return {}; + }); + + return result; +} + std::shared_ptr bookie_api_impl::get_plugin() { return app.get_plugin("bookie"); @@ -132,9 +155,9 @@ asset bookie_api_impl::get_total_matched_bet_amount_for_betting_market_group(bet return get_plugin()->get_total_matched_bet_amount_for_betting_market_group(group_id); } -void bookie_api_impl::get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language) +std::vector bookie_api_impl::get_events_containing_sub_string(const std::string& sub_string, const std::string& language) { - get_plugin()->get_events_containing_sub_string(events, sub_string, language); + return get_plugin()->get_events_containing_sub_string(sub_string, language); } } // detail @@ -156,11 +179,13 @@ asset bookie_api::get_total_matched_bet_amount_for_betting_market_group(betting_ std::vector bookie_api::get_events_containing_sub_string(const std::string& sub_string, const std::string& language) { - std::vector events; - my->get_events_containing_sub_string(events, sub_string, language); - return events; + return my->get_events_containing_sub_string(sub_string, language); } +fc::variants bookie_api::get_objects(const vector& ids) const +{ + return my->get_objects(ids); +} } } // graphene::bookie diff --git a/libraries/plugins/bookie/bookie_plugin.cpp b/libraries/plugins/bookie/bookie_plugin.cpp index 681f18a2..681e2338 100644 --- a/libraries/plugins/bookie/bookie_plugin.cpp +++ b/libraries/plugins/bookie/bookie_plugin.cpp @@ -23,16 +23,15 @@ */ #include +#include #include #include #include -#include #include #include #include -#include #include #include @@ -54,144 +53,6 @@ namespace graphene { namespace bookie { namespace detail { - -class persistent_event_object : public graphene::db::abstract_object -{ - public: - static const uint8_t space_id = bookie_objects; - static const uint8_t type_id = persistent_event_object_type; - - event_id_type event_object_id; - - internationalized_string_type name; - - internationalized_string_type season; - - optional start_time; - - event_group_id_type event_group_id; - - event_status status; - vector scores; -}; -typedef object_id persistent_event_id_type; - -struct by_event_id; -typedef multi_index_container< - persistent_event_object, - indexed_by< - ordered_unique, member >, - ordered_unique, member > > > persistent_event_object_multi_index_type; - -typedef generic_index persistent_event_object_index; - -#if 0 // we no longer have competitors, just leaving this here as an example of how to do a secondary index -class events_by_competitor_index : public secondary_index -{ - public: - virtual ~events_by_competitor_index() {} - - 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; - protected: - - map > competitor_to_events; -}; - -void events_by_competitor_index::object_inserted( const object& obj ) -{ - const persistent_event_object& event_obj = *boost::polymorphic_downcast(&obj); - for (const competitor_id_type& competitor_id : event_obj.competitors) - competitor_to_events[competitor_id].insert(event_obj.id); - for (const competitor_id_type& competitor_id : event_obj.competitors) - competitor_to_events[competitor_id].insert(event_obj.id); -} -void events_by_competitor_index::object_removed( const object& obj ) -{ - const persistent_event_object& event_obj = *boost::polymorphic_downcast(&obj); - for (const competitor_id_type& competitor_id : event_obj.competitors) - competitor_to_events[competitor_id].erase(event_obj.id); -} -void events_by_competitor_index::about_to_modify( const object& before ) -{ - object_removed(before); -} -void events_by_competitor_index::object_modified( const object& after ) -{ - object_inserted(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 @@ -259,7 +120,7 @@ class bookie_plugin_impl void fill_localized_event_strings(); - void get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language); + std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); graphene::chain::database& database() { @@ -497,28 +358,30 @@ void bookie_plugin_impl::fill_localized_event_strings() } } -void bookie_plugin_impl::get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language) +std::vector bookie_plugin_impl::get_events_containing_sub_string(const std::string& sub_string, const std::string& language) { - graphene::chain::database& db = database(); - if (localized_event_strings.find(language) != localized_event_strings.end()) - { - std::string lower_case_sub_string = boost::algorithm::to_lower_copy(sub_string); - const event_string_set& language_set = localized_event_strings[language]; - for (const event_string& pair : language_set) - { - std::string lower_case_string = boost::algorithm::to_lower_copy(pair.second); - if (lower_case_string.find(lower_case_sub_string) != std::string::npos) - events.push_back(pair.first(db)); - } - } + graphene::chain::database& db = database(); + std::vector events; + if (localized_event_strings.find(language) != localized_event_strings.end()) + { + std::string lower_case_sub_string = boost::algorithm::to_lower_copy(sub_string); + const event_string_set& language_set = localized_event_strings[language]; + for (const event_string& pair : language_set) + { + std::string lower_case_string = boost::algorithm::to_lower_copy(pair.second); + if (lower_case_string.find(lower_case_sub_string) != std::string::npos) + events.push_back(pair.first(db)); + } + } + return events; } asset bookie_plugin_impl::get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id) { - graphene::chain::database& db = database(); - FC_ASSERT( db.find_object(group_id), "Invalid betting market group specified" ); - const betting_market_group_object& betting_market_group = group_id(db); - return asset(betting_market_group.total_matched_bets_amount, betting_market_group.asset_id); + graphene::chain::database& db = database(); + FC_ASSERT( db.find_object(group_id), "Invalid betting market group specified" ); + const betting_market_group_object& betting_market_group = group_id(db); + return asset(betting_market_group.total_matched_bets_amount, betting_market_group.asset_id); } } // end namespace detail @@ -579,15 +442,11 @@ asset bookie_plugin::get_total_matched_bet_amount_for_betting_market_group(betti ilog("bookie plugin: get_total_matched_bet_amount_for_betting_market_group($group_id)", ("group_d", group_id)); return my->get_total_matched_bet_amount_for_betting_market_group(group_id); } -void bookie_plugin::get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language) +std::vector bookie_plugin::get_events_containing_sub_string(const std::string& sub_string, const std::string& language) { - ilog("bookie plugin: get_events_containing_sub_string($s, $l)", ("s", sub_string)("l", language)); - my->get_events_containing_sub_string(events, sub_string, language); + ilog("bookie plugin: get_events_containing_sub_string(${sub_string}, ${language})", (sub_string)(language)); + return my->get_events_containing_sub_string(sub_string, language); } } } -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_api.hpp b/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp index 6aeb6068..de5e60b3 100644 --- a/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_api.hpp @@ -39,12 +39,13 @@ class bookie_api /** * Returns the current order book, binned according to the given precision. - * precision = 1 means bin using one decimal place: (1 - 1.1], (1.1 - 1.2], etc. + * precision = 1 means bin using one decimal place. for backs, (1 - 1.1], (1.1 - 1.2], etc. * precision = 2 would bin on (1 - 1.01], (1.01 - 1.02] */ binned_order_book get_binned_order_book(graphene::chain::betting_market_id_type betting_market_id, int32_t precision); asset get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id); std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); + fc::variants get_objects(const vector& ids)const; std::shared_ptr my; }; @@ -58,5 +59,6 @@ FC_API(graphene::bookie::bookie_api, (get_binned_order_book) (get_total_matched_bet_amount_for_betting_market_group) (get_events_containing_sub_string) + (get_objects) ) diff --git a/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp b/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp new file mode 100644 index 00000000..f465fdc0 --- /dev/null +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_objects.hpp @@ -0,0 +1,187 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include +#include + +namespace graphene { namespace bookie { +using namespace chain; + +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 persistent_event_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = bookie_objects; + static const uint8_t type_id = persistent_event_object_type; + + event_id_type event_object_id; + + internationalized_string_type name; + + internationalized_string_type season; + + optional start_time; + + event_group_id_type event_group_id; + + event_status status; + vector scores; +}; +typedef object_id persistent_event_id_type; + +struct by_event_id; +typedef multi_index_container< + persistent_event_object, + indexed_by< + ordered_unique, member >, + ordered_unique, member > > > persistent_event_object_multi_index_type; + +typedef generic_index persistent_event_object_index; + +#if 0 // we no longer have competitors, just leaving this here as an example of how to do a secondary index +class events_by_competitor_index : public secondary_index +{ + public: + virtual ~events_by_competitor_index() {} + + 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; + protected: + + map > competitor_to_events; +}; + +void events_by_competitor_index::object_inserted( const object& obj ) +{ + const persistent_event_object& event_obj = *boost::polymorphic_downcast(&obj); + for (const competitor_id_type& competitor_id : event_obj.competitors) + competitor_to_events[competitor_id].insert(event_obj.id); + for (const competitor_id_type& competitor_id : event_obj.competitors) + competitor_to_events[competitor_id].insert(event_obj.id); +} +void events_by_competitor_index::object_removed( const object& obj ) +{ + const persistent_event_object& event_obj = *boost::polymorphic_downcast(&obj); + for (const competitor_id_type& competitor_id : event_obj.competitors) + competitor_to_events[competitor_id].erase(event_obj.id); +} +void events_by_competitor_index::about_to_modify( const object& before ) +{ + object_removed(before); +} +void events_by_competitor_index::object_modified( const object& after ) +{ + object_inserted(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; + +} } } //graphene::bookie::detail + +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 0bd25a2b..7ea15450 100644 --- a/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp +++ b/libraries/plugins/bookie/include/graphene/bookie/bookie_plugin.hpp @@ -44,15 +44,6 @@ using namespace chain; enum spaces { bookie_objects = 6 }; -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 { @@ -73,7 +64,7 @@ class bookie_plugin : public graphene::app::plugin flat_set tracked_accounts()const; asset get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id); - void get_events_containing_sub_string(std::vector& events, const std::string& sub_string, const std::string& language); + std::vector get_events_containing_sub_string(const std::string& sub_string, const std::string& language); friend class detail::bookie_plugin_impl; std::unique_ptr my; diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index 6c92620c..ace12a87 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -309,22 +309,27 @@ BOOST_AUTO_TEST_CASE(inexact_odds) 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); + BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value); + + // lay 46 at 1.94 odds (50:47) -- this is too small to be placed on the books and there's + // nothing for it to match, so it should be canceled + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(46, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + BOOST_REQUIRE_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); + BOOST_REQUIRE_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); + BOOST_REQUIRE_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); + BOOST_REQUIRE_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: @@ -335,7 +340,50 @@ BOOST_AUTO_TEST_CASE(inexact_odds) // 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); + BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value); + } + FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE(persistent_objects_test) +{ + try + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + graphene::bookie::bookie_api bookie_api(app); + + transfer(account_id_type(), alice_id, asset(10000000)); + transfer(account_id_type(), bob_id, asset(10000000)); + share_type alice_expected_balance = 10000000; + share_type bob_expected_balance = 10000000; + BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value); + BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value); + + // lay 46 at 1.94 odds (50:47) -- this is too small to be placed on the books and there's + // nothing for it to match, so it should be canceled + bet_id_type automatically_canceled_bet_id = place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(46, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + BOOST_CHECK_MESSAGE(!db.find(automatically_canceled_bet_id), "Bet should have been canceled, but the blockchain still knows about it"); + fc::variants objects_from_bookie = bookie_api.get_objects({automatically_canceled_bet_id}); + BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1); + BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == automatically_canceled_bet_id, "Bookie Plugin didn't return a deleted bet it"); + + // lay 47 at 1.94 odds (50:47) -- this bet should go on the order books normally + bet_id_type first_bet_on_books = place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(47, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + BOOST_CHECK_MESSAGE(db.find(first_bet_on_books), "Bet should exist on the blockchain"); + objects_from_bookie = bookie_api.get_objects({first_bet_on_books}); + BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 1); + BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == first_bet_on_books, "Bookie Plugin didn't return a bet that is currently on the books"); + + // place a bet that exactly matches 'first_bet_on_books', should result in empty books (thus, no bet_objects from the blockchain) + bet_id_type matching_bet = place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(50, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100); + BOOST_CHECK_MESSAGE(!db.find(first_bet_on_books), "Bet should have been filled, but the blockchain still knows about it"); + BOOST_CHECK_MESSAGE(!db.find(matching_bet), "Bet should have been filled, but the blockchain still knows about it"); + + objects_from_bookie = bookie_api.get_objects({first_bet_on_books, matching_bet}); + BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 2); + BOOST_CHECK_MESSAGE(objects_from_bookie[0]["id"].as() == first_bet_on_books, "Bookie Plugin didn't return a bet that has been filled"); + BOOST_CHECK_MESSAGE(objects_from_bookie[1]["id"].as() == matching_bet, "Bookie Plugin didn't return a bet that has been filled"); } FC_LOG_AND_RETHROW() }