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.
This commit is contained in:
Eric Frias 2017-08-09 17:17:51 -04:00
parent 26c2eb4c7a
commit d13783a3c4
7 changed files with 300 additions and 201 deletions

View file

@ -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",

View file

@ -15,6 +15,7 @@
#include <graphene/bookie/bookie_api.hpp>
#include <graphene/bookie/bookie_plugin.hpp>
#include <graphene/bookie/bookie_objects.hpp>
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<graphene::bookie::bookie_plugin> 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<event_object>& events, const std::string& sub_string, const std::string& language);
std::vector<event_object> get_events_containing_sub_string(const std::string& sub_string, const std::string& language);
fc::variants get_objects(const vector<object_id_type>& 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<object_id_type>& ids) const
{
std::shared_ptr<graphene::chain::database> 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<detail::persistent_bet_index>().indices().get<by_bet_id>();
auto iter = persistent_bets_by_bet_id.find(id.as<bet_id_type>());
if (iter != persistent_bets_by_bet_id.end())
return iter->ephemeral_bet_object.to_variant();
}
return {};
});
return result;
}
std::shared_ptr<graphene::bookie::bookie_plugin> bookie_api_impl::get_plugin()
{
return app.get_plugin<graphene::bookie::bookie_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<event_object>& events, const std::string& sub_string, const std::string& language)
std::vector<event_object> 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<event_object> bookie_api::get_events_containing_sub_string(const std::string& sub_string, const std::string& language)
{
std::vector<event_object> 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<object_id_type>& ids) const
{
return my->get_objects(ids);
}
} } // graphene::bookie

View file

@ -23,16 +23,15 @@
*/
#include <graphene/bookie/bookie_plugin.hpp>
#include <graphene/bookie/bookie_objects.hpp>
#include <graphene/app/impacted.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/config.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/transaction_evaluation_state.hpp>
@ -54,144 +53,6 @@ namespace graphene { namespace bookie {
namespace detail
{
class persistent_event_object : public graphene::db::abstract_object<persistent_event_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<time_point_sec> start_time;
event_group_id_type event_group_id;
event_status status;
vector<string> scores;
};
typedef object_id<bookie_objects, persistent_event_object_type, persistent_event_object> persistent_event_id_type;
struct by_event_id;
typedef multi_index_container<
persistent_event_object,
indexed_by<
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_event_id>, member<persistent_event_object, event_id_type, &persistent_event_object::event_object_id> > > > persistent_event_object_multi_index_type;
typedef generic_index<persistent_event_object, persistent_event_object_multi_index_type> 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_id_type, set<persistent_event_id_type> > competitor_to_events;
};
void events_by_competitor_index::object_inserted( const object& obj )
{
const persistent_event_object& event_obj = *boost::polymorphic_downcast<const persistent_event_object*>(&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<const persistent_event_object*>(&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<persistent_betting_market_group_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_betting_market_group_id>, const_mem_fun<persistent_betting_market_group_object, betting_market_group_id_type, &persistent_betting_market_group_object::get_betting_market_group_id> > > > persistent_betting_market_group_multi_index_type;
typedef generic_index<persistent_betting_market_group_object, persistent_betting_market_group_multi_index_type> persistent_betting_market_group_index;
//////////// betting_market_object //////////////////
class persistent_betting_market_object : public graphene::db::abstract_object<persistent_betting_market_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_betting_market_id>, const_mem_fun<persistent_betting_market_object, betting_market_id_type, &persistent_betting_market_object::get_betting_market_id> > > > persistent_betting_market_multi_index_type;
typedef generic_index<persistent_betting_market_object, persistent_betting_market_multi_index_type> persistent_betting_market_index;
//////////// bet_object //////////////////
class persistent_bet_object : public graphene::db::abstract_object<persistent_bet_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_bet_id>, const_mem_fun<persistent_bet_object, bet_id_type, &persistent_bet_object::get_bet_id> > > > persistent_bet_multi_index_type;
typedef generic_index<persistent_bet_object, persistent_bet_multi_index_type> 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<event_object>& events, const std::string& sub_string, const std::string& language);
std::vector<event_object> 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<event_object>& events, const std::string& sub_string, const std::string& language)
std::vector<event_object> 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<event_object> 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<event_object>& events, const std::string& sub_string, const std::string& language)
std::vector<event_object> 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) )

View file

@ -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<event_object> get_events_containing_sub_string(const std::string& sub_string, const std::string& language);
fc::variants get_objects(const vector<object_id_type>& ids)const;
std::shared_ptr<detail::bookie_api_impl> 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)
)

View file

@ -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 <graphene/chain/database.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/event_object.hpp>
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<persistent_event_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<time_point_sec> start_time;
event_group_id_type event_group_id;
event_status status;
vector<string> scores;
};
typedef object_id<bookie_objects, persistent_event_object_type, persistent_event_object> persistent_event_id_type;
struct by_event_id;
typedef multi_index_container<
persistent_event_object,
indexed_by<
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_event_id>, member<persistent_event_object, event_id_type, &persistent_event_object::event_object_id> > > > persistent_event_object_multi_index_type;
typedef generic_index<persistent_event_object, persistent_event_object_multi_index_type> 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_id_type, set<persistent_event_id_type> > competitor_to_events;
};
void events_by_competitor_index::object_inserted( const object& obj )
{
const persistent_event_object& event_obj = *boost::polymorphic_downcast<const persistent_event_object*>(&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<const persistent_event_object*>(&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<persistent_betting_market_group_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_betting_market_group_id>, const_mem_fun<persistent_betting_market_group_object, betting_market_group_id_type, &persistent_betting_market_group_object::get_betting_market_group_id> > > > persistent_betting_market_group_multi_index_type;
typedef generic_index<persistent_betting_market_group_object, persistent_betting_market_group_multi_index_type> persistent_betting_market_group_index;
//////////// betting_market_object //////////////////
class persistent_betting_market_object : public graphene::db::abstract_object<persistent_betting_market_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_betting_market_id>, const_mem_fun<persistent_betting_market_object, betting_market_id_type, &persistent_betting_market_object::get_betting_market_id> > > > persistent_betting_market_multi_index_type;
typedef generic_index<persistent_betting_market_object, persistent_betting_market_multi_index_type> persistent_betting_market_index;
//////////// bet_object //////////////////
class persistent_bet_object : public graphene::db::abstract_object<persistent_bet_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<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_unique<tag<by_bet_id>, const_mem_fun<persistent_bet_object, bet_id_type, &persistent_bet_object::get_bet_id> > > > persistent_bet_multi_index_type;
typedef generic_index<persistent_bet_object, persistent_bet_multi_index_type> 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) )

View file

@ -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<account_id_type> 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<event_object>& events, const std::string& sub_string, const std::string& language);
std::vector<event_object> get_events_containing_sub_string(const std::string& sub_string, const std::string& language);
friend class detail::bookie_plugin_impl;
std::unique_ptr<detail::bookie_plugin_impl> my;

View file

@ -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<bet_id_type>() == 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<bet_id_type>() == 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<bet_id_type>() == first_bet_on_books, "Bookie Plugin didn't return a bet that has been filled");
BOOST_CHECK_MESSAGE(objects_from_bookie[1]["id"].as<bet_id_type>() == matching_bet, "Bookie Plugin didn't return a bet that has been filled");
}
FC_LOG_AND_RETHROW()
}