From 4cdcbe32daa5bb1d289cd08f6b40d02ab92d0d46 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Wed, 22 Mar 2017 15:04:11 -0400 Subject: [PATCH] Validate the fee paid when placing a bet. Add virtual op for canceling bet --- libraries/app/impacted.cpp | 14 ++++++- libraries/chain/CMakeLists.txt | 1 + libraries/chain/betting_market_evaluator.cpp | 41 ++++++++++++++----- libraries/chain/db_bet.cpp | 22 ++++++---- .../chain/betting_market_evaluator.hpp | 3 ++ .../graphene/chain/betting_market_object.hpp | 4 +- .../chain/include/graphene/chain/config.hpp | 2 +- .../chain/include/graphene/chain/database.hpp | 18 ++++++-- .../chain/protocol/betting_market.hpp | 41 +++++++++++++++++-- .../graphene/chain/protocol/operations.hpp | 3 +- libraries/fc | 2 +- 11 files changed, 121 insertions(+), 30 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 67da8262..7cda823a 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -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 ); + } }; diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index fde347d4..d91209ce 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -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" diff --git a/libraries/chain/betting_market_evaluator.cpp b/libraries/chain/betting_market_evaluator.cpp index 02b87d3b..1cae7947 100644 --- a/libraries/chain/betting_market_evaluator.cpp +++ b/libraries/chain/betting_market_evaluator.cpp @@ -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().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_obj ) { + d.create( [&]( 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) ) } diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 57829d40..99e58f56 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -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; +} + } } diff --git a/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp b/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp index f9462f6e..a56616bb 100644 --- a/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp @@ -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 @@ -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 diff --git a/libraries/chain/include/graphene/chain/betting_market_object.hpp b/libraries/chain/include/graphene/chain/betting_market_object.hpp index 023358e0..7fad68df 100644 --- a/libraries/chain/include/graphene/chain/betting_market_object.hpp +++ b/libraries/chain/include/graphene/chain/betting_market_object.hpp @@ -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; }; diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 1dcd756b..a3933a5e 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -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) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 0c264e58..b3ed41d6 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -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. */ diff --git a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp index 4d1553c8..c2637942 100644 --- a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp +++ b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp @@ -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) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 14fa20c1..bc625531 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -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 diff --git a/libraries/fc b/libraries/fc index 57d14c7d..9d408aa5 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 57d14c7de849c567d753fc5cab5465d68602ff95 +Subproject commit 9d408aa53267834542279490eac4c25878107967