From 7857ac48a4731aeaf079e1afd7bd814b74e2d022 Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Mon, 27 Jun 2016 16:24:13 -0400 Subject: [PATCH] Correctly generating virtual transactions for payouts --- libraries/app/impacted.cpp | 5 +++ libraries/chain/db_maint.cpp | 37 +++++++++++++++-- .../include/graphene/chain/account_object.hpp | 13 +++++- .../graphene/chain/protocol/asset_ops.hpp | 40 ++++++++++++++++++- .../graphene/chain/protocol/operations.hpp | 3 +- libraries/wallet/wallet.cpp | 20 +++++++++- tests/common/database_fixture.cpp | 8 +--- tests/tests/operation_tests.cpp | 36 ++++++++++++++--- 8 files changed, 141 insertions(+), 21 deletions(-) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index ad9d9e0c..68be8f92 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -91,6 +91,11 @@ struct get_impacted_account_visitor void operator()( const asset_update_bitasset_operation& op ) {} void operator()( const asset_update_dividend_operation& op ) {} + void operator()( const asset_dividend_distribution_operation& op ) + { + _impacted.insert( op.account_id ); + } + void operator()( const asset_update_feed_producers_operation& op ) {} void operator()( const asset_issue_operation& op ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 6bef6051..0f9137b9 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -799,8 +799,8 @@ void schedule_pending_dividend_balances(database& db, dlog("Crediting account ${account} with ${amount}", ("account", holder_balance_object.owner(db).name)("amount", amount_to_credit)); auto pending_payout_iter = - pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); - if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) db.create( [&]( pending_dividend_payout_balance_object& obj ){ obj.owner = holder_balance_object.owner; obj.dividend_holder_asset_type = dividend_holder_asset_obj.id; @@ -839,7 +839,7 @@ void schedule_pending_dividend_balances(database& db, // Reduce all pending payouts proportionally share_type total_pending_balances; auto pending_payouts_range = - pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type)); for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second)) total_pending_balances += pending_balance_object.pending_balance; @@ -927,11 +927,28 @@ void process_dividend_assets(database& db) // for debugging, sum up our payouts here std::map amounts_paid_out_by_asset; #endif + auto pending_payouts_range = - pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + pending_payout_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + // the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account + // we iterate in this order so we can build up a list of payouts for each account to put in the + // virtual op + flat_set payouts_for_this_holder; + fc::optional last_holder_account_id; for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; ) { const pending_dividend_payout_balance_object& pending_balance_object = *pending_balance_object_iter; + + if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner) + { + // we've moved on to a new account, generate the dividend payment virtual op for the previous one + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, + payouts_for_this_holder)); + ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + payouts_for_this_holder.clear(); + } + ilog("Processing payout of ${asset} to account ${account}", ("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)) ("account", pending_balance_object.owner(db).name)); @@ -939,6 +956,9 @@ void process_dividend_assets(database& db) db.adjust_balance(pending_balance_object.owner, asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type)); + payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance, + pending_balance_object.dividend_payout_asset_type)); + last_holder_account_id = pending_balance_object.owner; #ifndef NDEBUG amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; #endif @@ -946,6 +966,15 @@ void process_dividend_assets(database& db) ++pending_balance_object_iter; db.remove(pending_balance_object); } + // we will always be left with the last holder's data, generate the virtual op for it now. + if (last_holder_account_id) + { + // we've moved on to a new account, generate the dividend payment virtual op for the previous one + db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id, + *last_holder_account_id, + payouts_for_this_holder)); + ilog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name)); + } // now debit the total amount of dividends paid out from the distribution account auto distributed_balance_range = diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index 90becf9e..c6c5ded7 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -389,7 +389,8 @@ namespace graphene { namespace chain { */ typedef generic_index account_index; - struct by_dividend_asset_account_asset{}; + struct by_dividend_payout_account{}; + struct by_dividend_account_payout{}; /** * @ingroup object_index @@ -398,13 +399,21 @@ namespace graphene { namespace chain { pending_dividend_payout_balance_object, indexed_by< ordered_unique< tag, member< object, object_id_type, &object::id > >, - ordered_unique< tag, + ordered_unique< tag, composite_key< pending_dividend_payout_balance_object, member, member, member > + >, + ordered_unique< tag, + composite_key< + pending_dividend_payout_balance_object, + member, + member, + member + > > > > pending_dividend_payout_balance_object_multi_index_type; diff --git a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp index eab3ae38..db111cbe 100644 --- a/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp +++ b/libraries/chain/include/graphene/chain/protocol/asset_ops.hpp @@ -260,6 +260,43 @@ namespace graphene { namespace chain { { return 0; } }; + /** + * Virtual op generated when a dividend asset pays out dividends + */ + struct asset_dividend_distribution_operation : public base_operation + { + asset_dividend_distribution_operation() {} + asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id, + const account_id_type& account_id, + const flat_set& amounts) : + dividend_asset_id(dividend_asset_id), + account_id(account_id), + amounts(amounts) + {} + struct fee_parameters_type { }; + + asset fee; + + /// The dividend-paying asset which triggered this payout + asset_id_type dividend_asset_id; + + /// The user account receiving the dividends + account_id_type account_id; + + /// The amounts received + flat_set amounts; + + extensions_type extensions; + + account_id_type fee_payer()const { return account_id; } + void validate()const { + FC_ASSERT( false, "virtual operation" ); + } + + share_type calculate_fee(const fee_parameters_type& params)const + { return 0; } + }; + /** * @ingroup operations */ @@ -545,7 +582,7 @@ FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_paramete FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::asset_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) ) - +FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, ) FC_REFLECT( graphene::chain::asset_create_operation, (fee) @@ -593,3 +630,4 @@ FC_REFLECT( graphene::chain::asset_reserve_operation, (fee)(payer)(amount_to_reserve)(extensions) ) FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) ); +FC_REFLECT( graphene::chain::asset_dividend_distribution_operation, (fee)(dividend_asset_id)(account_id)(amounts)(extensions) ); diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 0c02ed75..244f44e0 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -92,7 +92,8 @@ namespace graphene { namespace chain { asset_settle_cancel_operation, // VIRTUAL asset_claim_fees_operation, fba_distribute_operation, // VIRTUAL - asset_update_dividend_operation + asset_update_dividend_operation, + asset_dividend_distribution_operation // VIRTUAL > operation; /// @} // operations group diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d52d5f58..91632b58 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -123,6 +124,7 @@ public: std::string operator()(const account_create_operation& op)const; std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; + std::string operator()(const asset_dividend_distribution_operation& op)const; }; template @@ -2639,9 +2641,7 @@ std::string operation_printer::operator()(const T& op)const operation_result_printer rprinter(wallet); std::string str_result = result.visit(rprinter); if( str_result != "" ) - { out << " result: " << str_result; - } return ""; } std::string operation_printer::operator()(const transfer_from_blind_operation& op)const @@ -2721,6 +2721,22 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons return fee(op.fee); } +std::string operation_printer::operator()(const asset_dividend_distribution_operation& op)const +{ + asset_object dividend_paying_asset = wallet.get_asset(op.dividend_asset_id); + account_object receiver = wallet.get_account(op.account_id); + + out << receiver.name << " received dividend payments for " << dividend_paying_asset.symbol << ": "; + std::vector pretty_payout_amounts; + for (const asset& payment : op.amounts) + { + asset_object payout_asset = wallet.get_asset(payment.asset); + pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payout_asset.amount)); + } + out << boost::algorithm::join(pretty_payout_amounts, ", "); + return ""; +} + std::string operation_result_printer::operator()(const void_result& x) const { return ""; diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 23e38dad..a69bfc30 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1042,13 +1042,9 @@ int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type divi { const pending_dividend_payout_balance_object_index& pending_payout_balance_index = db.get_index_type(); - dlog("searching ${a}", ("a", dividend_holder_asset_type(db).symbol)); - dlog("searching ${b}", ("b", dividend_payout_asset_type(db).symbol)); - dlog("searching ${c}", ("c", dividend_holder_account_id(db).name)); - dlog("searching ${a} ${b} ${c}", ("a", dividend_holder_asset_type(db).symbol)("b", dividend_payout_asset_type(db).symbol)("c", dividend_holder_account_id(db).name)); auto pending_payout_iter = - pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); - if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) + pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); + if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) return 0; else return pending_payout_iter->pending_balance.value; diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index fc9bdefa..cfd47f13 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -1143,7 +1144,7 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) 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.next_payout_time = db.head_block_time() + fc::minutes(1); op.new_options.payout_interval = 60 * 60 * 24 * 7; // one week trx.operations.push_back(op); @@ -1286,10 +1287,16 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) 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 - 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 + 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 + } }; fc::time_point_sec old_next_payout_scheduled_time = *dividend_data.options.next_payout_time; @@ -1302,14 +1309,33 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) 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(distribution_operation.amounts.find(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); + BOOST_TEST_MESSAGE("Removing the payout interval"); { asset_update_dividend_operation op;