From fe7c8d6ad14e86b5106a97d4faaa3e97656c0b3e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 22 Mar 2018 19:01:56 +0100 Subject: [PATCH 01/23] Implemented item 1 --- libraries/chain/account_evaluator.cpp | 3 +++ libraries/chain/hardfork.d/999.hf | 4 ++++ .../include/graphene/chain/account_object.hpp | 4 +++- .../chain/include/graphene/chain/config.hpp | 2 +- .../graphene/chain/protocol/account.hpp | 20 ++++++++++++++++ libraries/chain/proposal_evaluator.cpp | 7 ++++++ libraries/chain/protocol/account.cpp | 23 +++++++++++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) create mode 100644 libraries/chain/hardfork.d/999.hf diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index 51205ae5..a08e9031 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -108,6 +108,8 @@ void_result account_create_evaluator::do_evaluate( const account_create_operatio FC_ASSERT( !op.extensions.value.active_special_authority.valid() ); FC_ASSERT( !op.extensions.value.buyback_options.valid() ); } + if( d.head_block_time() < HARDFORK_999_TIME ) + FC_ASSERT( !op.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); FC_ASSERT( fee_paying_account->is_lifetime_member(), "Only Lifetime members may register an account." ); FC_ASSERT( op.referrer(d).is_member(d.head_block_time()), "The referrer must be either a lifetime or annual subscriber." ); @@ -186,6 +188,7 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.allowed_assets = o.extensions.value.buyback_options->markets; obj.allowed_assets->emplace( o.extensions.value.buyback_options->asset_to_buy ); } + obj.affiliate_distributions = o.extensions.value.affiliate_distributions; }); if( has_small_percent ) diff --git a/libraries/chain/hardfork.d/999.hf b/libraries/chain/hardfork.d/999.hf new file mode 100644 index 00000000..55b99fc1 --- /dev/null +++ b/libraries/chain/hardfork.d/999.hf @@ -0,0 +1,4 @@ +// Placeholder HF for affiliate reward system +#ifndef HARDFORK_999_TIME +#define HARDFORK_999_TIME (fc::time_point_sec( 1600000000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 333168a8..51ba6748 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -227,6 +227,8 @@ namespace graphene { namespace chain { */ optional< flat_set > allowed_assets; + optional< affiliate_reward_distributions > affiliate_distributions; + bool has_special_authority()const { return (owner_special_authority.which() != special_authority::tag< no_special_authority >::value) @@ -446,7 +448,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_object, (cashback_vb) (owner_special_authority)(active_special_authority) (top_n_control_flags) - (allowed_assets) + (allowed_assets)(affiliate_distributions) ) FC_REFLECT_DERIVED( graphene::chain::account_balance_object, diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 5df38771..c8d7966f 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -151,7 +151,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "PPY1.11" +#define GRAPHENE_CURRENT_DB_VERSION "PPY_999" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 93dbb115..6d13a4d3 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -60,6 +60,21 @@ namespace graphene { namespace chain { void validate()const; }; + enum app_tag { + bookie = 0, + rps = 1 + }; + struct affiliate_reward_distribution + { + fc::flat_map _dist; + void validate()const; + }; + struct affiliate_reward_distributions + { + fc::flat_map _dists; + void validate()const; + }; + /** * @ingroup operations */ @@ -71,6 +86,7 @@ namespace graphene { namespace chain { optional< special_authority > owner_special_authority; optional< special_authority > active_special_authority; optional< buyback_account_options > buyback_options; + optional< affiliate_reward_distributions > affiliate_distributions; }; struct fee_parameters_type @@ -268,6 +284,10 @@ FC_REFLECT(graphene::chain::account_options, (memo_key)(voting_account)(num_witn FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, (no_listing)(white_listed)(black_listed)(white_and_black_listed)) +FC_REFLECT_ENUM( graphene::chain::app_tag, (bookie)(rps) ) +FC_REFLECT( graphene::chain::affiliate_reward_distribution, (_dist) ); +FC_REFLECT( graphene::chain::affiliate_reward_distributions, (_dists) ); + FC_REFLECT(graphene::chain::account_create_operation::ext, (null_ext)(owner_special_authority)(active_special_authority)(buyback_options) ) FC_REFLECT( graphene::chain::account_create_operation, (fee)(registrar) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 2eada590..f3d057fb 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -25,8 +25,10 @@ #include #include #include +#include #include #include +#include #include @@ -120,6 +122,11 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_1000_TIME, "event_update_status_operation not allowed yet!" ); } + void operator()(const graphene::chain::account_create_operation &aco) const { + if (block_time < HARDFORK_999_TIME) + FC_ASSERT( !aco.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); + } + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index 3aed8fb3..cf592d5c 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -182,6 +182,27 @@ void account_options::validate() const "May not specify fewer witnesses or committee members than the number voted for."); } +void affiliate_reward_distribution::validate() const +{ + // sum of weights must equal 100% + uint32_t sum = 0; + for( const auto& share : _dist ) + { + FC_ASSERT( share.second > 0, "Must leave out affilates who receive 0%!" ); + FC_ASSERT( share.second <= GRAPHENE_100_PERCENT, "Can't pay out more than 100% per affiliate!" ); + sum += share.second; + FC_ASSERT( sum <= GRAPHENE_100_PERCENT, "Can't pay out more than 100% total!" ); + } + FC_ASSERT( sum == GRAPHENE_100_PERCENT, "Total affiliate distributions must cover 100%!" ); +} + +void affiliate_reward_distributions::validate() const +{ + FC_ASSERT( !_dists.empty(), "Empty affiliate reward distributions not allowed!" ); + for( const auto& dist: _dists ) + dist.second.validate(); +} + share_type account_create_operation::calculate_fee( const fee_parameters_type& k )const { auto core_fee_required = k.basic_fee; @@ -226,6 +247,8 @@ void account_create_operation::validate()const FC_ASSERT( m != extensions.value.buyback_options->asset_to_buy ); } } + if( extensions.value.affiliate_distributions.valid() ) + extensions.value.affiliate_distributions->validate(); } From 86fdba07824f99c71332242db4070866f6a54797 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 23 Mar 2018 12:06:16 +0100 Subject: [PATCH 02/23] Added unit tests for setting affiliate_distribution during account creation --- tests/tests/affiliate_tests.cpp | 160 ++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/tests/affiliate_tests.cpp diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp new file mode 100644 index 00000000..aa9462ee --- /dev/null +++ b/tests/tests/affiliate_tests.cpp @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include "../common/database_fixture.hpp" + +#include +#include + +using namespace graphene::chain; +using namespace graphene::db; + +BOOST_FIXTURE_TEST_SUITE( affiliate_tests, database_fixture ) + +/** + * Verify that names are RFC-1035 compliant https://tools.ietf.org/html/rfc1035 + * https://github.com/cryptonomex/graphene/issues/15 + */ +BOOST_AUTO_TEST_CASE( account_test ) +{ + ACTORS( (alice)(ann)(audrey) ); + fund( alice ); + + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + + account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + test::set_expiration( db, trx ); + trx.clear(); + trx.operations.push_back( aco ); + // not allowed before hardfork + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + proposal_create_operation pco; + pco.fee_paying_account = alice_id; + aco.registrar = ann_id; + pco.proposed_ops.emplace_back( aco ); + aco.registrar = account_id_type(); + pco.expiration_time = db.head_block_time() + fc::days(1); + trx.clear(); + trx.operations.push_back( pco ); + // not allowed before hardfork + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + generate_blocks( HARDFORK_999_TIME ); + generate_block(); + + // Proposal is now allowed + pco.expiration_time = db.head_block_time() + fc::days(1); + trx.clear(); + trx.operations.push_back( pco ); + test::set_expiration( db, trx ); + db.push_transaction(trx, ~0); + + // Must contain at least one app-tag + aco.extensions.value.affiliate_distributions->_dists.clear(); + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // Distribution for app-tag must be non-empty + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist.clear(); + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // If more than one app-tag given, neither can be empty + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist.clear(); + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // Sum of percentage must be 100% + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = 1; + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // Individual percentages cannot exceed 100% + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = -1; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[audrey_id] = 1 + GRAPHENE_100_PERCENT; + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // Sum of percentage must be 100% + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT - 10; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[audrey_id] = 9; + trx.clear(); + trx.operations.push_back( aco ); + GRAPHENE_REQUIRE_THROW( db.push_transaction(trx, ~0), fc::assert_exception ); + + // Ok + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[audrey_id] = 10; + trx.clear(); + trx.operations.push_back( aco ); + db.push_transaction(trx, ~0); + + const auto& paula = get_account( aco.name ); + BOOST_CHECK( paula.affiliate_distributions.valid() ); + BOOST_CHECK_EQUAL( 2, paula.affiliate_distributions->_dists.size() ); + + const auto& app_rps = paula.affiliate_distributions->_dists.find( rps ); + BOOST_CHECK( app_rps != paula.affiliate_distributions->_dists.end() ); + BOOST_CHECK_EQUAL( 1, app_rps->second._dist.size() ); + const auto& share_alice = app_rps->second._dist.find( alice_id ); + BOOST_CHECK( share_alice != app_rps->second._dist.end() ); + BOOST_CHECK_EQUAL( GRAPHENE_100_PERCENT, share_alice->second ); + + const auto& app_bookie = paula.affiliate_distributions->_dists.find( bookie ); + BOOST_CHECK( app_bookie != paula.affiliate_distributions->_dists.end() ); + BOOST_CHECK_EQUAL( 2, app_bookie->second._dist.size() ); + const auto& share_ann = app_bookie->second._dist.find( ann_id ); + BOOST_CHECK( share_ann != app_bookie->second._dist.end() ); + BOOST_CHECK_EQUAL( GRAPHENE_100_PERCENT - 10, share_ann->second ); + const auto& share_audrey = app_bookie->second._dist.find( audrey_id ); + BOOST_CHECK( share_audrey != app_bookie->second._dist.end() ); + BOOST_CHECK_EQUAL( 10, share_audrey->second ); +} + +BOOST_AUTO_TEST_SUITE_END() From 8c0fcf9db8939aa4928560650e9a7074d4f3093b Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 23 Mar 2018 12:14:18 +0100 Subject: [PATCH 03/23] Ported security fix from https://github.com/bitshares/bitshares-core/pull/591/ --- libraries/chain/db_block.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index b63dfde4..bcf79cfb 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -658,13 +658,10 @@ operation_result database::apply_operation(transaction_evaluation_state& eval_st { try { int i_which = op.which(); uint64_t u_which = uint64_t( i_which ); - if( i_which < 0 ) - assert( "Negative operation tag" && false ); - if( u_which >= _operation_evaluators.size() ) - assert( "No registered evaluator for this operation" && false ); + FC_ASSERT( i_which >= 0, "Negative operation tag in operation ${op}", ("op",op) ); + FC_ASSERT( u_which < _operation_evaluators.size(), "No registered evaluator for operation ${op}", ("op",op) ); unique_ptr& eval = _operation_evaluators[ u_which ]; - if( !eval ) - assert( "No registered evaluator for this operation" && false ); + FC_ASSERT( eval, "No registered evaluator for operation ${op}", ("op",op) ); auto op_id = push_applied_operation( op ); auto result = eval->evaluate( eval_state, op, true ); set_applied_operation_result( op_id, result ); From 03bc5921141d8a5085f30d92c06ebcb90d37bbc6 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 23 Mar 2018 14:17:22 +0100 Subject: [PATCH 04/23] Prevent some virtual ops from being included in proposals --- libraries/chain/proposal_evaluator.cpp | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index f3d057fb..83282537 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include @@ -54,9 +55,21 @@ struct proposal_operation_hardfork_visitor "Parameter extensions are not allowed yet!" ); } - void operator()(const sport_create_operation &v) const { - FC_ASSERT( block_time >= HARDFORK_1000_TIME, "sport_create_operation not allowed yet!" ); - } + void operator()(const graphene::chain::tournament_payout_operation &o) const { + // TODO: move check into tournament_payout_operation::validate after HARDFORK_999_TIME + FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); + } + + void operator()(const graphene::chain::asset_settle_cancel_operation &o) const { + // TODO: move check into asset_settle_cancel_operation::validate after HARDFORK_999_TIME + FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); + } + + void operator()(const graphene::chain::account_create_operation &aco) const { + // TODO: remove after HARDFORK_999_TIME + if (block_time < HARDFORK_999_TIME) + FC_ASSERT( !aco.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); + } void operator()(const sport_update_operation &v) const { FC_ASSERT( block_time >= HARDFORK_1000_TIME, "sport_update_operation not allowed yet!" ); From 1ab596952ecb54289b0ee2ce2f81845246643783 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 5 Apr 2018 15:58:55 +0200 Subject: [PATCH 05/23] Rake calculation fix as outlined by Dan Notestein --- libraries/chain/tournament_object.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index 86049b81..c49d9174 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -311,10 +311,8 @@ namespace graphene { namespace chain { const account_id_type& winner = *event.match.match_winners.begin(); uint16_t rake_fee_percentage = event.db.get_global_properties().parameters.rake_fee_percentage; - // check whether the core asset pays dividends. If so, we transfer the rake fee - // to the core asset's dividend account - const asset_object& core_asset_obj = asset_id_type()(event.db); - optional dividend_id = core_asset_obj.dividend_data_id; + const asset_object & asset_obj = asset_id_type(0)(event.db); + optional dividend_id = asset_obj.dividend_data_id; share_type rake_amount = 0; if (dividend_id) From fdd7c193a6b374ba844839d1cb52c2829216ac74 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Apr 2018 16:19:15 +0200 Subject: [PATCH 06/23] Implemented affiliate payouts --- libraries/app/impacted.cpp | 5 + libraries/chain/CMakeLists.txt | 2 + libraries/chain/affiliate_payout.cpp | 87 ++++++++++++++++++ libraries/chain/db_bet.cpp | 8 ++ libraries/chain/db_notify.cpp | 5 + .../graphene/chain/affiliate_payout.hpp | 74 +++++++++++++++ .../graphene/chain/protocol/affiliate.hpp | 92 +++++++++++++++++++ .../graphene/chain/protocol/operations.hpp | 5 +- libraries/chain/tournament_object.cpp | 11 ++- 9 files changed, 287 insertions(+), 2 deletions(-) create mode 100644 libraries/chain/affiliate_payout.cpp create mode 100644 libraries/chain/include/graphene/chain/affiliate_payout.hpp create mode 100644 libraries/chain/include/graphene/chain/protocol/affiliate.hpp diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index c765de39..f2ca49c9 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -275,6 +275,11 @@ struct get_impacted_account_visitor { _impacted.insert( op.payout_account_id ); } + void operator()( const affiliate_payout_operation& op ) + { + _impacted.insert( op.affiliate ); + } + void operator()( const affiliate_referral_payout_operation& op ) { } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 399720fa..cd3c29a2 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -108,6 +108,8 @@ add_library( graphene_chain betting_market_object.cpp betting_market_group_object.cpp + affiliate_payout.cpp + ${HEADERS} ${PROTOCOL_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" diff --git a/libraries/chain/affiliate_payout.cpp b/libraries/chain/affiliate_payout.cpp new file mode 100644 index 00000000..9dd5ae4b --- /dev/null +++ b/libraries/chain/affiliate_payout.cpp @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include + +#include + +namespace graphene { namespace chain { + + share_type affiliate_payout_helper::payout( account_id_type player, const asset& amount ) + { + return payout( player(_db), amount ); + } + + share_type affiliate_payout_helper::payout( const account_object& player, const asset& amount ) + { + if( !player.affiliate_distributions.valid() ) + return 0; + const auto& dist = player.affiliate_distributions->_dists.find( tag ); + if( dist == player.affiliate_distributions->_dists.end() || dist->second._dist.empty() ) + return 0; + + share_type to_pay = amount.amount.value / 5; // 20% fixed + if( to_pay <= 0 ) + return 0; + + uint16_t remaining = GRAPHENE_100_PERCENT; + share_type paid = 0; + for( const auto& entry : dist->second._dist ) + { + const account_id_type affiliate = entry.first; + const uint16_t share = entry.second; + fc::uint128_t payout = to_pay.value; + if( share != remaining ) + { + FC_ASSERT( share < remaining ); + payout *= share; + payout /= remaining; + remaining -= share; + } + FC_ASSERT( payout.to_uint64() <= to_pay ); + if( payout > 0 ) + { + accumulator[affiliate] += asset( payout.to_uint64(), amount.asset_id ); + to_pay -= payout.to_uint64(); + paid += payout.to_uint64(); + } + } + FC_ASSERT( to_pay == 0 ); + + _db.push_applied_operation( affiliate_referral_payout_operation( player.id, amount ) ); + + return paid; + } + + void affiliate_payout_helper::commit() + { + for( const auto& entry : accumulator ) + { + _db.adjust_balance( entry.first, entry.second ); + _db.push_applied_operation( affiliate_payout_operation( entry.first, tag, entry.second ) ); + } + } + +} } // graphene::chain diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 337b28c5..f4d34d49 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -167,6 +168,8 @@ void database::settle_betting_market_group(const betting_market_group_object& be rake_account_id = core_asset_dividend_data_obj.dividend_distribution_account; } + affiliate_payout_helper payout_helper( *this, betting_market_group ); + // collect the resolutions of all markets in the BMG: they were previously published and // stored in the individual betting markets std::map resolutions_by_market_id; @@ -261,6 +264,9 @@ void database::settle_betting_market_group(const betting_market_group_object& be if (net_profits.value > 0 && rake_account_id) { rake_amount = ((fc::uint128_t(net_profits.value) * rake_fee_percentage + GRAPHENE_100_PERCENT - 1) / GRAPHENE_100_PERCENT).to_uint64(); + if (rake_amount.value) + rake_amount -= payout_helper.payout( bettor_id, asset( rake_amount, betting_market_group.asset_id ) ); + FC_ASSERT( rake_amount.value >= 0 ); if (rake_amount.value) adjust_balance(*rake_account_id, asset(rake_amount, betting_market_group.asset_id)); } @@ -323,6 +329,8 @@ void database::remove_completed_events() fc_dlog(fc::logger::get("betting"), "removing settled event ${id}", ("id", event.id)); remove(event); } + + payout_helper.commit(); } share_type adjust_betting_position(database& db, diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 9b7fc250..1251e091 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -262,6 +262,11 @@ struct get_impacted_account_visitor { _impacted.insert( op.payout_account_id ); } + void operator()( const affiliate_payout_operation& op ) + { + _impacted.insert( op.affiliate ); + } + void operator()( const affiliate_referral_payout_operation& op ) { } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/include/graphene/chain/affiliate_payout.hpp b/libraries/chain/include/graphene/chain/affiliate_payout.hpp new file mode 100644 index 00000000..8558e2ec --- /dev/null +++ b/libraries/chain/include/graphene/chain/affiliate_payout.hpp @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + class database; + + namespace impl { + class game_type_visitor { + public: + typedef app_tag result_type; + + inline app_tag operator()( const rock_paper_scissors_game_options& o )const { return rps; } + }; + } + + template + app_tag get_tag_for_game( const GAME& game ); + + template<> + inline app_tag get_tag_for_game( const betting_market_group_object& game ) + { + return bookie; + } + + template<> + inline app_tag get_tag_for_game( const tournament_object& game ) + { + return game.options.game_options.visit( impl::game_type_visitor() ); + } + + class affiliate_payout_helper { + public: + template + affiliate_payout_helper( database& db, const GAME& game ) + : tag( get_tag_for_game( game ) ), _db(db) {} + + share_type payout( account_id_type player, const asset& amount ); + share_type payout( const account_object& player, const asset& amount ); + void commit(); + + private: + database& _db; + app_tag tag; + std::map accumulator; + }; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/protocol/affiliate.hpp b/libraries/chain/include/graphene/chain/protocol/affiliate.hpp new file mode 100644 index 00000000..b2c3ee88 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/affiliate.hpp @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + /** + * Virtual op generated when an affiliate receives payout. + */ + + struct affiliate_payout_operation : public base_operation + { + affiliate_payout_operation(){} + affiliate_payout_operation( account_id_type a, app_tag t, const asset& amount ) + : affiliate(a), tag(t), payout(amount) {} + + struct fee_parameters_type { }; + asset fee; + + // Account of the receiving affiliate + account_id_type affiliate; + // App-tag for which the payout was generated + app_tag tag; + // Payout amount + asset payout; + + account_id_type fee_payer()const { return affiliate; } + void validate()const { + FC_ASSERT( false, "Virtual operation" ); + } + + share_type calculate_fee(const fee_parameters_type& params)const + { return 0; } + }; + + /** + * Virtual op generated when a player generates an affiliate payout + */ + struct affiliate_referral_payout_operation : public base_operation + { + affiliate_referral_payout_operation(){} + affiliate_referral_payout_operation( account_id_type p, const asset& amount ) + : player(p), payout(amount) {} + + struct fee_parameters_type { }; + asset fee; + + // Account of the winning player + account_id_type player; + // Payout amount + asset payout; + + account_id_type fee_payer()const { return player; } + void validate()const { + FC_ASSERT( false, "virtual operation" ); + } + + share_type calculate_fee(const fee_parameters_type& params)const + { return 0; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::affiliate_payout_operation::fee_parameters_type, ) +FC_REFLECT( graphene::chain::affiliate_referral_payout_operation::fee_parameters_type, ) + +FC_REFLECT( graphene::chain::affiliate_payout_operation, (fee)(affiliate)(tag)(payout) ) +FC_REFLECT( graphene::chain::affiliate_referral_payout_operation, (fee)(player)(payout) ) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 58cc2cba..4fac8992 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -24,6 +24,7 @@ #pragma once #include #include +#include #include #include #include @@ -124,7 +125,9 @@ namespace graphene { namespace chain { bet_canceled_operation, // VIRTUAL betting_market_group_update_operation, betting_market_update_operation, - event_update_status_operation + event_update_status_operation, + affiliate_payout_operation, // VIRTUAL + affiliate_referral_payout_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index c49d9174..0f0195aa 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include @@ -333,7 +334,15 @@ namespace graphene { namespace chain { event.db.push_applied_operation(op); } - if (dividend_id && rake_amount.value) + if (rake_amount.value) + { + affiliate_payout_helper payout_helper( event.db, tournament_obj ); + rake_amount -= payout_helper.payout( winner, asset( rake_amount, tournament_obj.options.buy_in.asset_id ) ); + payout_helper.commit(); + FC_ASSERT( rake_amount.value >= 0 ); + } + + if (rake_amount.value) { // Adjusting balance of dividend_distribution_account const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id; From 12a8bdc59042a4f55d5ddf2bfbdd725265e5451e Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Apr 2018 21:59:51 +0200 Subject: [PATCH 07/23] Fixes --- libraries/chain/affiliate_payout.cpp | 7 ++++++- .../chain/include/graphene/chain/affiliate_payout.hpp | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/chain/affiliate_payout.cpp b/libraries/chain/affiliate_payout.cpp index 9dd5ae4b..dac51760 100644 --- a/libraries/chain/affiliate_payout.cpp +++ b/libraries/chain/affiliate_payout.cpp @@ -58,12 +58,16 @@ namespace graphene { namespace chain { FC_ASSERT( share < remaining ); payout *= share; payout /= remaining; + //ilog("Paying ${p} of ${P} for ${s} of ${r}", ("p",payout.to_uint64())("P",to_pay.value)("s",share)("r",remaining) ); remaining -= share; } FC_ASSERT( payout.to_uint64() <= to_pay ); if( payout > 0 ) { - accumulator[affiliate] += asset( payout.to_uint64(), amount.asset_id ); + if ( accumulator.find(affiliate) == accumulator.end() ) + accumulator[affiliate] = asset( payout.to_uint64(), amount.asset_id ); + else + accumulator[affiliate] += asset( payout.to_uint64(), amount.asset_id ); to_pay -= payout.to_uint64(); paid += payout.to_uint64(); } @@ -82,6 +86,7 @@ namespace graphene { namespace chain { _db.adjust_balance( entry.first, entry.second ); _db.push_applied_operation( affiliate_payout_operation( entry.first, tag, entry.second ) ); } + accumulator.clear(); } } } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/affiliate_payout.hpp b/libraries/chain/include/graphene/chain/affiliate_payout.hpp index 8558e2ec..2a58861a 100644 --- a/libraries/chain/include/graphene/chain/affiliate_payout.hpp +++ b/libraries/chain/include/graphene/chain/affiliate_payout.hpp @@ -59,7 +59,7 @@ namespace graphene { namespace chain { public: template affiliate_payout_helper( database& db, const GAME& game ) - : tag( get_tag_for_game( game ) ), _db(db) {} + : _db(db), tag( get_tag_for_game( game ) ) {} share_type payout( account_id_type player, const asset& amount ); share_type payout( const account_object& player, const asset& amount ); From 2ecde50d48a4ef6e068ffc6aef86eb5d7b0b1a09 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Tue, 10 Apr 2018 22:00:44 +0200 Subject: [PATCH 08/23] Added test for payout helper --- tests/tests/affiliate_tests.cpp | 191 +++++++++++++++++++++++++++++++- 1 file changed, 186 insertions(+), 5 deletions(-) diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index aa9462ee..e291825d 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. * * The MIT License * @@ -33,6 +33,7 @@ #include #include #include +#include #include @@ -49,10 +50,6 @@ using namespace graphene::db; BOOST_FIXTURE_TEST_SUITE( affiliate_tests, database_fixture ) -/** - * Verify that names are RFC-1035 compliant https://tools.ietf.org/html/rfc1035 - * https://github.com/cryptonomex/graphene/issues/15 - */ BOOST_AUTO_TEST_CASE( account_test ) { ACTORS( (alice)(ann)(audrey) ); @@ -157,4 +154,188 @@ BOOST_AUTO_TEST_CASE( account_test ) BOOST_CHECK_EQUAL( 10, share_audrey->second ); } +BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) +{ + ACTORS( (alice)(ann)(audrey)(irene) ); + + const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; + issue_uia( irene, asset( 100000, btc_id ) ); + + generate_blocks( HARDFORK_999_TIME ); + generate_block(); + + test::set_expiration( db, trx ); + trx.clear(); + + // Paula: 100% to Alice for Bookie / nothing for RPS + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + trx.operations.push_back( aco ); + + // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey + const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); + aco = make_account( "penny", penny_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; + trx.operations.push_back( aco ); + + // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey + const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); + aco = make_account( "petra", petra_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; + trx.operations.push_back( aco ); + processed_transaction result = db.push_transaction(trx, ~0); + + account_id_type paula_id = result.operation_results[0].get(); + account_id_type penny_id = result.operation_results[1].get(); + account_id_type petra_id = result.operation_results[2].get(); + + int64_t alice_ppy = 0; + int64_t ann_ppy = 0; + int64_t audrey_ppy = 0; + int64_t alice_btc = 0; + int64_t ann_btc = 0; + int64_t audrey_btc = 0; + + { + const tournament_object& game = db.create( []( tournament_object& t ) { + t.options.game_options = rock_paper_scissors_game_options(); + t.options.buy_in = asset( 10 ); + }); + affiliate_payout_helper helper = affiliate_payout_helper( db, game ); + // Alice has no distribution set + BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, asset(1000) ).value ); + // Paula has nothing for Bookie + BOOST_CHECK_EQUAL( 0, helper.payout( paula_id, asset(1000) ).value ); + // 20% of 4 gets rounded down to 0 + BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, asset(4) ).value ); + + // 20% of 5 = 1 is paid to Audrey + BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, asset(5) ).value ); + audrey_ppy++; + + // 20% of 50 = 10: 2 to Alice, 3 to Ann, 5 to Audrey + BOOST_CHECK_EQUAL( 10, helper.payout( penny_id, asset(50) ).value ); + alice_ppy += 2; + ann_ppy += 3; + audrey_ppy += 5; + + // 20% of 59 = 11: 1 to Ann, 10 to Audrey + BOOST_CHECK_EQUAL( 11, helper.payout( petra_id, asset(59) ).value ); + audrey_ppy += 10; + ann_ppy += 1; + + // Cannot add BTC after paying out PPY + BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000, btc_id ) ), fc::assert_exception ); + + helper.commit(); + + BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ann_ppy, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( audrey_ppy, get_balance( audrey_id, asset_id_type() ) ); + } + + { + const tournament_object& game = db.create( [btc_id]( tournament_object& t ) { + t.options.game_options = rock_paper_scissors_game_options(); + t.options.buy_in = asset( 10, btc_id ); + }); + affiliate_payout_helper helper = affiliate_payout_helper( db, game ); + // 20% of 60 = 12: 2 to Alice, 3 to Ann, 7 to Audrey + BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, asset( 60, btc_id ) ).value ); + alice_btc += 2; + ann_btc += 3; + audrey_btc += 7; + // Cannot add PPY after paying out BTC + BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000 ) ), fc::assert_exception ); + helper.commit(); + BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( audrey_btc, get_balance( audrey_id, btc_id ) ); + } + + { + const betting_market_group_object& game = db.create( []( betting_market_group_object& b ) { + b.asset_id = asset_id_type(); + } ); + affiliate_payout_helper helper = affiliate_payout_helper( db, game ); + // Alice has no distribution set + BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, asset(1000) ).value ); + // Petra has nothing for Bookie + BOOST_CHECK_EQUAL( 0, helper.payout( petra_id, asset(1000) ).value ); + // 20% of 4 gets rounded down to 0 + BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, asset(4) ).value ); + + // 20% of 5 = 1 is paid to Ann + BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, asset(5) ).value ); + ann_ppy++; + + // 20% of 40 = 8: 8 to Alice + BOOST_CHECK_EQUAL( 8, helper.payout( paula_id, asset(40) ).value ); + alice_ppy += 8; + + // intermediate commit should clear internal accumulator + helper.commit(); + + // 20% of 59 = 11: 6 to Alice, 5 to Ann + BOOST_CHECK_EQUAL( 11, helper.payout( penny_id, asset(59) ).value ); + alice_ppy += 6; + ann_ppy += 5; + + // Cannot add BTC after paying out PPY + BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000, btc_id ) ), fc::assert_exception ); + + helper.commit(); + + BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ann_ppy, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( audrey_ppy, get_balance( audrey_id, asset_id_type() ) ); + } + + { + const betting_market_group_object& game = db.create( [btc_id]( betting_market_group_object& b ) { + b.asset_id = btc_id; + } ); + affiliate_payout_helper helper = affiliate_payout_helper( db, game ); + // 20% of 60 = 12: 7 to Alice, 5 to Ann + BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, asset( 60, btc_id ) ).value ); + alice_btc += 7; + ann_btc += 5; + // Cannot add PPY after paying out BTC + BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000 ) ), fc::assert_exception ); + helper.commit(); + BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( audrey_btc, get_balance( audrey_id, btc_id ) ); + } + + { + // Fix total supply + auto& index = db.get_index_type().indices().get(); + auto itr = index.find( boost::make_tuple( account_id_type(), asset_id_type() ) ); + BOOST_CHECK( itr != index.end() ); + db.modify( *itr, [alice_ppy,ann_ppy,audrey_ppy]( account_balance_object& bal ) { + bal.balance -= alice_ppy + ann_ppy + audrey_ppy; + }); + + itr = index.find( boost::make_tuple( irene_id, btc_id ) ); + BOOST_CHECK( itr != index.end() ); + db.modify( *itr, [alice_btc,ann_btc,audrey_btc]( account_balance_object& bal ) { + bal.balance -= alice_btc + ann_btc + audrey_btc; + }); + } +} + BOOST_AUTO_TEST_SUITE_END() From 517c86c60ff7a5f53faffa30e5a2d4e5bc4abac9 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 13 Apr 2018 14:45:17 +0200 Subject: [PATCH 09/23] Refactored handling of asset_id --- libraries/chain/affiliate_payout.cpp | 18 +++++---- libraries/chain/db_bet.cpp | 2 +- .../graphene/chain/affiliate_payout.hpp | 28 +++++++++++--- libraries/chain/tournament_object.cpp | 2 +- tests/tests/affiliate_tests.cpp | 38 +++++++------------ 5 files changed, 48 insertions(+), 40 deletions(-) diff --git a/libraries/chain/affiliate_payout.cpp b/libraries/chain/affiliate_payout.cpp index dac51760..328a0f64 100644 --- a/libraries/chain/affiliate_payout.cpp +++ b/libraries/chain/affiliate_payout.cpp @@ -29,12 +29,12 @@ namespace graphene { namespace chain { - share_type affiliate_payout_helper::payout( account_id_type player, const asset& amount ) + share_type affiliate_payout_helper::payout( account_id_type player, share_type amount ) { return payout( player(_db), amount ); } - share_type affiliate_payout_helper::payout( const account_object& player, const asset& amount ) + share_type affiliate_payout_helper::payout( const account_object& player, share_type amount ) { if( !player.affiliate_distributions.valid() ) return 0; @@ -42,12 +42,13 @@ namespace graphene { namespace chain { if( dist == player.affiliate_distributions->_dists.end() || dist->second._dist.empty() ) return 0; - share_type to_pay = amount.amount.value / 5; // 20% fixed - if( to_pay <= 0 ) + amount = amount.value / 5; // 20% fixed + if( amount <= 0 ) return 0; uint16_t remaining = GRAPHENE_100_PERCENT; share_type paid = 0; + share_type to_pay = amount; for( const auto& entry : dist->second._dist ) { const account_id_type affiliate = entry.first; @@ -65,16 +66,17 @@ namespace graphene { namespace chain { if( payout > 0 ) { if ( accumulator.find(affiliate) == accumulator.end() ) - accumulator[affiliate] = asset( payout.to_uint64(), amount.asset_id ); + accumulator[affiliate] = payout.to_uint64(); else - accumulator[affiliate] += asset( payout.to_uint64(), amount.asset_id ); + accumulator[affiliate] += payout.to_uint64(); to_pay -= payout.to_uint64(); paid += payout.to_uint64(); } } FC_ASSERT( to_pay == 0 ); + FC_ASSERT( paid == amount ); - _db.push_applied_operation( affiliate_referral_payout_operation( player.id, amount ) ); + _db.push_applied_operation( affiliate_referral_payout_operation( player.id, asset( amount, payout_asset ) ) ); return paid; } @@ -83,7 +85,7 @@ namespace graphene { namespace chain { { for( const auto& entry : accumulator ) { - _db.adjust_balance( entry.first, entry.second ); + _db.adjust_balance( entry.first, asset( entry.second, payout_asset ) ); _db.push_applied_operation( affiliate_payout_operation( entry.first, tag, entry.second ) ); } accumulator.clear(); diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index f4d34d49..0d7fdf26 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -265,7 +265,7 @@ void database::settle_betting_market_group(const betting_market_group_object& be { rake_amount = ((fc::uint128_t(net_profits.value) * rake_fee_percentage + GRAPHENE_100_PERCENT - 1) / GRAPHENE_100_PERCENT).to_uint64(); if (rake_amount.value) - rake_amount -= payout_helper.payout( bettor_id, asset( rake_amount, betting_market_group.asset_id ) ); + rake_amount -= payout_helper.payout( bettor_id, rake_amount ); FC_ASSERT( rake_amount.value >= 0 ); if (rake_amount.value) adjust_balance(*rake_account_id, asset(rake_amount, betting_market_group.asset_id)); diff --git a/libraries/chain/include/graphene/chain/affiliate_payout.hpp b/libraries/chain/include/graphene/chain/affiliate_payout.hpp index 2a58861a..1c0ea703 100644 --- a/libraries/chain/include/graphene/chain/affiliate_payout.hpp +++ b/libraries/chain/include/graphene/chain/affiliate_payout.hpp @@ -55,20 +55,36 @@ namespace graphene { namespace chain { return game.options.game_options.visit( impl::game_type_visitor() ); } + template + asset_id_type get_asset_for_game( const GAME& game ); + + template<> + inline asset_id_type get_asset_for_game( const betting_market_group_object& game ) + { + return game.asset_id; + } + + template<> + inline asset_id_type get_asset_for_game( const tournament_object& game ) + { + return game.options.buy_in.asset_id; + } + class affiliate_payout_helper { public: template affiliate_payout_helper( database& db, const GAME& game ) - : _db(db), tag( get_tag_for_game( game ) ) {} + : _db(db), tag( get_tag_for_game( game ) ), payout_asset( get_asset_for_game( game ) ) {} - share_type payout( account_id_type player, const asset& amount ); - share_type payout( const account_object& player, const asset& amount ); + share_type payout( account_id_type player, share_type amount ); + share_type payout( const account_object& player, share_type amount ); void commit(); private: - database& _db; - app_tag tag; - std::map accumulator; + database& _db; + app_tag tag; + asset_id_type payout_asset; + std::map accumulator; }; } } // graphene::chain diff --git a/libraries/chain/tournament_object.cpp b/libraries/chain/tournament_object.cpp index 0f0195aa..c1b53f79 100644 --- a/libraries/chain/tournament_object.cpp +++ b/libraries/chain/tournament_object.cpp @@ -337,7 +337,7 @@ namespace graphene { namespace chain { if (rake_amount.value) { affiliate_payout_helper payout_helper( event.db, tournament_obj ); - rake_amount -= payout_helper.payout( winner, asset( rake_amount, tournament_obj.options.buy_in.asset_id ) ); + rake_amount -= payout_helper.payout( winner, rake_amount ); payout_helper.commit(); FC_ASSERT( rake_amount.value >= 0 ); } diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index e291825d..3b09a135 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -216,30 +216,27 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) }); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // Alice has no distribution set - BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, asset(1000) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, 1000 ).value ); // Paula has nothing for Bookie - BOOST_CHECK_EQUAL( 0, helper.payout( paula_id, asset(1000) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( paula_id, 1000 ).value ); // 20% of 4 gets rounded down to 0 - BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, asset(4) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, 4 ).value ); // 20% of 5 = 1 is paid to Audrey - BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, asset(5) ).value ); + BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, 5 ).value ); audrey_ppy++; // 20% of 50 = 10: 2 to Alice, 3 to Ann, 5 to Audrey - BOOST_CHECK_EQUAL( 10, helper.payout( penny_id, asset(50) ).value ); + BOOST_CHECK_EQUAL( 10, helper.payout( penny_id, 50 ).value ); alice_ppy += 2; ann_ppy += 3; audrey_ppy += 5; // 20% of 59 = 11: 1 to Ann, 10 to Audrey - BOOST_CHECK_EQUAL( 11, helper.payout( petra_id, asset(59) ).value ); + BOOST_CHECK_EQUAL( 11, helper.payout( petra_id, 59 ).value ); audrey_ppy += 10; ann_ppy += 1; - // Cannot add BTC after paying out PPY - BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000, btc_id ) ), fc::assert_exception ); - helper.commit(); BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); @@ -254,12 +251,10 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) }); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // 20% of 60 = 12: 2 to Alice, 3 to Ann, 7 to Audrey - BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, asset( 60, btc_id ) ).value ); + BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, 60 ).value ); alice_btc += 2; ann_btc += 3; audrey_btc += 7; - // Cannot add PPY after paying out BTC - BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000 ) ), fc::assert_exception ); helper.commit(); BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); @@ -272,31 +267,28 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) } ); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // Alice has no distribution set - BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, asset(1000) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, 1000 ).value ); // Petra has nothing for Bookie - BOOST_CHECK_EQUAL( 0, helper.payout( petra_id, asset(1000) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( petra_id, 1000 ).value ); // 20% of 4 gets rounded down to 0 - BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, asset(4) ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, 4 ).value ); // 20% of 5 = 1 is paid to Ann - BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, asset(5) ).value ); + BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, 5 ).value ); ann_ppy++; // 20% of 40 = 8: 8 to Alice - BOOST_CHECK_EQUAL( 8, helper.payout( paula_id, asset(40) ).value ); + BOOST_CHECK_EQUAL( 8, helper.payout( paula_id, 40 ).value ); alice_ppy += 8; // intermediate commit should clear internal accumulator helper.commit(); // 20% of 59 = 11: 6 to Alice, 5 to Ann - BOOST_CHECK_EQUAL( 11, helper.payout( penny_id, asset(59) ).value ); + BOOST_CHECK_EQUAL( 11, helper.payout( penny_id, 59 ).value ); alice_ppy += 6; ann_ppy += 5; - // Cannot add BTC after paying out PPY - BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000, btc_id ) ), fc::assert_exception ); - helper.commit(); BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); @@ -310,11 +302,9 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) } ); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // 20% of 60 = 12: 7 to Alice, 5 to Ann - BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, asset( 60, btc_id ) ).value ); + BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, 60 ).value ); alice_btc += 7; ann_btc += 5; - // Cannot add PPY after paying out BTC - BOOST_CHECK_THROW( helper.payout( penny_id, asset( 1000 ) ), fc::assert_exception ); helper.commit(); BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); From d25604e467c4272cf4161982bb6dda1b58b8821f Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 13 Apr 2018 15:29:38 +0200 Subject: [PATCH 10/23] Refactored tournament_helper into common files --- tests/common/tournament_helper.cpp | 405 +++++++++++++++++++++++++ tests/common/tournament_helper.hpp | 116 +++++++ tests/tournament/tournament_tests.cpp | 417 +------------------------- 3 files changed, 522 insertions(+), 416 deletions(-) create mode 100644 tests/common/tournament_helper.cpp create mode 100644 tests/common/tournament_helper.hpp diff --git a/tests/common/tournament_helper.cpp b/tests/common/tournament_helper.cpp new file mode 100644 index 00000000..9ad98626 --- /dev/null +++ b/tests/common/tournament_helper.cpp @@ -0,0 +1,405 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "tournament_helper.hpp" + +#include +#include +#include + +#include + +using namespace graphene::chain; + +tournaments_helper::tournaments_helper(database_fixture& df) : df(df) +{ + assets.insert(asset_id_type()); + current_asset_idx = 0; + optional dividend_account = get_asset_dividend_account(asset_id_type()); + if (dividend_account.valid()) + players.insert(*dividend_account); +} + +const std::set& tournaments_helper::list_tournaments()const +{ + return tournaments; +} + +const std::map> tournaments_helper::list_players_balances()const +{ + std::map> result; + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + asset a = df.db.get_balance(player_id, asset_id); + result[player_id][a.asset_id] = a.amount; + } + } + return result; +} + +const std::map> tournaments_helper::get_players_fees()const +{ + return players_fees; +} + +void tournaments_helper::reset_players_fees() +{ + for (account_id_type player_id: players) + { + for( asset_id_type asset_id: assets) + { + players_fees[player_id][asset_id] = 0; + } + } +} + +void tournaments_helper::create_asset(const account_id_type& issuer_account_id, + const string& symbol, + uint8_t precision, + asset_options& common, + const fc::ecc::private_key& sig_priv_key) +{ + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + asset_create_operation op; + op.issuer = issuer_account_id; + op.symbol = symbol; + op.precision = precision; + op.common_options = common; + + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + assets.insert(asset_id_type(++current_asset_idx)); +} + +void tournaments_helper::update_dividend_asset(const asset_id_type asset_to_update_id, + dividend_asset_options new_options, + const fc::ecc::private_key& sig_priv_key) +{ + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + asset_update_dividend_operation update_op; + + update_op.issuer = asset_to_update_id(db).issuer; + update_op.asset_to_update = asset_to_update_id; + update_op.new_options = new_options; + + tx.operations = {update_op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + optional dividend_account = get_asset_dividend_account(asset_to_update_id); + if (dividend_account.valid()) + players.insert(*dividend_account); +} + +optional tournaments_helper::get_asset_dividend_account(const asset_id_type& asset_id)const +{ + graphene::chain::database& db = df.db; + optional result; + const asset_object& asset_obj = asset_id(db); + + if (asset_obj.dividend_data_id.valid()) + { + const asset_dividend_data_object& dividend_data = (*asset_obj.dividend_data_id)(db); + result = dividend_data.dividend_distribution_account; + } + return result; +} + +const tournament_id_type tournaments_helper::create_tournament (const account_id_type& creator, + const fc::ecc::private_key& sig_priv_key, + asset buy_in, + uint32_t number_of_players, + uint32_t time_per_commit_move, + uint32_t time_per_reveal_move, + uint32_t number_of_wins, + int32_t registration_deadline, + uint32_t start_delay, + uint32_t round_delay, + bool insurance_enabled, + uint32_t number_of_gestures, + uint32_t start_time, + fc::optional > whitelist + ) +{ + if (current_tournament_idx.valid()) + current_tournament_idx = *current_tournament_idx + 1; + else + current_tournament_idx = 0; + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction trx; + tournament_options options; + tournament_create_operation op; + rock_paper_scissors_game_options& game_options = options.game_options.get(); + + game_options.number_of_gestures = number_of_gestures; + game_options.time_per_commit_move = time_per_commit_move; + game_options.time_per_reveal_move = time_per_reveal_move; + game_options.insurance_enabled = insurance_enabled; + + options.registration_deadline = db.head_block_time() + fc::seconds(registration_deadline + *current_tournament_idx); + options.buy_in = buy_in; + options.number_of_players = number_of_players; + if (start_delay) + options.start_delay = start_delay; + if (start_time) + options.start_time = db.head_block_time() + fc::seconds(start_time); + options.round_delay = round_delay; + options.number_of_wins = number_of_wins; + if (whitelist.valid()) + options.whitelist = *whitelist; + + op.creator = creator; + op.options = options; + trx.operations = {op}; + for( auto& op : trx.operations ) + db.current_fee_schedule().set_fee(op); + trx.validate(); + trx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(trx, sig_priv_key); + PUSH_TX(db, trx); + + tournament_id_type tournament_id = tournament_id_type(*current_tournament_idx); + tournaments.insert(tournament_id); + return tournament_id; +} + +void tournaments_helper::join_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& payer_id, + const fc::ecc::private_key& sig_priv_key, + asset buy_in + ) +{ + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + tournament_join_operation op; + + op.payer_account_id = payer_id; + op.buy_in = buy_in; + op.player_account_id = player_id; + op.tournament_id = tournament_id; + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + players.insert(player_id); + players_keys[player_id] = sig_priv_key; +} + +void tournaments_helper::leave_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& canceling_account_id, + const fc::ecc::private_key& sig_priv_key + ) +{ + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + signed_transaction tx; + tournament_leave_operation op; + + op.canceling_account_id = canceling_account_id; + op.player_account_id = player_id; + op.tournament_id = tournament_id; + tx.operations = {op}; + for( auto& op : tx.operations ) + db.current_fee_schedule().set_fee(op); + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + PUSH_TX(db, tx); + + //players.erase(player_id); +} + +// stolen from cli_wallet +void tournaments_helper::rps_throw(const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key + ) +{ + + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + // check whether the gesture is appropriate for the game we're playing + game_object game_obj = game_id(db); + match_object match_obj = game_obj.match_id(db); + tournament_object tournament_obj = match_obj.tournament_id(db); + rock_paper_scissors_game_options game_options = tournament_obj.options.game_options.get(); + assert((int)gesture < game_options.number_of_gestures); + + account_object player_account_obj = player_id(db); + + // construct the complete throw, the commit, and reveal + rock_paper_scissors_throw full_throw; + fc::rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1)); + fc::rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2)); + full_throw.gesture = gesture; + + rock_paper_scissors_throw_commit commit_throw; + commit_throw.nonce1 = full_throw.nonce1; + std::vector full_throw_packed(fc::raw::pack(full_throw)); + commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); + + rock_paper_scissors_throw_reveal reveal_throw; + reveal_throw.nonce2 = full_throw.nonce2; + reveal_throw.gesture = full_throw.gesture; + + // store off the reveal for applying after both players commit + committed_game_moves[commit_throw] = reveal_throw; + + signed_transaction tx; + game_move_operation move_operation; + move_operation.game_id = game_id; + move_operation.player_account_id = player_account_obj.id; + move_operation.move = commit_throw; + tx.operations = {move_operation}; + for( operation& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, sig_priv_key); + if (/*match_obj.match_winners.empty() &&*/ game_obj.get_state() == game_state::expecting_commit_moves) // checking again + PUSH_TX(db, tx); +} + +// spaghetti programming +// walking through all tournaments, matches and games and throwing random moves +// optionaly skip generting randomly selected moves +// every_move_is >= 0 : every game is tie +void tournaments_helper::play_games(unsigned skip_some_commits, unsigned skip_some_reveals, int every_move_is) +{ + //try + //{ + graphene::chain::database& db = df.db; + const chain_parameters& params = db.get_global_properties().parameters; + + for(const auto& tournament_id: tournaments) + { + const tournament_object& tournament = tournament_id(db); + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + rock_paper_scissors_game_options game_options = tournament.options.game_options.get(); + for(const auto& match_id: tournament_details.matches) + { + const match_object& match = match_id(db); + for(const auto& game_id: match.games ) + { + const game_object& game = game_id(db); + const rock_paper_scissors_game_details& rps_details = game.game_details.get(); + if (game.get_state() == game_state::expecting_commit_moves) + { + for(const auto& player_id: game.players) + { + if ( players_keys.find(player_id) != players_keys.end()) + { + if (!skip_some_commits || player_id.instance.value % skip_some_commits != game_id.instance.value % skip_some_commits) + { + auto iter = std::find(game.players.begin(), game.players.end(), player_id); + unsigned player_index = std::distance(game.players.begin(), iter); + if (!rps_details.commit_moves.at(player_index)) + rps_throw(game_id, player_id, + (rock_paper_scissors_gesture) (every_move_is >= 0 ? every_move_is : (std::rand() % game_options.number_of_gestures)), players_keys[player_id]); + } + } + } + } + else if (game.get_state() == game_state::expecting_reveal_moves) + { + for (unsigned i = 0; i < 2; ++i) + { + if (rps_details.commit_moves.at(i) && + !rps_details.reveal_moves.at(i)) + { + const account_id_type& player_id = game.players[i]; + if (players_keys.find(player_id) != players_keys.end()) + { + { + + auto iter = committed_game_moves.find(*rps_details.commit_moves.at(i)); + if (iter != committed_game_moves.end()) + { + if (!skip_some_reveals || player_id.instance.value % skip_some_reveals != game_id.instance.value % skip_some_reveals) + { + const rock_paper_scissors_throw_reveal& reveal = iter->second; + + game_move_operation move_operation; + move_operation.game_id = game.id; + move_operation.player_account_id = player_id; + move_operation.move = reveal; + + signed_transaction tx; + tx.operations = {move_operation}; + for( auto& op : tx.operations ) + { + asset f = db.current_fee_schedule().set_fee(op); + players_fees[player_id][f.asset_id] -= f.amount; + } + tx.validate(); + tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); + df.sign(tx, players_keys[player_id]); + if (game.get_state() == game_state::expecting_reveal_moves) // check again + PUSH_TX(db, tx); + } + } + } + } + } + } + } + } + } + } +//} +//catch (fc::exception& e) +//{ +// edump((e.to_detail_string())); +// throw; +//} +} diff --git a/tests/common/tournament_helper.hpp b/tests/common/tournament_helper.hpp new file mode 100644 index 00000000..ee8c70f6 --- /dev/null +++ b/tests/common/tournament_helper.hpp @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; + +// class performing operations necessary for creating tournaments, +// having players join the tournaments and playing tournaments to completion. +class tournaments_helper +{ +public: + + tournaments_helper(database_fixture& df); + + const std::set& list_tournaments()const; + + const std::map> list_players_balances()const; + + const std::map> get_players_fees()const; + + void reset_players_fees(); + + void create_asset(const account_id_type& issuer_account_id, + const string& symbol, + uint8_t precision, + asset_options& common, + const fc::ecc::private_key& sig_priv_key); + + void update_dividend_asset(const asset_id_type asset_to_update_id, + dividend_asset_options new_options, + const fc::ecc::private_key& sig_priv_key); + + optional get_asset_dividend_account(const asset_id_type& asset_id)const; + + const tournament_id_type create_tournament (const account_id_type& creator, + const fc::ecc::private_key& sig_priv_key, + asset buy_in, + uint32_t number_of_players = 2, + uint32_t time_per_commit_move = 3, + uint32_t time_per_reveal_move = 1, + uint32_t number_of_wins = 3, + int32_t registration_deadline = 3600, + uint32_t start_delay = 3, + uint32_t round_delay = 3, + bool insurance_enabled = false, + uint32_t number_of_gestures = 3, + uint32_t start_time = 0, + fc::optional > whitelist = fc::optional >() + ); + + void join_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& payer_id, + const fc::ecc::private_key& sig_priv_key, + asset buy_in + ); + + void leave_tournament(const tournament_id_type & tournament_id, + const account_id_type& player_id, + const account_id_type& canceling_account_id, + const fc::ecc::private_key& sig_priv_key + ); + + // stolen from cli_wallet + void rps_throw(const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key + ); + + // spaghetti programming + // walking through all tournaments, matches and games and throwing random moves + // optionaly skip generting randomly selected moves + // every_move_is >= 0 : every game is tie + void play_games(unsigned skip_some_commits = 0, unsigned skip_some_reveals = 0, int every_move_is = -1); + +private: + database_fixture& df; + // index of last created tournament + fc::optional current_tournament_idx; + // index of last asset + uint64_t current_asset_idx; + // assets : core and maybe others + std::set assets; + // tournaments to be played + std::set tournaments; + // all players registered in tournaments + std::set players; + // players' private keys + std::map players_keys; + // total charges for moves made by every player + std::map> players_fees; + // store of commits and reveals + std::map committed_game_moves; +}; diff --git a/tests/tournament/tournament_tests.cpp b/tests/tournament/tournament_tests.cpp index 6b2f2f64..8aa88479 100644 --- a/tests/tournament/tournament_tests.cpp +++ b/tests/tournament/tournament_tests.cpp @@ -28,7 +28,7 @@ #include #include #include -#include "../common/database_fixture.hpp" +#include "../common/tournament_helper.hpp" #include #include #include @@ -44,421 +44,6 @@ using namespace graphene::chain; BOOST_AUTO_TEST_SUITE(tournament_tests) -// class performing operations necessary for creating tournaments, -// having players join the tournaments and playing tournaments to completion. -class tournaments_helper -{ -public: - - tournaments_helper(database_fixture& df) : df(df) - { - assets.insert(asset_id_type()); - current_asset_idx = 0; - optional dividend_account = get_asset_dividend_account(asset_id_type()); - if (dividend_account.valid()) - players.insert(*dividend_account); - } - - const std::set& list_tournaments() - { - return tournaments; - } - - std::map> list_players_balances() - { - std::map> result; - for (account_id_type player_id: players) - { - for( asset_id_type asset_id: assets) - { - asset a = df.db.get_balance(player_id, asset_id); - result[player_id][a.asset_id] = a.amount; - } - } - return result; - } - - std::map> get_players_fees() - { - return players_fees; - } - - void reset_players_fees() - { - for (account_id_type player_id: players) - { - for( asset_id_type asset_id: assets) - { - players_fees[player_id][asset_id] = 0; - } - } - } - - void create_asset(const account_id_type& issuer_account_id, - const string& symbol, - uint8_t precision, - asset_options& common, - const fc::ecc::private_key& sig_priv_key) - { - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - signed_transaction tx; - asset_create_operation op; - op.issuer = issuer_account_id; - op.symbol = symbol; - op.precision = precision; - op.common_options = common; - - tx.operations = {op}; - for( auto& op : tx.operations ) - db.current_fee_schedule().set_fee(op); - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, sig_priv_key); - PUSH_TX(db, tx); - - assets.insert(asset_id_type(++current_asset_idx)); - } - - void update_dividend_asset(const asset_id_type asset_to_update_id, - dividend_asset_options new_options, - const fc::ecc::private_key& sig_priv_key) - { - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - signed_transaction tx; - asset_update_dividend_operation update_op; - - update_op.issuer = asset_to_update_id(db).issuer; - update_op.asset_to_update = asset_to_update_id; - update_op.new_options = new_options; - - tx.operations = {update_op}; - for( auto& op : tx.operations ) - db.current_fee_schedule().set_fee(op); - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, sig_priv_key); - PUSH_TX(db, tx); - - optional dividend_account = get_asset_dividend_account(asset_to_update_id); - if (dividend_account.valid()) - players.insert(*dividend_account); - } - - optional get_asset_dividend_account(const asset_id_type& asset_id) - { - graphene::chain::database& db = df.db; - optional result; - const asset_object& asset_obj = asset_id(db); - - if (asset_obj.dividend_data_id.valid()) - { - const asset_dividend_data_object& dividend_data = (*asset_obj.dividend_data_id)(db); - result = dividend_data.dividend_distribution_account; - } - return result; - } - - const tournament_id_type create_tournament (const account_id_type& creator, - const fc::ecc::private_key& sig_priv_key, - asset buy_in, - uint32_t number_of_players = 2, - uint32_t time_per_commit_move = 3, - uint32_t time_per_reveal_move = 1, - uint32_t number_of_wins = 3, - int32_t registration_deadline = 3600, - uint32_t start_delay = 3, - uint32_t round_delay = 3, - bool insurance_enabled = false, - uint32_t number_of_gestures = 3, - uint32_t start_time = 0, - fc::optional > whitelist = fc::optional >() - ) - { - if (current_tournament_idx.valid()) - current_tournament_idx = *current_tournament_idx + 1; - else - current_tournament_idx = 0; - - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - signed_transaction trx; - tournament_options options; - tournament_create_operation op; - rock_paper_scissors_game_options& game_options = options.game_options.get(); - - game_options.number_of_gestures = number_of_gestures; - game_options.time_per_commit_move = time_per_commit_move; - game_options.time_per_reveal_move = time_per_reveal_move; - game_options.insurance_enabled = insurance_enabled; - - options.registration_deadline = db.head_block_time() + fc::seconds(registration_deadline + *current_tournament_idx); - options.buy_in = buy_in; - options.number_of_players = number_of_players; - if (start_delay) - options.start_delay = start_delay; - if (start_time) - options.start_time = db.head_block_time() + fc::seconds(start_time); - options.round_delay = round_delay; - options.number_of_wins = number_of_wins; - if (whitelist.valid()) - options.whitelist = *whitelist; - - op.creator = creator; - op.options = options; - trx.operations = {op}; - for( auto& op : trx.operations ) - db.current_fee_schedule().set_fee(op); - trx.validate(); - trx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(trx, sig_priv_key); - PUSH_TX(db, trx); - - tournament_id_type tournament_id = tournament_id_type(*current_tournament_idx); - tournaments.insert(tournament_id); - return tournament_id; - } - - void join_tournament(const tournament_id_type & tournament_id, - const account_id_type& player_id, - const account_id_type& payer_id, - const fc::ecc::private_key& sig_priv_key, - asset buy_in - ) - { - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - signed_transaction tx; - tournament_join_operation op; - - op.payer_account_id = payer_id; - op.buy_in = buy_in; - op.player_account_id = player_id; - op.tournament_id = tournament_id; - tx.operations = {op}; - for( auto& op : tx.operations ) - db.current_fee_schedule().set_fee(op); - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, sig_priv_key); - PUSH_TX(db, tx); - - players.insert(player_id); - players_keys[player_id] = sig_priv_key; - } - - void leave_tournament(const tournament_id_type & tournament_id, - const account_id_type& player_id, - const account_id_type& canceling_account_id, - const fc::ecc::private_key& sig_priv_key - ) - { - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - signed_transaction tx; - tournament_leave_operation op; - - op.canceling_account_id = canceling_account_id; - op.player_account_id = player_id; - op.tournament_id = tournament_id; - tx.operations = {op}; - for( auto& op : tx.operations ) - db.current_fee_schedule().set_fee(op); - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, sig_priv_key); - PUSH_TX(db, tx); - - //players.erase(player_id); - } - - - - // stolen from cli_wallet - void rps_throw(const game_id_type& game_id, - const account_id_type& player_id, - rock_paper_scissors_gesture gesture, - const fc::ecc::private_key& sig_priv_key - ) - { - - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - - // check whether the gesture is appropriate for the game we're playing - game_object game_obj = game_id(db); - match_object match_obj = game_obj.match_id(db); - tournament_object tournament_obj = match_obj.tournament_id(db); - rock_paper_scissors_game_options game_options = tournament_obj.options.game_options.get(); - assert((int)gesture < game_options.number_of_gestures); - - account_object player_account_obj = player_id(db); - - // construct the complete throw, the commit, and reveal - rock_paper_scissors_throw full_throw; - rand_bytes((char*)&full_throw.nonce1, sizeof(full_throw.nonce1)); - rand_bytes((char*)&full_throw.nonce2, sizeof(full_throw.nonce2)); - full_throw.gesture = gesture; - - rock_paper_scissors_throw_commit commit_throw; - commit_throw.nonce1 = full_throw.nonce1; - std::vector full_throw_packed(fc::raw::pack(full_throw)); - commit_throw.throw_hash = fc::sha256::hash(full_throw_packed.data(), full_throw_packed.size()); - - rock_paper_scissors_throw_reveal reveal_throw; - reveal_throw.nonce2 = full_throw.nonce2; - reveal_throw.gesture = full_throw.gesture; - - // store off the reveal for applying after both players commit - committed_game_moves[commit_throw] = reveal_throw; - - signed_transaction tx; - game_move_operation move_operation; - move_operation.game_id = game_id; - move_operation.player_account_id = player_account_obj.id; - move_operation.move = commit_throw; - tx.operations = {move_operation}; - for( operation& op : tx.operations ) - { - asset f = db.current_fee_schedule().set_fee(op); - players_fees[player_id][f.asset_id] -= f.amount; - } - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, sig_priv_key); - if (/*match_obj.match_winners.empty() &&*/ game_obj.get_state() == game_state::expecting_commit_moves) // checking again - PUSH_TX(db, tx); - } - - // spaghetti programming - // walking through all tournaments, matches and games and throwing random moves - // optionaly skip generting randomly selected moves - // every_move_is >= 0 : every game is tie - void play_games(unsigned skip_some_commits = 0, unsigned skip_some_reveals = 0, int every_move_is = -1) - { - //try - //{ - graphene::chain::database& db = df.db; - const chain_parameters& params = db.get_global_properties().parameters; - - for(const auto& tournament_id: tournaments) - { - const tournament_object& tournament = tournament_id(db); - const tournament_details_object& tournament_details = tournament.tournament_details_id(db); - rock_paper_scissors_game_options game_options = tournament.options.game_options.get(); - for(const auto& match_id: tournament_details.matches) - { - const match_object& match = match_id(db); - for(const auto& game_id: match.games ) - { - const game_object& game = game_id(db); - const rock_paper_scissors_game_details& rps_details = game.game_details.get(); - if (game.get_state() == game_state::expecting_commit_moves) - { - for(const auto& player_id: game.players) - { - if ( players_keys.find(player_id) != players_keys.end()) - { - if (!skip_some_commits || player_id.instance.value % skip_some_commits != game_id.instance.value % skip_some_commits) - { - auto iter = std::find(game.players.begin(), game.players.end(), player_id); - unsigned player_index = std::distance(game.players.begin(), iter); - if (!rps_details.commit_moves.at(player_index)) - rps_throw(game_id, player_id, - (rock_paper_scissors_gesture) (every_move_is >= 0 ? every_move_is : (std::rand() % game_options.number_of_gestures)), players_keys[player_id]); - } - } - } - } - else if (game.get_state() == game_state::expecting_reveal_moves) - { - for (unsigned i = 0; i < 2; ++i) - { - if (rps_details.commit_moves.at(i) && - !rps_details.reveal_moves.at(i)) - { - const account_id_type& player_id = game.players[i]; - if (players_keys.find(player_id) != players_keys.end()) - { - { - - auto iter = committed_game_moves.find(*rps_details.commit_moves.at(i)); - if (iter != committed_game_moves.end()) - { - if (!skip_some_reveals || player_id.instance.value % skip_some_reveals != game_id.instance.value % skip_some_reveals) - { - const rock_paper_scissors_throw_reveal& reveal = iter->second; - - game_move_operation move_operation; - move_operation.game_id = game.id; - move_operation.player_account_id = player_id; - move_operation.move = reveal; - - signed_transaction tx; - tx.operations = {move_operation}; - for( auto& op : tx.operations ) - { - asset f = db.current_fee_schedule().set_fee(op); - players_fees[player_id][f.asset_id] -= f.amount; - } - tx.validate(); - tx.set_expiration(db.head_block_time() + fc::seconds( params.block_interval * (params.maintenance_skip_slots + 1) * 3)); - df.sign(tx, players_keys[player_id]); - if (game.get_state() == game_state::expecting_reveal_moves) // check again - PUSH_TX(db, tx); - } - } - } - } - } - } - } - } - } - } - //} - //catch (fc::exception& e) - //{ - // edump((e.to_detail_string())); - // throw; - //} - } - -private: - database_fixture& df; - // index of last created tournament - fc::optional current_tournament_idx; - // index of last asset - uint64_t current_asset_idx; - // assets : core and maybe others - std::set assets; - // tournaments to be played - std::set tournaments; - // all players registered in tournaments - std::set players; - // players' private keys - std::map players_keys; - // total charges for moves made by every player - std::map> players_fees; - // store of commits and reveals - std::map committed_game_moves; - - // taken from rand.cpp - void rand_bytes(char* buf, int count) - { - fc::init_openssl(); - - int result = RAND_bytes((unsigned char*)buf, count); - if (result != 1) - FC_THROW("Error calling OpenSSL's RAND_bytes(): ${code}", ("code", (uint32_t)ERR_get_error())); - } -}; - -/// Expecting assertion - -// creating tournament - BOOST_FIXTURE_TEST_CASE( Registration_deadline_has_already, database_fixture ) { try From 55fb6c4ce23e7344ebc6d84629663182f62f410c Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sun, 15 Apr 2018 10:18:41 +0200 Subject: [PATCH 11/23] Test affiliate payout in RPS tournament --- tests/common/tournament_helper.cpp | 32 +++++- tests/common/tournament_helper.hpp | 11 +- tests/tests/affiliate_tests.cpp | 161 +++++++++++++++++++++++++---- 3 files changed, 182 insertions(+), 22 deletions(-) diff --git a/tests/common/tournament_helper.cpp b/tests/common/tournament_helper.cpp index 9ad98626..4cb27b08 100644 --- a/tests/common/tournament_helper.cpp +++ b/tests/common/tournament_helper.cpp @@ -131,7 +131,7 @@ optional tournaments_helper::get_asset_dividend_account(const a { graphene::chain::database& db = df.db; optional result; - const asset_object& asset_obj = asset_id(db); + const asset_object& asset_obj = asset_id_type()(db); if (asset_obj.dividend_data_id.valid()) { @@ -270,7 +270,7 @@ void tournaments_helper::rps_throw(const game_id_type& game_id, match_object match_obj = game_obj.match_id(db); tournament_object tournament_obj = match_obj.tournament_id(db); rock_paper_scissors_game_options game_options = tournament_obj.options.game_options.get(); - assert((int)gesture < game_options.number_of_gestures); + FC_ASSERT( (int)gesture < game_options.number_of_gestures ); account_object player_account_obj = player_id(db); @@ -291,6 +291,7 @@ void tournaments_helper::rps_throw(const game_id_type& game_id, // store off the reveal for applying after both players commit committed_game_moves[commit_throw] = reveal_throw; + latest_committs[player_account_obj.id] = commit_throw; signed_transaction tx; game_move_operation move_operation; @@ -310,6 +311,33 @@ void tournaments_helper::rps_throw(const game_id_type& game_id, PUSH_TX(db, tx); } +void tournaments_helper::rps_reveal( const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key ) +{ + graphene::chain::database& db = df.db; + + FC_ASSERT( latest_committs.find(player_id) != latest_committs.end() ); + const auto& reveal = committed_game_moves.find( latest_committs[player_id] ); + FC_ASSERT( reveal != committed_game_moves.end() ); + FC_ASSERT( reveal->second.gesture == gesture ); + + game_move_operation move_operation; + move_operation.game_id = game_id; + move_operation.player_account_id = player_id; + move_operation.move = reveal->second; + + signed_transaction tx; + tx.operations.push_back( move_operation ); + const asset f = db.current_fee_schedule().set_fee( tx.operations[0] ); + players_fees[player_id][f.asset_id] -= f.amount; + tx.validate(); + test::set_expiration( db, tx ); + df.sign( tx, sig_priv_key ); + PUSH_TX(db, tx); +} + // spaghetti programming // walking through all tournaments, matches and games and throwing random moves // optionaly skip generting randomly selected moves diff --git a/tests/common/tournament_helper.hpp b/tests/common/tournament_helper.hpp index ee8c70f6..f9ff6282 100644 --- a/tests/common/tournament_helper.hpp +++ b/tests/common/tournament_helper.hpp @@ -51,9 +51,9 @@ public: dividend_asset_options new_options, const fc::ecc::private_key& sig_priv_key); - optional get_asset_dividend_account(const asset_id_type& asset_id)const; + optional get_asset_dividend_account( const asset_id_type& asset_id = asset_id_type() )const; - const tournament_id_type create_tournament (const account_id_type& creator, + const tournament_id_type create_tournament( const account_id_type& creator, const fc::ecc::private_key& sig_priv_key, asset buy_in, uint32_t number_of_players = 2, @@ -89,6 +89,11 @@ public: const fc::ecc::private_key& sig_priv_key ); + void rps_reveal( const game_id_type& game_id, + const account_id_type& player_id, + rock_paper_scissors_gesture gesture, + const fc::ecc::private_key& sig_priv_key ); + // spaghetti programming // walking through all tournaments, matches and games and throwing random moves // optionaly skip generting randomly selected moves @@ -113,4 +118,6 @@ private: std::map> players_fees; // store of commits and reveals std::map committed_game_moves; + // store of latest commits, for use by rps_reveal + std::map latest_committs; }; diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 3b09a135..8926ec39 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -24,26 +24,13 @@ #include -#include -#include -#include -#include +#include "../common/tournament_helper.hpp" -#include -#include -#include -#include #include - -#include - -#include -#include -#include -#include "../common/database_fixture.hpp" - -#include -#include +#include +#include +#include +#include using namespace graphene::chain; using namespace graphene::db; @@ -328,4 +315,142 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) } } +BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) +{ try { + ACTORS( (alice)(ann)(audrey)(martha) ); + + generate_blocks( HARDFORK_999_TIME ); + generate_block(); + + test::set_expiration( db, trx ); + trx.clear(); + + // Paula: 100% to Alice for Bookie / nothing for RPS + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + trx.operations.push_back( aco ); + + // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey + const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); + aco = make_account( "penny", penny_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; + trx.operations.push_back( aco ); + + // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey + const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); + aco = make_account( "petra", petra_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; + trx.operations.push_back( aco ); + processed_transaction result = db.push_transaction(trx, ~0); + trx.clear(); + + account_id_type paula_id = result.operation_results[0].get(); + account_id_type penny_id = result.operation_results[1].get(); + account_id_type petra_id = result.operation_results[2].get(); + + fund( martha_id(db), asset(1000000000) ); + fund( paula_id(db), asset(20000000) ); + fund( penny_id(db), asset(30000000) ); + fund( petra_id(db), asset(40000000) ); + + upgrade_to_lifetime_member( martha ); + + tournaments_helper helper(*this); + account_id_type dividend_id = *helper.get_asset_dividend_account(); + int64_t div_ppy = get_balance( dividend_id, asset_id_type() ); + const asset buy_in = asset(12000); + tournament_id_type tournament_id = helper.create_tournament( martha_id, martha_private_key, buy_in, 3, 3, 1, 1 ); + BOOST_REQUIRE(tournament_id == tournament_id_type()); + + helper.join_tournament( tournament_id, paula_id, paula_id, paula_private_key, buy_in); + helper.join_tournament( tournament_id, penny_id, penny_id, penny_private_key, buy_in); + helper.join_tournament( tournament_id, petra_id, petra_id, petra_private_key, buy_in); + + generate_block(); + const tournament_object& tournament = db.get( tournament_id ); + + BOOST_CHECK_EQUAL( 19988000, get_balance( paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 29988000, get_balance( penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 39988000, get_balance( petra_id, asset_id_type() ) ); + + const tournament_details_object& tournament_details = tournament.tournament_details_id(db); + BOOST_CHECK_EQUAL( 3, tournament_details.matches.size() ); + + { // 3 players, match 1 is a bye for penny + const match_object& match = tournament_details.matches[0](db); + BOOST_CHECK_EQUAL( int64_t(match_state::match_complete), int64_t(match.get_state()) ); + BOOST_CHECK_EQUAL( 1, match.players.size() ); + BOOST_CHECK_EQUAL( object_id_type(penny_id).instance(), object_id_type(match.players[0]).instance() ); + } + { // match 2 is paula vs. petra: paula wins + const match_object& match = tournament_details.matches[1](db); + BOOST_CHECK_EQUAL( int64_t(match_state::match_in_progress), int64_t(match.get_state()) ); + BOOST_CHECK_EQUAL( 2, match.players.size() ); + BOOST_CHECK_EQUAL( object_id_type(paula_id).instance(), object_id_type(match.players[0]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(petra_id).instance(), object_id_type(match.players[1]).instance() ); + BOOST_CHECK_EQUAL( 1, match.games.size() ); + + const game_object& game = match.games[0](db); + BOOST_CHECK_EQUAL( int64_t(game_state::expecting_commit_moves), int64_t(game.get_state()) ); + helper.rps_throw( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); + helper.rps_throw( game.id, petra_id, rock_paper_scissors_gesture::rock, petra_private_key ); + + BOOST_CHECK_EQUAL( int64_t(game_state::expecting_reveal_moves), int64_t(game.get_state()) ); + helper.rps_reveal( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); + helper.rps_reveal( game.id, petra_id, rock_paper_scissors_gesture::rock, petra_private_key ); + + BOOST_CHECK_EQUAL( int64_t(game_state::game_complete), int64_t(game.get_state()) ); + BOOST_CHECK_EQUAL( 1, match.games.size() ); + BOOST_CHECK_EQUAL( int64_t(match_state::match_complete), int64_t(match.get_state()) ); + } + { // match 3 is penny vs. paula: penny wins + const match_object& match = tournament_details.matches[2](db); + BOOST_CHECK_EQUAL( int64_t(match_state::match_in_progress), int64_t(match.get_state()) ); + BOOST_CHECK_EQUAL( 2, match.players.size() ); + BOOST_CHECK_EQUAL( object_id_type(penny_id).instance(), object_id_type(match.players[0]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(paula_id).instance(), object_id_type(match.players[1]).instance() ); + BOOST_CHECK_EQUAL( 1, match.games.size() ); + + const game_object& game = match.games[0](db); + BOOST_CHECK_EQUAL( int64_t(game_state::expecting_commit_moves), int64_t(game.get_state()) ); + helper.rps_throw( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); + helper.rps_throw( game.id, penny_id, rock_paper_scissors_gesture::scissors, penny_private_key ); + + BOOST_CHECK_EQUAL( int64_t(game_state::expecting_reveal_moves), int64_t(game.get_state()) ); + helper.rps_reveal( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); + helper.rps_reveal( game.id, penny_id, rock_paper_scissors_gesture::scissors, penny_private_key ); + + BOOST_CHECK_EQUAL( int64_t(game_state::game_complete), int64_t(game.get_state()) ); + BOOST_CHECK_EQUAL( int64_t(match_state::match_complete), int64_t(match.get_state()) ); + } + + BOOST_CHECK_EQUAL( 3*GRAPHENE_1_PERCENT, db.get_global_properties().parameters.rake_fee_percentage ); + // Penny wins net 3*buy_in minus rake = 36000 - 1080 = 34920 + BOOST_CHECK_EQUAL( 19988000, get_balance( paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 29988000 + 34920, get_balance( penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 39988000, get_balance( petra_id, asset_id_type() ) ); + + // Dividend account receives 80% of rake = 864 + BOOST_CHECK_EQUAL( div_ppy + 864, get_balance( dividend_id, asset_id_type() ) ); + + // 20% of rake = 216 is paid to Penny's affiliates: 43 to Alice, 64 to Ann, 109 to Audrey + BOOST_CHECK_EQUAL( 43, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 64, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 109, get_balance( audrey_id, asset_id_type() ) ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From d8828dc3a91a3c355ecc2b125afba00f324f8898 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 18 Apr 2018 15:43:58 +0200 Subject: [PATCH 12/23] Refactored betting market macros into common header --- tests/betting/betting_tests.cpp | 3 +- tests/common/betting_test_markets.hpp | 139 ++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 2 deletions(-) create mode 100644 tests/common/betting_test_markets.hpp diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index 2d83f3d5..484c4da4 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -28,7 +28,7 @@ #include #pragma GCC diagnostic pop -#include "../common/database_fixture.hpp" +#include "../common/betting_test_markets.hpp" #include #include @@ -48,7 +48,6 @@ #include #include -//#include struct enable_betting_logging_config { enable_betting_logging_config() diff --git a/tests/common/betting_test_markets.hpp b/tests/common/betting_test_markets.hpp new file mode 100644 index 00000000..75363daf --- /dev/null +++ b/tests/common/betting_test_markets.hpp @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include "database_fixture.hpp" + +#include +#include +#include +#include + +using namespace graphene::chain; + +#define CREATE_ICE_HOCKEY_BETTING_MARKET(never_in_play, delay_before_settling) \ + create_sport({{"en", "Ice Hockey"}, {"zh_Hans", "冰球"}, {"ja", "アイスホッケー"}}); \ + generate_blocks(1); \ + const sport_object& ice_hockey = *db.get_index_type().indices().get().rbegin(); \ + create_event_group({{"en", "NHL"}, {"zh_Hans", "國家冰球聯盟"}, {"ja", "ナショナルホッケーリーグ"}}, ice_hockey.id); \ + generate_blocks(1); \ + const event_group_object& nhl = *db.get_index_type().indices().get().rbegin(); \ + create_event({{"en", "Washington Capitals/Chicago Blackhawks"}, {"zh_Hans", "華盛頓首都隊/芝加哥黑鷹"}, {"ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"}}, {{"en", "2016-17"}}, nhl.id); \ + generate_blocks(1); \ + const event_object& capitals_vs_blackhawks = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_rules({{"en", "NHL Rules v1.0"}}, {{"en", "The winner will be the team with the most points at the end of the game. The team with fewer points will not be the winner."}}); \ + generate_blocks(1); \ + const betting_market_rules_object& betting_market_rules = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_group({{"en", "Moneyline"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type(), never_in_play, delay_before_settling); \ + generate_blocks(1); \ + const betting_market_group_object& moneyline_betting_markets = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_betting_markets.id, {{"en", "Washington Capitals win"}}); \ + generate_blocks(1); \ + const betting_market_object& capitals_win_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_betting_markets.id, {{"en", "Chicago Blackhawks win"}}); \ + generate_blocks(1); \ + const betting_market_object& blackhawks_win_market = *db.get_index_type().indices().get().rbegin(); \ + (void)capitals_win_market; (void)blackhawks_win_market; + +// create the basic betting market, plus groups for the first, second, and third period results +#define CREATE_EXTENDED_ICE_HOCKEY_BETTING_MARKET(never_in_play, delay_before_settling) \ + CREATE_ICE_HOCKEY_BETTING_MARKET(never_in_play, delay_before_settling) \ + create_betting_market_group({{"en", "First Period Result"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type(), never_in_play, delay_before_settling); \ + generate_blocks(1); \ + const betting_market_group_object& first_period_result_betting_markets = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(first_period_result_betting_markets.id, {{"en", "Washington Capitals win"}}); \ + generate_blocks(1); \ + const betting_market_object& first_period_capitals_win_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(first_period_result_betting_markets.id, {{"en", "Chicago Blackhawks win"}}); \ + generate_blocks(1); \ + const betting_market_object& first_period_blackhawks_win_market = *db.get_index_type().indices().get().rbegin(); \ + (void)first_period_capitals_win_market; (void)first_period_blackhawks_win_market; \ + \ + create_betting_market_group({{"en", "Second Period Result"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type(), never_in_play, delay_before_settling); \ + generate_blocks(1); \ + const betting_market_group_object& second_period_result_betting_markets = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(second_period_result_betting_markets.id, {{"en", "Washington Capitals win"}}); \ + generate_blocks(1); \ + const betting_market_object& second_period_capitals_win_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(second_period_result_betting_markets.id, {{"en", "Chicago Blackhawks win"}}); \ + generate_blocks(1); \ + const betting_market_object& second_period_blackhawks_win_market = *db.get_index_type().indices().get().rbegin(); \ + (void)second_period_capitals_win_market; (void)second_period_blackhawks_win_market; \ + \ + create_betting_market_group({{"en", "Third Period Result"}}, capitals_vs_blackhawks.id, betting_market_rules.id, asset_id_type(), never_in_play, delay_before_settling); \ + generate_blocks(1); \ + const betting_market_group_object& third_period_result_betting_markets = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(third_period_result_betting_markets.id, {{"en", "Washington Capitals win"}}); \ + generate_blocks(1); \ + const betting_market_object& third_period_capitals_win_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(third_period_result_betting_markets.id, {{"en", "Chicago Blackhawks win"}}); \ + generate_blocks(1); \ + const betting_market_object& third_period_blackhawks_win_market = *db.get_index_type().indices().get().rbegin(); \ + (void)third_period_capitals_win_market; (void)third_period_blackhawks_win_market; + +#define CREATE_TENNIS_BETTING_MARKET() \ + create_betting_market_rules({{"en", "Tennis Rules v1.0"}}, {{"en", "The winner is the player who wins the last ball in the match."}}); \ + generate_blocks(1); \ + const betting_market_rules_object& tennis_rules = *db.get_index_type().indices().get().rbegin(); \ + create_sport({{"en", "Tennis"}}); \ + generate_blocks(1); \ + const sport_object& tennis = *db.get_index_type().indices().get().rbegin(); \ + create_event_group({{"en", "Wimbledon"}}, tennis.id); \ + generate_blocks(1); \ + const event_group_object& wimbledon = *db.get_index_type().indices().get().rbegin(); \ + create_event({{"en", "R. Federer/T. Berdych"}}, {{"en", "2017"}}, wimbledon.id); \ + generate_blocks(1); \ + const event_object& berdych_vs_federer = *db.get_index_type().indices().get().rbegin(); \ + create_event({{"en", "M. Cilic/S. Querrye"}}, {{"en", "2017"}}, wimbledon.id); \ + generate_blocks(1); \ + const event_object& cilic_vs_querrey = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_group({{"en", "Moneyline 1st sf"}}, berdych_vs_federer.id, tennis_rules.id, asset_id_type(), false, 0); \ + generate_blocks(1); \ + const betting_market_group_object& moneyline_berdych_vs_federer = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_group({{"en", "Moneyline 2nd sf"}}, cilic_vs_querrey.id, tennis_rules.id, asset_id_type(), false, 0); \ + generate_blocks(1); \ + const betting_market_group_object& moneyline_cilic_vs_querrey = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_berdych_vs_federer.id, {{"en", "T. Berdych defeats R. Federer"}}); \ + generate_blocks(1); \ + const betting_market_object& berdych_wins_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_berdych_vs_federer.id, {{"en", "R. Federer defeats T. Berdych"}}); \ + generate_blocks(1); \ + const betting_market_object& federer_wins_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_cilic_vs_querrey.id, {{"en", "M. Cilic defeats S. Querrey"}}); \ + generate_blocks(1); \ + const betting_market_object& cilic_wins_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_cilic_vs_querrey.id, {{"en", "S. Querrey defeats M. Cilic"}});\ + generate_blocks(1); \ + const betting_market_object& querrey_wins_market = *db.get_index_type().indices().get().rbegin(); \ + create_event({{"en", "R. Federer/M. Cilic"}}, {{"en", "2017"}}, wimbledon.id); \ + generate_blocks(1); \ + const event_object& cilic_vs_federer = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_group({{"en", "Moneyline final"}}, cilic_vs_federer.id, tennis_rules.id, asset_id_type(), false, 0); \ + generate_blocks(1); \ + const betting_market_group_object& moneyline_cilic_vs_federer = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_cilic_vs_federer.id, {{"en", "R. Federer defeats M. Cilic"}}); \ + generate_blocks(1); \ + const betting_market_object& federer_wins_final_market = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market(moneyline_cilic_vs_federer.id, {{"en", "M. Cilic defeats R. Federer"}}); \ + generate_blocks(1); \ + const betting_market_object& cilic_wins_final_market = *db.get_index_type().indices().get().rbegin(); \ + (void)federer_wins_market;(void)cilic_wins_market;(void)federer_wins_final_market; (void)cilic_wins_final_market; (void)berdych_wins_market; (void)querrey_wins_market; From abf909803c907a22d2c58c8ab5e16eedb3b2badd Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 18 Apr 2018 17:06:25 +0200 Subject: [PATCH 13/23] Refactored betting market macros into common header --- tests/betting/betting_tests.cpp | 38 --------------------------- tests/common/betting_test_markets.hpp | 34 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 38 deletions(-) diff --git a/tests/betting/betting_tests.cpp b/tests/betting/betting_tests.cpp index 484c4da4..c192701b 100644 --- a/tests/betting/betting_tests.cpp +++ b/tests/betting/betting_tests.cpp @@ -41,11 +41,7 @@ #include #include -#include -#include -#include #include -#include #include @@ -1381,40 +1377,6 @@ BOOST_AUTO_TEST_CASE( cancel_one_event_in_group ) BOOST_AUTO_TEST_SUITE_END() -// set up a fixture that places a series of two matched bets, we'll use this fixture to verify -// the result in all three possible outcomes -struct simple_bet_test_fixture : database_fixture { - betting_market_id_type capitals_win_betting_market_id; - betting_market_id_type blackhawks_win_betting_market_id; - betting_market_group_id_type moneyline_betting_markets_id; - - simple_bet_test_fixture() - { - ACTORS( (alice)(bob) ); - CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0); - - // give alice and bob 10k each - transfer(account_id_type(), alice_id, asset(10000)); - transfer(account_id_type(), bob_id, asset(10000)); - - // place bets at 10:1 - place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); - - // reverse positions at 1:1 - place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - - capitals_win_betting_market_id = capitals_win_market.id; - blackhawks_win_betting_market_id = blackhawks_win_market.id; - moneyline_betting_markets_id = moneyline_betting_markets.id; - - // close betting to prepare for the next operation which will be grading or cancel - update_betting_market_group(moneyline_betting_markets.id, _status = betting_market_group_status::closed); - generate_blocks(1); - } -}; - BOOST_FIXTURE_TEST_SUITE( simple_bet_tests, simple_bet_test_fixture ) BOOST_AUTO_TEST_CASE( win ) diff --git a/tests/common/betting_test_markets.hpp b/tests/common/betting_test_markets.hpp index 75363daf..f67dc067 100644 --- a/tests/common/betting_test_markets.hpp +++ b/tests/common/betting_test_markets.hpp @@ -137,3 +137,37 @@ using namespace graphene::chain; generate_blocks(1); \ const betting_market_object& cilic_wins_final_market = *db.get_index_type().indices().get().rbegin(); \ (void)federer_wins_market;(void)cilic_wins_market;(void)federer_wins_final_market; (void)cilic_wins_final_market; (void)berdych_wins_market; (void)querrey_wins_market; + +// set up a fixture that places a series of two matched bets, we'll use this fixture to verify +// the result in all three possible outcomes +struct simple_bet_test_fixture : database_fixture { + betting_market_id_type capitals_win_betting_market_id; + betting_market_id_type blackhawks_win_betting_market_id; + betting_market_group_id_type moneyline_betting_markets_id; + + simple_bet_test_fixture() + { + ACTORS( (alice)(bob) ); + CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0); + + // give alice and bob 10k each + transfer(account_id_type(), alice_id, asset(10000)); + transfer(account_id_type(), bob_id, asset(10000)); + + // place bets at 10:1 + place_bet(alice_id, capitals_win_market.id, bet_type::back, asset(100, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, capitals_win_market.id, bet_type::lay, asset(1000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + + // reverse positions at 1:1 + place_bet(alice_id, capitals_win_market.id, bet_type::lay, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + + capitals_win_betting_market_id = capitals_win_market.id; + blackhawks_win_betting_market_id = blackhawks_win_market.id; + moneyline_betting_markets_id = moneyline_betting_markets.id; + + // close betting to prepare for the next operation which will be grading or cancel + update_betting_market_group(moneyline_betting_markets.id, graphene::chain::keywords::_status = betting_market_group_status::closed); + generate_blocks(1); + } +}; From 9cfd190fa135c74d55b5c942effaa715ee6773d6 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 18 Apr 2018 18:40:04 +0200 Subject: [PATCH 14/23] Added bookie payout test --- tests/tests/affiliate_tests.cpp | 136 ++++++++++++++++++++++++++++++++ 1 file changed, 136 insertions(+) diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 8926ec39..07bce5f2 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -24,6 +24,7 @@ #include +#include "../common/betting_test_markets.hpp" #include "../common/tournament_helper.hpp" #include @@ -453,4 +454,139 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) } FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( bookie_payout_test ) +{ try { + ACTORS( (alice)(ann)(audrey)(irene) ); + + generate_blocks( HARDFORK_999_TIME ); + generate_block(); + + const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; + + test::set_expiration( db, trx ); + trx.clear(); + + // Paula: 100% to Alice for Bookie / nothing for RPS + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + trx.operations.push_back( aco ); + + // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey + const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); + aco = make_account( "penny", penny_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; + trx.operations.push_back( aco ); + + // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey + const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); + aco = make_account( "petra", petra_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; + trx.operations.push_back( aco ); + processed_transaction result = db.push_transaction(trx, ~0); + trx.clear(); + + account_id_type paula_id = result.operation_results[0].get(); + account_id_type penny_id = result.operation_results[1].get(); + account_id_type petra_id = result.operation_results[2].get(); + + fund( paula_id(db), asset(20000000) ); + fund( penny_id(db), asset(30000000) ); + fund( petra_id(db), asset(40000000) ); + + + CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0); + + // place bets at 10:1 + place_bet(paula_id, capitals_win_market.id, bet_type::back, asset(10000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(penny_id, capitals_win_market.id, bet_type::lay, asset(100000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + + // reverse positions at 1:1 + place_bet(paula_id, capitals_win_market.id, bet_type::lay, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(penny_id, capitals_win_market.id, bet_type::back, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + + update_betting_market_group(moneyline_betting_markets.id, graphene::chain::keywords::_status = betting_market_group_status::closed); + resolve_betting_market_group(moneyline_betting_markets.id, + {{capitals_win_market.id, betting_market_resolution_type::win}, + {blackhawks_win_market.id, betting_market_resolution_type::not_win}}); + generate_block(); + + uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; + BOOST_CHECK_EQUAL( 3 * GRAPHENE_1_PERCENT, rake_fee_percentage ); + uint32_t rake_value; + // rake_value = (-10000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; + // paula starts with 20000000, pays 10000 (bet), wins 110000, then pays 110000 (bet), wins 0 + BOOST_CHECK_EQUAL( get_balance( paula_id, asset_id_type() ), 20000000 - 10000 + 110000 - 110000 + 0 ); + // no wins -> no affiliate payouts + + rake_value = (-100000 - 110000 + 220000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; + // penny starts with 30000000, pays 100000 (bet), wins 0, then pays 110000 (bet), wins 220000 + BOOST_CHECK_EQUAL( get_balance( penny_id, asset_id_type() ), 30000000 - 100000 + 0 - 110000 + 220000 - rake_value ); + // penny wins 10000 net, rake is 3% of that (=300) + // Affiliate pay is 20% of rake (=60). Of this, 60%=36 go to Alice, 40%=24 go to Ann + BOOST_CHECK_EQUAL( 36, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 24, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( 0, get_balance( audrey_id, asset_id_type() ) ); + + { + issue_uia( paula_id, asset( 1000000, btc_id ) ); + issue_uia( petra_id, asset( 1000000, btc_id ) ); + + create_event({{"en", "Washington Capitals/Chicago Blackhawks"}, {"zh_Hans", "華盛頓首都隊/芝加哥黑鷹"}, {"ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"}}, {{"en", "2016-17"}}, nhl.id); \ + generate_blocks(1); \ + const event_object& capitals_vs_blackhawks2 = *db.get_index_type().indices().get().rbegin(); \ + create_betting_market_group({{"en", "Moneyline"}}, capitals_vs_blackhawks2.id, betting_market_rules.id, btc_id, false, 0); + generate_blocks(1); + const betting_market_group_object& moneyline_betting_markets2 = *db.get_index_type().indices().get().rbegin(); + create_betting_market(moneyline_betting_markets2.id, {{"en", "Washington Capitals win"}}); + generate_blocks(1); + const betting_market_object& capitals_win_market2 = *db.get_index_type().indices().get().rbegin(); + create_betting_market(moneyline_betting_markets2.id, {{"en", "Chicago Blackhawks win"}}); + generate_blocks(1); + const betting_market_object& blackhawks_win_market2 = *db.get_index_type().indices().get().rbegin(); + + // place bets at 10:1 + place_bet(paula_id, capitals_win_market2.id, bet_type::back, asset(10000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(petra_id, capitals_win_market2.id, bet_type::lay, asset(100000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + + // reverse positions at 1:1 + place_bet(paula_id, capitals_win_market2.id, bet_type::lay, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(petra_id, capitals_win_market2.id, bet_type::back, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + + update_betting_market_group(moneyline_betting_markets2.id, graphene::chain::keywords::_status = betting_market_group_status::closed); + resolve_betting_market_group(moneyline_betting_markets2.id, + {{capitals_win_market2.id, betting_market_resolution_type::not_win}, + {blackhawks_win_market2.id, betting_market_resolution_type::win}}); + generate_block(); + + rake_value = (-10000 + 0 - 110000 + 220000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; + // paula starts with 1000000, pays 10000 (bet), wins 0, then pays 110000 (bet), wins 220000 + BOOST_CHECK_EQUAL( get_balance( paula_id, btc_id ), 1000000 - 10000 + 0 - 110000 + 220000 - rake_value ); + // net win 100000, 3% rake = 3000, 20% of that is 600, 100% of that goes to Alice + BOOST_CHECK_EQUAL( 600, get_balance( alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( 0, get_balance( ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( 0, get_balance( audrey_id, btc_id ) ); + + // rake_value = (-100000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; + rake_value = 0; + // petra starts with 1000000, pays 100000 (bet), wins 110000, then pays 110000 (bet), wins 0 + BOOST_CHECK_EQUAL( get_balance( petra_id, btc_id ), 1000000 - 100000 + 110000 - 110000 + 0 - rake_value ); + // petra wins nothing -> no payout + } + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From 2428383466ea7a9e817b2c7bb4759677386adfac Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 18 Apr 2018 18:39:39 +0200 Subject: [PATCH 15/23] Fixed bookie payout --- libraries/chain/db_bet.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 0d7fdf26..2c40c200 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -264,11 +264,12 @@ void database::settle_betting_market_group(const betting_market_group_object& be if (net_profits.value > 0 && rake_account_id) { rake_amount = ((fc::uint128_t(net_profits.value) * rake_fee_percentage + GRAPHENE_100_PERCENT - 1) / GRAPHENE_100_PERCENT).to_uint64(); + share_type affiliates_share; if (rake_amount.value) - rake_amount -= payout_helper.payout( bettor_id, rake_amount ); - FC_ASSERT( rake_amount.value >= 0 ); - if (rake_amount.value) - adjust_balance(*rake_account_id, asset(rake_amount, betting_market_group.asset_id)); + affiliates_share = payout_helper.payout( bettor_id, rake_amount ); + FC_ASSERT( rake_amount.value >= affiliates_share.value ); + if (rake_amount.value > affiliates_share.value) + adjust_balance(*rake_account_id, asset(rake_amount - affiliates_share, betting_market_group.asset_id)); } // pay winning - rake From ade7cf2d871f4082ec345c5a5aee34defe5a5789 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Wed, 18 Apr 2018 23:18:05 +0200 Subject: [PATCH 16/23] Created plugin stub for affiliate statistics --- libraries/app/CMakeLists.txt | 2 +- libraries/app/api.cpp | 12 ++ libraries/app/application.cpp | 1 + libraries/app/include/graphene/app/api.hpp | 5 + libraries/plugins/CMakeLists.txt | 1 + .../plugins/affiliate_stats/CMakeLists.txt | 24 ++++ .../affiliate_stats/affiliate_stats_api.cpp | 64 ++++++++++ .../affiliate_stats_plugin.cpp | 117 ++++++++++++++++++ .../affiliate_stats/affiliate_stats_api.hpp | 61 +++++++++ .../affiliate_stats_plugin.hpp | 70 +++++++++++ programs/witness_node/CMakeLists.txt | 2 +- programs/witness_node/main.cpp | 2 + 12 files changed, 359 insertions(+), 2 deletions(-) create mode 100644 libraries/plugins/affiliate_stats/CMakeLists.txt create mode 100644 libraries/plugins/affiliate_stats/affiliate_stats_api.cpp create mode 100644 libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp create mode 100644 libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp create mode 100644 libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index a6157560..077eb4aa 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -13,7 +13,7 @@ add_library( graphene_app # need to link graphene_debug_witness because plugins aren't sufficiently isolated #246 #target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness ) -target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie ) +target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_affiliate_stats graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 6c6359c2..077ad0d1 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -117,6 +117,12 @@ namespace graphene { namespace app { if( _app.get_plugin( "bookie" ) ) _bookie_api = std::make_shared(std::ref(_app)); } + else if( api_name == "affiliate_stats_api" ) + { + // can only enable this API if the plugin was loaded + if( _app.get_plugin( "affiliate_stats" ) ) + _affiliate_stats_api = std::make_shared(std::ref(_app)); + } return; } @@ -281,6 +287,12 @@ namespace graphene { namespace app { return *_bookie_api; } + fc::api login_api::affiliate_stats() const + { + FC_ASSERT(_affiliate_stats_api); + return *_affiliate_stats_api; + } + #if 0 vector get_relevant_accounts( const object* obj ) { diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 5b079b94..9572af5e 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -493,6 +493,7 @@ namespace detail { wild_access.allowed_apis.push_back( "history_api" ); wild_access.allowed_apis.push_back( "crypto_api" ); wild_access.allowed_apis.push_back( "bookie_api" ); + wild_access.allowed_apis.push_back( "affiliate_stats_api" ); _apiaccess.permission_map["*"] = wild_access; } diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index d4532b42..2fe0c938 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -32,6 +32,7 @@ #include #include +#include #include #include @@ -362,6 +363,8 @@ namespace graphene { namespace app { fc::api debug()const; /// @brief Retrieve the bookie API (if available) fc::api bookie()const; + /// @brief Retrieve the affiliate_stats API (if available) + fc::api affiliate_stats()const; /// @brief Called to enable an API, not reflected. void enable_api( const string& api_name ); @@ -377,6 +380,7 @@ namespace graphene { namespace app { optional< fc::api > _asset_api; optional< fc::api > _debug_api; optional< fc::api > _bookie_api; + optional< fc::api > _affiliate_stats_api; }; }} // graphene::app @@ -446,4 +450,5 @@ FC_API(graphene::app::login_api, (asset) (debug) (bookie) + (affiliate_stats) ) diff --git a/libraries/plugins/CMakeLists.txt b/libraries/plugins/CMakeLists.txt index 8bf98141..01079fe2 100644 --- a/libraries/plugins/CMakeLists.txt +++ b/libraries/plugins/CMakeLists.txt @@ -1,6 +1,7 @@ add_subdirectory( witness ) add_subdirectory( account_history ) add_subdirectory( accounts_list ) +add_subdirectory( affiliate_stats ) add_subdirectory( market_history ) add_subdirectory( delayed_node ) add_subdirectory( bookie ) diff --git a/libraries/plugins/affiliate_stats/CMakeLists.txt b/libraries/plugins/affiliate_stats/CMakeLists.txt new file mode 100644 index 00000000..fec2544c --- /dev/null +++ b/libraries/plugins/affiliate_stats/CMakeLists.txt @@ -0,0 +1,24 @@ +file(GLOB HEADERS "include/graphene/affiliate_stats/*.hpp") + +add_library( graphene_affiliate_stats + affiliate_stats_api.cpp + affiliate_stats_plugin.cpp + ) + +target_link_libraries( graphene_affiliate_stats graphene_chain graphene_app ) +target_include_directories( graphene_affiliate_stats + PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) + +if(MSVC) + set_source_files_properties( affiliate_stats_plugin.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) +endif(MSVC) + +install( TARGETS + graphene_affiliate_stats + + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) +INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/affiliate_stats" ) + diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp new file mode 100644 index 00000000..0b072cca --- /dev/null +++ b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include + +#include +#include + +namespace graphene { namespace affiliate_stats { + +namespace detail { + +class affiliate_stats_api_impl +{ + public: + affiliate_stats_api_impl(graphene::app::application& _app); + + graphene::app::application& app; +}; + +affiliate_stats_api_impl::affiliate_stats_api_impl(graphene::app::application& _app) + : app(_app) {} + +} // detail + +affiliate_stats_api::affiliate_stats_api(graphene::app::application& app) + : my(std::make_shared(app)) {} + +} } // graphene::affiliate_stats + + diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp new file mode 100644 index 00000000..ee4f7df5 --- /dev/null +++ b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace graphene { namespace affiliate_stats { + +namespace detail { + +class affiliate_stats_plugin_impl +{ + public: + affiliate_stats_plugin_impl(affiliate_stats_plugin& _plugin) + : _self( _plugin ) + { } + virtual ~affiliate_stats_plugin_impl(); + + + /** this method is called as a callback after a block is applied + * and will process/index all operations that were applied in the block. + */ + void update_affiliate_stats( const signed_block& b ); + + graphene::chain::database& database() + { + return _self.database(); + } + + affiliate_stats_plugin& _self; + private: +}; + +affiliate_stats_plugin_impl::~affiliate_stats_plugin_impl() {} + +void affiliate_stats_plugin_impl::update_affiliate_stats( const signed_block& b ) +{ + graphene::chain::database& db = database(); + vector >& hist = db.get_applied_operations(); + for( optional< operation_history_object >& o_op : hist ) + { + if( !o_op.valid() ) + continue; + + const operation_history_object& op = *o_op; + + } +} + +} // end namespace detail + + +affiliate_stats_plugin::affiliate_stats_plugin() + : my( new detail::affiliate_stats_plugin_impl(*this) ) {} + +affiliate_stats_plugin::~affiliate_stats_plugin() {} + +std::string affiliate_stats_plugin::plugin_name()const +{ + return "affiliate_stats"; +} + +void affiliate_stats_plugin::plugin_set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg + ) +{ + cli.add_options() + ; + cfg.add(cli); +} + +void affiliate_stats_plugin::plugin_initialize(const boost::program_options::variables_map& options) +{ + database().applied_block.connect( [this]( const signed_block& b){ my->update_affiliate_stats(b); } ); + + // FIXME + // my->_oho_index = database().add_index< primary_index< simple_index< operation_history_object > > >(); + // database().add_index< primary_index< account_transaction_history_index > >(); +} + +void affiliate_stats_plugin::plugin_startup() {} + +} } // graphene::affiliate_stats diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp new file mode 100644 index 00000000..b3a27475 --- /dev/null +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#pragma once + +#include +#include + +#include +#include +#include + +using namespace graphene::chain; + +namespace graphene { namespace app { + class application; +} } + +namespace graphene { namespace affiliate_stats { + +namespace detail { + class affiliate_stats_api_impl; +} + +class affiliate_stats_api +{ + public: + affiliate_stats_api(graphene::app::application& app); + + std::shared_ptr my; +}; + +} } // graphene::affiliate_stats +/* +FC_REFLECT(graphene::bookie::order_bin, (amount_to_bet)(backer_multiplier)) +FC_REFLECT(graphene::bookie::binned_order_book, (aggregated_back_bets)(aggregated_lay_bets)) +FC_REFLECT(graphene::bookie::matched_bet_object, (id)(bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay)(amount_matched)(associated_operations)) +*/ +FC_API(graphene::affiliate_stats::affiliate_stats_api, + ) diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp new file mode 100644 index 00000000..2262cfab --- /dev/null +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once + +#include +#include + +#include + +namespace graphene { namespace affiliate_stats { + using namespace chain; + +// +// Plugins should #define their SPACE_ID's so plugins with +// conflicting SPACE_ID assignments can be compiled into the +// same binary (by simply re-assigning some of the conflicting #defined +// SPACE_ID's in a build script). +// +// Assignment of SPACE_ID's cannot be done at run-time because +// various template automagic depends on them being known at compile +// time. +// +#ifndef AFFILIATE_STATS_SPACE_ID +#define AFFILIATE_STATS_SPACE_ID 7 +#endif + +namespace detail +{ + class affiliate_stats_plugin_impl; +} + +class affiliate_stats_plugin : public graphene::app::plugin +{ + public: + affiliate_stats_plugin(); + virtual ~affiliate_stats_plugin(); + + std::string plugin_name()const override; + virtual void plugin_set_program_options( + boost::program_options::options_description& cli, + boost::program_options::options_description& cfg) override; + virtual void plugin_initialize(const boost::program_options::variables_map& options) override; + virtual void plugin_startup() override; + + friend class detail::affiliate_stats_plugin_impl; + std::unique_ptr my; +}; + +} } //graphene::affiliate_stats diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index b3fa5c88..3d03253b 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246 target_link_libraries( witness_node - PRIVATE graphene_app graphene_account_history graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_bookie graphene_egenesis_full graphene_snapshot fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE graphene_app graphene_account_history graphene_affiliate_stats graphene_market_history graphene_witness graphene_chain graphene_debug_witness graphene_bookie graphene_egenesis_full fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) # also add dependencies to graphene_generate_genesis graphene_generate_uia_sharedrop_genesis if you want those plugins install( TARGETS diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index 426113c4..4d080aa4 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -29,6 +29,7 @@ #include //#include //#include +#include #include #include #include @@ -84,6 +85,7 @@ int main(int argc, char** argv) { //auto generate_genesis_plug = node->register_plugin(); //auto generate_uia_sharedrop_genesis_plug = node->register_plugin(); auto list_plug = node->register_plugin(); + auto affiliate_stats_plug = node->register_plugin(); auto bookie_plug = node->register_plugin(); auto snapshot_plug = node->register_plugin(); From 8903a5a3aff7f32a63173ab3d2230ef5aec56c68 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Sun, 22 Apr 2018 11:45:19 +0200 Subject: [PATCH 17/23] Implemented top rewards --- .../affiliate_stats/affiliate_stats_api.cpp | 50 +++++++- .../affiliate_stats_plugin.cpp | 61 +++++++-- .../affiliate_stats/affiliate_stats_api.hpp | 44 ++++++- .../affiliate_stats_objects.hpp | 117 ++++++++++++++++++ 4 files changed, 257 insertions(+), 15 deletions(-) create mode 100644 libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_objects.hpp diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp index 0b072cca..5fcbe0d6 100644 --- a/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp +++ b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp @@ -37,7 +37,6 @@ #include #include -#include namespace graphene { namespace affiliate_stats { @@ -48,6 +47,28 @@ class affiliate_stats_api_impl public: affiliate_stats_api_impl(graphene::app::application& _app); + std::vector list_top_referred_accounts( asset_id_type asset, uint16_t limit )const + { + std::vector result; + result.reserve( limit ); + auto& idx = app.chain_database()->get_index_type().indices().get(); + auto itr = idx.find( asset ); + while( itr != idx.end() && itr->get_asset_id() == asset && limit-- > 0 ) + result.push_back( *itr ); + return result; + } + + std::vector list_top_rewards_per_app( asset_id_type asset, uint16_t limit )const + { + std::vector result; + result.reserve( limit ); + auto& idx = app.chain_database()->get_index_type().indices().get(); + auto itr = idx.find( asset ); + while( itr != idx.end() && itr->get_asset_id() == asset && limit-- > 0 ) + result.push_back( *itr ); + return result; + } + graphene::app::application& app; }; @@ -56,9 +77,36 @@ affiliate_stats_api_impl::affiliate_stats_api_impl(graphene::app::application& _ } // detail +top_referred_account::top_referred_account() {} + +top_referred_account::top_referred_account( const referral_reward_object& rro ) + : referral( rro.referral ), total_payout( rro.total_payout ) {} + +top_app::top_app() {} + +top_app::top_app( const app_reward_object& aro ) + : app( aro.app ), total_payout( aro.total_payout ) {} + affiliate_stats_api::affiliate_stats_api(graphene::app::application& app) : my(std::make_shared(app)) {} +std::vector affiliate_stats_api::list_top_referred_accounts( asset_id_type asset, uint16_t limit )const +{ + FC_ASSERT( limit <= 100 ); + return my->list_top_referred_accounts( asset, limit ); +} + +std::vector affiliate_stats_api::list_top_rewards_per_app( asset_id_type asset, uint16_t limit )const +{ + FC_ASSERT( limit <= 100 ); + return my->list_top_rewards_per_app( asset, limit ); +} + +std::vector affiliate_stats_api::list_historic_referral_rewards( account_id_type affiliate )const +{ + FC_ASSERT( false, "Not implemented!" ); +} + } } // graphene::affiliate_stats diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp index ee4f7df5..3099b331 100644 --- a/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp +++ b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp @@ -23,6 +23,7 @@ */ #include +#include #include @@ -45,11 +46,9 @@ class affiliate_stats_plugin_impl { public: affiliate_stats_plugin_impl(affiliate_stats_plugin& _plugin) - : _self( _plugin ) - { } + : _self( _plugin ) { } virtual ~affiliate_stats_plugin_impl(); - /** this method is called as a callback after a block is applied * and will process/index all operations that were applied in the block. */ @@ -60,23 +59,67 @@ class affiliate_stats_plugin_impl return _self.database(); } + typedef void result_type; + template + void operator()( const Operation& op ) {} + affiliate_stats_plugin& _self; + app_reward_index* _ar_index; + referral_reward_index* _rr_index; private: }; affiliate_stats_plugin_impl::~affiliate_stats_plugin_impl() {} +template<> +void affiliate_stats_plugin_impl::operator()( const affiliate_payout_operation& op ) +{ + auto& by_app = _ar_index->indices().get(); + auto itr = by_app.find( boost::make_tuple( op.tag, op.payout.asset_id ) ); + if( itr == by_app.end() ) + { + database().create( [&op]( app_reward_object& aro ) { + aro.app = op.tag; + aro.total_payout = op.payout; + }); + } + else + { + database().modify( *itr, [&op]( app_reward_object& aro ) { + aro.total_payout += op.payout; + }); + } +} + +template<> +void affiliate_stats_plugin_impl::operator()( const affiliate_referral_payout_operation& op ) +{ + auto& by_referral = _rr_index->indices().get(); + auto itr = by_referral.find( boost::make_tuple( op.player, op.payout.asset_id ) ); + if( itr == by_referral.end() ) + { + database().create( [&op]( referral_reward_object& rro ) { + rro.referral = op.player; + rro.total_payout = op.payout; + }); + } + else + { + database().modify( *itr, [&op]( referral_reward_object& rro ) { + rro.total_payout += op.payout; + }); + } +} + void affiliate_stats_plugin_impl::update_affiliate_stats( const signed_block& b ) { - graphene::chain::database& db = database(); - vector >& hist = db.get_applied_operations(); + vector >& hist = database().get_applied_operations(); for( optional< operation_history_object >& o_op : hist ) { if( !o_op.valid() ) continue; - const operation_history_object& op = *o_op; - + o_op->op.visit( *this ); } } @@ -107,9 +150,9 @@ void affiliate_stats_plugin::plugin_initialize(const boost::program_options::var { database().applied_block.connect( [this]( const signed_block& b){ my->update_affiliate_stats(b); } ); - // FIXME // my->_oho_index = database().add_index< primary_index< simple_index< operation_history_object > > >(); - // database().add_index< primary_index< account_transaction_history_index > >(); + my->_ar_index = database().add_index< primary_index< app_reward_index > >(); + my->_rr_index = database().add_index< primary_index< referral_reward_index > >(); } void affiliate_stats_plugin::plugin_startup() {} diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp index b3a27475..d41e6d44 100644 --- a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp @@ -31,6 +31,8 @@ #include #include +#include + using namespace graphene::chain; namespace graphene { namespace app { @@ -43,19 +45,51 @@ namespace detail { class affiliate_stats_api_impl; } +class referral_payment { +public: + +}; + +class top_referred_account { +public: + top_referred_account(); + top_referred_account( const referral_reward_object& rro ); + + account_id_type referral; + asset total_payout; +}; + +class top_app { +public: + top_app(); + top_app( const app_reward_object& rro ); + + app_tag app; + asset total_payout; +}; + class affiliate_stats_api { public: affiliate_stats_api(graphene::app::application& app); + std::vector list_historic_referral_rewards( account_id_type affiliate )const; + // get_pending_referral_reward() - not implemented because we have continuous payouts + // get_previous_referral_reward() - not implemented because we have continuous payouts + std::vector list_top_referred_accounts( asset_id_type asset, uint16_t limit = 100 )const; + std::vector list_top_rewards_per_app( asset_id_type asset, uint16_t limit = 100 )const; + std::shared_ptr my; }; } } // graphene::affiliate_stats -/* -FC_REFLECT(graphene::bookie::order_bin, (amount_to_bet)(backer_multiplier)) -FC_REFLECT(graphene::bookie::binned_order_book, (aggregated_back_bets)(aggregated_lay_bets)) -FC_REFLECT(graphene::bookie::matched_bet_object, (id)(bettor_id)(betting_market_id)(amount_to_bet)(backer_multiplier)(back_or_lay)(end_of_delay)(amount_matched)(associated_operations)) -*/ + +FC_REFLECT(graphene::affiliate_stats::referral_payment, ) +FC_REFLECT(graphene::affiliate_stats::top_referred_account, (referral)(total_payout) ) +FC_REFLECT(graphene::affiliate_stats::top_app, (app)(total_payout) ) + FC_API(graphene::affiliate_stats::affiliate_stats_api, + (list_historic_referral_rewards) + (list_top_referred_accounts) + (list_top_rewards_per_app) ) diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_objects.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_objects.hpp new file mode 100644 index 00000000..1dc70276 --- /dev/null +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_objects.hpp @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2018 Peerplays Blockchain Standards Association, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#pragma once +#include +#include + +namespace graphene { namespace affiliate_stats { +using namespace chain; + +enum stats_object_type +{ + app_reward_object_type, + referral_reward_object_type, + STATS_OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types +}; + +class app_reward_object : public graphene::db::abstract_object +{ +public: + static const uint8_t space_id = AFFILIATE_STATS_SPACE_ID; + static const uint8_t type_id = app_reward_object_type; + + app_tag app; + asset total_payout; + + inline share_type get_amount()const { return total_payout.amount; } + inline asset_id_type get_asset_id()const { return total_payout.asset_id; } +}; + +typedef object_id app_reward_id_type; + +struct by_asset; +struct by_app_asset; +typedef multi_index_container< + app_reward_object, + indexed_by< + ordered_unique, member >, + ordered_non_unique, + composite_key< + app_reward_object, + const_mem_fun, + const_mem_fun >, + composite_key_compare< + std::less, + std::greater > + >, + ordered_unique, + composite_key< + app_reward_object, + member, + const_mem_fun > + > > > app_reward_multi_index_type; +typedef generic_index app_reward_index; + +class referral_reward_object : public graphene::db::abstract_object +{ +public: + static const uint8_t space_id = AFFILIATE_STATS_SPACE_ID; + static const uint8_t type_id = referral_reward_object_type; + + account_id_type referral; + asset total_payout; + + inline share_type get_amount()const { return total_payout.amount; } + inline asset_id_type get_asset_id()const { return total_payout.asset_id; } +}; + +typedef object_id referral_reward_id_type; + +struct by_referral_asset; +typedef multi_index_container< + referral_reward_object, + indexed_by< + ordered_unique, member >, + ordered_non_unique, + composite_key< + referral_reward_object, + const_mem_fun, + const_mem_fun >, + composite_key_compare< + std::less, + std::greater > + >, + ordered_unique, + composite_key< + referral_reward_object, + member, + const_mem_fun > + > > > referral_reward_multi_index_type; +typedef generic_index referral_reward_index; + +} } //graphene::affiliate_stats + +FC_REFLECT_DERIVED( graphene::affiliate_stats::app_reward_object, (graphene::db::object), (app)(total_payout) ) +FC_REFLECT_DERIVED( graphene::affiliate_stats::referral_reward_object, (graphene::db::object), (referral)(total_payout) ) + From f81b064597fa34928ffcf9cf2b5e0743a7ab7c20 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 26 Apr 2018 16:03:02 +0200 Subject: [PATCH 18/23] Implemented additional index + API call for call list_historic_referral_rewards --- .../account_history_plugin.hpp | 11 ++++ .../affiliate_stats/affiliate_stats_api.cpp | 36 ++++++++-- .../affiliate_stats_plugin.cpp | 65 +++++++++++++++++-- .../affiliate_stats/affiliate_stats_api.hpp | 12 +++- .../affiliate_stats_plugin.hpp | 2 + 5 files changed, 112 insertions(+), 14 deletions(-) diff --git a/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp b/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp index ef89c488..784c7e45 100644 --- a/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp +++ b/libraries/plugins/account_history/include/graphene/account_history/account_history_plugin.hpp @@ -81,6 +81,17 @@ class account_history_plugin : public graphene::app::plugin std::unique_ptr my; }; +class affiliate_reward_index : public secondary_index +{ + public: + virtual void object_inserted( const object& obj ) override; + virtual void object_removed( const object& obj ) override; + virtual void about_to_modify( const object& before ) override{}; + virtual void object_modified( const object& after ) override{}; + + map > _history_by_account; +}; + } } //graphene::account_history /*struct by_id; diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp index 5fcbe0d6..d37a0360 100644 --- a/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp +++ b/libraries/plugins/affiliate_stats/affiliate_stats_api.cpp @@ -38,6 +38,8 @@ #include +#include + namespace graphene { namespace affiliate_stats { namespace detail { @@ -52,9 +54,9 @@ class affiliate_stats_api_impl std::vector result; result.reserve( limit ); auto& idx = app.chain_database()->get_index_type().indices().get(); - auto itr = idx.find( asset ); + auto itr = idx.lower_bound( boost::make_tuple( asset, share_type(GRAPHENE_MAX_SHARE_SUPPLY) ) ); while( itr != idx.end() && itr->get_asset_id() == asset && limit-- > 0 ) - result.push_back( *itr ); + result.push_back( *itr++ ); return result; } @@ -63,9 +65,22 @@ class affiliate_stats_api_impl std::vector result; result.reserve( limit ); auto& idx = app.chain_database()->get_index_type().indices().get(); - auto itr = idx.find( asset ); + auto itr = idx.lower_bound( boost::make_tuple( asset, share_type(GRAPHENE_MAX_SHARE_SUPPLY) ) ); while( itr != idx.end() && itr->get_asset_id() == asset && limit-- > 0 ) - result.push_back( *itr ); + result.push_back( *itr++ ); + return result; + } + + std::vector list_historic_referral_rewards( account_id_type affiliate, operation_history_id_type start, uint16_t limit )const + { + shared_ptr plugin = app.get_plugin( "affiliate_stats" ); + + std::vector result; + const auto& list = plugin->get_reward_history( affiliate ); + result.reserve( limit ); + auto inner = list.lower_bound( start ); + while( inner != list.end() && result.size() < limit ) + result.push_back( referral_payment( (*inner++)(*app.chain_database()) ) ); return result; } @@ -87,6 +102,7 @@ top_app::top_app() {} top_app::top_app( const app_reward_object& aro ) : app( aro.app ), total_payout( aro.total_payout ) {} + affiliate_stats_api::affiliate_stats_api(graphene::app::application& app) : my(std::make_shared(app)) {} @@ -102,11 +118,19 @@ std::vector affiliate_stats_api::list_top_rewards_per_app( asset_id_typ return my->list_top_rewards_per_app( asset, limit ); } -std::vector affiliate_stats_api::list_historic_referral_rewards( account_id_type affiliate )const +std::vector affiliate_stats_api::list_historic_referral_rewards( account_id_type affiliate, operation_history_id_type start, uint16_t limit )const { - FC_ASSERT( false, "Not implemented!" ); + FC_ASSERT( limit <= 100 ); + return my->list_historic_referral_rewards( affiliate, start, limit ); } + +referral_payment::referral_payment() {} + +referral_payment::referral_payment( const operation_history_object& oho ) + : id(oho.id), block_num(oho.block_num), tag(oho.op.get().tag), + payout(oho.op.get().payout) {} + } } // graphene::affiliate_stats diff --git a/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp index 3099b331..438b1aca 100644 --- a/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp +++ b/libraries/plugins/affiliate_stats/affiliate_stats_plugin.cpp @@ -42,6 +42,19 @@ namespace graphene { namespace affiliate_stats { namespace detail { +class affiliate_reward_index : public graphene::db::index_observer +{ + public: + affiliate_reward_index( graphene::chain::database& _db ) : db(_db) {} + virtual void on_add( const graphene::db::object& obj ) override; + virtual void on_remove( const graphene::db::object& obj ) override; + virtual void on_modify( const graphene::db::object& before ) override{}; + + std::map > _history_by_account; + private: + graphene::chain::database& db; +}; + class affiliate_stats_plugin_impl { public: @@ -59,13 +72,16 @@ class affiliate_stats_plugin_impl return _self.database(); } + const std::set& get_reward_history( account_id_type& affiliate )const; + typedef void result_type; template void operator()( const Operation& op ) {} - affiliate_stats_plugin& _self; - app_reward_index* _ar_index; - referral_reward_index* _rr_index; + shared_ptr _fr_index; + affiliate_stats_plugin& _self; + app_reward_index* _ar_index; + referral_reward_index* _rr_index; private: }; @@ -123,9 +139,42 @@ void affiliate_stats_plugin_impl::update_affiliate_stats( const signed_block& b } } -} // end namespace detail +static const std::set EMPTY; +const std::set& affiliate_stats_plugin_impl::get_reward_history( account_id_type& affiliate )const +{ + auto itr = _fr_index->_history_by_account.find( affiliate ); + if( itr == _fr_index->_history_by_account.end() ) + return EMPTY; + return itr->second; +} +static optional> get_account( const database& db, const object& obj ) +{ + FC_ASSERT( dynamic_cast(&obj) ); + const account_transaction_history_object& ath = static_cast(obj); + const operation_history_object& oho = db.get( ath.operation_id ); + if( oho.op.which() == operation::tag::value ) + return std::make_pair( ath.account, ath.operation_id ); + return optional>(); +} + +void affiliate_reward_index::on_add( const object& obj ) +{ + optional> acct_ath = get_account( db, obj ); + if( !acct_ath.valid() ) return; + _history_by_account[acct_ath->first].insert( acct_ath->second ); +} + +void affiliate_reward_index::on_remove( const object& obj ) +{ + optional> acct_ath = get_account( db, obj ); + if( !acct_ath.valid() ) return; + _history_by_account[acct_ath->first].erase( acct_ath->second ); +} + +} // end namespace detail + affiliate_stats_plugin::affiliate_stats_plugin() : my( new detail::affiliate_stats_plugin_impl(*this) ) {} @@ -150,11 +199,17 @@ void affiliate_stats_plugin::plugin_initialize(const boost::program_options::var { database().applied_block.connect( [this]( const signed_block& b){ my->update_affiliate_stats(b); } ); - // my->_oho_index = database().add_index< primary_index< simple_index< operation_history_object > > >(); my->_ar_index = database().add_index< primary_index< app_reward_index > >(); my->_rr_index = database().add_index< primary_index< referral_reward_index > >(); + my->_fr_index = shared_ptr( new detail::affiliate_reward_index( database() ) ); + const_cast&>(database().get_index_type>()).add_observer( my->_fr_index ); } void affiliate_stats_plugin::plugin_startup() {} +const std::set& affiliate_stats_plugin::get_reward_history( account_id_type& affiliate )const +{ + return my->get_reward_history( affiliate ); +} + } } // graphene::affiliate_stats diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp index d41e6d44..29c45d9f 100644 --- a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_api.hpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -47,7 +48,12 @@ namespace detail { class referral_payment { public: - + referral_payment(); + referral_payment( const operation_history_object& oho ); + operation_history_id_type id; + uint32_t block_num; + app_tag tag; + asset payout; }; class top_referred_account { @@ -73,7 +79,7 @@ class affiliate_stats_api public: affiliate_stats_api(graphene::app::application& app); - std::vector list_historic_referral_rewards( account_id_type affiliate )const; + std::vector list_historic_referral_rewards( account_id_type affiliate, operation_history_id_type start, uint16_t limit = 100 )const; // get_pending_referral_reward() - not implemented because we have continuous payouts // get_previous_referral_reward() - not implemented because we have continuous payouts std::vector list_top_referred_accounts( asset_id_type asset, uint16_t limit = 100 )const; @@ -84,7 +90,7 @@ class affiliate_stats_api } } // graphene::affiliate_stats -FC_REFLECT(graphene::affiliate_stats::referral_payment, ) +FC_REFLECT(graphene::affiliate_stats::referral_payment, (id)(block_num)(tag)(payout) ) FC_REFLECT(graphene::affiliate_stats::top_referred_account, (referral)(total_payout) ) FC_REFLECT(graphene::affiliate_stats::top_app, (app)(total_payout) ) diff --git a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp index 2262cfab..2d60f8ca 100644 --- a/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp +++ b/libraries/plugins/affiliate_stats/include/graphene/affiliate_stats/affiliate_stats_plugin.hpp @@ -63,6 +63,8 @@ class affiliate_stats_plugin : public graphene::app::plugin virtual void plugin_initialize(const boost::program_options::variables_map& options) override; virtual void plugin_startup() override; + const std::set& get_reward_history( account_id_type& affiliate )const; + friend class detail::affiliate_stats_plugin_impl; std::unique_ptr my; }; From 95844b3ef3a8d8a43067f3cb812f48c0ae930edd Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 27 Apr 2018 18:07:51 +0200 Subject: [PATCH 19/23] Refactored tests, step 1 --- tests/tests/affiliate_tests.cpp | 337 ++++++++++++++++++-------------- 1 file changed, 192 insertions(+), 145 deletions(-) diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 07bce5f2..4c5ea6bc 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -33,6 +33,8 @@ #include #include +#include + using namespace graphene::chain; using namespace graphene::db; @@ -142,53 +144,140 @@ BOOST_AUTO_TEST_CASE( account_test ) BOOST_CHECK_EQUAL( 10, share_audrey->second ); } +static vector create_players( database_fixture& db ) +{ + auto generate_private_key = [&db]( const string& seed ) { return db.generate_private_key( seed ); }; + auto create_account = [&db]( const string& name, const fc::ecc::public_key& key ) { return db.create_account( name, key ); }; + vector result; + const auto& accounts = db.db.get_index_type().indices().get(); + auto find_account = [&accounts]( const string& name ) { + auto itr = accounts.find( name ); + if( itr != accounts.end() ) + return optional( &(*itr) ); + return optional(); + }; + + { + optional tmp = find_account("alice"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + ACTOR( alice ); + result.push_back( alice_id ); + } + } + + { + optional tmp = find_account("ann"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + ACTOR( ann ); + result.push_back( ann_id ); + } + } + + { + optional tmp = find_account("audrey"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + ACTOR( audrey ); + result.push_back( audrey_id ); + } + } + + const account_id_type alice_id = result[0]; + const account_id_type ann_id = result[1]; + const account_id_type audrey_id = result[2]; + + db.generate_blocks( HARDFORK_999_TIME ); + db.generate_block(); + + signed_transaction trx; + test::set_expiration( db.db, trx ); + + optional tmp = find_account("paula"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + // Paula: 100% to Alice for Bookie / nothing for RPS + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + account_create_operation aco = db.make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + trx.operations.push_back( aco ); + processed_transaction op_result = db.db.push_transaction(trx, ~0); + result.push_back( op_result.operation_results[0].get() ); + trx.clear(); + db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(20000000) ); + } + + tmp = find_account("penny"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey + const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); + account_create_operation aco = db.make_account( "penny", penny_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; + trx.operations.push_back( aco ); + processed_transaction op_result = db.db.push_transaction(trx, ~0); + result.push_back( op_result.operation_results[0].get() ); + trx.clear(); + db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(30000000) ); + } + + tmp = find_account("petra"); + if( tmp.valid() ) + result.push_back( (*tmp)->id ); + else + { + // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey + const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); + account_create_operation aco = db.make_account( "petra", petra_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; + trx.operations.push_back( aco ); + processed_transaction op_result = db.db.push_transaction(trx, ~0); + result.push_back( op_result.operation_results[0].get() ); + trx.clear(); + db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(40000000) ); + } + + return result; +} + BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) { - ACTORS( (alice)(ann)(audrey)(irene) ); + ACTORS( (irene) ); const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; issue_uia( irene, asset( 100000, btc_id ) ); - generate_blocks( HARDFORK_999_TIME ); - generate_block(); - - test::set_expiration( db, trx ); - trx.clear(); - - // Paula: 100% to Alice for Bookie / nothing for RPS - const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); - account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; - trx.operations.push_back( aco ); - - // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey - const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); - aco = make_account( "penny", penny_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; - trx.operations.push_back( aco ); - - // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey - const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); - aco = make_account( "petra", petra_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; - trx.operations.push_back( aco ); - processed_transaction result = db.push_transaction(trx, ~0); - - account_id_type paula_id = result.operation_results[0].get(); - account_id_type penny_id = result.operation_results[1].get(); - account_id_type petra_id = result.operation_results[2].get(); + vector actors = create_players( *this ); + BOOST_REQUIRE_EQUAL( 6, actors.size() ); + const auto alice_id = actors[0]; + const auto ann_id = actors[1]; + const auto audrey_id = actors[2]; + const auto paula_id = actors[3]; + const auto penny_id = actors[4]; + const auto petra_id = actors[5]; int64_t alice_ppy = 0; int64_t ann_ppy = 0; @@ -318,56 +407,31 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) { try { - ACTORS( (alice)(ann)(audrey)(martha) ); + ACTORS( (martha) ); - generate_blocks( HARDFORK_999_TIME ); - generate_block(); + vector actors = create_players( *this ); + BOOST_REQUIRE_EQUAL( 6, actors.size() ); + const auto alice_id = actors[0]; + const auto ann_id = actors[1]; + const auto audrey_id = actors[2]; + const auto paula_id = actors[3]; + const auto penny_id = actors[4]; + const auto petra_id = actors[5]; - test::set_expiration( db, trx ); - trx.clear(); + const int64_t alice_ppy_start = get_balance( alice_id, asset_id_type() ); + const int64_t ann_ppy_start = get_balance( ann_id, asset_id_type() ); + const int64_t audrey_ppy_start = get_balance( audrey_id, asset_id_type() ); + const int64_t paula_ppy_start = get_balance( paula_id, asset_id_type() ); + const int64_t penny_ppy_start = get_balance( penny_id, asset_id_type() ); + const int64_t petra_ppy_start = get_balance( petra_id, asset_id_type() ); - // Paula: 100% to Alice for Bookie / nothing for RPS - const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); - account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; - trx.operations.push_back( aco ); - - // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey - const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); - aco = make_account( "penny", penny_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; - trx.operations.push_back( aco ); - - // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey - const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); - aco = make_account( "petra", petra_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; - trx.operations.push_back( aco ); - processed_transaction result = db.push_transaction(trx, ~0); - trx.clear(); - - account_id_type paula_id = result.operation_results[0].get(); - account_id_type penny_id = result.operation_results[1].get(); - account_id_type petra_id = result.operation_results[2].get(); + const auto paula_private_key = generate_private_key("paula"); + const auto penny_private_key = generate_private_key("penny"); + const auto petra_private_key = generate_private_key("petra"); fund( martha_id(db), asset(1000000000) ); - fund( paula_id(db), asset(20000000) ); - fund( penny_id(db), asset(30000000) ); - fund( petra_id(db), asset(40000000) ); - upgrade_to_lifetime_member( martha ); + upgrade_to_lifetime_member( martha_id(db) ); tournaments_helper helper(*this); account_id_type dividend_id = *helper.get_asset_dividend_account(); @@ -383,9 +447,9 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) generate_block(); const tournament_object& tournament = db.get( tournament_id ); - BOOST_CHECK_EQUAL( 19988000, get_balance( paula_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 29988000, get_balance( penny_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 39988000, get_balance( petra_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( paula_ppy_start - 12000, get_balance( paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( penny_ppy_start - 12000, get_balance( penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( petra_ppy_start - 12000, get_balance( petra_id, asset_id_type() ) ); const tournament_details_object& tournament_details = tournament.tournament_details_id(db); BOOST_CHECK_EQUAL( 3, tournament_details.matches.size() ); @@ -440,73 +504,42 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) BOOST_CHECK_EQUAL( 3*GRAPHENE_1_PERCENT, db.get_global_properties().parameters.rake_fee_percentage ); // Penny wins net 3*buy_in minus rake = 36000 - 1080 = 34920 - BOOST_CHECK_EQUAL( 19988000, get_balance( paula_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 29988000 + 34920, get_balance( penny_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 39988000, get_balance( petra_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( paula_ppy_start - 12000, get_balance( paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( penny_ppy_start - 12000 + 34920, get_balance( penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( petra_ppy_start - 12000, get_balance( petra_id, asset_id_type() ) ); // Dividend account receives 80% of rake = 864 BOOST_CHECK_EQUAL( div_ppy + 864, get_balance( dividend_id, asset_id_type() ) ); // 20% of rake = 216 is paid to Penny's affiliates: 43 to Alice, 64 to Ann, 109 to Audrey - BOOST_CHECK_EQUAL( 43, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 64, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 109, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( alice_ppy_start + 43, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ann_ppy_start + 64, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( audrey_ppy_start + 109, get_balance( audrey_id, asset_id_type() ) ); } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_CASE( bookie_payout_test ) { try { - ACTORS( (alice)(ann)(audrey)(irene) ); - - generate_blocks( HARDFORK_999_TIME ); - generate_block(); + ACTORS( (irene) ); const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; - test::set_expiration( db, trx ); - trx.clear(); - - // Paula: 100% to Alice for Bookie / nothing for RPS - const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); - account_create_operation aco = make_account( "paula", paula_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; - trx.operations.push_back( aco ); - - // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey - const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); - aco = make_account( "penny", penny_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; - trx.operations.push_back( aco ); - - // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey - const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); - aco = make_account( "petra", petra_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; - trx.operations.push_back( aco ); - processed_transaction result = db.push_transaction(trx, ~0); - trx.clear(); - - account_id_type paula_id = result.operation_results[0].get(); - account_id_type penny_id = result.operation_results[1].get(); - account_id_type petra_id = result.operation_results[2].get(); - - fund( paula_id(db), asset(20000000) ); - fund( penny_id(db), asset(30000000) ); - fund( petra_id(db), asset(40000000) ); + vector actors = create_players( *this ); + BOOST_REQUIRE_EQUAL( 6, actors.size() ); + const auto alice_id = actors[0]; + const auto ann_id = actors[1]; + const auto audrey_id = actors[2]; + const auto paula_id = actors[3]; + const auto penny_id = actors[4]; + const auto petra_id = actors[5]; + const int64_t alice_ppy_start = get_balance( alice_id, asset_id_type() ); + const int64_t ann_ppy_start = get_balance( ann_id, asset_id_type() ); + const int64_t audrey_ppy_start = get_balance( audrey_id, asset_id_type() ); + const int64_t paula_ppy_start = get_balance( paula_id, asset_id_type() ); + const int64_t penny_ppy_start = get_balance( penny_id, asset_id_type() ); +// const int64_t petra_ppy_start = get_balance( petra_id, asset_id_type() ); CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0); @@ -529,19 +562,20 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) uint32_t rake_value; // rake_value = (-10000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; // paula starts with 20000000, pays 10000 (bet), wins 110000, then pays 110000 (bet), wins 0 - BOOST_CHECK_EQUAL( get_balance( paula_id, asset_id_type() ), 20000000 - 10000 + 110000 - 110000 + 0 ); + BOOST_CHECK_EQUAL( get_balance( paula_id, asset_id_type() ), paula_ppy_start - 10000 + 110000 - 110000 + 0 ); // no wins -> no affiliate payouts rake_value = (-100000 - 110000 + 220000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; // penny starts with 30000000, pays 100000 (bet), wins 0, then pays 110000 (bet), wins 220000 - BOOST_CHECK_EQUAL( get_balance( penny_id, asset_id_type() ), 30000000 - 100000 + 0 - 110000 + 220000 - rake_value ); + BOOST_CHECK_EQUAL( get_balance( penny_id, asset_id_type() ), penny_ppy_start - 100000 + 0 - 110000 + 220000 - rake_value ); // penny wins 10000 net, rake is 3% of that (=300) // Affiliate pay is 20% of rake (=60). Of this, 60%=36 go to Alice, 40%=24 go to Ann - BOOST_CHECK_EQUAL( 36, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 24, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( 0, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( alice_ppy_start + 36, get_balance( alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ann_ppy_start + 24, get_balance( ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( audrey_ppy_start + 0, get_balance( audrey_id, asset_id_type() ) ); { + test::set_expiration( db, trx ); issue_uia( paula_id, asset( 1000000, btc_id ) ); issue_uia( petra_id, asset( 1000000, btc_id ) ); @@ -589,4 +623,17 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) } FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( statistics_test ) +{ try { + + INVOKE(rps_tournament_payout_test); + INVOKE(bookie_payout_test); + + generate_block(); + + graphene::affiliate_stats::affiliate_stats_api stats( app ); + +} FC_LOG_AND_RETHROW() } + BOOST_AUTO_TEST_SUITE_END() From ac078afff08367558dad0ffa58cfcf0ff5e75da7 Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 3 May 2018 17:53:05 +0200 Subject: [PATCH 20/23] More refactoring --- tests/tests/affiliate_tests.cpp | 473 +++++++++++++++----------------- 1 file changed, 226 insertions(+), 247 deletions(-) diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 4c5ea6bc..89bd5f73 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -38,6 +38,146 @@ using namespace graphene::chain; using namespace graphene::db; +class affiliate_test_helper +{ +public: + affiliate_test_helper( database_fixture& f ) : fixture(f), accounts(&fixture.db.get_index_type()) + { + fixture.generate_blocks( HARDFORK_999_TIME ); + fixture.generate_block(); + + test::set_expiration( fixture.db, fixture.trx ); + fixture.trx.clear(); + + alice_id = find_account( "alice" ); + if( alice_id == account_id_type() ) + { + ACTOR(alice); + this->alice_id = alice_id; + } + + ann_id = find_account( "ann" ); + if( ann_id == account_id_type() ) + { + ACTOR(ann); + this->ann_id = ann_id; + } + + audrey_id = find_account( "audrey" ); + if( audrey_id == account_id_type() ) + { + ACTOR(audrey); + this->audrey_id = audrey_id; + } + + paula_id = find_account("paula"); + if( paula_id == account_id_type() ) + { + // Paula: 100% to Alice for Bookie / nothing for RPS + account_create_operation aco = fixture.make_account( "paula", paula_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; + fixture.trx.operations.push_back( aco ); + processed_transaction op_result = fixture.db.push_transaction( fixture.trx, ~0 ); + paula_id = op_result.operation_results[0].get(); + fixture.trx.clear(); + fixture.fund( paula_id(fixture.db), asset(20000000) ); + } + + penny_id = find_account("penny"); + if( penny_id == account_id_type() ) + { + // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey + account_create_operation aco = fixture.make_account( "penny", penny_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; + aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; + fixture.trx.operations.push_back( aco ); + processed_transaction op_result = fixture.db.push_transaction( fixture.trx, ~0 ); + penny_id = op_result.operation_results[0].get(); + fixture.trx.clear(); + fixture.fund( penny_id(fixture.db), asset(30000000) ); + } + + petra_id = find_account("petra"); + if( petra_id == account_id_type() ) + { + // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey + account_create_operation aco = fixture.make_account( "petra", petra_private_key.get_public_key() ); + aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; + aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT + - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; + fixture.trx.operations.push_back( aco ); + processed_transaction op_result = fixture.db.push_transaction( fixture.trx, ~0 ); + petra_id = op_result.operation_results[0].get(); + fixture.trx.clear(); + fixture.fund( petra_id(fixture.db), asset(40000000) ); + } + + update_balances(); + } + + void update_balances() + { + alice_ppy = fixture.get_balance( alice_id, asset_id_type() ); + ann_ppy = fixture.get_balance( ann_id, asset_id_type() ); + audrey_ppy = fixture.get_balance( audrey_id, asset_id_type() ); + paula_ppy = fixture.get_balance( paula_id, asset_id_type() ); + penny_ppy = fixture.get_balance( penny_id, asset_id_type() ); + petra_ppy = fixture.get_balance( petra_id, asset_id_type() ); + } + + database_fixture& fixture; + + account_id_type alice_id; + account_id_type ann_id; + account_id_type audrey_id; + account_id_type paula_id; + account_id_type penny_id; + account_id_type petra_id; + + int64_t alice_ppy; + int64_t ann_ppy; + int64_t audrey_ppy; + int64_t paula_ppy; + int64_t penny_ppy; + int64_t petra_ppy; + +private: + const account_index* accounts; + account_id_type find_account( const string& name ) + { + auto itr = accounts->indices().get().find( name ); + if( itr == accounts->indices().get().end() ) return account_id_type(); + return itr->id; + } + + static fc::ecc::private_key generate_private_key( const string& seed ) + { + return database_fixture::generate_private_key( seed ); + } + + const account_object& create_account( const string& name, const fc::ecc::public_key& key ) + { + return fixture.create_account( name, key ); + } + +public: + const fc::ecc::private_key alice_private_key = generate_private_key( "alice" ); + const fc::ecc::private_key ann_private_key = generate_private_key( "ann" ); + const fc::ecc::private_key audrey_private_key = generate_private_key( "audrey" ); + const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); + const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); + const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); +}; + BOOST_FIXTURE_TEST_SUITE( affiliate_tests, database_fixture ) BOOST_AUTO_TEST_CASE( account_test ) @@ -144,125 +284,6 @@ BOOST_AUTO_TEST_CASE( account_test ) BOOST_CHECK_EQUAL( 10, share_audrey->second ); } -static vector create_players( database_fixture& db ) -{ - auto generate_private_key = [&db]( const string& seed ) { return db.generate_private_key( seed ); }; - auto create_account = [&db]( const string& name, const fc::ecc::public_key& key ) { return db.create_account( name, key ); }; - vector result; - const auto& accounts = db.db.get_index_type().indices().get(); - auto find_account = [&accounts]( const string& name ) { - auto itr = accounts.find( name ); - if( itr != accounts.end() ) - return optional( &(*itr) ); - return optional(); - }; - - { - optional tmp = find_account("alice"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - ACTOR( alice ); - result.push_back( alice_id ); - } - } - - { - optional tmp = find_account("ann"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - ACTOR( ann ); - result.push_back( ann_id ); - } - } - - { - optional tmp = find_account("audrey"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - ACTOR( audrey ); - result.push_back( audrey_id ); - } - } - - const account_id_type alice_id = result[0]; - const account_id_type ann_id = result[1]; - const account_id_type audrey_id = result[2]; - - db.generate_blocks( HARDFORK_999_TIME ); - db.generate_block(); - - signed_transaction trx; - test::set_expiration( db.db, trx ); - - optional tmp = find_account("paula"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - // Paula: 100% to Alice for Bookie / nothing for RPS - const fc::ecc::private_key paula_private_key = generate_private_key( "paula" ); - account_create_operation aco = db.make_account( "paula", paula_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT; - trx.operations.push_back( aco ); - processed_transaction op_result = db.db.push_transaction(trx, ~0); - result.push_back( op_result.operation_results[0].get() ); - trx.clear(); - db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(20000000) ); - } - - tmp = find_account("penny"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - // Penny: For Bookie 60% to Alice + 40% to Ann / For RPS 20% to Alice, 30% to Ann, 50% to Audrey - const fc::ecc::private_key penny_private_key = generate_private_key( "penny" ); - account_create_operation aco = db.make_account( "penny", penny_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id] = GRAPHENE_100_PERCENT * 3 / 5; - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[bookie]._dist[alice_id]; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] = GRAPHENE_100_PERCENT / 5; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT / 2; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[alice_id] - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id]; - trx.operations.push_back( aco ); - processed_transaction op_result = db.db.push_transaction(trx, ~0); - result.push_back( op_result.operation_results[0].get() ); - trx.clear(); - db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(30000000) ); - } - - tmp = find_account("petra"); - if( tmp.valid() ) - result.push_back( (*tmp)->id ); - else - { - // Petra: nothing for Bookie / For RPS 10% to Ann, 90% to Audrey - const fc::ecc::private_key petra_private_key = generate_private_key( "petra" ); - account_create_operation aco = db.make_account( "petra", petra_private_key.get_public_key() ); - aco.extensions.value.affiliate_distributions = affiliate_reward_distributions(); - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id] = GRAPHENE_100_PERCENT / 10; - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[audrey_id] = GRAPHENE_100_PERCENT - - aco.extensions.value.affiliate_distributions->_dists[rps]._dist[ann_id]; - trx.operations.push_back( aco ); - processed_transaction op_result = db.db.push_transaction(trx, ~0); - result.push_back( op_result.operation_results[0].get() ); - trx.clear(); - db.fund( account_id_type(op_result.operation_results[0].get())( db.db ), asset(40000000) ); - } - - return result; -} - BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) { ACTORS( (irene) ); @@ -270,18 +291,8 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; issue_uia( irene, asset( 100000, btc_id ) ); - vector actors = create_players( *this ); - BOOST_REQUIRE_EQUAL( 6, actors.size() ); - const auto alice_id = actors[0]; - const auto ann_id = actors[1]; - const auto audrey_id = actors[2]; - const auto paula_id = actors[3]; - const auto penny_id = actors[4]; - const auto petra_id = actors[5]; + affiliate_test_helper ath( *this ); - int64_t alice_ppy = 0; - int64_t ann_ppy = 0; - int64_t audrey_ppy = 0; int64_t alice_btc = 0; int64_t ann_btc = 0; int64_t audrey_btc = 0; @@ -293,32 +304,32 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) }); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // Alice has no distribution set - BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, 1000 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.alice_id, 1000 ).value ); // Paula has nothing for Bookie - BOOST_CHECK_EQUAL( 0, helper.payout( paula_id, 1000 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.paula_id, 1000 ).value ); // 20% of 4 gets rounded down to 0 - BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, 4 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.penny_id, 4 ).value ); // 20% of 5 = 1 is paid to Audrey - BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, 5 ).value ); - audrey_ppy++; + BOOST_CHECK_EQUAL( 1, helper.payout( ath.penny_id, 5 ).value ); + ath.audrey_ppy++; // 20% of 50 = 10: 2 to Alice, 3 to Ann, 5 to Audrey - BOOST_CHECK_EQUAL( 10, helper.payout( penny_id, 50 ).value ); - alice_ppy += 2; - ann_ppy += 3; - audrey_ppy += 5; + BOOST_CHECK_EQUAL( 10, helper.payout( ath.penny_id, 50 ).value ); + ath.alice_ppy += 2; + ath.ann_ppy += 3; + ath.audrey_ppy += 5; // 20% of 59 = 11: 1 to Ann, 10 to Audrey - BOOST_CHECK_EQUAL( 11, helper.payout( petra_id, 59 ).value ); - audrey_ppy += 10; - ann_ppy += 1; + BOOST_CHECK_EQUAL( 11, helper.payout( ath.petra_id, 59 ).value ); + ath.audrey_ppy += 10; + ath.ann_ppy += 1; helper.commit(); - BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( ann_ppy, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( audrey_ppy, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.alice_ppy, get_balance( ath.alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.ann_ppy, get_balance( ath.ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.audrey_ppy, get_balance( ath.audrey_id, asset_id_type() ) ); } { @@ -328,14 +339,14 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) }); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // 20% of 60 = 12: 2 to Alice, 3 to Ann, 7 to Audrey - BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, 60 ).value ); + BOOST_CHECK_EQUAL( 12, helper.payout( ath.penny_id, 60 ).value ); alice_btc += 2; ann_btc += 3; audrey_btc += 7; helper.commit(); - BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); - BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); - BOOST_CHECK_EQUAL( audrey_btc, get_balance( audrey_id, btc_id ) ); + BOOST_CHECK_EQUAL( alice_btc, get_balance( ath.alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( ann_btc, get_balance( ath.ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( audrey_btc, get_balance( ath.audrey_id, btc_id ) ); } { @@ -344,33 +355,33 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) } ); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // Alice has no distribution set - BOOST_CHECK_EQUAL( 0, helper.payout( alice_id, 1000 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.alice_id, 1000 ).value ); // Petra has nothing for Bookie - BOOST_CHECK_EQUAL( 0, helper.payout( petra_id, 1000 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.petra_id, 1000 ).value ); // 20% of 4 gets rounded down to 0 - BOOST_CHECK_EQUAL( 0, helper.payout( penny_id, 4 ).value ); + BOOST_CHECK_EQUAL( 0, helper.payout( ath.penny_id, 4 ).value ); // 20% of 5 = 1 is paid to Ann - BOOST_CHECK_EQUAL( 1, helper.payout( penny_id, 5 ).value ); - ann_ppy++; + BOOST_CHECK_EQUAL( 1, helper.payout( ath.penny_id, 5 ).value ); + ath.ann_ppy++; // 20% of 40 = 8: 8 to Alice - BOOST_CHECK_EQUAL( 8, helper.payout( paula_id, 40 ).value ); - alice_ppy += 8; + BOOST_CHECK_EQUAL( 8, helper.payout( ath.paula_id, 40 ).value ); + ath.alice_ppy += 8; // intermediate commit should clear internal accumulator helper.commit(); // 20% of 59 = 11: 6 to Alice, 5 to Ann - BOOST_CHECK_EQUAL( 11, helper.payout( penny_id, 59 ).value ); - alice_ppy += 6; - ann_ppy += 5; + BOOST_CHECK_EQUAL( 11, helper.payout( ath.penny_id, 59 ).value ); + ath.alice_ppy += 6; + ath.ann_ppy += 5; helper.commit(); - BOOST_CHECK_EQUAL( alice_ppy, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( ann_ppy, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( audrey_ppy, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.alice_ppy, get_balance( ath.alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.ann_ppy, get_balance( ath.ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.audrey_ppy, get_balance( ath.audrey_id, asset_id_type() ) ); } { @@ -379,13 +390,13 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) } ); affiliate_payout_helper helper = affiliate_payout_helper( db, game ); // 20% of 60 = 12: 7 to Alice, 5 to Ann - BOOST_CHECK_EQUAL( 12, helper.payout( penny_id, 60 ).value ); + BOOST_CHECK_EQUAL( 12, helper.payout( ath.penny_id, 60 ).value ); alice_btc += 7; ann_btc += 5; helper.commit(); - BOOST_CHECK_EQUAL( alice_btc, get_balance( alice_id, btc_id ) ); - BOOST_CHECK_EQUAL( ann_btc, get_balance( ann_id, btc_id ) ); - BOOST_CHECK_EQUAL( audrey_btc, get_balance( audrey_id, btc_id ) ); + BOOST_CHECK_EQUAL( alice_btc, get_balance( ath.alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( ann_btc, get_balance( ath.ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( audrey_btc, get_balance( ath.audrey_id, btc_id ) ); } { @@ -393,8 +404,8 @@ BOOST_AUTO_TEST_CASE( affiliate_payout_helper_test ) auto& index = db.get_index_type().indices().get(); auto itr = index.find( boost::make_tuple( account_id_type(), asset_id_type() ) ); BOOST_CHECK( itr != index.end() ); - db.modify( *itr, [alice_ppy,ann_ppy,audrey_ppy]( account_balance_object& bal ) { - bal.balance -= alice_ppy + ann_ppy + audrey_ppy; + db.modify( *itr, [&ath]( account_balance_object& bal ) { + bal.balance -= ath.alice_ppy + ath.ann_ppy + ath.audrey_ppy; }); itr = index.find( boost::make_tuple( irene_id, btc_id ) ); @@ -409,25 +420,7 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) { try { ACTORS( (martha) ); - vector actors = create_players( *this ); - BOOST_REQUIRE_EQUAL( 6, actors.size() ); - const auto alice_id = actors[0]; - const auto ann_id = actors[1]; - const auto audrey_id = actors[2]; - const auto paula_id = actors[3]; - const auto penny_id = actors[4]; - const auto petra_id = actors[5]; - - const int64_t alice_ppy_start = get_balance( alice_id, asset_id_type() ); - const int64_t ann_ppy_start = get_balance( ann_id, asset_id_type() ); - const int64_t audrey_ppy_start = get_balance( audrey_id, asset_id_type() ); - const int64_t paula_ppy_start = get_balance( paula_id, asset_id_type() ); - const int64_t penny_ppy_start = get_balance( penny_id, asset_id_type() ); - const int64_t petra_ppy_start = get_balance( petra_id, asset_id_type() ); - - const auto paula_private_key = generate_private_key("paula"); - const auto penny_private_key = generate_private_key("penny"); - const auto petra_private_key = generate_private_key("petra"); + affiliate_test_helper ath( *this ); fund( martha_id(db), asset(1000000000) ); @@ -440,16 +433,16 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) tournament_id_type tournament_id = helper.create_tournament( martha_id, martha_private_key, buy_in, 3, 3, 1, 1 ); BOOST_REQUIRE(tournament_id == tournament_id_type()); - helper.join_tournament( tournament_id, paula_id, paula_id, paula_private_key, buy_in); - helper.join_tournament( tournament_id, penny_id, penny_id, penny_private_key, buy_in); - helper.join_tournament( tournament_id, petra_id, petra_id, petra_private_key, buy_in); + helper.join_tournament( tournament_id, ath.paula_id, ath.paula_id, ath.paula_private_key, buy_in); + helper.join_tournament( tournament_id, ath.penny_id, ath.penny_id, ath.penny_private_key, buy_in); + helper.join_tournament( tournament_id, ath.petra_id, ath.petra_id, ath.petra_private_key, buy_in); generate_block(); const tournament_object& tournament = db.get( tournament_id ); - BOOST_CHECK_EQUAL( paula_ppy_start - 12000, get_balance( paula_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( penny_ppy_start - 12000, get_balance( penny_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( petra_ppy_start - 12000, get_balance( petra_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.paula_ppy - 12000, get_balance( ath.paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.penny_ppy - 12000, get_balance( ath.penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.petra_ppy - 12000, get_balance( ath.petra_id, asset_id_type() ) ); const tournament_details_object& tournament_details = tournament.tournament_details_id(db); BOOST_CHECK_EQUAL( 3, tournament_details.matches.size() ); @@ -458,24 +451,24 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) const match_object& match = tournament_details.matches[0](db); BOOST_CHECK_EQUAL( int64_t(match_state::match_complete), int64_t(match.get_state()) ); BOOST_CHECK_EQUAL( 1, match.players.size() ); - BOOST_CHECK_EQUAL( object_id_type(penny_id).instance(), object_id_type(match.players[0]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(ath.penny_id).instance(), object_id_type(match.players[0]).instance() ); } { // match 2 is paula vs. petra: paula wins const match_object& match = tournament_details.matches[1](db); BOOST_CHECK_EQUAL( int64_t(match_state::match_in_progress), int64_t(match.get_state()) ); BOOST_CHECK_EQUAL( 2, match.players.size() ); - BOOST_CHECK_EQUAL( object_id_type(paula_id).instance(), object_id_type(match.players[0]).instance() ); - BOOST_CHECK_EQUAL( object_id_type(petra_id).instance(), object_id_type(match.players[1]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(ath.paula_id).instance(), object_id_type(match.players[0]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(ath.petra_id).instance(), object_id_type(match.players[1]).instance() ); BOOST_CHECK_EQUAL( 1, match.games.size() ); const game_object& game = match.games[0](db); BOOST_CHECK_EQUAL( int64_t(game_state::expecting_commit_moves), int64_t(game.get_state()) ); - helper.rps_throw( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); - helper.rps_throw( game.id, petra_id, rock_paper_scissors_gesture::rock, petra_private_key ); + helper.rps_throw( game.id, ath.paula_id, rock_paper_scissors_gesture::paper, ath.paula_private_key ); + helper.rps_throw( game.id, ath.petra_id, rock_paper_scissors_gesture::rock, ath.petra_private_key ); BOOST_CHECK_EQUAL( int64_t(game_state::expecting_reveal_moves), int64_t(game.get_state()) ); - helper.rps_reveal( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); - helper.rps_reveal( game.id, petra_id, rock_paper_scissors_gesture::rock, petra_private_key ); + helper.rps_reveal( game.id, ath.paula_id, rock_paper_scissors_gesture::paper, ath.paula_private_key ); + helper.rps_reveal( game.id, ath.petra_id, rock_paper_scissors_gesture::rock, ath.petra_private_key ); BOOST_CHECK_EQUAL( int64_t(game_state::game_complete), int64_t(game.get_state()) ); BOOST_CHECK_EQUAL( 1, match.games.size() ); @@ -485,18 +478,18 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) const match_object& match = tournament_details.matches[2](db); BOOST_CHECK_EQUAL( int64_t(match_state::match_in_progress), int64_t(match.get_state()) ); BOOST_CHECK_EQUAL( 2, match.players.size() ); - BOOST_CHECK_EQUAL( object_id_type(penny_id).instance(), object_id_type(match.players[0]).instance() ); - BOOST_CHECK_EQUAL( object_id_type(paula_id).instance(), object_id_type(match.players[1]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(ath.penny_id).instance(), object_id_type(match.players[0]).instance() ); + BOOST_CHECK_EQUAL( object_id_type(ath.paula_id).instance(), object_id_type(match.players[1]).instance() ); BOOST_CHECK_EQUAL( 1, match.games.size() ); const game_object& game = match.games[0](db); BOOST_CHECK_EQUAL( int64_t(game_state::expecting_commit_moves), int64_t(game.get_state()) ); - helper.rps_throw( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); - helper.rps_throw( game.id, penny_id, rock_paper_scissors_gesture::scissors, penny_private_key ); + helper.rps_throw( game.id, ath.paula_id, rock_paper_scissors_gesture::paper, ath.paula_private_key ); + helper.rps_throw( game.id, ath.penny_id, rock_paper_scissors_gesture::scissors, ath.penny_private_key ); BOOST_CHECK_EQUAL( int64_t(game_state::expecting_reveal_moves), int64_t(game.get_state()) ); - helper.rps_reveal( game.id, paula_id, rock_paper_scissors_gesture::paper, paula_private_key ); - helper.rps_reveal( game.id, penny_id, rock_paper_scissors_gesture::scissors, penny_private_key ); + helper.rps_reveal( game.id, ath.paula_id, rock_paper_scissors_gesture::paper, ath.paula_private_key ); + helper.rps_reveal( game.id, ath.penny_id, rock_paper_scissors_gesture::scissors, ath.penny_private_key ); BOOST_CHECK_EQUAL( int64_t(game_state::game_complete), int64_t(game.get_state()) ); BOOST_CHECK_EQUAL( int64_t(match_state::match_complete), int64_t(match.get_state()) ); @@ -504,17 +497,17 @@ BOOST_AUTO_TEST_CASE( rps_tournament_payout_test ) BOOST_CHECK_EQUAL( 3*GRAPHENE_1_PERCENT, db.get_global_properties().parameters.rake_fee_percentage ); // Penny wins net 3*buy_in minus rake = 36000 - 1080 = 34920 - BOOST_CHECK_EQUAL( paula_ppy_start - 12000, get_balance( paula_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( penny_ppy_start - 12000 + 34920, get_balance( penny_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( petra_ppy_start - 12000, get_balance( petra_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.paula_ppy - 12000, get_balance( ath.paula_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.penny_ppy - 12000 + 34920, get_balance( ath.penny_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.petra_ppy - 12000, get_balance( ath.petra_id, asset_id_type() ) ); // Dividend account receives 80% of rake = 864 BOOST_CHECK_EQUAL( div_ppy + 864, get_balance( dividend_id, asset_id_type() ) ); // 20% of rake = 216 is paid to Penny's affiliates: 43 to Alice, 64 to Ann, 109 to Audrey - BOOST_CHECK_EQUAL( alice_ppy_start + 43, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( ann_ppy_start + 64, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( audrey_ppy_start + 109, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.alice_ppy + 43, get_balance( ath.alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.ann_ppy + 64, get_balance( ath.ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.audrey_ppy + 109, get_balance( ath.audrey_id, asset_id_type() ) ); } FC_LOG_AND_RETHROW() } @@ -525,31 +518,17 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id; - vector actors = create_players( *this ); - BOOST_REQUIRE_EQUAL( 6, actors.size() ); - const auto alice_id = actors[0]; - const auto ann_id = actors[1]; - const auto audrey_id = actors[2]; - const auto paula_id = actors[3]; - const auto penny_id = actors[4]; - const auto petra_id = actors[5]; - - const int64_t alice_ppy_start = get_balance( alice_id, asset_id_type() ); - const int64_t ann_ppy_start = get_balance( ann_id, asset_id_type() ); - const int64_t audrey_ppy_start = get_balance( audrey_id, asset_id_type() ); - const int64_t paula_ppy_start = get_balance( paula_id, asset_id_type() ); - const int64_t penny_ppy_start = get_balance( penny_id, asset_id_type() ); -// const int64_t petra_ppy_start = get_balance( petra_id, asset_id_type() ); + affiliate_test_helper ath( *this ); CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0); // place bets at 10:1 - place_bet(paula_id, capitals_win_market.id, bet_type::back, asset(10000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(penny_id, capitals_win_market.id, bet_type::lay, asset(100000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.paula_id, capitals_win_market.id, bet_type::back, asset(10000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.penny_id, capitals_win_market.id, bet_type::lay, asset(100000, asset_id_type()), 11 * GRAPHENE_BETTING_ODDS_PRECISION); // reverse positions at 1:1 - place_bet(paula_id, capitals_win_market.id, bet_type::lay, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(penny_id, capitals_win_market.id, bet_type::back, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.paula_id, capitals_win_market.id, bet_type::lay, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.penny_id, capitals_win_market.id, bet_type::back, asset(110000, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION); update_betting_market_group(moneyline_betting_markets.id, graphene::chain::keywords::_status = betting_market_group_status::closed); resolve_betting_market_group(moneyline_betting_markets.id, @@ -562,22 +541,22 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) uint32_t rake_value; // rake_value = (-10000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; // paula starts with 20000000, pays 10000 (bet), wins 110000, then pays 110000 (bet), wins 0 - BOOST_CHECK_EQUAL( get_balance( paula_id, asset_id_type() ), paula_ppy_start - 10000 + 110000 - 110000 + 0 ); + BOOST_CHECK_EQUAL( get_balance( ath.paula_id, asset_id_type() ), ath.paula_ppy - 10000 + 110000 - 110000 + 0 ); // no wins -> no affiliate payouts rake_value = (-100000 - 110000 + 220000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; // penny starts with 30000000, pays 100000 (bet), wins 0, then pays 110000 (bet), wins 220000 - BOOST_CHECK_EQUAL( get_balance( penny_id, asset_id_type() ), penny_ppy_start - 100000 + 0 - 110000 + 220000 - rake_value ); + BOOST_CHECK_EQUAL( get_balance( ath.penny_id, asset_id_type() ), ath.penny_ppy - 100000 + 0 - 110000 + 220000 - rake_value ); // penny wins 10000 net, rake is 3% of that (=300) // Affiliate pay is 20% of rake (=60). Of this, 60%=36 go to Alice, 40%=24 go to Ann - BOOST_CHECK_EQUAL( alice_ppy_start + 36, get_balance( alice_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( ann_ppy_start + 24, get_balance( ann_id, asset_id_type() ) ); - BOOST_CHECK_EQUAL( audrey_ppy_start + 0, get_balance( audrey_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.alice_ppy + 36, get_balance( ath.alice_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.ann_ppy + 24, get_balance( ath.ann_id, asset_id_type() ) ); + BOOST_CHECK_EQUAL( ath.audrey_ppy + 0, get_balance( ath.audrey_id, asset_id_type() ) ); { test::set_expiration( db, trx ); - issue_uia( paula_id, asset( 1000000, btc_id ) ); - issue_uia( petra_id, asset( 1000000, btc_id ) ); + issue_uia( ath.paula_id, asset( 1000000, btc_id ) ); + issue_uia( ath.petra_id, asset( 1000000, btc_id ) ); create_event({{"en", "Washington Capitals/Chicago Blackhawks"}, {"zh_Hans", "華盛頓首都隊/芝加哥黑鷹"}, {"ja", "ワシントン・キャピタルズ/シカゴ・ブラックホークス"}}, {{"en", "2016-17"}}, nhl.id); \ generate_blocks(1); \ @@ -593,12 +572,12 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) const betting_market_object& blackhawks_win_market2 = *db.get_index_type().indices().get().rbegin(); // place bets at 10:1 - place_bet(paula_id, capitals_win_market2.id, bet_type::back, asset(10000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(petra_id, capitals_win_market2.id, bet_type::lay, asset(100000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.paula_id, capitals_win_market2.id, bet_type::back, asset(10000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.petra_id, capitals_win_market2.id, bet_type::lay, asset(100000, btc_id), 11 * GRAPHENE_BETTING_ODDS_PRECISION); // reverse positions at 1:1 - place_bet(paula_id, capitals_win_market2.id, bet_type::lay, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); - place_bet(petra_id, capitals_win_market2.id, bet_type::back, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.paula_id, capitals_win_market2.id, bet_type::lay, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); + place_bet(ath.petra_id, capitals_win_market2.id, bet_type::back, asset(110000, btc_id), 2 * GRAPHENE_BETTING_ODDS_PRECISION); update_betting_market_group(moneyline_betting_markets2.id, graphene::chain::keywords::_status = betting_market_group_status::closed); resolve_betting_market_group(moneyline_betting_markets2.id, @@ -608,16 +587,16 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) rake_value = (-10000 + 0 - 110000 + 220000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; // paula starts with 1000000, pays 10000 (bet), wins 0, then pays 110000 (bet), wins 220000 - BOOST_CHECK_EQUAL( get_balance( paula_id, btc_id ), 1000000 - 10000 + 0 - 110000 + 220000 - rake_value ); + BOOST_CHECK_EQUAL( get_balance( ath.paula_id, btc_id ), 1000000 - 10000 + 0 - 110000 + 220000 - rake_value ); // net win 100000, 3% rake = 3000, 20% of that is 600, 100% of that goes to Alice - BOOST_CHECK_EQUAL( 600, get_balance( alice_id, btc_id ) ); - BOOST_CHECK_EQUAL( 0, get_balance( ann_id, btc_id ) ); - BOOST_CHECK_EQUAL( 0, get_balance( audrey_id, btc_id ) ); + BOOST_CHECK_EQUAL( 600, get_balance( ath.alice_id, btc_id ) ); + BOOST_CHECK_EQUAL( 0, get_balance( ath.ann_id, btc_id ) ); + BOOST_CHECK_EQUAL( 0, get_balance( ath.audrey_id, btc_id ) ); // rake_value = (-100000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100; rake_value = 0; // petra starts with 1000000, pays 100000 (bet), wins 110000, then pays 110000 (bet), wins 0 - BOOST_CHECK_EQUAL( get_balance( petra_id, btc_id ), 1000000 - 100000 + 110000 - 110000 + 0 - rake_value ); + BOOST_CHECK_EQUAL( get_balance( ath.petra_id, btc_id ), 1000000 - 100000 + 110000 - 110000 + 0 - rake_value ); // petra wins nothing -> no payout } From b40e823875d439fe2c3abd61870a8fa2e929badd Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Fri, 4 May 2018 18:35:48 +0200 Subject: [PATCH 21/23] Added unit test for affiliate_stats plugin and API --- tests/common/database_fixture.cpp | 9 ++- tests/tests/affiliate_tests.cpp | 114 ++++++++++++++++++++++++++++++ 2 files changed, 121 insertions(+), 2 deletions(-) diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 28c44564..4db37ea7 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -83,6 +84,7 @@ database_fixture::database_fixture() auto ahplugin = app.register_plugin(); auto mhplugin = app.register_plugin(); auto bookieplugin = app.register_plugin(); + auto affiliateplugin = app.register_plugin(); init_account_pub_key = init_account_priv_key.get_public_key(); boost::program_options::variables_map options; @@ -114,10 +116,13 @@ database_fixture::database_fixture() mhplugin->plugin_initialize(options); bookieplugin->plugin_set_app(&app); bookieplugin->plugin_initialize(options); + affiliateplugin->plugin_set_app(&app); + affiliateplugin->plugin_initialize(options); ahplugin->plugin_startup(); mhplugin->plugin_startup(); bookieplugin->plugin_startup(); + affiliateplugin->plugin_startup(); generate_block(); @@ -451,7 +456,7 @@ const asset_object& database_fixture::get_asset( const string& symbol )const { const auto& idx = db.get_index_type().indices().get(); const auto itr = idx.find(symbol); - assert( itr != idx.end() ); + FC_ASSERT( itr != idx.end() ); return *itr; } @@ -459,7 +464,7 @@ const account_object& database_fixture::get_account( const string& name )const { const auto& idx = db.get_index_type().indices().get(); const auto itr = idx.find(name); - assert( itr != idx.end() ); + FC_ASSERT( itr != idx.end() ); return *itr; } diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index 89bd5f73..e1b23d9c 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -34,6 +34,7 @@ #include #include +#include using namespace graphene::chain; using namespace graphene::db; @@ -607,12 +608,125 @@ BOOST_AUTO_TEST_CASE( statistics_test ) { try { INVOKE(rps_tournament_payout_test); + + affiliate_test_helper ath( *this ); + + transfer( ath.audrey_id, ath.alice_id, asset( 10 ), asset(0) ); + transfer( ath.audrey_id, ath.ann_id, asset( 10 ), asset(0) ); + INVOKE(bookie_payout_test); + const asset_id_type btc_id = get_asset( "BTC" ).id; + + transfer( ath.alice_id, ath.ann_id, asset( 100, btc_id ), asset(0) ); + transfer( ath.alice_id, ath.audrey_id, asset( 100, btc_id ), asset(0) ); + generate_block(); + { + const auto& idx = db.get_index_type().indices().get(); + BOOST_CHECK_EQUAL( 2, idx.size() ); // penny 216+60 CORE, paula 600 BTC + } + + { + const auto& idx = db.get_index_type().indices().get(); + BOOST_CHECK_EQUAL( 3, idx.size() ); // rps 216 CORE, bookie 60 CORE + 600 BTC + } + graphene::affiliate_stats::affiliate_stats_api stats( app ); + { + std::vector refs = stats.list_historic_referral_rewards( ath.alice_id, operation_history_id_type() ); + BOOST_CHECK_EQUAL( 3, refs.size() ); + BOOST_REQUIRE_LE( 1, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::rps, refs[0].tag ); + BOOST_CHECK_EQUAL( 43, refs[0].payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[0].payout.asset_id.instance.value ); + BOOST_REQUIRE_LE( 2, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::bookie, refs[1].tag ); + BOOST_CHECK_EQUAL( 36, refs[1].payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[1].payout.asset_id.instance.value ); + BOOST_REQUIRE_LE( 3, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::bookie, refs[2].tag ); + BOOST_CHECK_EQUAL( 600, refs[2].payout.amount.value ); + BOOST_CHECK_EQUAL( btc_id.instance.value, refs[2].payout.asset_id.instance.value ); + + BOOST_CHECK_EQUAL( 3, stats.list_historic_referral_rewards( ath.alice_id, refs[0].id ).size() ); + BOOST_CHECK_EQUAL( 2, stats.list_historic_referral_rewards( ath.alice_id, refs[1].id ).size() ); + BOOST_CHECK_EQUAL( 1, stats.list_historic_referral_rewards( ath.alice_id, refs[2].id ).size() ); + + refs = stats.list_historic_referral_rewards( ath.ann_id, operation_history_id_type() ); + BOOST_CHECK_EQUAL( 2, refs.size() ); + BOOST_REQUIRE_LE( 1, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::rps, refs[0].tag ); + BOOST_CHECK_EQUAL( 64, refs[0].payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[0].payout.asset_id.instance.value ); + BOOST_REQUIRE_LE( 2, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::bookie, refs[1].tag ); + BOOST_CHECK_EQUAL( 24, refs[1].payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[1].payout.asset_id.instance.value ); + + BOOST_CHECK_EQUAL( 2, stats.list_historic_referral_rewards( ath.ann_id, refs[0].id ).size() ); + BOOST_CHECK_EQUAL( 1, stats.list_historic_referral_rewards( ath.ann_id, refs[1].id ).size() ); + + refs = stats.list_historic_referral_rewards( ath.audrey_id, operation_history_id_type() ); + BOOST_CHECK_EQUAL( 1, refs.size() ); + BOOST_REQUIRE_LE( 1, refs.size() ); + BOOST_CHECK_EQUAL( app_tag::rps, refs[0].tag ); + BOOST_CHECK_EQUAL( 109, refs[0].payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[0].payout.asset_id.instance.value ); + + BOOST_CHECK_EQUAL( 0, stats.list_historic_referral_rewards( ath.alice_id, operation_history_id_type(9990) ).size() ); + BOOST_CHECK_EQUAL( 1, stats.list_historic_referral_rewards( ath.alice_id, operation_history_id_type(), 1 ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_historic_referral_rewards( ath.paula_id, operation_history_id_type() ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_historic_referral_rewards( ath.penny_id, operation_history_id_type() ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_historic_referral_rewards( ath.petra_id, operation_history_id_type() ).size() ); + } + + { + std::vector refs = stats.list_top_referred_accounts( asset_id_type() ); + BOOST_CHECK_EQUAL( 1, refs.size() ); + BOOST_REQUIRE_LE( 1, refs.size() ); + BOOST_CHECK_EQUAL( ath.penny_id.instance.value, refs[0].referral.instance.value ); + BOOST_CHECK_EQUAL( 276, refs[0].total_payout.amount.value ); + BOOST_CHECK_EQUAL( 0, refs[0].total_payout.asset_id.instance.value ); + + refs = stats.list_top_referred_accounts( btc_id ); + BOOST_CHECK_EQUAL( 1, refs.size() ); + BOOST_REQUIRE_LE( 1, refs.size() ); + BOOST_CHECK_EQUAL( ath.paula_id.instance.value, refs[0].referral.instance.value ); + BOOST_CHECK_EQUAL( 600, refs[0].total_payout.amount.value ); + BOOST_CHECK_EQUAL( btc_id.instance.value, refs[0].total_payout.asset_id.instance.value ); + + BOOST_CHECK_EQUAL( 1, stats.list_top_referred_accounts( btc_id, 1 ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_top_referred_accounts( btc_id, 0 ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_top_referred_accounts( asset_id_type(9999) ).size() ); + } + + { + std::vector top = stats.list_top_rewards_per_app( asset_id_type() ); + BOOST_CHECK_EQUAL( 2, top.size() ); + BOOST_REQUIRE_LE( 1, top.size() ); + BOOST_CHECK_EQUAL( app_tag::rps, top[0].app ); + BOOST_CHECK_EQUAL( 216, top[0].total_payout.amount.value ); + BOOST_CHECK_EQUAL( 0, top[0].total_payout.asset_id.instance.value ); + BOOST_REQUIRE_LE( 2, top.size() ); + BOOST_CHECK_EQUAL( app_tag::bookie, top[1].app ); + BOOST_CHECK_EQUAL( 60, top[1].total_payout.amount.value ); + BOOST_CHECK_EQUAL( 0, top[1].total_payout.asset_id.instance.value ); + + top = stats.list_top_rewards_per_app( btc_id ); + BOOST_CHECK_EQUAL( 1, top.size() ); + BOOST_REQUIRE_LE( 1, top.size() ); + BOOST_CHECK_EQUAL( app_tag::bookie, top[0].app ); + BOOST_CHECK_EQUAL( 600, top[0].total_payout.amount.value ); + BOOST_CHECK_EQUAL( btc_id.instance.value, top[0].total_payout.asset_id.instance.value ); + + BOOST_CHECK_EQUAL( 1, stats.list_top_rewards_per_app( asset_id_type(), 1 ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_top_referred_accounts( btc_id, 0 ).size() ); + BOOST_CHECK_EQUAL( 0, stats.list_top_rewards_per_app( asset_id_type(9999) ).size() ); + } + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() From 9e624bd2c8c35ab65ce3d5b508f7d22e23dbd54b Mon Sep 17 00:00:00 2001 From: Peter Conrad Date: Thu, 3 May 2018 22:35:10 +0200 Subject: [PATCH 22/23] Payout operation fix --- libraries/chain/affiliate_payout.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/libraries/chain/affiliate_payout.cpp b/libraries/chain/affiliate_payout.cpp index 328a0f64..3138117c 100644 --- a/libraries/chain/affiliate_payout.cpp +++ b/libraries/chain/affiliate_payout.cpp @@ -85,8 +85,9 @@ namespace graphene { namespace chain { { for( const auto& entry : accumulator ) { - _db.adjust_balance( entry.first, asset( entry.second, payout_asset ) ); - _db.push_applied_operation( affiliate_payout_operation( entry.first, tag, entry.second ) ); + asset payout = asset( entry.second, payout_asset ); + _db.adjust_balance( entry.first, payout ); + _db.push_applied_operation( affiliate_payout_operation( entry.first, tag, payout ) ); } accumulator.clear(); } From fc21e79206081fdf81d573d6adf3cdac387257be Mon Sep 17 00:00:00 2001 From: Fabian Schuh Date: Thu, 11 Oct 2018 13:36:49 +0200 Subject: [PATCH 23/23] Fix rebase --- libraries/chain/db_bet.cpp | 4 ++-- libraries/chain/proposal_evaluator.cpp | 31 +++++++++++--------------- programs/witness_node/main.cpp | 4 ++-- tests/tests/affiliate_tests.cpp | 2 +- 4 files changed, 18 insertions(+), 23 deletions(-) diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 2c40c200..8c3e1357 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -307,6 +307,8 @@ void database::settle_betting_market_group(const betting_market_group_object& be fc_dlog(fc::logger::get("betting"), "removing betting market group ${id}", ("id", betting_market_group.id)); remove(betting_market_group); + + payout_helper.commit(); } void database::remove_completed_events() @@ -330,8 +332,6 @@ void database::remove_completed_events() fc_dlog(fc::logger::get("betting"), "removing settled event ${id}", ("id", event.id)); remove(event); } - - payout_helper.commit(); } share_type adjust_betting_position(database& db, diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 83282537..a6640ea4 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -55,21 +55,21 @@ struct proposal_operation_hardfork_visitor "Parameter extensions are not allowed yet!" ); } - void operator()(const graphene::chain::tournament_payout_operation &o) const { - // TODO: move check into tournament_payout_operation::validate after HARDFORK_999_TIME - FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); - } + void operator()(const graphene::chain::tournament_payout_operation &o) const { + // TODO: move check into tournament_payout_operation::validate after HARDFORK_999_TIME + FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); + } - void operator()(const graphene::chain::asset_settle_cancel_operation &o) const { - // TODO: move check into asset_settle_cancel_operation::validate after HARDFORK_999_TIME - FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); - } + void operator()(const graphene::chain::asset_settle_cancel_operation &o) const { + // TODO: move check into asset_settle_cancel_operation::validate after HARDFORK_999_TIME + FC_ASSERT( block_time < HARDFORK_999_TIME, "Not allowed!" ); + } - void operator()(const graphene::chain::account_create_operation &aco) const { - // TODO: remove after HARDFORK_999_TIME - if (block_time < HARDFORK_999_TIME) - FC_ASSERT( !aco.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); - } + void operator()(const graphene::chain::account_create_operation &aco) const { + // TODO: remove after HARDFORK_999_TIME + if (block_time < HARDFORK_999_TIME) + FC_ASSERT( !aco.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); + } void operator()(const sport_update_operation &v) const { FC_ASSERT( block_time >= HARDFORK_1000_TIME, "sport_update_operation not allowed yet!" ); @@ -135,11 +135,6 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_1000_TIME, "event_update_status_operation not allowed yet!" ); } - void operator()(const graphene::chain::account_create_operation &aco) const { - if (block_time < HARDFORK_999_TIME) - FC_ASSERT( !aco.extensions.value.affiliate_distributions.valid(), "Affiliate reward distributions not allowed yet" ); - } - // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index 4d080aa4..6f55d593 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -32,7 +32,7 @@ #include #include #include -#include +//#include #include #include @@ -87,7 +87,7 @@ int main(int argc, char** argv) { auto list_plug = node->register_plugin(); auto affiliate_stats_plug = node->register_plugin(); auto bookie_plug = node->register_plugin(); - auto snapshot_plug = node->register_plugin(); +// auto snapshot_plug = node->register_plugin(); try { diff --git a/tests/tests/affiliate_tests.cpp b/tests/tests/affiliate_tests.cpp index e1b23d9c..ab109ad3 100644 --- a/tests/tests/affiliate_tests.cpp +++ b/tests/tests/affiliate_tests.cpp @@ -537,7 +537,7 @@ BOOST_AUTO_TEST_CASE( bookie_payout_test ) {blackhawks_win_market.id, betting_market_resolution_type::not_win}}); generate_block(); - uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage; + uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage(); BOOST_CHECK_EQUAL( 3 * GRAPHENE_1_PERCENT, rake_fee_percentage ); uint32_t rake_value; // rake_value = (-10000 + 110000 - 110000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;