Initial work on dividend-paying assets. Basic functionality works in simple
cases.
This commit is contained in:
parent
006d548633
commit
abc7853c99
15 changed files with 863 additions and 4 deletions
|
|
@ -90,6 +90,7 @@ struct get_impacted_account_visitor
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()( const asset_update_bitasset_operation& op ) {}
|
void operator()( const asset_update_bitasset_operation& op ) {}
|
||||||
|
void operator()( const asset_update_dividend_operation& op ) {}
|
||||||
void operator()( const asset_update_feed_producers_operation& op ) {}
|
void operator()( const asset_update_feed_producers_operation& op ) {}
|
||||||
|
|
||||||
void operator()( const asset_issue_operation& op )
|
void operator()( const asset_issue_operation& op )
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
|
#include <boost/algorithm/string/case_conv.hpp>
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op )
|
void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op )
|
||||||
|
|
@ -355,6 +357,65 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||||
|
|
||||||
|
void_result asset_update_dividend_evaluator::do_evaluate(const asset_update_dividend_operation& o)
|
||||||
|
{ try {
|
||||||
|
database& d = db();
|
||||||
|
|
||||||
|
const asset_object& a = o.asset_to_update(d);
|
||||||
|
asset_to_update = &a;
|
||||||
|
|
||||||
|
FC_ASSERT( o.issuer == a.issuer, "", ("o.issuer", o.issuer)("a.issuer", a.issuer) );
|
||||||
|
auto& params = db().get_global_properties().parameters;
|
||||||
|
if (o.new_options.payout_interval &&
|
||||||
|
*o.new_options.payout_interval < params.maintenance_interval)
|
||||||
|
FC_THROW("New payout interval may not be less than the maintenance interval",
|
||||||
|
("new_payout_interval", o.new_options.payout_interval)("maintenance_interval", params.maintenance_interval));
|
||||||
|
return void_result();
|
||||||
|
} FC_CAPTURE_AND_RETHROW( (o) ) }
|
||||||
|
|
||||||
|
void_result asset_update_dividend_evaluator::do_apply( const asset_update_dividend_operation& op )
|
||||||
|
{ try {
|
||||||
|
database& d = db();
|
||||||
|
if (!asset_to_update->dividend_data_id)
|
||||||
|
{
|
||||||
|
// this was not a dividend-paying asset, we're converting it to a dividend-paying asset
|
||||||
|
std::string dividend_distribution_account_name(boost::to_lower_copy(asset_to_update->symbol) + "-dividend-distribution");
|
||||||
|
|
||||||
|
const auto& new_acnt_object = db().create<account_object>( [&]( account_object& obj ){
|
||||||
|
obj.registrar = op.issuer;
|
||||||
|
obj.referrer = op.issuer;
|
||||||
|
obj.lifetime_referrer = op.issuer(db()).lifetime_referrer;
|
||||||
|
|
||||||
|
auto& params = db().get_global_properties().parameters;
|
||||||
|
obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE;
|
||||||
|
obj.lifetime_referrer_fee_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE;
|
||||||
|
obj.referrer_rewards_percentage = GRAPHENE_DEFAULT_LIFETIME_REFERRER_PERCENT_OF_FEE;
|
||||||
|
|
||||||
|
obj.name = dividend_distribution_account_name;
|
||||||
|
obj.owner.weight_threshold = 1;
|
||||||
|
obj.active.weight_threshold = 1;
|
||||||
|
obj.statistics = db().create<account_statistics_object>([&](account_statistics_object& s){s.owner = obj.id;}).id;
|
||||||
|
});
|
||||||
|
|
||||||
|
const asset_dividend_data_object& dividend_data = d.create<asset_dividend_data_object>( [&]( asset_dividend_data_object& dividend_data_obj ) {
|
||||||
|
dividend_data_obj.options = op.new_options;
|
||||||
|
dividend_data_obj.dividend_distribution_account = new_acnt_object.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
d.modify(*asset_to_update, [&](asset_object& a) {
|
||||||
|
a.dividend_data_id = dividend_data.id;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return void_result();
|
||||||
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o)
|
void_result asset_update_feed_producers_evaluator::do_evaluate(const asset_update_feed_producers_evaluator::operation_type& o)
|
||||||
{ try {
|
{ try {
|
||||||
database& d = db();
|
database& d = db();
|
||||||
|
|
|
||||||
|
|
@ -143,6 +143,7 @@ void database::initialize_evaluators()
|
||||||
register_evaluator<asset_reserve_evaluator>();
|
register_evaluator<asset_reserve_evaluator>();
|
||||||
register_evaluator<asset_update_evaluator>();
|
register_evaluator<asset_update_evaluator>();
|
||||||
register_evaluator<asset_update_bitasset_evaluator>();
|
register_evaluator<asset_update_bitasset_evaluator>();
|
||||||
|
register_evaluator<asset_update_dividend_evaluator>();
|
||||||
register_evaluator<asset_update_feed_producers_evaluator>();
|
register_evaluator<asset_update_feed_producers_evaluator>();
|
||||||
register_evaluator<asset_settle_evaluator>();
|
register_evaluator<asset_settle_evaluator>();
|
||||||
register_evaluator<asset_global_settle_evaluator>();
|
register_evaluator<asset_global_settle_evaluator>();
|
||||||
|
|
@ -204,6 +205,7 @@ void database::initialize_indexes()
|
||||||
add_index< primary_index<transaction_index > >();
|
add_index< primary_index<transaction_index > >();
|
||||||
add_index< primary_index<account_balance_index > >();
|
add_index< primary_index<account_balance_index > >();
|
||||||
add_index< primary_index<asset_bitasset_data_index > >();
|
add_index< primary_index<asset_bitasset_data_index > >();
|
||||||
|
add_index< primary_index<asset_dividend_data_object_index > >();
|
||||||
add_index< primary_index<simple_index<global_property_object >> >();
|
add_index< primary_index<simple_index<global_property_object >> >();
|
||||||
add_index< primary_index<simple_index<dynamic_global_property_object >> >();
|
add_index< primary_index<simple_index<dynamic_global_property_object >> >();
|
||||||
add_index< primary_index<simple_index<account_statistics_object >> >();
|
add_index< primary_index<simple_index<account_statistics_object >> >();
|
||||||
|
|
@ -216,6 +218,8 @@ void database::initialize_indexes()
|
||||||
add_index< primary_index< buyback_index > >();
|
add_index< primary_index< buyback_index > >();
|
||||||
|
|
||||||
add_index< primary_index< simple_index< fba_accumulator_object > > >();
|
add_index< primary_index< simple_index< fba_accumulator_object > > >();
|
||||||
|
add_index< primary_index<pending_dividend_payout_balance_object_index > >();
|
||||||
|
add_index< primary_index<distributed_dividend_balance_object_index > >();
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::init_genesis(const genesis_state_type& genesis_state)
|
void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
|
|
|
||||||
|
|
@ -716,6 +716,291 @@ void deprecate_annual_members( database& db )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void schedule_pending_dividend_balances(database& db,
|
||||||
|
const asset_object& dividend_holder_asset_obj,
|
||||||
|
const asset_dividend_data_object& dividend_data,
|
||||||
|
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}", ("holder_asset", dividend_holder_asset_obj.symbol));
|
||||||
|
auto current_distribution_account_balance_range =
|
||||||
|
balance_index.indices().get<by_account_asset>().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||||
|
auto previous_distribution_account_balance_range =
|
||||||
|
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||||
|
// 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
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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
|
||||||
|
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());
|
||||||
|
|
||||||
|
remaining_amount_to_distribute -= shares_to_credit;
|
||||||
|
remaining_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_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())
|
||||||
|
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", pending_payout.pending_balance));
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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_asset_account_asset>().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;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate
|
||||||
|
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)
|
||||||
|
++current_distribution_account_balance_iter;
|
||||||
|
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)
|
||||||
|
++previous_distribution_account_balance_iter;
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++current_distribution_account_balance_iter;
|
||||||
|
++previous_distribution_account_balance_iter;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_dividend_assets(database& db)
|
||||||
|
{
|
||||||
|
ilog("In process_dividend_assets time ${time}", ("time", db.head_block_time()));
|
||||||
|
|
||||||
|
const account_balance_index& balance_index = db.get_index_type<account_balance_index>();
|
||||||
|
const distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type<distributed_dividend_balance_object_index>();
|
||||||
|
const pending_dividend_payout_balance_object_index& pending_payout_balance_index = db.get_index_type<pending_dividend_payout_balance_object_index>();
|
||||||
|
|
||||||
|
// TODO: switch to iterating over only dividend assets (generalize the by_type index)
|
||||||
|
for( const asset_object& dividend_holder_asset_obj : db.get_index_type<asset_index>().indices() )
|
||||||
|
if (dividend_holder_asset_obj.dividend_data_id)
|
||||||
|
{
|
||||||
|
const asset_dividend_data_object& dividend_data = dividend_holder_asset_obj.dividend_data(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();
|
||||||
|
if (dividend_data.options.next_payout_time &&
|
||||||
|
db.head_block_time() >= *dividend_data.options.next_payout_time)
|
||||||
|
{
|
||||||
|
dlog("Dividend payout time has arrived for asset ${holder_asset}",
|
||||||
|
("holder_asset", dividend_holder_asset_obj.symbol));
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
|
// dump balances before the payouts for debugging
|
||||||
|
const auto& balance_idx = db.get_index_type<account_balance_index>().indices().get<by_account_asset>();
|
||||||
|
auto holder_account_balance_range = balance_idx.equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||||
|
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_account_balance_range.first, holder_account_balance_range.second))
|
||||||
|
ilog(" Current balance: ${asset}", ("asset", asset(holder_balance_object.balance, holder_balance_object.asset_type)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// when we do the payouts, we first increase the balances in all of the receiving accounts
|
||||||
|
// 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<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));
|
||||||
|
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;
|
||||||
|
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));
|
||||||
|
#ifndef NDEBUG
|
||||||
|
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
++pending_balance_object_iter;
|
||||||
|
db.remove(pending_balance_object);
|
||||||
|
}
|
||||||
|
|
||||||
|
// now debit the total amount of dividends paid out from the distribution account
|
||||||
|
auto distributed_balance_range =
|
||||||
|
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||||
|
|
||||||
|
#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))
|
||||||
|
{
|
||||||
|
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
|
||||||
|
|
||||||
|
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.remove(distributed_balance_object); // now they've been paid out, reset to zero
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
if (dividend_data_obj.options.payout_interval)
|
||||||
|
{
|
||||||
|
// if there was a previous payout, make our next payment one interval
|
||||||
|
uint32_t current_time_sec = current_head_block_time.sec_since_epoch();
|
||||||
|
fc::time_point_sec reference_time = *dividend_data_obj.last_scheduled_payout_time;
|
||||||
|
uint32_t next_possible_time_sec = dividend_data_obj.last_scheduled_payout_time->sec_since_epoch();
|
||||||
|
do
|
||||||
|
next_possible_time_sec += *dividend_data_obj.options.payout_interval;
|
||||||
|
while (next_possible_time_sec <= current_time_sec);
|
||||||
|
|
||||||
|
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));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props)
|
||||||
{
|
{
|
||||||
const auto& gpo = get_global_properties();
|
const auto& gpo = get_global_properties();
|
||||||
|
|
@ -723,6 +1008,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
distribute_fba_balances(*this);
|
distribute_fba_balances(*this);
|
||||||
create_buyback_orders(*this);
|
create_buyback_orders(*this);
|
||||||
|
|
||||||
|
process_dividend_assets(*this);
|
||||||
|
|
||||||
struct vote_tally_helper {
|
struct vote_tally_helper {
|
||||||
database& d;
|
database& d;
|
||||||
const global_property_object& props;
|
const global_property_object& props;
|
||||||
|
|
|
||||||
|
|
@ -308,6 +308,31 @@ namespace graphene { namespace chain {
|
||||||
/** maps the referrer to the set of accounts that they have referred */
|
/** maps the referrer to the set of accounts that they have referred */
|
||||||
map< account_id_type, set<account_id_type> > referred_by;
|
map< account_id_type, set<account_id_type> > referred_by;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Tracks a pending payout of a single dividend payout asset
|
||||||
|
* from a single dividend holder asset to a holder's account.
|
||||||
|
*
|
||||||
|
* Each maintenance interval, this will be adjusted to account for
|
||||||
|
* any new transfers to the dividend distribution account.
|
||||||
|
* @ingroup object
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
class pending_dividend_payout_balance_object : public abstract_object<pending_dividend_payout_balance_object>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const uint8_t space_id = implementation_ids;
|
||||||
|
static const uint8_t type_id = impl_pending_dividend_payout_balance_object_type;
|
||||||
|
|
||||||
|
account_id_type owner;
|
||||||
|
asset_id_type dividend_holder_asset_type;
|
||||||
|
asset_id_type dividend_payout_asset_type;
|
||||||
|
share_type pending_balance;
|
||||||
|
|
||||||
|
asset get_pending_balance()const { return asset(pending_balance, dividend_payout_asset_type); }
|
||||||
|
void adjust_balance(const asset& delta);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
struct by_account_asset;
|
struct by_account_asset;
|
||||||
struct by_asset_balance;
|
struct by_asset_balance;
|
||||||
|
|
@ -364,6 +389,31 @@ namespace graphene { namespace chain {
|
||||||
*/
|
*/
|
||||||
typedef generic_index<account_object, account_multi_index_type> account_index;
|
typedef generic_index<account_object, account_multi_index_type> account_index;
|
||||||
|
|
||||||
|
struct by_dividend_asset_account_asset{};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup object_index
|
||||||
|
*/
|
||||||
|
typedef multi_index_container<
|
||||||
|
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>,
|
||||||
|
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>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> pending_dividend_payout_balance_object_multi_index_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup object_index
|
||||||
|
*/
|
||||||
|
typedef generic_index<pending_dividend_payout_balance_object, pending_dividend_payout_balance_object_multi_index_type> pending_dividend_payout_balance_object_index;
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::account_object,
|
FC_REFLECT_DERIVED( graphene::chain::account_object,
|
||||||
|
|
@ -392,3 +442,8 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object,
|
||||||
(pending_fees)(pending_vested_fees)
|
(pending_fees)(pending_vested_fees)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT_DERIVED( graphene::chain::pending_dividend_payout_balance_object,
|
||||||
|
(graphene::db::object),
|
||||||
|
(owner)(dividend_holder_asset_type)(dividend_payout_asset_type)(pending_balance) )
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,18 @@ namespace graphene { namespace chain {
|
||||||
const asset_bitasset_data_object* bitasset_to_update = nullptr;
|
const asset_bitasset_data_object* bitasset_to_update = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class asset_update_dividend_evaluator : public evaluator<asset_update_dividend_evaluator>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
typedef asset_update_dividend_operation operation_type;
|
||||||
|
|
||||||
|
void_result do_evaluate( const asset_update_dividend_operation& o );
|
||||||
|
void_result do_apply( const asset_update_dividend_operation& o );
|
||||||
|
|
||||||
|
const asset_object* asset_to_update = nullptr;
|
||||||
|
const asset_dividend_data_object* asset_dividend_data_to_update = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
class asset_update_feed_producers_evaluator : public evaluator<asset_update_feed_producers_evaluator>
|
class asset_update_feed_producers_evaluator : public evaluator<asset_update_feed_producers_evaluator>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
||||||
|
|
@ -132,6 +132,9 @@ namespace graphene { namespace chain {
|
||||||
|
|
||||||
optional<account_id_type> buyback_account;
|
optional<account_id_type> buyback_account;
|
||||||
|
|
||||||
|
/// Extra data associated with dividend-paying assets.
|
||||||
|
optional<asset_dividend_data_id_type> dividend_data_id;
|
||||||
|
|
||||||
asset_id_type get_id()const { return id; }
|
asset_id_type get_id()const { return id; }
|
||||||
|
|
||||||
void validate()const
|
void validate()const
|
||||||
|
|
@ -148,6 +151,10 @@ namespace graphene { namespace chain {
|
||||||
const asset_bitasset_data_object& bitasset_data(const DB& db)const
|
const asset_bitasset_data_object& bitasset_data(const DB& db)const
|
||||||
{ assert(bitasset_data_id); return db.get(*bitasset_data_id); }
|
{ assert(bitasset_data_id); return db.get(*bitasset_data_id); }
|
||||||
|
|
||||||
|
template<class DB>
|
||||||
|
const asset_dividend_data_object& dividend_data(const DB& db)const
|
||||||
|
{ assert(dividend_data_id); return db.get(*dividend_data_id); }
|
||||||
|
|
||||||
template<class DB>
|
template<class DB>
|
||||||
const asset_dynamic_data_object& dynamic_data(const DB& db)const
|
const asset_dynamic_data_object& dynamic_data(const DB& db)const
|
||||||
{ return db.get(dynamic_asset_data_id); }
|
{ return db.get(dynamic_asset_data_id); }
|
||||||
|
|
@ -247,6 +254,72 @@ namespace graphene { namespace chain {
|
||||||
> asset_object_multi_index_type;
|
> asset_object_multi_index_type;
|
||||||
typedef generic_index<asset_object, asset_object_multi_index_type> asset_index;
|
typedef generic_index<asset_object, asset_object_multi_index_type> asset_index;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief contains properties that only apply to dividend-paying assets
|
||||||
|
*
|
||||||
|
* @ingroup object
|
||||||
|
* @ingroup implementation
|
||||||
|
*/
|
||||||
|
class asset_dividend_data_object : public abstract_object<asset_dividend_data_object>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const uint8_t space_id = implementation_ids;
|
||||||
|
static const uint8_t type_id = impl_asset_dividend_data_type;
|
||||||
|
|
||||||
|
/// The tunable options for Dividend-paying assets are stored in this field.
|
||||||
|
dividend_asset_options options;
|
||||||
|
|
||||||
|
/// The time payouts on this asset were scheduled to be processed last
|
||||||
|
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)
|
||||||
|
fc::optional<time_point_sec> last_payout_time;
|
||||||
|
|
||||||
|
/// The account which collects pending payouts
|
||||||
|
account_id_type dividend_distribution_account;
|
||||||
|
};
|
||||||
|
typedef multi_index_container<
|
||||||
|
asset_dividend_data_object,
|
||||||
|
indexed_by<
|
||||||
|
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >
|
||||||
|
>
|
||||||
|
> asset_dividend_data_object_multi_index_type;
|
||||||
|
typedef generic_index<asset_dividend_data_object, asset_dividend_data_object_multi_index_type> asset_dividend_data_object_index;
|
||||||
|
|
||||||
|
|
||||||
|
// This tracks the balances in a dividend distribution account at the last time
|
||||||
|
// pending dividend payouts were calculated (last maintenance interval).
|
||||||
|
// At each maintenance interval, we will compare the current balance to the
|
||||||
|
// balance stored here to see how much was deposited during that interval.
|
||||||
|
class distributed_dividend_balance_object : public abstract_object<distributed_dividend_balance_object>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const uint8_t space_id = implementation_ids;
|
||||||
|
static const uint8_t type_id = impl_distributed_dividend_balance_data_type;
|
||||||
|
|
||||||
|
asset_id_type dividend_holder_asset_type;
|
||||||
|
asset_id_type dividend_payout_asset_type;
|
||||||
|
share_type balance_at_last_maintenance_interval;
|
||||||
|
};
|
||||||
|
struct by_dividend_payout_asset{};
|
||||||
|
typedef multi_index_container<
|
||||||
|
distributed_dividend_balance_object,
|
||||||
|
indexed_by<
|
||||||
|
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||||
|
ordered_unique< tag<by_dividend_payout_asset>,
|
||||||
|
composite_key<
|
||||||
|
distributed_dividend_balance_object,
|
||||||
|
member<distributed_dividend_balance_object, asset_id_type, &distributed_dividend_balance_object::dividend_holder_asset_type>,
|
||||||
|
member<distributed_dividend_balance_object, asset_id_type, &distributed_dividend_balance_object::dividend_payout_asset_type>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> distributed_dividend_balance_object_multi_index_type;
|
||||||
|
typedef generic_index<distributed_dividend_balance_object, distributed_dividend_balance_object_multi_index_type> distributed_dividend_balance_object_index;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object),
|
||||||
|
|
@ -262,6 +335,19 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
|
||||||
(settlement_price)
|
(settlement_price)
|
||||||
(settlement_fund)
|
(settlement_fund)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT_DERIVED( graphene::chain::asset_dividend_data_object, (graphene::db::object),
|
||||||
|
(options)
|
||||||
|
(last_scheduled_payout_time)
|
||||||
|
(last_payout_time )
|
||||||
|
(dividend_distribution_account)
|
||||||
|
)
|
||||||
|
|
||||||
|
FC_REFLECT_DERIVED( graphene::chain::distributed_dividend_balance_object, (graphene::db::object),
|
||||||
|
(dividend_holder_asset_type)
|
||||||
|
(dividend_payout_asset_type)
|
||||||
|
(balance_at_last_maintenance_interval)
|
||||||
|
)
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
|
FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
|
||||||
(symbol)
|
(symbol)
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,30 @@ namespace graphene { namespace chain {
|
||||||
void validate()const;
|
void validate()const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The dividend_asset_options struct contains configurable options available only to dividend-paying assets.
|
||||||
|
*
|
||||||
|
* @note Changes to this struct will break protocol compatibility
|
||||||
|
*/
|
||||||
|
struct dividend_asset_options {
|
||||||
|
/// Time when the next payout should occur.
|
||||||
|
/// The payouts will happen on the maintenance interval at or after this time
|
||||||
|
/// If this is set to null, there will be no payouts.
|
||||||
|
fc::optional<fc::time_point_sec> next_payout_time;
|
||||||
|
/// If payouts happen on a fixed schedule, this specifies the interval between
|
||||||
|
/// payouts in seconds. After each payout, the next payout time will be incremented by
|
||||||
|
/// this amount.
|
||||||
|
/// 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;
|
||||||
|
|
||||||
|
extensions_type extensions;
|
||||||
|
|
||||||
|
/// Perform internal consistency checks.
|
||||||
|
/// @throws fc::exception if any check fails
|
||||||
|
void validate()const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ingroup operations
|
* @ingroup operations
|
||||||
|
|
@ -319,6 +343,35 @@ namespace graphene { namespace chain {
|
||||||
void validate()const;
|
void validate()const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update options specific to dividend-paying assets
|
||||||
|
* @ingroup operations
|
||||||
|
*
|
||||||
|
* Dividend-paying assets have some options which are not relevant to other asset types.
|
||||||
|
* This operation is used to update those options an an existing dividend-paying asset.
|
||||||
|
* This can also be used to convert a non-dividend-paying asset into a dividend-paying
|
||||||
|
* asset.
|
||||||
|
*
|
||||||
|
* @pre @ref issuer MUST be an existing account and MUST match asset_object::issuer on @ref asset_to_update
|
||||||
|
* @pre @ref fee MUST be nonnegative, and @ref issuer MUST have a sufficient balance to pay it
|
||||||
|
* @pre @ref new_options SHALL be internally consistent, as verified by @ref validate()
|
||||||
|
* @post @ref asset_to_update will have dividend-specific options matching those of new_options
|
||||||
|
*/
|
||||||
|
struct asset_update_dividend_operation : public base_operation
|
||||||
|
{
|
||||||
|
struct fee_parameters_type { uint64_t fee = 500 * GRAPHENE_BLOCKCHAIN_PRECISION; };
|
||||||
|
|
||||||
|
asset fee;
|
||||||
|
account_id_type issuer;
|
||||||
|
asset_id_type asset_to_update;
|
||||||
|
|
||||||
|
dividend_asset_options new_options;
|
||||||
|
extensions_type extensions;
|
||||||
|
|
||||||
|
account_id_type fee_payer()const { return issuer; }
|
||||||
|
void validate()const;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Update the set of feed-producing accounts for a BitAsset
|
* @brief Update the set of feed-producing accounts for a BitAsset
|
||||||
* @ingroup operations
|
* @ingroup operations
|
||||||
|
|
@ -462,6 +515,13 @@ FC_REFLECT( graphene::chain::asset_options,
|
||||||
(description)
|
(description)
|
||||||
(extensions)
|
(extensions)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT( graphene::chain::dividend_asset_options,
|
||||||
|
(next_payout_time)
|
||||||
|
(payout_interval)
|
||||||
|
(extensions)
|
||||||
|
)
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::bitasset_options,
|
FC_REFLECT( graphene::chain::bitasset_options,
|
||||||
(feed_lifetime_sec)
|
(feed_lifetime_sec)
|
||||||
(minimum_feeds)
|
(minimum_feeds)
|
||||||
|
|
@ -480,6 +540,7 @@ FC_REFLECT( graphene::chain::asset_settle_cancel_operation::fee_parameters_type,
|
||||||
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
FC_REFLECT( graphene::chain::asset_update_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||||
FC_REFLECT( graphene::chain::asset_update_bitasset_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::asset_update_bitasset_operation::fee_parameters_type, (fee) )
|
||||||
|
FC_REFLECT( graphene::chain::asset_update_dividend_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::asset_publish_feed_operation::fee_parameters_type, (fee) )
|
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_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||||
|
|
@ -511,6 +572,13 @@ FC_REFLECT( graphene::chain::asset_update_bitasset_operation,
|
||||||
(new_options)
|
(new_options)
|
||||||
(extensions)
|
(extensions)
|
||||||
)
|
)
|
||||||
|
FC_REFLECT( graphene::chain::asset_update_dividend_operation,
|
||||||
|
(fee)
|
||||||
|
(issuer)
|
||||||
|
(asset_to_update)
|
||||||
|
(new_options)
|
||||||
|
(extensions)
|
||||||
|
)
|
||||||
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation,
|
FC_REFLECT( graphene::chain::asset_update_feed_producers_operation,
|
||||||
(fee)(issuer)(asset_to_update)(new_feed_producers)(extensions)
|
(fee)(issuer)(asset_to_update)(new_feed_producers)(extensions)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,8 @@ namespace graphene { namespace chain {
|
||||||
transfer_from_blind_operation,
|
transfer_from_blind_operation,
|
||||||
asset_settle_cancel_operation, // VIRTUAL
|
asset_settle_cancel_operation, // VIRTUAL
|
||||||
asset_claim_fees_operation,
|
asset_claim_fees_operation,
|
||||||
fba_distribute_operation // VIRTUAL
|
fba_distribute_operation, // VIRTUAL
|
||||||
|
asset_update_dividend_operation
|
||||||
> operation;
|
> operation;
|
||||||
|
|
||||||
/// @} // operations group
|
/// @} // operations group
|
||||||
|
|
|
||||||
|
|
@ -155,7 +155,10 @@ namespace graphene { namespace chain {
|
||||||
impl_budget_record_object_type,
|
impl_budget_record_object_type,
|
||||||
impl_special_authority_object_type,
|
impl_special_authority_object_type,
|
||||||
impl_buyback_object_type,
|
impl_buyback_object_type,
|
||||||
impl_fba_accumulator_object_type
|
impl_fba_accumulator_object_type,
|
||||||
|
impl_asset_dividend_data_type,
|
||||||
|
impl_pending_dividend_payout_balance_object_type,
|
||||||
|
impl_distributed_dividend_balance_data_type
|
||||||
};
|
};
|
||||||
|
|
||||||
//typedef fc::unsigned_int object_id_type;
|
//typedef fc::unsigned_int object_id_type;
|
||||||
|
|
@ -207,11 +210,15 @@ namespace graphene { namespace chain {
|
||||||
class special_authority_object;
|
class special_authority_object;
|
||||||
class buyback_object;
|
class buyback_object;
|
||||||
class fba_accumulator_object;
|
class fba_accumulator_object;
|
||||||
|
class asset_dividend_data_object;
|
||||||
|
class pending_dividend_payout_balance_object;
|
||||||
|
|
||||||
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
|
typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type;
|
||||||
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
|
typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type;
|
||||||
typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type;
|
typedef object_id< implementation_ids, impl_asset_dynamic_data_type, asset_dynamic_data_object> asset_dynamic_data_id_type;
|
||||||
typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type;
|
typedef object_id< implementation_ids, impl_asset_bitasset_data_type, asset_bitasset_data_object> asset_bitasset_data_id_type;
|
||||||
|
typedef object_id< implementation_ids, impl_asset_dividend_data_type, asset_dividend_data_object> asset_dividend_data_id_type;
|
||||||
|
typedef object_id< implementation_ids, impl_pending_dividend_payout_balance_object_type, pending_dividend_payout_balance_object> pending_dividend_payout_balance_object_type;
|
||||||
typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type;
|
typedef object_id< implementation_ids, impl_account_balance_object_type, account_balance_object> account_balance_id_type;
|
||||||
typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type;
|
typedef object_id< implementation_ids, impl_account_statistics_object_type,account_statistics_object> account_statistics_id_type;
|
||||||
typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type;
|
typedef object_id< implementation_ids, impl_transaction_object_type, transaction_object> transaction_obj_id_type;
|
||||||
|
|
@ -359,6 +366,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
||||||
(impl_special_authority_object_type)
|
(impl_special_authority_object_type)
|
||||||
(impl_buyback_object_type)
|
(impl_buyback_object_type)
|
||||||
(impl_fba_accumulator_object_type)
|
(impl_fba_accumulator_object_type)
|
||||||
|
(impl_asset_dividend_data_type)
|
||||||
|
(impl_pending_dividend_payout_balance_object_type)
|
||||||
|
(impl_distributed_dividend_balance_data_type)
|
||||||
)
|
)
|
||||||
|
|
||||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||||
|
|
|
||||||
|
|
@ -180,6 +180,12 @@ void asset_update_bitasset_operation::validate() const
|
||||||
new_options.validate();
|
new_options.validate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void asset_update_dividend_operation::validate() const
|
||||||
|
{
|
||||||
|
FC_ASSERT( fee.amount >= 0 );
|
||||||
|
new_options.validate();
|
||||||
|
}
|
||||||
|
|
||||||
void asset_update_feed_producers_operation::validate() const
|
void asset_update_feed_producers_operation::validate() const
|
||||||
{
|
{
|
||||||
FC_ASSERT( fee.amount >= 0 );
|
FC_ASSERT( fee.amount >= 0 );
|
||||||
|
|
@ -198,6 +204,10 @@ void bitasset_options::validate() const
|
||||||
FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);
|
FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void dividend_asset_options::validate() const
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
void asset_options::validate()const
|
void asset_options::validate()const
|
||||||
{
|
{
|
||||||
FC_ASSERT( max_supply > 0 );
|
FC_ASSERT( max_supply > 0 );
|
||||||
|
|
|
||||||
|
|
@ -1199,6 +1199,27 @@ public:
|
||||||
return sign_transaction( tx, broadcast );
|
return sign_transaction( tx, broadcast );
|
||||||
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
|
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
|
||||||
|
|
||||||
|
signed_transaction update_dividend_asset(string symbol,
|
||||||
|
dividend_asset_options new_options,
|
||||||
|
bool broadcast /* = false */)
|
||||||
|
{ try {
|
||||||
|
optional<asset_object> asset_to_update = find_asset(symbol);
|
||||||
|
if (!asset_to_update)
|
||||||
|
FC_THROW("No asset with that symbol exists!");
|
||||||
|
|
||||||
|
asset_update_dividend_operation update_op;
|
||||||
|
update_op.issuer = asset_to_update->issuer;
|
||||||
|
update_op.asset_to_update = asset_to_update->id;
|
||||||
|
update_op.new_options = new_options;
|
||||||
|
|
||||||
|
signed_transaction tx;
|
||||||
|
tx.operations.push_back( update_op );
|
||||||
|
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
|
||||||
|
tx.validate();
|
||||||
|
|
||||||
|
return sign_transaction( tx, broadcast );
|
||||||
|
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
|
||||||
|
|
||||||
signed_transaction update_asset_feed_producers(string symbol,
|
signed_transaction update_asset_feed_producers(string symbol,
|
||||||
flat_set<string> new_feed_producers,
|
flat_set<string> new_feed_producers,
|
||||||
bool broadcast /* = false */)
|
bool broadcast /* = false */)
|
||||||
|
|
@ -2207,7 +2228,7 @@ public:
|
||||||
<< "\n====================================================================================="
|
<< "\n====================================================================================="
|
||||||
<< "|=====================================================================================\n";
|
<< "|=====================================================================================\n";
|
||||||
|
|
||||||
for (int i = 0; i < bids.size() || i < asks.size() ; i++)
|
for (unsigned i = 0; i < bids.size() || i < asks.size() ; i++)
|
||||||
{
|
{
|
||||||
if ( i < bids.size() )
|
if ( i < bids.size() )
|
||||||
{
|
{
|
||||||
|
|
@ -2788,7 +2809,7 @@ vector<operation_detail> wallet_api::get_account_history(string name, int limit)
|
||||||
auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result));
|
auto memo = o.op.visit(detail::operation_printer(ss, *my, o.result));
|
||||||
result.push_back( operation_detail{ memo, ss.str(), o } );
|
result.push_back( operation_detail{ memo, ss.str(), o } );
|
||||||
}
|
}
|
||||||
if( current.size() < std::min(100,limit) )
|
if( (int)current.size() < std::min(100,limit) )
|
||||||
break;
|
break;
|
||||||
limit -= current.size();
|
limit -= current.size();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1036,6 +1036,24 @@ int64_t database_fixture::get_balance( const account_object& account, const asse
|
||||||
return db.get_balance(account.get_id(), a.get_id()).amount.value;
|
return db.get_balance(account.get_id(), a.get_id()).amount.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type,
|
||||||
|
account_id_type dividend_holder_account_id,
|
||||||
|
asset_id_type dividend_payout_asset_type) const
|
||||||
|
{
|
||||||
|
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())
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return pending_payout_iter->pending_balance.value;
|
||||||
|
}
|
||||||
|
|
||||||
vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const
|
vector< operation_history_object > database_fixture::get_operation_history( account_id_type account_id )const
|
||||||
{
|
{
|
||||||
vector< operation_history_object > result;
|
vector< operation_history_object > result;
|
||||||
|
|
|
||||||
|
|
@ -280,6 +280,9 @@ struct database_fixture {
|
||||||
void print_joint_market( const string& syma, const string& symb )const;
|
void print_joint_market( const string& syma, const string& symb )const;
|
||||||
int64_t get_balance( account_id_type account, asset_id_type a )const;
|
int64_t get_balance( account_id_type account, asset_id_type a )const;
|
||||||
int64_t get_balance( const account_object& account, const asset_object& a )const;
|
int64_t get_balance( const account_object& account, const asset_object& a )const;
|
||||||
|
int64_t get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type,
|
||||||
|
account_id_type dividend_holder_account_id,
|
||||||
|
asset_id_type dividend_payout_asset_type) const;
|
||||||
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
|
vector< operation_history_object > get_operation_history( account_id_type account_id )const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1111,6 +1111,228 @@ BOOST_AUTO_TEST_CASE( uia_fees )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( create_dividend_uia )
|
||||||
|
{
|
||||||
|
using namespace graphene;
|
||||||
|
try {
|
||||||
|
BOOST_TEST_MESSAGE("Creating dividend holder asset");
|
||||||
|
{
|
||||||
|
asset_create_operation creator;
|
||||||
|
creator.issuer = account_id_type();
|
||||||
|
creator.fee = asset();
|
||||||
|
creator.symbol = "DIVIDEND";
|
||||||
|
creator.common_options.max_supply = 100000000;
|
||||||
|
creator.precision = 2;
|
||||||
|
creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/
|
||||||
|
creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;
|
||||||
|
creator.common_options.flags = charge_market_fee;
|
||||||
|
creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))});
|
||||||
|
trx.operations.push_back(std::move(creator));
|
||||||
|
set_expiration(db, trx);
|
||||||
|
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 = fc::time_point::now() + 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");
|
||||||
|
create_account("bob");
|
||||||
|
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");
|
||||||
|
{
|
||||||
|
asset_create_operation creator;
|
||||||
|
creator.issuer = account_id_type();
|
||||||
|
creator.fee = asset();
|
||||||
|
creator.symbol = "TEST";
|
||||||
|
creator.common_options.max_supply = 100000000;
|
||||||
|
creator.precision = 2;
|
||||||
|
creator.common_options.market_fee_percent = GRAPHENE_MAX_MARKET_FEE_PERCENT/100; /*1%*/
|
||||||
|
creator.common_options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK;
|
||||||
|
creator.common_options.flags = charge_market_fee;
|
||||||
|
creator.common_options.core_exchange_rate = price({asset(2),asset(1,asset_id_type(1))});
|
||||||
|
trx.operations.push_back(std::move(creator));
|
||||||
|
set_expiration(db, trx);
|
||||||
|
PUSH_TX( db, trx, ~0 );
|
||||||
|
trx.operations.clear();
|
||||||
|
}
|
||||||
|
generate_block();
|
||||||
|
|
||||||
|
// it should not yet be a divdend asset
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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<pending_dividend_payout_balance_object_index>().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
|
||||||
|
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
|
||||||
|
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;
|
||||||
|
advance_to_next_payout_time();
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_REQUIRE_MESSAGE(dividend_data.options.next_payout_time, "No new payout was scheduled");
|
||||||
|
BOOST_CHECK_MESSAGE(old_next_payout_scheduled_time != *dividend_data.options.next_payout_time,
|
||||||
|
"New payout was scheduled for the same time as the last payout");
|
||||||
|
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");
|
||||||
|
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000);
|
||||||
|
verify_pending_balance(alice, test_asset_object, 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000);
|
||||||
|
verify_pending_balance(bob, test_asset_object, 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000);
|
||||||
|
verify_pending_balance(carol, test_asset_object, 0);
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE( cancel_limit_order_test )
|
BOOST_AUTO_TEST_CASE( cancel_limit_order_test )
|
||||||
{ try {
|
{ try {
|
||||||
INVOKE( issue_uia );
|
INVOKE( issue_uia );
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue