Finish delayed (live) betting.

Remove the ability to change the event of a betting market group after creation.
This commit is contained in:
Eric Frias 2017-08-15 18:44:09 -04:00
parent 08a8e11374
commit b505c375af
13 changed files with 376 additions and 164 deletions

View file

@ -52,27 +52,25 @@ object_id_type betting_market_rules_create_evaluator::do_apply(const betting_mar
void_result betting_market_rules_update_evaluator::do_evaluate(const betting_market_rules_update_operation& op)
{ try {
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_name.valid() || op.new_description.valid());
_rules = &op.betting_market_rules_id(db());
FC_ASSERT(op.new_name.valid() || op.new_description.valid(), "nothing to update");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_rules_update_evaluator::do_apply(const betting_market_rules_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.betting_market_rules_id),
[&]( betting_market_rules_object& bmro )
{
db().modify(*_rules, [&](betting_market_rules_object& betting_market_rules) {
if (op.new_name.valid())
bmro.name = *op.new_name;
betting_market_rules.name = *op.new_name;
if (op.new_description.valid())
bmro.description = *op.new_description;
betting_market_rules.description = *op.new_description;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_create_evaluator::do_evaluate(const betting_market_group_create_operation& op)
{ try {
database& d = db();
FC_ASSERT(trx_state->_is_proposed_trx);
// the event_id in the operation can be a relative id. If it is,
@ -84,10 +82,10 @@ void_result betting_market_group_create_evaluator::do_evaluate(const betting_mar
FC_ASSERT(resolved_event_id.space() == event_id_type::space_id &&
resolved_event_id.type() == event_id_type::type_id,
"event_id must refer to a event_id_type");
event_id = resolved_event_id;
FC_ASSERT( db().find_object(event_id), "Invalid event specified" );
_event_id = resolved_event_id;
FC_ASSERT(d.find_object(_event_id), "Invalid event specified");
FC_ASSERT( db().find_object(op.asset_id), "Invalid asset specified" );
FC_ASSERT(d.find_object(op.asset_id), "Invalid asset specified");
// the rules_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly rules
@ -98,8 +96,8 @@ void_result betting_market_group_create_evaluator::do_evaluate(const betting_mar
FC_ASSERT(resolved_rules_id.space() == betting_market_rules_id_type::space_id &&
resolved_rules_id.type() == betting_market_rules_id_type::type_id,
"rules_id must refer to a betting_market_rules_id_type");
rules_id = resolved_rules_id;
FC_ASSERT( db().find_object(rules_id), "Invalid rules specified" );
_rules_id = resolved_rules_id;
FC_ASSERT(d.find_object(_rules_id), "Invalid rules specified");
return void_result();
} FC_CAPTURE_AND_RETHROW((op)) }
@ -107,8 +105,8 @@ object_id_type betting_market_group_create_evaluator::do_apply(const betting_mar
{ try {
const betting_market_group_object& new_betting_market_group =
db().create<betting_market_group_object>([&](betting_market_group_object& betting_market_group_obj) {
betting_market_group_obj.event_id = event_id;
betting_market_group_obj.rules_id = rules_id;
betting_market_group_obj.event_id = _event_id;
betting_market_group_obj.rules_id = _rules_id;
betting_market_group_obj.description = op.description;
betting_market_group_obj.asset_id = op.asset_id;
betting_market_group_obj.frozen = false;
@ -119,28 +117,15 @@ object_id_type betting_market_group_create_evaluator::do_apply(const betting_mar
void_result betting_market_group_update_evaluator::do_evaluate(const betting_market_group_update_operation& op)
{ try {
database& d = db();
FC_ASSERT(trx_state->_is_proposed_trx);
FC_ASSERT(op.new_event_id.valid() ||
op.new_description.valid() ||
_betting_market_group = &op.betting_market_group_id(d);
FC_ASSERT(op.new_description.valid() ||
op.new_rules_id.valid() ||
op.freeze.valid() ||
op.delay_bets.valid(), "nothing to change");
// the event_id in the operation can be a relative id. If it is,
// resolve it and verify that it is truly an event
if (op.new_event_id.valid())
{
object_id_type resolved_event_id = *op.new_event_id;
if (is_relative(*op.new_event_id))
resolved_event_id = get_relative_id(*op.new_event_id);
FC_ASSERT(resolved_event_id.space() == event_id_type::space_id &&
resolved_event_id.type() == event_id_type::type_id,
"event_id must refer to a event_id_type");
event_id = resolved_event_id;
FC_ASSERT( db().find_object(event_id), "invalid event specified" );
}
if (op.new_rules_id.valid())
{
// the rules_id in the operation can be a relative id. If it is,
@ -152,41 +137,58 @@ void_result betting_market_group_update_evaluator::do_evaluate(const betting_mar
FC_ASSERT(resolved_rules_id.space() == betting_market_rules_id_type::space_id &&
resolved_rules_id.type() == betting_market_rules_id_type::type_id,
"rules_id must refer to a betting_market_rules_id_type");
rules_id = resolved_rules_id;
FC_ASSERT( db().find_object(rules_id), "invalid rules specified" );
_rules_id = resolved_rules_id;
FC_ASSERT(d.find_object(_rules_id), "invalid rules specified");
}
if (op.freeze.valid())
{
const auto& _betting_market_group = &op.betting_market_group_id(db());
FC_ASSERT(_betting_market_group->frozen != *op.freeze, "freeze would not change the state of the betting market group");
}
if (op.delay_bets.valid())
{
const auto& _betting_market_group = &op.betting_market_group_id(db());
FC_ASSERT(_betting_market_group->delay_bets != *op.delay_bets, "delay_bets would not change the state of the betting market group");
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
void_result betting_market_group_update_evaluator::do_apply(const betting_market_group_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.betting_market_group_id),
[&]( betting_market_group_object& bmgo )
{
database& d = db();
d.modify(*_betting_market_group, [&](betting_market_group_object& betting_market_group) {
if (op.new_description.valid())
bmgo.description = *op.new_description;
if( op.new_event_id.valid() )
bmgo.event_id = event_id;
betting_market_group.description = *op.new_description;
if (op.new_rules_id.valid())
bmgo.rules_id = rules_id;
betting_market_group.rules_id = _rules_id;
if (op.freeze.valid())
bmgo.frozen = *op.freeze;
betting_market_group.frozen = *op.freeze;
if (op.delay_bets.valid())
bmgo.delay_bets = *op.delay_bets;
{
assert(_betting_market_group->delay_bets != *op.delay_bets); // we checked this in evaluate
betting_market_group.delay_bets = *op.delay_bets;
if (!*op.delay_bets)
{
// we have switched from delayed to not-delayed. if there are any delayed bets,
// push them through now.
const auto& bet_odds_idx = d.get_index_type<bet_object_index>().indices().get<by_odds>();
auto bet_iter = bet_odds_idx.begin();
bool last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
while (!last)
{
const bet_object& delayed_bet = *bet_iter;
++bet_iter;
last = bet_iter == bet_odds_idx.end() || !bet_iter->end_of_delay;
const betting_market_object& betting_market = delayed_bet.betting_market_id(d);
if (betting_market.group_id == op.betting_market_group_id && !_betting_market_group->frozen)
{
d.modify(delayed_bet, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
d.place_bet(delayed_bet);
}
}
}
}
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -204,8 +206,8 @@ void_result betting_market_create_evaluator::do_evaluate(const betting_market_cr
FC_ASSERT(resolved_betting_market_group_id.space() == betting_market_group_id_type::space_id &&
resolved_betting_market_group_id.type() == betting_market_group_id_type::type_id,
"betting_market_group_id must refer to a betting_market_group_id_type");
group_id = resolved_betting_market_group_id;
FC_ASSERT( db().find_object(group_id), "Invalid betting_market_group specified" );
_group_id = resolved_betting_market_group_id;
FC_ASSERT(db().find_object(_group_id), "Invalid betting_market_group specified");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -214,7 +216,7 @@ object_id_type betting_market_create_evaluator::do_apply(const betting_market_cr
{ try {
const betting_market_object& new_betting_market =
db().create<betting_market_object>([&](betting_market_object& betting_market_obj) {
betting_market_obj.group_id = group_id;
betting_market_obj.group_id = _group_id;
betting_market_obj.description = op.description;
betting_market_obj.payout_condition = op.payout_condition;
});
@ -223,7 +225,9 @@ object_id_type betting_market_create_evaluator::do_apply(const betting_market_cr
void_result betting_market_update_evaluator::do_evaluate(const betting_market_update_operation& op)
{ try {
database& d = db();
FC_ASSERT(trx_state->_is_proposed_trx);
_betting_market = &op.betting_market_id(d);
FC_ASSERT(op.new_group_id.valid() || op.new_description.valid() || op.new_payout_condition.valid(), "nothing to change");
if (op.new_group_id.valid())
@ -237,8 +241,8 @@ void_result betting_market_update_evaluator::do_evaluate(const betting_market_up
FC_ASSERT(resolved_betting_market_group_id.space() == betting_market_group_id_type::space_id &&
resolved_betting_market_group_id.type() == betting_market_group_id_type::type_id,
"betting_market_group_id must refer to a betting_market_group_id_type");
group_id = resolved_betting_market_group_id;
FC_ASSERT( db().find_object(group_id), "invalid betting_market_group specified" );
_group_id = resolved_betting_market_group_id;
FC_ASSERT(d.find_object(_group_id), "invalid betting_market_group specified");
}
return void_result();
@ -246,17 +250,13 @@ void_result betting_market_update_evaluator::do_evaluate(const betting_market_up
void_result betting_market_update_evaluator::do_apply(const betting_market_update_operation& op)
{ try {
database& _db = db();
_db.modify(
_db.get(op.betting_market_id),
[&]( betting_market_object& bmo )
{
db().modify(*_betting_market, [&](betting_market_object& betting_market) {
if (op.new_group_id.valid())
bmo.group_id = group_id;
betting_market.group_id = _group_id;
if (op.new_payout_condition.valid())
bmo.payout_condition = *op.new_payout_condition;
betting_market.payout_condition = *op.new_payout_condition;
if (op.new_description.valid())
bmo.description = *op.new_description;
betting_market.description = *op.new_description;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -276,18 +276,18 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
_asset = &_betting_market_group->asset_id(d);
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
const chain_parameters& current_params = d.get_global_properties().parameters;
_current_params = &d.get_global_properties().parameters;
// are their odds valid
FC_ASSERT( op.backer_multiplier >= current_params.min_bet_multiplier &&
op.backer_multiplier <= current_params.max_bet_multiplier,
FC_ASSERT( op.backer_multiplier >= _current_params->min_bet_multiplier &&
op.backer_multiplier <= _current_params->max_bet_multiplier,
"Bet odds are outside the blockchain's limits" );
if (!current_params.permitted_betting_odds_increments.empty())
if (!_current_params->permitted_betting_odds_increments.empty())
{
bet_multiplier_type allowed_increment;
const auto iter = current_params.permitted_betting_odds_increments.upper_bound(op.backer_multiplier);
if (iter == current_params.permitted_betting_odds_increments.end())
allowed_increment = std::prev(current_params.permitted_betting_odds_increments.end())->second;
const auto iter = _current_params->permitted_betting_odds_increments.upper_bound(op.backer_multiplier);
if (iter == _current_params->permitted_betting_odds_increments.end())
allowed_increment = std::prev(_current_params->permitted_betting_odds_increments.end())->second;
else
allowed_increment = iter->second;
FC_ASSERT(op.backer_multiplier % allowed_increment == 0, "Bet odds must be a multiple of ${allowed_increment}", ("allowed_increment", allowed_increment));
@ -312,13 +312,15 @@ object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
bet_obj.amount_to_bet = op.amount_to_bet;
bet_obj.backer_multiplier = op.backer_multiplier;
bet_obj.back_or_lay = op.back_or_lay;
if (_betting_market_group->delay_bets)
bet_obj.end_of_delay = d.head_block_time() + _current_params->live_betting_delay_time;
});
bet_id_type new_bet_id = new_bet.id; // save the bet id here, new_bet may be deleted during place_bet()
d.adjust_balance(fee_paying_account->id, -op.amount_to_bet);
//bool bet_matched =
if (!_betting_market_group->delay_bets || _current_params->live_betting_delay_time <= 0)
d.place_bet(new_bet);
return new_bet_id;
@ -341,9 +343,9 @@ void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
void_result betting_market_group_resolve_evaluator::do_evaluate(const betting_market_group_resolve_operation& op)
{ try {
const database& d = db();
database& d = db();
_betting_market_group = &op.betting_market_group_id(d);
db().validate_betting_market_group_resolutions(*_betting_market_group, op.resolutions);
d.validate_betting_market_group_resolutions(*_betting_market_group, op.resolutions);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -355,8 +357,7 @@ void_result betting_market_group_resolve_evaluator::do_apply(const betting_marke
void_result betting_market_group_cancel_unmatched_bets_evaluator::do_evaluate(const betting_market_group_cancel_unmatched_bets_operation& op)
{ try {
const database& d = db();
_betting_market_group = &op.betting_market_group_id(d);
_betting_market_group = &op.betting_market_group_id(db());
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -26,6 +26,8 @@ void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
void database::cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market)
{
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
// first, cancel all bets on the active books
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(betting_market.id));
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(betting_market.id));
while (book_itr != book_end)
@ -34,6 +36,18 @@ void database::cancel_all_unmatched_bets_on_betting_market(const betting_market_
++book_itr;
cancel_bet(*old_book_itr, true);
}
// then, cancel any delayed bets on that market. We don't have an index for
// that, so walk through all delayed bets
book_itr = bet_odds_idx.begin();
while (book_itr != bet_odds_idx.end() &&
book_itr->end_of_delay)
{
auto old_book_itr = book_itr;
++book_itr;
if (old_book_itr->betting_market_id == betting_market.id)
cancel_bet(*old_book_itr, true);
}
}
void database::cancel_all_betting_markets_for_event(const event_object& event_obj)

View file

@ -540,6 +540,7 @@ void database::_apply_block( const signed_block& next_block )
perform_chain_maintenance(next_block, global_props);
create_block_summary(next_block);
place_delayed_bets(); // must happen after update_global_dynamic_data() updates the time
clear_expired_transactions();
clear_expired_proposals();
clear_expired_orders();

View file

@ -188,6 +188,53 @@ void database::clear_expired_transactions()
transaction_idx.remove(*dedupe_index.begin());
} FC_CAPTURE_AND_RETHROW() }
void database::place_delayed_bets()
{ try {
// If any bets have been placed during live betting where bets are delayed for a few seconds, see if there are
// any bets whose delays have expired.
// Delayed bets are sorted to the beginning of the order book, so if there are any bets that need placing,
// they're right at the front of the book
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
auto iter = bet_odds_idx.begin();
// we use an awkward looping mechanism here because there's a case where we are processing the
// last delayed bet before the "real" order book starts and `iter` was pointing at the first
// real order. The place_bet() call can cause the that real order to be deleted, so we need
// to decide whether this is the last delayed bet before `place_bet` is called.
bool last = iter == bet_odds_idx.end() ||
!iter->end_of_delay ||
*iter->end_of_delay > head_block_time();
while (!last)
{
const bet_object& bet_to_place = *iter;
++iter;
last = iter == bet_odds_idx.end() ||
!iter->end_of_delay ||
*iter->end_of_delay > head_block_time();
// it's possible that the betting market was active when the bet was placed,
// but has been frozen before the delay expired. If that's the case here,
// don't try to match the bet.
// Since this check happens every block, this could impact performance if a
// market with many delayed bets is frozen for a long time.
// Our current understanding is that the witnesses will typically cancel all unmatched
// bets on frozen markets to avoid this.
const betting_market_object& betting_market = bet_to_place.betting_market_id(*this);
const betting_market_group_object& betting_market_group = betting_market.group_id(*this);
if (!betting_market_group.frozen)
{
modify(bet_to_place, [](bet_object& bet_obj) {
// clear the end_of_delay, which will re-sort the bet into its place in the book
bet_obj.end_of_delay.reset();
});
place_bet(bet_to_place);
}
}
} FC_CAPTURE_AND_RETHROW() }
void database::clear_expired_proposals()
{
const auto& proposal_expiration_index = get_index_type<proposal_index>().indices().get<by_expiration>();

View file

@ -45,6 +45,8 @@ namespace graphene { namespace chain {
void_result do_evaluate( const betting_market_rules_update_operation& o );
void_result do_apply( const betting_market_rules_update_operation& o );
private:
const betting_market_rules_object* _rules;
};
class betting_market_group_create_evaluator : public evaluator<betting_market_group_create_evaluator>
@ -55,8 +57,8 @@ namespace graphene { namespace chain {
void_result do_evaluate(const betting_market_group_create_operation& o);
object_id_type do_apply(const betting_market_group_create_operation& o);
private:
event_id_type event_id;
betting_market_rules_id_type rules_id;
event_id_type _event_id;
betting_market_rules_id_type _rules_id;
};
class betting_market_group_update_evaluator : public evaluator<betting_market_group_update_evaluator>
@ -67,8 +69,8 @@ namespace graphene { namespace chain {
void_result do_evaluate(const betting_market_group_update_operation& o);
void_result do_apply(const betting_market_group_update_operation& o);
private:
event_id_type event_id;
betting_market_rules_id_type rules_id;
betting_market_rules_id_type _rules_id;
const betting_market_group_object* _betting_market_group;
};
class betting_market_create_evaluator : public evaluator<betting_market_create_evaluator>
@ -79,7 +81,7 @@ namespace graphene { namespace chain {
void_result do_evaluate( const betting_market_create_operation& o );
object_id_type do_apply( const betting_market_create_operation& o );
private:
betting_market_group_id_type group_id;
betting_market_group_id_type _group_id;
};
class betting_market_update_evaluator : public evaluator<betting_market_update_evaluator>
@ -90,7 +92,8 @@ namespace graphene { namespace chain {
void_result do_evaluate( const betting_market_update_operation& o );
void_result do_apply( const betting_market_update_operation& o );
private:
betting_market_group_id_type group_id;
const betting_market_object* _betting_market;
betting_market_group_id_type _group_id;
};
class bet_place_evaluator : public evaluator<bet_place_evaluator>
@ -103,6 +106,7 @@ namespace graphene { namespace chain {
private:
const betting_market_group_object* _betting_market_group;
const betting_market_object* _betting_market;
const chain_parameters* _current_params;
const asset_object* _asset;
share_type _stake_plus_fees;
};

View file

@ -98,6 +98,8 @@ class bet_object : public graphene::db::abstract_object< bet_object >
bet_type back_or_lay;
fc::optional<fc::time_point_sec> end_of_delay;
static share_type get_approximate_matching_amount(share_type bet_amount, bet_multiplier_type backer_multiplier, bet_type back_or_lay, bool round_up = false);
// returns the amount of a bet that completely matches this bet
@ -161,78 +163,121 @@ typedef generic_index<betting_market_object, betting_market_object_multi_index_t
struct compare_bet_by_odds {
bool operator()(const bet_object& lhs, const bet_object& rhs) const
{
return compare(lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0>
bool operator() (const std::tuple<T0>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), rhs.betting_market_id);
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), rhs.end_of_delay, rhs.betting_market_id);
}
template<typename T0>
bool operator() (const bet_object& lhs, const std::tuple<T0>& rhs) const
{
return compare(lhs.betting_market_id, std::get<0>(rhs));
return compare(lhs.end_of_delay, lhs.betting_market_id, fc::optional<time_point_sec>(), std::get<0>(rhs));
}
template<typename T0, typename T1>
bool operator() (const std::tuple<T0, T1>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), rhs.betting_market_id, rhs.back_or_lay);
return compare(fc::optional<fc::time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay);
}
template<typename T0, typename T1>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1>& rhs) const
{
return compare(lhs.betting_market_id, lhs.back_or_lay, std::get<0>(rhs), std::get<1>(rhs));
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs));
}
template<typename T0, typename T1, typename T2>
bool operator() (const std::tuple<T0, T1, T2>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs),
rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier);
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs),
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier);
}
template<typename T0, typename T1, typename T2>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2>& rhs) const
{
return compare(lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier,
std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs));
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier,
fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs));
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const std::tuple<T0, T1, T2, T3>& lhs, const bet_object& rhs) const
{
return compare(std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs), std::get<3>(lhs),
rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
return compare(fc::optional<time_point_sec>(), std::get<0>(lhs), std::get<1>(lhs), std::get<2>(lhs), std::get<3>(lhs),
rhs.end_of_delay, rhs.betting_market_id, rhs.back_or_lay, rhs.backer_multiplier, rhs.id);
}
template<typename T0, typename T1, typename T2, typename T3>
bool operator() (const bet_object& lhs, const std::tuple<T0, T1, T2, T3>& rhs) const
{
return compare(lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs), std::get<3>(rhs));
return compare(lhs.end_of_delay, lhs.betting_market_id, lhs.back_or_lay, lhs.backer_multiplier, lhs.id,
fc::optional<time_point_sec>(), std::get<0>(rhs), std::get<1>(rhs), std::get<2>(rhs), std::get<3>(rhs));
}
bool compare(const betting_market_id_type& lhs_betting_market_id, const betting_market_id_type& rhs_betting_market_id) const
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
return lhs_betting_market_id < rhs_betting_market_id;
}
bool compare(const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
return false;
return lhs_bet_type < rhs_bet_type;
}
bool compare(const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
return *lhs_end_of_delay < *rhs_end_of_delay;
}
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
@ -246,11 +291,33 @@ struct compare_bet_by_odds {
else
return lhs_backer_multiplier > rhs_backer_multiplier;
}
bool compare(const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bool compare(const fc::optional<fc::time_point_sec>& lhs_end_of_delay,
const betting_market_id_type& lhs_betting_market_id, bet_type lhs_bet_type,
bet_multiplier_type lhs_backer_multiplier, const bet_id_type& lhs_bet_id,
const fc::optional<fc::time_point_sec>& rhs_end_of_delay,
const betting_market_id_type& rhs_betting_market_id, bet_type rhs_bet_type,
bet_multiplier_type rhs_backer_multiplier, const bet_id_type& rhs_bet_id) const
{
// if either bet is delayed, sort the delayed bet to the
// front. If both are delayed, the delay expiring soonest
// comes first.
if (lhs_end_of_delay || rhs_end_of_delay)
{
if (!rhs_end_of_delay)
return true;
if (!lhs_end_of_delay)
return false;
if (*lhs_end_of_delay < *rhs_end_of_delay)
return true;
if (*lhs_end_of_delay > *rhs_end_of_delay)
return false;
// if both bets have the same delay, prefer the one
// that was placed first (lowest id)
return lhs_bet_id < rhs_bet_id;
}
// if neither bet was delayed
if (lhs_betting_market_id < rhs_betting_market_id)
return true;
if (lhs_betting_market_id > rhs_betting_market_id)
@ -451,6 +518,6 @@ typedef generic_index<betting_market_position_object, betting_market_position_mu
FC_REFLECT_DERIVED( graphene::chain::betting_market_rules_object, (graphene::db::object), (name)(description) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_group_object, (graphene::db::object), (description)(event_id)(rules_id)(asset_id)(frozen)(delay_bets)(total_matched_bets_amount) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_object, (graphene::db::object), (group_id)(description)(payout_condition) )
FC_REFLECT_DERIVED( graphene::chain::bet_object, (graphene::db::object), (bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay) )
FC_REFLECT_DERIVED( graphene::chain::bet_object, (graphene::db::object), (bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay) )
FC_REFLECT_DERIVED( graphene::chain::betting_market_position_object, (graphene::db::object), (bettor_id)(betting_market_id)(pay_if_payout_condition)(pay_if_not_payout_condition)(pay_if_canceled)(pay_if_not_canceled)(fees_collected) )

View file

@ -464,6 +464,7 @@ namespace graphene { namespace chain {
void update_signing_witness(const witness_object& signing_witness, const signed_block& new_block);
void update_last_irreversible_block();
void clear_expired_transactions();
void place_delayed_bets();
void clear_expired_proposals();
void clear_expired_orders();
void update_expired_feeds();

View file

@ -112,8 +112,6 @@ struct betting_market_group_update_operation : public base_operation
optional<internationalized_string_type> new_description;
optional<object_id_type> new_event_id;
optional<object_id_type> new_rules_id;
optional<bool> freeze;
@ -388,7 +386,7 @@ FC_REFLECT( graphene::chain::betting_market_group_create_operation,
FC_REFLECT( graphene::chain::betting_market_group_update_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_group_update_operation,
(fee)(betting_market_group_id)(new_description)(new_event_id)(new_rules_id)(freeze)(delay_bets)(extensions) )
(fee)(betting_market_group_id)(new_description)(new_rules_id)(freeze)(delay_bets)(extensions) )
FC_REFLECT( graphene::chain::betting_market_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::betting_market_create_operation,

View file

@ -1658,7 +1658,6 @@ class wallet_api
fc::time_point_sec expiration_time,
betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> event_id,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
bool broadcast = false);

View file

@ -5308,7 +5308,6 @@ signed_transaction wallet_api::propose_update_betting_market_group(
fc::time_point_sec expiration_time,
betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> event_id,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
bool broadcast /*= false*/)
@ -5319,7 +5318,6 @@ signed_transaction wallet_api::propose_update_betting_market_group(
betting_market_group_update_operation betting_market_group_update_op;
betting_market_group_update_op.betting_market_group_id = betting_market_group_id;
betting_market_group_update_op.new_description = description;
betting_market_group_update_op.new_event_id = event_id;
betting_market_group_update_op.new_rules_id = rules_id;
betting_market_group_update_op.freeze = freeze;

View file

@ -388,6 +388,92 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test)
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(delayed_bets_test) // test live betting
{
try
{
const auto& bet_odds_idx = db.get_index_type<bet_object_index>().indices().get<by_odds>();
ACTORS( (alice)(bob) );
{
// we're generating blocks, and the hacky way we keep references to the objects generated
// in this macro doesn't work, so do this in an anonymous scope to prevent us from using
// the variables it declares outside later in the function
CREATE_ICE_HOCKEY_BETTING_MARKET();
}
generate_blocks(1);
BOOST_REQUIRE_EQUAL(db.get_index_type<betting_market_group_object_index>().indices().get<by_id>().size(), 1);
const betting_market_group_object& caps_vs_blackhawks_moneyline_betting_market = *db.get_index_type<betting_market_group_object_index>().indices().get<by_id>().begin();
BOOST_REQUIRE_EQUAL(db.get_index_type<betting_market_object_index>().indices().get<by_id>().size(), 2);
const betting_market_object& capitals_win_market = *db.get_index_type<betting_market_object_index>().indices().get<by_id>().begin();
update_betting_market_group(caps_vs_blackhawks_moneyline_betting_market.id,
fc::optional<internationalized_string_type>(),
fc::optional<object_id_type>(),
fc::optional<bool>(),
true);
generate_blocks(1);
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);
generate_blocks(1);
BOOST_TEST_MESSAGE("Testing basic delayed bet mechanics");
// alice backs 100 at odds 2
BOOST_TEST_MESSAGE("Alice places a back bet of 100 at odds 2.0");
bet_id_type delayed_back_bet = place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
generate_blocks(1);
// verify the bet hasn't been placed in the active book
auto first_bet_in_market = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
BOOST_CHECK(first_bet_in_market == bet_odds_idx.end());
// after 3 blocks, the delay should have expired and it will be promoted to the active book
generate_blocks(2);
first_bet_in_market = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
auto last_bet_in_market = bet_odds_idx.upper_bound(std::make_tuple(capitals_win_market.id));
BOOST_CHECK(first_bet_in_market != bet_odds_idx.end());
BOOST_CHECK(std::distance(first_bet_in_market, last_bet_in_market) == 1);
// bob lays 100 at odds 2 to match alice's bet currently on the books
BOOST_TEST_MESSAGE("Bob places a lay bet of 100 at odds 2.0");
bet_id_type delayed_lay_bet = place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
generate_blocks(1);
// bob's bet will still be delayed, so the active order book will only contain alice's bet
first_bet_in_market = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
last_bet_in_market = bet_odds_idx.upper_bound(std::make_tuple(capitals_win_market.id));
BOOST_CHECK(std::distance(first_bet_in_market, last_bet_in_market) == 1);
// once bob's bet's delay has expired, the two bets will annihilate each other, leaving
// an empty order book
generate_blocks(2);
BOOST_CHECK(bet_odds_idx.empty());
// now test that when we cancel all bets on a market, delayed bets get canceled too
BOOST_TEST_MESSAGE("Alice places a back bet of 100 at odds 2.0");
delayed_back_bet = place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
generate_blocks(1);
first_bet_in_market = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
BOOST_CHECK(!bet_odds_idx.empty());
BOOST_CHECK(first_bet_in_market == bet_odds_idx.end());
BOOST_TEST_MESSAGE("Cancel all bets");
cancel_unmatched_bets(caps_vs_blackhawks_moneyline_betting_market.id);
BOOST_CHECK(bet_odds_idx.empty());
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE( chained_market_create_test )
{
@ -785,10 +871,10 @@ BOOST_AUTO_TEST_CASE(betting_market_group_update_test)
fc::optional<object_id_type> new_rule = new_betting_market_rules.id;
fc::optional<bool> empty_bool;
update_betting_market_group(moneyline_betting_markets.id, new_desc, empty_object_id, empty_object_id, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, dempty, new_event, empty_object_id, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, dempty, empty_object_id, new_rule, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, new_desc, new_event, new_rule, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, new_desc, empty_object_id, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, dempty, empty_object_id, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, dempty, new_rule, empty_bool, empty_bool);
update_betting_market_group(moneyline_betting_markets.id, new_desc, new_rule, empty_bool, empty_bool);
transfer(account_id_type(), bob_id, asset(10000000));
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
@ -1044,7 +1130,7 @@ BOOST_AUTO_TEST_CASE( wimbledon_2017_gentelmen_singles_final_test )
fc::optional<bool> empty_bool;
fc::optional<bool> true_bool = true;
update_betting_market_group(moneyline_cilic_vs_federer_id, empty_str,
empty_obj, empty_obj,
empty_obj,
empty_bool, true_bool);
place_bet(alice_id, cilic_wins_final_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);

View file

@ -1357,21 +1357,18 @@ const betting_market_group_object& database_fixture::create_betting_market_group
void database_fixture::update_betting_market_group(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> event_id,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<bool> delay_bets
)
fc::optional<bool> delay_bets)
{ try {
betting_market_group_update_operation betting_market_group_update_op;
betting_market_group_update_op.betting_market_group_id = betting_market_group_id;
betting_market_group_update_op.new_description = description;
betting_market_group_update_op.new_event_id = event_id;
betting_market_group_update_op.new_rules_id = rules_id;
betting_market_group_update_op.freeze = freeze;
betting_market_group_update_op.delay_bets = delay_bets;
process_operation_by_witnesses(betting_market_group_update_op);
} FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(description)(event_id)(rules_id)(freeze)(delay_bets)) }
} FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(description)(rules_id)(freeze)(delay_bets)) }
const betting_market_object& database_fixture::create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition)

View file

@ -309,7 +309,6 @@ struct database_fixture {
const betting_market_group_object& create_betting_market_group(internationalized_string_type description, event_id_type event_id, betting_market_rules_id_type rules_id, asset_id_type asset_id);
void update_betting_market_group(betting_market_group_id_type betting_market_group_id,
fc::optional<internationalized_string_type> description,
fc::optional<object_id_type> event_id,
fc::optional<object_id_type> rules_id,
fc::optional<bool> freeze,
fc::optional<bool> delay_bets);