Merge branch 'affiliate_rewards_rebased' into beatrice

This commit is contained in:
Fabian Schuh 2018-10-11 13:51:03 +02:00
commit 2e38cd4f04
No known key found for this signature in database
GPG key ID: F2538A4B282D6238
38 changed files with 2560 additions and 479 deletions

View file

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

View file

@ -117,6 +117,12 @@ namespace graphene { namespace app {
if( _app.get_plugin( "bookie" ) )
_bookie_api = std::make_shared<graphene::bookie::bookie_api>(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<graphene::affiliate_stats::affiliate_stats_api>(std::ref(_app));
}
return;
}
@ -281,6 +287,12 @@ namespace graphene { namespace app {
return *_bookie_api;
}
fc::api<graphene::affiliate_stats::affiliate_stats_api> login_api::affiliate_stats() const
{
FC_ASSERT(_affiliate_stats_api);
return *_affiliate_stats_api;
}
#if 0
vector<account_id_type> get_relevant_accounts( const object* obj )
{

View file

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

View file

@ -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<account_id_type>& result )

View file

@ -32,6 +32,7 @@
#include <graphene/accounts_list/accounts_list_plugin.hpp>
#include <graphene/debug_witness/debug_api.hpp>
#include <graphene/affiliate_stats/affiliate_stats_api.hpp>
#include <graphene/bookie/bookie_api.hpp>
#include <graphene/net/node.hpp>
@ -362,6 +363,8 @@ namespace graphene { namespace app {
fc::api<graphene::debug_witness::debug_api> debug()const;
/// @brief Retrieve the bookie API (if available)
fc::api<graphene::bookie::bookie_api> bookie()const;
/// @brief Retrieve the affiliate_stats API (if available)
fc::api<graphene::affiliate_stats::affiliate_stats_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> > _asset_api;
optional< fc::api<graphene::debug_witness::debug_api> > _debug_api;
optional< fc::api<graphene::bookie::bookie_api> > _bookie_api;
optional< fc::api<graphene::affiliate_stats::affiliate_stats_api> > _affiliate_stats_api;
};
}} // graphene::app
@ -446,4 +450,5 @@ FC_API(graphene::app::login_api,
(asset)
(debug)
(bookie)
(affiliate_stats)
)

View file

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

View file

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

View file

@ -0,0 +1,95 @@
/*
* 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 <graphene/chain/account_object.hpp>
#include <graphene/chain/affiliate_payout.hpp>
#include <graphene/chain/database.hpp>
#include <fc/uint128.hpp>
namespace graphene { namespace chain {
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, share_type 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;
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;
const uint16_t share = entry.second;
fc::uint128_t payout = to_pay.value;
if( share != remaining )
{
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 )
{
if ( accumulator.find(affiliate) == accumulator.end() )
accumulator[affiliate] = payout.to_uint64();
else
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, asset( amount, payout_asset ) ) );
return paid;
}
void affiliate_payout_helper::commit()
{
for( const auto& entry : accumulator )
{
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();
}
} } // graphene::chain

View file

@ -24,6 +24,7 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/affiliate_payout.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/event_object.hpp>
@ -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<betting_market_id_type, betting_market_resolution_type> resolutions_by_market_id;
@ -261,8 +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)
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
@ -300,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()

View file

@ -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<op_evaluator>& 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 );

View file

@ -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<account_id_type>& result )

View file

@ -0,0 +1,4 @@
// Placeholder HF for affiliate reward system
#ifndef HARDFORK_999_TIME
#define HARDFORK_999_TIME (fc::time_point_sec( 1600000000 ))
#endif

View file

@ -227,6 +227,8 @@ namespace graphene { namespace chain {
*/
optional< flat_set<asset_id_type> > 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,

View file

@ -0,0 +1,90 @@
/*
* 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 <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/protocol/affiliate.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/tournament_object.hpp>
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<typename GAME>
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() );
}
template<typename GAME>
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<typename GAME>
affiliate_payout_helper( database& db, const GAME& game )
: _db(db), tag( get_tag_for_game( game ) ), payout_asset( get_asset_for_game( game ) ) {}
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;
asset_id_type payout_asset;
std::map<account_id_type, share_type> accumulator;
};
} } // graphene::chain

View file

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

View file

@ -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<account_id_type,uint16_t> _dist;
void validate()const;
};
struct affiliate_reward_distributions
{
fc::flat_map<app_tag,affiliate_reward_distribution> _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)

View file

@ -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 <graphene/chain/protocol/account.hpp>
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/memo.hpp>
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) )

View file

@ -24,6 +24,7 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/account.hpp>
#include <graphene/chain/protocol/affiliate.hpp>
#include <graphene/chain/protocol/assert.hpp>
#include <graphene/chain/protocol/asset_ops.hpp>
#include <graphene/chain/protocol/balance.hpp>
@ -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

View file

@ -25,8 +25,11 @@
#include <graphene/chain/proposal_evaluator.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/protocol/account.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <fc/smart_ref_impl.hpp>
@ -52,8 +55,20 @@ 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 {

View file

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

View file

@ -24,6 +24,7 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/affiliate_payout.hpp>
#include <boost/msm/back/state_machine.hpp>
#include <boost/msm/front/state_machine_def.hpp>
@ -311,10 +312,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<asset_dividend_data_id_type> dividend_id = core_asset_obj.dividend_data_id;
const asset_object & asset_obj = asset_id_type(0)(event.db);
optional<asset_dividend_data_id_type> dividend_id = asset_obj.dividend_data_id;
share_type rake_amount = 0;
if (dividend_id)
@ -335,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, rake_amount );
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;

View file

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

View file

@ -81,6 +81,17 @@ class account_history_plugin : public graphene::app::plugin
std::unique_ptr<detail::account_history_plugin_impl> 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<account_id_type, set<operation_history_id_type> > _history_by_account;
};
} } //graphene::account_history
/*struct by_id;

View file

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

View file

@ -0,0 +1,136 @@
/*
* 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 <fc/optional.hpp>
#include <fc/variant_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <graphene/app/application.hpp>
#include <graphene/chain/block_database.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <graphene/affiliate_stats/affiliate_stats_api.hpp>
#include <graphene/account_history/account_history_plugin.hpp>
namespace graphene { namespace affiliate_stats {
namespace detail {
class affiliate_stats_api_impl
{
public:
affiliate_stats_api_impl(graphene::app::application& _app);
std::vector<top_referred_account> list_top_referred_accounts( asset_id_type asset, uint16_t limit )const
{
std::vector<top_referred_account> result;
result.reserve( limit );
auto& idx = app.chain_database()->get_index_type<referral_reward_index>().indices().get<by_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++ );
return result;
}
std::vector<top_app> list_top_rewards_per_app( asset_id_type asset, uint16_t limit )const
{
std::vector<top_app> result;
result.reserve( limit );
auto& idx = app.chain_database()->get_index_type<app_reward_index>().indices().get<by_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++ );
return result;
}
std::vector<referral_payment> list_historic_referral_rewards( account_id_type affiliate, operation_history_id_type start, uint16_t limit )const
{
shared_ptr<const affiliate_stats_plugin> plugin = app.get_plugin<const affiliate_stats_plugin>( "affiliate_stats" );
std::vector<referral_payment> 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;
}
graphene::app::application& app;
};
affiliate_stats_api_impl::affiliate_stats_api_impl(graphene::app::application& _app)
: app(_app) {}
} // 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<detail::affiliate_stats_api_impl>(app)) {}
std::vector<top_referred_account> 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<top_app> 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<referral_payment> affiliate_stats_api::list_historic_referral_rewards( account_id_type affiliate, operation_history_id_type start, uint16_t limit )const
{
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<affiliate_payout_operation>().tag),
payout(oho.op.get<affiliate_payout_operation>().payout) {}
} } // graphene::affiliate_stats

View file

@ -0,0 +1,215 @@
/*
* 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 <graphene/affiliate_stats/affiliate_stats_plugin.hpp>
#include <graphene/affiliate_stats/affiliate_stats_objects.hpp>
#include <graphene/app/impacted.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/config.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/chain/transaction_evaluation_state.hpp>
#include <fc/smart_ref_impl.hpp>
#include <fc/thread/thread.hpp>
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<graphene::chain::account_id_type, std::set<graphene::chain::operation_history_id_type> > _history_by_account;
private:
graphene::chain::database& db;
};
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();
}
const std::set<graphene::chain::operation_history_id_type>& get_reward_history( account_id_type& affiliate )const;
typedef void result_type;
template<typename Operation>
void operator()( const Operation& op ) {}
shared_ptr<affiliate_reward_index> _fr_index;
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<by_app_asset>();
auto itr = by_app.find( boost::make_tuple( op.tag, op.payout.asset_id ) );
if( itr == by_app.end() )
{
database().create<app_reward_object>( [&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<by_referral_asset>();
auto itr = by_referral.find( boost::make_tuple( op.player, op.payout.asset_id ) );
if( itr == by_referral.end() )
{
database().create<referral_reward_object>( [&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 )
{
vector<optional< operation_history_object > >& hist = database().get_applied_operations();
for( optional< operation_history_object >& o_op : hist )
{
if( !o_op.valid() )
continue;
o_op->op.visit( *this );
}
}
static const std::set<graphene::chain::operation_history_id_type> EMPTY;
const std::set<graphene::chain::operation_history_id_type>& 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<std::pair<account_id_type, operation_history_id_type>> get_account( const database& db, const object& obj )
{
FC_ASSERT( dynamic_cast<const account_transaction_history_object*>(&obj) );
const account_transaction_history_object& ath = static_cast<const account_transaction_history_object&>(obj);
const operation_history_object& oho = db.get<operation_history_object>( ath.operation_id );
if( oho.op.which() == operation::tag<affiliate_payout_operation>::value )
return std::make_pair( ath.account, ath.operation_id );
return optional<std::pair<account_id_type, operation_history_id_type>>();
}
void affiliate_reward_index::on_add( const object& obj )
{
optional<std::pair<account_id_type, operation_history_id_type>> 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<std::pair<account_id_type, operation_history_id_type>> 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) ) {}
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); } );
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<detail::affiliate_reward_index>( new detail::affiliate_reward_index( database() ) );
const_cast<primary_index<account_transaction_history_index>&>(database().get_index_type<primary_index<account_transaction_history_index>>()).add_observer( my->_fr_index );
}
void affiliate_stats_plugin::plugin_startup() {}
const std::set<graphene::chain::operation_history_id_type>& affiliate_stats_plugin::get_reward_history( account_id_type& affiliate )const
{
return my->get_reward_history( affiliate );
}
} } // graphene::affiliate_stats

View file

@ -0,0 +1,101 @@
/*
* 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 <fc/api.hpp>
#include <fc/variant_object.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/operation_history_object.hpp>
#include <graphene/affiliate_stats/affiliate_stats_objects.hpp>
using namespace graphene::chain;
namespace graphene { namespace app {
class application;
} }
namespace graphene { namespace affiliate_stats {
namespace detail {
class affiliate_stats_api_impl;
}
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 {
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<referral_payment> 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<top_referred_account> list_top_referred_accounts( asset_id_type asset, uint16_t limit = 100 )const;
std::vector<top_app> list_top_rewards_per_app( asset_id_type asset, uint16_t limit = 100 )const;
std::shared_ptr<detail::affiliate_stats_api_impl> my;
};
} } // graphene::affiliate_stats
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) )
FC_API(graphene::affiliate_stats::affiliate_stats_api,
(list_historic_referral_rewards)
(list_top_referred_accounts)
(list_top_rewards_per_app)
)

View file

@ -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 <graphene/chain/database.hpp>
#include <graphene/affiliate_stats/affiliate_stats_plugin.hpp>
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<app_reward_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<AFFILIATE_STATS_SPACE_ID, app_reward_object_type, app_reward_object> app_reward_id_type;
struct by_asset;
struct by_app_asset;
typedef multi_index_container<
app_reward_object,
indexed_by<
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_non_unique<tag<by_asset>,
composite_key<
app_reward_object,
const_mem_fun<app_reward_object, asset_id_type, &app_reward_object::get_asset_id>,
const_mem_fun<app_reward_object, share_type, &app_reward_object::get_amount> >,
composite_key_compare<
std::less<asset_id_type>,
std::greater<share_type> >
>,
ordered_unique<tag<by_app_asset>,
composite_key<
app_reward_object,
member<app_reward_object, app_tag, &app_reward_object::app>,
const_mem_fun<app_reward_object, asset_id_type, &app_reward_object::get_asset_id> >
> > > app_reward_multi_index_type;
typedef generic_index<app_reward_object, app_reward_multi_index_type> app_reward_index;
class referral_reward_object : public graphene::db::abstract_object<referral_reward_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<AFFILIATE_STATS_SPACE_ID, referral_reward_object_type, referral_reward_object> referral_reward_id_type;
struct by_referral_asset;
typedef multi_index_container<
referral_reward_object,
indexed_by<
ordered_unique<tag<by_id>, member<object, object_id_type, &object::id> >,
ordered_non_unique<tag<by_asset>,
composite_key<
referral_reward_object,
const_mem_fun<referral_reward_object, asset_id_type, &referral_reward_object::get_asset_id>,
const_mem_fun<referral_reward_object, share_type, &referral_reward_object::get_amount> >,
composite_key_compare<
std::less<asset_id_type>,
std::greater<share_type> >
>,
ordered_unique<tag<by_referral_asset>,
composite_key<
referral_reward_object,
member<referral_reward_object, account_id_type, &referral_reward_object::referral>,
const_mem_fun<referral_reward_object, asset_id_type, &referral_reward_object::get_asset_id> >
> > > referral_reward_multi_index_type;
typedef generic_index<referral_reward_object, referral_reward_multi_index_type> 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) )

View file

@ -0,0 +1,72 @@
/*
* 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 <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <fc/thread/future.hpp>
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;
const std::set<graphene::chain::operation_history_id_type>& get_reward_history( account_id_type& affiliate )const;
friend class detail::affiliate_stats_plugin_impl;
std::unique_ptr<detail::affiliate_stats_plugin_impl> my;
};
} } //graphene::affiliate_stats

View file

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

View file

@ -29,9 +29,10 @@
#include <graphene/market_history/market_history_plugin.hpp>
//#include <graphene/generate_genesis/generate_genesis_plugin.hpp>
//#include <graphene/generate_uia_sharedrop_genesis/generate_uia_sharedrop_genesis.hpp>
#include <graphene/affiliate_stats/affiliate_stats_plugin.hpp>
#include <graphene/bookie/bookie_plugin.hpp>
#include <graphene/utilities/git_revision.hpp>
#include <graphene/snapshot/snapshot.hpp>
//#include <graphene/snapshot/snapshot.hpp>
#include <fc/exception/exception.hpp>
#include <fc/thread/thread.hpp>
@ -84,8 +85,9 @@ int main(int argc, char** argv) {
//auto generate_genesis_plug = node->register_plugin<generate_genesis_plugin::generate_genesis_plugin>();
//auto generate_uia_sharedrop_genesis_plug = node->register_plugin<generate_uia_sharedrop_genesis::generate_uia_sharedrop_genesis_plugin>();
auto list_plug = node->register_plugin<accounts_list::accounts_list_plugin>();
auto affiliate_stats_plug = node->register_plugin<affiliate_stats::affiliate_stats_plugin>();
auto bookie_plug = node->register_plugin<bookie::bookie_plugin>();
auto snapshot_plug = node->register_plugin<snapshot_plugin::snapshot_plugin>();
// auto snapshot_plug = node->register_plugin<snapshot_plugin::snapshot_plugin>();
try
{

View file

@ -28,7 +28,7 @@
#include <boost/scoped_ptr.hpp>
#pragma GCC diagnostic pop
#include "../common/database_fixture.hpp"
#include "../common/betting_test_markets.hpp"
#include <boost/test/unit_test.hpp>
#include <fc/crypto/openssl.hpp>
@ -41,14 +41,9 @@
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
#include <graphene/bookie/bookie_api.hpp>
//#include <boost/algorithm/string/replace.hpp>
struct enable_betting_logging_config {
enable_betting_logging_config()
@ -1382,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 )

View file

@ -0,0 +1,173 @@
/*
* 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 <graphene/chain/sport_object.hpp>
#include <graphene/chain/event_object.hpp>
#include <graphene/chain/event_group_object.hpp>
#include <graphene/chain/betting_market_object.hpp>
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<sport_object_index>().indices().get<by_id>().rbegin(); \
create_event_group({{"en", "NHL"}, {"zh_Hans", "國家冰球聯盟"}, {"ja", "ナショナルホッケーリーグ"}}, ice_hockey.id); \
generate_blocks(1); \
const event_group_object& nhl = *db.get_index_type<event_group_object_index>().indices().get<by_id>().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<event_object_index>().indices().get<by_id>().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<betting_market_rules_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_rules_object_index>().indices().get<by_id>().rbegin(); \
create_sport({{"en", "Tennis"}}); \
generate_blocks(1); \
const sport_object& tennis = *db.get_index_type<sport_object_index>().indices().get<by_id>().rbegin(); \
create_event_group({{"en", "Wimbledon"}}, tennis.id); \
generate_blocks(1); \
const event_group_object& wimbledon = *db.get_index_type<event_group_object_index>().indices().get<by_id>().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<event_object_index>().indices().get<by_id>().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<event_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<event_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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);
}
};

View file

@ -28,6 +28,7 @@
#include <graphene/market_history/market_history_plugin.hpp>
#include <graphene/bookie/bookie_plugin.hpp>
#include <graphene/bookie/bookie_api.hpp>
#include <graphene/affiliate_stats/affiliate_stats_plugin.hpp>
#include <graphene/db/simple_index.hpp>
@ -83,6 +84,7 @@ database_fixture::database_fixture()
auto ahplugin = app.register_plugin<graphene::account_history::account_history_plugin>();
auto mhplugin = app.register_plugin<graphene::market_history::market_history_plugin>();
auto bookieplugin = app.register_plugin<graphene::bookie::bookie_plugin>();
auto affiliateplugin = app.register_plugin<graphene::affiliate_stats::affiliate_stats_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<asset_index>().indices().get<by_symbol>();
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<account_index>().indices().get<by_name>();
const auto itr = idx.find(name);
assert( itr != idx.end() );
FC_ASSERT( itr != idx.end() );
return *itr;
}

View file

@ -0,0 +1,433 @@
/*
* 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 <graphene/chain/game_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <fc/crypto/rand.hpp>
using namespace graphene::chain;
tournaments_helper::tournaments_helper(database_fixture& df) : df(df)
{
assets.insert(asset_id_type());
current_asset_idx = 0;
optional<account_id_type> dividend_account = get_asset_dividend_account(asset_id_type());
if (dividend_account.valid())
players.insert(*dividend_account);
}
const std::set<tournament_id_type>& tournaments_helper::list_tournaments()const
{
return tournaments;
}
const std::map<account_id_type, std::map<asset_id_type, share_type>> tournaments_helper::list_players_balances()const
{
std::map<account_id_type, std::map<asset_id_type, share_type>> 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<account_id_type, std::map<asset_id_type, share_type>> 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<account_id_type> dividend_account = get_asset_dividend_account(asset_to_update_id);
if (dividend_account.valid())
players.insert(*dividend_account);
}
optional<account_id_type> tournaments_helper::get_asset_dividend_account(const asset_id_type& asset_id)const
{
graphene::chain::database& db = df.db;
optional<account_id_type> result;
const asset_object& asset_obj = asset_id_type()(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<flat_set<account_id_type> > 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<rock_paper_scissors_game_options>();
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<rock_paper_scissors_game_options>();
FC_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<char> 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;
latest_committs[player_account_obj.id] = commit_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);
}
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
// 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<rock_paper_scissors_game_options>();
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<rock_paper_scissors_game_details>();
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;
//}
}

View file

@ -0,0 +1,123 @@
/*
* 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<tournament_id_type>& list_tournaments()const;
const std::map<account_id_type, std::map<asset_id_type, share_type>> list_players_balances()const;
const std::map<account_id_type, std::map<asset_id_type, share_type>> 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<account_id_type> 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 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<flat_set<account_id_type> > whitelist = fc::optional<flat_set<account_id_type> >()
);
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
);
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
// 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<uint64_t> current_tournament_idx;
// index of last asset
uint64_t current_asset_idx;
// assets : core and maybe others
std::set<asset_id_type> assets;
// tournaments to be played
std::set<tournament_id_type> tournaments;
// all players registered in tournaments
std::set<account_id_type> players;
// players' private keys
std::map<account_id_type, fc::ecc::private_key> players_keys;
// total charges for moves made by every player
std::map<account_id_type, std::map<asset_id_type, share_type>> players_fees;
// store of commits and reveals
std::map<rock_paper_scissors_throw_commit, rock_paper_scissors_throw_reveal> committed_game_moves;
// store of latest commits, for use by rps_reveal
std::map<account_id_type, rock_paper_scissors_throw_commit> latest_committs;
};

View file

@ -0,0 +1,732 @@
/*
* 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 <boost/test/unit_test.hpp>
#include "../common/betting_test_markets.hpp"
#include "../common/tournament_helper.hpp"
#include <graphene/chain/affiliate_payout.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/affiliate_stats/affiliate_stats_api.hpp>
#include <graphene/affiliate_stats/affiliate_stats_objects.hpp>
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<account_index>())
{
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<object_id_type>();
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<object_id_type>();
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<object_id_type>();
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<by_name>().find( name );
if( itr == accounts->indices().get<by_name>().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 )
{
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_CASE( affiliate_payout_helper_test )
{
ACTORS( (irene) );
const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id;
issue_uia( irene, asset( 100000, btc_id ) );
affiliate_test_helper ath( *this );
int64_t alice_btc = 0;
int64_t ann_btc = 0;
int64_t audrey_btc = 0;
{
const tournament_object& game = db.create<tournament_object>( []( 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( ath.alice_id, 1000 ).value );
// Paula has nothing for Bookie
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( ath.penny_id, 4 ).value );
// 20% of 5 = 1 is paid to Audrey
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( 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( ath.petra_id, 59 ).value );
ath.audrey_ppy += 10;
ath.ann_ppy += 1;
helper.commit();
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() ) );
}
{
const tournament_object& game = db.create<tournament_object>( [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( ath.penny_id, 60 ).value );
alice_btc += 2;
ann_btc += 3;
audrey_btc += 7;
helper.commit();
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 ) );
}
{
const betting_market_group_object& game = db.create<betting_market_group_object>( []( 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( ath.alice_id, 1000 ).value );
// Petra has nothing for Bookie
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( ath.penny_id, 4 ).value );
// 20% of 5 = 1 is paid to Ann
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( 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( ath.penny_id, 59 ).value );
ath.alice_ppy += 6;
ath.ann_ppy += 5;
helper.commit();
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() ) );
}
{
const betting_market_group_object& game = db.create<betting_market_group_object>( [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( ath.penny_id, 60 ).value );
alice_btc += 7;
ann_btc += 5;
helper.commit();
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 ) );
}
{
// Fix total supply
auto& index = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
auto itr = index.find( boost::make_tuple( account_id_type(), asset_id_type() ) );
BOOST_CHECK( itr != index.end() );
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 ) );
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_CASE( rps_tournament_payout_test )
{ try {
ACTORS( (martha) );
affiliate_test_helper ath( *this );
fund( martha_id(db), asset(1000000000) );
upgrade_to_lifetime_member( martha_id(db) );
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, 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_object>( tournament_id );
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() );
{ // 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(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(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, 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, 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() );
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(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, 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, 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()) );
}
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( 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( 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() }
BOOST_AUTO_TEST_CASE( bookie_payout_test )
{ try {
ACTORS( (irene) );
const asset_id_type btc_id = create_user_issued_asset( "BTC", irene, 0 ).id;
affiliate_test_helper ath( *this );
CREATE_ICE_HOCKEY_BETTING_MARKET(false, 0);
// place bets at 10:1
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(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,
{{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( 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( 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( 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( 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); \
const event_object& capitals_vs_blackhawks2 = *db.get_index_type<event_object_index>().indices().get<by_id>().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<betting_market_group_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().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<betting_market_object_index>().indices().get<by_id>().rbegin();
// place bets at 10:1
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(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,
{{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( 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( 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( ath.petra_id, btc_id ), 1000000 - 100000 + 110000 - 110000 + 0 - rake_value );
// petra wins nothing -> no payout
}
} FC_LOG_AND_RETHROW() }
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<graphene::affiliate_stats::referral_reward_index>().indices().get<graphene::affiliate_stats::by_asset>();
BOOST_CHECK_EQUAL( 2, idx.size() ); // penny 216+60 CORE, paula 600 BTC
}
{
const auto& idx = db.get_index_type<graphene::affiliate_stats::app_reward_index>().indices().get<graphene::affiliate_stats::by_asset>();
BOOST_CHECK_EQUAL( 3, idx.size() ); // rps 216 CORE, bookie 60 CORE + 600 BTC
}
graphene::affiliate_stats::affiliate_stats_api stats( app );
{
std::vector<graphene::affiliate_stats::referral_payment> 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<graphene::affiliate_stats::top_referred_account> 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<graphene::affiliate_stats::top_app> 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()

View file

@ -28,7 +28,7 @@
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include "../common/database_fixture.hpp"
#include "../common/tournament_helper.hpp"
#include <graphene/utilities/tempdir.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
@ -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<account_id_type> dividend_account = get_asset_dividend_account(asset_id_type());
if (dividend_account.valid())
players.insert(*dividend_account);
}
const std::set<tournament_id_type>& list_tournaments()
{
return tournaments;
}
std::map<account_id_type, std::map<asset_id_type, share_type>> list_players_balances()
{
std::map<account_id_type, std::map<asset_id_type, share_type>> 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<account_id_type, std::map<asset_id_type, share_type>> 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<account_id_type> dividend_account = get_asset_dividend_account(asset_to_update_id);
if (dividend_account.valid())
players.insert(*dividend_account);
}
optional<account_id_type> get_asset_dividend_account(const asset_id_type& asset_id)
{
graphene::chain::database& db = df.db;
optional<account_id_type> 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<flat_set<account_id_type> > whitelist = fc::optional<flat_set<account_id_type> >()
)
{
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<rock_paper_scissors_game_options>();
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<rock_paper_scissors_game_options>();
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<char> 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<rock_paper_scissors_game_options>();
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<rock_paper_scissors_game_details>();
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<uint64_t> current_tournament_idx;
// index of last asset
uint64_t current_asset_idx;
// assets : core and maybe others
std::set<asset_id_type> assets;
// tournaments to be played
std::set<tournament_id_type> tournaments;
// all players registered in tournaments
std::set<account_id_type> players;
// players' private keys
std::map<account_id_type, fc::ecc::private_key> players_keys;
// total charges for moves made by every player
std::map<account_id_type, std::map<asset_id_type, share_type>> players_fees;
// store of commits and reveals
std::map<rock_paper_scissors_throw_commit, rock_paper_scissors_throw_reveal> 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