2018-10-09 14:33:31 +00:00
/*
* Copyright ( c ) 2018 Peerplays Blockchain Standards Association , and contributors .
*
* The MIT License
*
* Permission is hereby granted , free of charge , to any person obtaining a copy
* of this software and associated documentation files ( the " Software " ) , to deal
* in the Software without restriction , including without limitation the rights
* to use , copy , modify , merge , publish , distribute , sublicense , and / or sell
* copies of the Software , and to permit persons to whom the Software is
* furnished to do so , subject to the following conditions :
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software .
*
* THE SOFTWARE IS PROVIDED " AS IS " , WITHOUT WARRANTY OF ANY KIND , EXPRESS OR
* IMPLIED , INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY ,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT . IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM , DAMAGES OR OTHER
* LIABILITY , WHETHER IN AN ACTION OF CONTRACT , TORT OR OTHERWISE , ARISING FROM ,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE .
*/
2017-03-22 16:03:08 +00:00
# include <graphene/chain/database.hpp>
# include <graphene/chain/account_object.hpp>
2018-04-10 14:19:15 +00:00
# include <graphene/chain/affiliate_payout.hpp>
2017-03-22 16:03:08 +00:00
# include <graphene/chain/asset_object.hpp>
# include <graphene/chain/betting_market_object.hpp>
2017-03-24 05:33:05 +00:00
# include <graphene/chain/event_object.hpp>
2017-03-22 16:03:08 +00:00
2017-03-24 15:51:35 +00:00
# include <boost/range/iterator_range.hpp>
2018-02-07 15:12:58 +00:00
# include <boost/range/combine.hpp>
2018-04-17 17:17:38 +00:00
# include <boost/range/join.hpp>
2018-02-07 15:12:58 +00:00
# include <boost/tuple/tuple.hpp>
2017-03-24 15:51:35 +00:00
2017-03-22 16:03:08 +00:00
namespace graphene { namespace chain {
void database : : cancel_bet ( const bet_object & bet , bool create_virtual_op )
{
2017-03-22 19:04:11 +00:00
asset amount_to_refund = bet . amount_to_bet ;
2017-03-22 16:03:08 +00:00
//TODO: update global statistics
2017-08-09 15:12:59 +00:00
adjust_balance ( bet . bettor_id , amount_to_refund ) ;
2017-03-22 16:03:08 +00:00
if ( create_virtual_op )
2017-08-01 19:40:49 +00:00
{
bet_canceled_operation bet_canceled_virtual_op ( bet . bettor_id , bet . id ,
2017-08-09 15:12:59 +00:00
bet . amount_to_bet ) ;
2018-05-09 19:41:48 +00:00
//fc_idump(fc::logger::get("betting"), (bet_canceled_virtual_op));
2017-08-01 19:40:49 +00:00
push_applied_operation ( std : : move ( bet_canceled_virtual_op ) ) ;
}
2017-03-22 16:03:08 +00:00
remove ( bet ) ;
}
2017-03-24 04:09:43 +00:00
void database : : cancel_all_unmatched_bets_on_betting_market ( const betting_market_object & betting_market )
{
2017-03-24 14:40:53 +00:00
const auto & bet_odds_idx = get_index_type < bet_object_index > ( ) . indices ( ) . get < by_odds > ( ) ;
2017-08-15 22:44:09 +00:00
// first, cancel all bets on the active books
2017-03-24 14:40:53 +00:00
auto book_itr = bet_odds_idx . lower_bound ( std : : make_tuple ( betting_market . id ) ) ;
auto book_end = bet_odds_idx . upper_bound ( std : : make_tuple ( betting_market . id ) ) ;
while ( book_itr ! = book_end )
{
auto old_book_itr = book_itr ;
+ + book_itr ;
cancel_bet ( * old_book_itr , true ) ;
}
2017-08-15 22:44:09 +00:00
// then, cancel any delayed bets on that market. We don't have an index for
// that, so walk through all delayed bets
book_itr = bet_odds_idx . begin ( ) ;
while ( book_itr ! = bet_odds_idx . end ( ) & &
book_itr - > end_of_delay )
{
auto old_book_itr = book_itr ;
+ + book_itr ;
if ( old_book_itr - > betting_market_id = = betting_market . id )
cancel_bet ( * old_book_itr , true ) ;
}
2017-03-24 04:09:43 +00:00
}
2018-02-07 15:12:58 +00:00
void database : : validate_betting_market_group_resolutions ( const betting_market_group_object & betting_market_group ,
const std : : map < betting_market_id_type , betting_market_resolution_type > & resolutions )
2017-03-24 05:33:05 +00:00
{
2017-03-24 15:51:35 +00:00
auto & betting_market_index = get_index_type < betting_market_object_index > ( ) . indices ( ) . get < by_betting_market_group_id > ( ) ;
2018-02-07 15:12:58 +00:00
auto betting_markets_in_group = boost : : make_iterator_range ( betting_market_index . equal_range ( betting_market_group . id ) ) ;
2017-03-24 15:51:35 +00:00
2018-02-07 15:12:58 +00:00
// we must have one resolution for each betting market
FC_ASSERT ( resolutions . size ( ) = = boost : : size ( betting_markets_in_group ) ,
" You must publish resolutions for all ${size} markets in the group, you published ${published} " , ( " size " , boost : : size ( betting_markets_in_group ) ) ( " published " , resolutions . size ( ) ) ) ;
2017-03-24 05:33:05 +00:00
2018-02-07 15:12:58 +00:00
// both are sorted by id, we can walk through both and verify that they match
unsigned number_of_wins = 0 ;
unsigned number_of_cancels = 0 ;
for ( const auto & zipped : boost : : combine ( resolutions , betting_markets_in_group ) )
{
const auto & resolution = boost : : get < 0 > ( zipped ) ;
const auto & betting_market = boost : : get < 1 > ( zipped ) ;
FC_ASSERT ( resolution . first = = betting_market . id , " Missing resolution for betting market ${id} " , ( " id " , betting_market . id ) ) ;
if ( resolution . second = = betting_market_resolution_type : : cancel )
+ + number_of_cancels ;
else if ( resolution . second = = betting_market_resolution_type : : win )
+ + number_of_wins ;
else
FC_ASSERT ( resolution . second = = betting_market_resolution_type : : not_win ) ;
}
if ( number_of_cancels ! = 0 )
FC_ASSERT ( number_of_cancels = = resolutions . size ( ) , " You must cancel all betting markets or none of the betting markets in the group " ) ;
else
FC_ASSERT ( number_of_wins = = 1 , " There must be exactly one winning market " ) ;
2017-07-04 14:55:34 +00:00
}
2017-03-24 04:09:43 +00:00
2017-07-19 08:29:51 +00:00
void database : : cancel_all_unmatched_bets_on_betting_market_group ( const betting_market_group_object & betting_market_group )
{
auto & betting_market_index = get_index_type < betting_market_object_index > ( ) . indices ( ) . get < by_betting_market_group_id > ( ) ;
auto betting_market_itr = betting_market_index . lower_bound ( betting_market_group . id ) ;
while ( betting_market_itr ! = betting_market_index . end ( ) & & betting_market_itr - > group_id = = betting_market_group . id )
{
const betting_market_object & betting_market = * betting_market_itr ;
+ + betting_market_itr ;
cancel_all_unmatched_bets_on_betting_market ( betting_market ) ;
}
}
2017-07-04 14:55:34 +00:00
void database : : resolve_betting_market_group ( const betting_market_group_object & betting_market_group ,
2018-02-07 15:12:58 +00:00
const std : : map < betting_market_id_type , betting_market_resolution_type > & resolutions )
2017-07-04 14:55:34 +00:00
{
2018-02-07 15:12:58 +00:00
auto & betting_market_index = get_index_type < betting_market_object_index > ( ) . indices ( ) . get < by_betting_market_group_id > ( ) ;
auto betting_markets_in_group = boost : : make_iterator_range ( betting_market_index . equal_range ( betting_market_group . id ) ) ;
bool group_was_canceled = resolutions . begin ( ) - > second = = betting_market_resolution_type : : cancel ;
if ( group_was_canceled )
modify ( betting_market_group , [ group_was_canceled , this ] ( betting_market_group_object & betting_market_group_obj ) {
betting_market_group_obj . on_canceled_event ( * this , false ) ; // this cancels the betting markets
} ) ;
else {
// TODO: this should be pushed into the bmg's on_graded_event
// both are sorted by id, we can walk through both and verify that they match
for ( const auto & zipped : boost : : combine ( resolutions , betting_markets_in_group ) )
{
const auto & resolution = boost : : get < 0 > ( zipped ) ;
const auto & betting_market = boost : : get < 1 > ( zipped ) ;
modify ( betting_market , [ this , & resolution ] ( betting_market_object & betting_market_obj ) {
betting_market_obj . on_graded_event ( * this , resolution . second ) ;
} ) ;
}
modify ( betting_market_group , [ group_was_canceled , this ] ( betting_market_group_object & betting_market_group_obj ) {
betting_market_group_obj . on_graded_event ( * this ) ;
} ) ;
}
}
2017-07-09 22:39:53 +00:00
2018-02-07 15:12:58 +00:00
void database : : settle_betting_market_group ( const betting_market_group_object & betting_market_group )
{
2018-05-09 19:41:48 +00:00
fc_ilog ( fc : : logger : : get ( " betting " ) , " Settling betting market group ${id} " , ( " id " , betting_market_group . id ) ) ;
2017-07-09 22:39:53 +00:00
// 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 ;
const asset_object & core_asset_obj = asset_id_type ( 0 ) ( * this ) ;
if ( core_asset_obj . dividend_data_id )
{
const asset_dividend_data_object & core_asset_dividend_data_obj = ( * core_asset_obj . dividend_data_id ) ( * this ) ;
rake_account_id = core_asset_dividend_data_obj . dividend_distribution_account ;
}
2018-04-10 14:19:15 +00:00
affiliate_payout_helper payout_helper ( * this , betting_market_group ) ;
2018-02-07 15:12:58 +00:00
// collect the resolutions of all markets in the BMG: they were previously published and
// stored in the individual betting markets
std : : map < betting_market_id_type , betting_market_resolution_type > resolutions_by_market_id ;
2017-07-09 22:39:53 +00:00
// collecting bettors and their positions
std : : map < account_id_type , std : : vector < const betting_market_position_object * > > bettor_positions_map ;
auto & betting_market_index = get_index_type < betting_market_object_index > ( ) . indices ( ) . get < by_betting_market_group_id > ( ) ;
2017-07-26 19:57:07 +00:00
// [ROL] it seems to be my mistake - wrong index used
//auto& position_index = get_index_type<betting_market_position_index>().indices().get<by_bettor_betting_market>();
auto & position_index = get_index_type < betting_market_position_index > ( ) . indices ( ) . get < by_betting_market_bettor > ( ) ;
2017-07-09 22:39:53 +00:00
auto betting_market_itr = betting_market_index . lower_bound ( betting_market_group . id ) ;
while ( betting_market_itr ! = betting_market_index . end ( ) & & betting_market_itr - > group_id = = betting_market_group . id )
{
const betting_market_object & betting_market = * betting_market_itr ;
2018-02-07 15:12:58 +00:00
FC_ASSERT ( betting_market_itr - > resolution , " Unexpected error settling betting market ${market_id}: no published resolution " ,
( " market_id " , betting_market_itr - > id ) ) ;
resolutions_by_market_id . emplace ( betting_market . id , * betting_market_itr - > resolution ) ;
2017-07-09 22:39:53 +00:00
+ + betting_market_itr ;
cancel_all_unmatched_bets_on_betting_market ( betting_market ) ;
2017-07-26 19:57:07 +00:00
auto position_itr = position_index . lower_bound ( betting_market . id ) ;
2017-07-09 22:39:53 +00:00
while ( position_itr ! = position_index . end ( ) & & position_itr - > betting_market_id = = betting_market . id )
{
const betting_market_position_object & position = * position_itr ;
+ + position_itr ;
bettor_positions_map [ position . bettor_id ] . push_back ( & position ) ;
}
}
// walking through bettors' positions and collecting winings and fees respecting asset_id
for ( const auto & bettor_positions_pair : bettor_positions_map )
{
2018-09-05 14:43:35 +00:00
uint16_t rake_fee_percentage = get_global_properties ( ) . parameters . betting_rake_fee_percentage ( ) ;
2017-07-09 22:39:53 +00:00
share_type net_profits ;
share_type payout_amounts ;
2017-07-04 14:55:34 +00:00
account_id_type bettor_id = bettor_positions_pair . first ;
const std : : vector < const betting_market_position_object * > & bettor_positions = bettor_positions_pair . second ;
for ( const betting_market_position_object * position : bettor_positions )
2017-03-23 23:35:10 +00:00
{
2017-07-09 22:39:53 +00:00
betting_market_resolution_type resolution ;
2018-02-07 15:12:58 +00:00
try
{
resolution = resolutions_by_market_id . at ( position - > betting_market_id ) ;
}
catch ( std : : out_of_range & )
2017-07-09 22:39:53 +00:00
{
2018-02-07 15:12:58 +00:00
FC_THROW_EXCEPTION ( fc : : key_not_found_exception , " Unexpected betting market ID, shouldn't happen " ) ;
2017-07-09 22:39:53 +00:00
}
2018-02-07 15:12:58 +00:00
///if (cancel)
/// resolution = betting_market_resolution_type::cancel;
///else
///{
/// // checked in evaluator, should never happen, see above
/// assert(resolutions.count(position->betting_market_id));
/// resolution = resolutions.at(position->betting_market_id);
///}
2017-07-09 22:39:53 +00:00
switch ( resolution )
{
case betting_market_resolution_type : : win :
{
share_type total_payout = position - > pay_if_payout_condition + position - > pay_if_not_canceled ;
payout_amounts + = total_payout ;
net_profits + = total_payout - position - > pay_if_canceled ;
break ;
}
case betting_market_resolution_type : : not_win :
{
share_type total_payout = position - > pay_if_not_payout_condition + position - > pay_if_not_canceled ;
payout_amounts + = total_payout ;
net_profits + = total_payout - position - > pay_if_canceled ;
break ;
}
case betting_market_resolution_type : : cancel :
2017-08-09 15:12:59 +00:00
payout_amounts + = position - > pay_if_canceled ;
2017-07-09 22:39:53 +00:00
break ;
default :
continue ;
}
remove ( * position ) ;
2017-07-04 14:55:34 +00:00
}
2017-03-24 15:51:35 +00:00
2017-07-09 22:39:53 +00:00
// pay the fees to the dividend-distribution account if net profit
share_type rake_amount ;
if ( net_profits . value > 0 & & rake_account_id )
2017-07-04 14:55:34 +00:00
{
2017-07-09 22:39:53 +00:00
rake_amount = ( ( fc : : uint128_t ( net_profits . value ) * rake_fee_percentage + GRAPHENE_100_PERCENT - 1 ) / GRAPHENE_100_PERCENT ) . to_uint64 ( ) ;
2018-04-18 16:39:39 +00:00
share_type affiliates_share ;
2018-04-10 14:19:15 +00:00
if ( rake_amount . value )
2018-04-18 16:39:39 +00:00
affiliates_share = payout_helper . payout ( bettor_id , rake_amount ) ;
FC_ASSERT ( rake_amount . value > = affiliates_share . value ) ;
if ( rake_amount . value > affiliates_share . value )
adjust_balance ( * rake_account_id , asset ( rake_amount - affiliates_share , betting_market_group . asset_id ) ) ;
2017-07-04 14:55:34 +00:00
}
2017-07-09 22:39:53 +00:00
// pay winning - rake
adjust_balance ( bettor_id , asset ( payout_amounts - rake_amount , betting_market_group . asset_id ) ) ;
2017-07-26 19:57:07 +00:00
// [ROL]
2018-05-09 19:41:48 +00:00
//fc_idump(fc::logger::get("betting"), (payout_amounts)(net_profits.value)(rake_amount.value));
2017-07-09 22:39:53 +00:00
2017-07-04 14:55:34 +00:00
push_applied_operation ( betting_market_group_resolved_operation ( bettor_id ,
2017-07-09 22:39:53 +00:00
betting_market_group . id ,
2018-02-07 15:12:58 +00:00
resolutions_by_market_id ,
2017-07-09 22:39:53 +00:00
payout_amounts ,
2017-08-09 15:12:59 +00:00
rake_amount ) ) ;
2017-07-09 22:39:53 +00:00
}
2018-02-07 15:12:58 +00:00
// At this point, the betting market group will either be in the "graded" or "canceled" state,
// if it was graded, mark it as settled. if it's canceled, let it remain canceled.
bool was_canceled = betting_market_group . get_status ( ) = = betting_market_group_status : : canceled ;
if ( ! was_canceled )
modify ( betting_market_group , [ & ] ( betting_market_group_object & group ) {
group . on_settled_event ( * this ) ;
} ) ;
2017-07-09 22:39:53 +00:00
betting_market_itr = betting_market_index . lower_bound ( betting_market_group . id ) ;
2018-02-07 15:12:58 +00:00
while ( betting_market_itr ! = betting_market_index . end ( ) & & betting_market_itr - > group_id = = betting_market_group . id ) {
2017-07-09 22:39:53 +00:00
const betting_market_object & betting_market = * betting_market_itr ;
2018-02-07 15:12:58 +00:00
2017-07-09 22:39:53 +00:00
+ + betting_market_itr ;
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " removing betting market ${id} " , ( " id " , betting_market . id ) ) ;
2018-02-07 15:12:58 +00:00
remove ( betting_market ) ;
2017-03-23 23:35:10 +00:00
}
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " removing betting market group ${id} " , ( " id " , betting_market_group . id ) ) ;
2018-02-07 15:12:58 +00:00
remove ( betting_market_group ) ;
2018-10-11 11:36:49 +00:00
payout_helper . commit ( ) ;
2018-04-17 17:17:38 +00:00
}
2018-02-07 15:12:58 +00:00
2018-04-17 17:17:38 +00:00
void database : : remove_completed_events ( )
{
const auto & event_index = get_index_type < event_object_index > ( ) . indices ( ) . get < by_event_status > ( ) ;
2018-04-29 22:59:15 +00:00
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 ;
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " removing canceled event ${id} " , ( " id " , event . id ) ) ;
2018-04-29 22:59:15 +00:00
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 ;
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " removing settled event ${id} " , ( " id " , event . id ) ) ;
2018-02-07 15:12:58 +00:00
remove ( event ) ;
}
2017-04-04 21:02:19 +00:00
}
2018-04-29 22:59:15 +00:00
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 )
2017-03-23 20:22:25 +00:00
{ try {
assert ( bet_amount > = 0 ) ;
share_type guaranteed_winnings_returned = 0 ;
if ( bet_amount = = 0 )
return guaranteed_winnings_returned ;
auto & index = db . get_index_type < betting_market_position_index > ( ) . indices ( ) . get < by_bettor_betting_market > ( ) ;
auto itr = index . find ( boost : : make_tuple ( bettor_id , betting_market_id ) ) ;
if ( itr = = index . end ( ) )
{
db . create < betting_market_position_object > ( [ & ] ( betting_market_position_object & position ) {
2018-04-29 22:59:15 +00:00
position . bettor_id = bettor_id ;
position . betting_market_id = betting_market_id ;
2017-03-24 14:40:53 +00:00
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 ;
2017-03-23 20:22:25 +00:00
position . pay_if_canceled = bet_amount ;
position . pay_if_not_canceled = 0 ;
// this should not be reducible
} ) ;
} else {
db . modify ( * itr , [ & ] ( betting_market_position_object & position ) {
2018-04-29 22:59:15 +00:00
assert ( position . bettor_id = = bettor_id ) ;
assert ( position . betting_market_id = = betting_market_id ) ;
2017-03-24 14:40:53 +00:00
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 ;
2017-03-23 20:22:25 +00:00
position . pay_if_canceled + = bet_amount ;
guaranteed_winnings_returned = position . reduce ( ) ;
} ) ;
}
return guaranteed_winnings_returned ;
} FC_CAPTURE_AND_RETHROW ( ( bettor_id ) ( betting_market_id ) ( bet_amount ) ) }
2017-03-31 15:10:37 +00:00
// 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 ,
2017-08-30 21:18:58 +00:00
bet_multiplier_type actual_multiplier ,
bool refund_unmatched_portion )
2017-03-23 20:22:25 +00:00
{
// record their bet, modifying their position, and return any winnings
share_type guaranteed_winnings_returned = adjust_betting_position ( db , bet . bettor_id , bet . betting_market_id ,
2017-08-09 15:12:59 +00:00
bet . back_or_lay , amount_bet , amount_matched ) ;
2017-03-23 20:22:25 +00:00
db . adjust_balance ( bet . bettor_id , asset ( guaranteed_winnings_returned , bet . amount_to_bet . asset_id ) ) ;
// generate a virtual "match" op
asset asset_amount_bet ( amount_bet , bet . amount_to_bet . asset_id ) ;
2017-08-01 19:40:49 +00:00
2017-08-09 15:12:59 +00:00
bet_matched_operation bet_matched_virtual_op ( bet . bettor_id , bet . id ,
2017-08-01 19:40:49 +00:00
asset_amount_bet ,
actual_multiplier ,
guaranteed_winnings_returned ) ;
2018-05-09 19:41:48 +00:00
//fc_edump(fc::logger::get("betting"), (bet_matched_virtual_op));
2017-08-01 19:40:49 +00:00
db . push_applied_operation ( std : : move ( bet_matched_virtual_op ) ) ;
2017-03-23 20:22:25 +00:00
// update the bet on the books
if ( asset_amount_bet = = bet . amount_to_bet )
{
db . remove ( bet ) ;
return true ;
}
else
{
db . modify ( bet , [ & ] ( bet_object & bet_obj ) {
bet_obj . amount_to_bet - = asset_amount_bet ;
} ) ;
2017-08-30 21:18:58 +00:00
if ( refund_unmatched_portion )
{
db . cancel_bet ( bet ) ;
return true ;
}
else
return false ;
2017-03-23 20:22:25 +00:00
}
}
/**
* Matches the two orders ,
*
* @ return a bit field indicating which orders were filled ( and thus removed )
*
2017-03-31 15:10:37 +00:00
* 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
2017-03-23 20:22:25 +00:00
*/
int match_bet ( database & db , const bet_object & taker_bet , const bet_object & maker_bet )
{
2018-05-29 15:36:10 +00:00
//fc_idump(fc::logger::get("betting"), (taker_bet)(maker_bet));
2017-03-23 20:22:25 +00:00
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 ) ;
2018-05-02 23:19:01 +00:00
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 ) ;
2017-03-23 20:22:25 +00:00
assert ( taker_bet . back_or_lay ! = maker_bet . back_or_lay ) ;
int result = 0 ;
2018-05-09 19:41:48 +00:00
//fc_idump(fc::logger::get("betting"), (taker_bet)(maker_bet));
2017-08-01 19:40:49 +00:00
// 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)
share_type back_odds_ratio ;
share_type lay_odds_ratio ;
std : : tie ( back_odds_ratio , lay_odds_ratio ) = maker_bet . get_ratio ( ) ;
// 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 ;
2018-05-02 23:19:01 +00:00
// 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.
//
2018-05-09 19:41:48 +00:00
//fc_idump(fc::logger::get("betting"), (back_odds_ratio)(lay_odds_ratio));
//fc_idump(fc::logger::get("betting"), (maker_odds_ratio)(taker_odds_ratio));
2017-08-01 19:40:49 +00:00
// 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.
2018-05-02 23:19:01 +00:00
share_type maximum_factor_taker_is_willing_to_pay = taker_bet . amount_to_bet . amount / taker_odds_ratio ;
2018-05-29 15:36:10 +00:00
share_type maximum_taker_factor = maximum_factor_taker_is_willing_to_pay ;
if ( taker_bet . back_or_lay = = bet_type : : lay ) {
share_type maximum_factor_taker_is_willing_to_receive = taker_bet . get_exact_matching_amount ( ) / maker_odds_ratio ;
//fc_idump(fc::logger::get("betting"), (maximum_factor_taker_is_willing_to_pay));
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 ;
}
//fc_idump(fc::logger::get("betting"), (maximum_factor_taker_is_willing_to_pay)(maximum_taker_factor));
2018-05-02 23:19:01 +00:00
2017-08-01 19:40:49 +00:00
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 ;
2018-05-09 19:41:48 +00:00
fc_idump ( fc : : logger : : get ( " betting " ) , ( maker_amount_to_match ) ( taker_amount_to_match ) ) ;
2017-08-01 19:40:49 +00:00
// TODO: analyze whether maximum_maker_amount_to_match can ever be zero here
assert ( maker_amount_to_match ! = 0 ) ;
if ( maker_amount_to_match = = 0 )
return 0 ;
# ifndef NDEBUG
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 ) ;
2017-03-23 20:22:25 +00:00
{
2018-04-29 22:59:15 +00:00
// 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 ) ;
2017-03-23 20:22:25 +00:00
}
2017-08-01 19:40:49 +00:00
# endif
2017-03-23 20:22:25 +00:00
2018-05-09 19:41:48 +00:00
//fc_idump(fc::logger::get("betting"), (taker_amount_to_match)(maker_amount_to_match));
2017-08-01 19:40:49 +00:00
2017-08-30 21:18:58 +00:00
// 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 ;
2018-05-02 23:19:01 +00:00
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 ;
2018-05-29 15:36:10 +00:00
share_type taker_refund_amount ;
2018-05-02 23:19:01 +00:00
2018-05-29 15:36:10 +00:00
if ( taker_bet . back_or_lay = = bet_type : : back )
{
// 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 = ( taker_bet . amount_to_bet . amount - taker_amount_to_match ) / takers_odds_taker_odds_ratio ;
share_type taker_remaining_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio ;
taker_refund_amount = taker_bet . amount_to_bet . amount - taker_amount_to_match - taker_remaining_bet_amount ;
//idump((taker_remaining_factor)(taker_remaining_bet_amount)(taker_refund_amount));
}
else
{
// the taker bet is a lay bet. because we matched at the maker's odds and not the taker's odds,
// there are two things we need to take into account. First, we may have achieved more of a position
// than we expected had we matched at our taker odds. If so, we can refund the unused stake.
// Second, the remaining amount to match may not be an even multiple of the taker's odds; round it down.
share_type unrounded_taker_remaining_amount_to_match = taker_bet . get_exact_matching_amount ( ) - maker_amount_to_match ;
//idump((unrounded_taker_remaining_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_bet_amount = taker_remaining_factor * takers_odds_taker_odds_ratio ;
taker_refund_amount = taker_bet . amount_to_bet . amount - taker_amount_to_match - taker_remaining_bet_amount ;
}
2018-05-02 23:19:01 +00:00
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 ;
} ) ;
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " Refunding ${taker_refund_amount} to taker because we matched at the maker's odds of "
2018-05-29 15:36:10 +00:00
" ${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 ) ) ;
2018-05-09 19:41:48 +00:00
fc_ddump ( fc : : logger : : get ( " betting " ) , ( taker_bet ) ) ;
2018-05-02 23:19:01 +00:00
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 ) ) ;
2018-05-09 19:41:48 +00:00
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
2018-05-02 23:19:01 +00:00
db . push_applied_operation ( std : : move ( bet_adjusted_op ) ) ;
}
}
2017-08-30 21:18:58 +00:00
// 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 ) ;
result | = bet_was_matched ( db , maker_bet , maker_amount_to_match , taker_amount_to_match , maker_bet . backer_multiplier , false ) < < 1 ;
2017-03-23 20:22:25 +00:00
assert ( result ! = 0 ) ;
return result ;
}
2017-08-09 15:12:59 +00:00
// called from the bet_place_evaluator
2017-03-22 19:04:11 +00:00
bool database : : place_bet ( const bet_object & new_bet_object )
{
2018-05-02 23:19:01 +00:00
// 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 ) ;
2018-05-09 19:41:48 +00:00
// fc_idump(fc::logger::get("betting"), (bet_adjusted_op)(new_bet_object));
2018-05-02 23:19:01 +00:00
push_applied_operation ( std : : move ( bet_adjusted_op ) ) ;
2018-05-09 19:41:48 +00:00
fc_dlog ( fc : : logger : : get ( " betting " ) , " Refunded ${refund_amount} to round the bet down to something that can match exactly, new bet: ${new_bet} " ,
2018-05-02 23:19:01 +00:00
( " refund_amount " , stake_returned . amount )
( " new_bet " , new_bet_object ) ) ;
}
2017-03-23 20:22:25 +00:00
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 ) ) ;
2018-05-09 19:41:48 +00:00
// fc_ilog(fc::logger::get("betting"), "");
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
2017-08-09 15:12:59 +00:00
// for (auto itr = book_itr; itr != book_end; ++itr)
2018-05-09 19:41:48 +00:00
// fc_idump(fc::logger::get("betting"), (*itr));
// fc_ilog(fc::logger::get("betting"), "------------ order book ------------------");
2017-08-09 15:12:59 +00:00
2017-03-23 20:22:25 +00:00
int orders_matched_flags = 0 ;
bool finished = false ;
while ( ! finished & & book_itr ! = book_end )
{
auto old_book_itr = book_itr ;
+ + book_itr ;
orders_matched_flags = match_bet ( * this , new_bet_object , * old_book_itr ) ;
2017-08-09 15:12:59 +00:00
// we continue if the maker bet was completely consumed AND the taker bet was not
2017-03-23 20:22:25 +00:00
finished = orders_matched_flags ! = 2 ;
}
2017-08-09 15:12:59 +00:00
if ( ! ( orders_matched_flags & 1 ) )
2018-05-09 19:41:48 +00:00
fc_ddump ( fc : : logger : : get ( " betting " ) , ( new_bet_object ) ) ;
2017-08-09 15:12:59 +00:00
2018-05-02 23:19:01 +00:00
// return true if the taker bet was completely consumed
return ( orders_matched_flags & 1 ) ! = 0 ;
2017-03-22 19:04:11 +00:00
}
2017-03-22 16:03:08 +00:00
} }