Fixes to paying out non-core assets using their fee pools
This commit is contained in:
parent
f67dd3ea58
commit
67d0898394
7 changed files with 415 additions and 143 deletions
|
|
@ -422,6 +422,11 @@ void_result asset_update_dividend_evaluator::do_apply( const asset_update_divide
|
|||
const asset_dividend_data_object& dividend_data = asset_to_update->dividend_data(d);
|
||||
d.modify(dividend_data, [&]( asset_dividend_data_object& dividend_data_obj ) {
|
||||
dividend_data_obj.options = op.new_options;
|
||||
// whenever new options are set, clear out the scheduled payout/distribution times
|
||||
// this will reset and cause the next distribution to happen at the next maintenance
|
||||
// interval and a payout at the next_payout_time
|
||||
dividend_data_obj.last_scheduled_payout_time.reset();
|
||||
dividend_data_obj.last_scheduled_distribution_time.reset();
|
||||
});
|
||||
}
|
||||
return void_result();
|
||||
|
|
|
|||
|
|
@ -724,11 +724,12 @@ void deprecate_annual_members( database& db )
|
|||
void schedule_pending_dividend_balances(database& db,
|
||||
const asset_object& dividend_holder_asset_obj,
|
||||
const asset_dividend_data_object& dividend_data,
|
||||
const fc::time_point_sec& current_head_block_time,
|
||||
const account_balance_index& balance_index,
|
||||
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} at time ${t}",
|
||||
dlog("Processing dividend payments for dividend holder 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<by_account_asset>().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||
|
|
@ -737,148 +738,257 @@ void schedule_pending_dividend_balances(database& db,
|
|||
// the current range is now all current balances for the distribution account, sorted by asset_type
|
||||
// the previous range is now all previous balances for this account, sorted by asset type
|
||||
|
||||
const auto& gpo = db.get_global_properties();
|
||||
|
||||
// get the list of accounts that hold nonzero balances of the dividend asset
|
||||
auto holder_balances_begin =
|
||||
balance_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
auto holder_balances_end =
|
||||
balance_index.indices().get<by_asset_balance>().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type()));
|
||||
uint32_t holder_account_count = std::distance(holder_balances_begin, holder_balances_end);
|
||||
uint64_t distribution_base_fee = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_base_fee;
|
||||
uint32_t distribution_fee_per_holder = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder;
|
||||
// the fee, in BTS, for distributing each asset in the account
|
||||
uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder;
|
||||
|
||||
auto current_distribution_account_balance_iter = current_distribution_account_balance_range.first;
|
||||
auto previous_distribution_account_balance_iter = previous_distribution_account_balance_range.first;
|
||||
dlog("Current balances in distribution account: ${current}, Previous balances: ${previous}",
|
||||
("current", std::distance(current_distribution_account_balance_range.first, current_distribution_account_balance_range.second))
|
||||
("previous", std::distance(previous_distribution_account_balance_range.first, previous_distribution_account_balance_range.second)));
|
||||
|
||||
// loop through all of the assets currently or previously held in the distribution account
|
||||
while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second)
|
||||
{
|
||||
share_type current_balance;
|
||||
share_type previous_balance;
|
||||
asset_id_type payout_asset_type;
|
||||
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type)
|
||||
try
|
||||
{
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
idump((payout_asset_type)(current_balance));
|
||||
}
|
||||
else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type)
|
||||
{
|
||||
payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(previous_balance));
|
||||
}
|
||||
else
|
||||
{
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(current_balance)(previous_balance));
|
||||
}
|
||||
// First, figure out how much the balance on this asset has changed since the last sharing out
|
||||
share_type current_balance;
|
||||
share_type previous_balance;
|
||||
asset_id_type payout_asset_type;
|
||||
|
||||
share_type delta_balance = current_balance - previous_balance;
|
||||
dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance));
|
||||
if (delta_balance > 0)
|
||||
{
|
||||
// we need to pay out delta_balance to shareholders proportional to their stake
|
||||
auto holder_account_balance_range =
|
||||
balance_index.indices().get<by_asset_balance>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||
share_type 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)
|
||||
// TODO: if holder_balance_object.owner is able to accept payout_asset_type
|
||||
total_balance_of_dividend_asset += holder_balance_object.balance;
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
current_distribution_account_balance_iter->asset_type < previous_distribution_account_balance_iter->dividend_payout_asset_type)
|
||||
{
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
idump((payout_asset_type)(current_balance));
|
||||
}
|
||||
else if (current_distribution_account_balance_iter == current_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type < current_distribution_account_balance_iter->asset_type)
|
||||
{
|
||||
payout_asset_type = previous_distribution_account_balance_iter->dividend_payout_asset_type;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(previous_balance));
|
||||
}
|
||||
else
|
||||
{
|
||||
payout_asset_type = current_distribution_account_balance_iter->asset_type;
|
||||
current_balance = current_distribution_account_balance_iter->balance;
|
||||
previous_balance = previous_distribution_account_balance_iter->balance_at_last_maintenance_interval;
|
||||
idump((payout_asset_type)(current_balance)(previous_balance));
|
||||
}
|
||||
|
||||
dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}",
|
||||
("count", std::distance(holder_account_balance_range.first, holder_account_balance_range.second))
|
||||
("total", total_balance_of_dividend_asset));
|
||||
share_type remaining_amount_to_distribute = delta_balance;
|
||||
share_type remaining_balance_of_dividend_asset = total_balance_of_dividend_asset;
|
||||
share_type delta_balance = current_balance - previous_balance;
|
||||
|
||||
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 &&
|
||||
holder_balance_object.balance.value)
|
||||
// Next, figure out if we want to share this out -- if the amount added to the distribution
|
||||
// account since last payout is too small, we won't bother.
|
||||
|
||||
share_type total_fee_per_asset_in_payout_asset;
|
||||
const asset_object* payout_asset_object = nullptr;
|
||||
if (payout_asset_type == asset_id_type())
|
||||
{
|
||||
payout_asset_object = &db.get_core_asset();
|
||||
total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core;
|
||||
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||
("payout_asset_type", asset_id_type()(db).symbol)
|
||||
("fee", asset(total_fee_per_asset_in_core, asset_id_type())));
|
||||
}
|
||||
else
|
||||
{
|
||||
// figure out what the total fee is in terms of the payout asset
|
||||
const asset_index& asset_object_index = db.get_index_type<asset_index>();
|
||||
auto payout_asset_object_iter = asset_object_index.indices().find(payout_asset_type);
|
||||
FC_ASSERT(payout_asset_object_iter != asset_object_index.indices().end());
|
||||
|
||||
payout_asset_object = &*payout_asset_object_iter;
|
||||
asset total_fee_per_asset = asset(total_fee_per_asset_in_core, asset_id_type()) * payout_asset_object->options.core_exchange_rate;
|
||||
FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type);
|
||||
|
||||
total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount;
|
||||
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||
("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset));
|
||||
}
|
||||
|
||||
share_type minimum_shares_to_distribute;
|
||||
if (dividend_data.options.minimum_fee_percentage)
|
||||
{
|
||||
fc::uint128_t minimum_amount_to_distribute = total_fee_per_asset_in_payout_asset.value;
|
||||
minimum_amount_to_distribute *= 100 * GRAPHENE_1_PERCENT;
|
||||
minimum_amount_to_distribute /= dividend_data.options.minimum_fee_percentage;
|
||||
wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options));
|
||||
minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64();
|
||||
}
|
||||
|
||||
dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance));
|
||||
if (delta_balance > 0)
|
||||
{
|
||||
if (delta_balance >= minimum_shares_to_distribute)
|
||||
{
|
||||
// TODO: if holder_balance_object.owner is able to accept payout_asset_type
|
||||
fc::uint128_t amount_to_credit(remaining_amount_to_distribute.value);
|
||||
amount_to_credit *= holder_balance_object.balance.value;
|
||||
amount_to_credit /= remaining_balance_of_dividend_asset.value;
|
||||
share_type shares_to_credit((int64_t)amount_to_credit.to_uint64());
|
||||
// first, pay the fee for scheduling these dividend payments
|
||||
if (payout_asset_type == asset_id_type())
|
||||
{
|
||||
// pay fee to network
|
||||
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||
});
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-total_fee_per_asset_in_core, asset_id_type()));
|
||||
delta_balance -= total_fee_per_asset_in_core;
|
||||
}
|
||||
else
|
||||
{
|
||||
const asset_dynamic_data_object& dynamic_data = payout_asset_object->dynamic_data(db);
|
||||
if (dynamic_data.fee_pool < total_fee_per_asset_in_core)
|
||||
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||
"because insufficient funds in fee pool (need: ${need}, have: ${have})",
|
||||
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||
("payout_asset_type", payout_asset_object->symbol)
|
||||
("need", asset(total_fee_per_asset_in_core, asset_id_type()))
|
||||
("have", asset(dynamic_data.fee_pool, payout_asset_type)));
|
||||
// deduct the fee from the dividend distribution account
|
||||
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||
asset(-total_fee_per_asset_in_payout_asset, payout_asset_type));
|
||||
// convert it to core
|
||||
db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) {
|
||||
d.fee_pool -= total_fee_per_asset_in_core;
|
||||
d.accumulated_fees += total_fee_per_asset_in_payout_asset;
|
||||
});
|
||||
// and pay it to the network
|
||||
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||
});
|
||||
delta_balance -= total_fee_per_asset_in_payout_asset;
|
||||
}
|
||||
|
||||
remaining_amount_to_distribute -= shares_to_credit;
|
||||
remaining_balance_of_dividend_asset -= holder_balance_object.balance;
|
||||
// we need to pay out the remaining delta_balance to shareholders proportional to their stake
|
||||
// so find out what the total stake
|
||||
share_type total_balance_of_dividend_asset;
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||
if (holder_balance_object.owner != dividend_data.dividend_distribution_account)
|
||||
total_balance_of_dividend_asset += holder_balance_object.balance;
|
||||
|
||||
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_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;
|
||||
dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}",
|
||||
("count", holder_account_count)
|
||||
("total", total_balance_of_dividend_asset));
|
||||
share_type remaining_amount_to_distribute = delta_balance;
|
||||
|
||||
// credit each account with their portion
|
||||
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||
if (holder_balance_object.owner != dividend_data.dividend_distribution_account &&
|
||||
holder_balance_object.balance.value)
|
||||
{
|
||||
fc::uint128_t amount_to_credit(delta_balance.value);
|
||||
amount_to_credit *= holder_balance_object.balance.value;
|
||||
amount_to_credit /= total_balance_of_dividend_asset.value;
|
||||
wdump((delta_balance.value)(holder_balance_object.balance)(total_balance_of_dividend_asset));
|
||||
share_type shares_to_credit((int64_t)amount_to_credit.to_uint64());
|
||||
|
||||
remaining_amount_to_distribute -= shares_to_credit;
|
||||
|
||||
dlog("Crediting account ${account} with ${amount}",
|
||||
("account", holder_balance_object.owner(db).name)
|
||||
("amount", asset(shares_to_credit, payout_asset_type)));
|
||||
auto pending_payout_iter =
|
||||
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;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.pending_balance = shares_to_credit;
|
||||
});
|
||||
else
|
||||
db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){
|
||||
pending_balance.pending_balance += shares_to_credit;
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& pending_payout : pending_payout_balance_index.indices())
|
||||
dlog("Pending payout: ${account_name} -> ${amount}",
|
||||
("account_name", pending_payout.owner(db).name)
|
||||
("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type)));
|
||||
dlog("Remaining balance not paid out: ${amount}",
|
||||
("amount", asset(remaining_amount_to_distribute, payout_asset_type)));
|
||||
|
||||
|
||||
share_type distributed_amount = delta_balance - remaining_amount_to_distribute;
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||
db.create<distributed_dividend_balance_object>( [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.pending_balance = shares_to_credit;
|
||||
obj.balance_at_last_maintenance_interval = distributed_amount;
|
||||
});
|
||||
else
|
||||
db.modify(*pending_payout_iter, [&]( pending_dividend_payout_balance_object& pending_balance ){
|
||||
pending_balance.pending_balance += shares_to_credit;
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval += distributed_amount;
|
||||
});
|
||||
}
|
||||
|
||||
for (const auto& pending_payout : pending_payout_balance_index.indices())
|
||||
{
|
||||
dlog("Pending payout: ${account_name} -> ${amount}", ("account_name", pending_payout.owner(db).name)("amount", pending_payout.pending_balance));
|
||||
else
|
||||
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||
"because amount ${delta_balance} is too small an amount to distribute.",
|
||||
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||
("payout_asset_type", payout_asset_object->symbol)
|
||||
("delta_balance", asset(delta_balance, payout_asset_type)));
|
||||
}
|
||||
else if (delta_balance < 0)
|
||||
{
|
||||
// some amount of the asset has been withdrawn from the dividend_distribution_account,
|
||||
// meaning the current pending payout balances will add up to more than our current balance.
|
||||
// This should be extremely rare.
|
||||
// Reduce all pending payouts proportionally
|
||||
share_type total_pending_balances;
|
||||
auto pending_payouts_range =
|
||||
pending_payout_balance_index.indices().get<by_dividend_payout_account>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type));
|
||||
|
||||
share_type distributed_amount = current_balance - remaining_amount_to_distribute;
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||
db.create<distributed_dividend_balance_object>( [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.balance_at_last_maintenance_interval = distributed_amount;
|
||||
});
|
||||
else
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval = distributed_amount;
|
||||
});
|
||||
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;
|
||||
|
||||
share_type remaining_amount_to_recover = -delta_balance;
|
||||
share_type remaining_pending_balances = total_pending_balances;
|
||||
for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second))
|
||||
{
|
||||
fc::uint128_t amount_to_debit(remaining_amount_to_recover.value);
|
||||
amount_to_debit *= pending_balance_object.pending_balance.value;
|
||||
amount_to_debit /= remaining_pending_balances.value;
|
||||
share_type shares_to_debit((int64_t)amount_to_debit.to_uint64());
|
||||
|
||||
remaining_amount_to_recover -= shares_to_debit;
|
||||
remaining_pending_balances -= pending_balance_object.pending_balance;
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){
|
||||
pending_balance.pending_balance -= shares_to_debit;
|
||||
});
|
||||
}
|
||||
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||
db.create<distributed_dividend_balance_object>( [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.balance_at_last_maintenance_interval = 0;
|
||||
});
|
||||
else
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval = 0;
|
||||
});
|
||||
} // end if deposit was large enough to distribute
|
||||
}
|
||||
else if (delta_balance < 0)
|
||||
catch (const fc::exception& e)
|
||||
{
|
||||
// some amount of the asset has been withdrawn from the dividend_distribution_account,
|
||||
// meaning the current pending payout balances will add up to more than our current balance.
|
||||
// This should be extremely rare.
|
||||
// Reduce all pending payouts proportionally
|
||||
share_type total_pending_balances;
|
||||
auto pending_payouts_range =
|
||||
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;
|
||||
|
||||
share_type remaining_amount_to_recover = -delta_balance;
|
||||
share_type remaining_pending_balances = total_pending_balances;
|
||||
for (const pending_dividend_payout_balance_object& pending_balance_object : boost::make_iterator_range(pending_payouts_range.first, pending_payouts_range.second))
|
||||
{
|
||||
fc::uint128_t amount_to_debit(remaining_amount_to_recover.value);
|
||||
amount_to_debit *= pending_balance_object.pending_balance.value;
|
||||
amount_to_debit /= remaining_pending_balances.value;
|
||||
share_type shares_to_debit((int64_t)amount_to_debit.to_uint64());
|
||||
|
||||
remaining_amount_to_recover -= shares_to_debit;
|
||||
remaining_pending_balances -= pending_balance_object.pending_balance;
|
||||
|
||||
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_object& pending_balance ){
|
||||
pending_balance.pending_balance -= shares_to_debit;
|
||||
});
|
||||
}
|
||||
|
||||
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||
db.create<distributed_dividend_balance_object>( [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.dividend_holder_asset_type = dividend_holder_asset_obj.id;
|
||||
obj.dividend_payout_asset_type = payout_asset_type;
|
||||
obj.balance_at_last_maintenance_interval = 0;
|
||||
});
|
||||
else
|
||||
db.modify(*previous_distribution_account_balance_iter, [&]( distributed_dividend_balance_object& obj ){
|
||||
obj.balance_at_last_maintenance_interval = 0;
|
||||
});
|
||||
dlog("${e}", ("e", e));
|
||||
}
|
||||
|
||||
// iterate
|
||||
|
|
@ -894,6 +1004,11 @@ void schedule_pending_dividend_balances(database& db,
|
|||
++previous_distribution_account_balance_iter;
|
||||
}
|
||||
}
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dividend_data_obj.last_scheduled_distribution_time = current_head_block_time;
|
||||
dividend_data_obj.last_distribution_time = current_head_block_time;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
void process_dividend_assets(database& db)
|
||||
|
|
@ -911,9 +1026,10 @@ void process_dividend_assets(database& db)
|
|||
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();
|
||||
|
||||
schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, current_head_block_time,
|
||||
balance_index, distributed_dividend_balance_index, pending_payout_balance_index);
|
||||
if (dividend_data.options.next_payout_time &&
|
||||
db.head_block_time() >= *dividend_data.options.next_payout_time)
|
||||
{
|
||||
|
|
@ -965,7 +1081,7 @@ void process_dividend_assets(database& db)
|
|||
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));
|
||||
dlog("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();
|
||||
}
|
||||
|
|
@ -974,7 +1090,7 @@ void process_dividend_assets(database& db)
|
|||
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}",
|
||||
dlog("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));
|
||||
|
||||
|
|
@ -1000,7 +1116,7 @@ void process_dividend_assets(database& db)
|
|||
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));
|
||||
dlog("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
|
||||
|
|
@ -1027,7 +1143,6 @@ void process_dividend_assets(database& db)
|
|||
|
||||
// now schedule the next payout time
|
||||
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||
dlog("Updating dividend payout time, new values are:");
|
||||
dividend_data_obj.last_scheduled_payout_time = dividend_data_obj.options.next_payout_time;
|
||||
dividend_data_obj.last_payout_time = current_head_block_time;
|
||||
fc::optional<fc::time_point_sec> next_payout_time;
|
||||
|
|
@ -1044,7 +1159,9 @@ void process_dividend_assets(database& db)
|
|||
next_payout_time = next_possible_time_sec;
|
||||
}
|
||||
dividend_data_obj.options.next_payout_time = next_payout_time;
|
||||
idump((dividend_data_obj.last_scheduled_payout_time)(dividend_data_obj.last_payout_time)(dividend_data_obj.options.next_payout_time));
|
||||
idump((dividend_data_obj.last_scheduled_payout_time)
|
||||
(dividend_data_obj.last_payout_time)
|
||||
(dividend_data_obj.options.next_payout_time));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -272,12 +272,24 @@ namespace graphene { namespace chain {
|
|||
dividend_asset_options options;
|
||||
|
||||
/// The time payouts on this asset were scheduled to be processed last
|
||||
/// This field is reset any time the dividend_asset_options are updated
|
||||
fc::optional<time_point_sec> last_scheduled_payout_time;
|
||||
|
||||
/// The time payouts on this asset were last processed
|
||||
/// (this should be the maintenance interval at or after last_scheduled_payout_time)
|
||||
/// This can be displayed for the user
|
||||
fc::optional<time_point_sec> last_payout_time;
|
||||
|
||||
/// The time pending payouts on this asset were last computed, used for
|
||||
/// correctly computing the next pending payout time.
|
||||
/// This field is reset any time the dividend_asset_options are updated
|
||||
fc::optional<time_point_sec> last_scheduled_distribution_time;
|
||||
|
||||
/// The time pending payouts on this asset were last computed.
|
||||
/// (this should be the maintenance interval at or after last_scheduled_distribution_time)
|
||||
/// This can be displayed for the user
|
||||
fc::optional<time_point_sec> last_distribution_time;
|
||||
|
||||
/// The account which collects pending payouts
|
||||
account_id_type dividend_distribution_account;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -128,6 +128,29 @@ namespace graphene { namespace chain {
|
|||
/// If payout_interval is not set, the next payout (if any) will be the last until
|
||||
/// the options are updated again.
|
||||
fc::optional<uint32_t> payout_interval;
|
||||
/// Each dividend distribution incurs a fee that is based on the number of accounts
|
||||
/// that hold the dividend asset, not as a percentage of the amount paid out.
|
||||
/// This parameter prevents assets from being distributed unless the fee is less than
|
||||
/// the percentage here, to prevent a slow trickle of deposits to the account from being
|
||||
/// completely consumed.
|
||||
/// In other words, if you set this parameter to 10% and the fees work out to 100 BTS
|
||||
/// to share out, balances in the dividend distribution accounts will not be shared out
|
||||
/// if the balance is less than 10000 BTS.
|
||||
uint64_t minimum_fee_percentage;
|
||||
|
||||
/// Normally, pending dividend payments are calculated each maintenance interval in
|
||||
/// which there are balances in the dividend distribution account. At present, this
|
||||
/// is once per hour on the BitShares blockchain. If this is too often (too expensive
|
||||
/// in fees or to computationally-intensive for the blockchain) this can be increased.
|
||||
/// If you set this to, for example, one day, distributions will take place on even
|
||||
/// multiples of one day, allowing deposits to the distribution account to accumulate
|
||||
/// for 23 maintenance intervals and then computing the pending payouts on the 24th.
|
||||
///
|
||||
/// Payouts will always occur at the next payout time whether or not it falls on a
|
||||
/// multiple of the distribution interval, and the timer on the distribution interval
|
||||
/// are reset at payout time. So if you have the distribution interval at three days
|
||||
/// and the payout interval at one week, payouts will occur at days 3, 6, 7, 10, 13, 14...
|
||||
fc::optional<uint32_t> minimum_distribution_interval;
|
||||
|
||||
extensions_type extensions;
|
||||
|
||||
|
|
@ -273,7 +296,23 @@ namespace graphene { namespace chain {
|
|||
account_id(account_id),
|
||||
amounts(amounts)
|
||||
{}
|
||||
struct fee_parameters_type { };
|
||||
struct fee_parameters_type {
|
||||
/* note: this is a virtual op and there are no fees directly charged for it */
|
||||
|
||||
/* Whenever the system computes the pending dividend payments for an asset,
|
||||
* it charges the distribution_base_fee + distribution_fee_per_holder.
|
||||
* The computational cost of distributing the dividend payment is proportional
|
||||
* to the number of dividend holders the asset is divided up among.
|
||||
*/
|
||||
/** This fee is charged whenever the system schedules pending dividend
|
||||
* payments.
|
||||
*/
|
||||
uint64_t distribution_base_fee;
|
||||
/** This fee is charged (in addition to the distribution_base_fee) for each
|
||||
* user the dividend payment is shared out amongst
|
||||
*/
|
||||
uint32_t distribution_fee_per_holder;
|
||||
};
|
||||
|
||||
asset fee;
|
||||
|
||||
|
|
@ -582,7 +621,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_dividend_distribution_operation::fee_parameters_type, (distribution_base_fee)(distribution_fee_per_holder))
|
||||
|
||||
FC_REFLECT( graphene::chain::asset_create_operation,
|
||||
(fee)
|
||||
|
|
|
|||
|
|
@ -1053,6 +1053,21 @@ class wallet_api
|
|||
bitasset_options new_options,
|
||||
bool broadcast = false);
|
||||
|
||||
|
||||
/** Update the given asset's dividend asset options.
|
||||
*
|
||||
* If the asset is not already a dividend-paying asset, it will be converted into one.
|
||||
*
|
||||
* @param symbol the name or id of the asset to update, which must be a market-issued asset
|
||||
* @param new_options the new dividend_asset_options object, which will entirely replace the existing
|
||||
* options.
|
||||
* @param broadcast true to broadcast the transaction on the network
|
||||
* @returns the signed transaction updating the asset
|
||||
*/
|
||||
signed_transaction update_dividend_asset(string symbol,
|
||||
dividend_asset_options new_options,
|
||||
bool broadcast = false);
|
||||
|
||||
/** Update the set of feed-producing accounts for a BitAsset.
|
||||
*
|
||||
* BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers.
|
||||
|
|
@ -1697,6 +1712,7 @@ FC_API( graphene::wallet::wallet_api,
|
|||
(create_asset)
|
||||
(update_asset)
|
||||
(update_bitasset)
|
||||
(update_dividend_asset)
|
||||
(update_asset_feed_producers)
|
||||
(publish_asset_feed)
|
||||
(issue_asset)
|
||||
|
|
|
|||
|
|
@ -3269,6 +3269,14 @@ signed_transaction wallet_api::update_bitasset(string symbol,
|
|||
return my->update_bitasset(symbol, new_options, broadcast);
|
||||
}
|
||||
|
||||
signed_transaction wallet_api::update_dividend_asset(string symbol,
|
||||
dividend_asset_options new_options,
|
||||
bool broadcast /* = false */)
|
||||
{
|
||||
return my->update_dividend_asset(symbol, new_options, broadcast);
|
||||
}
|
||||
|
||||
|
||||
signed_transaction wallet_api::update_asset_feed_producers(string symbol,
|
||||
flat_set<string> new_feed_producers,
|
||||
bool broadcast /* = false */)
|
||||
|
|
|
|||
|
|
@ -1162,6 +1162,18 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia )
|
|||
}
|
||||
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);
|
||||
|
|
@ -1192,6 +1204,12 @@ BOOST_AUTO_TEST_CASE( create_dividend_uia )
|
|||
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<asset_dividend_distribution_operation>().distribution_base_fee = 100;
|
||||
// _gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder = 100;
|
||||
// } );
|
||||
|
||||
|
||||
} catch(fc::exception& e) {
|
||||
edump((e.to_detail_string()));
|
||||
|
|
@ -1208,6 +1226,26 @@ BOOST_AUTO_TEST_CASE( test_update_dividend_interval )
|
|||
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;
|
||||
|
|
@ -1227,6 +1265,23 @@ BOOST_AUTO_TEST_CASE( test_update_dividend_interval )
|
|||
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<uint32_t>();
|
||||
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;
|
||||
|
|
@ -1365,6 +1420,28 @@ BOOST_AUTO_TEST_CASE( test_basic_dividend_distribution )
|
|||
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;
|
||||
|
|
@ -1463,26 +1540,24 @@ BOOST_AUTO_TEST_CASE( check_dividend_corner_cases )
|
|||
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");
|
||||
BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount");
|
||||
verify_pending_balance(alice, test_asset_object, 1000);
|
||||
|
||||
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<uint32_t>();
|
||||
trx.operations.push_back(op);
|
||||
set_expiration(db, trx);
|
||||
PUSH_TX( db, trx, ~0 );
|
||||
trx.operations.clear();
|
||||
}
|
||||
// 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(!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");
|
||||
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue