From 5b437d73633a753a65d8c268cf43605d72e4eb2b Mon Sep 17 00:00:00 2001 From: Eric Frias Date: Thu, 30 Jun 2016 12:05:16 -0400 Subject: [PATCH] Cherry-picked commit b584ee1. Separate out unit tests for dividend-assets into their own test suite --- libraries/app/database_api.cpp | 5 + .../app/include/graphene/app/full_account.hpp | 3 + libraries/chain/db_maint.cpp | 104 +++--- .../include/graphene/chain/account_object.hpp | 13 +- tests/tests/operation_tests.cpp | 327 ++++++++++++------ 5 files changed, 308 insertions(+), 144 deletions(-) diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index e36842bf..a1c5f554 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -719,6 +720,10 @@ std::map database_api_impl::get_full_accounts( const acnt.withdraws.emplace_back(withdraw); }); + auto pending_payouts_range = + _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); + + std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments)); results[account_name_or_id] = acnt; } diff --git a/libraries/app/include/graphene/app/full_account.hpp b/libraries/app/include/graphene/app/full_account.hpp index dea5eb7e..b30495f9 100644 --- a/libraries/app/include/graphene/app/full_account.hpp +++ b/libraries/app/include/graphene/app/full_account.hpp @@ -48,6 +48,7 @@ namespace graphene { namespace app { vector proposals; vector assets; vector withdraws; + vector pending_dividend_payments; }; } } @@ -68,4 +69,6 @@ FC_REFLECT( graphene::app::full_account, (proposals) (assets) (withdraws) + (proposals) + (pending_dividend_payments) ) diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index d2e44327..93507b7c 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -716,6 +717,10 @@ void deprecate_annual_members( database& db ) return; } +// Schedules payouts from a dividend distribution account to the current holders of the +// dividend-paying asset. This takes any deposits made to the dividend distribution account +// since the last time it was called, and distributes them to the current owners of the +// dividend-paying asset according to the amount they own. void schedule_pending_dividend_balances(database& db, const asset_object& dividend_holder_asset_obj, const asset_dividend_data_object& dividend_data, @@ -723,7 +728,8 @@ void schedule_pending_dividend_balances(database& db, const distributed_dividend_balance_object_index& distributed_dividend_balance_index, const pending_dividend_payout_balance_object_index& pending_payout_balance_index) { - dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset}", ("holder_asset", dividend_holder_asset_obj.symbol)); + dlog("Processing dividend payments for dividend holder asset asset type ${holder_asset} at time ${t}", + ("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time())); auto current_distribution_account_balance_range = balance_index.indices().get().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account)); auto previous_distribution_account_balance_range = @@ -786,7 +792,8 @@ void schedule_pending_dividend_balances(database& db, share_type remaining_balance_of_dividend_asset = total_balance_of_dividend_asset; for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second)) - if (holder_balance_object.owner != dividend_data.dividend_distribution_account) + if (holder_balance_object.owner != dividend_data.dividend_distribution_account && + holder_balance_object.balance.value) { // TODO: if holder_balance_object.owner is able to accept payout_asset_type fc::uint128_t amount_to_credit(remaining_amount_to_distribute.value); @@ -902,6 +909,8 @@ void process_dividend_assets(database& db) if (dividend_holder_asset_obj.dividend_data_id) { const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(db); + const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db); + schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, balance_index, distributed_dividend_balance_index, pending_payout_balance_index); fc::time_point_sec current_head_block_time = db.head_block_time(); @@ -923,10 +932,7 @@ void process_dividend_assets(database& db) // and use this map to keep track of the total amount of each asset paid out. // Afterwards, we decrease the distribution account's balance by the total amount paid out, // and modify the distributed_balances accordingly -#ifndef NDEBUG - // 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)); @@ -935,6 +941,20 @@ void process_dividend_assets(database& db) // virtual op flat_set payouts_for_this_holder; fc::optional last_holder_account_id; + + // cache the assets the distribution account is approved to send, we will be asking + // for these often + flat_map approved_assets; // assets that the dividend distribution account is authorized to send/receive + auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) { + auto approved_assets_iter = approved_assets.find(asset_id); + if (approved_assets_iter != approved_assets.end()) + return approved_assets_iter->second; + bool is_approved = is_authorized_asset(db, dividend_distribution_account_object, + asset_id(db)); + approved_assets[asset_id] = is_approved; + return is_approved; + }; + 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; @@ -947,26 +967,31 @@ void process_dividend_assets(database& db) 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(); + last_holder_account_id.reset(); } - 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)); - 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 + if (is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) && + is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type)) + { + 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)); + + 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; + amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance; + + db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ + pending_balance.pending_balance = 0; + }); + } ++pending_balance_object_iter; - db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){ - pending_balance.pending_balance = 0; - }); } // we will always be left with the last holder's data, generate the virtual op for it now. if (last_holder_account_id) @@ -979,34 +1004,25 @@ void process_dividend_assets(database& db) } // now debit the total amount of dividends paid out from the distribution account - auto distributed_balance_range = - distributed_dividend_balance_index.indices().get().equal_range(boost::make_tuple(dividend_holder_asset_obj.id)); + // and reduce the distributed_balances accordingly -#ifndef NDEBUG - // validate that we actually paid out exactly as much as we had planned to - assert(amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)); - if (amounts_paid_out_by_asset.size() == std::distance(distributed_balance_range.first, distributed_balance_range.second)) + for (const auto& value : amounts_paid_out_by_asset) { - auto distributed_balance_object_iter = distributed_balance_range.first; - for (const auto& asset_and_amount_paid_out : amounts_paid_out_by_asset) - { - assert(distributed_balance_object_iter->dividend_payout_asset_type == asset_and_amount_paid_out.first); - assert(distributed_balance_object_iter->balance_at_last_maintenance_interval == asset_and_amount_paid_out.second); - ++distributed_balance_object_iter; - } - } -#endif + const asset_id_type& asset_paid_out = value.first; + const share_type& amount_paid_out = value.second; - for (auto distributed_balance_object_iter = distributed_balance_range.first; distributed_balance_object_iter != distributed_balance_range.second; ) - { - const distributed_dividend_balance_object& distributed_balance_object = *distributed_balance_object_iter; db.adjust_balance(dividend_data.dividend_distribution_account, - asset(-distributed_balance_object.balance_at_last_maintenance_interval, - distributed_balance_object.dividend_payout_asset_type)); - ++distributed_balance_object_iter; - db.modify(distributed_balance_object, [&]( distributed_dividend_balance_object& obj ){ - obj.balance_at_last_maintenance_interval = 0; // now they've been paid out, reset to zero - }); + asset(-amount_paid_out, + asset_paid_out)); + auto distributed_balance_iter = + distributed_dividend_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_obj.id, + asset_paid_out)); + assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()); + if (distributed_balance_iter != distributed_dividend_balance_index.indices().get().end()) + db.modify(*distributed_balance_iter, [&]( distributed_dividend_balance_object& obj ){ + obj.balance_at_last_maintenance_interval -= amount_paid_out; // now they've been paid out, reset to zero + }); + } // now schedule the next payout time diff --git a/libraries/chain/include/graphene/chain/account_object.hpp b/libraries/chain/include/graphene/chain/account_object.hpp index a90b93ba..4a20cef1 100644 --- a/libraries/chain/include/graphene/chain/account_object.hpp +++ b/libraries/chain/include/graphene/chain/account_object.hpp @@ -392,8 +392,9 @@ namespace graphene { namespace chain { */ typedef generic_index account_index; - struct by_dividend_payout_account{}; - struct by_dividend_account_payout{}; + struct by_dividend_payout_account{}; // use when calculating pending payouts + struct by_dividend_account_payout{}; // use when doing actual payouts + struct by_account_dividend_payout{}; // use in get_full_accounts() /** * @ingroup object_index @@ -417,6 +418,14 @@ namespace graphene { namespace chain { 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/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index cfd47f13..85ec439f 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -1112,6 +1112,8 @@ BOOST_AUTO_TEST_CASE( uia_fees ) } } +BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture ) + BOOST_AUTO_TEST_CASE( create_dividend_uia ) { using namespace graphene; @@ -1133,60 +1135,6 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) PUSH_TX( db, trx, ~0 ); trx.operations.clear(); } - generate_block(); - - // it 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 * 7; // one week - - trx.operations.push_back(op); - set_expiration(db, trx); - PUSH_TX( db, trx, ~0 ); - trx.operations.clear(); - } - generate_block(); - - //const auto& test_readback = get_asset("TEST"); - //BOOST_REQUIRE(test_readback.dividend_data_id); - 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 * 7); - } - - 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; // one day - 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); - } - - const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db); - BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution"); - BOOST_TEST_MESSAGE("Creating test accounts"); create_account("alice"); @@ -1194,12 +1142,6 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) create_account("carol"); create_account("dave"); create_account("frank"); - generate_block(); - 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"); BOOST_TEST_MESSAGE("Creating test asset"); { @@ -1220,7 +1162,90 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) } generate_block(); - // it should not yet be a divdend asset + // 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"); + + + } 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); + + 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); + } + } 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) @@ -1235,49 +1260,12 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) trx.operations.clear(); }; - // 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(); // get the maintenance skip slots out of the way - - 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 - - // for (const auto& pending_payout : db.get_index_type().indices()) - // dlog("In test, pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance)); - - dlog("Test asset object symbol is ${symbol}", ("symbol", test_asset_object.get_id()(db).symbol)); 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); }; - 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); auto advance_to_next_payout_time = [&]() { // Advance to the next upcoming payout time @@ -1299,6 +1287,45 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) } }; + // 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(); @@ -1333,8 +1360,111 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) 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( 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 pending payments were scheduled"); + verify_pending_balance(alice, test_asset_object, 1000); BOOST_TEST_MESSAGE("Removing the payout interval"); { @@ -1358,6 +1488,7 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia ) throw; } } +BOOST_AUTO_TEST_SUITE_END() // end dividend_tests suite BOOST_AUTO_TEST_CASE( cancel_limit_order_test ) { try {