Merge pull request #183 from peerplays-network/son_vesting

Son vesting balance implementation
This commit is contained in:
Alfredo Garcia 2019-10-20 10:29:15 -03:00 committed by GitHub
commit 412177964c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 172 additions and 31 deletions

View file

@ -232,8 +232,10 @@
#define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month
#define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week
#define MIN_SON_MEMBER_COUNT 15
#define SON_VESTING_AMOUNT (50*GRAPHENE_BLOCKCHAIN_PRECISION) // 50 PPY
#define SON_VESTING_PERIOD (60*60*24*30) // 2 days
#define MIN_SON_PAY_DAILY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200))
#define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT)
#define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0))
#define SWEEPS_VESTING_BALANCE_MULTIPLIER 100000000
#define SWEEPS_ACCUMULATOR_ACCOUNT (graphene::chain::account_id_type(0))
#define MIN_SON_PAY_DAILY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200))

View file

@ -27,6 +27,8 @@
#include <graphene/chain/protocol/types.hpp>
#include <fc/smart_ref_fwd.hpp>
#include <graphene/chain/hardfork.hpp>
namespace graphene { namespace chain { struct fee_schedule; } }
namespace graphene { namespace chain {
@ -41,6 +43,8 @@ namespace graphene { namespace chain {
optional< uint16_t > sweeps_distribution_percentage;
optional< asset_id_type > sweeps_distribution_asset;
optional< account_id_type > sweeps_vesting_accumulator_account;
optional < uint32_t > son_vesting_amount;
optional < uint32_t > son_vesting_period;
optional < uint32_t > son_pay_daily_max;
};
@ -125,6 +129,11 @@ namespace graphene { namespace chain {
inline uint16_t son_count()const {
return extensions.value.son_count.valid() ? *extensions.value.son_count : MIN_SON_MEMBER_COUNT;
}
inline uint32_t son_vesting_amount()const {
return extensions.value.son_vesting_amount.valid() ? *extensions.value.son_vesting_amount : SON_VESTING_AMOUNT; /// current period start date
}
inline uint32_t son_vesting_period()const {
return extensions.value.son_vesting_period.valid() ? *extensions.value.son_vesting_period : SON_VESTING_PERIOD; /// current period start date
inline uint16_t son_pay_daily_max()const {
return extensions.value.son_pay_daily_max.valid() ? *extensions.value.son_pay_daily_max : MIN_SON_PAY_DAILY_MAX;
}
@ -142,6 +151,8 @@ FC_REFLECT( graphene::chain::parameter_extension,
(sweeps_distribution_percentage)
(sweeps_distribution_asset)
(sweeps_vesting_accumulator_account)
(son_vesting_amount)
(son_vesting_period)
(son_pay_daily_max)
)

View file

@ -24,7 +24,9 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
namespace graphene { namespace chain {
namespace graphene { namespace chain {
enum class vesting_balance_type { normal, gpos, son };
struct linear_vesting_policy_initializer
{
@ -42,9 +44,10 @@ namespace graphene { namespace chain {
cdd_vesting_policy_initializer( uint32_t vest_sec = 0, fc::time_point_sec sc = fc::time_point_sec() ):start_claim(sc),vesting_seconds(vest_sec){}
};
typedef fc::static_variant<linear_vesting_policy_initializer, cdd_vesting_policy_initializer> vesting_policy_initializer;
struct dormant_vesting_policy_initializer {};
typedef fc::static_variant<linear_vesting_policy_initializer, cdd_vesting_policy_initializer,
dormant_vesting_policy_initializer> vesting_policy_initializer;
/**
* @brief Create a vesting balance.
@ -72,6 +75,7 @@ namespace graphene { namespace chain {
account_id_type owner; ///< Who is able to withdraw the balance
asset amount;
vesting_policy_initializer policy;
vesting_balance_type balance_type;
account_id_type fee_payer()const { return creator; }
void validate()const
@ -112,9 +116,12 @@ namespace graphene { namespace chain {
FC_REFLECT( graphene::chain::vesting_balance_create_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy) )
FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy)(balance_type) )
FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) )
FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) )
FC_REFLECT(graphene::chain::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) )
FC_REFLECT(graphene::chain::dormant_vesting_policy_initializer, )
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer )
FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (normal)(gpos)(son) )

View file

@ -24,6 +24,7 @@
#pragma once
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/protocol/vesting.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
@ -118,10 +119,32 @@ namespace graphene { namespace chain {
void on_withdraw(const vesting_policy_context& ctx);
};
/**
* @brief Cant withdraw anything while balance is in this policy.
*
* This policy is needed to register SON users where balance may be claimable only after
* the SON object is deleted(plus a linear policy).
* When deleting a SON member the dormant mode will be replaced by a linear policy.
*
* @note New funds may not be added to a dormant vesting balance.
*/
struct dormant_vesting_policy
{
asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
bool is_deposit_allowed(const vesting_policy_context& ctx)const;
bool is_deposit_vested_allowed(const vesting_policy_context&)const { return false; }
bool is_withdraw_allowed(const vesting_policy_context& ctx)const;
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);
};
typedef fc::static_variant<
linear_vesting_policy,
cdd_vesting_policy
> vesting_policy;
cdd_vesting_policy,
dormant_vesting_policy
> vesting_policy;
/**
* Vesting balance object is a balance that is locked by the blockchain for a period of time.
@ -140,6 +163,9 @@ namespace graphene { namespace chain {
/// The vesting policy stores details on when funds vest, and controls when they may be withdrawn
vesting_policy policy;
/// We can have 3 types of vesting, gpos, son and the rest
vesting_balance_type balance_type = vesting_balance_type::normal;
vesting_balance_object() {}
asset_id_type get_asset_id() const { return balance.asset_id; }
@ -219,10 +245,13 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy,
(coin_seconds_earned_last_update)
)
FC_REFLECT(graphene::chain::dormant_vesting_policy, )
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy )
FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),
(owner)
(balance)
(policy)
(balance_type)
)

View file

@ -3,6 +3,7 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
namespace graphene { namespace chain {
@ -10,7 +11,9 @@ void_result create_son_evaluator::do_evaluate(const son_create_operation& op)
{ try{
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK");
FC_ASSERT(db().get(op.owner_account).is_lifetime_member(), "Only Lifetime members may register a SON.");
return void_result();
FC_ASSERT(op.deposit(db()).policy.which() == vesting_policy::tag<dormant_vesting_policy>::value,
"Deposit balance must have dormant vesting policy");
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
object_id_type create_son_evaluator::do_apply(const son_create_operation& op)
@ -69,7 +72,19 @@ void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op)
void_result delete_son_evaluator::do_apply(const son_delete_operation& op)
{ try {
const auto& idx = db().get_index_type<son_index>().indices().get<by_id>();
db().remove(*idx.find(op.son_id));
auto son = idx.find(op.son_id);
if(son != idx.end()) {
vesting_balance_object deposit = son->deposit(db());
linear_vesting_policy new_vesting_policy;
new_vesting_policy.begin_timestamp = db().head_block_time();
new_vesting_policy.vesting_cliff_seconds = db().get_global_properties().parameters.son_vesting_period();
db().modify(son->deposit(db()), [&new_vesting_policy](vesting_balance_object &vbo) {
vbo.policy = new_vesting_policy;
});
db().remove(*son);
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -42,6 +42,12 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance
FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount );
FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() );
if(d.head_block_time() < HARDFORK_SON_TIME) // Todo: can be removed after gpos hf time pass
FC_ASSERT( op.balance_type == vesting_balance_type::normal);
if(d.head_block_time() >= HARDFORK_SON_TIME && op.balance_type == vesting_balance_type::son) // Todo: hf check can be removed after pass
FC_ASSERT( op.amount.amount >= d.get_global_properties().parameters.son_vesting_amount() );
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -76,6 +82,11 @@ struct init_policy_visitor
policy.coin_seconds_earned_last_update = now;
p = policy;
}
void operator()( const dormant_vesting_policy_initializer& i )const
{
dormant_vesting_policy policy;
p = policy;
}
};
object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op )
@ -92,10 +103,9 @@ object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance
// If making changes to this logic, check if those changes should also be made there as well.
obj.owner = op.owner;
obj.balance = op.amount;
obj.balance_type = op.balance_type;
op.policy.visit( init_policy_visitor( obj.policy, op.amount.amount, now ) );
} );
return vbo.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -157,6 +157,33 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c
return (ctx.amount <= get_allowed_withdraw(ctx));
}
asset dormant_vesting_policy::get_allowed_withdraw( const vesting_policy_context& ctx )const
{
share_type allowed_withdraw = 0;
return asset( allowed_withdraw, ctx.balance.asset_id );
}
void dormant_vesting_policy::on_deposit(const vesting_policy_context& ctx)
{
}
bool dormant_vesting_policy::is_deposit_allowed(const vesting_policy_context& ctx)const
{
return (ctx.amount.asset_id == ctx.balance.asset_id)
&& sum_below_max_shares(ctx.amount, ctx.balance);
}
void dormant_vesting_policy::on_withdraw(const vesting_policy_context& ctx)
{
}
bool dormant_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)const
{
return (ctx.amount.asset_id == ctx.balance.asset_id)
&& (ctx.amount <= get_allowed_withdraw(ctx));
}
#define VESTING_VISITOR(NAME, MAYBE_CONST) \
struct NAME ## _visitor \
{ \

View file

@ -1561,7 +1561,7 @@ BOOST_AUTO_TEST_CASE( vesting_balance_create_test )
op.amount = test_asset.amount( 100 );
//op.vesting_seconds = 60*60*24;
op.policy = cdd_vesting_policy_initializer{ 60*60*24 };
//op.balance_type == vesting_balance_type::unspecified;
op.balance_type == vesting_balance_type::normal;
// Fee must be non-negative
REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount(1) );
@ -1581,7 +1581,7 @@ BOOST_AUTO_TEST_CASE( vesting_balance_create_test )
op.creator = alice_account.get_id();
op.owner = alice_account.get_id();
//op.balance_type = vesting_balance_type::unspecified;
op.balance_type = vesting_balance_type::normal;
account_id_type nobody = account_id_type(1234);
@ -1652,7 +1652,7 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test )
create_op.owner = owner;
create_op.amount = amount;
create_op.policy = cdd_vesting_policy_initializer(vesting_seconds);
//create_op.balance_type = vesting_balance_type::unspecified;
create_op.balance_type = vesting_balance_type::normal;
tx.operations.push_back( create_op );
set_expiration( db, tx );

View file

@ -1316,7 +1316,7 @@ BOOST_AUTO_TEST_CASE(zero_second_vbo)
create_op.owner = alice_id;
create_op.amount = asset(500);
create_op.policy = pinit;
//create_op.balance_type = vesting_balance_type::unspecified;
create_op.balance_type = vesting_balance_type::normal;
signed_transaction create_tx;
create_tx.operations.push_back( create_op );
@ -1400,7 +1400,7 @@ BOOST_AUTO_TEST_CASE( vbo_withdraw_different )
create_op.owner = alice_id;
create_op.amount = asset(100, stuff_id);
create_op.policy = pinit;
//create_op.balance_type = vesting_balance_type::unspecified;
create_op.balance_type = vesting_balance_type::normal;
signed_transaction create_tx;
create_tx.operations.push_back( create_op );

View file

@ -5,6 +5,7 @@
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/son_evaluator.hpp>
#include <graphene/chain/vesting_balance_object.hpp>
using namespace graphene::chain;
using namespace graphene::chain::test;
@ -13,9 +14,6 @@ BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture )
BOOST_AUTO_TEST_CASE( create_son_test ) {
generate_blocks(HARDFORK_SON_TIME);
while (db.head_block_time() <= HARDFORK_SON_TIME) {
generate_block();
}
generate_block();
set_expiration(db, trx);
@ -24,8 +22,8 @@ BOOST_AUTO_TEST_CASE( create_son_test ) {
upgrade_to_lifetime_member(alice);
upgrade_to_lifetime_member(bob);
transfer( committee_account, alice_id, asset( 100000 ) );
transfer( committee_account, bob_id, asset( 100000 ) );
transfer( committee_account, alice_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) );
transfer( committee_account, bob_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) );
set_expiration(db, trx);
std::string test_url = "https://create_son_test";
@ -36,31 +34,51 @@ BOOST_AUTO_TEST_CASE( create_son_test ) {
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = asset(10);
//op.balance_type = vesting_balance_type::unspecified;
op.amount = asset(10*GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::son;
op.policy = dormant_vesting_policy_initializer {};
trx.operations.push_back(op);
// amount in the son balance need to be at least 50
GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::exception );
op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION);
trx.clear();
trx.operations.push_back(op);
set_expiration(db, trx);
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
deposit = ptx.operation_results[0].get<object_id_type>();
auto deposit_vesting = db.get<vesting_balance_object>(ptx.operation_results[0].get<object_id_type>());
BOOST_CHECK_EQUAL(deposit(db).balance.amount.value, 50*GRAPHENE_BLOCKCHAIN_PRECISION);
auto now = db.head_block_time();
BOOST_CHECK_EQUAL(deposit(db).is_withdraw_allowed(now, asset(50*GRAPHENE_BLOCKCHAIN_PRECISION)), false); // cant withdraw
}
// create payment vesting
vesting_balance_id_type payment;
generate_block();
set_expiration(db, trx);
// create payment normal vesting
vesting_balance_id_type payment ;
{
vesting_balance_create_operation op;
op.creator = alice_id;
op.owner = alice_id;
op.amount = asset(10);
//op.balance_type = vesting_balance_type::unspecified;
op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION);
op.balance_type = vesting_balance_type::normal;
op.validate();
trx.operations.push_back(op);
set_expiration(db, trx);
trx.validate();
processed_transaction ptx = PUSH_TX(db, trx, ~0);
trx.clear();
payment = ptx.operation_results[0].get<object_id_type>();
}
generate_block();
set_expiration(db, trx);
// alice became son
{
son_create_operation op;
@ -112,10 +130,14 @@ BOOST_AUTO_TEST_CASE( update_son_test ) {
}
BOOST_AUTO_TEST_CASE( delete_son_test ) {
try {
INVOKE(create_son_test);
GET_ACTOR(alice);
auto deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
auto now = db.head_block_time();
BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // cant withdraw
{
son_delete_operation op;
op.owner_account = alice_id;
@ -129,7 +151,25 @@ BOOST_AUTO_TEST_CASE( delete_son_test ) {
const auto& idx = db.get_index_type<son_index>().indices().get<by_account>();
BOOST_REQUIRE( idx.empty() );
deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
BOOST_CHECK_EQUAL(deposit_vesting.policy.get<linear_vesting_policy>().vesting_cliff_seconds,
db.get_global_properties().parameters.son_vesting_period()); // in linear policy
now = db.head_block_time();
BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // but still cant withdraw
generate_blocks(now + fc::seconds(db.get_global_properties().parameters.son_vesting_period()));
generate_block();
deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
now = db.head_block_time();
BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), true); // after 2 days withdraw is allowed
}
catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
} }
BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner
try {