Merge branch 'betting-merge' into baxter-hardfork
This commit is contained in:
commit
861c287109
17 changed files with 1236 additions and 247 deletions
|
|
@ -277,6 +277,14 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
|
|||
ddump((_betting_market_group->get_status()));
|
||||
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::frozen,
|
||||
"Unable to place bets while the market is frozen" );
|
||||
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::closed,
|
||||
"Unable to place bets while the market is closed" );
|
||||
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::graded,
|
||||
"Unable to place bets while the market is graded" );
|
||||
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::re_grading,
|
||||
"Unable to place bets while the market is re-grading" );
|
||||
FC_ASSERT( _betting_market_group->get_status() != betting_market_group_status::settled,
|
||||
"Unable to place bets while the market is settled" );
|
||||
|
||||
_asset = &_betting_market_group->asset_id(d);
|
||||
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
|
||||
|
|
@ -300,10 +308,6 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
|
|||
|
||||
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",
|
||||
("balance", d.get_balance(*fee_paying_account, *_asset))("amount_to_bet", op.amount_to_bet.amount) );
|
||||
|
||||
return void_result();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
|
|
@ -326,12 +330,18 @@ object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
|
|||
|
||||
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);
|
||||
|
||||
// place the bet, this may return guaranteed winnings
|
||||
ddump((_betting_market_group->bets_are_delayed())(_current_params->live_betting_delay_time));
|
||||
if (!_betting_market_group->bets_are_delayed() || _current_params->live_betting_delay_time <= 0)
|
||||
d.place_bet(new_bet);
|
||||
|
||||
// now that their guaranteed winnings have been returned, check whether 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",
|
||||
("balance", d.get_balance(*fee_paying_account, *_asset))("amount_to_bet", op.amount_to_bet.amount) );
|
||||
|
||||
// pay for it
|
||||
d.adjust_balance(fee_paying_account->id, -op.amount_to_bet);
|
||||
|
||||
return new_bet_id;
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
|
|
|
|||
|
|
@ -373,10 +373,13 @@ namespace {
|
|||
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
|
||||
// at least contain the string we're looking for
|
||||
const char* fc_reflected_value_name = fc::reflector<betting_market_group_state>::to_string((betting_market_group_state)i);
|
||||
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
|
||||
if (!strstr(filled_state_names[i], fc_reflected_value_name))
|
||||
{
|
||||
fc_elog(fc::logger::get("default"),
|
||||
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
|
||||
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
|
||||
++error_count;
|
||||
}
|
||||
}
|
||||
catch (const fc::bad_cast_exception&)
|
||||
{
|
||||
|
|
@ -386,7 +389,10 @@ namespace {
|
|||
++error_count;
|
||||
}
|
||||
}
|
||||
dlog("Done checking constants");
|
||||
if (error_count == 0)
|
||||
dlog("Betting market group status constants are correct");
|
||||
else
|
||||
wlog("There were ${count} errors in the betting market group status constants", ("count", error_count));
|
||||
|
||||
return error_count == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ namespace graphene { namespace chain {
|
|||
unresolved,
|
||||
frozen,
|
||||
closed,
|
||||
canceled,
|
||||
graded,
|
||||
canceled,
|
||||
settled
|
||||
};
|
||||
} }
|
||||
|
|
@ -23,8 +23,8 @@ FC_REFLECT_ENUM(graphene::chain::betting_market_state,
|
|||
(unresolved)
|
||||
(frozen)
|
||||
(closed)
|
||||
(canceled)
|
||||
(graded)
|
||||
(canceled)
|
||||
(settled))
|
||||
|
||||
|
||||
|
|
@ -252,6 +252,7 @@ betting_market_object::betting_market_object(const betting_market_object& rhs) :
|
|||
group_id(rhs.group_id),
|
||||
description(rhs.description),
|
||||
payout_condition(rhs.payout_condition),
|
||||
resolution(rhs.resolution),
|
||||
my(new impl(this))
|
||||
{
|
||||
my->state_machine = rhs.my->state_machine;
|
||||
|
|
@ -265,6 +266,7 @@ betting_market_object& betting_market_object::operator=(const betting_market_obj
|
|||
group_id = rhs.group_id;
|
||||
description = rhs.description;
|
||||
payout_condition = rhs.payout_condition;
|
||||
resolution = rhs.resolution;
|
||||
|
||||
my->state_machine = rhs.my->state_machine;
|
||||
my->state_machine.betting_market_obj = this;
|
||||
|
|
@ -293,10 +295,14 @@ namespace {
|
|||
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
|
||||
// at least contain the string we're looking for
|
||||
const char* fc_reflected_value_name = fc::reflector<betting_market_state>::to_string((betting_market_state)i);
|
||||
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
|
||||
if (!strstr(filled_state_names[i], fc_reflected_value_name))
|
||||
{
|
||||
fc_elog(fc::logger::get("default"),
|
||||
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
|
||||
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: "
|
||||
"boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
|
||||
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
|
||||
++error_count;
|
||||
}
|
||||
}
|
||||
catch (const fc::bad_cast_exception&)
|
||||
{
|
||||
|
|
@ -306,7 +312,10 @@ namespace {
|
|||
++error_count;
|
||||
}
|
||||
}
|
||||
dlog("Done checking constants");
|
||||
if (error_count == 0)
|
||||
dlog("Betting market status constants are correct");
|
||||
else
|
||||
wlog("There were ${count} errors in the betting market status constants", ("count", error_count));
|
||||
|
||||
return error_count == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
#define DEFAULT_LOGGER "betting"
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
|
|
@ -7,6 +6,7 @@
|
|||
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
#include <boost/range/combine.hpp>
|
||||
#include <boost/range/join.hpp>
|
||||
#include <boost/tuple/tuple.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
|
@ -20,7 +20,7 @@ void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
|
|||
{
|
||||
bet_canceled_operation bet_canceled_virtual_op(bet.bettor_id, bet.id,
|
||||
bet.amount_to_bet);
|
||||
//idump((bet_canceled_virtual_op));
|
||||
//fc_idump(fc::logger::get("betting"), (bet_canceled_virtual_op));
|
||||
push_applied_operation(std::move(bet_canceled_virtual_op));
|
||||
}
|
||||
remove(bet);
|
||||
|
|
@ -132,7 +132,7 @@ void database::resolve_betting_market_group(const betting_market_group_object& b
|
|||
|
||||
void database::settle_betting_market_group(const betting_market_group_object& betting_market_group)
|
||||
{
|
||||
ilog("Settling betting market group ${id}", ("id", betting_market_group.id));
|
||||
fc_ilog(fc::logger::get("betting"), "Settling betting market group ${id}", ("id", betting_market_group.id));
|
||||
// we pay the rake fee to the dividend distribution account for the core asset, go ahead
|
||||
// and look up that account now
|
||||
fc::optional<account_id_type> rake_account_id;
|
||||
|
|
@ -244,7 +244,7 @@ void database::settle_betting_market_group(const betting_market_group_object& be
|
|||
// pay winning - rake
|
||||
adjust_balance(bettor_id, asset(payout_amounts - rake_amount, betting_market_group.asset_id));
|
||||
// [ROL]
|
||||
//idump((payout_amounts)(net_profits.value)(rake_amount.value));
|
||||
//fc_idump(fc::logger::get("betting"), (payout_amounts)(net_profits.value)(rake_amount.value));
|
||||
|
||||
push_applied_operation(betting_market_group_resolved_operation(bettor_id,
|
||||
betting_market_group.id,
|
||||
|
|
@ -268,23 +268,45 @@ void database::settle_betting_market_group(const betting_market_group_object& be
|
|||
const betting_market_object& betting_market = *betting_market_itr;
|
||||
|
||||
++betting_market_itr;
|
||||
dlog("removing betting market ${id}", ("id", betting_market.id));
|
||||
fc_dlog(fc::logger::get("betting"), "removing betting market ${id}", ("id", betting_market.id));
|
||||
remove(betting_market);
|
||||
}
|
||||
|
||||
const event_object& event = betting_market_group.event_id(*this);
|
||||
|
||||
dlog("removing betting market group ${id}", ("id", betting_market_group.id));
|
||||
fc_dlog(fc::logger::get("betting"), "removing betting market group ${id}", ("id", betting_market_group.id));
|
||||
remove(betting_market_group);
|
||||
}
|
||||
|
||||
if (event.get_status() == event_status::canceled ||
|
||||
event.get_status() == event_status::settled) {
|
||||
dlog("removing event ${id}", ("id", event.id));
|
||||
void database::remove_completed_events()
|
||||
{
|
||||
const auto& event_index = get_index_type<event_object_index>().indices().get<by_event_status>();
|
||||
|
||||
auto canceled_event_iter = event_index.lower_bound(event_status::canceled);
|
||||
while (canceled_event_iter != event_index.end() && canceled_event_iter->get_status() == event_status::canceled)
|
||||
{
|
||||
const event_object& event = *canceled_event_iter;
|
||||
++canceled_event_iter;
|
||||
fc_dlog(fc::logger::get("betting"), "removing canceled event ${id}", ("id", event.id));
|
||||
remove(event);
|
||||
}
|
||||
|
||||
auto settled_event_iter = event_index.lower_bound(event_status::settled);
|
||||
while (settled_event_iter != event_index.end() && settled_event_iter->get_status() == event_status::settled)
|
||||
{
|
||||
const event_object& event = *settled_event_iter;
|
||||
++settled_event_iter;
|
||||
fc_dlog(fc::logger::get("betting"), "removing settled event ${id}", ("id", event.id));
|
||||
remove(event);
|
||||
}
|
||||
}
|
||||
|
||||
share_type adjust_betting_position(database& db, account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, share_type bet_amount, share_type matched_amount)
|
||||
share_type adjust_betting_position(database& db,
|
||||
account_id_type bettor_id,
|
||||
betting_market_id_type betting_market_id,
|
||||
bet_type back_or_lay,
|
||||
share_type bet_amount,
|
||||
share_type matched_amount)
|
||||
{ try {
|
||||
assert(bet_amount >= 0);
|
||||
|
||||
|
|
@ -298,8 +320,8 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett
|
|||
if (itr == index.end())
|
||||
{
|
||||
db.create<betting_market_position_object>([&](betting_market_position_object& position) {
|
||||
position.bettor_id = bettor_id;
|
||||
position.betting_market_id = betting_market_id;
|
||||
position.bettor_id = bettor_id;
|
||||
position.betting_market_id = betting_market_id;
|
||||
position.pay_if_payout_condition = back_or_lay == bet_type::back ? bet_amount + matched_amount : 0;
|
||||
position.pay_if_not_payout_condition = back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0;
|
||||
position.pay_if_canceled = bet_amount;
|
||||
|
|
@ -308,8 +330,8 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett
|
|||
});
|
||||
} else {
|
||||
db.modify(*itr, [&](betting_market_position_object& position) {
|
||||
assert(position.bettor_id == bettor_id);
|
||||
assert(position.betting_market_id == betting_market_id);
|
||||
assert(position.bettor_id == bettor_id);
|
||||
assert(position.betting_market_id == betting_market_id);
|
||||
position.pay_if_payout_condition += back_or_lay == bet_type::back ? bet_amount + matched_amount : 0;
|
||||
position.pay_if_not_payout_condition += back_or_lay == bet_type::lay ? bet_amount + matched_amount : 0;
|
||||
position.pay_if_canceled += bet_amount;
|
||||
|
|
@ -339,7 +361,7 @@ bool bet_was_matched(database& db, const bet_object& bet,
|
|||
asset_amount_bet,
|
||||
actual_multiplier,
|
||||
guaranteed_winnings_returned);
|
||||
//edump((bet_matched_virtual_op));
|
||||
//fc_edump(fc::logger::get("betting"), (bet_matched_virtual_op));
|
||||
db.push_applied_operation(std::move(bet_matched_virtual_op));
|
||||
|
||||
// update the bet on the books
|
||||
|
|
@ -378,11 +400,12 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
{
|
||||
assert(taker_bet.amount_to_bet.asset_id == maker_bet.amount_to_bet.asset_id);
|
||||
assert(taker_bet.amount_to_bet.amount > 0 && maker_bet.amount_to_bet.amount > 0);
|
||||
assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier : taker_bet.backer_multiplier >= maker_bet.backer_multiplier);
|
||||
assert(taker_bet.back_or_lay == bet_type::back ? taker_bet.backer_multiplier <= maker_bet.backer_multiplier :
|
||||
taker_bet.backer_multiplier >= maker_bet.backer_multiplier);
|
||||
assert(taker_bet.back_or_lay != maker_bet.back_or_lay);
|
||||
|
||||
int result = 0;
|
||||
//idump((taker_bet)(maker_bet));
|
||||
//fc_idump(fc::logger::get("betting"), (taker_bet)(maker_bet));
|
||||
|
||||
// using the maker's odds, figure out how much of the maker's bet we would match, rounding down
|
||||
// go ahead and get look up the ratio for the bet (a bet with odds 1.92 will have a ratio 25:23)
|
||||
|
|
@ -393,17 +416,33 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
// and make some shortcuts to get to the maker's and taker's side of the ratio
|
||||
const share_type& maker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? back_odds_ratio : lay_odds_ratio;
|
||||
const share_type& taker_odds_ratio = maker_bet.back_or_lay == bet_type::back ? lay_odds_ratio : back_odds_ratio;
|
||||
//idump((back_odds_ratio)(lay_odds_ratio));
|
||||
//idump((maker_odds_ratio)(taker_odds_ratio));
|
||||
// we need to figure out how much of the bet matches. the smallest amount
|
||||
// that could match is one maker_odds_ratio to one taker_odds_ratio,
|
||||
// but we can match any integer multiple of that ratio (called the 'factor' below),
|
||||
// limited only by the bet amounts.
|
||||
//
|
||||
//fc_idump(fc::logger::get("betting"), (back_odds_ratio)(lay_odds_ratio));
|
||||
//fc_idump(fc::logger::get("betting"), (maker_odds_ratio)(taker_odds_ratio));
|
||||
|
||||
// now figure out how much of the maker bet we'll consume. We don't yet know whether the maker or taker
|
||||
// will be the limiting factor.
|
||||
share_type maximum_taker_factor = taker_bet.amount_to_bet.amount / taker_odds_ratio;
|
||||
share_type maximum_factor_taker_is_willing_to_pay = taker_bet.amount_to_bet.amount / taker_odds_ratio;
|
||||
share_type maximum_factor_taker_is_willing_to_receive = taker_bet.get_exact_matching_amount() / maker_odds_ratio;
|
||||
|
||||
share_type maximum_taker_factor;
|
||||
bool taker_was_limited_by_matching_amount = maximum_factor_taker_is_willing_to_receive < maximum_factor_taker_is_willing_to_pay;
|
||||
if (taker_was_limited_by_matching_amount)
|
||||
maximum_taker_factor = maximum_factor_taker_is_willing_to_receive;
|
||||
else
|
||||
maximum_taker_factor = maximum_factor_taker_is_willing_to_pay;
|
||||
|
||||
fc_idump(fc::logger::get("betting"), (maximum_factor_taker_is_willing_to_pay)(maximum_factor_taker_is_willing_to_receive)(maximum_taker_factor));
|
||||
|
||||
share_type maximum_maker_factor = maker_bet.amount_to_bet.amount / maker_odds_ratio;
|
||||
share_type maximum_factor = std::min(maximum_taker_factor, maximum_maker_factor);
|
||||
share_type maker_amount_to_match = maximum_factor * maker_odds_ratio;
|
||||
share_type taker_amount_to_match = maximum_factor * taker_odds_ratio;
|
||||
//idump((maker_amount_to_match)(taker_amount_to_match));
|
||||
fc_idump(fc::logger::get("betting"), (maker_amount_to_match)(taker_amount_to_match));
|
||||
|
||||
// TODO: analyze whether maximum_maker_amount_to_match can ever be zero here
|
||||
assert(maker_amount_to_match != 0);
|
||||
|
|
@ -414,20 +453,64 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
assert(taker_amount_to_match <= taker_bet.amount_to_bet.amount);
|
||||
assert(taker_amount_to_match / taker_odds_ratio * taker_odds_ratio == taker_amount_to_match);
|
||||
{
|
||||
// verify we're getting the odds we expect
|
||||
fc::uint128_t payout_128 = maker_amount_to_match.value;
|
||||
payout_128 += taker_amount_to_match.value;
|
||||
payout_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
|
||||
payout_128 /= maker_bet.back_or_lay == bet_type::back ? maker_amount_to_match.value : taker_amount_to_match.value;
|
||||
assert(payout_128.to_uint64() == maker_bet.backer_multiplier);
|
||||
// verify we're getting the odds we expect
|
||||
fc::uint128_t payout_128 = maker_amount_to_match.value;
|
||||
payout_128 += taker_amount_to_match.value;
|
||||
payout_128 *= GRAPHENE_BETTING_ODDS_PRECISION;
|
||||
payout_128 /= maker_bet.back_or_lay == bet_type::back ? maker_amount_to_match.value : taker_amount_to_match.value;
|
||||
assert(payout_128.to_uint64() == maker_bet.backer_multiplier);
|
||||
}
|
||||
#endif
|
||||
|
||||
//idump((taker_amount_to_match)(maker_amount_to_match));
|
||||
//fc_idump(fc::logger::get("betting"), (taker_amount_to_match)(maker_amount_to_match));
|
||||
|
||||
// maker bets will always be an exact multiple of maker_odds_ratio, so they will either completely match or remain on the books
|
||||
bool maker_bet_will_completely_match = maker_amount_to_match == maker_bet.amount_to_bet.amount;
|
||||
|
||||
if (maker_bet_will_completely_match && taker_amount_to_match != taker_bet.amount_to_bet.amount)
|
||||
{
|
||||
// then the taker bet will stay on the books. If the taker odds != the maker odds, we will
|
||||
// need to refund the stake the taker was expecting to pay but didn't.
|
||||
// compute how much of the taker's bet should still be left on the books and how much
|
||||
// the taker should pay for the remaining amount; refund any amount that won't remain
|
||||
// on the books and isn't used to pay the bet we're currently matching.
|
||||
|
||||
share_type takers_odds_back_odds_ratio;
|
||||
share_type takers_odds_lay_odds_ratio;
|
||||
std::tie(takers_odds_back_odds_ratio, takers_odds_lay_odds_ratio) = taker_bet.get_ratio();
|
||||
const share_type& takers_odds_taker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_back_odds_ratio : takers_odds_lay_odds_ratio;
|
||||
const share_type& takers_odds_maker_odds_ratio = taker_bet.back_or_lay == bet_type::back ? takers_odds_lay_odds_ratio : takers_odds_back_odds_ratio;
|
||||
|
||||
share_type unrounded_taker_remaining_amount_to_match = taker_bet.get_exact_matching_amount() - maker_amount_to_match;
|
||||
// because we matched at the maker's odds and not the taker's odds, the remaining amount to match
|
||||
// may not be an even multiple of the taker's odds; round it down.
|
||||
share_type taker_remaining_factor = unrounded_taker_remaining_amount_to_match / takers_odds_maker_odds_ratio;
|
||||
share_type taker_remaining_maker_amount_to_match = taker_remaining_factor * takers_odds_maker_odds_ratio;
|
||||
share_type taker_remaining_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio;
|
||||
|
||||
share_type taker_refund_amount = taker_bet.amount_to_bet.amount - taker_amount_to_match - taker_remaining_bet_amount;
|
||||
|
||||
if (taker_refund_amount > share_type())
|
||||
{
|
||||
db.modify(taker_bet, [&taker_refund_amount](bet_object& taker_bet_object) {
|
||||
taker_bet_object.amount_to_bet.amount -= taker_refund_amount;
|
||||
});
|
||||
fc_dlog(fc::logger::get("betting"), "Refunding ${taker_refund_amount} to taker because we matched at the maker's odds of "
|
||||
"${maker_odds} instead of the taker's odds ${taker_odds}",
|
||||
("taker_refund_amount", taker_refund_amount)
|
||||
("maker_odds", maker_bet.backer_multiplier)
|
||||
("taker_odds", taker_bet.backer_multiplier));
|
||||
fc_ddump(fc::logger::get("betting"), (taker_bet));
|
||||
|
||||
db.adjust_balance(taker_bet.bettor_id, asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(taker_bet.bettor_id, taker_bet.id,
|
||||
asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id));
|
||||
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
|
||||
db.push_applied_operation(std::move(bet_adjusted_op));
|
||||
}
|
||||
}
|
||||
|
||||
// if the maker bet stays on the books, we need to make sure the taker bet is removed from the books (either it fills completely,
|
||||
// or any un-filled amount is canceled)
|
||||
result |= bet_was_matched(db, taker_bet, taker_amount_to_match, maker_amount_to_match, maker_bet.backer_multiplier, !maker_bet_will_completely_match);
|
||||
|
|
@ -441,17 +524,50 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker
|
|||
// called from the bet_place_evaluator
|
||||
bool database::place_bet(const bet_object& new_bet_object)
|
||||
{
|
||||
// We allow users to place bets for any amount, but only amounts that are exact multiples of the odds
|
||||
// ratio can be matched. Immediately return any unmatchable amount in this bet.
|
||||
share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount();
|
||||
share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount;
|
||||
share_type rounded_bet_amount = scale_factor * minimum_matchable_amount;
|
||||
|
||||
if (rounded_bet_amount == share_type())
|
||||
{
|
||||
// the bet was too small to match at all, cancel the bet
|
||||
cancel_bet(new_bet_object, true);
|
||||
return true;
|
||||
}
|
||||
else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount)
|
||||
{
|
||||
asset stake_returned = new_bet_object.amount_to_bet;
|
||||
stake_returned.amount -= rounded_bet_amount;
|
||||
|
||||
modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) {
|
||||
modified_bet_object.amount_to_bet.amount = rounded_bet_amount;
|
||||
});
|
||||
|
||||
adjust_balance(new_bet_object.bettor_id, stake_returned);
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id,
|
||||
stake_returned);
|
||||
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
|
||||
push_applied_operation(std::move(bet_adjusted_op));
|
||||
|
||||
fc_dlog(fc::logger::get("betting"), "Refunded ${refund_amount} to round the bet down to something that can match exactly, new bet: ${new_bet}",
|
||||
("refund_amount", stake_returned.amount)
|
||||
("new_bet", new_bet_object));
|
||||
}
|
||||
|
||||
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
|
||||
|
||||
bet_type bet_type_to_match = new_bet_object.back_or_lay == bet_type::back ? bet_type::lay : bet_type::back;
|
||||
auto book_itr = bet_odds_idx.lower_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match));
|
||||
auto book_end = bet_odds_idx.upper_bound(std::make_tuple(new_bet_object.betting_market_id, bet_type_to_match, new_bet_object.backer_multiplier));
|
||||
|
||||
// ilog("");
|
||||
// ilog("------------ order book ------------------");
|
||||
// fc_ilog(fc::logger::get("betting"), "");
|
||||
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
|
||||
// for (auto itr = book_itr; itr != book_end; ++itr)
|
||||
// idump((*itr));
|
||||
// ilog("------------ order book ------------------");
|
||||
// fc_idump(fc::logger::get("betting"), (*itr));
|
||||
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
|
||||
|
||||
int orders_matched_flags = 0;
|
||||
bool finished = false;
|
||||
|
|
@ -465,45 +581,12 @@ bool database::place_bet(const bet_object& new_bet_object)
|
|||
// we continue if the maker bet was completely consumed AND the taker bet was not
|
||||
finished = orders_matched_flags != 2;
|
||||
}
|
||||
|
||||
if (!(orders_matched_flags & 1))
|
||||
{
|
||||
// if the new (taker) bet was not completely consumed, we need to put whatever remains
|
||||
// of it on the books. But we only allow bets that can be exactly matched
|
||||
// on the books, so round the amount down if necessary
|
||||
share_type minimum_matchable_amount = new_bet_object.get_minimum_matchable_amount();
|
||||
share_type scale_factor = new_bet_object.amount_to_bet.amount / minimum_matchable_amount;
|
||||
share_type rounded_bet_amount = scale_factor * minimum_matchable_amount;
|
||||
//idump((new_bet_object.amount_to_bet.amount)(rounded_bet_amount)(minimum_matchable_amount)(scale_factor));
|
||||
fc_ddump(fc::logger::get("betting"), (new_bet_object));
|
||||
|
||||
if (rounded_bet_amount == share_type())
|
||||
{
|
||||
// the remainder of the bet was too small to match, cancel the bet
|
||||
cancel_bet(new_bet_object, true);
|
||||
return true;
|
||||
}
|
||||
else if (rounded_bet_amount != new_bet_object.amount_to_bet.amount)
|
||||
{
|
||||
asset stake_returned = new_bet_object.amount_to_bet;
|
||||
stake_returned.amount -= rounded_bet_amount;
|
||||
|
||||
modify(new_bet_object, [&rounded_bet_amount](bet_object& modified_bet_object) {
|
||||
modified_bet_object.amount_to_bet.amount = rounded_bet_amount;
|
||||
});
|
||||
|
||||
adjust_balance(new_bet_object.bettor_id, stake_returned);
|
||||
// TODO: update global statistics
|
||||
bet_adjusted_operation bet_adjusted_op(new_bet_object.bettor_id, new_bet_object.id,
|
||||
stake_returned);
|
||||
// idump((bet_adjusted_op)(new_bet_object));
|
||||
push_applied_operation(std::move(bet_adjusted_op));
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else
|
||||
return true;
|
||||
// return true if the taker bet was completely consumed
|
||||
return (orders_matched_flags & 1) != 0;
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@ void database::update_active_witnesses()
|
|||
{
|
||||
vote_counter vc;
|
||||
for( const witness_object& wit : wits )
|
||||
vc.add( wit.witness_account, _vote_tally_buffer[wit.vote_id] );
|
||||
vc.add( wit.witness_account, std::max(_vote_tally_buffer[wit.vote_id], UINT64_C(1)) );
|
||||
vc.finish( a.active );
|
||||
}
|
||||
} );
|
||||
|
|
@ -310,7 +310,7 @@ void database::update_active_committee_members()
|
|||
{
|
||||
vote_counter vc;
|
||||
for( const committee_member_object& cm : committee_members )
|
||||
vc.add( cm.committee_member_account, _vote_tally_buffer[cm.vote_id] );
|
||||
vc.add( cm.committee_member_account, std::max(_vote_tally_buffer[cm.vote_id], UINT64_C(1)) );
|
||||
vc.finish( a.active );
|
||||
}
|
||||
} );
|
||||
|
|
@ -737,7 +737,7 @@ void schedule_pending_dividend_balances(database& db,
|
|||
const vesting_balance_index& vesting_index,
|
||||
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index,
|
||||
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index)
|
||||
{
|
||||
{ try {
|
||||
dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}",
|
||||
("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time()));
|
||||
auto current_distribution_account_balance_range =
|
||||
|
|
@ -1059,10 +1059,10 @@ void schedule_pending_dividend_balances(database& db,
|
|||
dividend_data_obj.last_distribution_time = current_head_block_time;
|
||||
});
|
||||
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
void process_dividend_assets(database& db)
|
||||
{
|
||||
{ try {
|
||||
ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time()));
|
||||
|
||||
const account_balance_index& balance_index = db.get_index_type<account_balance_index>();
|
||||
|
|
@ -1084,143 +1084,146 @@ void process_dividend_assets(database& db)
|
|||
if (dividend_data.options.next_payout_time &&
|
||||
db.head_block_time() >= *dividend_data.options.next_payout_time)
|
||||
{
|
||||
dlog("Dividend payout time has arrived for asset ${holder_asset}",
|
||||
("holder_asset", dividend_holder_asset_obj.symbol));
|
||||
|
||||
try
|
||||
{
|
||||
dlog("Dividend payout time has arrived for asset ${holder_asset}",
|
||||
("holder_asset", dividend_holder_asset_obj.symbol));
|
||||
#ifndef NDEBUG
|
||||
// dump balances before the payouts for debugging
|
||||
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
|
||||
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
|
||||
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
|
||||
// dump balances before the payouts for debugging
|
||||
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
|
||||
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
|
||||
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
|
||||
#endif
|
||||
|
||||
// when we do the payouts, we first increase the balances in all of the receiving accounts
|
||||
// and use this map to keep track of the total amount of each asset paid out.
|
||||
// Afterwards, we decrease the distribution account's balance by the total amount paid out,
|
||||
// and modify the distributed_balances accordingly
|
||||
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
|
||||
// when we do the payouts, we first increase the balances in all of the receiving accounts
|
||||
// and use this map to keep track of the total amount of each asset paid out.
|
||||
// Afterwards, we decrease the distribution account's balance by the total amount paid out,
|
||||
// and modify the distributed_balances accordingly
|
||||
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
|
||||
|
||||
auto pending_payouts_range =
|
||||
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
|
||||
// we iterate in this order so we can build up a list of payouts for each account to put in the
|
||||
// virtual op
|
||||
flat_set<asset> payouts_for_this_holder;
|
||||
fc::optional<account_id_type> last_holder_account_id;
|
||||
auto pending_payouts_range =
|
||||
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
|
||||
// we iterate in this order so we can build up a list of payouts for each account to put in the
|
||||
// virtual op
|
||||
vector<asset> payouts_for_this_holder;
|
||||
fc::optional<account_id_type> last_holder_account_id;
|
||||
|
||||
// cache the assets the distribution account is approved to send, we will be asking
|
||||
// for these often
|
||||
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
|
||||
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
|
||||
auto approved_assets_iter = approved_assets.find(asset_id);
|
||||
if (approved_assets_iter != approved_assets.end())
|
||||
return approved_assets_iter->second;
|
||||
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
|
||||
asset_id(db));
|
||||
approved_assets[asset_id] = is_approved;
|
||||
return is_approved;
|
||||
};
|
||||
// cache the assets the distribution account is approved to send, we will be asking
|
||||
// for these often
|
||||
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
|
||||
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
|
||||
auto approved_assets_iter = approved_assets.find(asset_id);
|
||||
if (approved_assets_iter != approved_assets.end())
|
||||
return approved_assets_iter->second;
|
||||
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
|
||||
asset_id(db));
|
||||
approved_assets[asset_id] = is_approved;
|
||||
return is_approved;
|
||||
};
|
||||
|
||||
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
|
||||
{
|
||||
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
|
||||
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
|
||||
{
|
||||
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
|
||||
|
||||
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size())
|
||||
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size())
|
||||
{
|
||||
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||
*last_holder_account_id,
|
||||
payouts_for_this_holder));
|
||||
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||
payouts_for_this_holder.clear();
|
||||
last_holder_account_id.reset();
|
||||
}
|
||||
|
||||
|
||||
if (pending_balance_object.pending_balance.value &&
|
||||
is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
|
||||
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
|
||||
{
|
||||
dlog("Processing payout of ${asset} to account ${account}",
|
||||
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
|
||||
("account", pending_balance_object.owner(db).name));
|
||||
|
||||
db.adjust_balance(pending_balance_object.owner,
|
||||
asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
payouts_for_this_holder.push_back(asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
last_holder_account_id = pending_balance_object.owner;
|
||||
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||
pending_balance.pending_balance = 0;
|
||||
});
|
||||
}
|
||||
|
||||
++pending_balance_object_iter;
|
||||
}
|
||||
// we will always be left with the last holder's data, generate the virtual op for it now.
|
||||
if (last_holder_account_id && payouts_for_this_holder.size())
|
||||
{
|
||||
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||
*last_holder_account_id,
|
||||
payouts_for_this_holder));
|
||||
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||
payouts_for_this_holder.clear();
|
||||
last_holder_account_id.reset();
|
||||
}
|
||||
|
||||
// now debit the total amount of dividends paid out from the distribution account
|
||||
// and reduce the distributed_balances accordingly
|
||||
|
||||
if (pending_balance_object.pending_balance.value &&
|
||||
is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
|
||||
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
|
||||
for (const auto& value : amounts_paid_out_by_asset)
|
||||
{
|
||||
dlog("Processing payout of ${asset} to account ${account}",
|
||||
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
|
||||
("account", pending_balance_object.owner(db).name));
|
||||
const asset_id_type& asset_paid_out = value.first;
|
||||
const share_type& amount_paid_out = value.second;
|
||||
|
||||
db.adjust_balance(pending_balance_object.owner,
|
||||
asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance,
|
||||
pending_balance_object.dividend_payout_asset_type));
|
||||
last_holder_account_id = pending_balance_object.owner;
|
||||
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-amount_paid_out,
|
||||
asset_paid_out));
|
||||
auto distributed_balance_iter =
|
||||
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
|
||||
asset_paid_out));
|
||||
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
|
||||
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
|
||||
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero
|
||||
});
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||
pending_balance.pending_balance = 0;
|
||||
});
|
||||
}
|
||||
|
||||
++pending_balance_object_iter;
|
||||
}
|
||||
// we will always be left with the last holder's data, generate the virtual op for it now.
|
||||
if (last_holder_account_id && payouts_for_this_holder.size())
|
||||
{
|
||||
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||
*last_holder_account_id,
|
||||
payouts_for_this_holder));
|
||||
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||
}
|
||||
// now schedule the next payout time
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
|
||||
dividend_data_obj.last_payout_time = current_head_block_time;
|
||||
fc::optional<fc::time_point_sec> next_payout_time;
|
||||
if (dividend_data_obj.options.payout_interval)
|
||||
{
|
||||
// if there was a previous payout, make our next payment one interval
|
||||
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
|
||||
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
|
||||
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
|
||||
do
|
||||
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
|
||||
while (next_possible_time_sec <= current_time_sec);
|
||||
|
||||
// now debit the total amount of dividends paid out from the distribution account
|
||||
// and reduce the distributed_balances accordingly
|
||||
|
||||
for (const auto& value : amounts_paid_out_by_asset)
|
||||
{
|
||||
const asset_id_type& asset_paid_out = value.first;
|
||||
const share_type& amount_paid_out = value.second;
|
||||
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-amount_paid_out,
|
||||
asset_paid_out));
|
||||
auto distributed_balance_iter =
|
||||
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
|
||||
asset_paid_out));
|
||||
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
|
||||
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
|
||||
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// now schedule the next payout time
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
|
||||
dividend_data_obj.last_payout_time = current_head_block_time;
|
||||
fc::optional<fc::time_point_sec> next_payout_time;
|
||||
if (dividend_data_obj.options.payout_interval)
|
||||
{
|
||||
// if there was a previous payout, make our next payment one interval
|
||||
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
|
||||
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
|
||||
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
|
||||
do
|
||||
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
|
||||
while (next_possible_time_sec <= current_time_sec);
|
||||
|
||||
next_payout_time = next_possible_time_sec;
|
||||
}
|
||||
dividend_data_obj.options.next_payout_time = next_payout_time;
|
||||
idump((dividend_data_obj.last_scheduled_payout_time)
|
||||
(dividend_data_obj.last_payout_time)
|
||||
(dividend_data_obj.options.next_payout_time));
|
||||
});
|
||||
next_payout_time = next_possible_time_sec;
|
||||
}
|
||||
dividend_data_obj.options.next_payout_time = next_payout_time;
|
||||
idump((dividend_data_obj.last_scheduled_payout_time)
|
||||
(dividend_data_obj.last_payout_time)
|
||||
(dividend_data_obj.options.next_payout_time));
|
||||
});
|
||||
}
|
||||
FC_RETHROW_EXCEPTIONS(error, "Error while paying out dividends for holder asset ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol))
|
||||
}
|
||||
}
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
||||
{
|
||||
{ try {
|
||||
const auto& gpo = get_global_properties();
|
||||
|
||||
distribute_fba_balances(*this);
|
||||
|
|
@ -1416,6 +1419,6 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
// process_budget needs to run at the bottom because
|
||||
// it needs to know the next_maintenance_time
|
||||
process_budget();
|
||||
}
|
||||
} FC_CAPTURE_AND_RETHROW() }
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -677,6 +677,7 @@ void process_settled_betting_markets(database& db, fc::time_point_sec current_bl
|
|||
void database::update_betting_markets(fc::time_point_sec current_block_time)
|
||||
{
|
||||
process_settled_betting_markets(*this, current_block_time);
|
||||
remove_completed_events();
|
||||
}
|
||||
|
||||
} }
|
||||
|
|
|
|||
|
|
@ -392,10 +392,13 @@ namespace graphene { namespace chain {
|
|||
// this is an approximate test, the state name provided by typeinfo will be mangled, but should
|
||||
// at least contain the string we're looking for
|
||||
const char* fc_reflected_value_name = fc::reflector<event_state>::to_string((event_state)i);
|
||||
if (!strcmp(fc_reflected_value_name, filled_state_names[i]))
|
||||
if (!strstr(filled_state_names[i], fc_reflected_value_name))
|
||||
{
|
||||
fc_elog(fc::logger::get("default"),
|
||||
"Error, state string mismatch between fc and boost::msm for int value ${int_value}: boost::msm -> ${boost_string}, fc::reflect -> ${fc_string}",
|
||||
("int_value", i)("boost_string", filled_state_names[i])("fc_string", fc_reflected_value_name));
|
||||
++error_count;
|
||||
}
|
||||
}
|
||||
catch (const fc::bad_cast_exception&)
|
||||
{
|
||||
|
|
@ -405,7 +408,10 @@ namespace graphene { namespace chain {
|
|||
++error_count;
|
||||
}
|
||||
}
|
||||
dlog("Done checking constants");
|
||||
if (error_count == 0)
|
||||
dlog("Event status constants are correct");
|
||||
else
|
||||
wlog("There were ${count} errors in the event status constants", ("count", error_count));
|
||||
|
||||
return error_count == 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -396,6 +396,7 @@ namespace graphene { namespace chain {
|
|||
void resolve_betting_market_group(const betting_market_group_object& betting_market_group,
|
||||
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
|
||||
void settle_betting_market_group(const betting_market_group_object& betting_market_group);
|
||||
void remove_completed_events();
|
||||
/**
|
||||
* @brief Process a new bet
|
||||
* @param new_bet_object The new bet to process
|
||||
|
|
|
|||
|
|
@ -95,11 +95,13 @@ class event_object : public graphene::db::abstract_object< event_object >
|
|||
};
|
||||
|
||||
struct by_event_group_id;
|
||||
struct by_event_status;
|
||||
typedef multi_index_container<
|
||||
event_object,
|
||||
indexed_by<
|
||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||
ordered_non_unique< tag<by_event_group_id>, member< event_object, event_group_id_type, &event_object::event_group_id > > > > event_object_multi_index_type;
|
||||
ordered_non_unique< tag<by_event_group_id>, member< event_object, event_group_id_type, &event_object::event_group_id > >,
|
||||
ordered_non_unique< tag<by_event_status>, const_mem_fun< event_object, event_status, &event_object::get_status > > > > event_object_multi_index_type;
|
||||
|
||||
typedef generic_index<event_object, event_object_multi_index_type> event_object_index;
|
||||
|
||||
|
|
|
|||
|
|
@ -291,7 +291,7 @@ namespace graphene { namespace chain {
|
|||
asset_dividend_distribution_operation() {}
|
||||
asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id,
|
||||
const account_id_type& account_id,
|
||||
const flat_set<asset>& amounts) :
|
||||
const vector<asset>& amounts) :
|
||||
dividend_asset_id(dividend_asset_id),
|
||||
account_id(account_id),
|
||||
amounts(amounts)
|
||||
|
|
@ -323,7 +323,7 @@ namespace graphene { namespace chain {
|
|||
account_id_type account_id;
|
||||
|
||||
/// The amounts received
|
||||
flat_set<asset> amounts;
|
||||
vector<asset> amounts;
|
||||
|
||||
extensions_type extensions;
|
||||
|
||||
|
|
|
|||
|
|
@ -304,53 +304,51 @@ namespace graphene { namespace chain {
|
|||
const tournament_details_object& details = tournament_obj.tournament_details_id(event.db);
|
||||
share_type total_prize = 0;
|
||||
for (const auto& payer_pair : details.payers)
|
||||
{
|
||||
total_prize += payer_pair.second;
|
||||
}
|
||||
total_prize += payer_pair.second;
|
||||
assert(total_prize == tournament_obj.prize_pool);
|
||||
#endif
|
||||
assert(event.match.match_winners.size() == 1);
|
||||
const account_id_type& winner = *event.match.match_winners.begin();
|
||||
uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage;
|
||||
|
||||
// check whether the core asset pays dividends. If so, we transfer the rake fee
|
||||
// to the core asset's dividend account
|
||||
const asset_object& core_asset_obj = asset_id_type()(event.db);
|
||||
optional<asset_dividend_data_id_type> dividend_id = core_asset_obj.dividend_data_id;
|
||||
|
||||
share_type rake_amount = 0;
|
||||
|
||||
const asset_object & asset_obj = tournament_obj.options.buy_in.asset_id(event.db);
|
||||
optional<asset_dividend_data_id_type> dividend_id = asset_obj.dividend_data_id;
|
||||
|
||||
if (dividend_id.valid())
|
||||
{
|
||||
rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
|
||||
}
|
||||
if (dividend_id)
|
||||
rake_amount = (fc::uint128_t(tournament_obj.prize_pool.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
|
||||
asset won_prize(tournament_obj.prize_pool - rake_amount, tournament_obj.options.buy_in.asset_id);
|
||||
tournament_payout_operation op;
|
||||
|
||||
if (won_prize.amount.value)
|
||||
{
|
||||
// Adjusting balance of winner
|
||||
event.db.adjust_balance(winner, won_prize);
|
||||
// Adjusting balance of winner
|
||||
event.db.adjust_balance(winner, won_prize);
|
||||
|
||||
// Generating a virtual operation that shows the payment
|
||||
op.tournament_id = tournament_obj.id;
|
||||
op.payout_amount = won_prize;
|
||||
op.payout_account_id = winner;
|
||||
op.type = payout_type::prize_award;
|
||||
event.db.push_applied_operation(op);
|
||||
// Generating a virtual operation that shows the payment
|
||||
op.tournament_id = tournament_obj.id;
|
||||
op.payout_amount = won_prize;
|
||||
op.payout_account_id = winner;
|
||||
op.type = payout_type::prize_award;
|
||||
event.db.push_applied_operation(op);
|
||||
}
|
||||
|
||||
if (dividend_id.valid() && rake_amount.value)
|
||||
if (dividend_id && rake_amount.value)
|
||||
{
|
||||
// Adjusting balance of dividend_distribution_account
|
||||
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
|
||||
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db);
|
||||
const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account;
|
||||
asset rake(rake_amount, tournament_obj.options.buy_in.asset_id);
|
||||
event.db.adjust_balance(rake_account_id, rake);
|
||||
// Adjusting balance of dividend_distribution_account
|
||||
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
|
||||
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(event.db);
|
||||
const account_id_type& rake_account_id = dividend_obj.dividend_distribution_account;
|
||||
asset rake(rake_amount, tournament_obj.options.buy_in.asset_id);
|
||||
event.db.adjust_balance(rake_account_id, rake);
|
||||
|
||||
// Generating a virtual operation that shows the payment
|
||||
op.payout_amount = rake;
|
||||
op.payout_account_id = rake_account_id;
|
||||
op.type = payout_type::rake_fee;
|
||||
event.db.push_applied_operation(op);
|
||||
// Generating a virtual operation that shows the payment
|
||||
op.payout_amount = rake;
|
||||
op.payout_account_id = rake_account_id;
|
||||
op.type = payout_type::rake_fee;
|
||||
event.db.push_applied_operation(op);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 8df97c6b3543c6d555377ceddc884cf2011c68e4
|
||||
Subproject commit 69a1ceabce1778ee49004eeb6f821d31e4383be2
|
||||
|
|
@ -61,8 +61,6 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
|
|||
binned_order_book result;
|
||||
|
||||
// use a bet_object here for convenience. we really only use it to track the amount, odds, and back_or_lay
|
||||
// note, the current bin is accumulating the matching bets, so it will be of type 'lay' when we're
|
||||
// binning 'back' bets, and vice versa
|
||||
fc::optional<bet_object> current_bin;
|
||||
|
||||
auto flush_current_bin = [¤t_bin, &result]()
|
||||
|
|
@ -72,9 +70,9 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
|
|||
order_bin current_order_bin;
|
||||
|
||||
current_order_bin.backer_multiplier = current_bin->backer_multiplier;
|
||||
current_order_bin.amount_to_bet = current_bin->get_approximate_matching_amount(true /* round up */);
|
||||
current_order_bin.amount_to_bet = current_bin->amount_to_bet.amount;
|
||||
//idump((*current_bin)(current_order_bin));
|
||||
if (current_bin->back_or_lay == bet_type::lay)
|
||||
if (current_bin->back_or_lay == bet_type::back)
|
||||
result.aggregated_back_bets.emplace_back(std::move(current_order_bin));
|
||||
else // current_bin is aggregating back positions
|
||||
result.aggregated_lay_bets.emplace_back(std::move(current_order_bin));
|
||||
|
|
@ -89,7 +87,7 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
|
|||
++bet_odds_iter)
|
||||
{
|
||||
if (current_bin &&
|
||||
(bet_odds_iter->back_or_lay == current_bin->back_or_lay /* we have switched from back to lay bets */ ||
|
||||
(bet_odds_iter->back_or_lay != current_bin->back_or_lay /* we have switched from back to lay bets */ ||
|
||||
(bet_odds_iter->back_or_lay == bet_type::back ? bet_odds_iter->backer_multiplier > current_bin->backer_multiplier :
|
||||
bet_odds_iter->backer_multiplier < current_bin->backer_multiplier)))
|
||||
flush_current_bin();
|
||||
|
|
@ -105,19 +103,19 @@ binned_order_book bookie_api_impl::get_binned_order_book(graphene::chain::bettin
|
|||
{
|
||||
current_bin->backer_multiplier = (bet_odds_iter->backer_multiplier + bin_size - 1) / bin_size * bin_size;
|
||||
current_bin->backer_multiplier = std::min<graphene::chain::bet_multiplier_type>(current_bin->backer_multiplier, current_params.max_bet_multiplier);
|
||||
current_bin->back_or_lay = bet_type::lay;
|
||||
current_bin->back_or_lay = bet_type::back;
|
||||
}
|
||||
else
|
||||
{
|
||||
current_bin->backer_multiplier = bet_odds_iter->backer_multiplier / bin_size * bin_size;
|
||||
current_bin->backer_multiplier = std::max<graphene::chain::bet_multiplier_type>(current_bin->backer_multiplier, current_params.min_bet_multiplier);
|
||||
current_bin->back_or_lay = bet_type::back;
|
||||
current_bin->back_or_lay = bet_type::lay;
|
||||
}
|
||||
|
||||
current_bin->amount_to_bet.amount = 0;
|
||||
}
|
||||
|
||||
current_bin->amount_to_bet.amount += bet_odds_iter->get_exact_matching_amount();
|
||||
current_bin->amount_to_bet.amount += bet_odds_iter->amount_to_bet.amount;
|
||||
}
|
||||
if (current_bin)
|
||||
flush_current_bin();
|
||||
|
|
|
|||
|
|
@ -32,12 +32,14 @@
|
|||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <fc/crypto/openssl.hpp>
|
||||
#include <fc/log/appender.hpp>
|
||||
#include <openssl/rand.h>
|
||||
|
||||
#include <graphene/utilities/tempdir.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/is_authorized_asset.hpp>
|
||||
#include <graphene/chain/witness_object.hpp>
|
||||
#include <graphene/chain/hardfork.hpp>
|
||||
|
||||
#include <graphene/chain/sport_object.hpp>
|
||||
#include <graphene/chain/event_object.hpp>
|
||||
|
|
@ -48,10 +50,91 @@
|
|||
#include <graphene/bookie/bookie_api.hpp>
|
||||
//#include <boost/algorithm/string/replace.hpp>
|
||||
|
||||
struct enable_betting_logging_config {
|
||||
enable_betting_logging_config()
|
||||
{
|
||||
fc::logger::get("betting").add_appender(fc::appender::get("stdout"));
|
||||
fc::logger::get("betting").set_log_level(fc::log_level::debug);
|
||||
}
|
||||
~enable_betting_logging_config() {
|
||||
fc::logger::get("betting").remove_appender(fc::appender::get("stdout"));
|
||||
}
|
||||
};
|
||||
BOOST_GLOBAL_FIXTURE( enable_betting_logging_config );
|
||||
|
||||
using namespace graphene::chain;
|
||||
using namespace graphene::chain::test;
|
||||
using namespace graphene::chain::keywords;
|
||||
|
||||
|
||||
// While the bets are placed, stored, and sorted using the decimal form of their odds, matching
|
||||
// uses the ratios to ensure no rounding takes place.
|
||||
// The allowed odds are defined by rules that can be changed at runtime.
|
||||
// For reference when designing/debugging tests, here is the list of allowed decimal odds and their
|
||||
// corresponding ratios as set in the genesis block.
|
||||
//
|
||||
// decimal ratio | decimal ratio | decimal ratio | decimal ratio | decimal ratio | decimal ratio
|
||||
// ------------------+-----------------+-------------------+-------------------+-------------------+-----------------
|
||||
// 1.01 100:1 | 1.6 5:3 | 2.38 50:69 | 4.8 5:19 | 26 1:25 | 440 1:439
|
||||
// 1.02 50:1 | 1.61 100:61 | 2.4 5:7 | 4.9 10:39 | 27 1:26 | 450 1:449
|
||||
// 1.03 100:3 | 1.62 50:31 | 2.42 50:71 | 5 1:4 | 28 1:27 | 460 1:459
|
||||
// 1.04 25:1 | 1.63 100:63 | 2.44 25:36 | 5.1 10:41 | 29 1:28 | 470 1:469
|
||||
// 1.05 20:1 | 1.64 25:16 | 2.46 50:73 | 5.2 5:21 | 30 1:29 | 480 1:479
|
||||
// 1.06 50:3 | 1.65 20:13 | 2.48 25:37 | 5.3 10:43 | 32 1:31 | 490 1:489
|
||||
// 1.07 100:7 | 1.66 50:33 | 2.5 2:3 | 5.4 5:22 | 34 1:33 | 500 1:499
|
||||
// 1.08 25:2 | 1.67 100:67 | 2.52 25:38 | 5.5 2:9 | 36 1:35 | 510 1:509
|
||||
// 1.09 100:9 | 1.68 25:17 | 2.54 50:77 | 5.6 5:23 | 38 1:37 | 520 1:519
|
||||
// 1.1 10:1 | 1.69 100:69 | 2.56 25:39 | 5.7 10:47 | 40 1:39 | 530 1:529
|
||||
// 1.11 100:11 | 1.7 10:7 | 2.58 50:79 | 5.8 5:24 | 42 1:41 | 540 1:539
|
||||
// 1.12 25:3 | 1.71 100:71 | 2.6 5:8 | 5.9 10:49 | 44 1:43 | 550 1:549
|
||||
// 1.13 100:13 | 1.72 25:18 | 2.62 50:81 | 6 1:5 | 46 1:45 | 560 1:559
|
||||
// 1.14 50:7 | 1.73 100:73 | 2.64 25:41 | 6.2 5:26 | 48 1:47 | 570 1:569
|
||||
// 1.15 20:3 | 1.74 50:37 | 2.66 50:83 | 6.4 5:27 | 50 1:49 | 580 1:579
|
||||
// 1.16 25:4 | 1.75 4:3 | 2.68 25:42 | 6.6 5:28 | 55 1:54 | 590 1:589
|
||||
// 1.17 100:17 | 1.76 25:19 | 2.7 10:17 | 6.8 5:29 | 60 1:59 | 600 1:599
|
||||
// 1.18 50:9 | 1.77 100:77 | 2.72 25:43 | 7 1:6 | 65 1:64 | 610 1:609
|
||||
// 1.19 100:19 | 1.78 50:39 | 2.74 50:87 | 7.2 5:31 | 70 1:69 | 620 1:619
|
||||
// 1.2 5:1 | 1.79 100:79 | 2.76 25:44 | 7.4 5:32 | 75 1:74 | 630 1:629
|
||||
// 1.21 100:21 | 1.8 5:4 | 2.78 50:89 | 7.6 5:33 | 80 1:79 | 640 1:639
|
||||
// 1.22 50:11 | 1.81 100:81 | 2.8 5:9 | 7.8 5:34 | 85 1:84 | 650 1:649
|
||||
// 1.23 100:23 | 1.82 50:41 | 2.82 50:91 | 8 1:7 | 90 1:89 | 660 1:659
|
||||
// 1.24 25:6 | 1.83 100:83 | 2.84 25:46 | 8.2 5:36 | 95 1:94 | 670 1:669
|
||||
// 1.25 4:1 | 1.84 25:21 | 2.86 50:93 | 8.4 5:37 | 100 1:99 | 680 1:679
|
||||
// 1.26 50:13 | 1.85 20:17 | 2.88 25:47 | 8.6 5:38 | 110 1:109 | 690 1:689
|
||||
// 1.27 100:27 | 1.86 50:43 | 2.9 10:19 | 8.8 5:39 | 120 1:119 | 700 1:699
|
||||
// 1.28 25:7 | 1.87 100:87 | 2.92 25:48 | 9 1:8 | 130 1:129 | 710 1:709
|
||||
// 1.29 100:29 | 1.88 25:22 | 2.94 50:97 | 9.2 5:41 | 140 1:139 | 720 1:719
|
||||
// 1.3 10:3 | 1.89 100:89 | 2.96 25:49 | 9.4 5:42 | 150 1:149 | 730 1:729
|
||||
// 1.31 100:31 | 1.9 10:9 | 2.98 50:99 | 9.6 5:43 | 160 1:159 | 740 1:739
|
||||
// 1.32 25:8 | 1.91 100:91 | 3 1:2 | 9.8 5:44 | 170 1:169 | 750 1:749
|
||||
// 1.33 100:33 | 1.92 25:23 | 3.05 20:41 | 10 1:9 | 180 1:179 | 760 1:759
|
||||
// 1.34 50:17 | 1.93 100:93 | 3.1 10:21 | 10.5 2:19 | 190 1:189 | 770 1:769
|
||||
// 1.35 20:7 | 1.94 50:47 | 3.15 20:43 | 11 1:10 | 200 1:199 | 780 1:779
|
||||
// 1.36 25:9 | 1.95 20:19 | 3.2 5:11 | 11.5 2:21 | 210 1:209 | 790 1:789
|
||||
// 1.37 100:37 | 1.96 25:24 | 3.25 4:9 | 12 1:11 | 220 1:219 | 800 1:799
|
||||
// 1.38 50:19 | 1.97 100:97 | 3.3 10:23 | 12.5 2:23 | 230 1:229 | 810 1:809
|
||||
// 1.39 100:39 | 1.98 50:49 | 3.35 20:47 | 13 1:12 | 240 1:239 | 820 1:819
|
||||
// 1.4 5:2 | 1.99 100:99 | 3.4 5:12 | 13.5 2:25 | 250 1:249 | 830 1:829
|
||||
// 1.41 100:41 | 2 1:1 | 3.45 20:49 | 14 1:13 | 260 1:259 | 840 1:839
|
||||
// 1.42 50:21 | 2.02 50:51 | 3.5 2:5 | 14.5 2:27 | 270 1:269 | 850 1:849
|
||||
// 1.43 100:43 | 2.04 25:26 | 3.55 20:51 | 15 1:14 | 280 1:279 | 860 1:859
|
||||
// 1.44 25:11 | 2.06 50:53 | 3.6 5:13 | 15.5 2:29 | 290 1:289 | 870 1:869
|
||||
// 1.45 20:9 | 2.08 25:27 | 3.65 20:53 | 16 1:15 | 300 1:299 | 880 1:879
|
||||
// 1.46 50:23 | 2.1 10:11 | 3.7 10:27 | 16.5 2:31 | 310 1:309 | 890 1:889
|
||||
// 1.47 100:47 | 2.12 25:28 | 3.75 4:11 | 17 1:16 | 320 1:319 | 900 1:899
|
||||
// 1.48 25:12 | 2.14 50:57 | 3.8 5:14 | 17.5 2:33 | 330 1:329 | 910 1:909
|
||||
// 1.49 100:49 | 2.16 25:29 | 3.85 20:57 | 18 1:17 | 340 1:339 | 920 1:919
|
||||
// 1.5 2:1 | 2.18 50:59 | 3.9 10:29 | 18.5 2:35 | 350 1:349 | 930 1:929
|
||||
// 1.51 100:51 | 2.2 5:6 | 3.95 20:59 | 19 1:18 | 360 1:359 | 940 1:939
|
||||
// 1.52 25:13 | 2.22 50:61 | 4 1:3 | 19.5 2:37 | 370 1:369 | 950 1:949
|
||||
// 1.53 100:53 | 2.24 25:31 | 4.1 10:31 | 20 1:19 | 380 1:379 | 960 1:959
|
||||
// 1.54 50:27 | 2.26 50:63 | 4.2 5:16 | 21 1:20 | 390 1:389 | 970 1:969
|
||||
// 1.55 20:11 | 2.28 25:32 | 4.3 10:33 | 22 1:21 | 400 1:399 | 980 1:979
|
||||
// 1.56 25:14 | 2.3 10:13 | 4.4 5:17 | 23 1:22 | 410 1:409 | 990 1:989
|
||||
// 1.57 100:57 | 2.32 25:33 | 4.5 2:7 | 24 1:23 | 420 1:419 | 1000 1:999
|
||||
// 1.58 50:29 | 2.34 50:67 | 4.6 5:18 | 25 1:24 | 430 1:429 |
|
||||
// 1.59 100:59 | 2.36 25:34 | 4.7 10:37
|
||||
|
||||
#define CREATE_ICE_HOCKEY_BETTING_MARKET(never_in_play, delay_before_settling) \
|
||||
create_sport({{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \
|
||||
generate_blocks(1); \
|
||||
|
|
@ -273,12 +356,14 @@ BOOST_AUTO_TEST_CASE(binned_order_books)
|
|||
|
||||
// the binned orders returned should be chosen so that we if we assume those orders are real and we place
|
||||
// matching lay orders, we will completely consume the underlying orders and leave no orders on the books
|
||||
//
|
||||
// for the bets bob placed above, we should get: 200 @ 1.6, 300 @ 1.7
|
||||
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_back_bets.size(), 2u);
|
||||
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_lay_bets.size(), 0u);
|
||||
for (const graphene::bookie::order_bin& binned_order : binned_orders_point_one.aggregated_back_bets)
|
||||
{
|
||||
// compute the matching lay order
|
||||
share_type lay_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::back, false /* round down */);
|
||||
share_type lay_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::back, true /* round up */);
|
||||
ilog("Alice is laying with ${lay_amount} at odds ${odds} to match the binned back amount ${back_amount}", ("lay_amount", lay_amount)("odds", binned_order.backer_multiplier)("back_amount", binned_order.amount_to_bet));
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(lay_amount, asset_id_type()), binned_order.backer_multiplier);
|
||||
}
|
||||
|
|
@ -294,6 +379,7 @@ BOOST_AUTO_TEST_CASE(binned_order_books)
|
|||
BOOST_CHECK(bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id)) == bet_odds_idx.end());
|
||||
|
||||
// place lay bets at decimal odds of 1.55, 1.6, 1.65, 1.66, and 1.67
|
||||
// these bets will get rounded down, actual amounts are 99, 99, 91, 99, and 67
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 155 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(100, asset_id_type()), 165 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
|
|
@ -305,16 +391,28 @@ BOOST_AUTO_TEST_CASE(binned_order_books)
|
|||
|
||||
// the binned orders returned should be chosen so that we if we assume those orders are real and we place
|
||||
// matching lay orders, we will completely consume the underlying orders and leave no orders on the books
|
||||
//
|
||||
// for the bets bob placed above, we shoudl get 356 @ 1.6, 99 @ 1.5
|
||||
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_back_bets.size(), 0u);
|
||||
BOOST_CHECK_EQUAL(binned_orders_point_one.aggregated_lay_bets.size(), 2u);
|
||||
for (const graphene::bookie::order_bin& binned_order : binned_orders_point_one.aggregated_lay_bets)
|
||||
{
|
||||
// compute the matching lay order
|
||||
share_type back_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::lay, false /* round down */);
|
||||
share_type back_amount = bet_object::get_approximate_matching_amount(binned_order.amount_to_bet, binned_order.backer_multiplier, bet_type::lay, true /* round up */);
|
||||
ilog("Alice is backing with ${back_amount} at odds ${odds} to match the binned lay amount ${lay_amount}", ("back_amount", back_amount)("odds", binned_order.backer_multiplier)("lay_amount", binned_order.amount_to_bet));
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(back_amount, asset_id_type()), binned_order.backer_multiplier);
|
||||
|
||||
ilog("After alice's bet, order book is:");
|
||||
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
|
||||
while (bet_iter != bet_odds_idx.end() &&
|
||||
bet_iter->betting_market_id == capitals_win_market.id)
|
||||
{
|
||||
idump((*bet_iter));
|
||||
++bet_iter;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
|
||||
while (bet_iter != bet_odds_idx.end() &&
|
||||
bet_iter->betting_market_id == capitals_win_market.id)
|
||||
|
|
@ -396,6 +494,336 @@ BOOST_AUTO_TEST_CASE( cancel_unmatched_in_betting_group_test )
|
|||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
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_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_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_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.5
|
||||
// This should:
|
||||
// match the full 47 @ 1.94 with 50, and be refunded 44 (leaving 206 left in the bet)
|
||||
// match the full 91 @ 1.91 with 100, and be refunded 82 (leaving 24 in the bet)
|
||||
// bob's balance goes down by 300 - 44 - 82 = 124
|
||||
// leaves a back bet of 24 @ 1.5 on the books
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(300, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 300 - 44 - 82;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts2)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 470 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(470, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 470;
|
||||
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_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a back of 900 at 1.5
|
||||
// This should:
|
||||
// match 423 out of the 470 @ 1.94 with 450, and be refunded 396 (leaving 54 left in the bet)
|
||||
// this is as close as bob can get without getting of a position than he planned, so the remainder
|
||||
// of bob's bet is canceled (refunding the remaining 54)
|
||||
// bob's balance goes down by the 450 he paid matching the bet.
|
||||
// alice's bet remains on the books as a lay of 47 @ 1.94
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(900, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 900 - 396 - 54;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts3)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// lay 470 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(470, asset_id_type()), 194 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 470;
|
||||
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_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a back of 1000 at 1.5
|
||||
// This should:
|
||||
// match all of the 470 @ 1.94 with 500, and be refunded 440 (leaving 60 left in the bet)
|
||||
// bob's balance goes down by the 500 he paid matching the bet and the 60 that is left.
|
||||
// alice's bet is removed from the books
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
bob_expected_balance -= 1000 - 440;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts4)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1000 at 1.89 odds (100:89) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 189 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1000 at 1.97 odds (100:97) -- again, this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000, asset_id_type()), 197 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1000;
|
||||
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_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a lay of 3000 at 2.66
|
||||
// * This means bob expects to pay 3000 and match against 1807.2289. Or,
|
||||
// put another way, bob wants to buy a payout of up to 1807.2289 if the
|
||||
// capitals win, and he is willing to pay up to 3000 to do so.
|
||||
// * The first thing that happens is bob's bet gets rounded down to something
|
||||
// that can match exactly. 2.66 is 50:83 odds, so bob's bet is
|
||||
// reduced to 2988, which should match against 1800.
|
||||
// So bob gets an immediate refund of 12
|
||||
// * The next thing that happens is a match against the top bet on the order book.
|
||||
// That's 1000 @ 1.89. We match at those odds (100:89), so bob will fully match
|
||||
// this bet, paying 890 to get 1000 of his desired win position.
|
||||
// * this top bet is removed from the order books
|
||||
// * bob now has 1000 of the 1800 win position he wants. we adjust his bet
|
||||
// so that what is left will only match 800. This means we will
|
||||
// refund bob 770. His remaining bet is now lay 1328 @ 2.66
|
||||
// * Now we match the next bet on the order books, which is 1000 @ 1.97 (100:97).
|
||||
// Bob only wants 800 of 1000 win position being offered, so he will not
|
||||
// completely consume this bet.
|
||||
// * Bob pays 776 to get his 800 win position.
|
||||
// * alice's top bet on the books is reduced 200 @ 1.97
|
||||
// * Bob now has as much of a position as he was willing to buy. We refund
|
||||
// the remainder of his bet, which is 552
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(3000, asset_id_type()), 266 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
bob_expected_balance -= 3000 - 12 - 770 - 552;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts5)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back 1100 at 1.86 odds (50:43) -- this is an exact amount, nothing surprising should happen here
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 186 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
alice_expected_balance -= 1100;
|
||||
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_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// now have bob match it with a lay of 1100 at 1.98
|
||||
// * This means bob expects to pay 1100 and match against 1122.4, Or,
|
||||
// put another way, bob wants to buy a payout of up to 1122.4 if the
|
||||
// capitals win, and he is willing to pay up to 1100 to do so.
|
||||
// * The first thing that happens is bob's bet gets rounded down to something
|
||||
// that can match exactly. 1.98 (50:49) odds, so bob's bet is
|
||||
// reduced to 1078, which should match against 1100.
|
||||
// So bob gets an immediate refund of 22
|
||||
// * The next thing that happens is a match against the top bet on the order book.
|
||||
// That's 1100 @ 1.86, At these odds, bob's 980 can buy all 1100 of bet, he
|
||||
// pays 1100:946.
|
||||
//
|
||||
// * alice's bet is fully matched, it is removed from the books
|
||||
// * bob's bet is fully matched, he is refunded the remaining 132 and his
|
||||
// bet is complete
|
||||
// * bob's balance is reduced by the 946 he paid
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 198 * GRAPHENE_BETTING_ODDS_PRECISION / 100);
|
||||
bob_expected_balance -= 1100 - 22 - 132;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(match_using_takers_expected_amounts6)
|
||||
{
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
graphene::bookie::bookie_api bookie_api(app);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(1000000000));
|
||||
share_type alice_expected_balance = 1000000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(10000000, asset_id_type()), 13 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(10000000, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(10000000, asset_id_type()), 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
alice_expected_balance -= 30000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// check order books to see they match the bets we placed
|
||||
const auto& bet_odds_idx = db.get_index_type<bet_object_index>().indices().get<by_odds>();
|
||||
auto bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
|
||||
BOOST_REQUIRE(bet_iter != bet_odds_idx.end());
|
||||
BOOST_REQUIRE(bet_iter->betting_market_id == capitals_win_market.id);
|
||||
BOOST_REQUIRE(bet_iter->bettor_id == alice_id);
|
||||
BOOST_REQUIRE(bet_iter->amount_to_bet == asset(10000000, asset_id_type()));
|
||||
BOOST_REQUIRE(bet_iter->backer_multiplier == 13 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
BOOST_REQUIRE(bet_iter->back_or_lay == bet_type::back);
|
||||
++bet_iter;
|
||||
BOOST_REQUIRE(bet_iter != bet_odds_idx.end());
|
||||
BOOST_REQUIRE(bet_iter->betting_market_id == capitals_win_market.id);
|
||||
BOOST_REQUIRE(bet_iter->bettor_id == alice_id);
|
||||
BOOST_REQUIRE(bet_iter->amount_to_bet == asset(10000000, asset_id_type()));
|
||||
BOOST_REQUIRE(bet_iter->backer_multiplier == 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
BOOST_REQUIRE(bet_iter->back_or_lay == bet_type::back);
|
||||
++bet_iter;
|
||||
BOOST_REQUIRE(bet_iter != bet_odds_idx.end());
|
||||
BOOST_REQUIRE(bet_iter->betting_market_id == capitals_win_market.id);
|
||||
BOOST_REQUIRE(bet_iter->bettor_id == alice_id);
|
||||
BOOST_REQUIRE(bet_iter->amount_to_bet == asset(10000000, asset_id_type()));
|
||||
BOOST_REQUIRE(bet_iter->backer_multiplier == 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
BOOST_REQUIRE(bet_iter->back_or_lay == bet_type::back);
|
||||
++bet_iter;
|
||||
BOOST_REQUIRE(bet_iter == bet_odds_idx.end() || bet_iter->betting_market_id != capitals_win_market.id);
|
||||
|
||||
// check the binned order books from the bookie plugin to make sure they match
|
||||
graphene::bookie::binned_order_book binned_orders_point_one = bookie_api.get_binned_order_book(capitals_win_market.id, 1);
|
||||
auto aggregated_back_bets_iter = binned_orders_point_one.aggregated_back_bets.begin();
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter != binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->amount_to_bet.value == 10000000);
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->backer_multiplier == 13 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
++aggregated_back_bets_iter;
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter != binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->amount_to_bet.value == 10000000);
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->backer_multiplier == 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
++aggregated_back_bets_iter;
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter != binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->amount_to_bet.value == 10000000);
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->backer_multiplier == 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
++aggregated_back_bets_iter;
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter == binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(binned_orders_point_one.aggregated_lay_bets.empty());
|
||||
|
||||
transfer(account_id_type(), bob_id, asset(1000000000));
|
||||
share_type bob_expected_balance = 1000000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(5000000, asset_id_type()), 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
ilog("Order books after bob's matching lay bet:");
|
||||
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
|
||||
while (bet_iter != bet_odds_idx.end() &&
|
||||
bet_iter->betting_market_id == capitals_win_market.id)
|
||||
{
|
||||
idump((*bet_iter));
|
||||
++bet_iter;
|
||||
}
|
||||
|
||||
bob_expected_balance -= 5000000 - 2000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance.value);
|
||||
|
||||
// check order books to see they match after bob's bet matched
|
||||
bet_iter = bet_odds_idx.lower_bound(std::make_tuple(capitals_win_market.id));
|
||||
BOOST_REQUIRE(bet_iter != bet_odds_idx.end());
|
||||
BOOST_REQUIRE(bet_iter->betting_market_id == capitals_win_market.id);
|
||||
BOOST_REQUIRE(bet_iter->bettor_id == alice_id);
|
||||
BOOST_REQUIRE(bet_iter->amount_to_bet == asset(10000000, asset_id_type()));
|
||||
BOOST_REQUIRE(bet_iter->backer_multiplier == 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
BOOST_REQUIRE(bet_iter->back_or_lay == bet_type::back);
|
||||
++bet_iter;
|
||||
BOOST_REQUIRE(bet_iter != bet_odds_idx.end());
|
||||
BOOST_REQUIRE(bet_iter->betting_market_id == capitals_win_market.id);
|
||||
BOOST_REQUIRE(bet_iter->bettor_id == alice_id);
|
||||
BOOST_REQUIRE(bet_iter->amount_to_bet == asset(10000000, asset_id_type()));
|
||||
BOOST_REQUIRE(bet_iter->backer_multiplier == 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
BOOST_REQUIRE(bet_iter->back_or_lay == bet_type::back);
|
||||
++bet_iter;
|
||||
BOOST_REQUIRE(bet_iter == bet_odds_idx.end() || bet_iter->betting_market_id != capitals_win_market.id);
|
||||
|
||||
// check the binned order books from the bookie plugin to make sure they match
|
||||
binned_orders_point_one = bookie_api.get_binned_order_book(capitals_win_market.id, 1);
|
||||
aggregated_back_bets_iter = binned_orders_point_one.aggregated_back_bets.begin();
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter != binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->amount_to_bet.value == 10000000);
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->backer_multiplier == 15 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
++aggregated_back_bets_iter;
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter != binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->amount_to_bet.value == 10000000);
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter->backer_multiplier == 16 * GRAPHENE_BETTING_ODDS_PRECISION / 10);
|
||||
++aggregated_back_bets_iter;
|
||||
BOOST_REQUIRE(aggregated_back_bets_iter == binned_orders_point_one.aggregated_back_bets.end());
|
||||
BOOST_REQUIRE(binned_orders_point_one.aggregated_lay_bets.empty());
|
||||
|
||||
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(inexact_odds)
|
||||
{
|
||||
try
|
||||
|
|
@ -442,6 +870,71 @@ BOOST_AUTO_TEST_CASE(inexact_odds)
|
|||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bet_reversal_test)
|
||||
{
|
||||
// test whether we can bet our entire balance in one direction, then reverse our bet (while having zero balance)
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
share_type alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance.value);
|
||||
|
||||
// back with our entire balance
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(10000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), 0);
|
||||
|
||||
// reverse the bet
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(20000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), 0);
|
||||
|
||||
// try to re-reverse it, but go too far
|
||||
BOOST_CHECK_THROW( place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(30000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION), fc::exception);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), 0);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(bet_against_exposure_test)
|
||||
{
|
||||
// test whether we can bet our entire balance in one direction, have it match, then reverse our bet (while having zero balance)
|
||||
try
|
||||
{
|
||||
generate_blocks(1);
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
transfer(account_id_type(), alice_id, asset(10000000));
|
||||
transfer(account_id_type(), bob_id, asset(10000000));
|
||||
int64_t alice_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance);
|
||||
int64_t bob_expected_balance = 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance);
|
||||
|
||||
// back with alice's entire balance
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(10000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
|
||||
alice_expected_balance -= 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance);
|
||||
|
||||
// lay with bob's entire balance, which fully matches bob's bet
|
||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(10000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
|
||||
bob_expected_balance -= 10000000;
|
||||
BOOST_REQUIRE_EQUAL(get_balance(bob_id, asset_id_type()), bob_expected_balance);
|
||||
|
||||
// reverse the bet
|
||||
place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(20000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance);
|
||||
|
||||
// try to re-reverse it, but go too far
|
||||
BOOST_CHECK_THROW( place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(30000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION), fc::exception);
|
||||
BOOST_REQUIRE_EQUAL(get_balance(alice_id, asset_id_type()), alice_expected_balance);
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(persistent_objects_test)
|
||||
{
|
||||
try
|
||||
|
|
@ -539,6 +1032,59 @@ BOOST_AUTO_TEST_CASE(persistent_objects_test)
|
|||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(test_settled_market_states)
|
||||
{
|
||||
try
|
||||
{
|
||||
ACTORS( (alice)(bob) );
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
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);
|
||||
|
||||
idump((capitals_win_market.get_status()));
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the event to in_progress");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::in_progress);
|
||||
generate_blocks(1);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the event to finished");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::finished);
|
||||
generate_blocks(1);
|
||||
|
||||
resolve_betting_market_group(moneyline_betting_markets.id,
|
||||
{{capitals_win_market.id, betting_market_resolution_type::win},
|
||||
{blackhawks_win_market.id, betting_market_resolution_type::not_win}});
|
||||
|
||||
// as soon as the market is resolved during the generate_block(), these markets
|
||||
// should be deleted and our references will go out of scope. Save the
|
||||
// market ids here so we can verify that they were really deleted
|
||||
betting_market_id_type capitals_win_market_id = capitals_win_market.id;
|
||||
betting_market_id_type blackhawks_win_market_id = blackhawks_win_market.id;
|
||||
|
||||
generate_blocks(1);
|
||||
|
||||
// test getting markets
|
||||
// test that we cannot get them from the database directly
|
||||
BOOST_CHECK_THROW(capitals_win_market_id(db), fc::exception);
|
||||
BOOST_CHECK_THROW(blackhawks_win_market_id(db), fc::exception);
|
||||
|
||||
fc::variants objects_from_bookie = bookie_api.get_objects({capitals_win_market_id, blackhawks_win_market_id});
|
||||
BOOST_REQUIRE_EQUAL(objects_from_bookie.size(), 2u);
|
||||
idump((objects_from_bookie));
|
||||
BOOST_CHECK(!objects_from_bookie[0].is_null());
|
||||
BOOST_CHECK(!objects_from_bookie[1].is_null());
|
||||
}
|
||||
FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
|
||||
BOOST_AUTO_TEST_CASE(delayed_bets_test) // test live betting
|
||||
{
|
||||
try
|
||||
|
|
@ -731,6 +1277,97 @@ BOOST_AUTO_TEST_CASE( chained_market_create_test )
|
|||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( testnet_witness_block_production_error )
|
||||
{
|
||||
try
|
||||
{
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
create_betting_market_group({{"en", "Unused"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type(), false, 0);
|
||||
generate_blocks(1);
|
||||
const betting_market_group_object& unused_betting_markets = *db.get_index_type<betting_market_group_object_index>().indices().get<by_id>().rbegin();
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the event in progress");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::in_progress);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::in_progress);
|
||||
BOOST_CHECK(moneyline_betting_markets.get_status() == betting_market_group_status::in_play);
|
||||
BOOST_CHECK(unused_betting_markets.get_status() == betting_market_group_status::in_play);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the event to finished");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::finished);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::finished);
|
||||
BOOST_CHECK(moneyline_betting_markets.get_status() == betting_market_group_status::closed);
|
||||
BOOST_CHECK(capitals_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(blackhawks_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(unused_betting_markets.get_status() == betting_market_group_status::closed);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the event to canceled");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::canceled);
|
||||
generate_blocks(1);
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( cancel_one_event_in_group )
|
||||
{
|
||||
// test that creates an event group with two events in it. We walk one event through the
|
||||
// usual sequence and cancel it, verify that it doesn't alter the other event in the group
|
||||
try
|
||||
{
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
|
||||
|
||||
// create a second event in the same betting market group
|
||||
create_event({{"en", "Boston Bruins/Pittsburgh Penguins"}}, {{"en", "2016-17"}}, nhl.id);
|
||||
generate_blocks(1);
|
||||
const event_object& bruins_vs_penguins = *db.get_index_type<event_object_index>().indices().get<by_id>().rbegin();
|
||||
create_betting_market_group({{"en", "Moneyline"}}, bruins_vs_penguins.id, betting_market_rules.id, asset_id_type(), false, 0);
|
||||
generate_blocks(1);
|
||||
const betting_market_group_object& bruins_penguins_moneyline_betting_markets = *db.get_index_type<betting_market_group_object_index>().indices().get<by_id>().rbegin();
|
||||
create_betting_market(bruins_penguins_moneyline_betting_markets.id, {{"en", "Boston Bruins win"}});
|
||||
generate_blocks(1);
|
||||
const betting_market_object& bruins_win_market = *db.get_index_type<betting_market_object_index>().indices().get<by_id>().rbegin();
|
||||
create_betting_market(bruins_penguins_moneyline_betting_markets.id, {{"en", "Pittsburgh Penguins win"}});
|
||||
generate_blocks(1);
|
||||
const betting_market_object& penguins_win_market = *db.get_index_type<betting_market_object_index>().indices().get<by_id>().rbegin();
|
||||
(void)bruins_win_market; (void)penguins_win_market;
|
||||
|
||||
// check the initial state
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::upcoming);
|
||||
BOOST_CHECK(bruins_vs_penguins.get_status() == event_status::upcoming);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the capitals_vs_blackhawks event to in-progress, leaving bruins_vs_penguins in upcoming");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::in_progress);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::in_progress);
|
||||
BOOST_CHECK(moneyline_betting_markets.get_status() == betting_market_group_status::in_play);
|
||||
|
||||
BOOST_CHECK(bruins_vs_penguins.get_status() == event_status::upcoming);
|
||||
BOOST_CHECK(bruins_penguins_moneyline_betting_markets.get_status() == betting_market_group_status::upcoming);
|
||||
BOOST_CHECK(bruins_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(penguins_win_market.get_status() == betting_market_status::unresolved);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the capitals_vs_blackhawks event to finished");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::finished);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::finished);
|
||||
BOOST_CHECK(moneyline_betting_markets.get_status() == betting_market_group_status::closed);
|
||||
BOOST_CHECK(capitals_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(blackhawks_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(bruins_vs_penguins.get_status() == event_status::upcoming);
|
||||
BOOST_CHECK(bruins_penguins_moneyline_betting_markets.get_status() == betting_market_group_status::upcoming);
|
||||
BOOST_CHECK(bruins_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(penguins_win_market.get_status() == betting_market_status::unresolved);
|
||||
|
||||
BOOST_TEST_MESSAGE("setting the capitals_vs_blackhawks event to canceled");
|
||||
update_event(capitals_vs_blackhawks.id, _status = event_status::canceled);
|
||||
generate_blocks(1);
|
||||
BOOST_CHECK(bruins_vs_penguins.get_status() == event_status::upcoming);
|
||||
BOOST_CHECK(bruins_penguins_moneyline_betting_markets.get_status() == betting_market_group_status::upcoming);
|
||||
BOOST_CHECK(bruins_win_market.get_status() == betting_market_status::unresolved);
|
||||
BOOST_CHECK(penguins_win_market.get_status() == betting_market_status::unresolved);
|
||||
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
||||
|
|
@ -1141,8 +1778,13 @@ BOOST_AUTO_TEST_CASE(event_driven_standard_progression_1_with_delay)
|
|||
{
|
||||
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 60 /* seconds */);
|
||||
graphene::bookie::bookie_api bookie_api(app);
|
||||
// save the event id for checking after it is deleted
|
||||
|
||||
// save the ids for checking after it is deleted
|
||||
event_id_type capitals_vs_blackhawks_id = capitals_vs_blackhawks.id;
|
||||
betting_market_group_id_type moneyline_betting_markets_id = moneyline_betting_markets.id;
|
||||
betting_market_id_type capitals_win_market_id = capitals_win_market.id;
|
||||
betting_market_id_type blackhawks_win_market_id = blackhawks_win_market.id;
|
||||
|
||||
|
||||
BOOST_TEST_MESSAGE("verify everything is in the correct initial state");
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::upcoming);
|
||||
|
|
@ -1163,17 +1805,31 @@ BOOST_AUTO_TEST_CASE(event_driven_standard_progression_1_with_delay)
|
|||
{{capitals_win_market.id, betting_market_resolution_type::win},
|
||||
{blackhawks_win_market.id, betting_market_resolution_type::not_win}});
|
||||
generate_blocks(1);
|
||||
|
||||
// it should be waiting 60 seconds before it settles
|
||||
BOOST_CHECK(capitals_vs_blackhawks.get_status() == event_status::finished);
|
||||
BOOST_CHECK(moneyline_betting_markets.get_status() == betting_market_group_status::graded);
|
||||
BOOST_CHECK(capitals_win_market.get_status() == betting_market_status::graded);
|
||||
BOOST_CHECK(capitals_win_market.resolution == betting_market_resolution_type::win);
|
||||
BOOST_CHECK(blackhawks_win_market.get_status() == betting_market_status::graded);
|
||||
BOOST_CHECK(blackhawks_win_market.resolution == betting_market_resolution_type::not_win);
|
||||
|
||||
generate_blocks(60);
|
||||
// as soon as a block is generated, the betting market group will settle, and the market
|
||||
// and group will cease to exist. The event should transition to "settled", then
|
||||
// removed.
|
||||
fc::variants objects_from_bookie = bookie_api.get_objects({capitals_vs_blackhawks_id});
|
||||
fc::variants objects_from_bookie = bookie_api.get_objects({capitals_vs_blackhawks_id,
|
||||
moneyline_betting_markets_id,
|
||||
capitals_win_market_id,
|
||||
blackhawks_win_market_id});
|
||||
|
||||
idump((objects_from_bookie));
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[0]["status"].as<std::string>(), "settled");
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[1]["status"].as<std::string>(), "settled");
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[2]["status"].as<std::string>(), "settled");
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[2]["resolution"].as<std::string>(), "win");
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[3]["status"].as<std::string>(), "settled");
|
||||
BOOST_CHECK_EQUAL(objects_from_bookie[3]["resolution"].as<std::string>(), "not_win");
|
||||
} FC_LOG_AND_RETHROW()
|
||||
}
|
||||
|
||||
|
|
@ -1975,6 +2631,10 @@ BOOST_AUTO_TEST_SUITE_END()
|
|||
boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) {
|
||||
std::srand(time(NULL));
|
||||
std::cout << "Random number generator seeded to " << time(NULL) << std::endl;
|
||||
|
||||
// betting operations don't take effect until HARDFORK 1000
|
||||
GRAPHENE_TESTING_GENESIS_TIMESTAMP = HARDFORK_1000_TIME.sec_since_epoch();
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1150,6 +1150,45 @@ void database_fixture::process_operation_by_witnesses(operation op)
|
|||
}
|
||||
}
|
||||
|
||||
void database_fixture::process_operation_by_committee(operation op)
|
||||
{
|
||||
const vector<committee_member_id_type>& active_committee_members = db.get_global_properties().active_committee_members;
|
||||
|
||||
proposal_create_operation proposal_op;
|
||||
proposal_op.fee_paying_account = (*active_committee_members.begin())(db).committee_member_account;
|
||||
proposal_op.proposed_ops.emplace_back(op);
|
||||
proposal_op.expiration_time = db.head_block_time() + fc::days(1);
|
||||
|
||||
signed_transaction tx;
|
||||
tx.operations.push_back(proposal_op);
|
||||
set_expiration(db, tx);
|
||||
sign(tx, init_account_priv_key);
|
||||
|
||||
processed_transaction processed_tx = db.push_transaction(tx);
|
||||
proposal_id_type proposal_id = processed_tx.operation_results[0].get<object_id_type>();
|
||||
|
||||
for (const committee_member_id_type& committee_member_id : active_committee_members)
|
||||
{
|
||||
const committee_member_object& committee_member = committee_member_id(db);
|
||||
const account_object& committee_member_account = committee_member.committee_member_account(db);
|
||||
|
||||
proposal_update_operation pup;
|
||||
pup.proposal = proposal_id;
|
||||
pup.fee_paying_account = committee_member_account.id;
|
||||
pup.active_approvals_to_add.insert(committee_member_account.id);
|
||||
|
||||
signed_transaction tx;
|
||||
tx.operations.push_back( pup );
|
||||
set_expiration( db, tx );
|
||||
sign(tx, init_account_priv_key);
|
||||
|
||||
db.push_transaction(tx, ~0);
|
||||
const auto& proposal_idx = db.get_index_type<proposal_index>().indices().get<by_id>();
|
||||
if (proposal_idx.find(proposal_id) == proposal_idx.end())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void database_fixture::force_operation_by_witnesses(operation op)
|
||||
{
|
||||
const chain_parameters& params = db.get_global_properties().parameters;
|
||||
|
|
|
|||
|
|
@ -299,6 +299,7 @@ struct database_fixture {
|
|||
asset_id_type dividend_payout_asset_type) const;
|
||||
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
|
||||
void process_operation_by_witnesses(operation op);
|
||||
void process_operation_by_committee(operation op);
|
||||
void force_operation_by_witnesses(operation op);
|
||||
void set_is_proposed_trx(operation op);
|
||||
const sport_object& create_sport(internationalized_string_type name);
|
||||
|
|
|
|||
|
|
@ -1287,6 +1287,7 @@ BOOST_AUTO_TEST_CASE( test_update_dividend_interval )
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution )
|
||||
{
|
||||
using namespace graphene;
|
||||
|
|
@ -1420,6 +1421,177 @@ BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution )
|
|||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution_to_core_asset )
|
||||
{
|
||||
using namespace graphene;
|
||||
try {
|
||||
BOOST_TEST_MESSAGE("Creating test accounts");
|
||||
create_account("alice");
|
||||
create_account("bob");
|
||||
create_account("carol");
|
||||
create_account("dave");
|
||||
create_account("frank");
|
||||
|
||||
BOOST_TEST_MESSAGE("Creating test asset");
|
||||
{
|
||||
asset_create_operation creator;
|
||||
creator.issuer = account_id_type();
|
||||
creator.fee = asset();
|
||||
creator.symbol = "TEST";
|
||||
creator.common_options.max_supply = 100000000;
|
||||
creator.precision = 2;
|
||||
creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/
|
||||
creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;
|
||||
creator.common_options.flags = charge_market_fee;
|
||||
creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))});
|
||||
trx.operations.push_back(std::move(creator));
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
generate_block();
|
||||
|
||||
const auto& dividend_holder_asset_object = asset_id_type(0)(db);
|
||||
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||
const account_object& alice = get_account("alice");
|
||||
const account_object& bob = get_account("bob");
|
||||
const account_object& carol = get_account("carol");
|
||||
const account_object& dave = get_account("dave");
|
||||
const account_object& frank = get_account("frank");
|
||||
const auto& test_asset_object = get_asset("TEST");
|
||||
|
||||
auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue)
|
||||
{
|
||||
asset_issue_operation op;
|
||||
op.issuer = asset_to_issue.issuer;
|
||||
op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id);
|
||||
op.issue_to_account = destination_account.id;
|
||||
trx.operations.push_back( op );
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
};
|
||||
|
||||
auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) {
|
||||
int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id,
|
||||
holder_account_obj.id,
|
||||
payout_asset_obj.id);
|
||||
BOOST_CHECK_EQUAL(pending_balance, expected_balance);
|
||||
};
|
||||
|
||||
auto advance_to_next_payout_time = [&]() {
|
||||
// Advance to the next upcoming payout time
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
idump((next_payout_scheduled_time));
|
||||
// generate blocks up to the next scheduled time
|
||||
generate_blocks(next_payout_scheduled_time);
|
||||
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||
if (dividend_data.options.next_payout_time)
|
||||
{
|
||||
// we know there was a next_payout_time set when we entered this, so if
|
||||
// it has been cleared, we must have already processed payouts, no need to
|
||||
// further advance time.
|
||||
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
}
|
||||
idump((db.head_block_time()));
|
||||
};
|
||||
|
||||
// the first test will be testing pending balances, so we need to hit a
|
||||
// maintenance interval that isn't the payout interval. Payout is
|
||||
// every 3 days, maintenance interval is every 1 day.
|
||||
advance_to_next_payout_time();
|
||||
|
||||
// Set up the first test, issue alice, bob, and carol, and dave each 1/4 of the total
|
||||
// supply of the core asset.
|
||||
// Then deposit 400 TEST in the distribution account, and see that they
|
||||
// each are credited 100 TEST.
|
||||
transfer( committee_account(db), alice, asset( 250000000000000 ) );
|
||||
transfer( committee_account(db), bob, asset( 250000000000000 ) );
|
||||
transfer( committee_account(db), carol, asset( 250000000000000 ) );
|
||||
transfer( committee_account(db), dave, asset( 250000000000000 ) );
|
||||
|
||||
BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account");
|
||||
issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000);
|
||||
|
||||
generate_block();
|
||||
|
||||
BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
|
||||
verify_pending_balance(alice, test_asset_object, 10000);
|
||||
verify_pending_balance(bob, test_asset_object, 10000);
|
||||
verify_pending_balance(carol, test_asset_object, 10000);
|
||||
verify_pending_balance(dave, test_asset_object, 10000);
|
||||
|
||||
// For the second test, issue dave more than the other two, so it's
|
||||
// alice: 1/5 CORE, bob: 1/5 CORE, carol: 1/5 CORE, dave: 2/5 CORE
|
||||
// Then deposit 500 TEST in the distribution account, and see that alice
|
||||
// bob, and carol are credited with 100 TEST, and dave gets 200 TEST
|
||||
BOOST_TEST_MESSAGE("Issuing dave twice as much of the holder asset");
|
||||
transfer( alice, dave, asset( 50000000000000 ) );
|
||||
transfer( bob, dave, asset( 50000000000000 ) );
|
||||
transfer( carol, dave, asset( 50000000000000 ) );
|
||||
issue_asset_to_account(test_asset_object, dividend_distribution_account, 50000); // 500 at two digits of precision
|
||||
BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" );
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||
generate_block(); // get the maintenance skip slots out of the way
|
||||
verify_pending_balance(alice, test_asset_object, 20000);
|
||||
verify_pending_balance(bob, test_asset_object, 20000);
|
||||
verify_pending_balance(carol, test_asset_object, 20000);
|
||||
verify_pending_balance(dave, test_asset_object, 30000);
|
||||
|
||||
fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||
advance_to_next_payout_time();
|
||||
|
||||
|
||||
BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled");
|
||||
BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time,
|
||||
"New payout was scheduled for the same time as the last payout");
|
||||
BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time,
|
||||
"New payout was not scheduled for the expected time");
|
||||
|
||||
auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout)
|
||||
{
|
||||
BOOST_TEST_MESSAGE("Verifying the virtual op was created");
|
||||
const account_transaction_history_index& hist_idx = db.get_index_type<account_transaction_history_index>();
|
||||
auto account_history_range = hist_idx.indices().get<by_seq>().equal_range(boost::make_tuple(destination_account.id));
|
||||
BOOST_REQUIRE(account_history_range.first != account_history_range.second);
|
||||
const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db);
|
||||
const asset_dividend_distribution_operation& distribution_operation = history_object.op.get<asset_dividend_distribution_operation>();
|
||||
BOOST_CHECK(distribution_operation.account_id == destination_account.id);
|
||||
BOOST_CHECK(distribution_operation.amounts.find(expected_payout) != distribution_operation.amounts.end());
|
||||
};
|
||||
|
||||
BOOST_TEST_MESSAGE("Verifying the payouts");
|
||||
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000);
|
||||
verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id));
|
||||
verify_pending_balance(alice, test_asset_object, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000);
|
||||
verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id));
|
||||
verify_pending_balance(bob, test_asset_object, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 20000);
|
||||
verify_dividend_payout_operations(carol, asset(20000, test_asset_object.id));
|
||||
verify_pending_balance(carol, test_asset_object, 0);
|
||||
|
||||
BOOST_CHECK_EQUAL(get_balance(dave, test_asset_object), 30000);
|
||||
verify_dividend_payout_operations(dave, asset(30000, test_asset_object.id));
|
||||
verify_pending_balance(dave, test_asset_object, 0);
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval )
|
||||
{
|
||||
using namespace graphene;
|
||||
|
|
|
|||
Loading…
Reference in a new issue