Cherry-picked commit 7857ac4.

Correctly generating virtual transactions for payouts
This commit is contained in:
Eric Frias 2016-06-27 16:24:13 -04:00 committed by Roman Olearski
parent b9304caffa
commit b8e1165290
8 changed files with 141 additions and 21 deletions

View file

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

View file

@ -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<by_dividend_asset_account_asset>().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<by_dividend_asset_account_asset>().end())
pending_payout_balance_index.indices().get<by_dividend_payout_account>().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<by_dividend_payout_account>().end())
db.create<pending_dividend_payout_balance_object>( [&]( 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<by_dividend_asset_account_asset>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type));
pending_payout_balance_index.indices().get<by_dividend_payout_account>().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<asset_id_type, share_type> amounts_paid_out_by_asset;
#endif
auto pending_payouts_range =
pending_payout_balance_index.indices().get<by_dividend_asset_account_asset>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
pending_payout_balance_index.indices().get<by_dividend_account_payout>().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<asset> payouts_for_this_holder;
fc::optional<account_id_type> 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 =

View file

@ -392,7 +392,8 @@ namespace graphene { namespace chain {
*/
typedef generic_index<account_object, account_multi_index_type> account_index;
struct by_dividend_asset_account_asset{};
struct by_dividend_payout_account{};
struct by_dividend_account_payout{};
/**
* @ingroup object_index
@ -401,13 +402,21 @@ namespace graphene { namespace chain {
pending_dividend_payout_balance_object,
indexed_by<
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_dividend_asset_account_asset>,
ordered_unique< tag<by_dividend_payout_account>,
composite_key<
pending_dividend_payout_balance_object,
member<pending_dividend_payout_balance_object, asset_id_type, &pending_dividend_payout_balance_object::dividend_holder_asset_type>,
member<pending_dividend_payout_balance_object, asset_id_type, &pending_dividend_payout_balance_object::dividend_payout_asset_type>,
member<pending_dividend_payout_balance_object, account_id_type, &pending_dividend_payout_balance_object::owner>
>
>,
ordered_unique< tag<by_dividend_account_payout>,
composite_key<
pending_dividend_payout_balance_object,
member<pending_dividend_payout_balance_object, asset_id_type, &pending_dividend_payout_balance_object::dividend_holder_asset_type>,
member<pending_dividend_payout_balance_object, account_id_type, &pending_dividend_payout_balance_object::owner>,
member<pending_dividend_payout_balance_object, asset_id_type, &pending_dividend_payout_balance_object::dividend_payout_asset_type>
>
>
>
> pending_dividend_payout_balance_object_multi_index_type;

View file

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

View file

@ -109,7 +109,8 @@ namespace graphene { namespace chain {
bet_matched_operation, // VIRTUAL
bet_cancel_operation,
bet_canceled_operation, // VIRTUAL
asset_update_dividend_operation
asset_update_dividend_operation,
asset_dividend_distribution_operation // VIRTUAL
> operation;
/// @} // operations group

View file

@ -33,6 +33,7 @@
#include <boost/version.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
@ -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<class T>
@ -2656,9 +2658,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
@ -2738,6 +2738,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<std::string> 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 "";

View file

@ -1069,13 +1069,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<pending_dividend_payout_balance_object_index>();
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<by_dividend_asset_account_asset>().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<by_dividend_asset_account_asset>().end())
pending_payout_balance_index.indices().get<by_dividend_payout_account>().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<by_dividend_payout_account>().end())
return 0;
else
return pending_payout_iter->pending_balance.value;

View file

@ -35,6 +35,7 @@
#include <graphene/chain/vesting_balance_object.hpp>
#include <graphene/chain/withdraw_permission_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/account_history/account_history_plugin.hpp>
#include <fc/crypto/digest.hpp>
@ -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<account_transaction_history_index>();
auto account_history_range = hist_idx.indices().get<by_seq>().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<asset_dividend_distribution_operation>();
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;