Create a bunch of utility functions to make writing betting market tests easier

This commit is contained in:
Eric Frias 2017-03-31 11:10:37 -04:00
parent 69192a889d
commit 39093df26c
4 changed files with 189 additions and 102 deletions

View file

@ -13,7 +13,7 @@ void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
asset amount_to_refund = bet.amount_to_bet;
amount_to_refund += bet.amount_reserved_for_fees;
//TODO: update global statistics
adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake
adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake + fees
//TODO: do special fee accounting as required
if (create_virtual_op)
push_applied_operation(bet_canceled_operation(bet.bettor_id, bet.id,
@ -157,7 +157,10 @@ share_type adjust_betting_position(database& db, account_id_type bettor_id, bett
} FC_CAPTURE_AND_RETHROW((bettor_id)(betting_market_id)(bet_amount)) }
bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet, share_type amount_matched, bet_multiplier_type actual_multiplier, bool cull_if_small)
// called twice when a bet is matched, once for the taker, once for the maker
bool bet_was_matched(database& db, const bet_object& bet,
share_type amount_bet, share_type amount_matched,
bet_multiplier_type actual_multiplier, bool cull_if_small)
{
// calculate the percentage fee paid
fc::uint128_t percentage_fee_128 = bet.amount_reserved_for_fees.value;
@ -191,6 +194,8 @@ bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet,
bet_obj.amount_to_bet -= asset_amount_bet;
bet_obj.amount_reserved_for_fees -= fee_paid;
});
//TODO: cull_if_small is currently always true, remove the parameter if we don't find a
// need for it soon
if (cull_if_small)
return maybe_cull_small_bet(db, bet);
return false;
@ -202,10 +207,10 @@ bool bet_was_matched(database& db, const bet_object& bet, share_type amount_bet,
*
* @return a bit field indicating which orders were filled (and thus removed)
*
* 0 - no orders were matched
* 1 - bid was filled
* 2 - ask was filled
* 3 - both were filled
* 0 - no bet was matched (this will never happen)
* 1 - taker_bet was filled and removed from the books
* 2 - maker_bet was filled and removed from the books
* 3 - both were filled and removed from the books
*/
int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker_bet )
{
@ -244,11 +249,6 @@ bool database::place_bet(const bet_object& new_bet_object)
const auto& bet_odds_idx = get_index_type<bet_object_index>().indices().get<by_odds>();
// TODO: it should be possible to simply check the NEXT/PREV iterator after new_order_object to
// determine whether or not this order has "changed the book" in a way that requires us to
// check orders. For now I just lookup the lower bound and check for equality... this is log(n) vs
// constant time check. Potential optimization.
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));

View file

