Progress #31: Add some testing
This commit is contained in:
parent
81c6338dfe
commit
ce4846e81b
8 changed files with 152 additions and 85 deletions
|
|
@ -224,6 +224,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator:
|
|||
if( o.upgrade_to_lifetime_member )
|
||||
{
|
||||
// Upgrade to lifetime member. I don't care what the account was before.
|
||||
a.statistics(d).process_fees(a, d);
|
||||
a.membership_expiration_date = time_point_sec::maximum();
|
||||
a.referrer = a.registrar = a.lifetime_referrer = a.get_id();
|
||||
a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - a.network_fee_percentage;
|
||||
|
|
@ -234,6 +235,7 @@ void_result account_upgrade_evaluator::do_apply(const account_upgrade_evaluator:
|
|||
a.membership_expiration_date += fc::days(365);
|
||||
} else {
|
||||
// Upgrade from basic account.
|
||||
a.statistics(d).process_fees(a, d);
|
||||
assert(a.is_basic_account(d.head_block_time()));
|
||||
a.membership_expiration_date = d.head_block_time() + fc::days(365);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,24 @@
|
|||
*/
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/database.hpp>
|
||||
#include <fc/uint128.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
share_type cut_fee(share_type a, uint16_t p)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
bool account_object::is_authorized_asset(const asset_object& asset_obj) const {
|
||||
for( const auto id : blacklisting_accounts )
|
||||
if( asset_obj.options.blacklist_authorities.find(id) != asset_obj.options.blacklist_authorities.end() ) return false;
|
||||
|
|
@ -56,4 +70,65 @@ uint16_t account_statistics_object::calculate_bulk_discount_percent(const chain_
|
|||
return bulk_discount_percent;
|
||||
}
|
||||
|
||||
void account_statistics_object::process_fees(const account_object& a, database& d) const
|
||||
{
|
||||
if( pending_fees > 0 || pending_vested_fees > 0 )
|
||||
{
|
||||
const auto& props = d.get_global_properties();
|
||||
|
||||
auto 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 );
|
||||
};
|
||||
|
||||
share_type vesting_fee_subtotal(pending_fees);
|
||||
share_type vested_fee_subtotal(pending_vested_fees);
|
||||
share_type vesting_cashback, vested_cashback;
|
||||
|
||||
if( lifetime_fees_paid > props.parameters.bulk_discount_threshold_min &&
|
||||
a.is_member(d.head_block_time()) )
|
||||
{
|
||||
auto bulk_discount_rate = calculate_bulk_discount_percent(props.parameters);
|
||||
vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
|
||||
vesting_fee_subtotal -= vesting_cashback;
|
||||
|
||||
vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
|
||||
vested_fee_subtotal -= vested_cashback;
|
||||
}
|
||||
|
||||
pay_out_fees(a, vesting_fee_subtotal, true);
|
||||
d.deposit_cashback(a, vesting_cashback, true);
|
||||
pay_out_fees(a, vested_fee_subtotal, false);
|
||||
d.deposit_cashback(a, vested_cashback, false);
|
||||
|
||||
d.modify(*this, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
|
||||
s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
|
||||
s.pending_fees = 0;
|
||||
s.pending_vested_fees = 0;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
|
|||
|
|
@ -357,77 +357,8 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
|
|||
process_fees_helper(database& d, const global_property_object& 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) {
|
||||
const account_statistics_object& stats = a.statistics(d);
|
||||
|
||||
if( stats.pending_fees > 0 )
|
||||
{
|
||||
share_type vesting_fee_subtotal(stats.pending_fees);
|
||||
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 &&
|
||||
a.is_member(d.head_block_time()) )
|
||||
{
|
||||
auto bulk_discount_rate = stats.calculate_bulk_discount_percent(props.parameters);
|
||||
vesting_cashback = cut_fee(vesting_fee_subtotal, bulk_discount_rate);
|
||||
vesting_fee_subtotal -= vesting_cashback;
|
||||
|
||||
vested_cashback = cut_fee(vested_fee_subtotal, bulk_discount_rate);
|
||||
vested_fee_subtotal -= vested_cashback;
|
||||
}
|
||||
|
||||
pay_out_fees(a, vesting_fee_subtotal, true);
|
||||
d.deposit_cashback(a, vesting_cashback, true);
|
||||
pay_out_fees(a, vested_fee_subtotal, false);
|
||||
d.deposit_cashback(a, vested_cashback, false);
|
||||
|
||||
d.modify(stats, [vested_fee_subtotal, vesting_fee_subtotal](account_statistics_object& s) {
|
||||
s.lifetime_fees_paid += vested_fee_subtotal + vesting_fee_subtotal;
|
||||
s.pending_fees = 0;
|
||||
s.pending_vested_fees = 0;
|
||||
});
|
||||
}
|
||||
a.statistics(d).process_fees(a, d);
|
||||
}
|
||||
} fee_helper(*this, gpo);
|
||||
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
#include <boost/multi_index/composite_key.hpp>
|
||||
|
||||
namespace graphene { namespace chain {
|
||||
class database;
|
||||
|
||||
/**
|
||||
* @class account_statistics_object
|
||||
|
|
@ -64,8 +65,8 @@ namespace graphene { namespace chain {
|
|||
* them yet (registrar, referrer, lifetime referrer, network, etc). This is used as an optimization to avoid
|
||||
* doing massive amounts of uint128 arithmetic on each and every operation.
|
||||
*
|
||||
*These fees will be paid out as vesting cash-back, 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;
|
||||
/**
|
||||
|
|
@ -76,6 +77,8 @@ namespace graphene { namespace chain {
|
|||
|
||||
/// @brief Calculate the percentage discount this user receives on his fees
|
||||
uint16_t calculate_bulk_discount_percent(const chain_parameters& params)const;
|
||||
/// @brief Split up and pay out @ref pending_fees and @ref pending_vested_fees
|
||||
void process_fees(const account_object& a, database& d) const;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -189,6 +192,12 @@ namespace graphene { namespace chain {
|
|||
* Vesting balance which receives cashback_reward deposits.
|
||||
*/
|
||||
optional<vesting_balance_id_type> cashback_vb;
|
||||
template<typename DB>
|
||||
const vesting_balance_object& cashback_balance(const DB& db)const
|
||||
{
|
||||
FC_ASSERT(cashback_vb);
|
||||
return db.get(*cashback_vb);
|
||||
}
|
||||
|
||||
/// @return true if this is a lifetime member account; false otherwise.
|
||||
bool is_lifetime_member()const
|
||||
|
|
|
|||
|
|
@ -98,8 +98,7 @@ namespace graphene { namespace chain {
|
|||
> vesting_policy;
|
||||
|
||||
/**
|
||||
* Timelocked balance object is a balance that is locked by the
|
||||
* blockchain for a period of time.
|
||||
* Vesting balance object is a balance that is locked by the blockchain for a period of time.
|
||||
*/
|
||||
class vesting_balance_object : public abstract_object<vesting_balance_object>
|
||||
{
|
||||
|
|
|
|||
|
|
@ -270,11 +270,17 @@ void database_fixture::generate_blocks( uint32_t block_count )
|
|||
generate_block();
|
||||
}
|
||||
|
||||
void database_fixture::generate_blocks( fc::time_point_sec timestamp )
|
||||
void database_fixture::generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks)
|
||||
{
|
||||
if( miss_intermediate_blocks )
|
||||
{
|
||||
auto slots_to_miss = db.get_slot_at_time(timestamp) - 1;
|
||||
assert(slots_to_miss > 0);
|
||||
generate_block(~0, generate_private_key("genesis"), slots_to_miss);
|
||||
return;
|
||||
}
|
||||
while( db.head_block_time() < timestamp )
|
||||
generate_block();
|
||||
return;
|
||||
}
|
||||
|
||||
account_create_operation database_fixture::make_account(
|
||||
|
|
@ -683,14 +689,33 @@ void database_fixture::upgrade_to_lifetime_member( const account_object& account
|
|||
account_upgrade_operation op;
|
||||
op.account_to_upgrade = account.get_id();
|
||||
op.upgrade_to_lifetime_member = true;
|
||||
op.fee = op.calculate_fee(db.get_global_properties().parameters.current_fees);
|
||||
trx.operations = {op};
|
||||
db.push_transaction( trx, ~0 );
|
||||
db.push_transaction(trx, ~0);
|
||||
FC_ASSERT( op.account_to_upgrade(db).is_lifetime_member() );
|
||||
trx.clear();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((account))
|
||||
}
|
||||
|
||||
void database_fixture::upgrade_to_annual_member(account_id_type account)
|
||||
{
|
||||
upgrade_to_annual_member(account(db));
|
||||
}
|
||||
|
||||
void database_fixture::upgrade_to_annual_member(const account_object& account)
|
||||
{
|
||||
try {
|
||||
account_upgrade_operation op;
|
||||
op.account_to_upgrade = account.get_id();
|
||||
op.fee = op.calculate_fee(db.get_global_properties().parameters.current_fees);
|
||||
trx.operations = {op};
|
||||
db.push_transaction(trx, ~0);
|
||||
FC_ASSERT( op.account_to_upgrade(db).is_member(db.head_block_time()) );
|
||||
trx.clear();
|
||||
} FC_CAPTURE_AND_RETHROW((account))
|
||||
}
|
||||
|
||||
void database_fixture::print_market( const string& syma, const string& symb )const
|
||||
{
|
||||
const auto& limit_idx = db.get_index_type<limit_order_index>();
|
||||
|
|
|
|||
|
|
@ -119,13 +119,13 @@ struct database_fixture {
|
|||
* @brief Generates block_count blocks
|
||||
* @param block_count number of blocks to generate
|
||||
*/
|
||||
void generate_blocks( uint32_t block_count );
|
||||
void generate_blocks(uint32_t block_count);
|
||||
|
||||
/**
|
||||
* @brief Generates blocks until the head block time matches or exceeds timestamp
|
||||
* @param timestamp target time to generate blocks until
|
||||
*/
|
||||
void generate_blocks( fc::time_point_sec timestamp );
|
||||
void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = false);
|
||||
|
||||
account_create_operation make_account(
|
||||
const std::string& name = "nathan",
|
||||
|
|
@ -206,6 +206,8 @@ struct database_fixture {
|
|||
void enable_fees( share_type fee = GRAPHENE_BLOCKCHAIN_PRECISION );
|
||||
void upgrade_to_lifetime_member( account_id_type account );
|
||||
void upgrade_to_lifetime_member( const account_object& account );
|
||||
void upgrade_to_annual_member( account_id_type account );
|
||||
void upgrade_to_annual_member( const account_object& account );
|
||||
void print_market( const string& syma, const string& symb )const;
|
||||
string pretty( const asset& a )const;
|
||||
void print_short_order( const short_order_object& cur )const;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@
|
|||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#include <graphene/chain/vesting_balance_object.hpp>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
|
||||
#include "../common/database_fixture.hpp"
|
||||
|
|
@ -36,15 +38,15 @@ BOOST_AUTO_TEST_CASE( cashback_test )
|
|||
* /----------------\ /----------------\ | *
|
||||
* | ann (Annual) | | dumy (basic) | | *
|
||||
* \----------------/ \----------------/ |-------------. *
|
||||
* | Refers & L--------------------------------. | | *
|
||||
* v Registers Refers v v | *
|
||||
* | Refers L--------------------------------. | | *
|
||||
* v Refers v v | *
|
||||
* /----------------\ /----------------\ | *
|
||||
* | scud (basic) | | stud (basic) | | *
|
||||
* \----------------/ | (Upgrades to | | *
|
||||
* | scud (basic) |<------------------------| stud (basic) | | *
|
||||
* \----------------/ Registers | (Upgrades to | | *
|
||||
* | Lifetime) | v *
|
||||
* \----------------/ /--------------\ *
|
||||
* L------->| pleb (Basic) | *
|
||||
* \--------------/ *
|
||||
* Refers \--------------/ *
|
||||
* *
|
||||
*/
|
||||
ACTOR(life);
|
||||
|
|
@ -62,6 +64,7 @@ BOOST_AUTO_TEST_CASE( cashback_test )
|
|||
const auto& fees = db.get_global_properties().parameters.current_fees;
|
||||
|
||||
#define CustomRegisterActor(actor_name, registrar_name, referrer_name, referrer_rate) \
|
||||
account_id_type actor_name ## _id; \
|
||||
{ \
|
||||
account_create_operation op; \
|
||||
op.registrar = registrar_name ## _id; \
|
||||
|
|
@ -74,10 +77,31 @@ BOOST_AUTO_TEST_CASE( cashback_test )
|
|||
op.fee = op.calculate_fee(fees); \
|
||||
trx.operations = {op}; \
|
||||
trx.sign(registrar_name ## _key_id, registrar_name ## _private_key); \
|
||||
db.push_transaction(trx); \
|
||||
actor_name ## _id = db.push_transaction(trx).operation_results.front().get<object_id_type>(); \
|
||||
trx.clear(); \
|
||||
}
|
||||
|
||||
CustomRegisterActor(ann, life, life, 75);
|
||||
|
||||
transfer(life_id, ann_id, asset(1000000));
|
||||
upgrade_to_annual_member(ann_id);
|
||||
|
||||
CustomRegisterActor(dumy, reggie, life, 75);
|
||||
CustomRegisterActor(stud, reggie, ann, 80);
|
||||
|
||||
transfer(life_id, stud_id, asset(1000000));
|
||||
upgrade_to_lifetime_member(stud_id);
|
||||
|
||||
CustomRegisterActor(pleb, reggie, stud, 95);
|
||||
CustomRegisterActor(scud, stud, ann, 80);
|
||||
|
||||
generate_block();
|
||||
generate_blocks(db.get_dynamic_global_properties().next_maintenance_time, true);
|
||||
|
||||
BOOST_CHECK_EQUAL(life_id(db).cashback_balance(db).balance.amount.value, 78000);
|
||||
BOOST_CHECK_EQUAL(reggie_id(db).cashback_balance(db).balance.amount.value, 34000);
|
||||
BOOST_CHECK_EQUAL(ann_id(db).cashback_balance(db).balance.amount.value, 40000);
|
||||
BOOST_CHECK_EQUAL(stud_id(db).cashback_balance(db).balance.amount.value, 8000);
|
||||
} FC_LOG_AND_RETHROW() }
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
|
|
|||
Loading…
Reference in a new issue