Correctly generating virtual transactions for payouts
This commit is contained in:
parent
abc7853c99
commit
7857ac48a4
8 changed files with 141 additions and 21 deletions
|
|
@ -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 )
|
||||
|
|
|
|||
|
|
@ -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 =
|
||||
|
|
|
|||
|
|
@ -389,7 +389,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
|
||||
|
|
@ -398,13 +399,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;
|
||||
|
|
|
|||
|
|
@ -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) );
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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<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 "";
|
||||
|
|
|
|||
|
|
@ -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<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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue