peerplays_migrated/tests/tests/affiliate_tests.cpp
Nathan Hourt 4d836dacb9 Ref !3/#376: Graphene Updates
This adds the most important updates to Graphene from BitShares. Most notably,
https://github.com/bitshares/bitshares-core/issues/1506

Second most notably, it updates Peerplays' FC to be in sync with BitShares FC.

This is a squash commit of several subcommits. The subcommit messages are
reproduced below:

Replace fc::uint128 with boost::multiprecision::uint128_t

replace smart_ref with shared_ptr

Fixes/Remove Unused

Remove NTP time

Remove old macro

This macro is now in FC, so no need to define it here anymore

Replaced fc::array with std::array

Separate exception declaration and implementation

Adapted to fc promise changes

Fixes

Add back in some of Peter's fixes that got lost in the cherry pick

_hash endianness fixes

Remove all uses of fc/smart_ref

It's gone, can't use it anymore

Replace improper static_variant operator overloads with comparators

Fixes

Remove boost::signals from build system; it's header-only so it's not
listed in cmake anymore.

Also remove some unused hashing code

Impl. pack/unpack functions for extension class

Ref #1506: Isolate chain/protocol to its own library

Ref #1506: Add object_downcast_t

Allows the more concise expression `object_downcast_t<xyz>` instead of
the old `typename object_downcast<xyz>::type`

Ref #1506: Move ID types from db to protocol

The ID types, object_id and object_id_type, were defined in the db
library, and the protocol library depends on db to get these types.
Technically, the ID types are defined by the protocol and used by the
database, and not vice versa. Therefore these types should be in the
protocol library, and db should depend on protocol to get them.

This commit makes it so.

Ref #1506: Isolate chain/protocol to its own library

Remove commented-out index code

Wrap overlength line

Remove unused key types

Probably fix Docker build

Fix build after rebase

Ref #1506/#1737: Some requested changes

Ref #1506/#1737: Macro-fy ID type definitions

Define macros to fully de-boilerplate ID type definitions.

Externalities:
 - Rename transaction_object -> transaction_history_object
 - Rename impl_asset_dynamic_data_type ->
impl_asset_dynamic_data_object_type
 - Rename impl_asset_bitasset_data_type ->
impl_asset_bitasset_data_object_type

The first is to avoid a naming collision on transaction_id_type, and the
other two are to maintain consistency with the naming of the other
types.

Ref #1506/#1737: Fix clean_name()

Ref #1506/#1737: Oops

Fix .gitignore

Externalized serialization in protocol library

Fix compile sets

Delete a couple of ghost files that were in the tree but not part
of the project (I accidentally added them to CMakeLists while
merging, but they're broken and not part of the Peerplays code), and
add several files that got dropped from the build during merge.

General fixes

Fix warnings, build issues, unused code, etc.

Fix #1772 by decprecating cli_wallet -H

More fixes

Fix errors and warnings and generally coax it to build

Fix test

I'm pretty sure this didn't break from what I did... But I can't build
the original code, so I can't tell. Anyways, this one now passes...
Others still fail...

Small fix

Fix crash in auth checks

Final fixes

Last round of fixes following the rebase to Beatrice

Rename project in CMakeLists.txt

The CMakeLists.txt declared this project as BitShares and not Peerplays,
which makes it confusing in IDEs. Rename it to be clear which project is
open.

Resolve #374

Replace all object refs in macros with IDs, and fix affected tests to look
up objects by ID rather than using invalidated refs.

A full audit of all tests should be performed to eliminate any further
usage of invalidated object references.

Resolve #373: Add object notifiers

Various fixes

Fixes to various issues, primarily reflections, that cropped up
during merge conflict resolution

Fix startup bug in Bookie plugin

Bookie plugin was preventing the node from starting up because it
registered its secondary indexes to create objects in its own primary
indexes to track objects being created in other primary indexes, and did
so during its `initialize()` step, which is to say, before the database
was loaded from disk at startup. This caused the secondary indexes to
create tracker objects when the observed indexes were loading objects
from disk. This then caused a failure when these tracker indexes were
later loaded from disk, and the first object IDs collided.

This is fixed by refraining from defining secondary indexes until the
`startup()` stage rather than the `initialize()` stage. Primary indexes
are registered in `initialize()`, secondary indexes are registered in
`startup()`.

This also involved adding a new method, "add_secondary_index()", to
`object_database`, as before there was no way to do this because you
couldn't get a non-const index from a non-const database.

I have no idea how this was working before I got here...

Fix egenesis install

Fixes after updates

Rebase on updated develop branch and fix conflicts
2021-11-11 11:25:47 -05:00

732 lines
35 KiB
C++

/*
* 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( "BTCTEST", 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< primary_index< account_balance_index > >().get_secondary_index<balances_by_account_index>();
auto abo = index.get_account_balance( account_id_type(), asset_id_type() );
BOOST_CHECK( abo != nullptr );
db.modify( *abo, [&ath]( account_balance_object& bal ) {
bal.balance -= ath.alice_ppy + ath.ann_ppy + ath.audrey_ppy;
});
abo = index.get_account_balance( irene_id, btc_id );
BOOST_CHECK( abo != nullptr );
db.modify( *abo, [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( "BTCTEST", 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_id_type capitals_vs_blackhawks2_id = (*db.get_index_type<event_object_index>().indices().get<by_id>().rbegin()).id; \
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_id_type moneyline_betting_markets2_id = (*db.get_index_type<betting_market_group_object_index>().indices().get<by_id>().rbegin()).id;
create_betting_market(moneyline_betting_markets2_id, {{"en", "Washington Capitals win"}});
generate_blocks(1);
const betting_market_id_type capitals_win_market2_id = (*db.get_index_type<betting_market_object_index>().indices().get<by_id>().rbegin()).id;
create_betting_market(moneyline_betting_markets2_id, {{"en", "Chicago Blackhawks win"}});
generate_blocks(1);
const betting_market_id_type blackhawks_win_market2_id = (*db.get_index_type<betting_market_object_index>().indices().get<by_id>().rbegin()).id;
// 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( "BTCTEST" ).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()