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:
parent
26c2eb4c7a
commit
d13783a3c4
7 changed files with 300 additions and 201 deletions
|
|
@ -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));
|
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(op.amount_to_bet.amount > share_type(), "Cannot place a bet with zero amount");
|
||||||
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
|
|
||||||
|
|
||||||
// do they have enough in their account to place the bet
|
// 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",
|
FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= op.amount_to_bet.amount, "insufficient balance",
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
|
|
||||||
#include <graphene/bookie/bookie_api.hpp>
|
#include <graphene/bookie/bookie_api.hpp>
|
||||||
#include <graphene/bookie/bookie_plugin.hpp>
|
#include <graphene/bookie/bookie_plugin.hpp>
|
||||||
|
#include <graphene/bookie/bookie_objects.hpp>
|
||||||
|
|
||||||
namespace graphene { namespace bookie {
|
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);
|
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();
|
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);
|
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;
|
graphene::app::application& app;
|
||||||
};
|
};
|
||||||
|
|
@ -122,6 +124,27 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
|
||||||
return result;
|
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()
|
std::shared_ptr<graphene::bookie::bookie_plugin> bookie_api_impl::get_plugin()
|
||||||
{
|
{
|
||||||
return app.get_plugin<graphene::bookie::bookie_plugin>("bookie");
|
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);
|
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
|
} // 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> bookie_api::get_events_containing_sub_string(const std::string& sub_string, const std::string& language)
|
||||||
{
|
{
|
||||||
std::vector<event_object> events;
|
return my->get_events_containing_sub_string(sub_string, language);
|
||||||
my->get_events_containing_sub_string(events, sub_string, language);
|
|
||||||
return events;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fc::variants bookie_api::get_objects(const vector<object_id_type>& ids) const
|
||||||
|
{
|
||||||
|
return my->get_objects(ids);
|
||||||
|
}
|
||||||
|
|
||||||
} } // graphene::bookie
|
} } // graphene::bookie
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,16 +23,15 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <graphene/bookie/bookie_plugin.hpp>
|
#include <graphene/bookie/bookie_plugin.hpp>
|
||||||
|
#include <graphene/bookie/bookie_objects.hpp>
|
||||||
|
|
||||||
#include <graphene/app/impacted.hpp>
|
#include <graphene/app/impacted.hpp>
|
||||||
|
|
||||||
#include <graphene/chain/account_evaluator.hpp>
|
#include <graphene/chain/account_evaluator.hpp>
|
||||||
#include <graphene/chain/account_object.hpp>
|
#include <graphene/chain/account_object.hpp>
|
||||||
#include <graphene/chain/betting_market_object.hpp>
|
|
||||||
#include <graphene/chain/config.hpp>
|
#include <graphene/chain/config.hpp>
|
||||||
#include <graphene/chain/database.hpp>
|
#include <graphene/chain/database.hpp>
|
||||||
#include <graphene/chain/evaluator.hpp>
|
#include <graphene/chain/evaluator.hpp>
|
||||||
#include <graphene/chain/event_object.hpp>
|
|
||||||
#include <graphene/chain/operation_history_object.hpp>
|
#include <graphene/chain/operation_history_object.hpp>
|
||||||
#include <graphene/chain/transaction_evaluation_state.hpp>
|
#include <graphene/chain/transaction_evaluation_state.hpp>
|
||||||
|
|
||||||
|
|
@ -54,144 +53,6 @@ namespace graphene { namespace bookie {
|
||||||
|
|
||||||
namespace detail
|
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.
|
/* 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
|
* 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
|
* 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 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()
|
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();
|
graphene::chain::database& db = database();
|
||||||
if (localized_event_strings.find(language) != localized_event_strings.end())
|
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];
|
std::string lower_case_sub_string = boost::algorithm::to_lower_copy(sub_string);
|
||||||
for (const event_string& pair : language_set)
|
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)
|
std::string lower_case_string = boost::algorithm::to_lower_copy(pair.second);
|
||||||
events.push_back(pair.first(db));
|
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)
|
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();
|
graphene::chain::database& db = database();
|
||||||
FC_ASSERT( db.find_object(group_id), "Invalid betting market group specified" );
|
FC_ASSERT( db.find_object(group_id), "Invalid betting market group specified" );
|
||||||
const betting_market_group_object& betting_market_group = group_id(db);
|
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);
|
return asset(betting_market_group.total_matched_bets_amount, betting_market_group.asset_id);
|
||||||
}
|
}
|
||||||
} // end namespace detail
|
} // 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));
|
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);
|
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));
|
ilog("bookie plugin: get_events_containing_sub_string(${sub_string}, ${language})", (sub_string)(language));
|
||||||
my->get_events_containing_sub_string(events, 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) )
|
|
||||||
|
|
|
||||||
|
|
@ -39,12 +39,13 @@ class bookie_api
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the current order book, binned according to the given precision.
|
* 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]
|
* 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);
|
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);
|
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);
|
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;
|
std::shared_ptr<detail::bookie_api_impl> my;
|
||||||
};
|
};
|
||||||
|
|
@ -58,5 +59,6 @@ FC_API(graphene::bookie::bookie_api,
|
||||||
(get_binned_order_book)
|
(get_binned_order_book)
|
||||||
(get_total_matched_bet_amount_for_betting_market_group)
|
(get_total_matched_bet_amount_for_betting_market_group)
|
||||||
(get_events_containing_sub_string)
|
(get_events_containing_sub_string)
|
||||||
|
(get_objects)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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) )
|
||||||
|
|
||||||
|
|
@ -44,15 +44,6 @@ using namespace chain;
|
||||||
enum spaces {
|
enum spaces {
|
||||||
bookie_objects = 6
|
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
|
namespace detail
|
||||||
{
|
{
|
||||||
|
|
@ -73,7 +64,7 @@ class bookie_plugin : public graphene::app::plugin
|
||||||
|
|
||||||
flat_set<account_id_type> tracked_accounts()const;
|
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);
|
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;
|
friend class detail::bookie_plugin_impl;
|
||||||
std::unique_ptr<detail::bookie_plugin_impl> my;
|
std::unique_ptr<detail::bookie_plugin_impl> my;
|
||||||
|
|
|
||||||
|
|
@ -309,22 +309,27 @@ BOOST_AUTO_TEST_CASE(inexact_odds)
|
||||||
|
|
||||||
transfer(account_id_type(), alice_id, asset(10000000));
|
transfer(account_id_type(), alice_id, asset(10000000));
|
||||||
share_type alice_expected_balance = 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
|
// 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);
|
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;
|
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
|
// 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);
|
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;
|
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));
|
transfer(account_id_type(), bob_id, asset(10000000));
|
||||||
share_type bob_expected_balance = 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
|
// now have bob match it with a back of 300 at 1.91
|
||||||
// This should:
|
// This should:
|
||||||
|
|
@ -335,7 +340,50 @@ BOOST_AUTO_TEST_CASE(inexact_odds)
|
||||||
// leaves a back bet of 100 @ 1.91 on the books
|
// 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);
|
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;
|
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()
|
FC_LOG_AND_RETHROW()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue