Cherry-picked commit abc7853.
Initial work on dividend-paying assets. Basic functionality works in simple cases.
This commit is contained in:
parent
9b08b502be
commit
b9304caffa
15 changed files with 863 additions and 5 deletions
|
|
@ -90,6 +90,7 @@ struct get_impacted_account_visitor
|
|||
}
|
||||
|
||||
void operator()( const asset_update_bitasset_operation& op ) {}
|
||||
void operator()( const asset_update_dividend_operation& op ) {}
|
||||
void operator()( const asset_update_feed_producers_operation& op ) {}
|
||||
|
||||
void operator()( const asset_issue_operation& op )
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@
|
|||
|
||||
#include <functional>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op )
|
||||
|
|
@ -366,6 +368,65 @@ void_result asset_update_bitasset_evaluator::do_apply(const asset_update_bitasse
|
|||
return void_result();
|
||||
} 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)
|
||||
{ try {
|
||||
database& d = db();
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ void database::initialize_evaluators()
|
|||
register_evaluator<asset_reserve_evaluator>();
|
||||
register_evaluator<asset_update_evaluator>();
|
||||
register_evaluator<asset_update_bitasset_evaluator>();
|
||||
register_evaluator<asset_update_dividend_evaluator>();
|
||||
register_evaluator<asset_update_feed_producers_evaluator>();
|
||||
register_evaluator<asset_settle_evaluator>();
|
||||
register_evaluator<asset_global_settle_evaluator>();
|
||||
|
|
@ -259,6 +260,7 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<transaction_index > >();
|
||||
add_index< primary_index<account_balance_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<dynamic_global_property_object >> >();
|
||||
add_index< primary_index<simple_index<account_statistics_object >> >();
|
||||
|
|
@ -269,10 +271,11 @@ void database::initialize_indexes()
|
|||
add_index< primary_index<simple_index<budget_record_object > > >();
|
||||
add_index< primary_index< special_authority_index > >();
|
||||
add_index< primary_index< buyback_index > >();
|
||||
|
||||
add_index< primary_index< simple_index< fba_accumulator_object > > >();
|
||||
add_index< primary_index< betting_market_position_index > >();
|
||||
add_index< primary_index< global_betting_statistics_object_index > >();
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -716,6 +716,291 @@ void deprecate_annual_members( database& db )
|
|||
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)
|
||||
{
|
||||
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);
|
||||
create_buyback_orders(*this);
|
||||
|
||||
process_dividend_assets(*this);
|
||||
|
||||
struct vote_tally_helper {
|
||||
database& d;
|
||||
const global_property_object& props;
|
||||
|
|
|
|||
|
|
@ -311,6 +311,31 @@ namespace graphene { namespace chain {
|
|||
/** maps the referrer to the set of accounts that they have referred */
|
||||
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_asset_balance;
|
||||
|
|
@ -367,6 +392,31 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
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,
|
||||
|
|
@ -395,3 +445,8 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object,
|
|||
(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;
|
||||
};
|
||||
|
||||
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>
|
||||
{
|
||||
public:
|
||||
|
|
|
|||
|
|
@ -132,6 +132,9 @@ namespace graphene { namespace chain {
|
|||
|
||||
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; }
|
||||
|
||||
void validate()const
|
||||
|
|
@ -148,6 +151,10 @@ namespace graphene { namespace chain {
|
|||
const asset_bitasset_data_object& bitasset_data(const DB& db)const
|
||||
{ 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>
|
||||
const asset_dynamic_data_object& dynamic_data(const DB& db)const
|
||||
{ return db.get(dynamic_asset_data_id); }
|
||||
|
|
@ -249,6 +256,72 @@ namespace graphene { namespace chain {
|
|||
> asset_object_multi_index_type;
|
||||
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
|
||||
|
||||
FC_REFLECT_DERIVED( graphene::chain::asset_dynamic_data_object, (graphene::db::object),
|
||||
|
|
@ -264,6 +337,19 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
|
|||
(settlement_price)
|
||||
(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),
|
||||
(symbol)
|
||||
|
|
|
|||
|
|
@ -112,6 +112,30 @@ namespace graphene { namespace chain {
|
|||
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
|
||||
|
|
@ -319,6 +343,35 @@ namespace graphene { namespace chain {
|
|||
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
|
||||
* @ingroup operations
|
||||
|
|
@ -462,6 +515,13 @@ FC_REFLECT( graphene::chain::asset_options,
|
|||
(description)
|
||||
(extensions)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::dividend_asset_options,
|
||||
(next_payout_time)
|
||||
(payout_interval)
|
||||
(extensions)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::bitasset_options,
|
||||
(feed_lifetime_sec)
|
||||
(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_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_dividend_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_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) )
|
||||
|
|
@ -511,6 +572,13 @@ FC_REFLECT( graphene::chain::asset_update_bitasset_operation,
|
|||
(new_options)
|
||||
(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,
|
||||
(fee)(issuer)(asset_to_update)(new_feed_producers)(extensions)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -108,7 +108,8 @@ namespace graphene { namespace chain {
|
|||
betting_market_group_resolved_operation, // VIRTUAL
|
||||
bet_matched_operation, // VIRTUAL
|
||||
bet_cancel_operation,
|
||||
bet_canceled_operation // VIRTUAL
|
||||
bet_canceled_operation, // VIRTUAL
|
||||
asset_update_dividend_operation
|
||||
> operation;
|
||||
|
||||
/// @} // operations group
|
||||
|
|
|
|||
|
|
@ -164,7 +164,10 @@ namespace graphene { namespace chain {
|
|||
impl_buyback_object_type,
|
||||
impl_fba_accumulator_object_type,
|
||||
impl_betting_market_position_object_type,
|
||||
impl_global_betting_statistics_object_type
|
||||
impl_global_betting_statistics_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;
|
||||
|
|
@ -232,11 +235,15 @@ namespace graphene { namespace chain {
|
|||
class fba_accumulator_object;
|
||||
class betting_market_position_object;
|
||||
class global_betting_statistics_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_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_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_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;
|
||||
|
|
@ -400,6 +407,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
|||
(impl_fba_accumulator_object_type)
|
||||
(impl_betting_market_position_object_type)
|
||||
(impl_global_betting_statistics_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 )
|
||||
|
|
|
|||
|
|
@ -183,6 +183,12 @@ void asset_update_bitasset_operation::validate() const
|
|||
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
|
||||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
|
|
@ -201,6 +207,10 @@ void bitasset_options::validate() const
|
|||
FC_ASSERT(maximum_force_settlement_volume <= GRAPHENE_100_PERCENT);
|
||||
}
|
||||
|
||||
void dividend_asset_options::validate() const
|
||||
{
|
||||
}
|
||||
|
||||
void asset_options::validate()const
|
||||
{
|
||||
FC_ASSERT( max_supply > 0 );
|
||||
|
|
|
|||
|
|
@ -1199,6 +1199,27 @@ public:
|
|||
return sign_transaction( tx, 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,
|
||||
flat_set<string> new_feed_producers,
|
||||
bool broadcast /* = false */)
|
||||
|
|
@ -2224,7 +2245,7 @@ public:
|
|||
<< "\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() )
|
||||
{
|
||||
|
|
@ -2827,7 +2848,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));
|
||||
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;
|
||||
limit -= current.size();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1063,6 +1063,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;
|
||||
}
|
||||
|
||||
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 > result;
|
||||
|
|
|
|||
|
|
@ -280,6 +280,9 @@ struct database_fixture {
|
|||
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( 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;
|
||||
void process_operation_by_witnesses(operation op);
|
||||
const sport_object& create_sport(internationalized_string_type name);
|
||||
|
|
|
|||
|
|
@ -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 )
|
||||
{ try {
|
||||
INVOKE( issue_uia );
|
||||
|
|
|
|||
Loading…
Reference in a new issue