Progress #31: Implement threshold for vesting fees

This commit is contained in:
Nathan Hourt 2015-06-11 13:54:42 -04:00
parent c6a7cdf5a3
commit a185f864fc
10 changed files with 234 additions and 201 deletions

View file

@ -36,4 +36,24 @@ void account_balance_object::adjust_balance(const asset& delta)
balance += delta.amount; balance += delta.amount;
} }
uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_parameters& params) const
{
uint64_t bulk_discount_percent = 0;
if( lifetime_fees_paid >= params.bulk_discount_threshold_max )
bulk_discount_percent = params.max_bulk_discount_percent_of_fee;
else if(params.bulk_discount_threshold_max.value !=
params.bulk_discount_threshold_min.value)
{
bulk_discount_percent =
(params.max_bulk_discount_percent_of_fee *
(lifetime_fees_paid.value -
params.bulk_discount_threshold_min.value)) /
(params.bulk_discount_threshold_max.value -
params.bulk_discount_threshold_min.value);
}
assert( bulk_discount_percent <= GRAPHENE_100_PERCENT );
return bulk_discount_percent;
}
} } // graphene::chain } } // graphene::chain

View file

@ -1,23 +0,0 @@
/*
* Copyright (c) 2015, Cryptonomex, Inc.
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <graphene/chain/asset_operations.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_object.hpp>
namespace graphene { namespace chain {
} }

View file

@ -89,7 +89,7 @@ void database::adjust_core_in_orders( const account_object& acnt, asset delta )
} }
} }
void database::deposit_cashback( const account_object& acct, share_type amount ) void database::deposit_cashback(const account_object& acct, share_type amount, bool require_vesting)
{ {
// If we don't have a VBO, or if it has the wrong maturity // If we don't have a VBO, or if it has the wrong maturity
// due to a policy change, cut it loose. // due to a policy change, cut it loose.
@ -112,7 +112,10 @@ void database::deposit_cashback( const account_object& acct, share_type amount )
modify( cashback_vb, [&]( vesting_balance_object& obj ) modify( cashback_vb, [&]( vesting_balance_object& obj )
{ {
obj.deposit( now, amount ); if( require_vesting )
obj.deposit(now, amount);
else
obj.deposit_vested(now, amount);
} ); } );
return; return;
} }
@ -124,7 +127,7 @@ void database::deposit_cashback( const account_object& acct, share_type amount )
cdd_vesting_policy policy; cdd_vesting_policy policy;
policy.vesting_seconds = global_vesting_seconds; policy.vesting_seconds = global_vesting_seconds;
policy.coin_seconds_earned = 0; policy.coin_seconds_earned = require_vesting? 0 : amount.value * policy.vesting_seconds;
policy.coin_seconds_earned_last_update = now; policy.coin_seconds_earned_last_update = now;
obj.policy = policy; obj.policy = policy;

View file

@ -357,86 +357,81 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
process_fees_helper(database& d, const global_property_object& gpo) process_fees_helper(database& d, const global_property_object& gpo)
: d(d), props(gpo) {} : d(d), props(gpo) {}
share_type cut_fee(share_type a, uint16_t p)const
{
if( a == 0 || p == 0 )
return 0;
if( p == GRAPHENE_100_PERCENT )
return a;
fc::uint128 r(a.value);
r *= p;
r /= GRAPHENE_100_PERCENT;
return r.to_uint64();
}
void pay_out_fees(const account_object& account, share_type core_fee_total, bool require_vesting)
{
share_type network_cut = cut_fee(core_fee_total, account.network_fee_percentage);
assert( network_cut <= core_fee_total );
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee);
share_type accumulated = network_cut - burned;
assert( accumulated + burned == network_cut );
share_type lifetime_cut = cut_fee(core_fee_total, account.lifetime_referrer_fee_percentage);
share_type referral = core_fee_total - network_cut - lifetime_cut;
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) {
d.accumulated_fees += network_cut;
});
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
// it directly.
share_type referrer_cut = cut_fee(referral, account.referrer_rewards_percentage);
share_type registrar_cut = referral - referrer_cut;
d.deposit_cashback(d.get(account.lifetime_referrer), lifetime_cut, require_vesting);
d.deposit_cashback(d.get(account.referrer), referrer_cut, require_vesting);
d.deposit_cashback(d.get(account.registrar), registrar_cut, require_vesting);
assert( referrer_cut + registrar_cut + accumulated + burned + lifetime_cut == core_fee_total );
}
void operator()(const account_object& a) { void operator()(const account_object& a) {
const account_statistics_object& stats = a.statistics(d); const account_statistics_object& stats = a.statistics(d);
auto cut_fee = [](share_type a, uint16_t p) -> share_type
{
if( a == 0 || p == 0 )
return 0;
if( p == GRAPHENE_100_PERCENT )
return a;
fc::uint128 r(a.value);
r *= p;
r /= GRAPHENE_100_PERCENT;
return r.to_uint64();
};
if( stats.pending_fees > 0 ) if( stats.pending_fees > 0 )
{ {
share_type core_fee_subtotal(stats.pending_fees); share_type vesting_fee_subtotal(stats.pending_fees);
share_type bulk_cashback = share_type(0); share_type vested_fee_subtotal(stats.pending_vested_fees);
share_type vesting_cashback, vested_cashback;
if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min && if( stats.lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
a.is_member(d.head_block_time()) ) a.is_member(d.head_block_time()) )
{ {
uint64_t bulk_discount_percent = 0; auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters);
if( stats.lifetime_fees_paid >= props.parameters.bulk_discount_threshold_max ) vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
bulk_discount_percent = props.parameters.max_bulk_discount_percent_of_fee; vesting_fee_subtotal -= vesting_cashback;
else if(props.parameters.bulk_discount_threshold_max.value !=
props.parameters.bulk_discount_threshold_min.value)
{
bulk_discount_percent =
(props.parameters.max_bulk_discount_percent_of_fee *
(stats.lifetime_fees_paid.value -
props.parameters.bulk_discount_threshold_min.value)) /
(props.parameters.bulk_discount_threshold_max.value -
props.parameters.bulk_discount_threshold_min.value);
}
assert( bulk_discount_percent <= GRAPHENE_100_PERCENT );
assert( bulk_discount_percent >= 0 );
bulk_cashback = cut_fee(core_fee_subtotal, bulk_discount_percent); vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
assert( bulk_cashback <= core_fee_subtotal ); vested_fee_subtotal -= vested_cashback;
} }
share_type core_fee_total = core_fee_subtotal - bulk_cashback; pay_out_fees(a, vesting_fee_subtotal, true);
share_type network_cut = cut_fee(core_fee_total, a.network_fee_percentage); d.deposit_cashback(a, vesting_cashback, true);
assert( network_cut <= core_fee_total ); pay_out_fees(a, vested_fee_subtotal, false);
share_type burned = cut_fee(network_cut, props.parameters.burn_percent_of_fee); d.deposit_cashback(a, vested_cashback, false);
share_type accumulated = network_cut - burned;
assert( accumulated + burned == network_cut );
share_type lifetime_cut = cut_fee(core_fee_total, a.lifetime_referrer_fee_percentage);
share_type referral = core_fee_total - network_cut - lifetime_cut;
d.modify(dynamic_asset_data_id_type()(d), [network_cut](asset_dynamic_data_object& d) { d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
d.accumulated_fees += network_cut; s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
});
d.modify(a.statistics(d), [core_fee_total](account_statistics_object& s) {
s.lifetime_fees_paid += core_fee_total;
s.pending_fees = 0; s.pending_fees = 0;
s.pending_vested_fees = 0;
}); });
}
d.deposit_cashback( a, bulk_cashback );
// Potential optimization: Skip some of this math and object lookups by special casing on the account type.
// For example, if the account is a lifetime member, we can skip all this and just deposit the referral to
// it directly.
share_type referrer_cut = cut_fee(referral, a.referrer_rewards_percentage);
share_type registrar_cut = referral - referrer_cut;
d.deposit_cashback(d.get(a.lifetime_referrer), lifetime_cut);
d.deposit_cashback(d.get(a.referrer), referrer_cut);
d.deposit_cashback(d.get(a.registrar), registrar_cut);
idump((referrer_cut)(registrar_cut)(bulk_cashback)(accumulated)(burned)(lifetime_cut)(core_fee_subtotal));
assert( referrer_cut + registrar_cut + bulk_cashback + accumulated + burned + lifetime_cut == core_fee_subtotal );
}
} }
} fees_helper(*this, gpo); } fee_helper(*this, gpo);
perform_account_maintenance(std::tie(tally_helper, fees_helper)); perform_account_maintenance(std::tie(tally_helper, fee_helper));
struct clear_canary { struct clear_canary {
clear_canary(vector<uint64_t>& target): target(target){} clear_canary(vector<uint64_t>& target): target(target){}
@ -451,9 +446,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
update_active_witnesses(); update_active_witnesses();
update_active_delegates(); update_active_delegates();
const global_property_object& global_properties = get_global_properties(); if( gpo.pending_parameters )
if( global_properties.pending_parameters ) modify(gpo, [](global_property_object& p) {
modify(get_global_properties(), [](global_property_object& p) {
p.parameters = std::move(*p.pending_parameters); p.parameters = std::move(*p.pending_parameters);
p.pending_parameters.reset(); p.pending_parameters.reset();
}); });
@ -472,7 +466,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
} }
auto next_maintenance_time = get<dynamic_global_property_object>(dynamic_global_property_id_type()).next_maintenance_time; auto next_maintenance_time = get<dynamic_global_property_object>(dynamic_global_property_id_type()).next_maintenance_time;
auto maintenance_interval = get_global_properties().parameters.maintenance_interval; auto maintenance_interval = gpo.parameters.maintenance_interval;
if( next_maintenance_time <= next_block.timestamp ) if( next_maintenance_time <= next_block.timestamp )
{ {

View file

@ -67,7 +67,10 @@ namespace graphene { namespace chain {
d.fee_pool -= core_fee_paid; d.fee_pool -= core_fee_paid;
}); });
db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) { db().modify(*fee_paying_account_statistics, [&](account_statistics_object& s) {
s.pending_fees += core_fee_paid; if( core_fee_paid > db().get_global_properties().parameters.cashback_vesting_threshold )
s.pending_fees += core_fee_paid;
else
s.pending_vested_fees += core_fee_paid;
}); });
} FC_CAPTURE_AND_RETHROW() } } FC_CAPTURE_AND_RETHROW() }

View file

@ -62,11 +62,20 @@ namespace graphene { namespace chain {
/** /**
* Tracks the fees paid by this account which have not been disseminated to the various parties that receive * Tracks the fees paid by this account which have not been disseminated to the various parties that receive
* them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid * them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
* doing massive amounts of uint128 math on each and every operation. * doing massive amounts of uint128 arithmetic on each and every operation.
* *
* These fees will be paid out and this counter will reset during the maintenance interval. *These fees will be paid out as vesting cash-back, and this counter will reset during the maintenance
*interval.
*/ */
share_type pending_fees; share_type pending_fees;
/**
* Same as @ref pending_fees, except these fees will be paid out as pre-vested cash-back (immediately
* available for withdrawal) rather than requiring the normal vesting period.
*/
share_type pending_vested_fees;
/// @brief Calculate the percentage discount this user receives on his fees
uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const;
}; };
/** /**
@ -290,5 +299,6 @@ FC_REFLECT_DERIVED( graphene::chain::account_statistics_object, (graphene::chain
(most_recent_op) (most_recent_op)
(total_core_in_orders) (total_core_in_orders)
(lifetime_fees_paid) (lifetime_fees_paid)
(pending_fees)(pending_vested_fees)
) )

View file

@ -254,7 +254,7 @@ namespace graphene { namespace chain {
void adjust_core_in_orders( const account_object& acnt, asset delta ); void adjust_core_in_orders( const account_object& acnt, asset delta );
// helper to handle cashback rewards // helper to handle cashback rewards
void deposit_cashback( const account_object& acct, share_type amount ); void deposit_cashback(const account_object& acct, share_type amount, bool require_vesting = true);
//////////////////// db_debug.cpp //////////////////// //////////////////// db_debug.cpp ////////////////////

View file

@ -35,9 +35,9 @@ namespace graphene { namespace chain {
vesting_policy_context( vesting_policy_context(
asset _balance, asset _balance,
fc::time_point_sec _now, fc::time_point_sec _now,
asset _amount ) asset _amount)
: balance( _balance ), now( _now ), amount( _amount ) {} : balance(_balance), now(_now), amount(_amount) {}
asset balance; asset balance;
fc::time_point_sec now; fc::time_point_sec now;
asset amount; asset amount;
@ -53,11 +53,14 @@ namespace graphene { namespace chain {
share_type begin_balance; // same asset as balance share_type begin_balance; // same asset as balance
share_type total_withdrawn; // same asset as balance share_type total_withdrawn; // same asset as balance
asset get_allowed_withdraw( const vesting_policy_context& ctx )const; asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
bool is_deposit_allowed( const vesting_policy_context& ctx )const; bool is_deposit_allowed(const vesting_policy_context& ctx)const;
bool is_withdraw_allowed( const vesting_policy_context& ctx )const; bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; }
void on_deposit( const vesting_policy_context& ctx ); bool is_withdraw_allowed(const vesting_policy_context& ctx)const;
void on_withdraw( const vesting_policy_context& ctx ); void on_deposit(const vesting_policy_context& ctx);
void on_deposit_vested(const vesting_policy_context&)
{ FC_THROW( "May not deposit vested into a linear vesting balance." ); }
void on_withdraw(const vesting_policy_context& ctx);
}; };
struct cdd_vesting_policy struct cdd_vesting_policy
@ -71,20 +74,22 @@ namespace graphene { namespace chain {
* non-destructively figure out how many coin seconds * non-destructively figure out how many coin seconds
* are available. * are available.
*/ */
fc::uint128_t compute_coin_seconds_earned( const vesting_policy_context& ctx )const; fc::uint128_t compute_coin_seconds_earned(const vesting_policy_context& ctx)const;
/** /**
* Update coin_seconds_earned and * Update coin_seconds_earned and
* coin_seconds_earned_last_update fields; called by both * coin_seconds_earned_last_update fields; called by both
* on_deposit() and on_withdraw(). * on_deposit() and on_withdraw().
*/ */
void update_coin_seconds_earned( const vesting_policy_context& ctx ); void update_coin_seconds_earned(const vesting_policy_context& ctx);
asset get_allowed_withdraw( const vesting_policy_context& ctx )const; asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
bool is_deposit_allowed( const vesting_policy_context& ctx )const; bool is_deposit_allowed(const vesting_policy_context& ctx)const;
bool is_withdraw_allowed( const vesting_policy_context& ctx )const; bool is_deposit_vested_allowed(const vesting_policy_context& ctx)const;
void on_deposit( const vesting_policy_context& ctx ); bool is_withdraw_allowed(const vesting_policy_context& ctx)const;
void on_withdraw( const vesting_policy_context& ctx ); void on_deposit(const vesting_policy_context& ctx);
void on_deposit_vested(const vesting_policy_context& ctx);
void on_withdraw(const vesting_policy_context& ctx);
}; };
typedef fc::static_variant< typedef fc::static_variant<
@ -102,17 +107,21 @@ namespace graphene { namespace chain {
static const uint8_t space_id = protocol_ids; static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = vesting_balance_object_type; static const uint8_t type_id = vesting_balance_object_type;
account_id_type owner; account_id_type owner;
asset balance; asset balance;
vesting_policy policy; vesting_policy policy;
vesting_balance_object() {} vesting_balance_object() {}
/** /**
* Used to increase existing vesting balances. * Used to increase existing vesting balances.
*/ */
void deposit( const fc::time_point_sec& now, const asset& amount ); void deposit(const fc::time_point_sec& now, const asset& amount);
bool is_deposit_allowed( const fc::time_point_sec& now, const asset& amount )const; bool is_deposit_allowed(const fc::time_point_sec& now, const asset& amount)const;
/// @brief Deposit amount into vesting balance, making the new funds vest immediately
void deposit_vested(const fc::time_point_sec& now, const asset& amount);
bool is_deposit_vested_allowed(const fc::time_point_sec& now, const asset& amount)const;
/** /**
* Used to remove a vesting balance from the VBO. As well * Used to remove a vesting balance from the VBO. As well
@ -122,27 +131,27 @@ namespace graphene { namespace chain {
* The money doesn't "go" anywhere; the caller is responsible * The money doesn't "go" anywhere; the caller is responsible
* for crediting it to the proper account. * for crediting it to the proper account.
*/ */
void withdraw( const fc::time_point_sec& now, const asset& amount ); void withdraw(const fc::time_point_sec& now, const asset& amount);
bool is_withdraw_allowed( const fc::time_point_sec& now, const asset& amount )const; bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const;
}; };
} } // graphene::chain } } // graphene::chain
FC_REFLECT( graphene::chain::linear_vesting_policy, FC_REFLECT(graphene::chain::linear_vesting_policy,
(vesting_seconds) (vesting_seconds)
(begin_date) (begin_date)
(begin_balance) (begin_balance)
(total_withdrawn) (total_withdrawn)
) )
FC_REFLECT( graphene::chain::cdd_vesting_policy, FC_REFLECT(graphene::chain::cdd_vesting_policy,
(vesting_seconds) (vesting_seconds)
(coin_seconds_earned) (coin_seconds_earned)
(coin_seconds_earned_last_update) (coin_seconds_earned_last_update)
) )
FC_REFLECT_DERIVED( graphene::chain::vesting_balance_object, (graphene::db::object), FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),
(owner) (owner)
(balance) (balance)
(policy) (policy)
) )