@ -37,6 +37,11 @@
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/competitor_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/utilities/tempdir.hpp>
@ -1067,6 +1072,123 @@ vector< operation_history_object > database_fixture::get_operation_history( acco
return result;
}
void database_fixture::process_operation_by_witnesses(operation op)
{
const flat_set<witness_id_type>& active_witnesses = db.get_global_properties().active_witnesses;
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = (*active_witnesses.begin())(db).witness_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 witness_id_type& witness_id : active_witnesses)
{
const witness_object& witness = witness_id(db);
const account_object& witness_account = witness.witness_account(db);
proposal_update_operation pup;
pup.proposal = proposal_id;
pup.fee_paying_account = witness_account.id;
pup.active_approvals_to_add.insert(witness_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;
}
}
const sport_object& database_fixture::create_sport(internationalized_string_type name)
{ try {
sport_create_operation sport_create_op;
sport_create_op.name = name;
process_operation_by_witnesses(sport_create_op);
const auto& sport_index = db.get_index_type<sport_object_index>().indices().get<by_id>();
return *sport_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (name) ) }
const competitor_object& database_fixture::create_competitor(internationalized_string_type name, sport_id_type sport_id)
{ try {
competitor_create_operation competitor_create_op;
competitor_create_op.name = name;
competitor_create_op.sport_id = sport_id;
process_operation_by_witnesses(competitor_create_op);
const auto& competitor_index = db.get_index_type<competitor_object_index>().indices().get<by_id>();
return *competitor_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (name) ) }
const event_group_object& database_fixture::create_event_group(internationalized_string_type name, sport_id_type sport_id)
{ try {
event_group_create_operation event_group_create_op;
event_group_create_op.name = name;
event_group_create_op.sport_id = sport_id;
process_operation_by_witnesses(event_group_create_op);
const auto& event_group_index = db.get_index_type<event_group_object_index>().indices().get<by_id>();
return *event_group_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (name) ) }
const event_object& database_fixture::create_event(internationalized_string_type season, event_group_id_type event_group_id, vector<competitor_id_type> competitors)
{ try {
event_create_operation event_create_op;
event_create_op.season = season;
event_create_op.event_group_id = event_group_id;
event_create_op.competitors.assign(competitors.begin(), competitors.end());
process_operation_by_witnesses(event_create_op);
const auto& event_index = db.get_index_type<event_object_index>().indices().get<by_id>();
return *event_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (event_group_id) ) }
const betting_market_group_object& database_fixture::create_betting_market_group(event_id_type event_id, betting_market_options_type options)
{ try {
betting_market_group_create_operation betting_market_group_create_op;
betting_market_group_create_op.event_id = event_id;
betting_market_group_create_op.options = options;
process_operation_by_witnesses(betting_market_group_create_op);
const auto& betting_market_group_index = db.get_index_type<betting_market_group_object_index>().indices().get<by_id>();
return *betting_market_group_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (event_id) ) }
const betting_market_object& database_fixture::create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id)
{ try {
betting_market_create_operation betting_market_create_op;
betting_market_create_op.group_id = group_id;
betting_market_create_op.payout_condition = payout_condition;
betting_market_create_op.asset_id = asset_id;
process_operation_by_witnesses(betting_market_create_op);
const auto& betting_market_index = db.get_index_type<betting_market_object_index>().indices().get<by_id>();
return *betting_market_index.rbegin();
} FC_CAPTURE_AND_RETHROW( (payout_condition) ) }
void database_fixture::place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees)
{ try {
bet_place_operation bet_place_op;
bet_place_op.bettor_id = bettor_id;
bet_place_op.betting_market_id = betting_market_id;
bet_place_op.amount_to_bet = amount_to_bet;
bet_place_op.backer_multiplier = backer_multiplier;
bet_place_op.amount_reserved_for_fees = amount_reserved_for_fees;
bet_place_op.back_or_lay = back_or_lay;
trx.operations.push_back(bet_place_op);
trx.validate();
processed_transaction ptx = db.push_transaction(trx, ~0);
trx.operations.clear();
} FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) }
namespace test {
void set_expiration( const database& db, transaction& tx )
@ -1074,7 +1196,6 @@ void set_expiration( const database& db, transaction& tx )
const chain_parameters& params = db.get_global_properties().parameters;
tx.set_reference_block(db.head_block_id());
tx.set_expiration( db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3 ) );
return;
}
bool _push_block( database& db, const signed_block& b, uint32_t skip_flags /* = 0 */ )

View file

