diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index a328cf1f1..e5dbf3f03 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -108,7 +108,7 @@ add_library( graphene_chain betting_market_evaluator.cpp betting_market_object.cpp betting_market_group_object.cpp - + manager.cpp affiliate_payout.cpp ${HEADERS} diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index a08e9031e..2d117f520 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -279,6 +279,17 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio void_result account_update_evaluator::do_apply( const account_update_operation& o ) { try { database& d = db(); + + if( o.new_options.valid() ) + { + d.modify( acnt->statistics( d ), [&]( account_statistics_object& aso ) + { + if((o.new_options->votes != acnt->options.votes || + o.new_options->voting_account != acnt->options.voting_account)) + aso.last_vote_time = d.head_block_time(); + } ); + } + bool sa_before, sa_after; d.modify( *acnt, [&](account_object& a){ if( o.owner ) @@ -320,7 +331,6 @@ void_result account_update_evaluator::do_apply( const account_update_operation& sa.account = o.account; } ); } - return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 1346584c0..7105eeba3 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -57,35 +57,37 @@ void_result asset_create_evaluator::do_evaluate( const asset_create_operation& o if( d.head_block_time() > HARDFORK_385_TIME ) { - - if( d.head_block_time() <= HARDFORK_409_TIME ) - { - auto dotpos = op.symbol.find( '.' ); - if( dotpos != std::string::npos ) + if( d.head_block_time() <= HARDFORK_409_TIME ) { - auto prefix = op.symbol.substr( 0, dotpos ); - auto asset_symbol_itr = asset_indx.find( op.symbol ); - FC_ASSERT( asset_symbol_itr != asset_indx.end(), "Asset ${s} may only be created by issuer of ${p}, but ${p} has not been registered", - ("s",op.symbol)("p",prefix) ); - FC_ASSERT( asset_symbol_itr->issuer == op.issuer, "Asset ${s} may only be created by issuer of ${p}, ${i}", - ("s",op.symbol)("p",prefix)("i", op.issuer(d).name) ); + auto dotpos = op.symbol.find( '.' ); + if( dotpos != std::string::npos ) + { + auto prefix = op.symbol.substr( 0, dotpos ); + auto asset_symbol_itr = asset_indx.find( op.symbol ); + FC_ASSERT( asset_symbol_itr != asset_indx.end(), + "Asset ${s} may only be created by issuer of ${p}, but ${p} has not been registered", + ("s",op.symbol)("p",prefix) ); + FC_ASSERT( asset_symbol_itr->issuer == op.issuer, + "Asset ${s} may only be created by issuer of ${p}, ${i}", + ("s",op.symbol)("p",prefix)("i", op.issuer(d).name) ); + } } - } - else - { - auto dotpos = op.symbol.rfind( '.' ); - if( dotpos != std::string::npos ) + else { - auto prefix = op.symbol.substr( 0, dotpos ); - auto asset_symbol_itr = asset_indx.find( prefix ); - FC_ASSERT( asset_symbol_itr != asset_indx.end(), "Asset ${s} may only be created by issuer of ${p}, but ${p} has not been registered", - ("s",op.symbol)("p",prefix) ); - FC_ASSERT( asset_symbol_itr->issuer == op.issuer, "Asset ${s} may only be created by issuer of ${p}, ${i}", - ("s",op.symbol)("p",prefix)("i", op.issuer(d).name) ); + auto dotpos = op.symbol.rfind( '.' ); + if( dotpos != std::string::npos ) + { + auto prefix = op.symbol.substr( 0, dotpos ); + auto asset_symbol_itr = asset_indx.find( prefix ); + FC_ASSERT( asset_symbol_itr != asset_indx.end(), + "Asset ${s} may only be created by issuer of ${p}, but ${p} has not been registered", + ("s",op.symbol)("p",prefix) ); + FC_ASSERT( asset_symbol_itr->issuer == op.issuer, + "Asset ${s} may only be created by issuer of ${p}, ${i}", + ("s",op.symbol)("p",prefix)("i", op.issuer(d).name) ); + } } } - - } else { auto dotpos = op.symbol.find( '.' ); diff --git a/libraries/chain/betting_market_evaluator.cpp b/libraries/chain/betting_market_evaluator.cpp index e1d64e3c6..6bab2e2fe 100644 --- a/libraries/chain/betting_market_evaluator.cpp +++ b/libraries/chain/betting_market_evaluator.cpp @@ -31,6 +31,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -75,8 +76,6 @@ void_result betting_market_group_create_evaluator::do_evaluate(const betting_mar { try { database& d = db(); FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); - // the event_id in the operation can be a relative id. If it is, // resolve it and verify that it is truly an event object_id_type resolved_event_id = op.event_id; @@ -89,6 +88,15 @@ void_result betting_market_group_create_evaluator::do_evaluate(const betting_mar _event_id = resolved_event_id; FC_ASSERT(d.find_object(_event_id), "Invalid event specified"); + if( d.head_block_time() < HARDFORK_MANAGER_TIME ) { + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), _event_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + FC_ASSERT(d.find_object(op.asset_id), "Invalid asset specified"); // the rules_id in the operation can be a relative id. If it is, @@ -123,7 +131,16 @@ void_result betting_market_group_update_evaluator::do_evaluate(const betting_mar { try { database& d = db(); FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); + + if( d.head_block_time() < HARDFORK_MANAGER_TIME ) { + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.betting_market_group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + _betting_market_group = &op.betting_market_group_id(d); FC_ASSERT(op.new_description || op.new_rules_id || op.status, "nothing to change"); @@ -197,7 +214,6 @@ void_result betting_market_group_update_evaluator::do_apply(const betting_market void_result betting_market_create_evaluator::do_evaluate(const betting_market_create_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); // the betting_market_group_id in the operation can be a relative id. If it is, // resolve it and verify that it is truly an betting_market_group @@ -211,6 +227,15 @@ void_result betting_market_create_evaluator::do_evaluate(const betting_market_cr _group_id = resolved_betting_market_group_id; FC_ASSERT(db().find_object(_group_id), "Invalid betting_market_group specified"); + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), _group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -229,7 +254,16 @@ void_result betting_market_update_evaluator::do_evaluate(const betting_market_up { try { database& d = db(); FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); + + if( d.head_block_time() < HARDFORK_MANAGER_TIME ) { + FC_ASSERT(trx_state->_is_proposed_trx); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.betting_market_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + _betting_market = &op.betting_market_id(d); FC_ASSERT(op.new_group_id.valid() || op.new_description.valid() || op.new_payout_condition.valid(), "nothing to change"); @@ -245,6 +279,13 @@ void_result betting_market_update_evaluator::do_evaluate(const betting_market_up resolved_betting_market_group_id.type() == betting_market_group_id_type::type_id, "betting_market_group_id must refer to a betting_market_group_id_type"); _group_id = resolved_betting_market_group_id; + + if( db().head_block_time() >= HARDFORK_MANAGER_TIME ) + { + FC_ASSERT( is_manager( db(), _group_id, op.fee_payer() ), + "no manager permission for the new_betting_market_group_id" ); + } + FC_ASSERT(d.find_object(_group_id), "invalid betting_market_group specified"); } @@ -365,6 +406,16 @@ void_result betting_market_group_resolve_evaluator::do_evaluate(const betting_ma { try { database& d = db(); FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME); + + if( d.head_block_time() <= HARDFORK_MANAGER_TIME ) { + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( d, op.betting_market_group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + _betting_market_group = &op.betting_market_group_id(d); d.validate_betting_market_group_resolutions(*_betting_market_group, op.resolutions); return void_result(); @@ -379,6 +430,14 @@ void_result betting_market_group_resolve_evaluator::do_apply(const betting_marke void_result betting_market_group_cancel_unmatched_bets_evaluator::do_evaluate(const betting_market_group_cancel_unmatched_bets_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); + if( db().head_block_time() <= HARDFORK_MANAGER_TIME ) { + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.betting_market_group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } _betting_market_group = &op.betting_market_group_id(db()); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/db_bet.cpp b/libraries/chain/db_bet.cpp index 8c3e13575..367a6a73e 100644 --- a/libraries/chain/db_bet.cpp +++ b/libraries/chain/db_bet.cpp @@ -476,7 +476,8 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker share_type maximum_factor = std::min(maximum_taker_factor, maximum_maker_factor); share_type maker_amount_to_match = maximum_factor * maker_odds_ratio; share_type taker_amount_to_match = maximum_factor * taker_odds_ratio; - fc_idump(fc::logger::get("betting"), (maker_amount_to_match)(taker_amount_to_match)); + //TODO + //fc_idump(fc::logger::get("betting"), (maker_amount_to_match)(taker_amount_to_match)); // TODO: analyze whether maximum_maker_amount_to_match can ever be zero here assert(maker_amount_to_match != 0); @@ -554,7 +555,8 @@ int match_bet(database& db, const bet_object& taker_bet, const bet_object& maker ("taker_refund_amount", taker_refund_amount) ("maker_odds", maker_bet.backer_multiplier) ("taker_odds", taker_bet.backer_multiplier)); - fc_ddump(fc::logger::get("betting"), (taker_bet)); + // TODO + //fc_ddump(fc::logger::get("betting"), (taker_bet)); db.adjust_balance(taker_bet.bettor_id, asset(taker_refund_amount, taker_bet.amount_to_bet.asset_id)); // TODO: update global statistics @@ -635,8 +637,9 @@ bool database::place_bet(const bet_object& new_bet_object) // we continue if the maker bet was completely consumed AND the taker bet was not finished = orders_matched_flags != 2; } - if (!(orders_matched_flags & 1)) - fc_ddump(fc::logger::get("betting"), (new_bet_object)); + //TODO + //if (!(orders_matched_flags & 1)) + // fc_ddump(fc::logger::get("betting"), (new_bet_object)); // return true if the taker bet was completely consumed diff --git a/libraries/chain/event_evaluator.cpp b/libraries/chain/event_evaluator.cpp index 28e7476df..ce23cfcac 100644 --- a/libraries/chain/event_evaluator.cpp +++ b/libraries/chain/event_evaluator.cpp @@ -24,18 +24,19 @@ #include #include #include +#include #include #include #include #include #include +#include namespace graphene { namespace chain { void_result event_create_evaluator::do_evaluate(const event_create_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); //database& d = db(); // the event_group_id in the operation can be a relative id. If it is, @@ -48,7 +49,15 @@ void_result event_create_evaluator::do_evaluate(const event_create_operation& op resolved_event_group_id.type() == event_group_id_type::type_id, "event_group_id must refer to a event_group_id_type"); event_group_id = resolved_event_group_id; - //const event_group_object& event_group = event_group_id(d); + + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.new_manager && !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), event_group_id, op.fee_payer() ), + "trx is not proposed and fee_payer is not the manager of this object" ); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -62,7 +71,10 @@ object_id_type event_create_evaluator::do_apply(const event_create_operation& op event_obj.season = op.season; event_obj.start_time = op.start_time; event_obj.event_group_id = event_group_id; + if( op.extensions.value.new_manager ) + event_obj.manager = *op.extensions.value.new_manager; }); + //increment number of active events in global betting statistics object const global_betting_statistics_object& betting_statistics = global_betting_statistics_id_type()(d); d.modify( betting_statistics, [&](global_betting_statistics_object& bso) { @@ -74,10 +86,20 @@ object_id_type event_create_evaluator::do_apply(const event_create_operation& op void_result event_update_evaluator::do_evaluate(const event_update_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); - FC_ASSERT(op.new_event_group_id || op.new_name || op.new_season || - op.new_start_time || op.new_status, "nothing to change"); - + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.new_manager && !op.extensions.value.fee_paying_account ); + FC_ASSERT( op.new_event_group_id || op.new_name || op.new_season || + op.new_start_time || op.new_status, "nothing to change"); + } + else { + FC_ASSERT( is_manager( db(), op.event_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + FC_ASSERT( op.new_event_group_id || op.new_name || op.new_season || + op.new_start_time || op.new_status || op.extensions.value.new_manager, + "nothing to change" ); + } + if (op.new_event_group_id) { object_id_type resolved_event_group_id = *op.new_event_group_id; @@ -88,6 +110,12 @@ void_result event_update_evaluator::do_evaluate(const event_update_operation& op resolved_event_group_id.type() == event_group_id_type::type_id, "event_group_id must refer to a event_group_id_type"); event_group_id = resolved_event_group_id; + + if( db().head_block_time() >= HARDFORK_MANAGER_TIME ) + { + FC_ASSERT( is_manager( db(), event_group_id, *op.extensions.value.fee_paying_account ), + "no manager permission for the new_event_group_id" ); + } } return void_result(); @@ -108,16 +136,25 @@ void_result event_update_evaluator::do_apply(const event_update_operation& op) eo.event_group_id = event_group_id; if( op.new_status ) eo.dispatch_new_status(_db, *op.new_status); + if( op.extensions.value.new_manager ) + eo.manager = *op.extensions.value.new_manager; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } void_result event_update_status_evaluator::do_evaluate(const event_update_status_operation& op) { try { - FC_ASSERT(trx_state->_is_proposed_trx); - database& d = db(); FC_ASSERT(d.head_block_time() >= HARDFORK_1000_TIME); + if( d.head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.event_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + //check that the event to update exists _event_to_update = &op.event_id(d); diff --git a/libraries/chain/event_group_evaluator.cpp b/libraries/chain/event_group_evaluator.cpp index b337017b5..6a3dfba04 100644 --- a/libraries/chain/event_group_evaluator.cpp +++ b/libraries/chain/event_group_evaluator.cpp @@ -24,18 +24,19 @@ #include #include #include +#include #include #include #include #include #include +#include namespace graphene { namespace chain { void_result event_group_create_evaluator::do_evaluate(const event_group_create_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); // the sport id in the operation can be a relative id. If it is, // resolve it and verify that it is truly a sport @@ -49,6 +50,15 @@ void_result event_group_create_evaluator::do_evaluate(const event_group_create_o FC_ASSERT( db().find_object(sport_id), "Invalid sport specified" ); + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.new_manager && !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), sport_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -58,6 +68,8 @@ object_id_type event_group_create_evaluator::do_apply(const event_group_create_o db().create( [&]( event_group_object& event_group_obj ) { event_group_obj.name = op.name; event_group_obj.sport_id = sport_id; + if( op.extensions.value.new_manager ) + event_group_obj.manager = *op.extensions.value.new_manager; }); return new_event_group.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -65,9 +77,18 @@ object_id_type event_group_create_evaluator::do_apply(const event_group_create_o void_result event_group_update_evaluator::do_evaluate(const event_group_update_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); - FC_ASSERT(op.new_sport_id.valid() || op.new_name.valid(), "nothing to change"); - if( op.new_sport_id.valid() ) + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.new_manager && !op.extensions.value.fee_paying_account ); + FC_ASSERT( op.new_sport_id || op.new_name, "nothing to change" ); + } + else { + FC_ASSERT( is_manager( db(), op.event_group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + FC_ASSERT( op.new_sport_id || op.new_name || op.extensions.value.new_manager, "nothing to change" ); + } + + if( op.new_sport_id ) { object_id_type resolved_id = *op.new_sport_id; if (is_relative(*op.new_sport_id)) @@ -78,6 +99,12 @@ void_result event_group_update_evaluator::do_evaluate(const event_group_update_o sport_id = resolved_id; FC_ASSERT( db().find_object(sport_id), "invalid sport specified" ); + + if( db().head_block_time() >= HARDFORK_MANAGER_TIME ) + { + FC_ASSERT( is_manager( db(), sport_id, op.fee_payer() ), + "no manager permission for the new_sport_id" ); + } } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -93,6 +120,8 @@ void_result event_group_update_evaluator::do_apply(const event_group_update_oper ego.name = *op.new_name; if( op.new_sport_id.valid() ) ego.sport_id = sport_id; + if( op.extensions.value.new_manager ) + ego.manager = *op.extensions.value.new_manager; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -101,8 +130,15 @@ void_result event_group_update_evaluator::do_apply(const event_group_update_oper void_result event_group_delete_evaluator::do_evaluate(const event_group_delete_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1001_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); - + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.event_group_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } + //check for event group existence _event_group = &op.event_group_id(db()); diff --git a/libraries/chain/event_object.cpp b/libraries/chain/event_object.cpp index 99caf9047..6cf732797 100644 --- a/libraries/chain/event_object.cpp +++ b/libraries/chain/event_object.cpp @@ -370,6 +370,7 @@ namespace graphene { namespace chain { at_least_one_betting_market_group_settled(false), my(new impl(this)) { + manager = account_id_type(1); } event_object::event_object(const event_object& rhs) : @@ -380,6 +381,7 @@ namespace graphene { namespace chain { event_group_id(rhs.event_group_id), at_least_one_betting_market_group_settled(rhs.at_least_one_betting_market_group_settled), scores(rhs.scores), + manager(rhs.manager), my(new impl(this)) { my->state_machine = rhs.my->state_machine; @@ -396,6 +398,7 @@ namespace graphene { namespace chain { event_group_id = rhs.event_group_id; at_least_one_betting_market_group_settled = rhs.at_least_one_betting_market_group_settled; scores = rhs.scores; + manager = rhs.manager; my->state_machine = rhs.my->state_machine; my->state_machine.event_obj = this; diff --git a/libraries/chain/hardfork.d/HARDFORK_MANAGER.hf b/libraries/chain/hardfork.d/HARDFORK_MANAGER.hf new file mode 100644 index 000000000..aef9a66d3 --- /dev/null +++ b/libraries/chain/hardfork.d/HARDFORK_MANAGER.hf @@ -0,0 +1,4 @@ +// added manager +#ifndef HARDFORK_MANAGER_TIME +#define HARDFORK_MANAGER_TIME (fc::time_point_sec( 1858752402 )) +#endif diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 51ba67488..914ade33d 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -82,6 +82,11 @@ namespace graphene { namespace chain { */ share_type pending_vested_fees; + /** + * Keep the date of the last voting activity for this account. + */ + time_point_sec last_vote_time; + /// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees void process_fees(const account_object& a, database& d) const; @@ -463,6 +468,7 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (total_core_in_orders) (lifetime_fees_paid) (pending_fees)(pending_vested_fees) + (last_vote_time) ) FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_for_holder_object, diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index fcd3b7c1d..d85cc093e 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -151,7 +151,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "PPY2.0" +#define GRAPHENE_CURRENT_DB_VERSION "PPY2.1" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/event_group_object.hpp b/libraries/chain/include/graphene/chain/event_group_object.hpp index c618ba2a3..6e0ed0b12 100644 --- a/libraries/chain/include/graphene/chain/event_group_object.hpp +++ b/libraries/chain/include/graphene/chain/event_group_object.hpp @@ -41,7 +41,8 @@ class event_group_object : public graphene::db::abstract_object< event_group_obj internationalized_string_type name; sport_id_type sport_id; - + account_id_type manager = account_id_type(1); + void cancel_events(database& db) const; }; @@ -55,4 +56,4 @@ typedef multi_index_container< typedef generic_index event_group_object_index; } } // graphene::chain -FC_REFLECT_DERIVED( graphene::chain::event_group_object, (graphene::db::object), (name)(sport_id) ) +FC_REFLECT_DERIVED( graphene::chain::event_group_object, (graphene::db::object), (name)(sport_id)(manager) ) diff --git a/libraries/chain/include/graphene/chain/event_object.hpp b/libraries/chain/include/graphene/chain/event_object.hpp index 7682addcf..ac0c4c7de 100644 --- a/libraries/chain/include/graphene/chain/event_object.hpp +++ b/libraries/chain/include/graphene/chain/event_object.hpp @@ -60,6 +60,8 @@ class event_object : public graphene::db::abstract_object< event_object > event_group_id_type event_group_id; + account_id_type manager = account_id_type(1); + bool at_least_one_betting_market_group_settled; event_status get_status() const; @@ -151,5 +153,5 @@ typedef generic_index event_object_ return s; } } } // graphene::chain -FC_REFLECT(graphene::chain::event_object, (name)) +FC_REFLECT(graphene::chain::event_object, (name) (manager) ) diff --git a/libraries/chain/include/graphene/chain/manager.hpp b/libraries/chain/include/graphene/chain/manager.hpp new file mode 100644 index 000000000..22c4e456d --- /dev/null +++ b/libraries/chain/include/graphene/chain/manager.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +/** + * Determine whether the fee_paying_account for certain operations, in the context of + * sport_obj, event_group_obj, event_obj, betting_market_group_obj and betting_market_obj + * is the manager of this, or of the objects hierarchically above. + */ + +bool is_manager( const database& db, const sport_id_type& sport_id, const account_id_type& manager_id ); + +bool is_manager( const database& db, const event_group_id_type& event_group_id, const account_id_type& manager_id ); + +bool is_manager( const database& db, const event_id_type& event_id, const account_id_type& manager_id ); + +bool is_manager( const database& db, const betting_market_group_id_type& betting_market_group_id_type, + const account_id_type& manager_id ); + +bool is_manager( const database& db, const betting_market_id_type& betting_market_id_type, + const account_id_type& manager_id ); + +} } // graphene::chain \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp index 26b6f2637..5aedd8c80 100644 --- a/libraries/chain/include/graphene/chain/protocol/betting_market.hpp +++ b/libraries/chain/include/graphene/chain/protocol/betting_market.hpp @@ -25,6 +25,7 @@ #include #include +#include namespace graphene { namespace chain { @@ -100,6 +101,11 @@ enum class betting_market_group_status struct betting_market_group_create_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -140,14 +146,19 @@ struct betting_market_group_create_operation : public base_operation */ uint32_t delay_before_settling; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; struct betting_market_group_update_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -159,14 +170,19 @@ struct betting_market_group_update_operation : public base_operation optional status; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; struct betting_market_create_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -180,14 +196,19 @@ struct betting_market_create_operation : public base_operation internationalized_string_type payout_condition; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; struct betting_market_update_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -199,9 +220,9 @@ struct betting_market_update_operation : public base_operation optional new_payout_condition; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; @@ -214,6 +235,11 @@ enum class betting_market_resolution_type { struct betting_market_group_resolve_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -221,9 +247,9 @@ struct betting_market_group_resolve_operation : public base_operation std::map resolutions; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; @@ -262,14 +288,19 @@ struct betting_market_group_resolved_operation : public base_operation struct betting_market_group_cancel_unmatched_bets_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; betting_market_group_id_type betting_market_group_id; - extensions_type extensions; + extension extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; } void validate()const; }; @@ -448,24 +479,29 @@ FC_REFLECT( graphene::chain::betting_market_group_create_operation, (fee)(description)(event_id)(rules_id)(asset_id) (never_in_play)(delay_before_settling) (extensions) ) +FC_REFLECT( graphene::chain::betting_market_group_create_operation::ext, (fee_paying_account) ); FC_REFLECT( graphene::chain::betting_market_group_update_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::betting_market_group_update_operation, (fee)(betting_market_group_id)(new_description)(new_rules_id)(status)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_group_update_operation::ext, (fee_paying_account) ); FC_REFLECT( graphene::chain::betting_market_create_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::betting_market_create_operation, (fee)(group_id)(description)(payout_condition)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_create_operation::ext, (fee_paying_account) ); FC_REFLECT( graphene::chain::betting_market_update_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::betting_market_update_operation, (fee)(betting_market_id)(new_group_id)(new_description)(new_payout_condition)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_update_operation::ext, (fee_paying_account) ); FC_REFLECT_ENUM( graphene::chain::betting_market_resolution_type, (win)(not_win)(cancel)(BETTING_MARKET_RESOLUTION_COUNT) ) FC_REFLECT( graphene::chain::betting_market_group_resolve_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::betting_market_group_resolve_operation, (fee)(betting_market_group_id)(resolutions)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_group_resolve_operation::ext, (fee_paying_account) ); FC_REFLECT( graphene::chain::betting_market_group_resolved_operation::fee_parameters_type, ) FC_REFLECT( graphene::chain::betting_market_group_resolved_operation, @@ -474,6 +510,7 @@ FC_REFLECT( graphene::chain::betting_market_group_resolved_operation, FC_REFLECT( graphene::chain::betting_market_group_cancel_unmatched_bets_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::betting_market_group_cancel_unmatched_bets_operation, (fee)(betting_market_group_id)(extensions) ) +FC_REFLECT( graphene::chain::betting_market_group_cancel_unmatched_bets_operation::ext, (fee_paying_account) ); FC_REFLECT_ENUM( graphene::chain::bet_type, (back)(lay) ) FC_REFLECT( graphene::chain::bet_place_operation::fee_parameters_type, (fee) ) diff --git a/libraries/chain/include/graphene/chain/protocol/event.hpp b/libraries/chain/include/graphene/chain/protocol/event.hpp index 5934ad897..7fab385df 100644 --- a/libraries/chain/include/graphene/chain/protocol/event.hpp +++ b/libraries/chain/include/graphene/chain/protocol/event.hpp @@ -25,11 +25,18 @@ #include #include +#include namespace graphene { namespace chain { struct event_create_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport || event_group + optional< account_id_type > new_manager; // new manager to set + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -48,9 +55,11 @@ struct event_create_operation : public base_operation */ object_id_type event_group_id; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; @@ -76,6 +85,12 @@ enum class event_status struct event_update_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport || event_group || event + optional< account_id_type > new_manager; // new manager to set + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -91,9 +106,11 @@ struct event_update_operation : public base_operation optional new_status; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; @@ -107,6 +124,11 @@ struct event_update_operation : public base_operation */ struct event_update_status_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport || event_group || event + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -124,24 +146,29 @@ struct event_update_status_operation : public base_operation */ vector scores; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; } } FC_REFLECT( graphene::chain::event_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_create_operation::ext, (fee_paying_account)(new_manager) ) FC_REFLECT( graphene::chain::event_create_operation, (fee)(name)(season)(start_time)(event_group_id)(extensions) ) FC_REFLECT( graphene::chain::event_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_update_operation::ext, (fee_paying_account)(new_manager) ) FC_REFLECT( graphene::chain::event_update_operation, (fee)(event_id)(new_event_group_id)(new_name)(new_season)(new_start_time)(new_status)(extensions) ) FC_REFLECT_ENUM( graphene::chain::event_status, (upcoming)(in_progress)(frozen)(finished)(canceled)(settled)(STATUS_COUNT) ) FC_REFLECT( graphene::chain::event_update_status_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_update_status_operation::ext, (fee_paying_account) ) FC_REFLECT( graphene::chain::event_update_status_operation, (fee)(event_id)(status)(scores)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/event_group.hpp b/libraries/chain/include/graphene/chain/protocol/event_group.hpp index ad88ed351..026ab87b6 100644 --- a/libraries/chain/include/graphene/chain/protocol/event_group.hpp +++ b/libraries/chain/include/graphene/chain/protocol/event_group.hpp @@ -25,11 +25,18 @@ #include #include +#include namespace graphene { namespace chain { struct event_group_create_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport + optional< account_id_type > new_manager; // the new_manager for this object + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -44,14 +51,22 @@ struct event_group_create_operation : public base_operation */ object_id_type sport_id; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; struct event_group_update_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport || event_group + optional< account_id_type > new_manager; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -65,35 +80,47 @@ struct event_group_update_operation : public base_operation optional new_name; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; struct event_group_delete_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport || event_group + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; event_group_id_type event_group_id; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; } } FC_REFLECT( graphene::chain::event_group_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_group_create_operation::ext, (new_manager) (fee_paying_account) ) FC_REFLECT( graphene::chain::event_group_create_operation, (fee)(name)(sport_id)(extensions) ) FC_REFLECT( graphene::chain::event_group_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_group_update_operation::ext, (new_manager) (fee_paying_account) ) FC_REFLECT( graphene::chain::event_group_update_operation, (fee)(new_sport_id)(new_name)(event_group_id)(extensions) ) FC_REFLECT( graphene::chain::event_group_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::event_group_delete_operation::ext, (fee_paying_account) ) FC_REFLECT( graphene::chain::event_group_delete_operation, (fee)(event_group_id)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/protocol/sport.hpp b/libraries/chain/include/graphene/chain/protocol/sport.hpp index a33e70e91..20db1a720 100644 --- a/libraries/chain/include/graphene/chain/protocol/sport.hpp +++ b/libraries/chain/include/graphene/chain/protocol/sport.hpp @@ -25,11 +25,17 @@ #include #include +#include namespace graphene { namespace chain { struct sport_create_operation : public base_operation { + struct ext + { + optional< account_id_type > manager; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -38,7 +44,7 @@ struct sport_create_operation : public base_operation */ internationalized_string_type name; - extensions_type extensions; + extension< ext > extensions; account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } void validate()const; @@ -46,6 +52,12 @@ struct sport_create_operation : public base_operation struct sport_update_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport + optional< account_id_type > new_manager; + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; @@ -53,35 +65,47 @@ struct sport_update_operation : public base_operation optional new_name; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; struct sport_delete_operation : public base_operation { + struct ext + { + optional< account_id_type > fee_paying_account; // manager of sport + }; + struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; }; asset fee; sport_id_type sport_id; - extensions_type extensions; + extension< ext > extensions; - account_id_type fee_payer()const { return GRAPHENE_WITNESS_ACCOUNT; } + account_id_type fee_payer()const { + return extensions.value.fee_paying_account ? *extensions.value.fee_paying_account : GRAPHENE_WITNESS_ACCOUNT; + } void validate()const; }; } } FC_REFLECT( graphene::chain::sport_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sport_create_operation::ext, (manager) ) FC_REFLECT( graphene::chain::sport_create_operation, (fee)(name)(extensions) ) FC_REFLECT( graphene::chain::sport_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sport_update_operation::ext, (new_manager) (fee_paying_account) ) FC_REFLECT( graphene::chain::sport_update_operation, (fee)(sport_id)(new_name)(extensions) ) FC_REFLECT( graphene::chain::sport_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sport_delete_operation::ext, (fee_paying_account) ) FC_REFLECT( graphene::chain::sport_delete_operation, (fee)(sport_id)(extensions) ) diff --git a/libraries/chain/include/graphene/chain/sport_object.hpp b/libraries/chain/include/graphene/chain/sport_object.hpp index 4f3139d8b..63c4f6841 100644 --- a/libraries/chain/include/graphene/chain/sport_object.hpp +++ b/libraries/chain/include/graphene/chain/sport_object.hpp @@ -38,6 +38,10 @@ class sport_object : public graphene::db::abstract_object< sport_object > static const uint8_t type_id = sport_object_type; internationalized_string_type name; + + /// manager account can modify the sportobject without the permission + /// of the witness_account, also he can modify all objects beneath (event_group etc.) + account_id_type manager = account_id_type(1); }; typedef multi_index_container< @@ -48,4 +52,4 @@ typedef multi_index_container< typedef generic_index sport_object_index; } } // graphene::chain -FC_REFLECT_DERIVED( graphene::chain::sport_object, (graphene::db::object), (name) ) +FC_REFLECT_DERIVED( graphene::chain::sport_object, (graphene::db::object), (name) (manager) ) diff --git a/libraries/chain/manager.cpp b/libraries/chain/manager.cpp new file mode 100644 index 000000000..939321e9e --- /dev/null +++ b/libraries/chain/manager.cpp @@ -0,0 +1,44 @@ +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +bool is_manager( const database& db, const sport_id_type& sport_id, const account_id_type& manager_id ) +{ + return sport_id(db).manager == manager_id || GRAPHENE_WITNESS_ACCOUNT == manager_id; +} + +bool is_manager( const database& db, const event_group_id_type& event_group_id, const account_id_type& manager_id ) +{ + const event_group_object& event_group_obj = event_group_id(db); + if( event_group_obj.manager == manager_id || GRAPHENE_WITNESS_ACCOUNT == manager_id ) + return true; + + return is_manager( db, event_group_obj.sport_id, manager_id ); +} + +bool is_manager( const database& db, const event_id_type& event_id, const account_id_type& manager_id ) +{ + const event_object& event_obj = event_id(db); + if( event_obj.manager == manager_id || GRAPHENE_WITNESS_ACCOUNT == manager_id ) + return true; + + return is_manager( db, event_obj.event_group_id, manager_id ); +} + +bool is_manager( const database& db, const betting_market_group_id_type& betting_market_group_id, + const account_id_type& manager_id ) +{ + return GRAPHENE_WITNESS_ACCOUNT == manager_id || is_manager( db, betting_market_group_id(db).event_id, manager_id ); +} + +bool is_manager( const database& db, const betting_market_id_type& betting_market_id, + const account_id_type& manager_id ) +{ + return GRAPHENE_WITNESS_ACCOUNT == manager_id || is_manager( db, betting_market_id(db).group_id, manager_id ); +} + +} } // graphene::chain \ No newline at end of file diff --git a/libraries/chain/sport_evaluator.cpp b/libraries/chain/sport_evaluator.cpp index 649943069..ba7512543 100644 --- a/libraries/chain/sport_evaluator.cpp +++ b/libraries/chain/sport_evaluator.cpp @@ -29,13 +29,16 @@ #include #include #include +#include namespace graphene { namespace chain { void_result sport_create_evaluator::do_evaluate(const sport_create_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); + FC_ASSERT( trx_state->_is_proposed_trx ); + if( db().head_block_time() <= HARDFORK_MANAGER_TIME ) + FC_ASSERT( !op.extensions.value.manager ); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -45,6 +48,8 @@ object_id_type sport_create_evaluator::do_apply(const sport_create_operation& op const sport_object& new_sport = db().create( [&]( sport_object& sport_obj ) { sport_obj.name = op.name; + if( op.extensions.value.manager ) + sport_obj.manager = *op.extensions.value.manager; }); return new_sport.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -53,8 +58,18 @@ object_id_type sport_create_evaluator::do_apply(const sport_create_operation& op void_result sport_update_evaluator::do_evaluate(const sport_update_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1000_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); - FC_ASSERT(op.new_name.valid()); + + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( op.new_name, "nothing to change"); + FC_ASSERT( !op.extensions.value.new_manager && !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.sport_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + FC_ASSERT( op.new_name || op.extensions.value.new_manager, "nothing to change" ); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -67,6 +82,8 @@ void_result sport_update_evaluator::do_apply(const sport_update_operation& op) { if( op.new_name.valid() ) spo.name = *op.new_name; + if( op.extensions.value.new_manager ) + spo.manager = *op.extensions.value.new_manager; }); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -75,7 +92,14 @@ void_result sport_update_evaluator::do_apply(const sport_update_operation& op) void_result sport_delete_evaluator::do_evaluate( const sport_delete_operation& op ) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_1001_TIME); - FC_ASSERT(trx_state->_is_proposed_trx); + if( db().head_block_time() < HARDFORK_MANAGER_TIME ) { // remove after HARDFORK_MANAGER_TIME + FC_ASSERT( trx_state->_is_proposed_trx ); + FC_ASSERT( !op.extensions.value.fee_paying_account ); + } + else { + FC_ASSERT( is_manager( db(), op.sport_id, op.fee_payer() ), + "fee_payer is not the manager of this object" ); + } //check for sport existence _sport = &op.sport_id(db()); diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 3da01662a..7f92dc543 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -170,8 +170,10 @@ string database_fixture::generate_anon_acct_name() void database_fixture::verify_asset_supplies( const database& db ) { //wlog("*** Begin asset supply verification ***"); - const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db); - BOOST_CHECK(core_asset_data.fee_pool == 0); + + // It seems peerplays by default DO have core fee pool in genesis so commenting this out + //const asset_dynamic_data_object& core_asset_data = db.get_core_asset().dynamic_asset_data_id(db); + //BOOST_CHECK(core_asset_data.fee_pool == 0); const simple_index& statistics_index = db.get_index_type>(); const auto& balance_index = db.get_index_type().indices(); diff --git a/tests/tests/dividend_tests.cpp b/tests/tests/dividend_tests.cpp new file mode 100644 index 000000000..a3869b36e --- /dev/null +++ b/tests/tests/dividend_tests.cpp @@ -0,0 +1,659 @@ +/* + * Copyright (c) 2018 oxarbitrage and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( create_dividend_uia ) +{ + using namespace graphene; + try { + BOOST_TEST_MESSAGE("Creating dividend holder asset"); + { + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + creator.symbol = "DIVIDEND"; + creator.common_options.max_supply = 100000000; + creator.precision = 2; + creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ + creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.flags = charge_market_fee; + creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); + trx.operations.push_back(std::move(creator)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + + BOOST_TEST_MESSAGE("Creating test accounts"); + create_account("alice"); + create_account("bob"); + create_account("carol"); + create_account("dave"); + create_account("frank"); + + BOOST_TEST_MESSAGE("Creating test asset"); + { + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + creator.symbol = "TESTB"; //cant use TEST + creator.common_options.max_supply = 100000000; + creator.precision = 2; + creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ + creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.flags = charge_market_fee; + creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); + trx.operations.push_back(std::move(creator)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Funding asset fee pool"); + { + asset_fund_fee_pool_operation fund_op; + fund_op.from_account = account_id_type(); + fund_op.asset_id = get_asset("TESTB").id; + fund_op.amount = 500000000; + trx.operations.push_back(std::move(fund_op)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + + // our DIVIDEND asset should not yet be a divdend asset + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); + + BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24 * 3; + + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Verifying the dividend holder asset options"); + BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 3); + } + + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); + + // db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + // { + // _gpo.parameters.current_fees->get().distribution_base_fee = 100; + // _gpo.parameters.current_fees->get().distribution_fee_per_holder = 100; + // } ); + + + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( test_update_dividend_interval ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + }; + + BOOST_TEST_MESSAGE("Updating the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); + op.new_options.payout_interval = 60 * 60 * 24; // 1 days + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options"); + { + BOOST_REQUIRE(dividend_data.options.payout_interval); + BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); + } + + BOOST_TEST_MESSAGE("Removing the payout interval"); + { + asset_update_dividend_operation op; + op.issuer = dividend_holder_asset_object.issuer; + op.asset_to_update = dividend_holder_asset_object.id; + op.new_options.next_payout_time = dividend_data.options.next_payout_time; + op.new_options.payout_interval = fc::optional(); + trx.operations.push_back(op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + BOOST_CHECK(!dividend_data.options.payout_interval); + advance_to_next_payout_time(); + BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been"); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TESTB"); + + auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) + { + asset_issue_operation op; + op.issuer = asset_to_issue.issuer; + op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); + op.issue_to_account = destination_account.id; + trx.operations.push_back( op ); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + + auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { + int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, + holder_account_obj.id, + payout_asset_obj.id); + BOOST_CHECK_EQUAL(pending_balance, expected_balance); + }; + + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + }; + + // the first test will be testing pending balances, so we need to hit a + // maintenance interval that isn't the payout interval. Payout is + // every 3 days, maintenance interval is every 1 day. + advance_to_next_payout_time(); + + // Set up the first test, issue alice, bob, and carol each 100 DIVIDEND. + // Then deposit 300 TEST in the distribution account, and see that they + // each are credited 100 TEST. + issue_asset_to_account(dividend_holder_asset_object, alice, 100000); + issue_asset_to_account(dividend_holder_asset_object, bob, 100000); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); + + BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); + + generate_block(); + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + verify_pending_balance(alice, test_asset_object, 10000); + verify_pending_balance(bob, test_asset_object, 10000); + verify_pending_balance(carol, test_asset_object, 10000); + + // For the second test, issue carol more than the other two, so it's + // alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND + // Then deposit 400 TEST in the distribution account, and see that alice + // and bob are credited with 100 TEST, and carol gets 200 TEST + BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset"); + issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision + issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + verify_pending_balance(alice, test_asset_object, 20000); + verify_pending_balance(bob, test_asset_object, 20000); + verify_pending_balance(carol, test_asset_object, 30000); + + fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; + advance_to_next_payout_time(); + + + BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time, + "New payout was scheduled for the same time as the last payout"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, + "New payout was not scheduled for the expected time"); + + auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) + { + BOOST_TEST_MESSAGE("Verifying the virtual op was created"); + const account_transaction_history_index& hist_idx = db.get_index_type(); + auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); + BOOST_REQUIRE(account_history_range.first != account_history_range.second); + const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); + const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); + BOOST_CHECK(distribution_operation.account_id == destination_account.id); + BOOST_CHECK(std::find(distribution_operation.amounts.begin(), distribution_operation.amounts.end(), expected_payout) + != distribution_operation.amounts.end()); + }; + + BOOST_TEST_MESSAGE("Verifying the payouts"); + BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); + verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id)); + verify_pending_balance(alice, test_asset_object, 0); + + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); + verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id)); + verify_pending_balance(bob, test_asset_object, 0); + + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000); + verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id)); + verify_pending_balance(carol, test_asset_object, 0); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution_to_core_asset ) +{ + using namespace graphene; + try { + BOOST_TEST_MESSAGE("Creating test accounts"); + create_account("alice"); + create_account("bob"); + create_account("carol"); + create_account("dave"); + create_account("frank"); + + BOOST_TEST_MESSAGE("Creating test asset"); + { + asset_create_operation creator; + creator.issuer = account_id_type(); + creator.fee = asset(); + creator.symbol = "TESTB"; + creator.common_options.max_supply = 100000000; + creator.precision = 2; + creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ + creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + creator.common_options.flags = charge_market_fee; + creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); + trx.operations.push_back(std::move(creator)); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + } + generate_block(); + + const auto& dividend_holder_asset_object = asset_id_type(0)(db); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TESTB"); + + auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) + { + asset_issue_operation op; + op.issuer = asset_to_issue.issuer; + op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); + op.issue_to_account = destination_account.id; + trx.operations.push_back( op ); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + + auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { + int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, + holder_account_obj.id, + payout_asset_obj.id); + BOOST_CHECK_EQUAL(pending_balance, expected_balance); + }; + + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + idump((next_payout_scheduled_time)); + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + idump((db.head_block_time())); + }; + + // the first test will be testing pending balances, so we need to hit a + // maintenance interval that isn't the payout interval. Payout is + // every 3 days, maintenance interval is every 1 day. + advance_to_next_payout_time(); + + // Set up the first test, issue alice, bob, and carol, and dave each 1/4 of the total + // supply of the core asset. + // Then deposit 400 TEST in the distribution account, and see that they + // each are credited 100 TEST. + transfer( committee_account(db), alice, asset( 250000000000000 ) ); + transfer( committee_account(db), bob, asset( 250000000000000 ) ); + transfer( committee_account(db), carol, asset( 250000000000000 ) ); + transfer( committee_account(db), dave, asset( 250000000000000 ) ); + + BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); + + generate_block(); + + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + + verify_pending_balance(alice, test_asset_object, 10000); + verify_pending_balance(bob, test_asset_object, 10000); + verify_pending_balance(carol, test_asset_object, 10000); + verify_pending_balance(dave, test_asset_object, 10000); + + // For the second test, issue dave more than the other two, so it's + // alice: 1/5 CORE, bob: 1/5 CORE, carol: 1/5 CORE, dave: 2/5 CORE + // Then deposit 500 TEST in the distribution account, and see that alice + // bob, and carol are credited with 100 TEST, and dave gets 200 TEST + BOOST_TEST_MESSAGE("Issuing dave twice as much of the holder asset"); + transfer( alice, dave, asset( 50000000000000 ) ); + transfer( bob, dave, asset( 50000000000000 ) ); + transfer( carol, dave, asset( 50000000000000 ) ); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 50000); // 500 at two digits of precision + BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + verify_pending_balance(alice, test_asset_object, 20000); + verify_pending_balance(bob, test_asset_object, 20000); + verify_pending_balance(carol, test_asset_object, 20000); + verify_pending_balance(dave, test_asset_object, 30000); + + fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; + advance_to_next_payout_time(); + + + BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time, + "New payout was scheduled for the same time as the last payout"); + BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, + "New payout was not scheduled for the expected time"); + + auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) + { + BOOST_TEST_MESSAGE("Verifying the virtual op was created"); + const account_transaction_history_index& hist_idx = db.get_index_type(); + auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); + BOOST_REQUIRE(account_history_range.first != account_history_range.second); + const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); + const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); + BOOST_CHECK(distribution_operation.account_id == destination_account.id); + BOOST_CHECK(std::find(distribution_operation.amounts.begin(), distribution_operation.amounts.end(), expected_payout) + != distribution_operation.amounts.end()); + }; + + BOOST_TEST_MESSAGE("Verifying the payouts"); + BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); + verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id)); + verify_pending_balance(alice, test_asset_object, 0); + + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); + verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id)); + verify_pending_balance(bob, test_asset_object, 0); + + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 20000); + verify_dividend_payout_operations(carol, asset(20000, test_asset_object.id)); + verify_pending_balance(carol, test_asset_object, 0); + + BOOST_CHECK_EQUAL(get_balance(dave, test_asset_object), 30000); + verify_dividend_payout_operations(dave, asset(30000, test_asset_object.id)); + verify_pending_balance(dave, test_asset_object, 0); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + +BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TESTB"); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} + + +BOOST_AUTO_TEST_CASE( check_dividend_corner_cases ) +{ + using namespace graphene; + try { + INVOKE( create_dividend_uia ); + + const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); + const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); + const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); + const account_object& alice = get_account("alice"); + const account_object& bob = get_account("bob"); + const account_object& carol = get_account("carol"); + const account_object& dave = get_account("dave"); + const account_object& frank = get_account("frank"); + const auto& test_asset_object = get_asset("TESTB"); + + auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) + { + asset_issue_operation op; + op.issuer = asset_to_issue.issuer; + op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); + op.issue_to_account = destination_account.id; + trx.operations.push_back( op ); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + + auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { + int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, + holder_account_obj.id, + payout_asset_obj.id); + BOOST_CHECK_EQUAL(pending_balance, expected_balance); + }; + + auto reserve_asset_from_account = [&](const asset_object& asset_to_reserve, const account_object& from_account, int64_t amount_to_reserve) + { + asset_reserve_operation reserve_op; + reserve_op.payer = from_account.id; + reserve_op.amount_to_reserve = asset(amount_to_reserve, asset_to_reserve.id); + trx.operations.push_back(reserve_op); + set_expiration(db, trx); + PUSH_TX( db, trx, ~0 ); + trx.operations.clear(); + }; + auto advance_to_next_payout_time = [&]() { + // Advance to the next upcoming payout time + BOOST_REQUIRE(dividend_data.options.next_payout_time); + fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; + // generate blocks up to the next scheduled time + generate_blocks(next_payout_scheduled_time); + // if the scheduled time fell on a maintenance interval, then we should have paid out. + // if not, we need to advance to the next maintenance interval to trigger the payout + if (dividend_data.options.next_payout_time) + { + // we know there was a next_payout_time set when we entered this, so if + // it has been cleared, we must have already processed payouts, no need to + // further advance time. + BOOST_REQUIRE(dividend_data.options.next_payout_time); + if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + } + }; + + // the first test will be testing pending balances, so we need to hit a + // maintenance interval that isn't the payout interval. Payout is + // every 3 days, maintenance interval is every 1 day. + advance_to_next_payout_time(); + + BOOST_TEST_MESSAGE("Testing a payout interval when there are no users holding the dividend asset"); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); + issue_asset_to_account(test_asset_object, dividend_distribution_account, 1000); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled"); + verify_pending_balance(alice, test_asset_object, 0); + verify_pending_balance(bob, test_asset_object, 0); + verify_pending_balance(carol, test_asset_object, 0); + advance_to_next_payout_time(); + BOOST_TEST_MESSAGE("Verify that no actual payments took place"); + verify_pending_balance(alice, test_asset_object, 0); + verify_pending_balance(bob, test_asset_object, 0); + verify_pending_balance(carol, test_asset_object, 0); + BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0); + BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, test_asset_object), 1000); + + BOOST_TEST_MESSAGE("Now give alice a small balance and see that she takes it all"); + issue_asset_to_account(dividend_holder_asset_object, alice, 1); + generate_block(); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount"); + verify_pending_balance(alice, test_asset_object, 1000); + + // Test that we can pay out the dividend asset itself + issue_asset_to_account(dividend_holder_asset_object, bob, 1); + issue_asset_to_account(dividend_holder_asset_object, carol, 1); + issue_asset_to_account(dividend_holder_asset_object, dividend_distribution_account, 300); + generate_block(); + BOOST_CHECK_EQUAL(get_balance(alice, dividend_holder_asset_object), 1); + BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 1); + BOOST_CHECK_EQUAL(get_balance(carol, dividend_holder_asset_object), 1); + BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + generate_block(); // get the maintenance skip slots out of the way + BOOST_TEST_MESSAGE("Verify that the dividend asset was shared out"); + verify_pending_balance(alice, dividend_holder_asset_object, 100); + verify_pending_balance(bob, dividend_holder_asset_object, 100); + verify_pending_balance(carol, dividend_holder_asset_object, 100); + } catch(fc::exception& e) { + edump((e.to_detail_string())); + throw; + } +} +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/manager_tests.cpp b/tests/tests/manager_tests.cpp new file mode 100644 index 000000000..a7f6224f2 --- /dev/null +++ b/tests/tests/manager_tests.cpp @@ -0,0 +1,1047 @@ +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + +struct manager_tests_fixture : database_fixture +{ + internationalized_string_type default_sport_name = {{"TS", "TEST_SPORT"}}; + internationalized_string_type default_event_group_name = {{"EG", "TEST_GROUP"}}; + internationalized_string_type default_event_name = {{"EN", "EVENT_NAME"}}; + event_status default_event_status = event_status::upcoming; + internationalized_string_type default_betting_market_group_description = {{"BMG", "BET_GROUP"}}; + internationalized_string_type default_betting_market_description = {{"BM", "BET_MARKET"}}; + + + internationalized_string_type new_sport_name = {{"NS", "NEW_SPORT"}}; + internationalized_string_type new_event_group_name = {{"NG", "NEW_GROUP"}}; + internationalized_string_type new_event_name = {{"NE", "NEW_EVENT"}}; + event_status new_event_status = event_status::frozen; + internationalized_string_type new_betting_market_group_description = {{"ND", "NEW_DESC"}}; + internationalized_string_type new_betting_market_description = {{"NM", "NEW_MARKET"}}; + + + processed_transaction process_operation_by_fee_payer( operation op ) + { + signed_transaction trx; + set_expiration( db, trx ); + trx.operations.push_back( op ); + return PUSH_TX( db, trx, ~0 ); + } + + sport_id_type create_sport_by_witness( optional new_manager = optional() ) + { + auto scop = create_sport_create_op( new_manager ); + process_operation_by_witnesses( scop ); + + const sport_object& sport_obj = *db.get_index_type().indices().get().begin(); + return sport_obj.id; + } + + void update_sport_by_witness( sport_id_type sport_id, + optional new_manager = optional() ) + { + auto suop = create_sport_update_op( sport_id, new_manager ); + process_operation_by_witnesses( suop ); + } + + void update_sport_by_fee_payer( sport_id_type sport_id, + optional new_manager, + optional fee_paying_account ) + { + auto suop = create_sport_update_op( sport_id, new_manager, fee_paying_account ); + process_operation_by_fee_payer( suop ); + } + + void delete_sport_by_witness( sport_id_type sport_id ) + { + auto sdop = create_sport_delete_op( sport_id ); + process_operation_by_witnesses( sdop ); + } + + void delete_sport_by_fee_payer( sport_id_type sport_id, + optional fee_paying_account ) + { + auto sdop = create_sport_delete_op( sport_id, fee_paying_account ); + process_operation_by_fee_payer( sdop ); + } + + event_group_id_type create_event_group_by_witness( sport_id_type sport_id, + optional new_manager = optional() ) + { + auto egcop = create_event_group_create_op( sport_id, new_manager ); + process_operation_by_witnesses( egcop ); + + const auto& eg_obj = *db.get_index_type().indices().get().begin(); + return eg_obj.id; + } + + event_group_id_type create_event_group_by_fee_payer( sport_id_type sport_id, + optional new_manager, + optional fee_paying_account ) + { + auto egcop = create_event_group_create_op( sport_id, new_manager, fee_paying_account ); + process_operation_by_fee_payer( egcop ); + + const auto& eg_obj = *db.get_index_type().indices().get().begin(); + return eg_obj.id; + } + + void update_event_group_by_witness( event_group_id_type event_group_id, + optional new_manager = optional() ) + { + auto eguop = create_event_group_update_op( event_group_id, new_manager ); + process_operation_by_witnesses( eguop ); + } + + void update_event_group_by_fee_payer( event_group_id_type event_group_id, + optional new_manager, + optional fee_paying_account ) + { + auto eguop = create_event_group_update_op( event_group_id, new_manager, fee_paying_account ); + process_operation_by_fee_payer( eguop ); + } + + void delete_event_group_by_witness( event_group_id_type event_group_id ) + { + auto egdop = create_event_group_delete_op( event_group_id ); + process_operation_by_witnesses( egdop ); + } + + void delete_event_group_by_fee_payer( event_group_id_type event_group_id, + optional fee_paying_account ) + { + auto egdop = create_event_group_delete_op( event_group_id, fee_paying_account ); + process_operation_by_fee_payer( egdop ); + } + + event_id_type create_event_by_witness( event_group_id_type event_group_id, + optional new_manager = optional() ) + { + auto ecop = create_event_create_op( event_group_id, new_manager ); + process_operation_by_witnesses( ecop ); + + const auto& e_obj = *db.get_index_type().indices().get().begin(); + return e_obj.id; + } + + event_id_type create_event_by_fee_payer( event_group_id_type event_group_id, + optional new_manager, + optional fee_paying_account ) + { + auto ecop = create_event_create_op( event_group_id, new_manager, fee_paying_account ); + process_operation_by_fee_payer( ecop ); + + const auto& e_obj = *db.get_index_type().indices().get().begin(); + return e_obj.id; + } + + void update_event_by_witness( event_id_type event_id, + optional new_manager = optional() ) + { + auto euop = create_event_update_op( event_id, new_manager ); + process_operation_by_witnesses( euop ); + } + + void update_event_by_fee_payer( event_id_type event_id, + optional new_manager, + optional fee_paying_account ) + { + auto euop = create_event_update_op( event_id, new_manager, fee_paying_account ); + process_operation_by_fee_payer( euop ); + } + + void update_event_status_by_witness( event_id_type event_id ) + { + auto eusop = create_event_update_status_op( event_id ); + process_operation_by_witnesses( eusop ); + } + + void update_event_status_by_fee_payer( event_id_type event_id, + optional fee_paying_account ) + { + auto eusop = create_event_update_status_op( event_id, fee_paying_account ); + process_operation_by_fee_payer( eusop ); + } + + betting_market_rules_id_type create_betting_market_rules_by_witness() + { + auto bmrcop = create_betting_market_rules_create_op(); + process_operation_by_witnesses( bmrcop ); + + auto& betting_market_rules_obj = *db.get_index_type().indices().get().begin(); + return betting_market_rules_obj.id; + } + + betting_market_group_id_type create_betting_market_group_by_witness( event_id_type event_id, + betting_market_rules_id_type rules_id ) + { + auto bmgcop = create_betting_market_group_create_op( event_id, rules_id ); + process_operation_by_witnesses( bmgcop ); + + auto& betting_market_group_obj = *db.get_index_type().indices().get().begin(); + return betting_market_group_obj.id; + } + + betting_market_group_id_type create_betting_market_group_by_fee_payer( event_id_type event_id, + betting_market_rules_id_type rules_id, + optional fee_paying_account ) + { + auto bmgcop = create_betting_market_group_create_op( event_id, rules_id, fee_paying_account ); + process_operation_by_fee_payer( bmgcop ); + + auto& betting_market_group_obj = *db.get_index_type().indices().get().begin(); + return betting_market_group_obj.id; + } + + void update_betting_market_group_by_witness( betting_market_group_id_type betting_market_group_id ) + { + auto bmguop = create_betting_market_group_update_op( betting_market_group_id ); + process_operation_by_witnesses( bmguop ); + } + + void update_betting_market_group_by_fee_payer( betting_market_group_id_type betting_market_id, + optional fee_paying_account ) + { + auto bmguop = create_betting_market_group_update_op( betting_market_id, fee_paying_account ); + process_operation_by_fee_payer( bmguop ); + } + + void resolve_betting_market_group_by_witness( betting_market_group_id_type betting_market_group_id, + betting_market_id_type betting_market_id ) + { + auto bmgrop = create_betting_market_group_resolve_op( betting_market_group_id, betting_market_id ); + process_operation_by_witnesses( bmgrop ); + } + + void resolve_betting_market_group_by_fee_payer( betting_market_group_id_type betting_market_group_id, + betting_market_id_type betting_market_id, + optional fee_paying_account ) + { + auto bmgrop = create_betting_market_group_resolve_op( betting_market_group_id, betting_market_id, fee_paying_account ); + process_operation_by_fee_payer( bmgrop ); + } + + void cancel_betting_market_group_unmatched_bets_by_witness( betting_market_group_id_type betting_market_group_id ) + { + auto bmgcubop = create_bettting_market_group_cancel_unmatched_bets_op( betting_market_group_id ); + process_operation_by_witnesses( bmgcubop ); + } + + void cancel_betting_market_group_unmatched_bets_by_fee_payer( betting_market_group_id_type betting_market_group_id, + optional fee_paying_account) + { + auto bmgcubop = create_bettting_market_group_cancel_unmatched_bets_op( betting_market_group_id, fee_paying_account ); + process_operation_by_fee_payer( bmgcubop ); + } + + betting_market_id_type create_betting_market_by_witness( betting_market_group_id_type betting_market_group_id ) + { + auto bmcop = create_betting_market_create_op( betting_market_group_id ); + process_operation_by_witnesses( bmcop ); + + auto& betting_market_obj = *db.get_index_type().indices().get().begin(); + return betting_market_obj.id; + } + + betting_market_id_type create_betting_market_by_fee_payer( betting_market_group_id_type betting_market_group_id, + optional fee_paying_account ) + { + auto bmcop = create_betting_market_create_op( betting_market_group_id, fee_paying_account ); + process_operation_by_fee_payer( bmcop ); + + auto& betting_market_obj = *db.get_index_type().indices().get().begin(); + return betting_market_obj.id; + } + + void update_betting_market_by_witness( betting_market_id_type betting_market_id ) + { + auto bmuop = create_betting_market_update_op( betting_market_id ); + process_operation_by_witnesses( bmuop ); + } + + void update_betting_market_by_fee_payer( betting_market_id_type betting_market_id, + optional fee_paying_account ) + { + auto bmuop = create_betting_market_update_op( betting_market_id, fee_paying_account ); + process_operation_by_fee_payer( bmuop ); + } + + sport_create_operation create_sport_create_op( optional new_manager ) + { + sport_create_operation scop; + scop.name = default_sport_name; + if( new_manager ) + scop.extensions.value.manager = new_manager; + + return scop; + } + + sport_update_operation create_sport_update_op( sport_id_type sport_id, + optional new_manager = optional(), + optional fee_paying_account = optional() ) + { + sport_update_operation suop; + suop.sport_id = sport_id; + suop.new_name = new_sport_name; + if( new_manager ) + suop.extensions.value.new_manager = new_manager; + if( fee_paying_account ) + suop.extensions.value.fee_paying_account = fee_paying_account; + + return suop; + } + + sport_delete_operation create_sport_delete_op( sport_id_type sport_id, + optional fee_paying_account = optional() ) + { + sport_delete_operation sdop; + sdop.sport_id = sport_id; + if( fee_paying_account ) + sdop.extensions.value.fee_paying_account = fee_paying_account; + + return sdop; + } + + event_group_create_operation create_event_group_create_op( sport_id_type sport_id, + optional new_manager, + optional fee_paying_account = optional() ) + { + event_group_create_operation egcop; + egcop.sport_id = sport_id; + egcop.name = default_event_group_name; + if( new_manager ) + egcop.extensions.value.new_manager = new_manager; + if( fee_paying_account ) + egcop.extensions.value.fee_paying_account = fee_paying_account; + + return egcop; + } + + event_group_update_operation create_event_group_update_op( event_group_id_type event_group_id, + optional new_manager, + optional fee_paying_account = optional() ) + { + event_group_update_operation eguop; + eguop.event_group_id = event_group_id; + eguop.new_name = new_event_group_name; + if( new_manager ) + eguop.extensions.value.new_manager = new_manager; + if( fee_paying_account ) + eguop.extensions.value.fee_paying_account = fee_paying_account; + + return eguop; + } + + event_group_delete_operation create_event_group_delete_op( event_group_id_type event_group_id, + optional fee_paying_account = optional() ) + { + event_group_delete_operation egdop; + egdop.event_group_id = event_group_id; + if( fee_paying_account ) + egdop.extensions.value.fee_paying_account = fee_paying_account; + + return egdop; + } + + event_create_operation create_event_create_op( event_group_id_type event_group_id, + optional new_manager, + optional fee_paying_account = optional() ) + { + event_create_operation ecop; + ecop.event_group_id = event_group_id; + ecop.name = default_event_name; + ecop.season = { { "SN", "SEASON_NAME" } }; + if( new_manager ) + ecop.extensions.value.new_manager = new_manager; + if( fee_paying_account ) + ecop.extensions.value.fee_paying_account = fee_paying_account; + + return ecop; + } + + event_update_operation create_event_update_op( event_id_type event_id, + optional new_manager, + optional fee_paying_account = optional() ) + { + event_update_operation euop; + euop.event_id = event_id; + euop.new_name = new_event_name; + if( new_manager ) + euop.extensions.value.new_manager = new_manager; + if( fee_paying_account ) + euop.extensions.value.fee_paying_account = fee_paying_account; + + return euop; + } + + event_update_status_operation create_event_update_status_op( event_id_type event_id, + optional fee_paying_account = optional() ) + { + event_update_status_operation eusop; + eusop.event_id = event_id; + eusop.status = new_event_status; + if( fee_paying_account ) + eusop.extensions.value.fee_paying_account = fee_paying_account; + + return eusop; + } + + betting_market_rules_create_operation create_betting_market_rules_create_op() + { + internationalized_string_type default_betting_market_rules_name = {{"MR", "MARKET_RULES"}}; + internationalized_string_type default_betting_market_rules_description = {{"DC", "DISCRIPTION"}}; + + betting_market_rules_create_operation bmrcop; + bmrcop.name = default_betting_market_rules_name; + bmrcop.description = default_betting_market_rules_description; + + return bmrcop; + } + + betting_market_group_create_operation create_betting_market_group_create_op( + event_id_type event_id, + betting_market_rules_id_type betting_market_rules_id, + optional fee_paying_account = optional() ) + { + betting_market_group_create_operation bmgcop; + bmgcop.event_id = event_id; + bmgcop.rules_id = betting_market_rules_id; + bmgcop.description = default_betting_market_group_description; + if( fee_paying_account != account_id_type() ) + bmgcop.extensions.value.fee_paying_account = fee_paying_account; + + return bmgcop; + } + + betting_market_group_update_operation create_betting_market_group_update_op( + betting_market_group_id_type betting_market_group_id, + optional fee_paying_account = optional() ) + { + betting_market_group_update_operation bmguop; + bmguop.betting_market_group_id = betting_market_group_id; + bmguop.new_description = new_betting_market_group_description; + if( fee_paying_account ) + bmguop.extensions.value.fee_paying_account = fee_paying_account; + + return bmguop; + } + + betting_market_group_resolve_operation create_betting_market_group_resolve_op( + betting_market_group_id_type betting_market_group_id, + betting_market_id_type betting_market_id, + optional fee_paying_account = optional() ) + { + betting_market_group_resolve_operation bmgrop; + bmgrop.betting_market_group_id = betting_market_group_id; + bmgrop.resolutions = {{betting_market_id, betting_market_resolution_type::cancel}}; + if( fee_paying_account ) + bmgrop.extensions.value.fee_paying_account = fee_paying_account; + + return bmgrop; + } + + betting_market_group_cancel_unmatched_bets_operation create_bettting_market_group_cancel_unmatched_bets_op( + betting_market_group_id_type betting_market_group_id, + optional fee_paying_account = optional() ) + { + betting_market_group_cancel_unmatched_bets_operation bmgcubop; + bmgcubop.betting_market_group_id = betting_market_group_id; + if( fee_paying_account ) + bmgcubop.extensions.value.fee_paying_account = fee_paying_account; + + return bmgcubop; + } + + betting_market_create_operation create_betting_market_create_op( betting_market_group_id_type betting_market_group_id, + optional fee_paying_account = optional() ) + { + betting_market_create_operation bmcop; + bmcop.group_id = betting_market_group_id; + bmcop.description = default_betting_market_description; + if( fee_paying_account ) + bmcop.extensions.value.fee_paying_account = fee_paying_account; + + return bmcop; + } + + betting_market_update_operation create_betting_market_update_op( betting_market_id_type betting_market_id, + optional fee_paying_account = optional() ) + { + betting_market_update_operation bmuop; + bmuop.betting_market_id = betting_market_id; + bmuop.new_description = new_betting_market_description; + if( fee_paying_account ) + bmuop.extensions.value.fee_paying_account = fee_paying_account; + + return bmuop; + } +}; + +BOOST_FIXTURE_TEST_SUITE( manager_tests, manager_tests_fixture ) + +#define GRAPHENE_CHECK_NO_THROW( op, bool_description ) \ + { \ + bool bool_description = true; \ + try { \ + op; \ + } \ + catch( fc::assert_exception &e ) { \ + bool_description = false; \ + } \ + BOOST_CHECK( bool_description ); \ + } + + +BOOST_AUTO_TEST_CASE( before_hf_create_modify_and_delete_all_objects_and_check_default_manager_with_witness ) +{ try { + generate_blocks( HARDFORK_1000_TIME ); + generate_block(); + + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + + auto sport_id = create_sport_by_witness(); + BOOST_CHECK( sport_id(db).name == default_sport_name + && sport_id(db).manager == witness_id ); + + auto event_group_id = create_event_group_by_witness( sport_id ); + BOOST_CHECK( event_group_id(db).name == default_event_group_name + && event_group_id(db).manager == witness_id ); + + auto event_id = create_event_by_witness( event_group_id ); + BOOST_CHECK( event_id(db).name == default_event_name + && event_id(db).manager == witness_id ); + + auto rules_id = create_betting_market_rules_by_witness(); + + auto betting_market_group_id = create_betting_market_group_by_witness( event_id, rules_id ); + BOOST_CHECK( betting_market_group_id(db).description == default_betting_market_group_description ); + + auto betting_market_id = create_betting_market_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_id(db).description == default_betting_market_description ); + + + + update_sport_by_witness( sport_id ); + BOOST_CHECK( sport_id(db).name == new_sport_name ); + + update_event_group_by_witness( event_group_id ); + BOOST_CHECK( event_group_id(db).name == new_event_group_name ); + + update_event_by_witness( event_id ); + BOOST_CHECK( event_id(db).name == new_event_name ); + + update_event_status_by_witness( event_id ); + BOOST_CHECK( event_id(db).get_status() == new_event_status ); + + update_betting_market_group_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_group_id(db).description == new_betting_market_group_description ); + + GRAPHENE_CHECK_NO_THROW( resolve_betting_market_group_by_witness( betting_market_group_id, betting_market_id ), + resolve_betting_market_group ); + + GRAPHENE_CHECK_NO_THROW( cancel_betting_market_group_unmatched_bets_by_witness( betting_market_group_id ), + cancel_betting_market_group_unmatched_bets ); + + update_betting_market_by_witness( betting_market_id ); + BOOST_CHECK( betting_market_id(db).description == new_betting_market_description ); + + + + GRAPHENE_CHECK_NO_THROW( delete_event_group_by_witness( event_group_id ), + delete_event_group ); + + GRAPHENE_CHECK_NO_THROW( delete_sport_by_witness( sport_id ), + delete_sport ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( before_hf_check_manager_and_fee_paying_account_asserts ) +{ try { + generate_blocks( HARDFORK_1000_TIME ); + generate_block(); + + + auto default_id = optional(); + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + auto sport_id = create_sport_by_witness(); + auto event_group_id = create_event_group_by_witness( sport_id ); + auto event_id = create_event_by_witness( event_group_id ); + auto rules_id = create_betting_market_rules_by_witness(); + auto betting_market_group_id = create_betting_market_group_by_witness( event_id, rules_id ); + auto betting_market_id = create_betting_market_by_witness( betting_market_group_id ); + + + + GRAPHENE_REQUIRE_THROW( update_sport_by_fee_payer( sport_id, witness_id, default_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_sport_by_fee_payer( sport_id, default_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( delete_sport_by_fee_payer( sport_id, witness_id ), + fc::assert_exception ); + + + + GRAPHENE_REQUIRE_THROW( create_event_group_by_fee_payer( sport_id, witness_id, default_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( create_event_group_by_fee_payer( sport_id, default_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_group_by_fee_payer( event_group_id, witness_id, default_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_group_by_fee_payer( event_group_id, default_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( delete_event_group_by_fee_payer( event_group_id, witness_id ), + fc::assert_exception ); + + + + GRAPHENE_REQUIRE_THROW( create_event_by_fee_payer( event_group_id, witness_id, default_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( create_event_by_fee_payer( event_group_id, default_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_by_fee_payer( event_id, witness_id, default_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_by_fee_payer( event_id, default_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_status_by_fee_payer( event_id, witness_id ), + fc::assert_exception ); + + + + GRAPHENE_REQUIRE_THROW( create_betting_market_group_by_fee_payer( event_id, rules_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_betting_market_group_by_fee_payer( betting_market_group_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( cancel_betting_market_group_unmatched_bets_by_fee_payer( betting_market_group_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( resolve_betting_market_group_by_fee_payer( betting_market_group_id, betting_market_id, witness_id ), + fc::assert_exception ); + + + + GRAPHENE_REQUIRE_THROW( create_betting_market_by_fee_payer( betting_market_group_id, witness_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_betting_market_by_fee_payer( betting_market_id, witness_id ), + fc::assert_exception ); + + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( after_hf_check_all_is_not_manager_asserts ) +{ try { + generate_blocks( HARDFORK_1000_TIME ); + generate_block(); + + + ACTOR( alice ); + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + + auto sport_id = create_sport_by_witness(); + + GRAPHENE_REQUIRE_THROW( create_event_group_by_fee_payer( sport_id, witness_id, alice_id), + fc::assert_exception ); + + auto event_group_id = create_event_group_by_witness( sport_id ); + GRAPHENE_REQUIRE_THROW( create_event_by_fee_payer( event_group_id, witness_id, alice_id), + fc::assert_exception ); + + auto event_id = create_event_by_witness( event_group_id ); + auto rules_id = create_betting_market_rules_by_witness(); + GRAPHENE_REQUIRE_THROW( create_betting_market_group_by_fee_payer( event_id, rules_id, alice_id ), + fc::assert_exception ); + + auto betting_market_group_id = create_betting_market_group_by_witness( event_id, rules_id ); + GRAPHENE_REQUIRE_THROW( create_betting_market_by_fee_payer( betting_market_group_id, alice_id ), + fc::assert_exception ); + + auto betting_market_id = create_betting_market_by_witness( betting_market_group_id ); + + + + GRAPHENE_REQUIRE_THROW( update_sport_by_fee_payer( sport_id, witness_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_group_by_fee_payer( event_group_id, witness_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_by_fee_payer( event_id, witness_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_event_status_by_fee_payer( event_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_betting_market_group_by_fee_payer( betting_market_group_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( resolve_betting_market_group_by_fee_payer( betting_market_group_id, betting_market_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( cancel_betting_market_group_unmatched_bets_by_fee_payer( betting_market_group_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( update_betting_market_by_fee_payer( betting_market_id, alice_id ), + fc::assert_exception ); + + + + GRAPHENE_REQUIRE_THROW( delete_sport_by_fee_payer( sport_id, alice_id ), + fc::assert_exception ); + + GRAPHENE_REQUIRE_THROW( delete_event_group_by_fee_payer( event_group_id, alice_id ), + fc::assert_exception ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_CASE( after_hf_create_modify_and_delete_all_objects_and_check_default_manager_with_witness ) +{ + generate_blocks( HARDFORK_MANAGER_TIME ); + generate_block(); + + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + + auto sport_id = create_sport_by_witness(); + BOOST_CHECK( sport_id(db).name == default_sport_name + && sport_id(db).manager == witness_id ); + + auto event_group_id = create_event_group_by_witness( sport_id ); + BOOST_CHECK( event_group_id(db).name == default_event_group_name + && event_group_id(db).manager == witness_id ); + + auto event_id = create_event_by_witness( event_group_id ); + BOOST_CHECK( event_id(db).name == default_event_name + && event_id(db).manager == witness_id ); + + auto rules_id = create_betting_market_rules_by_witness(); + + auto betting_market_group_id = create_betting_market_group_by_witness( event_id, rules_id ); + BOOST_CHECK( betting_market_group_id(db).description == default_betting_market_group_description ); + + auto betting_market_id = create_betting_market_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_id(db).description == default_betting_market_description ); + + + + update_sport_by_witness( sport_id ); + BOOST_CHECK( sport_id(db).name == new_sport_name ); + + update_event_group_by_witness( event_group_id ); + BOOST_CHECK( event_group_id(db).name == new_event_group_name ); + + update_event_by_witness( event_id ); + BOOST_CHECK( event_id(db).name == new_event_name ); + + update_event_status_by_witness( event_id ); + BOOST_CHECK( event_id(db).get_status() == new_event_status ); + + update_betting_market_group_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_group_id(db).description == new_betting_market_group_description ); + + GRAPHENE_CHECK_NO_THROW( resolve_betting_market_group_by_witness( betting_market_group_id, betting_market_id ), + resolve_betting_market_group ); + + GRAPHENE_CHECK_NO_THROW( cancel_betting_market_group_unmatched_bets_by_witness( betting_market_group_id ), + cancel_betting_market_group_unmatched_bets ); + + update_betting_market_by_witness( betting_market_id ); + BOOST_CHECK( betting_market_id(db).description == new_betting_market_description ); + + + + GRAPHENE_CHECK_NO_THROW( delete_event_group_by_witness( event_group_id ), + delete_event_group ); + + GRAPHENE_CHECK_NO_THROW( delete_sport_by_witness( sport_id ), + delete_sport ); +} + +BOOST_AUTO_TEST_CASE( after_hf_create_modify_and_delete_all_obj_with_manager ) +{ + ACTOR( alice ); + + generate_blocks( HARDFORK_MANAGER_TIME ); + generate_block(); + + + + auto sport_id = create_sport_by_witness( alice_id ); + BOOST_CHECK( sport_id(db).manager == alice_id ); + + auto event_group_id = create_event_group_by_fee_payer( sport_id, alice_id, alice_id ); + BOOST_CHECK( event_group_id(db).manager == alice_id ); + + auto event_id = create_event_by_fee_payer( event_group_id, alice_id, alice_id ); + BOOST_CHECK( event_id(db).manager == alice_id ); + + auto rules_id = create_betting_market_rules_by_witness(); + + auto betting_market_group_id = create_betting_market_group_by_fee_payer( event_id, rules_id, alice_id ); + BOOST_CHECK( betting_market_group_id(db).description == default_betting_market_group_description ); + + auto betting_market_id = create_betting_market_by_fee_payer( betting_market_group_id, alice_id ); + BOOST_CHECK( betting_market_id(db).description == default_betting_market_description ); + + + + update_sport_by_fee_payer( sport_id, alice_id, alice_id ); + BOOST_CHECK( sport_id(db).name == new_sport_name ); + + update_event_group_by_fee_payer( event_group_id, alice_id, alice_id ); + BOOST_CHECK( event_group_id(db).name == new_event_group_name ); + + update_event_by_fee_payer( event_id, alice_id, alice_id ); + BOOST_CHECK( event_id(db).name == new_event_name ); + + update_event_status_by_witness( event_id ); + BOOST_CHECK( event_id(db).get_status() == new_event_status ); + + update_betting_market_group_by_fee_payer( betting_market_group_id, alice_id ); + BOOST_CHECK( betting_market_group_id(db).description == new_betting_market_group_description ); + + GRAPHENE_CHECK_NO_THROW( resolve_betting_market_group_by_witness( betting_market_group_id, betting_market_id ), + resolve_betting_market_group ); + + GRAPHENE_CHECK_NO_THROW( cancel_betting_market_group_unmatched_bets_by_fee_payer( betting_market_group_id, alice_id ), + cancel_betting_market_group_unmatched_bets ); + + update_betting_market_by_fee_payer( betting_market_id, alice_id ); + BOOST_CHECK( betting_market_id(db).description == new_betting_market_description ); + + + + GRAPHENE_CHECK_NO_THROW( delete_event_group_by_fee_payer( event_group_id, alice_id ), + delete_event_group ); + + GRAPHENE_CHECK_NO_THROW( delete_sport_by_fee_payer( sport_id, alice_id ), + delete_sport ); + +} + +BOOST_AUTO_TEST_CASE( after_hf_create_modify_and_delete_all_obj_with_manager_set_by_witness ) +{ + ACTOR( alice ); + + generate_blocks( HARDFORK_MANAGER_TIME ); + generate_block(); + + + auto sport_id = create_sport_by_witness( alice_id ); + BOOST_CHECK( sport_id(db).name == default_sport_name + && sport_id(db).manager == alice_id ); + + auto event_group_id = create_event_group_by_witness( sport_id, alice_id ); + BOOST_CHECK( event_group_id(db).name == default_event_group_name + && event_group_id(db).manager == alice_id ); + + auto event_id = create_event_by_witness( event_group_id, alice_id ); + BOOST_CHECK( event_id(db).name == default_event_name + && event_id(db).manager == alice_id ); + + auto rules_id = create_betting_market_rules_by_witness(); + + auto betting_market_group_id = create_betting_market_group_by_witness( event_id, rules_id ); + BOOST_CHECK( betting_market_group_id(db).description == default_betting_market_group_description ); + + auto betting_market_id = create_betting_market_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_id(db).description == default_betting_market_description ); + + + + update_sport_by_witness( sport_id ); + BOOST_CHECK( sport_id(db).name == new_sport_name ); + + update_event_group_by_witness( event_group_id ); + BOOST_CHECK( event_group_id(db).name == new_event_group_name ); + + update_event_by_witness( event_id ); + BOOST_CHECK( event_id(db).name == new_event_name ); + + update_event_status_by_witness( event_id ); + BOOST_CHECK( event_id(db).get_status() == new_event_status ); + + update_betting_market_group_by_witness( betting_market_group_id ); + BOOST_CHECK( betting_market_group_id(db).description == new_betting_market_group_description ); + + GRAPHENE_CHECK_NO_THROW( resolve_betting_market_group_by_witness( betting_market_group_id, betting_market_id ), + resolve_betting_market_group ); + + GRAPHENE_CHECK_NO_THROW( cancel_betting_market_group_unmatched_bets_by_witness( betting_market_group_id ), + cancel_betting_market_group_unmatched_bets ); + + update_betting_market_by_witness( betting_market_id ); + BOOST_CHECK( betting_market_id(db).description == new_betting_market_description ); + + + + GRAPHENE_CHECK_NO_THROW( delete_event_group_by_witness( event_group_id ), + delete_event_group ); + + GRAPHENE_CHECK_NO_THROW( delete_sport_by_witness( sport_id ), + delete_sport ); +} + +BOOST_AUTO_TEST_CASE( after_hf_test_is_manager_cascade ) +{ try { + ACTOR( alice ); + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + generate_blocks( HARDFORK_MANAGER_TIME ); + generate_block(); + + + + auto sport_id = create_sport_by_witness( alice_id ); + BOOST_CHECK( sport_id(db).manager == alice_id ); + + auto event_group_id = create_event_group_by_fee_payer( sport_id, witness_id, alice_id ); + BOOST_CHECK( event_group_id(db).manager == witness_id ); + + auto event_id = create_event_by_fee_payer( event_group_id, witness_id, alice_id ); + BOOST_CHECK( event_id(db).manager == witness_id ); + + auto rules_id = create_betting_market_rules_by_witness(); + + auto betting_market_group_id = create_betting_market_group_by_fee_payer( event_id, rules_id, alice_id ); + BOOST_CHECK( betting_market_group_id(db).description == default_betting_market_group_description ); + + auto betting_market_id = create_betting_market_by_fee_payer( betting_market_group_id, alice_id ); + BOOST_CHECK( betting_market_id(db).description == default_betting_market_description ); + + + + update_sport_by_fee_payer( sport_id, alice_id, alice_id ); + BOOST_CHECK( sport_id(db).name == new_sport_name ); + + update_event_group_by_fee_payer( event_group_id, witness_id, alice_id ); + BOOST_CHECK( event_group_id(db).name == new_event_group_name ); + + update_event_by_fee_payer( event_id, witness_id, alice_id ); + BOOST_CHECK( event_id(db).name == new_event_name ); + + update_event_status_by_witness( event_id ); + BOOST_CHECK( event_id(db).get_status() == new_event_status ); + + update_betting_market_group_by_fee_payer( betting_market_group_id, alice_id ); + BOOST_CHECK( betting_market_group_id(db).description == new_betting_market_group_description ); + + GRAPHENE_CHECK_NO_THROW( resolve_betting_market_group_by_witness( betting_market_group_id, betting_market_id ), + resolve_betting_market_group ); + + GRAPHENE_CHECK_NO_THROW( cancel_betting_market_group_unmatched_bets_by_fee_payer( betting_market_group_id, alice_id ), + cancel_betting_market_group_unmatched_bets ); + + update_betting_market_by_fee_payer( betting_market_id, alice_id ); + BOOST_CHECK( betting_market_id(db).description == new_betting_market_description ); + + + + GRAPHENE_CHECK_NO_THROW( delete_event_group_by_fee_payer( event_group_id, alice_id ), + delete_event_group ); + + GRAPHENE_CHECK_NO_THROW( delete_sport_by_fee_payer( sport_id, alice_id ), + delete_sport ); + +} FC_LOG_AND_RETHROW() } + + +BOOST_AUTO_TEST_CASE( after_hf_check_relative_object_creation_by_witness ) +{ try { + ACTOR( alice ); + auto witness_id = GRAPHENE_WITNESS_ACCOUNT; + + generate_blocks( HARDFORK_MANAGER_TIME ); + generate_block(); + + + + sport_id_type rel_sport_id; + event_group_id_type rel_event_group_id; + event_id_type rel_event_id; + betting_market_group_id_type rel_betting_market_group_id; + betting_market_rules_id_type rules_id = create_betting_market_rules_by_witness(); + betting_market_id_type rel_betting_market_id; + + auto sport_create_op = create_sport_create_op( alice_id ); + auto event_group_create_op = create_event_group_create_op( rel_sport_id, alice_id ); + auto event_create_op = create_event_create_op( rel_event_group_id, alice_id ); + auto betting_market_group_create_op = create_betting_market_group_create_op( rel_event_id, rules_id ); + auto betting_market_create_op = create_betting_market_create_op( rel_betting_market_group_id ); + + + + proposal_create_operation pcop; + pcop.fee_paying_account = witness_id; + pcop.expiration_time = db.head_block_time() + fc::hours(24); + pcop.proposed_ops.emplace_back( sport_create_op ); + pcop.proposed_ops.emplace_back( event_group_create_op ); + pcop.proposed_ops.emplace_back( event_create_op ); + pcop.proposed_ops.emplace_back( betting_market_group_create_op ); + pcop.proposed_ops.emplace_back( betting_market_create_op ); + + auto process_proposal_by_witness = [this, &pcop]() { + signed_transaction tx; + tx.operations.push_back(pcop); + set_expiration(db, tx); + sign(tx, init_account_priv_key); + + processed_transaction processed_tx = db.push_transaction(tx); + proposal_id_type proposal_id = processed_tx.operation_results[0].get(); + + auto& witnesses_set = db.get_global_properties().active_witnesses; + std::vector witnesses_vec( witnesses_set.begin(), witnesses_set.end() ); + process_proposal_by_witnesses( witnesses_vec, proposal_id, false ); + }; + process_proposal_by_witness(); + + + + const auto& s_obj = *db.get_index_type().indices().get().begin(); + const auto& eg_obj = *db.get_index_type().indices().get().begin(); + const auto& e_obj = *db.get_index_type().indices().get().begin(); + const auto& bmg_obj = *db.get_index_type().indices().get().begin(); + const auto& bm_obj = *db.get_index_type().indices().get().begin(); + + BOOST_CHECK( s_obj.manager == alice_id ); + BOOST_CHECK( eg_obj.manager == alice_id ); + BOOST_CHECK( e_obj.manager == alice_id ); + BOOST_CHECK( bmg_obj.description == default_betting_market_group_description ); + BOOST_CHECK( bm_obj.description == default_betting_market_description ); + +} FC_LOG_AND_RETHROW() } + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index fd35c3123..1f3961565 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1112,633 +1112,6 @@ BOOST_AUTO_TEST_CASE( uia_fees ) } } -BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture ) - -BOOST_AUTO_TEST_CASE( create_dividend_uia ) -{ - using namespace graphene; - try { - BOOST_TEST_MESSAGE("Creating dividend holder asset"); - { - asset_create_operation creator; - creator.issuer = account_id_type(); - creator.fee = asset(); - creator.symbol = "DIVIDEND"; - creator.common_options.max_supply = 100000000; - creator.precision = 2; - creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ - creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - creator.common_options.flags = charge_market_fee; - creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); - trx.operations.push_back(std::move(creator)); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - - BOOST_TEST_MESSAGE("Creating test accounts"); - create_account("alice"); - create_account("bob"); - create_account("carol"); - create_account("dave"); - create_account("frank"); - - BOOST_TEST_MESSAGE("Creating test asset"); - { - asset_create_operation creator; - creator.issuer = account_id_type(); - creator.fee = asset(); - creator.symbol = "TEST"; - creator.common_options.max_supply = 100000000; - creator.precision = 2; - creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ - creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - creator.common_options.flags = charge_market_fee; - creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); - trx.operations.push_back(std::move(creator)); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - BOOST_TEST_MESSAGE("Funding asset fee pool"); - { - asset_fund_fee_pool_operation fund_op; - fund_op.from_account = account_id_type(); - fund_op.asset_id = get_asset("TEST").id; - fund_op.amount = 500000000; - trx.operations.push_back(std::move(fund_op)); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - - // our DIVIDEND asset should not yet be a divdend asset - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id); - - BOOST_TEST_MESSAGE("Converting the new asset to a dividend holder asset"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = db.head_block_time() + fc::minutes(1); - op.new_options.payout_interval = 60 * 60 * 24 * 3; - - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - BOOST_TEST_MESSAGE("Verifying the dividend holder asset options"); - BOOST_REQUIRE(dividend_holder_asset_object.dividend_data_id); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - { - BOOST_REQUIRE(dividend_data.options.payout_interval); - BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24 * 3); - } - - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); - - // db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) - // { - // _gpo.parameters.current_fees->get().distribution_base_fee = 100; - // _gpo.parameters.current_fees->get().distribution_fee_per_holder = 100; - // } ); - - - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( test_update_dividend_interval ) -{ - using namespace graphene; - try { - INVOKE( create_dividend_uia ); - - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - - auto advance_to_next_payout_time = [&]() { - // Advance to the next upcoming payout time - BOOST_REQUIRE(dividend_data.options.next_payout_time); - fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; - // generate blocks up to the next scheduled time - generate_blocks(next_payout_scheduled_time); - // if the scheduled time fell on a maintenance interval, then we should have paid out. - // if not, we need to advance to the next maintenance interval to trigger the payout - if (dividend_data.options.next_payout_time) - { - // we know there was a next_payout_time set when we entered this, so if - // it has been cleared, we must have already processed payouts, no need to - // further advance time. - BOOST_REQUIRE(dividend_data.options.next_payout_time); - if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - } - }; - - BOOST_TEST_MESSAGE("Updating the payout interval"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = fc::time_point::now() + fc::minutes(1); - op.new_options.payout_interval = 60 * 60 * 24; // 1 days - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - BOOST_TEST_MESSAGE("Verifying the updated dividend holder asset options"); - { - BOOST_REQUIRE(dividend_data.options.payout_interval); - BOOST_CHECK_EQUAL(*dividend_data.options.payout_interval, 60 * 60 * 24); - } - - BOOST_TEST_MESSAGE("Removing the payout interval"); - { - asset_update_dividend_operation op; - op.issuer = dividend_holder_asset_object.issuer; - op.asset_to_update = dividend_holder_asset_object.id; - op.new_options.next_payout_time = dividend_data.options.next_payout_time; - op.new_options.payout_interval = fc::optional(); - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - BOOST_CHECK(!dividend_data.options.payout_interval); - advance_to_next_payout_time(); - BOOST_REQUIRE_MESSAGE(!dividend_data.options.next_payout_time, "A new payout was scheduled, but none should have been"); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution ) -{ - using namespace graphene; - try { - INVOKE( create_dividend_uia ); - - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - const account_object& alice = get_account("alice"); - const account_object& bob = get_account("bob"); - const account_object& carol = get_account("carol"); - const account_object& dave = get_account("dave"); - const account_object& frank = get_account("frank"); - const auto& test_asset_object = get_asset("TEST"); - - auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) - { - asset_issue_operation op; - op.issuer = asset_to_issue.issuer; - op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); - op.issue_to_account = destination_account.id; - trx.operations.push_back( op ); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - }; - - auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { - int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, - holder_account_obj.id, - payout_asset_obj.id); - BOOST_CHECK_EQUAL(pending_balance, expected_balance); - }; - - auto advance_to_next_payout_time = [&]() { - // Advance to the next upcoming payout time - BOOST_REQUIRE(dividend_data.options.next_payout_time); - fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; - // generate blocks up to the next scheduled time - generate_blocks(next_payout_scheduled_time); - // if the scheduled time fell on a maintenance interval, then we should have paid out. - // if not, we need to advance to the next maintenance interval to trigger the payout - if (dividend_data.options.next_payout_time) - { - // we know there was a next_payout_time set when we entered this, so if - // it has been cleared, we must have already processed payouts, no need to - // further advance time. - BOOST_REQUIRE(dividend_data.options.next_payout_time); - if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - } - }; - - // the first test will be testing pending balances, so we need to hit a - // maintenance interval that isn't the payout interval. Payout is - // every 3 days, maintenance interval is every 1 day. - advance_to_next_payout_time(); - - // Set up the first test, issue alice, bob, and carol each 100 DIVIDEND. - // Then deposit 300 TEST in the distribution account, and see that they - // each are credited 100 TEST. - issue_asset_to_account(dividend_holder_asset_object, alice, 100000); - issue_asset_to_account(dividend_holder_asset_object, bob, 100000); - issue_asset_to_account(dividend_holder_asset_object, carol, 100000); - - BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); - issue_asset_to_account(test_asset_object, dividend_distribution_account, 30000); - - generate_block(); - - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - - verify_pending_balance(alice, test_asset_object, 10000); - verify_pending_balance(bob, test_asset_object, 10000); - verify_pending_balance(carol, test_asset_object, 10000); - - // For the second test, issue carol more than the other two, so it's - // alice: 100 DIVIDND, bob: 100 DIVIDEND, carol: 200 DIVIDEND - // Then deposit 400 TEST in the distribution account, and see that alice - // and bob are credited with 100 TEST, and carol gets 200 TEST - BOOST_TEST_MESSAGE("Issuing carol twice as much of the holder asset"); - issue_asset_to_account(dividend_holder_asset_object, carol, 100000); // one thousand at two digits of precision - issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); // one thousand at two digits of precision - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - verify_pending_balance(alice, test_asset_object, 20000); - verify_pending_balance(bob, test_asset_object, 20000); - verify_pending_balance(carol, test_asset_object, 30000); - - fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; - advance_to_next_payout_time(); - - - BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled"); - BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time, - "New payout was scheduled for the same time as the last payout"); - BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, - "New payout was not scheduled for the expected time"); - - auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) - { - BOOST_TEST_MESSAGE("Verifying the virtual op was created"); - const account_transaction_history_index& hist_idx = db.get_index_type(); - auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); - BOOST_REQUIRE(account_history_range.first != account_history_range.second); - const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); - const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); - BOOST_CHECK(distribution_operation.account_id == destination_account.id); - BOOST_CHECK(std::find(distribution_operation.amounts.begin(), distribution_operation.amounts.end(), expected_payout) - != distribution_operation.amounts.end()); - }; - - BOOST_TEST_MESSAGE("Verifying the payouts"); - BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); - verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id)); - verify_pending_balance(alice, test_asset_object, 0); - - BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); - verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id)); - verify_pending_balance(bob, test_asset_object, 0); - - BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000); - verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id)); - verify_pending_balance(carol, test_asset_object, 0); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution_to_core_asset ) -{ - using namespace graphene; - try { - BOOST_TEST_MESSAGE("Creating test accounts"); - create_account("alice"); - create_account("bob"); - create_account("carol"); - create_account("dave"); - create_account("frank"); - - BOOST_TEST_MESSAGE("Creating test asset"); - { - asset_create_operation creator; - creator.issuer = account_id_type(); - creator.fee = asset(); - creator.symbol = "TEST"; - creator.common_options.max_supply = 100000000; - creator.precision = 2; - creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/ - creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; - creator.common_options.flags = charge_market_fee; - creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))}); - trx.operations.push_back(std::move(creator)); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - const auto& dividend_holder_asset_object = asset_id_type(0)(db); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - const account_object& alice = get_account("alice"); - const account_object& bob = get_account("bob"); - const account_object& carol = get_account("carol"); - const account_object& dave = get_account("dave"); - const account_object& frank = get_account("frank"); - const auto& test_asset_object = get_asset("TEST"); - - auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) - { - asset_issue_operation op; - op.issuer = asset_to_issue.issuer; - op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); - op.issue_to_account = destination_account.id; - trx.operations.push_back( op ); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - }; - - auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { - int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, - holder_account_obj.id, - payout_asset_obj.id); - BOOST_CHECK_EQUAL(pending_balance, expected_balance); - }; - - auto advance_to_next_payout_time = [&]() { - // Advance to the next upcoming payout time - BOOST_REQUIRE(dividend_data.options.next_payout_time); - fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; - idump((next_payout_scheduled_time)); - // generate blocks up to the next scheduled time - generate_blocks(next_payout_scheduled_time); - // if the scheduled time fell on a maintenance interval, then we should have paid out. - // if not, we need to advance to the next maintenance interval to trigger the payout - if (dividend_data.options.next_payout_time) - { - // we know there was a next_payout_time set when we entered this, so if - // it has been cleared, we must have already processed payouts, no need to - // further advance time. - BOOST_REQUIRE(dividend_data.options.next_payout_time); - if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - } - idump((db.head_block_time())); - }; - - // the first test will be testing pending balances, so we need to hit a - // maintenance interval that isn't the payout interval. Payout is - // every 3 days, maintenance interval is every 1 day. - advance_to_next_payout_time(); - - // Set up the first test, issue alice, bob, and carol, and dave each 1/4 of the total - // supply of the core asset. - // Then deposit 400 TEST in the distribution account, and see that they - // each are credited 100 TEST. - transfer( committee_account(db), alice, asset( 250000000000000 ) ); - transfer( committee_account(db), bob, asset( 250000000000000 ) ); - transfer( committee_account(db), carol, asset( 250000000000000 ) ); - transfer( committee_account(db), dave, asset( 250000000000000 ) ); - - BOOST_TEST_MESSAGE("Issuing 300 TEST to the dividend account"); - issue_asset_to_account(test_asset_object, dividend_distribution_account, 40000); - - generate_block(); - - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - - verify_pending_balance(alice, test_asset_object, 10000); - verify_pending_balance(bob, test_asset_object, 10000); - verify_pending_balance(carol, test_asset_object, 10000); - verify_pending_balance(dave, test_asset_object, 10000); - - // For the second test, issue dave more than the other two, so it's - // alice: 1/5 CORE, bob: 1/5 CORE, carol: 1/5 CORE, dave: 2/5 CORE - // Then deposit 500 TEST in the distribution account, and see that alice - // bob, and carol are credited with 100 TEST, and dave gets 200 TEST - BOOST_TEST_MESSAGE("Issuing dave twice as much of the holder asset"); - transfer( alice, dave, asset( 50000000000000 ) ); - transfer( bob, dave, asset( 50000000000000 ) ); - transfer( carol, dave, asset( 50000000000000 ) ); - issue_asset_to_account(test_asset_object, dividend_distribution_account, 50000); // 500 at two digits of precision - BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - verify_pending_balance(alice, test_asset_object, 20000); - verify_pending_balance(bob, test_asset_object, 20000); - verify_pending_balance(carol, test_asset_object, 20000); - verify_pending_balance(dave, test_asset_object, 30000); - - fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; - advance_to_next_payout_time(); - - - BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled"); - BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time, - "New payout was scheduled for the same time as the last payout"); - BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time + *dividend_data.options.payout_interval == *dividend_data.options.next_payout_time, - "New payout was not scheduled for the expected time"); - - auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout) - { - BOOST_TEST_MESSAGE("Verifying the virtual op was created"); - const account_transaction_history_index& hist_idx = db.get_index_type(); - auto account_history_range = hist_idx.indices().get().equal_range(boost::make_tuple(destination_account.id)); - BOOST_REQUIRE(account_history_range.first != account_history_range.second); - const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db); - const asset_dividend_distribution_operation& distribution_operation = history_object.op.get(); - BOOST_CHECK(distribution_operation.account_id == destination_account.id); - BOOST_CHECK(std::find(distribution_operation.amounts.begin(), distribution_operation.amounts.end(), expected_payout) - != distribution_operation.amounts.end()); - }; - - BOOST_TEST_MESSAGE("Verifying the payouts"); - BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000); - verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id)); - verify_pending_balance(alice, test_asset_object, 0); - - BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000); - verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id)); - verify_pending_balance(bob, test_asset_object, 0); - - BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 20000); - verify_dividend_payout_operations(carol, asset(20000, test_asset_object.id)); - verify_pending_balance(carol, test_asset_object, 0); - - BOOST_CHECK_EQUAL(get_balance(dave, test_asset_object), 30000); - verify_dividend_payout_operations(dave, asset(30000, test_asset_object.id)); - verify_pending_balance(dave, test_asset_object, 0); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval ) -{ - using namespace graphene; - try { - INVOKE( create_dividend_uia ); - - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - const account_object& alice = get_account("alice"); - const account_object& bob = get_account("bob"); - const account_object& carol = get_account("carol"); - const account_object& dave = get_account("dave"); - const account_object& frank = get_account("frank"); - const auto& test_asset_object = get_asset("TEST"); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} - - -BOOST_AUTO_TEST_CASE( check_dividend_corner_cases ) -{ - using namespace graphene; - try { - INVOKE( create_dividend_uia ); - - const auto& dividend_holder_asset_object = get_asset("DIVIDEND"); - const auto& dividend_data = dividend_holder_asset_object.dividend_data(db); - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - const account_object& alice = get_account("alice"); - const account_object& bob = get_account("bob"); - const account_object& carol = get_account("carol"); - const account_object& dave = get_account("dave"); - const account_object& frank = get_account("frank"); - const auto& test_asset_object = get_asset("TEST"); - - auto issue_asset_to_account = [&](const asset_object& asset_to_issue, const account_object& destination_account, int64_t amount_to_issue) - { - asset_issue_operation op; - op.issuer = asset_to_issue.issuer; - op.asset_to_issue = asset(amount_to_issue, asset_to_issue.id); - op.issue_to_account = destination_account.id; - trx.operations.push_back( op ); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - }; - - auto verify_pending_balance = [&](const account_object& holder_account_obj, const asset_object& payout_asset_obj, int64_t expected_balance) { - int64_t pending_balance = get_dividend_pending_payout_balance(dividend_holder_asset_object.id, - holder_account_obj.id, - payout_asset_obj.id); - BOOST_CHECK_EQUAL(pending_balance, expected_balance); - }; - - auto reserve_asset_from_account = [&](const asset_object& asset_to_reserve, const account_object& from_account, int64_t amount_to_reserve) - { - asset_reserve_operation reserve_op; - reserve_op.payer = from_account.id; - reserve_op.amount_to_reserve = asset(amount_to_reserve, asset_to_reserve.id); - trx.operations.push_back(reserve_op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - }; - auto advance_to_next_payout_time = [&]() { - // Advance to the next upcoming payout time - BOOST_REQUIRE(dividend_data.options.next_payout_time); - fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time; - // generate blocks up to the next scheduled time - generate_blocks(next_payout_scheduled_time); - // if the scheduled time fell on a maintenance interval, then we should have paid out. - // if not, we need to advance to the next maintenance interval to trigger the payout - if (dividend_data.options.next_payout_time) - { - // we know there was a next_payout_time set when we entered this, so if - // it has been cleared, we must have already processed payouts, no need to - // further advance time. - BOOST_REQUIRE(dividend_data.options.next_payout_time); - if (*dividend_data.options.next_payout_time == next_payout_scheduled_time) - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - } - }; - - // the first test will be testing pending balances, so we need to hit a - // maintenance interval that isn't the payout interval. Payout is - // every 3 days, maintenance interval is every 1 day. - advance_to_next_payout_time(); - - BOOST_TEST_MESSAGE("Testing a payout interval when there are no users holding the dividend asset"); - BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); - BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); - BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0); - issue_asset_to_account(test_asset_object, dividend_distribution_account, 1000); - BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled"); - verify_pending_balance(alice, test_asset_object, 0); - verify_pending_balance(bob, test_asset_object, 0); - verify_pending_balance(carol, test_asset_object, 0); - advance_to_next_payout_time(); - BOOST_TEST_MESSAGE("Verify that no actual payments took place"); - verify_pending_balance(alice, test_asset_object, 0); - verify_pending_balance(bob, test_asset_object, 0); - verify_pending_balance(carol, test_asset_object, 0); - BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 0); - BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0); - BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0); - BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, test_asset_object), 1000); - - BOOST_TEST_MESSAGE("Now give alice a small balance and see that she takes it all"); - issue_asset_to_account(dividend_holder_asset_object, alice, 1); - generate_block(); - BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount"); - verify_pending_balance(alice, test_asset_object, 1000); - - // Test that we can pay out the dividend asset itself - issue_asset_to_account(dividend_holder_asset_object, bob, 1); - issue_asset_to_account(dividend_holder_asset_object, carol, 1); - issue_asset_to_account(dividend_holder_asset_object, dividend_distribution_account, 300); - generate_block(); - BOOST_CHECK_EQUAL(get_balance(alice, dividend_holder_asset_object), 1); - BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 1); - BOOST_CHECK_EQUAL(get_balance(carol, dividend_holder_asset_object), 1); - BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval"); - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); // get the maintenance skip slots out of the way - BOOST_TEST_MESSAGE("Verify that the dividend asset was shared out"); - verify_pending_balance(alice, dividend_holder_asset_object, 100); - verify_pending_balance(bob, dividend_holder_asset_object, 100); - verify_pending_balance(carol, dividend_holder_asset_object, 100); - } catch(fc::exception& e) { - edump((e.to_detail_string())); - throw; - } -} -BOOST_AUTO_TEST_SUITE_END() // end dividend_tests suite - BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try { INVOKE( issue_uia ); diff --git a/tests/tests/voting_tests.cpp b/tests/tests/voting_tests.cpp new file mode 100644 index 000000000..b88f485ae --- /dev/null +++ b/tests/tests/voting_tests.cpp @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2018 oxarbitrage, and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include + +#include +#include + +#include + +#include "../common/database_fixture.hpp" + +using namespace graphene::chain; +using namespace graphene::chain::test; + + +BOOST_FIXTURE_TEST_SUITE(voting_tests, database_fixture) + +BOOST_AUTO_TEST_CASE(last_voting_date) +{ + try + { + ACTORS((alice)); + + transfer(committee_account, alice_id, asset(100)); + + // we are going to vote for this witness + auto witness1 = witness_id_type(1)(db); + + auto stats_obj = alice_id(db).statistics(db); + BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), 0); + + // alice votes + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice.options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0 ); + + auto now = db.head_block_time().sec_since_epoch(); + + // last_vote_time is updated for alice + stats_obj = alice_id(db).statistics(db); + BOOST_CHECK_EQUAL(stats_obj.last_vote_time.sec_since_epoch(), now); + + } FC_LOG_AND_RETHROW() +} +BOOST_AUTO_TEST_CASE(last_voting_date_proxy) +{ + try + { + ACTORS((alice)(proxy)(bob)); + + transfer(committee_account, alice_id, asset(100)); + transfer(committee_account, bob_id, asset(200)); + transfer(committee_account, proxy_id, asset(300)); + + generate_block(); + + // witness to vote for + auto witness1 = witness_id_type(1)(db); + + // round1: alice changes proxy, this is voting activity + { + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice_id(db).options; + op.new_options->voting_account = proxy_id; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0 ); + } + // alice last_vote_time is updated + auto alice_stats_obj = alice_id(db).statistics(db); + auto round1 = db.head_block_time().sec_since_epoch(); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + generate_block(); + + // round 2: alice update account but no proxy or voting changes are done + { + graphene::chain::account_update_operation op; + op.account = alice_id; + op.new_options = alice_id(db).options; + trx.operations.push_back(op); + sign(trx, alice_private_key); + set_expiration( db, trx ); + PUSH_TX( db, trx, ~0 ); + } + // last_vote_time is not updated + auto round2 = db.head_block_time().sec_since_epoch(); + alice_stats_obj = alice_id(db).statistics(db); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + generate_block(); + + // round 3: bob votes + { + graphene::chain::account_update_operation op; + op.account = bob_id; + op.new_options = bob_id(db).options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, bob_private_key); + set_expiration( db, trx ); + PUSH_TX(db, trx, ~0); + } + + // last_vote_time for bob is updated as he voted + auto round3 = db.head_block_time().sec_since_epoch(); + auto bob_stats_obj = bob_id(db).statistics(db); + BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3); + + generate_block(); + + // round 4: proxy votes + { + graphene::chain::account_update_operation op; + op.account = proxy_id; + op.new_options = proxy_id(db).options; + op.new_options->votes.insert(witness1.vote_id); + trx.operations.push_back(op); + sign(trx, proxy_private_key); + PUSH_TX(db, trx, ~0); + } + + // proxy just voted so the last_vote_time is updated + auto round4 = db.head_block_time().sec_since_epoch(); + auto proxy_stats_obj = proxy_id(db).statistics(db); + BOOST_CHECK_EQUAL(proxy_stats_obj.last_vote_time.sec_since_epoch(), round4); + + // alice haves proxy, proxy votes but last_vote_time is not updated for alice + alice_stats_obj = alice_id(db).statistics(db); + BOOST_CHECK_EQUAL(alice_stats_obj.last_vote_time.sec_since_epoch(), round1); + + // bob haves nothing to do with proxy so last_vote_time is not updated + bob_stats_obj = bob_id(db).statistics(db); + BOOST_CHECK_EQUAL(bob_stats_obj.last_vote_time.sec_since_epoch(), round3); + + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_SUITE_END()