diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 421dad39..4e05ce46 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -217,6 +217,8 @@ struct get_impacted_account_visitor void operator()( const betting_market_create_operation& op ) {} void operator()( const betting_market_group_resolve_operation& op ) {} void operator()( const betting_market_group_freeze_operation& op ) {} + void operator()( const betting_market_group_cancel_all_bets_operation& op ) {} + void operator()( const bet_place_operation& op ) { _impacted.insert( op.bettor_id ); diff --git a/libraries/chain/betting_market_evaluator.cpp b/libraries/chain/betting_market_evaluator.cpp index 31691cf8..36da74e4 100644 --- a/libraries/chain/betting_market_evaluator.cpp +++ b/libraries/chain/betting_market_evaluator.cpp @@ -253,4 +253,18 @@ void_result betting_market_group_freeze_evaluator::do_apply(const betting_market return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } +void_result betting_market_group_cancel_all_bets_evaluator::do_evaluate(const betting_market_group_cancel_all_bets_operation& op) +{ try { + const database& d = db(); + _betting_market_group = &op.betting_market_group_id(d); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result betting_market_group_cancel_all_bets_evaluator::do_apply(const betting_market_group_cancel_all_bets_operation& op) +{ try { + db().resolve_betting_market_group(*_betting_market_group, {}); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + + } } // graphene::chain diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 91916bde..9ab77bc4 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -222,6 +222,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); register_evaluator(); register_evaluator(); register_evaluator(); diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 636b80e9..48a5aa45 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -201,6 +201,7 @@ struct get_impacted_account_visitor void operator()(const betting_market_group_resolve_operation&){} void operator()(const betting_market_group_freeze_operation&){} void operator()(const betting_market_group_resolved_operation &){} + void operator()(const betting_market_group_cancel_all_bets_operation&){} void operator()(const bet_matched_operation &){} void operator()(const bet_cancel_operation&){} void operator()(const bet_canceled_operation &){} diff --git a/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp b/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp index d4020e88..df4ef2aa 100644 --- a/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/betting_market_evaluator.hpp @@ -107,4 +107,18 @@ namespace graphene { namespace chain { private: const betting_market_group_object* _betting_market_group; }; + + + class betting_market_group_cancel_all_bets_evaluator : public evaluator + { + public: + typedef betting_market_group_cancel_all_bets_operation operation_type; + + void_result do_evaluate( const betting_market_group_cancel_all_bets_operation& o ); + void_result do_apply( const betting_market_group_cancel_all_bets_operation& o ); + private: + const betting_market_group_object* _betting_market_group; + }; + + } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp index bd9365b3..5fe87c40 100644 --- a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp +++ b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp @@ -173,6 +173,19 @@ struct betting_market_group_freeze_operation : public base_operation void validate()const; }; +struct betting_market_group_cancel_all_bets_operation : public base_operation +{ + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; + asset fee; + + betting_market_group_id_type betting_market_group_id; + + extensions_type extensions; + + account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + void validate()const; +}; + enum class bet_type { back, lay }; struct bet_place_operation : public base_operation @@ -320,6 +333,10 @@ FC_REFLECT( graphene::chain::betting_market_group_freeze_operation::fee_paramete FC_REFLECT( graphene::chain::betting_market_group_freeze_operation, (fee)(betting_market_group_id)(freeze)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_group_cancel_all_bets_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::betting_market_group_cancel_all_bets_operation, + (fee)(betting_market_group_id)(extensions) ) + FC_REFLECT_ENUM( graphene::chain::bet_type, (back)(lay) ) FC_REFLECT( graphene::chain::bet_place_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::bet_place_operation, diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 58d67695..c77edabd 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -109,6 +109,7 @@ namespace graphene { namespace chain { betting_market_group_resolve_operation, betting_market_group_resolved_operation, // VIRTUAL betting_market_group_freeze_operation, + betting_market_group_cancel_all_bets_operation, bet_matched_operation, // VIRTUAL bet_cancel_operation, bet_canceled_operation, // VIRTUAL diff --git a/libraries/chain/protocol/betting_market.cpp b/libraries/chain/protocol/betting_market.cpp index ce85ab30..e505739c 100644 --- a/libraries/chain/protocol/betting_market.cpp +++ b/libraries/chain/protocol/betting_market.cpp @@ -50,6 +50,11 @@ void betting_market_group_freeze_operation::validate() const FC_ASSERT( fee.amount >= 0 ); } +void betting_market_group_cancel_all_bets_operation::validate() const +{ + FC_ASSERT( fee.amount >= 0 ); +} + void bet_place_operation::validate() const { FC_ASSERT( fee.amount >= 0 ); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index d8e56887..8e8bd3b5 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1627,6 +1627,12 @@ class wallet_api const std::map& resolutions, bool broadcast = false); + signed_transaction propose_cancel_betting_market_group( + const string& proposing_account, + fc::time_point_sec expiration_time, + betting_market_group_id_type betting_market_group_id, + bool broadcast = false); + /** Creates a new tournament * @param creator the accout that is paying the fee to create the tournament * @param options the options detailing the specifics of the tournament diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 080dae71..41200807 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -5222,6 +5222,33 @@ signed_transaction wallet_api::propose_resolve_betting_market_group( return my->sign_transaction(tx, broadcast); } +signed_transaction wallet_api::propose_cancel_betting_market_group( + const string& proposing_account, + fc::time_point_sec expiration_time, + betting_market_group_id_type betting_market_group_id, + bool broadcast /*= false*/) +{ + FC_ASSERT( !is_locked() ); + const chain_parameters& current_params = get_global_properties().parameters; + + betting_market_group_cancel_all_bets_operation betting_market_group_cancel_all_bets_op; + betting_market_group_cancel_all_bets_op.betting_market_group_id = betting_market_group_id; + + proposal_create_operation prop_op; + prop_op.expiration_time = expiration_time; + prop_op.review_period_seconds = current_params.committee_proposal_review_period; + prop_op.fee_paying_account = get_account(proposing_account).id; + prop_op.proposed_ops.emplace_back( betting_market_group_cancel_all_bets_op ); + current_params.current_fees->set_fee( prop_op.proposed_ops.back().op ); + + signed_transaction tx; + tx.operations.push_back(prop_op); + my->set_operation_fees(tx, current_params.current_fees); + tx.validate(); + + return my->sign_transaction(tx, broadcast); +} + signed_transaction wallet_api::tournament_create( string creator, tournament_options options, bool broadcast ) { FC_ASSERT( !is_locked() ); diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index cae93a97..3d333e73 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -72,7 +72,6 @@ BOOST_AUTO_TEST_CASE(try_create_sport) { try { - fc::optional result; sport_create_operation sport_create_op; sport_create_op.name = {{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}; @@ -181,6 +180,34 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test ) } FC_LOG_AND_RETHROW() } +BOOST_AUTO_TEST_CASE( cancel_betting_group_test ) +{ + try + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(); + + // give alice and bob 10M each + transfer(account_id_type(), alice_id, asset(10000000)); + transfer(account_id_type(), bob_id, asset(10000000)); + + // have bob lay a bet for 1M (+20k fees) at 1:1 odds + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + // have alice back a matching bet at 1:1 odds (also costing 1.02M) + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(1000000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 1000000 / 50 /* chain defaults to 2% fees */); + + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000); + + // cancel + cancel_all_bets_in_betting_market_group(moneyline_betting_markets.id); + + BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000); + BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000); + + } FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_CASE( chained_market_create_test ) { // Often you will want to create several objects that reference each other at the same time. diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 2765e6e3..70160fd8 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1286,6 +1286,14 @@ void database_fixture::resolve_betting_market_group(betting_market_group_id_type process_operation_by_witnesses(betting_market_group_resolve_op); } FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(resolutions) ) } +void database_fixture::cancel_all_bets_in_betting_market_group(betting_market_group_id_type betting_market_group_id) +{ try { + betting_market_group_cancel_all_bets_operation betting_market_group_cancel_all_bets_op; + betting_market_group_cancel_all_bets_op.betting_market_group_id = betting_market_group_id; + process_operation_by_witnesses(betting_market_group_cancel_all_bets_op); +} FC_CAPTURE_AND_RETHROW( (betting_market_group_id) ) } + + namespace test { void set_expiration( const database& db, transaction& tx ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 68566881..5695785b 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -294,6 +294,7 @@ struct database_fixture { void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees); void resolve_betting_market_group(betting_market_group_id_type betting_market_group_id, std::map resolutions); + void cancel_all_bets_in_betting_market_group(betting_market_group_id_type betting_market_group_id); proposal_id_type propose_operation(operation op); void process_proposal_by_witnesses(const std::vector& witnesses, proposal_id_type proposal_id, bool remove = false);