Validate the fee paid when placing a bet. Add virtual op for canceling bet

This commit is contained in:
Eric Frias 2017-03-22 15:04:11 -04:00
parent 58d5affe40
commit 4cdcbe32da
11 changed files with 121 additions and 30 deletions

View file

@ -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 );
}
};

View file

@ -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"

View file

@ -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) ) }

View file

@ -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;
}
} }

View file

@ -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

View file

@ -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;
};

View file

@ -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)

View file

@ -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.
*/

View file

@ -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) )

View file

@ -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