Merge branch 'betting-ro4' into betting-merge
This commit is contained in:
commit
5de1437d24
30 changed files with 1925 additions and 127 deletions
|
|
@ -24,6 +24,7 @@
|
||||||
|
|
||||||
#include <graphene/app/database_api.hpp>
|
#include <graphene/app/database_api.hpp>
|
||||||
#include <graphene/chain/get_config.hpp>
|
#include <graphene/chain/get_config.hpp>
|
||||||
|
#include <graphene/chain/account_object.hpp>
|
||||||
|
|
||||||
#include <fc/bloom_filter.hpp>
|
#include <fc/bloom_filter.hpp>
|
||||||
#include <fc/smart_ref_impl.hpp>
|
#include <fc/smart_ref_impl.hpp>
|
||||||
|
|
@ -719,6 +720,10 @@ std::map<std::string, full_account> database_api_impl::get_full_accounts( const
|
||||||
acnt.withdraws.emplace_back(withdraw);
|
acnt.withdraws.emplace_back(withdraw);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
auto pending_payouts_range =
|
||||||
|
_db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>().indices().get<by_account_dividend_payout>().equal_range(boost::make_tuple(account->id));
|
||||||
|
|
||||||
|
std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments));
|
||||||
|
|
||||||
results[account_name_or_id] = acnt;
|
results[account_name_or_id] = acnt;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,12 @@ 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_dividend_distribution_operation& op )
|
||||||
|
{
|
||||||
|
_impacted.insert( op.account_id );
|
||||||
|
}
|
||||||
|
|
||||||
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 )
|
||||||
|
|
@ -209,7 +215,7 @@ struct get_impacted_account_visitor
|
||||||
void operator()( const betting_market_rules_create_operation& op ) {}
|
void operator()( const betting_market_rules_create_operation& op ) {}
|
||||||
void operator()( const betting_market_group_create_operation& op ) {}
|
void operator()( const betting_market_group_create_operation& op ) {}
|
||||||
void operator()( const betting_market_create_operation& op ) {}
|
void operator()( const betting_market_create_operation& op ) {}
|
||||||
void operator()( const betting_market_resolve_operation& op ) {}
|
void operator()( const betting_market_group_resolve_operation& op ) {}
|
||||||
void operator()( const bet_place_operation& op )
|
void operator()( const bet_place_operation& op )
|
||||||
{
|
{
|
||||||
_impacted.insert( op.bettor_id );
|
_impacted.insert( op.bettor_id );
|
||||||
|
|
@ -226,7 +232,7 @@ struct get_impacted_account_visitor
|
||||||
{
|
{
|
||||||
_impacted.insert( op.bettor_id );
|
_impacted.insert( op.bettor_id );
|
||||||
}
|
}
|
||||||
void operator()( const betting_market_resolved_operation& op )
|
void operator()( const betting_market_group_resolved_operation& op )
|
||||||
{
|
{
|
||||||
_impacted.insert( op.bettor_id );
|
_impacted.insert( op.bettor_id );
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ namespace graphene { namespace app {
|
||||||
vector<proposal_object> proposals;
|
vector<proposal_object> proposals;
|
||||||
vector<asset_id_type> assets;
|
vector<asset_id_type> assets;
|
||||||
vector<withdraw_permission_object> withdraws;
|
vector<withdraw_permission_object> withdraws;
|
||||||
|
// vector<pending_dividend_payout_balance_object> pending_dividend_payments;
|
||||||
|
vector<pending_dividend_payout_balance_for_holder_object> pending_dividend_payments;
|
||||||
};
|
};
|
||||||
|
|
||||||
} }
|
} }
|
||||||
|
|
@ -68,4 +70,6 @@ FC_REFLECT( graphene::app::full_account,
|
||||||
(proposals)
|
(proposals)
|
||||||
(assets)
|
(assets)
|
||||||
(withdraws)
|
(withdraws)
|
||||||
|
(proposals)
|
||||||
|
(pending_dividend_payments)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -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 )
|
||||||
|
|
@ -366,6 +368,70 @@ 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;
|
||||||
|
// whenever new options are set, clear out the scheduled payout/distribution times
|
||||||
|
// this will reset and cause the next distribution to happen at the next maintenance
|
||||||
|
// interval and a payout at the next_payout_time
|
||||||
|
dividend_data_obj.last_scheduled_payout_time.reset();
|
||||||
|
dividend_data_obj.last_scheduled_distribution_time.reset();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return void_result();
|
||||||
|
} 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();
|
||||||
|
|
|
||||||
|
|
@ -218,17 +218,19 @@ void_result bet_cancel_evaluator::do_apply(const bet_cancel_operation& op)
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
void_result betting_market_resolve_evaluator::do_evaluate(const betting_market_resolve_operation& op)
|
void_result betting_market_group_resolve_evaluator::do_evaluate(const betting_market_group_resolve_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
const database& d = db();
|
const database& d = db();
|
||||||
_betting_market = &op.betting_market_id(d);
|
_betting_market_group = &op.betting_market_group_id(d);
|
||||||
|
db().validate_betting_market_group_resolutions(*_betting_market_group, op.resolutions);
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
void_result betting_market_resolve_evaluator::do_apply(const betting_market_resolve_operation& op)
|
void_result betting_market_group_resolve_evaluator::do_apply(const betting_market_group_resolve_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
db().resolve_betting_market(*_betting_market, op.resolution);
|
db().resolve_betting_market_group(*_betting_market_group, op.resolutions);
|
||||||
return void_result();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -44,64 +44,163 @@ void database::cancel_all_betting_markets_for_event(const event_object& event_ob
|
||||||
for (const betting_market_group_object& betting_market_group :
|
for (const betting_market_group_object& betting_market_group :
|
||||||
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj.id)))
|
boost::make_iterator_range(betting_market_group_index.equal_range(event_obj.id)))
|
||||||
{
|
{
|
||||||
//for each betting market in the betting market group
|
resolve_betting_market_group(betting_market_group, {});
|
||||||
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
|
|
||||||
while (betting_market_itr != betting_market_index.end() &&
|
|
||||||
betting_market_itr->group_id == betting_market_group.id)
|
|
||||||
{
|
|
||||||
auto old_betting_market_itr = betting_market_itr;
|
|
||||||
++betting_market_itr;
|
|
||||||
resolve_betting_market(*old_betting_market_itr, betting_market_resolution_type::cancel);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
//TODO: should we remove market groups once all their markets are resolved?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::resolve_betting_market(const betting_market_object& betting_market,
|
void database::validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
|
||||||
betting_market_resolution_type resolution)
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
|
||||||
{
|
{
|
||||||
cancel_all_unmatched_bets_on_betting_market(betting_market);
|
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
|
||||||
|
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
|
||||||
|
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
|
||||||
|
{
|
||||||
|
const betting_market_object& betting_market = *betting_market_itr;
|
||||||
|
// every betting market in the group tied with resolution
|
||||||
|
assert(resolutions.count(betting_market.id));
|
||||||
|
++betting_market_itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto& index = get_index_type<betting_market_position_index>().indices().get<by_bettor_betting_market>();
|
void database::resolve_betting_market_group(const betting_market_group_object& betting_market_group,
|
||||||
auto position_itr = index.lower_bound(std::make_tuple(betting_market.id));
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions)
|
||||||
while (position_itr != index.end() &&
|
{
|
||||||
position_itr->betting_market_id == betting_market.id)
|
bool cancel = resolutions.size() == 0;
|
||||||
{
|
// collecting bettors and their positions
|
||||||
const betting_market_position_object& position = *position_itr;
|
std::map<account_id_type, std::vector<const betting_market_position_object*> > bettor_positions_map;
|
||||||
++position_itr;
|
|
||||||
|
|
||||||
share_type payout_amount = 0;
|
auto& betting_market_index = get_index_type<betting_market_object_index>().indices().get<by_betting_market_group_id>();
|
||||||
switch (resolution)
|
auto betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
|
||||||
|
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
|
||||||
|
{
|
||||||
|
const betting_market_object& betting_market = *betting_market_itr;
|
||||||
|
++betting_market_itr;
|
||||||
|
cancel_all_unmatched_bets_on_betting_market(betting_market);
|
||||||
|
|
||||||
|
auto& index = get_index_type<betting_market_position_index>().indices().get<by_bettor_betting_market>();
|
||||||
|
auto position_itr = index.lower_bound(std::make_tuple(betting_market.id));
|
||||||
|
|
||||||
|
while (position_itr != index.end() && position_itr->betting_market_id == betting_market.id)
|
||||||
|
{
|
||||||
|
const betting_market_position_object& position = *position_itr;
|
||||||
|
++position_itr;
|
||||||
|
|
||||||
|
bettor_positions_map[position.bettor_id].push_back(&position);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// walking through bettors' positions and collecting winings and fees respecting asset_id
|
||||||
|
for (const auto& bettor_positions_pair: bettor_positions_map)
|
||||||
|
{
|
||||||
|
uint16_t rake_fee_percentage = get_global_properties().parameters.betting_rake_fee_percentage;
|
||||||
|
std::map<asset_id_type, share_type> net_profits;
|
||||||
|
std::map<asset_id_type, share_type> payout_amounts;
|
||||||
|
std::map<asset_id_type, share_type> fees_collected;
|
||||||
|
account_id_type bettor_id = bettor_positions_pair.first;
|
||||||
|
const std::vector<const betting_market_position_object*>& bettor_positions = bettor_positions_pair.second;
|
||||||
|
|
||||||
|
for (const betting_market_position_object* position : bettor_positions)
|
||||||
{
|
{
|
||||||
case betting_market_resolution_type::win:
|
betting_market_resolution_type resolution;
|
||||||
payout_amount += position.pay_if_payout_condition;
|
if (cancel)
|
||||||
payout_amount += position.pay_if_not_canceled;
|
{
|
||||||
//TODO: pay the fees to the correct (dividend-distribution) account
|
resolution = betting_market_resolution_type::cancel;
|
||||||
adjust_balance(account_id_type(), asset(position.fees_collected, betting_market.asset_id));
|
}
|
||||||
break;
|
else
|
||||||
case betting_market_resolution_type::not_win:
|
{
|
||||||
payout_amount += position.pay_if_not_payout_condition;
|
// checked in evaluator, should never happen, see above
|
||||||
payout_amount += position.pay_if_not_canceled;
|
assert(resolutions.count(position->betting_market_id));
|
||||||
//TODO: pay the fees to the correct (dividend-distribution) account
|
resolution = resolutions.at(position->betting_market_id);
|
||||||
adjust_balance(account_id_type(), asset(position.fees_collected, betting_market.asset_id));
|
}
|
||||||
break;
|
const betting_market_object& betting_market = position->betting_market_id(*this);
|
||||||
case betting_market_resolution_type::cancel:
|
|
||||||
payout_amount += position.pay_if_canceled;
|
switch (resolution)
|
||||||
payout_amount += position.fees_collected;
|
{
|
||||||
break;
|
case betting_market_resolution_type::win:
|
||||||
|
payout_amounts[betting_market.asset_id] += position->pay_if_payout_condition;
|
||||||
|
payout_amounts[betting_market.asset_id] += position->pay_if_not_canceled;
|
||||||
|
fees_collected[betting_market.asset_id] += position->fees_collected;
|
||||||
|
net_profits[betting_market.asset_id] += position->pay_if_payout_condition;
|
||||||
|
net_profits[betting_market.asset_id] += position->pay_if_not_canceled;
|
||||||
|
net_profits[betting_market.asset_id] -= position->pay_if_canceled;
|
||||||
|
break;
|
||||||
|
case betting_market_resolution_type::not_win:
|
||||||
|
payout_amounts[betting_market.asset_id] += position->pay_if_not_payout_condition;
|
||||||
|
payout_amounts[betting_market.asset_id] += position->pay_if_not_canceled;
|
||||||
|
fees_collected[betting_market.asset_id] += position->fees_collected;
|
||||||
|
net_profits[betting_market.asset_id] += position->pay_if_not_payout_condition;
|
||||||
|
net_profits[betting_market.asset_id] += position->pay_if_not_canceled;
|
||||||
|
net_profits[betting_market.asset_id] -= position->pay_if_canceled;
|
||||||
|
break;
|
||||||
|
case betting_market_resolution_type::cancel:
|
||||||
|
payout_amounts[betting_market.asset_id] += position->pay_if_canceled;
|
||||||
|
payout_amounts[betting_market.asset_id] += position->fees_collected;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
remove(*position);
|
||||||
}
|
}
|
||||||
|
|
||||||
adjust_balance(position.bettor_id, asset(payout_amount, betting_market.asset_id));
|
std::vector<asset> winnings;
|
||||||
|
std::vector<asset> fees;
|
||||||
|
for (const auto& payout_amount_pair: payout_amounts)
|
||||||
|
{
|
||||||
|
// pay the fees to the correct (dividend-distribution) account if net profit
|
||||||
|
asset_id_type asset_id = payout_amount_pair.first;
|
||||||
|
const asset_object & asset_obj = asset_id(*this);
|
||||||
|
optional<asset_dividend_data_id_type> dividend_id = asset_obj.dividend_data_id;
|
||||||
|
account_id_type rake_account_id;
|
||||||
|
if (dividend_id.valid())
|
||||||
|
{
|
||||||
|
const asset_dividend_data_id_type& asset_dividend_data_id_= *dividend_id;
|
||||||
|
const asset_dividend_data_object& dividend_obj = asset_dividend_data_id_(*this);
|
||||||
|
rake_account_id = dividend_obj.dividend_distribution_account;
|
||||||
|
}
|
||||||
|
asset fee = fees_collected[asset_id];
|
||||||
|
share_type net_profit = net_profits[asset_id];
|
||||||
|
share_type payout_amount = payout_amount_pair.second;
|
||||||
|
share_type rake_amount = 0;
|
||||||
|
if (dividend_id.valid())
|
||||||
|
{
|
||||||
|
if (net_profit.value > 0)
|
||||||
|
{
|
||||||
|
rake_amount = (fc::uint128_t(net_profit.value) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100).to_uint64();
|
||||||
|
if (rake_amount.value)
|
||||||
|
{
|
||||||
|
// adjusting balance of dividend_distribution_account
|
||||||
|
asset rake(rake_amount, asset_id);
|
||||||
|
adjust_balance(rake_account_id, rake);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adjust_balance(rake_account_id, fee);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
adjust_balance(account_id_type(), fee);
|
||||||
|
}
|
||||||
|
// pay winning - rake
|
||||||
|
asset payout = asset(payout_amount - rake_amount, asset_id);
|
||||||
|
adjust_balance(bettor_id, payout);
|
||||||
|
|
||||||
push_applied_operation(betting_market_resolved_operation(position.bettor_id,
|
winnings.push_back(payout);
|
||||||
betting_market.id,
|
fees.push_back(fee);
|
||||||
resolution,
|
}
|
||||||
payout_amount,
|
push_applied_operation(betting_market_group_resolved_operation(bettor_id,
|
||||||
position.fees_collected));
|
betting_market_group.id,
|
||||||
|
resolutions,
|
||||||
|
winnings,
|
||||||
|
fees));
|
||||||
|
}
|
||||||
|
|
||||||
remove(position);
|
betting_market_itr = betting_market_index.lower_bound(betting_market_group.id);
|
||||||
|
while (betting_market_itr != betting_market_index.end() && betting_market_itr->group_id == betting_market_group.id)
|
||||||
|
{
|
||||||
|
const betting_market_object& betting_market = *betting_market_itr;
|
||||||
|
++betting_market_itr;
|
||||||
|
remove(betting_market);
|
||||||
}
|
}
|
||||||
remove(betting_market);
|
remove(betting_market_group);
|
||||||
}
|
}
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
|
|
|
||||||
|
|
@ -181,6 +181,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>();
|
||||||
|
|
@ -216,7 +217,7 @@ void database::initialize_evaluators()
|
||||||
register_evaluator<betting_market_group_create_evaluator>();
|
register_evaluator<betting_market_group_create_evaluator>();
|
||||||
register_evaluator<betting_market_create_evaluator>();
|
register_evaluator<betting_market_create_evaluator>();
|
||||||
register_evaluator<bet_place_evaluator>();
|
register_evaluator<bet_place_evaluator>();
|
||||||
register_evaluator<betting_market_resolve_evaluator>();
|
register_evaluator<betting_market_group_resolve_evaluator>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void database::initialize_indexes()
|
void database::initialize_indexes()
|
||||||
|
|
@ -257,6 +258,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 >> >();
|
||||||
|
|
@ -267,10 +269,13 @@ void database::initialize_indexes()
|
||||||
add_index< primary_index<simple_index<budget_record_object > > >();
|
add_index< primary_index<simple_index<budget_record_object > > >();
|
||||||
add_index< primary_index< special_authority_index > >();
|
add_index< primary_index< special_authority_index > >();
|
||||||
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< betting_market_position_index > >();
|
add_index< primary_index< betting_market_position_index > >();
|
||||||
add_index< primary_index< global_betting_statistics_object_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 > >();
|
||||||
|
add_index< primary_index<pending_dividend_payout_balance_for_holder_object_index > >();
|
||||||
|
add_index< primary_index<total_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)
|
||||||
|
|
@ -365,6 +370,16 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
a.network_fee_percentage = 0;
|
a.network_fee_percentage = 0;
|
||||||
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
|
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
|
||||||
}).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT);
|
}).get_id() == GRAPHENE_PROXY_TO_SELF_ACCOUNT);
|
||||||
|
FC_ASSERT(create<account_object>([this](account_object& a) {
|
||||||
|
a.name = "default-dividend-distribution";
|
||||||
|
a.statistics = create<account_statistics_object>([&](account_statistics_object& s){s.owner = a.id;}).id;
|
||||||
|
a.owner.weight_threshold = 1;
|
||||||
|
a.active.weight_threshold = 1;
|
||||||
|
a.registrar = a.lifetime_referrer = a.referrer = GRAPHENE_PROXY_TO_SELF_ACCOUNT;
|
||||||
|
a.membership_expiration_date = time_point_sec::maximum();
|
||||||
|
a.network_fee_percentage = 0;
|
||||||
|
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT;
|
||||||
|
}).get_id() == GRAPHENE_RAKE_FEE_ACCOUNT_ID);
|
||||||
|
|
||||||
// Create more special accounts
|
// Create more special accounts
|
||||||
while( true )
|
while( true )
|
||||||
|
|
@ -391,6 +406,16 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
create<asset_dynamic_data_object>([&](asset_dynamic_data_object& a) {
|
create<asset_dynamic_data_object>([&](asset_dynamic_data_object& a) {
|
||||||
a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY;
|
a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const asset_dividend_data_object& div_asset =
|
||||||
|
create<asset_dividend_data_object>([&](asset_dividend_data_object& a) {
|
||||||
|
a.options.minimum_distribution_interval = 3*24*60*60;
|
||||||
|
a.options.minimum_fee_percentage = 10*GRAPHENE_1_PERCENT;
|
||||||
|
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
|
||||||
|
a.options.payout_interval = 7*24*60*60;
|
||||||
|
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
|
||||||
|
});
|
||||||
|
|
||||||
const asset_object& core_asset =
|
const asset_object& core_asset =
|
||||||
create<asset_object>( [&]( asset_object& a ) {
|
create<asset_object>( [&]( asset_object& a ) {
|
||||||
a.symbol = GRAPHENE_SYMBOL;
|
a.symbol = GRAPHENE_SYMBOL;
|
||||||
|
|
@ -398,15 +423,49 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
|
a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
|
||||||
a.options.flags = 0;
|
a.options.flags = 0;
|
||||||
a.options.issuer_permissions = 0;
|
a.options.issuer_permissions = 0;
|
||||||
a.issuer = GRAPHENE_NULL_ACCOUNT;
|
a.issuer = GRAPHENE_COMMITTEE_ACCOUNT;
|
||||||
a.options.core_exchange_rate.base.amount = 1;
|
a.options.core_exchange_rate.base.amount = 1;
|
||||||
a.options.core_exchange_rate.base.asset_id = asset_id_type(0);
|
a.options.core_exchange_rate.base.asset_id = asset_id_type(0);
|
||||||
a.options.core_exchange_rate.quote.amount = 1;
|
a.options.core_exchange_rate.quote.amount = 1;
|
||||||
a.options.core_exchange_rate.quote.asset_id = asset_id_type(0);
|
a.options.core_exchange_rate.quote.asset_id = asset_id_type(0);
|
||||||
a.dynamic_asset_data_id = dyn_asset.id;
|
a.dynamic_asset_data_id = dyn_asset.id;
|
||||||
});
|
a.dividend_data_id = div_asset.id;
|
||||||
|
});
|
||||||
assert( asset_id_type(core_asset.id) == asset().asset_id );
|
assert( asset_id_type(core_asset.id) == asset().asset_id );
|
||||||
assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) );
|
assert( get_balance(account_id_type(), asset_id_type()) == asset(dyn_asset.current_supply) );
|
||||||
|
|
||||||
|
// Create default dividend asset
|
||||||
|
const asset_dynamic_data_object& dyn_asset1 =
|
||||||
|
create<asset_dynamic_data_object>([&](asset_dynamic_data_object& a) {
|
||||||
|
a.current_supply = GRAPHENE_MAX_SHARE_SUPPLY;
|
||||||
|
});
|
||||||
|
const asset_dividend_data_object& div_asset1 =
|
||||||
|
create<asset_dividend_data_object>([&](asset_dividend_data_object& a) {
|
||||||
|
a.options.minimum_distribution_interval = 3*24*60*60;
|
||||||
|
a.options.minimum_fee_percentage = 10*GRAPHENE_1_PERCENT;
|
||||||
|
a.options.next_payout_time = genesis_state.initial_timestamp + fc::hours(1);
|
||||||
|
a.options.payout_interval = 7*24*60*60;
|
||||||
|
a.dividend_distribution_account = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
|
||||||
|
});
|
||||||
|
|
||||||
|
const asset_object& default_asset =
|
||||||
|
create<asset_object>( [&]( asset_object& a ) {
|
||||||
|
a.symbol = "DEF";
|
||||||
|
a.options.max_market_fee =
|
||||||
|
a.options.max_supply = genesis_state.max_core_supply;
|
||||||
|
a.precision = GRAPHENE_BLOCKCHAIN_PRECISION_DIGITS;
|
||||||
|
a.options.flags = 0;
|
||||||
|
a.options.issuer_permissions = 79;
|
||||||
|
a.issuer = GRAPHENE_RAKE_FEE_ACCOUNT_ID;
|
||||||
|
a.options.core_exchange_rate.base.amount = 1;
|
||||||
|
a.options.core_exchange_rate.base.asset_id = asset_id_type(0);
|
||||||
|
a.options.core_exchange_rate.quote.amount = 1;
|
||||||
|
a.options.core_exchange_rate.quote.asset_id = asset_id_type(1);
|
||||||
|
a.dynamic_asset_data_id = dyn_asset1.id;
|
||||||
|
a.dividend_data_id = div_asset1.id;
|
||||||
|
});
|
||||||
|
assert( default_asset.id == asset_id_type(1) );
|
||||||
|
|
||||||
// Create more special assets
|
// Create more special assets
|
||||||
while( true )
|
while( true )
|
||||||
{
|
{
|
||||||
|
|
@ -628,6 +687,8 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
{
|
{
|
||||||
total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY;
|
total_supplies[ asset_id_type(0) ] = GRAPHENE_MAX_SHARE_SUPPLY;
|
||||||
}
|
}
|
||||||
|
total_debts[ asset_id_type(1) ] =
|
||||||
|
total_supplies[ asset_id_type(1) ] = 0;
|
||||||
|
|
||||||
const auto& idx = get_index_type<asset_index>().indices().get<by_symbol>();
|
const auto& idx = get_index_type<asset_index>().indices().get<by_symbol>();
|
||||||
auto it = idx.begin();
|
auto it = idx.begin();
|
||||||
|
|
@ -647,6 +708,7 @@ void database::init_genesis(const genesis_state_type& genesis_state)
|
||||||
elog( "Genesis for asset ${aname} is not balanced\n"
|
elog( "Genesis for asset ${aname} is not balanced\n"
|
||||||
" Debt is ${debt}\n"
|
" Debt is ${debt}\n"
|
||||||
" Supply is ${supply}\n",
|
" Supply is ${supply}\n",
|
||||||
|
("aname", debt_itr->first)
|
||||||
("debt", debt_itr->second)
|
("debt", debt_itr->second)
|
||||||
("supply", supply_itr->second)
|
("supply", supply_itr->second)
|
||||||
);
|
);
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include <graphene/chain/database.hpp>
|
#include <graphene/chain/database.hpp>
|
||||||
#include <graphene/chain/fba_accumulator_id.hpp>
|
#include <graphene/chain/fba_accumulator_id.hpp>
|
||||||
#include <graphene/chain/hardfork.hpp>
|
#include <graphene/chain/hardfork.hpp>
|
||||||
|
#include <graphene/chain/is_authorized_asset.hpp>
|
||||||
|
|
||||||
#include <graphene/chain/account_object.hpp>
|
#include <graphene/chain/account_object.hpp>
|
||||||
#include <graphene/chain/asset_object.hpp>
|
#include <graphene/chain/asset_object.hpp>
|
||||||
|
|
@ -46,6 +47,8 @@
|
||||||
#include <graphene/chain/witness_object.hpp>
|
#include <graphene/chain/witness_object.hpp>
|
||||||
#include <graphene/chain/worker_object.hpp>
|
#include <graphene/chain/worker_object.hpp>
|
||||||
|
|
||||||
|
#define USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX // vesting_balance_object by_asset_balance index needed
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
template<class Index>
|
template<class Index>
|
||||||
|
|
@ -716,6 +719,501 @@ void deprecate_annual_members( database& db )
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Schedules payouts from a dividend distribution account to the current holders of the
|
||||||
|
// dividend-paying asset. This takes any deposits made to the dividend distribution account
|
||||||
|
// since the last time it was called, and distributes them to the current owners of the
|
||||||
|
// dividend-paying asset according to the amount they own.
|
||||||
|
void schedule_pending_dividend_balances(database& db,
|
||||||
|
const asset_object& dividend_holder_asset_obj,
|
||||||
|
const asset_dividend_data_object& dividend_data,
|
||||||
|
const fc::time_point_sec& current_head_block_time,
|
||||||
|
const account_balance_index& balance_index,
|
||||||
|
const vesting_balance_index& vesting_index,
|
||||||
|
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index,
|
||||||
|
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index)
|
||||||
|
{
|
||||||
|
dlog("Processing dividend payments for dividend holder asset type ${holder_asset} at time ${t}",
|
||||||
|
("holder_asset", dividend_holder_asset_obj.symbol)("t", db.head_block_time()));
|
||||||
|
auto current_distribution_account_balance_range =
|
||||||
|
balance_index.indices().get<by_account_asset>().equal_range(boost::make_tuple(dividend_data.dividend_distribution_account));
|
||||||
|
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
|
||||||
|
|
||||||
|
const auto& gpo = db.get_global_properties();
|
||||||
|
|
||||||
|
// get the list of accounts that hold nonzero balances of the dividend asset
|
||||||
|
auto holder_balances_begin =
|
||||||
|
balance_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||||
|
auto holder_balances_end =
|
||||||
|
balance_index.indices().get<by_asset_balance>().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type()));
|
||||||
|
uint32_t holder_account_count = std::distance(holder_balances_begin, holder_balances_end);
|
||||||
|
uint64_t distribution_base_fee = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_base_fee;
|
||||||
|
uint32_t distribution_fee_per_holder = gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder;
|
||||||
|
// the fee, in BTS, for distributing each asset in the account
|
||||||
|
uint64_t total_fee_per_asset_in_core = distribution_base_fee + holder_account_count * (uint64_t)distribution_fee_per_holder;
|
||||||
|
|
||||||
|
std::map<account_id_type, share_type> vesting_amounts;
|
||||||
|
#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX
|
||||||
|
// get only once a collection of accounts that hold nonzero vesting balances of the dividend asset
|
||||||
|
auto vesting_balances_begin =
|
||||||
|
vesting_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||||
|
auto vesting_balances_end =
|
||||||
|
vesting_index.indices().get<by_asset_balance>().upper_bound(boost::make_tuple(dividend_holder_asset_obj.id, share_type()));
|
||||||
|
for (const vesting_balance_object& vesting_balance_obj : boost::make_iterator_range(vesting_balances_begin, vesting_balances_end))
|
||||||
|
{
|
||||||
|
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
|
||||||
|
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
|
||||||
|
("owner", vesting_balance_obj.owner(db).name)
|
||||||
|
("amount", vesting_balance_obj.balance.amount));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// get only once a collection of accounts that hold nonzero vesting balances of the dividend asset
|
||||||
|
const auto& vesting_balances = vesting_index.indices().get<by_id>();
|
||||||
|
for (const vesting_balance_object& vesting_balance_obj : vesting_balances)
|
||||||
|
{
|
||||||
|
if (vesting_balance_obj.balance.asset_id == dividend_holder_asset_obj.id && vesting_balance_obj.balance.amount)
|
||||||
|
{
|
||||||
|
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
|
||||||
|
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
|
||||||
|
("owner", vesting_balance_obj.owner(db).name)
|
||||||
|
("amount", vesting_balance_obj.balance.amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
// when we pay out the dividends to the holders, we need to know the total balance of the dividend asset in all
|
||||||
|
// accounts other than the distribution account (it would be silly to distribute dividends back to
|
||||||
|
// the distribution account)
|
||||||
|
share_type total_balance_of_dividend_asset;
|
||||||
|
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||||
|
if (holder_balance_object.owner != dividend_data.dividend_distribution_account)
|
||||||
|
{
|
||||||
|
total_balance_of_dividend_asset += holder_balance_object.balance;
|
||||||
|
auto itr = vesting_amounts.find(holder_balance_object.owner);
|
||||||
|
if (itr != vesting_amounts.end())
|
||||||
|
total_balance_of_dividend_asset += itr->second;
|
||||||
|
}
|
||||||
|
// loop through all of the assets currently or previously held in the distribution account
|
||||||
|
while (current_distribution_account_balance_iter != current_distribution_account_balance_range.second ||
|
||||||
|
previous_distribution_account_balance_iter != previous_distribution_account_balance_range.second)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
// First, figure out how much the balance on this asset has changed since the last sharing out
|
||||||
|
share_type current_balance;
|
||||||
|
share_type previous_balance;
|
||||||
|
asset_id_type payout_asset_type;
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
// there are no more previous balances or there is no previous balance for this particular 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)
|
||||||
|
{
|
||||||
|
// there are no more current balances or there is no current balance for this particular previous 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
|
||||||
|
{
|
||||||
|
// we have both a previous and a current balance for this asset type
|
||||||
|
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;
|
||||||
|
|
||||||
|
// Next, figure out if we want to share this out -- if the amount added to the distribution
|
||||||
|
// account since last payout is too small, we won't bother.
|
||||||
|
|
||||||
|
share_type total_fee_per_asset_in_payout_asset;
|
||||||
|
const asset_object* payout_asset_object = nullptr;
|
||||||
|
if (payout_asset_type == asset_id_type())
|
||||||
|
{
|
||||||
|
payout_asset_object = &db.get_core_asset();
|
||||||
|
total_fee_per_asset_in_payout_asset = total_fee_per_asset_in_core;
|
||||||
|
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||||
|
("payout_asset_type", asset_id_type()(db).symbol)
|
||||||
|
("fee", asset(total_fee_per_asset_in_core, asset_id_type())));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// figure out what the total fee is in terms of the payout asset
|
||||||
|
const asset_index& asset_object_index = db.get_index_type<asset_index>();
|
||||||
|
auto payout_asset_object_iter = asset_object_index.indices().find(payout_asset_type);
|
||||||
|
FC_ASSERT(payout_asset_object_iter != asset_object_index.indices().end());
|
||||||
|
|
||||||
|
payout_asset_object = &*payout_asset_object_iter;
|
||||||
|
asset total_fee_per_asset = asset(total_fee_per_asset_in_core, asset_id_type()) * payout_asset_object->options.core_exchange_rate;
|
||||||
|
FC_ASSERT(total_fee_per_asset.asset_id == payout_asset_type);
|
||||||
|
|
||||||
|
total_fee_per_asset_in_payout_asset = total_fee_per_asset.amount;
|
||||||
|
dlog("Fee for distributing ${payout_asset_type}: ${fee}",
|
||||||
|
("payout_asset_type", payout_asset_type(db).symbol)("fee", total_fee_per_asset_in_payout_asset));
|
||||||
|
}
|
||||||
|
|
||||||
|
share_type minimum_shares_to_distribute;
|
||||||
|
if (dividend_data.options.minimum_fee_percentage)
|
||||||
|
{
|
||||||
|
fc::uint128_t minimum_amount_to_distribute = total_fee_per_asset_in_payout_asset.value;
|
||||||
|
minimum_amount_to_distribute *= 100 * GRAPHENE_1_PERCENT;
|
||||||
|
minimum_amount_to_distribute /= dividend_data.options.minimum_fee_percentage;
|
||||||
|
wdump((total_fee_per_asset_in_payout_asset)(dividend_data.options));
|
||||||
|
minimum_shares_to_distribute = minimum_amount_to_distribute.to_uint64();
|
||||||
|
}
|
||||||
|
|
||||||
|
dlog("Processing dividend payments of asset type ${payout_asset_type}, delta balance is ${delta_balance}", ("payout_asset_type", payout_asset_type(db).symbol)("delta_balance", delta_balance));
|
||||||
|
if (delta_balance > 0)
|
||||||
|
{
|
||||||
|
if (delta_balance >= minimum_shares_to_distribute)
|
||||||
|
{
|
||||||
|
// first, pay the fee for scheduling these dividend payments
|
||||||
|
if (payout_asset_type == asset_id_type())
|
||||||
|
{
|
||||||
|
// pay fee to network
|
||||||
|
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||||
|
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||||
|
});
|
||||||
|
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||||
|
asset(-total_fee_per_asset_in_core, asset_id_type()));
|
||||||
|
delta_balance -= total_fee_per_asset_in_core;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const asset_dynamic_data_object& dynamic_data = payout_asset_object->dynamic_data(db);
|
||||||
|
if (dynamic_data.fee_pool < total_fee_per_asset_in_core)
|
||||||
|
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||||
|
"because insufficient funds in fee pool (need: ${need}, have: ${have})",
|
||||||
|
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||||
|
("payout_asset_type", payout_asset_object->symbol)
|
||||||
|
("need", asset(total_fee_per_asset_in_core, asset_id_type()))
|
||||||
|
("have", asset(dynamic_data.fee_pool, payout_asset_type)));
|
||||||
|
// deduct the fee from the dividend distribution account
|
||||||
|
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||||
|
asset(-total_fee_per_asset_in_payout_asset, payout_asset_type));
|
||||||
|
// convert it to core
|
||||||
|
db.modify(payout_asset_object->dynamic_data(db), [total_fee_per_asset_in_core, total_fee_per_asset_in_payout_asset](asset_dynamic_data_object& d) {
|
||||||
|
d.fee_pool -= total_fee_per_asset_in_core;
|
||||||
|
d.accumulated_fees += total_fee_per_asset_in_payout_asset;
|
||||||
|
});
|
||||||
|
// and pay it to the network
|
||||||
|
db.modify(asset_dynamic_data_id_type()(db), [total_fee_per_asset_in_core](asset_dynamic_data_object& d) {
|
||||||
|
d.accumulated_fees += total_fee_per_asset_in_core;
|
||||||
|
});
|
||||||
|
delta_balance -= total_fee_per_asset_in_payout_asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
dlog("There are ${count} holders of the dividend-paying asset, with a total balance of ${total}",
|
||||||
|
("count", holder_account_count)
|
||||||
|
("total", total_balance_of_dividend_asset));
|
||||||
|
share_type remaining_amount_to_distribute = delta_balance;
|
||||||
|
|
||||||
|
// credit each account with their portion, don't send any back to the dividend distribution account
|
||||||
|
for (const account_balance_object& holder_balance_object : boost::make_iterator_range(holder_balances_begin, holder_balances_end))
|
||||||
|
{
|
||||||
|
if (holder_balance_object.owner == dividend_data.dividend_distribution_account) continue;
|
||||||
|
|
||||||
|
auto holder_balance = holder_balance_object.balance;
|
||||||
|
|
||||||
|
auto itr = vesting_amounts.find(holder_balance_object.owner);
|
||||||
|
if (itr != vesting_amounts.end())
|
||||||
|
holder_balance += itr->second;
|
||||||
|
//if (holder_balance.value)
|
||||||
|
|
||||||
|
fc::uint128_t amount_to_credit(delta_balance.value);
|
||||||
|
amount_to_credit *= holder_balance.value;
|
||||||
|
amount_to_credit /= total_balance_of_dividend_asset.value;
|
||||||
|
share_type shares_to_credit((int64_t)amount_to_credit.to_uint64());
|
||||||
|
if (shares_to_credit.value)
|
||||||
|
{
|
||||||
|
wdump((delta_balance.value)(holder_balance)(total_balance_of_dividend_asset));
|
||||||
|
|
||||||
|
remaining_amount_to_distribute -= shares_to_credit;
|
||||||
|
|
||||||
|
dlog("Crediting account ${account} with ${amount}",
|
||||||
|
("account", holder_balance_object.owner(db).name)
|
||||||
|
("amount", asset(shares_to_credit, payout_asset_type)));
|
||||||
|
auto pending_payout_iter =
|
||||||
|
pending_payout_balance_index.indices().get<by_dividend_payout_account>().find(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type, holder_balance_object.owner));
|
||||||
|
if (pending_payout_iter == pending_payout_balance_index.indices().get<by_dividend_payout_account>().end())
|
||||||
|
db.create<pending_dividend_payout_balance_for_holder_object>( [&]( pending_dividend_payout_balance_for_holder_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_for_holder_object& pending_balance ){
|
||||||
|
pending_balance.pending_balance += shares_to_credit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pending_payout : pending_payout_balance_index.indices())
|
||||||
|
if (pending_payout.pending_balance.value)
|
||||||
|
dlog("Pending payout: ${account_name} -> ${amount}",
|
||||||
|
("account_name", pending_payout.owner(db).name)
|
||||||
|
("amount", asset(pending_payout.pending_balance, pending_payout.dividend_payout_asset_type)));
|
||||||
|
dlog("Remaining balance not paid out: ${amount}",
|
||||||
|
("amount", asset(remaining_amount_to_distribute, payout_asset_type)));
|
||||||
|
|
||||||
|
share_type distributed_amount = delta_balance - remaining_amount_to_distribute;
|
||||||
|
if (previous_distribution_account_balance_iter == previous_distribution_account_balance_range.second ||
|
||||||
|
previous_distribution_account_balance_iter->dividend_payout_asset_type != payout_asset_type)
|
||||||
|
db.create<total_distributed_dividend_balance_object>( [&]( total_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, [&]( total_distributed_dividend_balance_object& obj ){
|
||||||
|
obj.balance_at_last_maintenance_interval += distributed_amount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else
|
||||||
|
FC_THROW("Not distributing dividends for ${holder_asset_type} in asset ${payout_asset_type} "
|
||||||
|
"because amount ${delta_balance} is too small an amount to distribute.",
|
||||||
|
("holder_asset_type", dividend_holder_asset_obj.symbol)
|
||||||
|
("payout_asset_type", payout_asset_object->symbol)
|
||||||
|
("delta_balance", asset(delta_balance, payout_asset_type)));
|
||||||
|
}
|
||||||
|
else if (delta_balance < 0)
|
||||||
|
{
|
||||||
|
// some amount of the asset has been withdrawn from the dividend_distribution_account,
|
||||||
|
// meaning the current pending payout balances will add up to more than our current balance.
|
||||||
|
// This should be extremely rare (caused by an override transfer by the asset owner).
|
||||||
|
// Reduce all pending payouts proportionally
|
||||||
|
share_type total_pending_balances;
|
||||||
|
auto pending_payouts_range =
|
||||||
|
pending_payout_balance_index.indices().get<by_dividend_payout_account>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id, payout_asset_type));
|
||||||
|
|
||||||
|
for (const pending_dividend_payout_balance_for_holder_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_for_holder_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_for_holder_object& pending_balance ){
|
||||||
|
pending_balance.pending_balance -= shares_to_debit;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we're here, we know there must be a previous balance, so just adjust it by the
|
||||||
|
// amount we just reclaimed
|
||||||
|
db.modify(*previous_distribution_account_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||||
|
obj.balance_at_last_maintenance_interval += delta_balance;
|
||||||
|
assert(obj.balance_at_last_maintenance_interval == current_balance);
|
||||||
|
});
|
||||||
|
} // end if deposit was large enough to distribute
|
||||||
|
}
|
||||||
|
catch (const fc::exception& e)
|
||||||
|
{
|
||||||
|
dlog("${e}", ("e", e));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
db.modify(dividend_data, [current_head_block_time](asset_dividend_data_object& dividend_data_obj) {
|
||||||
|
dividend_data_obj.last_scheduled_distribution_time = current_head_block_time;
|
||||||
|
dividend_data_obj.last_distribution_time = current_head_block_time;
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_dividend_assets(database& db)
|
||||||
|
{
|
||||||
|
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 vesting_balance_index& vbalance_index = db.get_index_type<vesting_balance_index>();
|
||||||
|
const total_distributed_dividend_balance_object_index& distributed_dividend_balance_index = db.get_index_type<total_distributed_dividend_balance_object_index>();
|
||||||
|
const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type<pending_dividend_payout_balance_for_holder_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);
|
||||||
|
const account_object& dividend_distribution_account_object = dividend_data.dividend_distribution_account(db);
|
||||||
|
|
||||||
|
fc::time_point_sec current_head_block_time = db.head_block_time();
|
||||||
|
|
||||||
|
schedule_pending_dividend_balances(db, dividend_holder_asset_obj, dividend_data, current_head_block_time,
|
||||||
|
balance_index, vbalance_index, distributed_dividend_balance_index, pending_payout_balance_index);
|
||||||
|
if (dividend_data.options.next_payout_time &&
|
||||||
|
db.head_block_time() >= *dividend_data.options.next_payout_time)
|
||||||
|
{
|
||||||
|
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
|
||||||
|
std::map<asset_id_type, share_type> amounts_paid_out_by_asset;
|
||||||
|
|
||||||
|
auto pending_payouts_range =
|
||||||
|
pending_payout_balance_index.indices().get<by_dividend_account_payout>().equal_range(boost::make_tuple(dividend_holder_asset_obj.id));
|
||||||
|
// the pending_payouts_range is all payouts for this dividend asset, sorted by the holder's account
|
||||||
|
// we iterate in this order so we can build up a list of payouts for each account to put in the
|
||||||
|
// virtual op
|
||||||
|
flat_set<asset> payouts_for_this_holder;
|
||||||
|
fc::optional<account_id_type> last_holder_account_id;
|
||||||
|
|
||||||
|
// cache the assets the distribution account is approved to send, we will be asking
|
||||||
|
// for these often
|
||||||
|
flat_map<asset_id_type, bool> approved_assets; // assets that the dividend distribution account is authorized to send/receive
|
||||||
|
auto is_asset_approved_for_distribution_account = [&](const asset_id_type& asset_id) {
|
||||||
|
auto approved_assets_iter = approved_assets.find(asset_id);
|
||||||
|
if (approved_assets_iter != approved_assets.end())
|
||||||
|
return approved_assets_iter->second;
|
||||||
|
bool is_approved = is_authorized_asset(db, dividend_distribution_account_object,
|
||||||
|
asset_id(db));
|
||||||
|
approved_assets[asset_id] = is_approved;
|
||||||
|
return is_approved;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto pending_balance_object_iter = pending_payouts_range.first; pending_balance_object_iter != pending_payouts_range.second; )
|
||||||
|
{
|
||||||
|
const pending_dividend_payout_balance_for_holder_object& pending_balance_object = *pending_balance_object_iter;
|
||||||
|
|
||||||
|
if (last_holder_account_id && *last_holder_account_id != pending_balance_object.owner && payouts_for_this_holder.size())
|
||||||
|
{
|
||||||
|
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||||
|
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||||
|
*last_holder_account_id,
|
||||||
|
payouts_for_this_holder));
|
||||||
|
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||||
|
payouts_for_this_holder.clear();
|
||||||
|
last_holder_account_id.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (pending_balance_object.pending_balance.value &&
|
||||||
|
is_authorized_asset(db, pending_balance_object.owner(db), pending_balance_object.dividend_payout_asset_type(db)) &&
|
||||||
|
is_asset_approved_for_distribution_account(pending_balance_object.dividend_payout_asset_type))
|
||||||
|
{
|
||||||
|
dlog("Processing payout of ${asset} to account ${account}",
|
||||||
|
("asset", asset(pending_balance_object.pending_balance, pending_balance_object.dividend_payout_asset_type))
|
||||||
|
("account", pending_balance_object.owner(db).name));
|
||||||
|
|
||||||
|
db.adjust_balance(pending_balance_object.owner,
|
||||||
|
asset(pending_balance_object.pending_balance,
|
||||||
|
pending_balance_object.dividend_payout_asset_type));
|
||||||
|
payouts_for_this_holder.insert(asset(pending_balance_object.pending_balance,
|
||||||
|
pending_balance_object.dividend_payout_asset_type));
|
||||||
|
last_holder_account_id = pending_balance_object.owner;
|
||||||
|
amounts_paid_out_by_asset[pending_balance_object.dividend_payout_asset_type] += pending_balance_object.pending_balance;
|
||||||
|
|
||||||
|
db.modify(pending_balance_object, [&]( pending_dividend_payout_balance_for_holder_object& pending_balance ){
|
||||||
|
pending_balance.pending_balance = 0;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
++pending_balance_object_iter;
|
||||||
|
}
|
||||||
|
// we will always be left with the last holder's data, generate the virtual op for it now.
|
||||||
|
if (last_holder_account_id && payouts_for_this_holder.size())
|
||||||
|
{
|
||||||
|
// we've moved on to a new account, generate the dividend payment virtual op for the previous one
|
||||||
|
db.push_applied_operation(asset_dividend_distribution_operation(dividend_holder_asset_obj.id,
|
||||||
|
*last_holder_account_id,
|
||||||
|
payouts_for_this_holder));
|
||||||
|
dlog("Just pushed virtual op for payout to ${account}", ("account", (*last_holder_account_id)(db).name));
|
||||||
|
}
|
||||||
|
|
||||||
|
// now debit the total amount of dividends paid out from the distribution account
|
||||||
|
// and reduce the distributed_balances accordingly
|
||||||
|
|
||||||
|
for (const auto& value : amounts_paid_out_by_asset)
|
||||||
|
{
|
||||||
|
const asset_id_type& asset_paid_out = value.first;
|
||||||
|
const share_type& amount_paid_out = value.second;
|
||||||
|
|
||||||
|
db.adjust_balance(dividend_data.dividend_distribution_account,
|
||||||
|
asset(-amount_paid_out,
|
||||||
|
asset_paid_out));
|
||||||
|
auto distributed_balance_iter =
|
||||||
|
distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().find(boost::make_tuple(dividend_holder_asset_obj.id,
|
||||||
|
asset_paid_out));
|
||||||
|
assert(distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end());
|
||||||
|
if (distributed_balance_iter != distributed_dividend_balance_index.indices().get<by_dividend_payout_asset>().end())
|
||||||
|
db.modify(*distributed_balance_iter, [&]( total_distributed_dividend_balance_object& obj ){
|
||||||
|
obj.balance_at_last_maintenance_interval -= amount_paid_out; // 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) {
|
||||||
|
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,9 +1221,12 @@ 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;
|
||||||
|
std::map<account_id_type, share_type> vesting_amounts;
|
||||||
|
|
||||||
vote_tally_helper(database& d, const global_property_object& gpo)
|
vote_tally_helper(database& d, const global_property_object& gpo)
|
||||||
: d(d), props(gpo)
|
: d(d), props(gpo)
|
||||||
|
|
@ -734,6 +1235,33 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1);
|
d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1);
|
||||||
d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1);
|
d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1);
|
||||||
d._total_voting_stake = 0;
|
d._total_voting_stake = 0;
|
||||||
|
|
||||||
|
const vesting_balance_index& vesting_index = d.get_index_type<vesting_balance_index>();
|
||||||
|
#ifdef USE_VESTING_OBJECT_BY_ASSET_BALANCE_INDEX
|
||||||
|
auto vesting_balances_begin =
|
||||||
|
vesting_index.indices().get<by_asset_balance>().lower_bound(boost::make_tuple(asset_id_type()));
|
||||||
|
auto vesting_balances_end =
|
||||||
|
vesting_index.indices().get<by_asset_balance>().upper_bound(boost::make_tuple(asset_id_type(), share_type()));
|
||||||
|
for (const vesting_balance_object& vesting_balance_obj : boost::make_iterator_range(vesting_balances_begin, vesting_balances_end))
|
||||||
|
{
|
||||||
|
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
|
||||||
|
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
|
||||||
|
("owner", vesting_balance_obj.owner(d).name)
|
||||||
|
("amount", vesting_balance_obj.balance.amount));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const auto& vesting_balances = vesting_index.indices().get<by_id>();
|
||||||
|
for (const vesting_balance_object& vesting_balance_obj : vesting_balances)
|
||||||
|
{
|
||||||
|
if (vesting_balance_obj.balance.asset_id == asset_id_type() && vesting_balance_obj.balance.amount)
|
||||||
|
{
|
||||||
|
vesting_amounts[vesting_balance_obj.owner] += vesting_balance_obj.balance.amount;
|
||||||
|
dlog("Vesting balance for account: ${owner}, amount: ${amount}",
|
||||||
|
("owner", vesting_balance_obj.owner(d).name)
|
||||||
|
("amount", vesting_balance_obj.balance.amount));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void operator()(const account_object& stake_account) {
|
void operator()(const account_object& stake_account) {
|
||||||
|
|
@ -752,6 +1280,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
||||||
+ (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0)
|
+ (stake_account.cashback_vb.valid() ? (*stake_account.cashback_vb)(d).balance.amount.value: 0)
|
||||||
+ d.get_balance(stake_account.get_id(), asset_id_type()).amount.value;
|
+ d.get_balance(stake_account.get_id(), asset_id_type()).amount.value;
|
||||||
|
|
||||||
|
auto itr = vesting_amounts.find(stake_account.id);
|
||||||
|
if (itr != vesting_amounts.end())
|
||||||
|
voting_stake += itr->second.value;
|
||||||
for( vote_id_type id : opinion_account.options.votes )
|
for( vote_id_type id : opinion_account.options.votes )
|
||||||
{
|
{
|
||||||
uint32_t offset = id.instance();
|
uint32_t offset = id.instance();
|
||||||
|
|
|
||||||
|
|
@ -73,6 +73,12 @@ 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_dividend_distribution_operation& op )
|
||||||
|
{
|
||||||
|
_impacted.insert( op.account_id );
|
||||||
|
}
|
||||||
|
|
||||||
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 )
|
||||||
|
|
@ -192,8 +198,8 @@ struct get_impacted_account_visitor
|
||||||
void operator()(const betting_market_group_create_operation&){}
|
void operator()(const betting_market_group_create_operation&){}
|
||||||
void operator()(const betting_market_create_operation&){}
|
void operator()(const betting_market_create_operation&){}
|
||||||
void operator()(const bet_place_operation&){}
|
void operator()(const bet_place_operation&){}
|
||||||
void operator()(const betting_market_resolve_operation&){}
|
void operator()(const betting_market_group_resolve_operation&){}
|
||||||
void operator()(const betting_market_resolved_operation &){}
|
void operator()(const betting_market_group_resolved_operation &){}
|
||||||
void operator()(const bet_matched_operation &){}
|
void operator()(const bet_matched_operation &){}
|
||||||
void operator()(const bet_cancel_operation&){}
|
void operator()(const bet_cancel_operation&){}
|
||||||
void operator()(const bet_canceled_operation &){}
|
void operator()(const bet_canceled_operation &){}
|
||||||
|
|
|
||||||
|
|
@ -312,6 +312,31 @@ namespace graphene { namespace chain {
|
||||||
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_for_holder_object : public abstract_object<pending_dividend_payout_balance_for_holder_object>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static const uint8_t space_id = implementation_ids;
|
||||||
|
static const uint8_t type_id = impl_pending_dividend_payout_balance_for_holder_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;
|
||||||
/**
|
/**
|
||||||
|
|
@ -367,6 +392,49 @@ 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_payout_account{}; // use when calculating pending payouts
|
||||||
|
struct by_dividend_account_payout{}; // use when doing actual payouts
|
||||||
|
struct by_account_dividend_payout{}; // use in get_full_accounts()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup object_index
|
||||||
|
*/
|
||||||
|
typedef multi_index_container<
|
||||||
|
pending_dividend_payout_balance_for_holder_object,
|
||||||
|
indexed_by<
|
||||||
|
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||||
|
ordered_unique< tag<by_dividend_payout_account>,
|
||||||
|
composite_key<
|
||||||
|
pending_dividend_payout_balance_for_holder_object,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
ordered_unique< tag<by_dividend_account_payout>,
|
||||||
|
composite_key<
|
||||||
|
pending_dividend_payout_balance_for_holder_object,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
ordered_unique< tag<by_account_dividend_payout>,
|
||||||
|
composite_key<
|
||||||
|
pending_dividend_payout_balance_for_holder_object,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, account_id_type, &pending_dividend_payout_balance_for_holder_object::owner>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_holder_asset_type>,
|
||||||
|
member<pending_dividend_payout_balance_for_holder_object, asset_id_type, &pending_dividend_payout_balance_for_holder_object::dividend_payout_asset_type>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> pending_dividend_payout_balance_for_holder_object_multi_index_type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ingroup object_index
|
||||||
|
*/
|
||||||
|
typedef generic_index<pending_dividend_payout_balance_for_holder_object, pending_dividend_payout_balance_for_holder_object_multi_index_type> pending_dividend_payout_balance_for_holder_object_index;
|
||||||
|
|
||||||
}}
|
}}
|
||||||
|
|
||||||
FC_REFLECT_DERIVED( graphene::chain::account_object,
|
FC_REFLECT_DERIVED( graphene::chain::account_object,
|
||||||
|
|
@ -395,3 +463,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_for_holder_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); }
|
||||||
|
|
@ -249,6 +256,84 @@ 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
|
||||||
|
/// This field is reset any time the dividend_asset_options are updated
|
||||||
|
fc::optional<time_point_sec> last_scheduled_payout_time;
|
||||||
|
|
||||||
|
/// The time payouts on this asset were last processed
|
||||||
|
/// (this should be the maintenance interval at or after last_scheduled_payout_time)
|
||||||
|
/// This can be displayed for the user
|
||||||
|
fc::optional<time_point_sec> last_payout_time;
|
||||||
|
|
||||||
|
/// The time pending payouts on this asset were last computed, used for
|
||||||
|
/// correctly computing the next pending payout time.
|
||||||
|
/// This field is reset any time the dividend_asset_options are updated
|
||||||
|
fc::optional<time_point_sec> last_scheduled_distribution_time;
|
||||||
|
|
||||||
|
/// The time pending payouts on this asset were last computed.
|
||||||
|
/// (this should be the maintenance interval at or after last_scheduled_distribution_time)
|
||||||
|
/// This can be displayed for the user
|
||||||
|
fc::optional<time_point_sec> last_distribution_time;
|
||||||
|
|
||||||
|
/// The account which collects pending payouts
|
||||||
|
account_id_type dividend_distribution_account;
|
||||||
|
};
|
||||||
|
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 total_distributed_dividend_balance_object : public abstract_object<total_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<
|
||||||
|
total_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<
|
||||||
|
total_distributed_dividend_balance_object,
|
||||||
|
member<total_distributed_dividend_balance_object, asset_id_type, &total_distributed_dividend_balance_object::dividend_holder_asset_type>,
|
||||||
|
member<total_distributed_dividend_balance_object, asset_id_type, &total_distributed_dividend_balance_object::dividend_payout_asset_type>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
>
|
||||||
|
> total_distributed_dividend_balance_object_multi_index_type;
|
||||||
|
typedef generic_index<total_distributed_dividend_balance_object, total_distributed_dividend_balance_object_multi_index_type> total_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),
|
||||||
|
|
@ -265,6 +350,21 @@ FC_REFLECT_DERIVED( graphene::chain::asset_bitasset_data_object, (graphene::db::
|
||||||
(settlement_fund)
|
(settlement_fund)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT_DERIVED( graphene::chain::asset_dividend_data_object, (graphene::db::object),
|
||||||
|
(options)
|
||||||
|
(last_scheduled_payout_time)
|
||||||
|
(last_payout_time )
|
||||||
|
(last_scheduled_distribution_time)
|
||||||
|
(last_distribution_time)
|
||||||
|
(dividend_distribution_account)
|
||||||
|
)
|
||||||
|
|
||||||
|
FC_REFLECT_DERIVED( graphene::chain::total_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)
|
||||||
(precision)
|
(precision)
|
||||||
|
|
@ -273,4 +373,5 @@ FC_REFLECT_DERIVED( graphene::chain::asset_object, (graphene::db::object),
|
||||||
(dynamic_asset_data_id)
|
(dynamic_asset_data_id)
|
||||||
(bitasset_data_id)
|
(bitasset_data_id)
|
||||||
(buyback_account)
|
(buyback_account)
|
||||||
|
(dividend_data_id)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -86,15 +86,16 @@ namespace graphene { namespace chain {
|
||||||
const bet_object* _bet_to_cancel;
|
const bet_object* _bet_to_cancel;
|
||||||
};
|
};
|
||||||
|
|
||||||
class betting_market_resolve_evaluator : public evaluator<betting_market_resolve_evaluator>
|
class betting_market_group_resolve_evaluator : public evaluator<betting_market_group_resolve_evaluator>
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
typedef betting_market_resolve_operation operation_type;
|
typedef betting_market_group_resolve_operation operation_type;
|
||||||
|
|
||||||
void_result do_evaluate( const betting_market_resolve_operation& o );
|
void_result do_evaluate( const betting_market_group_resolve_operation& o );
|
||||||
void_result do_apply( const betting_market_resolve_operation& o );
|
void_result do_apply( const betting_market_group_resolve_operation& o );
|
||||||
private:
|
private:
|
||||||
const betting_market_object* _betting_market;
|
const betting_market_group_object* _betting_market_group;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
} } // graphene::chain
|
} } // graphene::chain
|
||||||
|
|
|
||||||
|
|
@ -164,12 +164,16 @@
|
||||||
#define GRAPHENE_TEMP_ACCOUNT (graphene::chain::account_id_type(4))
|
#define GRAPHENE_TEMP_ACCOUNT (graphene::chain::account_id_type(4))
|
||||||
/// Represents the canonical account for specifying you will vote directly (as opposed to a proxy)
|
/// Represents the canonical account for specifying you will vote directly (as opposed to a proxy)
|
||||||
#define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5))
|
#define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5))
|
||||||
|
///
|
||||||
|
#define GRAPHENE_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6))
|
||||||
/// Sentinel value used in the scheduler.
|
/// Sentinel value used in the scheduler.
|
||||||
#define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0))
|
#define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0))
|
||||||
///@}
|
///@}
|
||||||
|
|
||||||
#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743))
|
#define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET (asset_id_type(743))
|
||||||
|
|
||||||
|
#define GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE (3*GRAPHENE_1_PERCENT)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Betting-related constants.
|
* Betting-related constants.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -375,8 +375,10 @@ namespace graphene { namespace chain {
|
||||||
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
|
void cancel_bet(const bet_object& bet, bool create_virtual_op = true);
|
||||||
void cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market);
|
void cancel_all_unmatched_bets_on_betting_market(const betting_market_object& betting_market);
|
||||||
void cancel_all_betting_markets_for_event(const event_object&);
|
void cancel_all_betting_markets_for_event(const event_object&);
|
||||||
void resolve_betting_market(const betting_market_object& betting_market,
|
void validate_betting_market_group_resolutions(const betting_market_group_object& betting_market_group,
|
||||||
betting_market_resolution_type resolution);
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
|
||||||
|
void resolve_betting_market_group(const betting_market_group_object& betting_market_group,
|
||||||
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions);
|
||||||
/**
|
/**
|
||||||
* @brief Process a new bet
|
* @brief Process a new bet
|
||||||
* @param new_bet_object The new bet to process
|
* @param new_bet_object The new bet to process
|
||||||
|
|
|
||||||
|
|
@ -112,6 +112,53 @@ 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;
|
||||||
|
/// Each dividend distribution incurs a fee that is based on the number of accounts
|
||||||
|
/// that hold the dividend asset, not as a percentage of the amount paid out.
|
||||||
|
/// This parameter prevents assets from being distributed unless the fee is less than
|
||||||
|
/// the percentage here, to prevent a slow trickle of deposits to the account from being
|
||||||
|
/// completely consumed.
|
||||||
|
/// In other words, if you set this parameter to 10% and the fees work out to 100 BTS
|
||||||
|
/// to share out, balances in the dividend distribution accounts will not be shared out
|
||||||
|
/// if the balance is less than 10000 BTS.
|
||||||
|
uint64_t minimum_fee_percentage;
|
||||||
|
|
||||||
|
/// Normally, pending dividend payments are calculated each maintenance interval in
|
||||||
|
/// which there are balances in the dividend distribution account. At present, this
|
||||||
|
/// is once per hour on the BitShares blockchain. If this is too often (too expensive
|
||||||
|
/// in fees or to computationally-intensive for the blockchain) this can be increased.
|
||||||
|
/// If you set this to, for example, one day, distributions will take place on even
|
||||||
|
/// multiples of one day, allowing deposits to the distribution account to accumulate
|
||||||
|
/// for 23 maintenance intervals and then computing the pending payouts on the 24th.
|
||||||
|
///
|
||||||
|
/// Payouts will always occur at the next payout time whether or not it falls on a
|
||||||
|
/// multiple of the distribution interval, and the timer on the distribution interval
|
||||||
|
/// are reset at payout time. So if you have the distribution interval at three days
|
||||||
|
/// and the payout interval at one week, payouts will occur at days 3, 6, 7, 10, 13, 14...
|
||||||
|
fc::optional<uint32_t> minimum_distribution_interval;
|
||||||
|
|
||||||
|
extensions_type extensions;
|
||||||
|
|
||||||
|
/// Perform internal consistency checks.
|
||||||
|
/// @throws fc::exception if any check fails
|
||||||
|
void validate()const;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ingroup operations
|
* @ingroup operations
|
||||||
|
|
@ -236,6 +283,59 @@ namespace graphene { namespace chain {
|
||||||
{ return 0; }
|
{ return 0; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Virtual op generated when a dividend asset pays out dividends
|
||||||
|
*/
|
||||||
|
struct asset_dividend_distribution_operation : public base_operation
|
||||||
|
{
|
||||||
|
asset_dividend_distribution_operation() {}
|
||||||
|
asset_dividend_distribution_operation(const asset_id_type& dividend_asset_id,
|
||||||
|
const account_id_type& account_id,
|
||||||
|
const flat_set<asset>& amounts) :
|
||||||
|
dividend_asset_id(dividend_asset_id),
|
||||||
|
account_id(account_id),
|
||||||
|
amounts(amounts)
|
||||||
|
{}
|
||||||
|
struct fee_parameters_type {
|
||||||
|
/* note: this is a virtual op and there are no fees directly charged for it */
|
||||||
|
|
||||||
|
/* Whenever the system computes the pending dividend payments for an asset,
|
||||||
|
* it charges the distribution_base_fee + distribution_fee_per_holder.
|
||||||
|
* The computational cost of distributing the dividend payment is proportional
|
||||||
|
* to the number of dividend holders the asset is divided up among.
|
||||||
|
*/
|
||||||
|
/** This fee is charged whenever the system schedules pending dividend
|
||||||
|
* payments.
|
||||||
|
*/
|
||||||
|
uint64_t distribution_base_fee;
|
||||||
|
/** This fee is charged (in addition to the distribution_base_fee) for each
|
||||||
|
* user the dividend payment is shared out amongst
|
||||||
|
*/
|
||||||
|
uint32_t distribution_fee_per_holder;
|
||||||
|
};
|
||||||
|
|
||||||
|
asset fee;
|
||||||
|
|
||||||
|
/// The dividend-paying asset which triggered this payout
|
||||||
|
asset_id_type dividend_asset_id;
|
||||||
|
|
||||||
|
/// The user account receiving the dividends
|
||||||
|
account_id_type account_id;
|
||||||
|
|
||||||
|
/// The amounts received
|
||||||
|
flat_set<asset> amounts;
|
||||||
|
|
||||||
|
extensions_type extensions;
|
||||||
|
|
||||||
|
account_id_type fee_payer()const { return account_id; }
|
||||||
|
void validate()const {
|
||||||
|
FC_ASSERT( false, "virtual operation" );
|
||||||
|
}
|
||||||
|
|
||||||
|
share_type calculate_fee(const fee_parameters_type& params)const
|
||||||
|
{ return 0; }
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @ingroup operations
|
* @ingroup operations
|
||||||
*/
|
*/
|
||||||
|
|
@ -319,6 +419,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 +591,15 @@ FC_REFLECT( graphene::chain::asset_options,
|
||||||
(description)
|
(description)
|
||||||
(extensions)
|
(extensions)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT( graphene::chain::dividend_asset_options,
|
||||||
|
(next_payout_time)
|
||||||
|
(payout_interval)
|
||||||
|
(minimum_fee_percentage)
|
||||||
|
(minimum_distribution_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,11 +618,12 @@ 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) )
|
||||||
FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::asset_reserve_operation::fee_parameters_type, (fee) )
|
||||||
|
FC_REFLECT( graphene::chain::asset_dividend_distribution_operation::fee_parameters_type, (distribution_base_fee)(distribution_fee_per_holder))
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::asset_create_operation,
|
FC_REFLECT( graphene::chain::asset_create_operation,
|
||||||
(fee)
|
(fee)
|
||||||
|
|
@ -511,6 +650,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)
|
||||||
)
|
)
|
||||||
|
|
@ -525,3 +671,4 @@ FC_REFLECT( graphene::chain::asset_reserve_operation,
|
||||||
(fee)(payer)(amount_to_reserve)(extensions) )
|
(fee)(payer)(amount_to_reserve)(extensions) )
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) );
|
FC_REFLECT( graphene::chain::asset_fund_fee_pool_operation, (fee)(from_account)(asset_id)(amount)(extensions) );
|
||||||
|
FC_REFLECT( graphene::chain::asset_dividend_distribution_operation, (fee)(dividend_asset_id)(account_id)(amounts)(extensions) );
|
||||||
|
|
|
||||||
|
|
@ -108,14 +108,14 @@ enum class betting_market_resolution_type {
|
||||||
BETTING_MARKET_RESOLUTION_COUNT
|
BETTING_MARKET_RESOLUTION_COUNT
|
||||||
};
|
};
|
||||||
|
|
||||||
struct betting_market_resolve_operation : public base_operation
|
struct betting_market_group_resolve_operation : public base_operation
|
||||||
{
|
{
|
||||||
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
|
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
|
||||||
asset fee;
|
asset fee;
|
||||||
|
|
||||||
betting_market_id_type betting_market_id;
|
betting_market_group_id_type betting_market_group_id;
|
||||||
|
|
||||||
betting_market_resolution_type resolution;
|
std::map<betting_market_id_type, betting_market_resolution_type> resolutions;
|
||||||
|
|
||||||
extensions_type extensions;
|
extensions_type extensions;
|
||||||
|
|
||||||
|
|
@ -123,30 +123,32 @@ struct betting_market_resolve_operation : public base_operation
|
||||||
void validate()const;
|
void validate()const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct betting_market_resolved_operation : public base_operation
|
struct betting_market_group_resolved_operation : public base_operation
|
||||||
{
|
{
|
||||||
struct fee_parameters_type {};
|
struct fee_parameters_type {};
|
||||||
|
|
||||||
account_id_type bettor_id;
|
account_id_type bettor_id;
|
||||||
betting_market_id_type betting_market_id;
|
betting_market_group_id_type betting_market_group_id;
|
||||||
betting_market_resolution_type resolution;
|
std::map<betting_market_id_type, betting_market_resolution_type> resolutions;
|
||||||
asset winnings;
|
std::vector<asset> winnings;
|
||||||
share_type fees_paid;
|
std::vector<asset> fees_paid;
|
||||||
|
|
||||||
asset fee; // unused in a virtual operation
|
asset fee; // unused in a virtual operation
|
||||||
|
|
||||||
betting_market_resolved_operation() {}
|
betting_market_group_resolved_operation() {}
|
||||||
betting_market_resolved_operation(account_id_type bettor_id,
|
betting_market_group_resolved_operation(account_id_type bettor_id,
|
||||||
betting_market_id_type betting_market_id,
|
betting_market_group_id_type betting_market_group_id,
|
||||||
betting_market_resolution_type resolution,
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
|
||||||
asset winnings,
|
std::vector<asset> winnings,
|
||||||
share_type fees_paid) :
|
std::vector<asset> fees_paid) :
|
||||||
bettor_id(bettor_id),
|
bettor_id(bettor_id),
|
||||||
betting_market_id(betting_market_id),
|
betting_market_group_id(betting_market_group_id),
|
||||||
resolution(resolution),
|
resolutions(resolutions),
|
||||||
winnings(winnings),
|
winnings(winnings),
|
||||||
fees_paid(fees_paid)
|
fees_paid(fees_paid)
|
||||||
{}
|
{
|
||||||
|
// TODO ?
|
||||||
|
}
|
||||||
|
|
||||||
account_id_type fee_payer()const { return bettor_id; }
|
account_id_type fee_payer()const { return bettor_id; }
|
||||||
void validate()const { FC_ASSERT(false, "virtual operation"); }
|
void validate()const { FC_ASSERT(false, "virtual operation"); }
|
||||||
|
|
@ -289,13 +291,13 @@ FC_REFLECT( graphene::chain::betting_market_create_operation,
|
||||||
|
|
||||||
FC_REFLECT_ENUM( graphene::chain::betting_market_resolution_type, (win)(not_win)(cancel)(BETTING_MARKET_RESOLUTION_COUNT) )
|
FC_REFLECT_ENUM( graphene::chain::betting_market_resolution_type, (win)(not_win)(cancel)(BETTING_MARKET_RESOLUTION_COUNT) )
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::betting_market_resolve_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::betting_market_group_resolve_operation::fee_parameters_type, (fee) )
|
||||||
FC_REFLECT( graphene::chain::betting_market_resolve_operation,
|
FC_REFLECT( graphene::chain::betting_market_group_resolve_operation,
|
||||||
(fee)(betting_market_id)(resolution)(extensions) )
|
(fee)(betting_market_group_id)(resolutions)(extensions) )
|
||||||
|
|
||||||
FC_REFLECT( graphene::chain::betting_market_resolved_operation::fee_parameters_type, )
|
FC_REFLECT( graphene::chain::betting_market_group_resolved_operation::fee_parameters_type, )
|
||||||
FC_REFLECT( graphene::chain::betting_market_resolved_operation,
|
FC_REFLECT( graphene::chain::betting_market_group_resolved_operation,
|
||||||
(bettor_id)(betting_market_id)(resolution)(winnings)(fees_paid)(fee) )
|
(bettor_id)(betting_market_group_id)(resolutions)(winnings)(fees_paid)(fee) )
|
||||||
|
|
||||||
FC_REFLECT_ENUM( graphene::chain::bet_type, (back)(lay) )
|
FC_REFLECT_ENUM( graphene::chain::bet_type, (back)(lay) )
|
||||||
FC_REFLECT( graphene::chain::bet_place_operation::fee_parameters_type, (fee) )
|
FC_REFLECT( graphene::chain::bet_place_operation::fee_parameters_type, (fee) )
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ namespace graphene { namespace chain {
|
||||||
uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings
|
uint16_t accounts_per_fee_scale = GRAPHENE_DEFAULT_ACCOUNTS_PER_FEE_SCALE; ///< number of accounts between fee scalings
|
||||||
uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling
|
uint8_t account_fee_scale_bitshifts = GRAPHENE_DEFAULT_ACCOUNT_FEE_SCALE_BITSHIFTS; ///< number of times to left bitshift account registration fee at each scaling
|
||||||
uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH;
|
uint8_t max_authority_depth = GRAPHENE_MAX_SIG_CHECK_DEPTH;
|
||||||
|
uint16_t betting_rake_fee_percentage = GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE; ///< part of prize paid into the dividend account for the core token holders
|
||||||
bet_multiplier_type min_bet_multiplier = GRAPHENE_DEFAULT_MIN_BET_MULTIPLIER;
|
bet_multiplier_type min_bet_multiplier = GRAPHENE_DEFAULT_MIN_BET_MULTIPLIER;
|
||||||
bet_multiplier_type max_bet_multiplier = GRAPHENE_DEFAULT_MAX_BET_MULTIPLIER;
|
bet_multiplier_type max_bet_multiplier = GRAPHENE_DEFAULT_MAX_BET_MULTIPLIER;
|
||||||
flat_map<bet_multiplier_type, bet_multiplier_type> permitted_betting_odds_increments = GRAPHENE_DEFAULT_PERMITTED_BETTING_ODDS_INCREMENTS;
|
flat_map<bet_multiplier_type, bet_multiplier_type> permitted_betting_odds_increments = GRAPHENE_DEFAULT_PERMITTED_BETTING_ODDS_INCREMENTS;
|
||||||
|
|
@ -113,6 +113,7 @@ FC_REFLECT( graphene::chain::chain_parameters,
|
||||||
(max_authority_depth)
|
(max_authority_depth)
|
||||||
(min_bet_multiplier)
|
(min_bet_multiplier)
|
||||||
(max_bet_multiplier)
|
(max_bet_multiplier)
|
||||||
|
(betting_rake_fee_percentage)
|
||||||
(permitted_betting_odds_increments)
|
(permitted_betting_odds_increments)
|
||||||
(extensions)
|
(extensions)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,8 @@ namespace graphene { namespace chain {
|
||||||
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,
|
||||||
|
asset_dividend_distribution_operation, // VIRTUAL
|
||||||
sport_create_operation,
|
sport_create_operation,
|
||||||
event_group_create_operation,
|
event_group_create_operation,
|
||||||
event_create_operation,
|
event_create_operation,
|
||||||
|
|
@ -103,8 +105,8 @@ namespace graphene { namespace chain {
|
||||||
betting_market_group_create_operation,
|
betting_market_group_create_operation,
|
||||||
betting_market_create_operation,
|
betting_market_create_operation,
|
||||||
bet_place_operation,
|
bet_place_operation,
|
||||||
betting_market_resolve_operation,
|
betting_market_group_resolve_operation,
|
||||||
betting_market_resolved_operation, // VIRTUAL
|
betting_market_group_resolved_operation, // VIRTUAL
|
||||||
bet_matched_operation, // VIRTUAL
|
bet_matched_operation, // VIRTUAL
|
||||||
bet_cancel_operation,
|
bet_cancel_operation,
|
||||||
bet_canceled_operation // VIRTUAL
|
bet_canceled_operation // VIRTUAL
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,10 @@ namespace graphene { namespace chain {
|
||||||
impl_buyback_object_type,
|
impl_buyback_object_type,
|
||||||
impl_fba_accumulator_object_type,
|
impl_fba_accumulator_object_type,
|
||||||
impl_betting_market_position_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_for_holder_object_type,
|
||||||
|
impl_distributed_dividend_balance_data_type
|
||||||
};
|
};
|
||||||
|
|
||||||
//typedef fc::unsigned_int object_id_type;
|
//typedef fc::unsigned_int object_id_type;
|
||||||
|
|
@ -232,11 +235,15 @@ namespace graphene { namespace chain {
|
||||||
class fba_accumulator_object;
|
class fba_accumulator_object;
|
||||||
class betting_market_position_object;
|
class betting_market_position_object;
|
||||||
class global_betting_statistics_object;
|
class global_betting_statistics_object;
|
||||||
|
class asset_dividend_data_object;
|
||||||
|
class pending_dividend_payout_balance_for_holder_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_for_holder_object_type, pending_dividend_payout_balance_for_holder_object> pending_dividend_payout_balance_for_holder_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;
|
||||||
|
|
@ -400,6 +407,9 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type,
|
||||||
(impl_fba_accumulator_object_type)
|
(impl_fba_accumulator_object_type)
|
||||||
(impl_betting_market_position_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_for_holder_object_type)
|
||||||
|
(impl_distributed_dividend_balance_data_type)
|
||||||
)
|
)
|
||||||
|
|
||||||
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
FC_REFLECT_TYPENAME( graphene::chain::share_type )
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,10 @@
|
||||||
#include <fc/uint128.hpp>
|
#include <fc/uint128.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <boost/multi_index/composite_key.hpp>
|
||||||
|
|
||||||
|
#define offset_d(i,f) (long(&(i)->f) - long(i))
|
||||||
|
#define offset_s(t,f) offset_d((t*)1000, f)
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
using namespace graphene::db;
|
using namespace graphene::db;
|
||||||
|
|
@ -171,13 +173,28 @@ namespace graphene { namespace chain {
|
||||||
* @ingroup object_index
|
* @ingroup object_index
|
||||||
*/
|
*/
|
||||||
struct by_account;
|
struct by_account;
|
||||||
|
struct by_asset_balance;
|
||||||
typedef multi_index_container<
|
typedef multi_index_container<
|
||||||
vesting_balance_object,
|
vesting_balance_object,
|
||||||
indexed_by<
|
indexed_by<
|
||||||
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
ordered_unique< tag<by_id>, member< object, object_id_type, &object::id > >,
|
||||||
ordered_non_unique< tag<by_account>,
|
ordered_non_unique< tag<by_account>,
|
||||||
member<vesting_balance_object, account_id_type, &vesting_balance_object::owner>
|
member<vesting_balance_object, account_id_type, &vesting_balance_object::owner>
|
||||||
>
|
>,
|
||||||
|
ordered_unique< tag<by_asset_balance>,
|
||||||
|
composite_key<
|
||||||
|
vesting_balance_object,
|
||||||
|
member_offset<vesting_balance_object, asset_id_type, (size_t) (offset_s(vesting_balance_object,balance) + offset_s(asset,asset_id))>,
|
||||||
|
member_offset<vesting_balance_object, share_type, (size_t) (offset_s(vesting_balance_object,balance) + offset_s(asset,amount))>
|
||||||
|
//member<vesting_balance_object, account_id_type, &vesting_balance_object::owner>
|
||||||
|
//member_offset<vesting_balance_object, account_id_type, (size_t) (offset_s(vesting_balance_object,owner))>
|
||||||
|
>,
|
||||||
|
composite_key_compare<
|
||||||
|
std::less< asset_id_type >,
|
||||||
|
std::greater< share_type >
|
||||||
|
//std::less< account_id_type >
|
||||||
|
>
|
||||||
|
>
|
||||||
>
|
>
|
||||||
> vesting_balance_multi_index_type;
|
> vesting_balance_multi_index_type;
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
*/
|
*/
|
||||||
#include <graphene/chain/protocol/account.hpp>
|
#include <graphene/chain/protocol/account.hpp>
|
||||||
#include <graphene/chain/hardfork.hpp>
|
#include <graphene/chain/hardfork.hpp>
|
||||||
|
#include <boost/algorithm/string/predicate.hpp>
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -135,6 +135,12 @@ bool is_valid_name( const string& name )
|
||||||
break;
|
break;
|
||||||
begin = end+1;
|
begin = end+1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only dividend distribution accounts linked to a dividend asset can end in -dividend-distribution, and
|
||||||
|
// these can only be created as a side-effect of the asset_update_dividend_operation
|
||||||
|
if( boost::algorithm::ends_with(name, "-dividend-distribution") )
|
||||||
|
return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} FC_CAPTURE_AND_RETHROW( (name) ) }
|
} FC_CAPTURE_AND_RETHROW( (name) ) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -183,6 +183,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 );
|
||||||
|
|
@ -201,6 +207,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 );
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,9 @@ void betting_market_create_operation::validate() const
|
||||||
FC_ASSERT( fee.amount >= 0 );
|
FC_ASSERT( fee.amount >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void betting_market_resolve_operation::validate() const
|
void betting_market_group_resolve_operation::validate() const
|
||||||
{
|
{
|
||||||
FC_ASSERT( fee.amount >= 0 );
|
//FC_ASSERT( fee.amount >= 0 );
|
||||||
}
|
}
|
||||||
|
|
||||||
void bet_place_operation::validate() const
|
void bet_place_operation::validate() const
|
||||||
|
|
|
||||||
|
|
@ -1053,6 +1053,21 @@ class wallet_api
|
||||||
bitasset_options new_options,
|
bitasset_options new_options,
|
||||||
bool broadcast = false);
|
bool broadcast = false);
|
||||||
|
|
||||||
|
|
||||||
|
/** Update the given asset's dividend asset options.
|
||||||
|
*
|
||||||
|
* If the asset is not already a dividend-paying asset, it will be converted into one.
|
||||||
|
*
|
||||||
|
* @param symbol the name or id of the asset to update, which must be a market-issued asset
|
||||||
|
* @param new_options the new dividend_asset_options object, which will entirely replace the existing
|
||||||
|
* options.
|
||||||
|
* @param broadcast true to broadcast the transaction on the network
|
||||||
|
* @returns the signed transaction updating the asset
|
||||||
|
*/
|
||||||
|
signed_transaction update_dividend_asset(string symbol,
|
||||||
|
dividend_asset_options new_options,
|
||||||
|
bool broadcast = false);
|
||||||
|
|
||||||
/** Update the set of feed-producing accounts for a BitAsset.
|
/** Update the set of feed-producing accounts for a BitAsset.
|
||||||
*
|
*
|
||||||
* BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers.
|
* BitAssets have price feeds selected by taking the median values of recommendations from a set of feed producers.
|
||||||
|
|
@ -1484,6 +1499,20 @@ class wallet_api
|
||||||
const variant_object& changed_values,
|
const variant_object& changed_values,
|
||||||
bool broadcast = false);
|
bool broadcast = false);
|
||||||
|
|
||||||
|
/** Propose a dividend asset update.
|
||||||
|
*
|
||||||
|
* @param proposing_account The account paying the fee to propose the tx
|
||||||
|
* @param expiration_time Timestamp specifying when the proposal will either take effect or expire.
|
||||||
|
* @param changed_values dividend asset parameters to update
|
||||||
|
* @param broadcast true if you wish to broadcast the transaction
|
||||||
|
* @return the signed version of the transaction
|
||||||
|
*/
|
||||||
|
signed_transaction propose_dividend_asset_update(
|
||||||
|
const string& proposing_account,
|
||||||
|
fc::time_point_sec expiration_time,
|
||||||
|
const variant_object& changed_values,
|
||||||
|
bool broadcast = false);
|
||||||
|
|
||||||
/** Approve or disapprove a proposal.
|
/** Approve or disapprove a proposal.
|
||||||
*
|
*
|
||||||
* @param fee_paying_account The account paying the fee for the op.
|
* @param fee_paying_account The account paying the fee for the op.
|
||||||
|
|
@ -1551,11 +1580,11 @@ class wallet_api
|
||||||
share_type amount_reserved_for_fees,
|
share_type amount_reserved_for_fees,
|
||||||
bool broadcast = false);
|
bool broadcast = false);
|
||||||
|
|
||||||
signed_transaction propose_resolve_betting_market(
|
signed_transaction propose_resolve_betting_market_group(
|
||||||
const string& proposing_account,
|
const string& proposing_account,
|
||||||
fc::time_point_sec expiration_time,
|
fc::time_point_sec expiration_time,
|
||||||
betting_market_id_type betting_market_id,
|
betting_market_group_id_type betting_market_group_id,
|
||||||
betting_market_resolution_type resolution,
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
|
||||||
bool broadcast = false);
|
bool broadcast = false);
|
||||||
|
|
||||||
void dbg_make_uia(string creator, string symbol);
|
void dbg_make_uia(string creator, string symbol);
|
||||||
|
|
@ -1689,6 +1718,7 @@ FC_API( graphene::wallet::wallet_api,
|
||||||
(create_asset)
|
(create_asset)
|
||||||
(update_asset)
|
(update_asset)
|
||||||
(update_bitasset)
|
(update_bitasset)
|
||||||
|
(update_dividend_asset)
|
||||||
(update_asset_feed_producers)
|
(update_asset_feed_producers)
|
||||||
(publish_asset_feed)
|
(publish_asset_feed)
|
||||||
(issue_asset)
|
(issue_asset)
|
||||||
|
|
@ -1737,6 +1767,7 @@ FC_API( graphene::wallet::wallet_api,
|
||||||
(get_prototype_operation)
|
(get_prototype_operation)
|
||||||
(propose_parameter_change)
|
(propose_parameter_change)
|
||||||
(propose_fee_change)
|
(propose_fee_change)
|
||||||
|
(propose_dividend_asset_update)
|
||||||
(approve_proposal)
|
(approve_proposal)
|
||||||
(dbg_make_uia)
|
(dbg_make_uia)
|
||||||
(dbg_make_mia)
|
(dbg_make_mia)
|
||||||
|
|
@ -1770,6 +1801,6 @@ FC_API( graphene::wallet::wallet_api,
|
||||||
(propose_create_betting_market_group)
|
(propose_create_betting_market_group)
|
||||||
(propose_create_betting_market)
|
(propose_create_betting_market)
|
||||||
(place_bet)
|
(place_bet)
|
||||||
(propose_resolve_betting_market)
|
(propose_resolve_betting_market_group)
|
||||||
(get_order_book)
|
(get_order_book)
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@
|
||||||
#include <boost/version.hpp>
|
#include <boost/version.hpp>
|
||||||
#include <boost/lexical_cast.hpp>
|
#include <boost/lexical_cast.hpp>
|
||||||
#include <boost/algorithm/string/replace.hpp>
|
#include <boost/algorithm/string/replace.hpp>
|
||||||
|
#include <boost/algorithm/string/join.hpp>
|
||||||
|
|
||||||
#include <boost/range/adaptor/map.hpp>
|
#include <boost/range/adaptor/map.hpp>
|
||||||
#include <boost/range/algorithm_ext/erase.hpp>
|
#include <boost/range/algorithm_ext/erase.hpp>
|
||||||
|
|
@ -123,6 +124,7 @@ public:
|
||||||
std::string operator()(const account_create_operation& op)const;
|
std::string operator()(const account_create_operation& op)const;
|
||||||
std::string operator()(const account_update_operation& op)const;
|
std::string operator()(const account_update_operation& op)const;
|
||||||
std::string operator()(const asset_create_operation& op)const;
|
std::string operator()(const asset_create_operation& op)const;
|
||||||
|
std::string operator()(const asset_dividend_distribution_operation& op)const;
|
||||||
};
|
};
|
||||||
|
|
||||||
template<class T>
|
template<class T>
|
||||||
|
|
@ -1199,6 +1201,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 */)
|
||||||
|
|
@ -2224,7 +2247,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() )
|
||||||
{
|
{
|
||||||
|
|
@ -2388,6 +2411,46 @@ public:
|
||||||
return sign_transaction(tx, broadcast);
|
return sign_transaction(tx, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signed_transaction propose_dividend_asset_update(
|
||||||
|
const string& proposing_account,
|
||||||
|
fc::time_point_sec expiration_time,
|
||||||
|
const variant_object& changed_values,
|
||||||
|
bool broadcast = false)
|
||||||
|
{
|
||||||
|
FC_ASSERT( changed_values.contains("asset_to_update") );
|
||||||
|
|
||||||
|
const chain_parameters& current_params = get_global_properties().parameters;
|
||||||
|
asset_update_dividend_operation changed_op;
|
||||||
|
fc::reflector<asset_update_dividend_operation>::visit(
|
||||||
|
fc::from_variant_visitor<asset_update_dividend_operation>( changed_values, changed_op )
|
||||||
|
);
|
||||||
|
|
||||||
|
optional<asset_object> asset_to_update = find_asset(changed_op.asset_to_update);
|
||||||
|
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 = changed_op.new_options;
|
||||||
|
|
||||||
|
proposal_create_operation prop_op;
|
||||||
|
|
||||||
|
prop_op.expiration_time = expiration_time;
|
||||||
|
prop_op.review_period_seconds = current_params.committee_proposal_review_period;
|
||||||
|
prop_op.fee_paying_account = get_account(proposing_account).id;
|
||||||
|
|
||||||
|
prop_op.proposed_ops.emplace_back( update_op );
|
||||||
|
current_params.current_fees->set_fee( prop_op.proposed_ops.back().op );
|
||||||
|
|
||||||
|
signed_transaction tx;
|
||||||
|
tx.operations.push_back(prop_op);
|
||||||
|
set_operation_fees(tx, current_params.current_fees);
|
||||||
|
tx.validate();
|
||||||
|
|
||||||
|
return sign_transaction(tx, broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
signed_transaction approve_proposal(
|
signed_transaction approve_proposal(
|
||||||
const string& fee_paying_account,
|
const string& fee_paying_account,
|
||||||
const string& proposal_id,
|
const string& proposal_id,
|
||||||
|
|
@ -2635,9 +2698,7 @@ std::string operation_printer::operator()(const T& op)const
|
||||||
operation_result_printer rprinter(wallet);
|
operation_result_printer rprinter(wallet);
|
||||||
std::string str_result = result.visit(rprinter);
|
std::string str_result = result.visit(rprinter);
|
||||||
if( str_result != "" )
|
if( str_result != "" )
|
||||||
{
|
|
||||||
out << " result: " << str_result;
|
out << " result: " << str_result;
|
||||||
}
|
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
std::string operation_printer::operator()(const transfer_from_blind_operation& op)const
|
std::string operation_printer::operator()(const transfer_from_blind_operation& op)const
|
||||||
|
|
@ -2717,6 +2778,22 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons
|
||||||
return fee(op.fee);
|
return fee(op.fee);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string operation_printer::operator()(const asset_dividend_distribution_operation& op)const
|
||||||
|
{
|
||||||
|
asset_object dividend_paying_asset = wallet.get_asset(op.dividend_asset_id);
|
||||||
|
account_object receiver = wallet.get_account(op.account_id);
|
||||||
|
|
||||||
|
out << receiver.name << " received dividend payments for " << dividend_paying_asset.symbol << ": ";
|
||||||
|
std::vector<std::string> pretty_payout_amounts;
|
||||||
|
for (const asset& payment : op.amounts)
|
||||||
|
{
|
||||||
|
asset_object payout_asset = wallet.get_asset(payment.asset_id);
|
||||||
|
pretty_payout_amounts.push_back(payout_asset.amount_to_pretty_string(payment));
|
||||||
|
}
|
||||||
|
out << boost::algorithm::join(pretty_payout_amounts, ", ");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
std::string operation_result_printer::operator()(const void_result& x) const
|
std::string operation_result_printer::operator()(const void_result& x) const
|
||||||
{
|
{
|
||||||
return "";
|
return "";
|
||||||
|
|
@ -2827,7 +2904,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();
|
||||||
}
|
}
|
||||||
|
|
@ -3232,6 +3309,14 @@ signed_transaction wallet_api::update_bitasset(string symbol,
|
||||||
return my->update_bitasset(symbol, new_options, broadcast);
|
return my->update_bitasset(symbol, new_options, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signed_transaction wallet_api::update_dividend_asset(string symbol,
|
||||||
|
dividend_asset_options new_options,
|
||||||
|
bool broadcast /* = false */)
|
||||||
|
{
|
||||||
|
return my->update_dividend_asset(symbol, new_options, broadcast);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
signed_transaction wallet_api::update_asset_feed_producers(string symbol,
|
signed_transaction wallet_api::update_asset_feed_producers(string symbol,
|
||||||
flat_set<string> new_feed_producers,
|
flat_set<string> new_feed_producers,
|
||||||
bool broadcast /* = false */)
|
bool broadcast /* = false */)
|
||||||
|
|
@ -3479,6 +3564,16 @@ signed_transaction wallet_api::propose_fee_change(
|
||||||
return my->propose_fee_change( proposing_account, expiration_time, changed_fees, broadcast );
|
return my->propose_fee_change( proposing_account, expiration_time, changed_fees, broadcast );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
signed_transaction wallet_api::propose_dividend_asset_update(
|
||||||
|
const string& proposing_account,
|
||||||
|
fc::time_point_sec expiration_time,
|
||||||
|
const variant_object& changed_fees,
|
||||||
|
bool broadcast /* = false */
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return my->propose_dividend_asset_update( proposing_account, expiration_time, changed_fees, broadcast );
|
||||||
|
}
|
||||||
|
|
||||||
signed_transaction wallet_api::approve_proposal(
|
signed_transaction wallet_api::approve_proposal(
|
||||||
const string& fee_paying_account,
|
const string& fee_paying_account,
|
||||||
const string& proposal_id,
|
const string& proposal_id,
|
||||||
|
|
@ -3489,6 +3584,9 @@ signed_transaction wallet_api::approve_proposal(
|
||||||
return my->approve_proposal( fee_paying_account, proposal_id, delta, broadcast );
|
return my->approve_proposal( fee_paying_account, proposal_id, delta, broadcast );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
global_property_object wallet_api::get_global_properties() const
|
global_property_object wallet_api::get_global_properties() const
|
||||||
{
|
{
|
||||||
return my->get_global_properties();
|
return my->get_global_properties();
|
||||||
|
|
@ -4546,25 +4644,25 @@ signed_transaction wallet_api::place_bet(
|
||||||
return my->sign_transaction(tx, broadcast);
|
return my->sign_transaction(tx, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
signed_transaction wallet_api::propose_resolve_betting_market(
|
signed_transaction wallet_api::propose_resolve_betting_market_group(
|
||||||
const string& proposing_account,
|
const string& proposing_account,
|
||||||
fc::time_point_sec expiration_time,
|
fc::time_point_sec expiration_time,
|
||||||
betting_market_id_type betting_market_id,
|
betting_market_group_id_type betting_market_group_id,
|
||||||
betting_market_resolution_type resolution,
|
const std::map<betting_market_id_type, betting_market_resolution_type>& resolutions,
|
||||||
bool broadcast /*= false*/)
|
bool broadcast /*= false*/)
|
||||||
{
|
{
|
||||||
FC_ASSERT( !is_locked() );
|
FC_ASSERT( !is_locked() );
|
||||||
const chain_parameters& current_params = get_global_properties().parameters;
|
const chain_parameters& current_params = get_global_properties().parameters;
|
||||||
|
|
||||||
betting_market_resolve_operation betting_market_resolve_op;
|
betting_market_group_resolve_operation betting_market_group_resolve_op;
|
||||||
betting_market_resolve_op.betting_market_id = betting_market_id;
|
betting_market_group_resolve_op.betting_market_group_id = betting_market_group_id;
|
||||||
betting_market_resolve_op.resolution = resolution;
|
betting_market_group_resolve_op.resolutions = resolutions;
|
||||||
|
|
||||||
proposal_create_operation prop_op;
|
proposal_create_operation prop_op;
|
||||||
prop_op.expiration_time = expiration_time;
|
prop_op.expiration_time = expiration_time;
|
||||||
prop_op.review_period_seconds = current_params.committee_proposal_review_period;
|
prop_op.review_period_seconds = current_params.committee_proposal_review_period;
|
||||||
prop_op.fee_paying_account = get_account(proposing_account).id;
|
prop_op.fee_paying_account = get_account(proposing_account).id;
|
||||||
prop_op.proposed_ops.emplace_back( betting_market_resolve_op );
|
prop_op.proposed_ops.emplace_back( betting_market_group_resolve_op );
|
||||||
current_params.current_fees->set_fee( prop_op.proposed_ops.back().op );
|
current_params.current_fees->set_fee( prop_op.proposed_ops.back().op );
|
||||||
|
|
||||||
signed_transaction tx;
|
signed_transaction tx;
|
||||||
|
|
|
||||||
|
|
@ -111,9 +111,15 @@ BOOST_AUTO_TEST_CASE( peerplays_sport_create_test )
|
||||||
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
|
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
|
||||||
|
|
||||||
// caps win
|
// caps win
|
||||||
resolve_betting_market(capitals_win_market.id, betting_market_resolution_type::win);
|
resolve_betting_market_group(moneyline_betting_markets.id,
|
||||||
|
{{capitals_win_market.id, betting_market_resolution_type::win},
|
||||||
|
{blackhawks_win_market.id, betting_market_resolution_type::cancel}});
|
||||||
|
|
||||||
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000);
|
|
||||||
|
uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage;
|
||||||
|
uint32_t rake_value = (-1000000 + 2000000) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;
|
||||||
|
BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value));
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000000 - 1000000 - 20000 + 2000000 - rake_value);
|
||||||
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
|
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000000 - 1000000 - 20000);
|
||||||
|
|
||||||
} FC_LOG_AND_RETHROW()
|
} FC_LOG_AND_RETHROW()
|
||||||
|
|
@ -230,6 +236,9 @@ BOOST_AUTO_TEST_SUITE_END()
|
||||||
// the result in all three possible outcomes
|
// the result in all three possible outcomes
|
||||||
struct simple_bet_test_fixture : database_fixture {
|
struct simple_bet_test_fixture : database_fixture {
|
||||||
betting_market_id_type capitals_win_betting_market_id;
|
betting_market_id_type capitals_win_betting_market_id;
|
||||||
|
betting_market_id_type blackhawks_win_betting_market_id;
|
||||||
|
betting_market_group_id_type moneyline_betting_markets_id;
|
||||||
|
|
||||||
simple_bet_test_fixture()
|
simple_bet_test_fixture()
|
||||||
{
|
{
|
||||||
ACTORS( (alice)(bob) );
|
ACTORS( (alice)(bob) );
|
||||||
|
|
@ -248,6 +257,8 @@ struct simple_bet_test_fixture : database_fixture {
|
||||||
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22);
|
place_bet(bob_id, capitals_win_market.id, bet_type::back, asset(1100, asset_id_type()), 2 * GRAPHENE_BETTING_ODDS_PRECISION, 22);
|
||||||
|
|
||||||
capitals_win_betting_market_id = capitals_win_market.id;
|
capitals_win_betting_market_id = capitals_win_market.id;
|
||||||
|
blackhawks_win_betting_market_id = blackhawks_win_market.id;
|
||||||
|
moneyline_betting_markets_id = moneyline_betting_markets.id;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -257,15 +268,23 @@ BOOST_AUTO_TEST_CASE( win )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::win);
|
resolve_betting_market_group(moneyline_betting_markets_id,
|
||||||
|
{{capitals_win_betting_market_id, betting_market_resolution_type::win},
|
||||||
|
{blackhawks_win_betting_market_id, betting_market_resolution_type::cancel}});
|
||||||
|
|
||||||
GET_ACTOR(alice);
|
GET_ACTOR(alice);
|
||||||
GET_ACTOR(bob);
|
GET_ACTOR(bob);
|
||||||
|
|
||||||
|
uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage;
|
||||||
|
uint32_t rake_value;
|
||||||
|
//rake_value = (-100 + 1100 - 1100) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;
|
||||||
// alice starts with 10000, pays 100 (bet) + 2 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0
|
// alice starts with 10000, pays 100 (bet) + 2 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0
|
||||||
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 1100 - 1100 - 22 + 0);
|
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 1100 - 1100 - 22 + 0);
|
||||||
|
|
||||||
|
rake_value = (-1000 - 1100 + 2200) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;
|
||||||
// bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200
|
// bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200
|
||||||
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 0 - 1100 - 22 + 2200);
|
BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value));
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 0 - 1100 - 22 + 2200 - rake_value);
|
||||||
} FC_LOG_AND_RETHROW()
|
} FC_LOG_AND_RETHROW()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,13 +292,20 @@ BOOST_AUTO_TEST_CASE( not_win )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::not_win);
|
resolve_betting_market_group(moneyline_betting_markets_id,
|
||||||
|
{{capitals_win_betting_market_id, betting_market_resolution_type::not_win},
|
||||||
|
{blackhawks_win_betting_market_id, betting_market_resolution_type::cancel}});
|
||||||
|
|
||||||
GET_ACTOR(alice);
|
GET_ACTOR(alice);
|
||||||
GET_ACTOR(bob);
|
GET_ACTOR(bob);
|
||||||
|
|
||||||
|
uint16_t rake_fee_percentage = db.get_global_properties().parameters.betting_rake_fee_percentage;
|
||||||
|
uint32_t rake_value = (-100 - 1100 + 2200) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;
|
||||||
// alice starts with 10000, pays 100 (bet) + 2 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200
|
// alice starts with 10000, pays 100 (bet) + 2 (fee), wins 0, then pays 1100 (bet) + 22 (fee), wins 2200
|
||||||
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 0 - 1100 - 22 + 2200);
|
BOOST_TEST_MESSAGE("Rake value " + std::to_string(rake_value));
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice_id, asset_id_type()), 10000 - 100 - 2 + 0 - 1100 - 22 + 2200 - rake_value);
|
||||||
|
|
||||||
|
//rake_value = (-1000 + 1100 - 1100) * rake_fee_percentage / GRAPHENE_1_PERCENT / 100;
|
||||||
// bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0
|
// bob starts with 10000, pays 1000 (bet) + 20 (fee), wins 1100, then pays 1100 (bet) + 22 (fee), wins 0
|
||||||
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 1100 - 1100 - 22 + 0);
|
BOOST_CHECK_EQUAL(get_balance(bob_id, asset_id_type()), 10000 - 1000 - 20 + 1100 - 1100 - 22 + 0);
|
||||||
} FC_LOG_AND_RETHROW()
|
} FC_LOG_AND_RETHROW()
|
||||||
|
|
@ -289,7 +315,9 @@ BOOST_AUTO_TEST_CASE( cancel )
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
resolve_betting_market(capitals_win_betting_market_id, betting_market_resolution_type::cancel);
|
resolve_betting_market_group(moneyline_betting_markets_id,
|
||||||
|
{{capitals_win_betting_market_id, betting_market_resolution_type::cancel},
|
||||||
|
{blackhawks_win_betting_market_id, betting_market_resolution_type::cancel}});
|
||||||
|
|
||||||
GET_ACTOR(alice);
|
GET_ACTOR(alice);
|
||||||
GET_ACTOR(bob);
|
GET_ACTOR(bob);
|
||||||
|
|
|
||||||
|
|
@ -1062,6 +1062,20 @@ 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_for_holder_object_index& pending_payout_balance_index =
|
||||||
|
db.get_index_type<pending_dividend_payout_balance_for_holder_object_index>();
|
||||||
|
auto pending_payout_iter =
|
||||||
|
pending_payout_balance_index.indices().get<by_dividend_payout_account>().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id));
|
||||||
|
if (pending_payout_iter == pending_payout_balance_index.indices().get<by_dividend_payout_account>().end())
|
||||||
|
return 0;
|
||||||
|
else
|
||||||
|
return pending_payout_iter->pending_balance.value;
|
||||||
|
}
|
||||||
|
|
||||||
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;
|
||||||
|
|
@ -1198,13 +1212,14 @@ const betting_market_object& database_fixture::create_betting_market(betting_mar
|
||||||
trx.operations.clear();
|
trx.operations.clear();
|
||||||
} FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) }
|
} FC_CAPTURE_AND_RETHROW( (bettor_id)(back_or_lay)(amount_to_bet) ) }
|
||||||
|
|
||||||
void database_fixture::resolve_betting_market(betting_market_id_type betting_market_id, betting_market_resolution_type resolution)
|
void database_fixture::resolve_betting_market_group(betting_market_group_id_type betting_market_group_id,
|
||||||
|
std::map<betting_market_id_type, betting_market_resolution_type> resolutions)
|
||||||
{ try {
|
{ try {
|
||||||
betting_market_resolve_operation betting_market_resolve_op;
|
betting_market_group_resolve_operation betting_market_group_resolve_op;
|
||||||
betting_market_resolve_op.betting_market_id = betting_market_id;
|
betting_market_group_resolve_op.betting_market_group_id = betting_market_group_id;
|
||||||
betting_market_resolve_op.resolution = resolution;
|
betting_market_group_resolve_op.resolutions = resolutions;
|
||||||
process_operation_by_witnesses(betting_market_resolve_op);
|
process_operation_by_witnesses(betting_market_group_resolve_op);
|
||||||
} FC_CAPTURE_AND_RETHROW( (betting_market_id)(resolution) ) }
|
} FC_CAPTURE_AND_RETHROW( (betting_market_group_id)(resolutions) ) }
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
void process_operation_by_witnesses(operation op);
|
void process_operation_by_witnesses(operation op);
|
||||||
const sport_object& create_sport(internationalized_string_type name);
|
const sport_object& create_sport(internationalized_string_type name);
|
||||||
|
|
@ -290,7 +293,7 @@ struct database_fixture {
|
||||||
const betting_market_object& create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id);
|
const betting_market_object& create_betting_market(betting_market_group_id_type group_id, internationalized_string_type payout_condition, asset_id_type asset_id);
|
||||||
|
|
||||||
void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees);
|
void place_bet(account_id_type bettor_id, betting_market_id_type betting_market_id, bet_type back_or_lay, asset amount_to_bet, bet_multiplier_type backer_multiplier, share_type amount_reserved_for_fees);
|
||||||
void resolve_betting_market(betting_market_id_type betting_market_id, betting_market_resolution_type resolution);
|
void resolve_betting_market_group(betting_market_group_id_type betting_market_group_id, std::map<betting_market_id_type, betting_market_resolution_type> resolutions);
|
||||||
};
|
};
|
||||||
|
|
||||||
namespace test {
|
namespace test {
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
#include <graphene/chain/vesting_balance_object.hpp>
|
#include <graphene/chain/vesting_balance_object.hpp>
|
||||||
#include <graphene/chain/withdraw_permission_object.hpp>
|
#include <graphene/chain/withdraw_permission_object.hpp>
|
||||||
#include <graphene/chain/witness_object.hpp>
|
#include <graphene/chain/witness_object.hpp>
|
||||||
|
#include <graphene/account_history/account_history_plugin.hpp>
|
||||||
|
|
||||||
#include <fc/crypto/digest.hpp>
|
#include <fc/crypto/digest.hpp>
|
||||||
|
|
||||||
|
|
@ -1111,6 +1112,459 @@ BOOST_AUTO_TEST_CASE( uia_fees )
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_FIXTURE_TEST_SUITE( dividend_tests, database_fixture )
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Creating test accounts");
|
||||||
|
create_account("alice");
|
||||||
|
create_account("bob");
|
||||||
|
create_account("carol");
|
||||||
|
create_account("dave");
|
||||||
|
create_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();
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Funding asset fee pool");
|
||||||
|
{
|
||||||
|
asset_fund_fee_pool_operation fund_op;
|
||||||
|
fund_op.from_account = account_id_type();
|
||||||
|
fund_op.asset_id = get_asset("TEST").id;
|
||||||
|
fund_op.amount = 500000000;
|
||||||
|
trx.operations.push_back(std::move(fund_op));
|
||||||
|
set_expiration(db, trx);
|
||||||
|
PUSH_TX( db, trx, ~0 );
|
||||||
|
trx.operations.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// our DIVIDEND asset should not yet be a divdend asset
|
||||||
|
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||||
|
BOOST_CHECK(!dividend_holder_asset_object.dividend_data_id);
|
||||||
|
|
||||||
|
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 = db.head_block_time() + fc::minutes(1);
|
||||||
|
op.new_options.payout_interval = 60 * 60 * 24 * 3;
|
||||||
|
|
||||||
|
trx.operations.push_back(op);
|
||||||
|
set_expiration(db, trx);
|
||||||
|
PUSH_TX( db, trx, ~0 );
|
||||||
|
trx.operations.clear();
|
||||||
|
}
|
||||||
|
generate_block();
|
||||||
|
|
||||||
|
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 * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||||
|
BOOST_CHECK_EQUAL(dividend_distribution_account.name, "dividend-dividend-distribution");
|
||||||
|
|
||||||
|
// db.modify( db.get_global_properties(), [&]( global_property_object& _gpo )
|
||||||
|
// {
|
||||||
|
// _gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_base_fee = 100;
|
||||||
|
// _gpo.parameters.current_fees->get<asset_dividend_distribution_operation>().distribution_fee_per_holder = 100;
|
||||||
|
// } );
|
||||||
|
|
||||||
|
|
||||||
|
} catch(fc::exception& e) {
|
||||||
|
edump((e.to_detail_string()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( test_update_dividend_interval )
|
||||||
|
{
|
||||||
|
using namespace graphene;
|
||||||
|
try {
|
||||||
|
INVOKE( create_dividend_uia );
|
||||||
|
|
||||||
|
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||||
|
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||||
|
|
||||||
|
auto advance_to_next_payout_time = [&]() {
|
||||||
|
// Advance to the next upcoming payout time
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||||
|
// generate blocks up to the next scheduled time
|
||||||
|
generate_blocks(next_payout_scheduled_time);
|
||||||
|
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||||
|
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||||
|
if (dividend_data.options.next_payout_time)
|
||||||
|
{
|
||||||
|
// we know there was a next_payout_time set when we entered this, so if
|
||||||
|
// it has been cleared, we must have already processed payouts, no need to
|
||||||
|
// further advance time.
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Updating the payout interval");
|
||||||
|
{
|
||||||
|
asset_update_dividend_operation op;
|
||||||
|
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; // 1 days
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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( test_basic_dividend_distribution )
|
||||||
|
{
|
||||||
|
using namespace graphene;
|
||||||
|
try {
|
||||||
|
INVOKE( create_dividend_uia );
|
||||||
|
|
||||||
|
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||||
|
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||||
|
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||||
|
const account_object& alice = get_account("alice");
|
||||||
|
const account_object& bob = get_account("bob");
|
||||||
|
const account_object& carol = get_account("carol");
|
||||||
|
const account_object& dave = get_account("dave");
|
||||||
|
const account_object& frank = get_account("frank");
|
||||||
|
const auto& test_asset_object = get_asset("TEST");
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto advance_to_next_payout_time = [&]() {
|
||||||
|
// Advance to the next upcoming payout time
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||||
|
// generate blocks up to the next scheduled time
|
||||||
|
generate_blocks(next_payout_scheduled_time);
|
||||||
|
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||||
|
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||||
|
if (dividend_data.options.next_payout_time)
|
||||||
|
{
|
||||||
|
// we know there was a next_payout_time set when we entered this, so if
|
||||||
|
// it has been cleared, we must have already processed payouts, no need to
|
||||||
|
// further advance time.
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the first test will be testing pending balances, so we need to hit a
|
||||||
|
// maintenance interval that isn't the payout interval. Payout is
|
||||||
|
// every 3 days, maintenance interval is every 1 day.
|
||||||
|
advance_to_next_payout_time();
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
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, 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);
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
auto verify_dividend_payout_operations = [&](const account_object& destination_account, const asset& expected_payout)
|
||||||
|
{
|
||||||
|
BOOST_TEST_MESSAGE("Verifying the virtual op was created");
|
||||||
|
const account_transaction_history_index& hist_idx = db.get_index_type<account_transaction_history_index>();
|
||||||
|
auto account_history_range = hist_idx.indices().get<by_seq>().equal_range(boost::make_tuple(destination_account.id));
|
||||||
|
BOOST_REQUIRE(account_history_range.first != account_history_range.second);
|
||||||
|
const operation_history_object& history_object = std::prev(account_history_range.second)->operation_id(db);
|
||||||
|
const asset_dividend_distribution_operation& distribution_operation = history_object.op.get<asset_dividend_distribution_operation>();
|
||||||
|
BOOST_CHECK(distribution_operation.account_id == destination_account.id);
|
||||||
|
BOOST_CHECK(distribution_operation.amounts.find(expected_payout) != distribution_operation.amounts.end());
|
||||||
|
};
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Verifying the payouts");
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 20000);
|
||||||
|
verify_dividend_payout_operations(alice, asset(20000, test_asset_object.id));
|
||||||
|
verify_pending_balance(alice, test_asset_object, 0);
|
||||||
|
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 20000);
|
||||||
|
verify_dividend_payout_operations(bob, asset(20000, test_asset_object.id));
|
||||||
|
verify_pending_balance(bob, test_asset_object, 0);
|
||||||
|
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 30000);
|
||||||
|
verify_dividend_payout_operations(carol, asset(30000, test_asset_object.id));
|
||||||
|
verify_pending_balance(carol, test_asset_object, 0);
|
||||||
|
} catch(fc::exception& e) {
|
||||||
|
edump((e.to_detail_string()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BOOST_AUTO_TEST_CASE( test_dividend_distribution_interval )
|
||||||
|
{
|
||||||
|
using namespace graphene;
|
||||||
|
try {
|
||||||
|
INVOKE( create_dividend_uia );
|
||||||
|
|
||||||
|
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||||
|
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||||
|
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||||
|
const account_object& alice = get_account("alice");
|
||||||
|
const account_object& bob = get_account("bob");
|
||||||
|
const account_object& carol = get_account("carol");
|
||||||
|
const account_object& dave = get_account("dave");
|
||||||
|
const account_object& frank = get_account("frank");
|
||||||
|
const auto& test_asset_object = get_asset("TEST");
|
||||||
|
} catch(fc::exception& e) {
|
||||||
|
edump((e.to_detail_string()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE( check_dividend_corner_cases )
|
||||||
|
{
|
||||||
|
using namespace graphene;
|
||||||
|
try {
|
||||||
|
INVOKE( create_dividend_uia );
|
||||||
|
|
||||||
|
const auto& dividend_holder_asset_object = get_asset("DIVIDEND");
|
||||||
|
const auto& dividend_data = dividend_holder_asset_object.dividend_data(db);
|
||||||
|
const account_object& dividend_distribution_account = dividend_data.dividend_distribution_account(db);
|
||||||
|
const account_object& alice = get_account("alice");
|
||||||
|
const account_object& bob = get_account("bob");
|
||||||
|
const account_object& carol = get_account("carol");
|
||||||
|
const account_object& dave = get_account("dave");
|
||||||
|
const account_object& frank = get_account("frank");
|
||||||
|
const auto& test_asset_object = get_asset("TEST");
|
||||||
|
|
||||||
|
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();
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto reserve_asset_from_account = [&](const asset_object& asset_to_reserve, const account_object& from_account, int64_t amount_to_reserve)
|
||||||
|
{
|
||||||
|
asset_reserve_operation reserve_op;
|
||||||
|
reserve_op.payer = from_account.id;
|
||||||
|
reserve_op.amount_to_reserve = asset(amount_to_reserve, asset_to_reserve.id);
|
||||||
|
trx.operations.push_back(reserve_op);
|
||||||
|
set_expiration(db, trx);
|
||||||
|
PUSH_TX( db, trx, ~0 );
|
||||||
|
trx.operations.clear();
|
||||||
|
};
|
||||||
|
auto advance_to_next_payout_time = [&]() {
|
||||||
|
// Advance to the next upcoming payout time
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
fc::time_point_sec next_payout_scheduled_time = *dividend_data.options.next_payout_time;
|
||||||
|
// generate blocks up to the next scheduled time
|
||||||
|
generate_blocks(next_payout_scheduled_time);
|
||||||
|
// if the scheduled time fell on a maintenance interval, then we should have paid out.
|
||||||
|
// if not, we need to advance to the next maintenance interval to trigger the payout
|
||||||
|
if (dividend_data.options.next_payout_time)
|
||||||
|
{
|
||||||
|
// we know there was a next_payout_time set when we entered this, so if
|
||||||
|
// it has been cleared, we must have already processed payouts, no need to
|
||||||
|
// further advance time.
|
||||||
|
BOOST_REQUIRE(dividend_data.options.next_payout_time);
|
||||||
|
if (*dividend_data.options.next_payout_time == next_payout_scheduled_time)
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// the first test will be testing pending balances, so we need to hit a
|
||||||
|
// maintenance interval that isn't the payout interval. Payout is
|
||||||
|
// every 3 days, maintenance interval is every 1 day.
|
||||||
|
advance_to_next_payout_time();
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Testing a payout interval when there are no users holding the dividend asset");
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 0);
|
||||||
|
issue_asset_to_account(test_asset_object, dividend_distribution_account, 1000);
|
||||||
|
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
BOOST_TEST_MESSAGE("Verify that no pending payments were scheduled");
|
||||||
|
verify_pending_balance(alice, test_asset_object, 0);
|
||||||
|
verify_pending_balance(bob, test_asset_object, 0);
|
||||||
|
verify_pending_balance(carol, test_asset_object, 0);
|
||||||
|
advance_to_next_payout_time();
|
||||||
|
BOOST_TEST_MESSAGE("Verify that no actual payments took place");
|
||||||
|
verify_pending_balance(alice, test_asset_object, 0);
|
||||||
|
verify_pending_balance(bob, test_asset_object, 0);
|
||||||
|
verify_pending_balance(carol, test_asset_object, 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice, test_asset_object), 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, test_asset_object), 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(carol, test_asset_object), 0);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(dividend_distribution_account, test_asset_object), 1000);
|
||||||
|
|
||||||
|
BOOST_TEST_MESSAGE("Now give alice a small balance and see that she takes it all");
|
||||||
|
issue_asset_to_account(dividend_holder_asset_object, alice, 1);
|
||||||
|
generate_block();
|
||||||
|
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
BOOST_TEST_MESSAGE("Verify that no alice received her payment of the entire amount");
|
||||||
|
verify_pending_balance(alice, test_asset_object, 1000);
|
||||||
|
|
||||||
|
// Test that we can pay out the dividend asset itself
|
||||||
|
issue_asset_to_account(dividend_holder_asset_object, bob, 1);
|
||||||
|
issue_asset_to_account(dividend_holder_asset_object, carol, 1);
|
||||||
|
issue_asset_to_account(dividend_holder_asset_object, dividend_distribution_account, 300);
|
||||||
|
generate_block();
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(alice, dividend_holder_asset_object), 1);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(bob, dividend_holder_asset_object), 1);
|
||||||
|
BOOST_CHECK_EQUAL(get_balance(carol, dividend_holder_asset_object), 1);
|
||||||
|
BOOST_TEST_MESSAGE("Generating blocks until next maintenance interval");
|
||||||
|
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time);
|
||||||
|
generate_block(); // get the maintenance skip slots out of the way
|
||||||
|
BOOST_TEST_MESSAGE("Verify that the dividend asset was shared out");
|
||||||
|
verify_pending_balance(alice, dividend_holder_asset_object, 100);
|
||||||
|
verify_pending_balance(bob, dividend_holder_asset_object, 100);
|
||||||
|
verify_pending_balance(carol, dividend_holder_asset_object, 100);
|
||||||
|
} catch(fc::exception& e) {
|
||||||
|
edump((e.to_detail_string()));
|
||||||
|
throw;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BOOST_AUTO_TEST_SUITE_END() // end dividend_tests suite
|
||||||
|
|
||||||
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