View file

@ -20,66 +20,64 @@
namespace graphene { namespace chain { namespace graphene { namespace chain {
inline bool sum_below_max_shares( const asset& a, const asset& b ) inline bool sum_below_max_shares(const asset& a, const asset& b)
{ {
assert( GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY ); assert(GRAPHENE_MAX_SHARE_SUPPLY + GRAPHENE_MAX_SHARE_SUPPLY > GRAPHENE_MAX_SHARE_SUPPLY);
return ( a.amount <= GRAPHENE_MAX_SHARE_SUPPLY) return (a.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
&& ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY) && ( b.amount <= GRAPHENE_MAX_SHARE_SUPPLY)
&& ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY) && ((a.amount + b.amount) <= GRAPHENE_MAX_SHARE_SUPPLY)
; ;
} }
asset linear_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx ) const asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const
{ {
if( ctx.now <= begin_date ) if(ctx.now <= begin_date)
return asset( 0, ctx.balance.asset_id ); return asset(0, ctx.balance.asset_id);
if( vesting_seconds == 0 ) if(vesting_seconds == 0)
return ctx.balance; return ctx.balance;
int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds(); int64_t elapsed_seconds = (ctx.now - begin_date).to_seconds();
// if elapsed_seconds <= 0, then ctx.now <= begin_date, // if elapsed_seconds <= 0, then ctx.now <= begin_date,
// and we should have returned above. // and we should have returned above.
assert( elapsed_seconds > 0 ); assert(elapsed_seconds > 0);
fc::uint128_t total_allowed = begin_balance.value; fc::uint128_t total_allowed = begin_balance.value;
total_allowed *= uint64_t( elapsed_seconds ); total_allowed *= uint64_t(elapsed_seconds);
total_allowed /= vesting_seconds; total_allowed /= vesting_seconds;
if( total_allowed <= total_withdrawn.value ) if(total_allowed <= total_withdrawn.value)
return asset( 0, ctx.balance.asset_id ); return asset(0, ctx.balance.asset_id);
total_allowed -= total_withdrawn.value; total_allowed -= total_withdrawn.value;
FC_ASSERT( total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY ); FC_ASSERT(total_allowed <= GRAPHENE_MAX_SHARE_SUPPLY);
return asset( total_allowed.to_uint64(), ctx.balance.asset_id ); return asset(total_allowed.to_uint64(), ctx.balance.asset_id);
} }
void linear_vesting_policy::on_deposit( const vesting_policy_context& ctx ) void linear_vesting_policy::on_deposit(const vesting_policy_context& ctx)
{ {
return;
} }
bool linear_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const bool linear_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const
{ {
return (ctx.amount.asset_id == ctx.balance.asset_id) return (ctx.amount.asset_id == ctx.balance.asset_id)
&& sum_below_max_shares( ctx.amount, ctx.balance ); && sum_below_max_shares(ctx.amount, ctx.balance);
} }
void linear_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) void linear_vesting_policy::on_withdraw(const vesting_policy_context& ctx)
{ {
total_withdrawn += ctx.amount.amount; total_withdrawn += ctx.amount.amount;
return;
} }
bool linear_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const bool linear_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const
{ {
return ( ctx.amount <= get_allowed_withdraw( ctx ) ); return (ctx.amount <= get_allowed_withdraw(ctx));
} }
fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_policy_context& ctx )const fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned(const vesting_policy_context& ctx)const
{ {
assert( ctx.now >= coin_seconds_earned_last_update ); assert(ctx.now >= coin_seconds_earned_last_update);
int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds(); int64_t delta_seconds = (ctx.now - coin_seconds_earned_last_update).to_seconds();
assert( delta_seconds >= 0 ); assert(delta_seconds >= 0);
fc::uint128_t delta_coin_seconds = ctx.balance.amount.value; fc::uint128_t delta_coin_seconds = ctx.balance.amount.value;
delta_coin_seconds *= delta_seconds; delta_coin_seconds *= delta_seconds;
@ -87,113 +85,132 @@ fc::uint128_t cdd_vesting_policy::compute_coin_seconds_earned( const vesting_pol
fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value; fc::uint128_t coin_seconds_earned_cap = ctx.balance.amount.value;
coin_seconds_earned_cap *= vesting_seconds; coin_seconds_earned_cap *= vesting_seconds;
return std::min( return std::min(coin_seconds_earned + delta_coin_seconds, coin_seconds_earned_cap);
coin_seconds_earned + delta_coin_seconds,
coin_seconds_earned_cap
);
} }
void cdd_vesting_policy::update_coin_seconds_earned( const vesting_policy_context& ctx ) void cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context& ctx)
{ {
coin_seconds_earned = compute_coin_seconds_earned( ctx ); coin_seconds_earned = compute_coin_seconds_earned(ctx);
coin_seconds_earned_last_update = ctx.now; coin_seconds_earned_last_update = ctx.now;
return;
} }
asset cdd_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const asset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const
{ {
fc::uint128_t cs_earned = compute_coin_seconds_earned( ctx ); fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx);
fc::uint128_t withdraw_available = cs_earned / vesting_seconds; fc::uint128_t withdraw_available = cs_earned / vesting_seconds;
assert( withdraw_available <= ctx.balance.amount.value ); assert(withdraw_available <= ctx.balance.amount.value);
return asset( withdraw_available.to_uint64(), ctx.balance.asset_id ); return asset(withdraw_available.to_uint64(), ctx.balance.asset_id);
} }
void cdd_vesting_policy::on_deposit( const vesting_policy_context& ctx ) void cdd_vesting_policy::on_deposit(const vesting_policy_context& ctx)
{ {
update_coin_seconds_earned( ctx ); update_coin_seconds_earned(ctx);
return;
} }
void cdd_vesting_policy::on_withdraw( const vesting_policy_context& ctx ) void cdd_vesting_policy::on_deposit_vested(const vesting_policy_context& ctx)
{ {
update_coin_seconds_earned( ctx ); on_deposit(ctx);
coin_seconds_earned += ctx.amount.amount.value * vesting_seconds;
}
void cdd_vesting_policy::on_withdraw(const vesting_policy_context& ctx)
{
update_coin_seconds_earned(ctx);
fc::uint128_t coin_seconds_needed = ctx.amount.amount.value; fc::uint128_t coin_seconds_needed = ctx.amount.amount.value;
coin_seconds_needed *= vesting_seconds; coin_seconds_needed *= vesting_seconds;
// is_withdraw_allowed should forbid any withdrawal that // is_withdraw_allowed should forbid any withdrawal that
// would trigger this assert // would trigger this assert
assert( coin_seconds_needed <= coin_seconds_earned ); assert(coin_seconds_needed <= coin_seconds_earned);
coin_seconds_earned -= coin_seconds_needed; coin_seconds_earned -= coin_seconds_needed;
return;
} }
bool cdd_vesting_policy::is_deposit_allowed( const vesting_policy_context& ctx )const bool cdd_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const
{ {
return (ctx.amount.asset_id == ctx.balance.asset_id) return (ctx.amount.asset_id == ctx.balance.asset_id)
&& sum_below_max_shares( ctx.amount, ctx.balance ); && sum_below_max_shares(ctx.amount, ctx.balance);
} }
bool cdd_vesting_policy::is_withdraw_allowed( const vesting_policy_context& ctx )const bool cdd_vesting_policy::is_deposit_vested_allowed(const vesting_policy_context& ctx) const
{ {
return ( ctx.amount <= get_allowed_withdraw( ctx ) ); return is_deposit_allowed(ctx);
} }
#define VESTING_VISITOR( NAME, MAYBE_CONST ) \ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const
{
return (ctx.amount <= get_allowed_withdraw(ctx));
}
#define VESTING_VISITOR(NAME, MAYBE_CONST) \
struct NAME ## _visitor \ struct NAME ## _visitor \
{ \ { \
typedef decltype( \ typedef decltype( \
std::declval<linear_vesting_policy>().NAME( \ std::declval<linear_vesting_policy>().NAME( \
std::declval<vesting_policy_context>()) \ std::declval<vesting_policy_context>()) \
) result_type; \ ) result_type; \
\ \
NAME ## _visitor( \ NAME ## _visitor( \
const asset& balance, \ const asset& balance, \
const time_point_sec& now, \ const time_point_sec& now, \
const asset& amount \ const asset& amount \
) \ ) \
: ctx( balance, now, amount ) {} \ : ctx(balance, now, amount) {} \
\ \
template< typename Policy > \ template< typename Policy > \
result_type \ result_type \
operator()( MAYBE_CONST Policy& policy ) MAYBE_CONST \ operator()(MAYBE_CONST Policy& policy) MAYBE_CONST \
{ \ { \
return policy.NAME( ctx ); \ return policy.NAME(ctx); \
} \ } \
\ \
vesting_policy_context ctx; \ vesting_policy_context ctx; \
} }
VESTING_VISITOR( on_deposit, ); VESTING_VISITOR(on_deposit,);
VESTING_VISITOR( on_withdraw, ); VESTING_VISITOR(on_deposit_vested,);
VESTING_VISITOR( is_deposit_allowed, const ); VESTING_VISITOR(on_withdraw,);
VESTING_VISITOR( is_withdraw_allowed, const ); VESTING_VISITOR(is_deposit_allowed, const);
VESTING_VISITOR(is_deposit_vested_allowed, const);
VESTING_VISITOR(is_withdraw_allowed, const);
bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const
{ {
return policy.visit( is_deposit_allowed_visitor( balance, now, amount ) ); return policy.visit(is_deposit_allowed_visitor(balance, now, amount));
} }
bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const bool vesting_balance_object::is_withdraw_allowed(const time_point_sec& now, const asset& amount)const
{ {
bool result = policy.visit( is_withdraw_allowed_visitor( balance, now, amount ) ); bool result = policy.visit(is_withdraw_allowed_visitor(balance, now, amount));
// if some policy allows you to withdraw more than your balance, // if some policy allows you to withdraw more than your balance,
// there's a programming bug in the policy algorithm // there's a programming bug in the policy algorithm
assert( (amount <= balance) || (!result) ); assert((amount <= balance) || (!result));
return result; return result;
} }
void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount) void vesting_balance_object::deposit(const time_point_sec& now, const asset& amount)
{ {
on_deposit_visitor vtor( balance, now, amount ); on_deposit_visitor vtor(balance, now, amount);
policy.visit( vtor ); policy.visit(vtor);
balance += amount; balance += amount;
} }
void vesting_balance_object::deposit_vested(const time_point_sec& now, const asset& amount)
{
on_deposit_vested_visitor vtor(balance, now, amount);
policy.visit(vtor);
balance += amount;
}
bool vesting_balance_object::is_deposit_vested_allowed(const time_point_sec& now, const asset& amount) const
{
return policy.visit(is_deposit_vested_allowed_visitor(balance, now, amount));
}
void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount) void vesting_balance_object::withdraw(const time_point_sec& now, const asset& amount)
{ {
assert( amount <= balance ); assert(amount <= balance);
on_withdraw_visitor vtor( balance, now, amount ); on_withdraw_visitor vtor(balance, now, amount);
policy.visit( vtor ); policy.visit(vtor);
balance -= amount; balance -= amount;
} }

View file

@ -119,7 +119,7 @@ void database_fixture::verify_asset_supplies( )const
for( const account_statistics_object& a : statistics_index ) for( const account_statistics_object& a : statistics_index )
{ {
reported_core_in_orders += a.total_core_in_orders; reported_core_in_orders += a.total_core_in_orders;
total_balances[asset_id_type()] += a.pending_fees; total_balances[asset_id_type()] += a.pending_fees + a.pending_vested_fees;
} }
for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() ) for( const limit_order_object& o : db.get_index_type<limit_order_index>().indices() )
{ {