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 betting_market_group_create_operation& op ) {}
|
||||
void operator()( const betting_market_create_operation& op ) {}
|
||||
void operator()( const bet_place_operation& op ) {}
|
||||
void operator()( const bet_cancel_operation& op ) {}
|
||||
void operator()( const bet_place_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
|
||||
protocol/betting_market.cpp
|
||||
betting_market_evaluator.cpp
|
||||
db_bet.cpp
|
||||
|
||||
${HEADERS}
|
||||
"${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_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 ) );
|
||||
|
||||
const chain_parameters& current_params = d.get_global_properties().parameters;
|
||||
|
||||
// are their odds valid
|
||||
FC_ASSERT( op.backer_multiplier >= current_params.min_bet_multiplier &&
|
||||
op.backer_multiplier <= current_params.max_bet_multiplier,
|
||||
"Bet odds are outside the blockchain's limits" );
|
||||
|
||||
if (!current_params.permitted_betting_odds_increments.empty())
|
||||
{
|
||||
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));
|
||||
}
|
||||
|
||||
share_type stake_plus_fees = op.amount_to_bet + 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) );
|
||||
// verify they reserved enough to cover the percentage fee
|
||||
uint16_t percentage_fee = current_params.current_fees->get<bet_place_operation>().percentage_fee;
|
||||
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();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
object_id_type bet_place_evaluator::do_apply(const bet_place_operation& op)
|
||||
{ try {
|
||||
database& d = db();
|
||||
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.betting_market_id = op.betting_market_id;
|
||||
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.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;
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void_result bet_cancel_evaluator::do_evaluate(const bet_cancel_operation& op)
|
||||
{ try {
|
||||
FC_ASSERT( db().find_object(op.bettor_id), "Invalid bettor account specified" );
|
||||
FC_ASSERT( db().find_object(op.bet_to_cancel), "Invalid bet specified" );
|
||||
const database& d = db();
|
||||
_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();
|
||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||
|
||||
void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
|
||||
{ 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();
|
||||
} 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 )
|
||||
{
|
||||
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
|
||||
adjust_balance(bet.bettor_id, amount_to_refund); //return unmatched stake
|
||||
//TODO: do special fee accounting as required
|
||||
if (create_virtual_op)
|
||||
{
|
||||
bet_cancel_operation vop;
|
||||
vop.bettor_id = bet.bettor_id;
|
||||
vop.bet_to_cancel = bet.id;
|
||||
push_applied_operation(vop);
|
||||
}
|
||||
push_applied_operation(bet_canceled_operation(bet.bettor_id, bet.id,
|
||||
bet.amount_to_bet,
|
||||
bet.amount_reserved_for_fees));
|
||||
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_object* _betting_market;
|
||||
const asset_object* _asset;
|
||||
share_type _stake_plus_fees;
|
||||
};
|
||||
|
||||
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_apply( const bet_cancel_operation& o );
|
||||
private:
|
||||
const bet_object* _bet_to_cancel;
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -68,11 +68,11 @@ class bet_object : public graphene::db::abstract_object< bet_object >
|
|||
|
||||
betting_market_id_type betting_market_id;
|
||||
|
||||
share_type amount_to_bet;
|
||||
asset amount_to_bet;
|
||||
|
||||
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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -198,4 +198,4 @@
|
|||
{ 500000, 20000}, /* <= 50: 2.00 */ \
|
||||
{ 1000000, 50000}, /* <= 100: 5.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();
|
||||
|
||||
//////////////////// db_bet.cpp //////////////////////
|
||||
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
|
||||
|
||||
//////////////////// db_market.cpp ////////////////////
|
||||
|
||||
/// @{ @group Market Helpers
|
||||
|
|
@ -364,6 +361,21 @@ namespace graphene { namespace chain {
|
|||
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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -114,7 +114,11 @@ enum class bet_type { back, lay };
|
|||
|
||||
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;
|
||||
|
||||
account_id_type bettor_id;
|
||||
|
|
@ -122,7 +126,7 @@ struct bet_place_operation : public base_operation
|
|||
betting_market_id_type betting_market_id;
|
||||
|
||||
/// 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.
|
||||
// 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.
|
||||
// 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;
|
||||
|
||||
bet_type back_or_lay;
|
||||
|
|
@ -160,6 +164,34 @@ struct bet_cancel_operation : public base_operation
|
|||
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, (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_create_operation,
|
||||
bet_place_operation,
|
||||
bet_cancel_operation
|
||||
bet_cancel_operation,
|
||||
bet_canceled_operation // VIRTUAL
|
||||
> operation;
|
||||
|
||||
/// @} // operations group
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 57d14c7de849c567d753fc5cab5465d68602ff95
|
||||
Subproject commit 9d408aa53267834542279490eac4c25878107967
|
||||
Loading…
Reference in a new issue