Progress #31: Add some testing

This commit is contained in:
Nathan Hourt 2015-06-15 17:28:20 -04:00
parent 81c6338dfe
commit ce4846e81b
8 changed files with 152 additions and 85 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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