@ -281,6 +281,15 @@ struct database_fixture {
int64_t get_balance( account_id_type account, asset_id_type a )const;
int64_t get_balance( const account_object& account, const asset_object& a )const;
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
void process_operation_by_witnesses(operation op);
const sport_object& create_sport(internationalized_string_type name);
const competitor_object& create_competitor(internationalized_string_type name, sport_id_type sport_id);
const event_group_object& create_event_group(internationalized_string_type name, sport_id_type sport_id);
const event_object& create_event(internationalized_string_type season, event_group_id_type event_group_id, vector<competitor_id_type> competitors);
const betting_market_group_object& create_betting_market_group(event_id_type event_id, betting_market_options_type options);
const betting_market_object& create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id);
void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees);
};
namespace test {

View file

@ -39,6 +39,8 @@
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/competitor_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
@ -1624,7 +1626,52 @@ BOOST_AUTO_TEST_CASE( buyback )
} FC_LOG_AND_RETHROW()
}
#define CREATE_ICE_HOCKEY_BETTING_MARKET() \
const sport_object& ice_hockey = create_sport({{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \
const competitor_object& capitals = create_competitor({{"en", "Washington Capitals"}, {"zh_Hans", "華盛頓首都隊"}, {"ja", "ワシントン・キャピタルズ"}}, ice_hockey.id); \
const competitor_object& blackhawks = create_competitor({{"en", "Chicago Blackhawks"}, {"zh_Hans", "芝加哥黑鷹"}, {"ja", "シカゴ・ブラックホークス"}}, ice_hockey.id); \
const event_group_object& nhl = create_event_group({{"en", "NHL"}, {"zh_Hans", "國家冰球聯盟"}, {"ja", "ナショナルホッケーリーグ"}}, ice_hockey.id); \
const event_object& capitals_vs_blackhawks = create_event({{"en", "2016-17"}}, nhl.id, {capitals.id, blackhawks.id}); \
const betting_market_group_object& moneyline_betting_markets = create_betting_market_group(capitals_vs_blackhawks.id, moneyline_market_options{}); \
const betting_market_object& capitals_win_market = create_betting_market(moneyline_betting_markets.id, {{"en", "Washington Capitals win"}}, asset_id_type()); \
const betting_market_object& blackhawks_win_market = create_betting_market(moneyline_betting_markets.id, {{"en", "Chicago Blackhawks win"}}, asset_id_type());
BOOST_AUTO_TEST_CASE( peerplays_sport_create_test )
{
try
{
ACTORS( (alice)(bob) );
CREATE_ICE_HOCKEY_BETTING_MARKET();
// give alice and bob 10M each
transfer(account_id_type(), alice_id, asset(10000000));
transfer(account_id_type(), bob_id, asset(10000000));
// have bob lay a bet for 1M (+20k fees) at 1:1 odds
place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */);
// have alice back a matching bet at 1:1 odds (also costing 1.02M)
place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */);
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
// caps win
{
betting_market_resolve_operation betting_market_resolve_op;
betting_market_resolve_op.betting_market_id = capitals_win_market.id;
betting_market_resolve_op.resolution = betting_market_resolution_type::win;
process_operation_by_witnesses(betting_market_resolve_op);
}
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE( chained_market_create_test )
{
ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin) );
@ -1801,96 +1848,6 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test )
}
}
// give alice and bob 10M each
transfer(account_id_type(), alice_id, asset(10000000));
transfer(account_id_type(), bob_id, asset(10000000));
{
// get a betting market to run tests in. It doesn't relly matter what it is, but in this test it will be "caps win".
const betting_market_object& market = *db.get_index_type<betting_market_object_index>().indices().begin();
{
// have bob lay a bet for 1M (+20k fees) at 1:1 odds
signed_transaction tx;
bet_place_operation bet_op;
bet_op.bettor_id = bob_id;
bet_op.betting_market_id = market.id;
bet_op.amount_to_bet = asset(1000000, asset_id_type());
bet_op.backer_multiplier = 2 * GRAPHENE_BETTING_ODDS_PRECISION;
bet_op.amount_reserved_for_fees = 1000000 / 50; // chain defaults to 2% fees
bet_op.back_or_lay = bet_type::lay;
tx.operations.push_back(bet_op);
db.current_fee_schedule().set_fee(tx.operations.back());
set_expiration(db, tx);
sign(tx, bob_private_key);
db.push_transaction(tx);
}
{
// have alice back a matching bet at 1:1 odds (also costing 1.02M)
signed_transaction tx;
bet_place_operation bet_op;
bet_op.bettor_id = alice_id;
bet_op.betting_market_id = market.id;
bet_op.amount_to_bet = asset(1000000, asset_id_type());
bet_op.backer_multiplier = 2 * GRAPHENE_BETTING_ODDS_PRECISION;
bet_op.amount_reserved_for_fees = 1000000 / 50; // chain defaults to 2% fees
bet_op.back_or_lay = bet_type::back;
tx.operations.push_back(bet_op);
db.current_fee_schedule().set_fee(tx.operations.back());
set_expiration(db, tx);
sign(tx, alice_private_key);
db.push_transaction(tx);
}
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
// caps win
{
proposal_create_operation proposal_op;
proposal_op.fee_paying_account = (*active_witnesses.begin())(db).witness_account;
betting_market_resolve_operation betting_market_resolve_op;
betting_market_resolve_op.betting_market_id = market.id;
betting_market_resolve_op.resolution = betting_market_resolution_type::win;
proposal_op.proposed_ops.emplace_back(betting_market_resolve_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);
db.push_transaction(tx);
}
BOOST_REQUIRE_EQUAL(db.get_index_type<proposal_index>().indices().size(), 1);
{
const proposal_object& prop = *db.get_index_type<proposal_index>().indices().begin();
for (const witness_id_type& witness_id : active_witnesses)
{
BOOST_TEST_MESSAGE("Approving market resolve witness " << fc::variant(witness_id).as<std::string>());
const witness_object& witness = witness_id(db);
const account_object& witness_account = witness.witness_account(db);
proposal_update_operation pup;
pup.proposal = prop.id;
pup.fee_paying_account = witness_account.id;
pup.active_approvals_to_add.insert(witness_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);
if (db.get_index_type<proposal_index>().indices().size() == 0)
break;
}
}
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000);
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
}
}
} FC_LOG_AND_RETHROW()