Validate the fee paid when placing a bet. Add virtual op for canceling bet
This commit is contained in:
parent
58d5affe40
commit
4cdcbe32da
11 changed files with 121 additions and 30 deletions
|
|
@ -209,8 +209,18 @@ struct get_impacted_account_visitor
|
||||||
void operator()( const event_create_operation& op ) {}
|
void operator()( const event_create_operation& op ) {}
|
||||||
void operator()( const betting_market_group_create_operation& op ) {}
|
void operator()( const betting_market_group_create_operation& op ) {}
|
||||||
void operator()( const betting_market_create_operation& op ) {}
|
void operator()( const betting_market_create_operation& op ) {}
|
||||||
void operator()( const bet_place_operation& op ) {}
|
void operator()( const bet_place_operation& op )
|
||||||
void operator()( const bet_cancel_operation& op ) {}
|
{
|
||||||
|
_impacted.insert( op.bettor_id );
|
||||||
|
}
|
||||||
|
void operator()( const bet_cancel_operation& op )
|
||||||
|
{
|
||||||
|
_impacted.insert( op.bettor_id );
|
||||||
|
}
|
||||||
|
void operator()( const bet_canceled_operation& op )
|
||||||
|
{
|
||||||
|
_impacted.insert( op.bettor_id );
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -100,6 +100,7 @@ add_library( graphene_chain
|
||||||
event_evaluator.cpp
|
event_evaluator.cpp
|
||||||
protocol/betting_market.cpp
|
protocol/betting_market.cpp
|
||||||
betting_market_evaluator.cpp
|
betting_market_evaluator.cpp
|
||||||
|
db_bet.cpp
|
||||||
|
|
||||||
${HEADERS}
|
${HEADERS}
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp"
|
"${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp"
|
||||||
|
|
|
||||||
|
|
@ -98,15 +98,19 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
|
||||||
|
|
||||||
_betting_market = &op.betting_market_id(d);
|
_betting_market = &op.betting_market_id(d);
|
||||||
_betting_market_group = &_betting_market->group_id(d);
|
_betting_market_group = &_betting_market->group_id(d);
|
||||||
_asset = &_betting_market->asset_id(d);
|
|
||||||
|
|
||||||
|
FC_ASSERT( op.amount_to_bet.asset_id == _betting_market->asset_id,
|
||||||
|
"Asset type bet does not match the market's asset type" );
|
||||||
|
|
||||||
|
_asset = &_betting_market->asset_id(d);
|
||||||
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
|
FC_ASSERT( is_authorized_asset( d, *fee_paying_account, *_asset ) );
|
||||||
|
|
||||||
const chain_parameters& current_params = d.get_global_properties().parameters;
|
const chain_parameters& current_params = d.get_global_properties().parameters;
|
||||||
|
|
||||||
|
// are their odds valid
|
||||||
FC_ASSERT( op.backer_multiplier >= current_params.min_bet_multiplier &&
|
FC_ASSERT( op.backer_multiplier >= current_params.min_bet_multiplier &&
|
||||||
op.backer_multiplier <= current_params.max_bet_multiplier,
|
op.backer_multiplier <= current_params.max_bet_multiplier,
|
||||||
"Bet odds are outside the blockchain's limits" );
|
"Bet odds are outside the blockchain's limits" );
|
||||||
|
|
||||||
if (!current_params.permitted_betting_odds_increments.empty())
|
if (!current_params.permitted_betting_odds_increments.empty())
|
||||||
{
|
{
|
||||||
bet_multiplier_type allowed_increment;
|
bet_multiplier_type allowed_increment;
|
||||||
|
|
@ -118,17 +122,29 @@ void_result bet_place_evaluator::do_evaluate(const bet_place_operation& op)
|
||||||
FC_ASSERT(op.backer_multiplier % allowed_increment == 0, "Bet odds must be a multiple of ${allowed_increment}", ("allowed_increment", allowed_increment));
|
FC_ASSERT(op.backer_multiplier % allowed_increment == 0, "Bet odds must be a multiple of ${allowed_increment}", ("allowed_increment", allowed_increment));
|
||||||
}
|
}
|
||||||
|
|
||||||
share_type stake_plus_fees = op.amount_to_bet + op.amount_reserved_for_fees;
|
// verify they reserved enough to cover the percentage fee
|
||||||
FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= stake_plus_fees, "insufficient balance",
|
uint16_t percentage_fee = current_params.current_fees->get<bet_place_operation>().percentage_fee;
|
||||||
("balance", d.get_balance(*fee_paying_account, *_asset))("stake_plus_fees", stake_plus_fees) );
|
fc::uint128_t minimum_percentage_fee_calculation = op.amount_to_bet.amount.value;
|
||||||
|
minimum_percentage_fee_calculation *= percentage_fee;
|
||||||
|
minimum_percentage_fee_calculation += GRAPHENE_100_PERCENT - 1; // round up
|
||||||
|
minimum_percentage_fee_calculation /= GRAPHENE_100_PERCENT;
|
||||||
|
share_type minimum_percentage_fee = minimum_percentage_fee_calculation.to_uint64();
|
||||||
|
FC_ASSERT(op.amount_reserved_for_fees >= minimum_percentage_fee, "insufficient fees",
|
||||||
|
("fee_provided", op.amount_reserved_for_fees)("fee_required", minimum_percentage_fee));
|
||||||
|
|
||||||
|
// do they have enough in their account to place the bet
|
||||||
|
_stake_plus_fees = op.amount_to_bet.amount + op.amount_reserved_for_fees;
|
||||||
|
FC_ASSERT( d.get_balance( *fee_paying_account, *_asset ).amount >= _stake_plus_fees, "insufficient balance",
|
||||||
|
("balance", d.get_balance(*fee_paying_account, *_asset))("stake_plus_fees", _stake_plus_fees) );
|
||||||
|
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
|
object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
|
database& d = db();
|
||||||
const bet_object& new_bet =
|
const bet_object& new_bet =
|
||||||
db().create<bet_object>( [&]( bet_object& bet_obj ) {
|
d.create<bet_object>( [&]( bet_object& bet_obj ) {
|
||||||
bet_obj.bettor_id = op.bettor_id;
|
bet_obj.bettor_id = op.bettor_id;
|
||||||
bet_obj.betting_market_id = op.betting_market_id;
|
bet_obj.betting_market_id = op.betting_market_id;
|
||||||
bet_obj.amount_to_bet = op.amount_to_bet;
|
bet_obj.amount_to_bet = op.amount_to_bet;
|
||||||
|
|
@ -136,21 +152,26 @@ object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
|
||||||
bet_obj.amount_reserved_for_fees = op.amount_reserved_for_fees;
|
bet_obj.amount_reserved_for_fees = op.amount_reserved_for_fees;
|
||||||
bet_obj.back_or_lay = op.back_or_lay;
|
bet_obj.back_or_lay = op.back_or_lay;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
d.adjust_balance(fee_paying_account->id, asset(-_stake_plus_fees, _betting_market->asset_id));
|
||||||
|
|
||||||
|
bool bet_matched = d.place_bet(new_bet);
|
||||||
|
|
||||||
return new_bet.id;
|
return new_bet.id;
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
void_result bet_cancel_evaluator::do_evaluate(const bet_cancel_operation& op)
|
void_result bet_cancel_evaluator::do_evaluate(const bet_cancel_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
FC_ASSERT( db().find_object(op.bettor_id), "Invalid bettor account specified" );
|
const database& d = db();
|
||||||
FC_ASSERT( db().find_object(op.bet_to_cancel), "Invalid bet specified" );
|
_bet_to_cancel = &op.bet_to_cancel(d);
|
||||||
|
FC_ASSERT( op.bettor_id == _bet_to_cancel->bettor_id, "You can only cancel your own bets" );
|
||||||
|
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
|
void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
const bet_object& bet_to_cancel = op.bet_to_cancel( db() );
|
db().cancel_bet(*_bet_to_cancel);
|
||||||
db().cancel_bet(bet_to_cancel);
|
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,18 +7,26 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
|
void database::cancel_bet( const bet_object& bet, bool create_virtual_op )
|
||||||
{
|
{
|
||||||
share_type amount_to_refund = bet.amount_to_bet;
|
asset amount_to_refund = bet.amount_to_bet;
|
||||||
|
amount_to_refund += bet.amount_reserved_for_fees;
|
||||||
//TODO: update global statistics
|
//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
|
||||||
//TODO: do special fee accounting as required
|
//TODO: do special fee accounting as required
|
||||||
if (create_virtual_op)
|
if (create_virtual_op)
|
||||||
{
|
push_applied_operation(bet_canceled_operation(bet.bettor_id, bet.id,
|
||||||
bet_cancel_operation vop;
|
bet.amount_to_bet,
|
||||||
vop.bettor_id = bet.bettor_id;
|
bet.amount_reserved_for_fees));
|
||||||
vop.bet_to_cancel = bet.id;
|
|
||||||
push_applied_operation(vop);
|
|
||||||
}
|
|
||||||
remove(bet);
|
remove(bet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool maybe_cull_small_bet( database& db, const bet_object& bet_object_to_cull )
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool database::place_bet(const bet_object& new_bet_object)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,7 @@ namespace graphene { namespace chain {
|
||||||
const betting_market_group_object* _betting_market_group;
|
const betting_market_group_object* _betting_market_group;
|
||||||
const betting_market_object* _betting_market;
|
const betting_market_object* _betting_market;
|
||||||
const asset_object* _asset;
|
const asset_object* _asset;
|
||||||
|
share_type _stake_plus_fees;
|
||||||
};
|
};
|
||||||
|
|
||||||
class bet_cancel_evaluator : public evaluator<bet_cancel_evaluator>
|
class bet_cancel_evaluator : public evaluator<bet_cancel_evaluator>
|
||||||
|
|
@ -71,6 +72,8 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
void_result do_evaluate( const bet_cancel_operation& o );
|
void_result do_evaluate( const bet_cancel_operation& o );
|
||||||
void_result do_apply( const bet_cancel_operation& o );
|
void_result do_apply( const bet_cancel_operation& o );
|
||||||
|
private:
|
||||||
|
const bet_object* _bet_to_cancel;
|
||||||
};
|
};
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -68,11 +68,11 @@ class bet_object : public graphene::db::abstract_object< bet_object >
|
||||||
|
|
||||||
betting_market_id_type betting_market_id;
|
betting_market_id_type betting_market_id;
|
||||||
|
|
||||||
share_type amount_to_bet;
|
asset amount_to_bet;
|
||||||
|
|
||||||
bet_multiplier_type backer_multiplier;
|
bet_multiplier_type backer_multiplier;
|
||||||
|
|
||||||
share_type amount_reserved_for_fees;
|
share_type amount_reserved_for_fees; // same asset type as amount_to_bet
|
||||||
|
|
||||||
bet_type back_or_lay;
|
bet_type back_or_lay;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -198,4 +198,4 @@
|
||||||
{ 500000, 20000}, /* <= 50: 2.00 */ \
|
{ 500000, 20000}, /* <= 50: 2.00 */ \
|
||||||
{ 1000000, 50000}, /* <= 100: 5.00 */ \
|
{ 1000000, 50000}, /* <= 100: 5.00 */ \
|
||||||
{ 10000000, 100000} } /* <= 1000: 10.00 */
|
{ 10000000, 100000} } /* <= 1000: 10.00 */
|
||||||
|
#define GRAPHENE_DEFAULT_BETTING_PERCENT_FEE (2 * GRAPHENE_1_PERCENT)
|
||||||
|
|
|
||||||
|
|
@ -323,9 +323,6 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
void debug_dump();
|
void debug_dump();
|
||||||
|
|
||||||
//////////////////// db_bet.cpp //////////////////////
|
|
||||||
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
|
|
||||||
|
|
||||||
//////////////////// db_market.cpp ////////////////////
|
//////////////////// db_market.cpp ////////////////////
|
||||||
|
|
||||||
/// @{ @group Market Helpers
|
/// @{ @group Market Helpers
|
||||||
|
|
@ -364,6 +361,21 @@ namespace graphene { namespace chain {
|
||||||
asset max_settlement);
|
asset max_settlement);
|
||||||
///@}
|
///@}
|
||||||
|
|
||||||
|
//////////////////// db_bet.cpp ////////////////////
|
||||||
|
|
||||||
|
/// @{ @group Betting Market Helpers
|
||||||
|
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
|
||||||
|
/**
|
||||||
|
* @brief Process a new bet
|
||||||
|
* @param new_bet_object The new bet to process
|
||||||
|
* @return true if order was completely filled; false otherwise
|
||||||
|
*
|
||||||
|
* This function takes a new bet and attempts to match it with existing
|
||||||
|
* bets already on the books.
|
||||||
|
*/
|
||||||
|
bool place_bet(const bet_object& new_bet_object);
|
||||||
|
///@}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if the order was completely filled and thus freed.
|
* @return true if the order was completely filled and thus freed.
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -114,7 +114,11 @@ enum class bet_type { back, lay };
|
||||||
|
|
||||||
struct bet_place_operation : public base_operation
|
struct bet_place_operation : public base_operation
|
||||||
{
|
{
|
||||||
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
|
struct fee_parameters_type
|
||||||
|
{
|
||||||
|
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; // fixed fee charged upon placing the bet
|
||||||
|
uint16_t percentage_fee = 2 * GRAPHENE_1_PERCENT; // charged when bet is matched
|
||||||
|
};
|
||||||
asset fee;
|
asset fee;
|
||||||
|
|
||||||
account_id_type bettor_id;
|
account_id_type bettor_id;
|
||||||
|
|
@ -122,7 +126,7 @@ struct bet_place_operation : public base_operation
|
||||||
betting_market_id_type betting_market_id;
|
betting_market_id_type betting_market_id;
|
||||||
|
|
||||||
/// the bettor's stake
|
/// the bettor's stake
|
||||||
share_type amount_to_bet;
|
asset amount_to_bet;
|
||||||
|
|
||||||
// decimal odds as seen by the backer, even if this is a lay bet.
|
// decimal odds as seen by the backer, even if this is a lay bet.
|
||||||
// this is a fixed-precision number scaled by GRAPHENE_BETTING_ODDS_PRECISION.
|
// this is a fixed-precision number scaled by GRAPHENE_BETTING_ODDS_PRECISION.
|
||||||
|
|
@ -133,7 +137,7 @@ struct bet_place_operation : public base_operation
|
||||||
|
|
||||||
// the amount the blockchain reserves to pay the percentage fee on matched bets.
|
// the amount the blockchain reserves to pay the percentage fee on matched bets.
|
||||||
// when this bet is (partially) matched, the blockchain will take (part of) the fee. If this bet is canceled
|
// when this bet is (partially) matched, the blockchain will take (part of) the fee. If this bet is canceled
|
||||||
// the remaining amount will be returned to the bettor
|
// the remaining amount will be returned to the bettor (same asset type as the amount_to_bet)
|
||||||
share_type amount_reserved_for_fees;
|
share_type amount_reserved_for_fees;
|
||||||
|
|
||||||
bet_type back_or_lay;
|
bet_type back_or_lay;
|
||||||
|
|
@ -160,6 +164,34 @@ struct bet_cancel_operation : public base_operation
|
||||||
void validate()const;
|
void validate()const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* virtual op generated when a bet is canceled
|
||||||
|
*/
|
||||||
|
struct bet_canceled_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type {};
|
||||||
|
|
||||||
|
bet_canceled_operation(){}
|
||||||
|
bet_canceled_operation(account_id_type bettor_id, bet_id_type bet_id,
|
||||||
|
asset stake_returned, share_type unused_fees_returned) :
|
||||||
|
bettor_id(bettor_id),
|
||||||
|
bet_id(bet_id),
|
||||||
|
stake_returned(stake_returned),
|
||||||
|
unused_fees_returned(unused_fees_returned)
|
||||||
|
{}
|
||||||
|
|
||||||
|
account_id_type bettor_id;
|
||||||
|
bet_id_type bet_id;
|
||||||
|
asset stake_returned;
|
||||||
|
share_type unused_fees_returned; // same asset type as stake_returned
|
||||||
|
asset fee; // unimportant for a virtual op
|
||||||
|
|
||||||
|
account_id_type fee_payer()const { return bettor_id; }
|
||||||
|
void validate()const { FC_ASSERT(false, "virtual operation"); }
|
||||||
|
|
||||||
|
/// This is a virtual operation; there is no fee
|
||||||
|
share_type calculate_fee(const fee_parameters_type& k)const { return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
@ -193,3 +225,6 @@ FC_REFLECT( graphene::chain::bet_place_operation,
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::bet_cancel_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::bet_cancel_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::bet_cancel_operation, (bettor_id) (bet_to_cancel) (extensions) )
|
FC_REFLECT( graphene::chain::bet_cancel_operation, (bettor_id) (bet_to_cancel) (extensions) )
|
||||||
|
|
||||||
|
FC_REFLECT( graphene::chain::bet_canceled_operation::fee_parameters_type, )
|
||||||
|
FC_REFLECT( graphene::chain::bet_canceled_operation, (bettor_id)(bet_id)(stake_returned)(unused_fees_returned) )
|
||||||
|
|
|
||||||
|
|
@ -104,7 +104,8 @@ namespace graphene { namespace chain {
|
||||||
betting_market_group_create_operation,
|
betting_market_group_create_operation,
|
||||||
betting_market_create_operation,
|
betting_market_create_operation,
|
||||||
bet_place_operation,
|
bet_place_operation,
|
||||||
bet_cancel_operation
|
bet_cancel_operation,
|
||||||
|
bet_canceled_operation // VIRTUAL
|
||||||
> operation;
|
> operation;
|
||||||
|
|
||||||
/// @} // operations group
|
/// @} // operations group
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
Subproject commit 57d14c7de849c567d753fc5cab5465d68602ff95
|
Subproject commit 9d408aa53267834542279490eac4c25878107967
|
||||||
Loading…
Reference in a new issue