Merge branch 'betting-ro4' into betting-merge

This commit is contained in:
Roman Olearski 2017-07-08 20:41:45 +02:00
commit 5de1437d24
30 changed files with 1925 additions and 127 deletions

View file

@ -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;
} }

View file

@ -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 );
} }

View file

@ -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)
) )

View file

@ -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();

View file

@ -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

View file

@ -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

View file

@ -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)
); );

View file

@ -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();

View file

@ -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 &){}

View file

@ -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) )

View file

@ -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:

View file

@ -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)
) )

View file

@ -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

View file

@ -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.
* *

View file

@ -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

View file

@ -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) );

View file

@ -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) )

View file

@ -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)
) )

View file

@ -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

View file

@ -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 )

View file

@ -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;
/** /**

View file

@ -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) ) }

View file

@ -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 );

View file

@ -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

View file

@ -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)
) )

View file

@ -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;

View file

@ -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);

View file

@ -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 {

View file

@ -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 {

View file

@ -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 );