add dormant vesting policy for son
This commit is contained in:
parent
fcdbce84ea
commit
964d7e0dc0
6 changed files with 192 additions and 16 deletions
|
|
@ -44,9 +44,16 @@ 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){}
|
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
|
||||||
|
{
|
||||||
|
/** none may be claimed if dormant is true, otherwise this is a linear policy */
|
||||||
|
bool dormant = true;
|
||||||
|
fc::time_point_sec begin_timestamp;
|
||||||
|
uint32_t vesting_cliff_seconds = 0;
|
||||||
|
uint32_t vesting_duration_seconds = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef fc::static_variant<linear_vesting_policy_initializer, cdd_vesting_policy_initializer, dormant_vesting_policy_initializer> vesting_policy_initializer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Create a vesting balance.
|
* @brief Create a vesting balance.
|
||||||
|
|
@ -120,6 +127,7 @@ FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_b
|
||||||
|
|
||||||
FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) )
|
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::cdd_vesting_policy_initializer, (start_claim)(vesting_seconds) )
|
||||||
|
FC_REFLECT(graphene::chain::dormant_vesting_policy_initializer, (dormant)(begin_timestamp)(vesting_cliff_seconds)(vesting_duration_seconds) )
|
||||||
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer )
|
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer )
|
||||||
|
|
||||||
FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(gpos)(son) )
|
FC_REFLECT_ENUM( graphene::chain::vesting_balance_type, (unspecified)(gpos)(son) )
|
||||||
|
|
|
||||||
|
|
@ -123,10 +123,44 @@ namespace graphene { namespace chain {
|
||||||
void on_withdraw(const vesting_policy_context& ctx);
|
void on_withdraw(const vesting_policy_context& ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Cant withdraw anything while dormant mode is true, linear policy after that changes.
|
||||||
|
*
|
||||||
|
* This vesting balance type 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 change and the linear policy will became active.
|
||||||
|
*
|
||||||
|
* @note New funds may not be added to a dormant vesting balance.
|
||||||
|
*/
|
||||||
|
struct dormant_vesting_policy
|
||||||
|
{
|
||||||
|
/// dormant mode flag indicates if we are in dormant mode or linear policy.
|
||||||
|
bool dormant_mode = true;
|
||||||
|
|
||||||
|
/// This is the time at which funds begin vesting.
|
||||||
|
fc::time_point_sec begin_timestamp;
|
||||||
|
/// No amount may be withdrawn before this many seconds of the vesting period have elapsed.
|
||||||
|
uint32_t vesting_cliff_seconds = 0;
|
||||||
|
/// Duration of the vesting period, in seconds. Must be greater than 0 and greater than vesting_cliff_seconds.
|
||||||
|
uint32_t vesting_duration_seconds = 0;
|
||||||
|
/// The total amount of asset to vest.
|
||||||
|
share_type begin_balance;
|
||||||
|
|
||||||
|
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<
|
typedef fc::static_variant<
|
||||||
linear_vesting_policy,
|
linear_vesting_policy,
|
||||||
cdd_vesting_policy
|
cdd_vesting_policy,
|
||||||
> vesting_policy;
|
dormant_vesting_policy
|
||||||
|
> vesting_policy;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Vesting 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.
|
||||||
|
|
@ -225,6 +259,14 @@ FC_REFLECT(graphene::chain::cdd_vesting_policy,
|
||||||
(coin_seconds_earned_last_update)
|
(coin_seconds_earned_last_update)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
FC_REFLECT(graphene::chain::dormant_vesting_policy,
|
||||||
|
(dormant_mode)
|
||||||
|
(begin_timestamp)
|
||||||
|
(vesting_cliff_seconds)
|
||||||
|
(vesting_duration_seconds)
|
||||||
|
(begin_balance)
|
||||||
|
)
|
||||||
|
|
||||||
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy )
|
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy )
|
||||||
|
|
||||||
FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),
|
FC_REFLECT_DERIVED(graphene::chain::vesting_balance_object, (graphene::db::object),
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <graphene/chain/database.hpp>
|
#include <graphene/chain/database.hpp>
|
||||||
#include <graphene/chain/son_object.hpp>
|
#include <graphene/chain/son_object.hpp>
|
||||||
#include <graphene/chain/hardfork.hpp>
|
#include <graphene/chain/hardfork.hpp>
|
||||||
|
#include <graphene/chain/vesting_balance_object.hpp>
|
||||||
|
|
||||||
namespace graphene { namespace chain {
|
namespace graphene { namespace chain {
|
||||||
|
|
||||||
|
|
@ -68,7 +69,20 @@ void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op)
|
||||||
void_result delete_son_evaluator::do_apply(const son_delete_operation& op)
|
void_result delete_son_evaluator::do_apply(const son_delete_operation& op)
|
||||||
{ try {
|
{ try {
|
||||||
const auto& idx = db().get_index_type<son_member_index>().indices().get<by_id>();
|
const auto& idx = db().get_index_type<son_member_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());
|
||||||
|
dormant_vesting_policy new_vesting_policy;
|
||||||
|
new_vesting_policy.dormant_mode = false;
|
||||||
|
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();
|
return void_result();
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,11 @@ void_result vesting_balance_create_evaluator::do_evaluate( const vesting_balance
|
||||||
if(d.head_block_time() < HARDFORK_GPOS_TIME) // Todo: can be removed after gpos hf time pass
|
if(d.head_block_time() < HARDFORK_GPOS_TIME) // Todo: can be removed after gpos hf time pass
|
||||||
FC_ASSERT( op.balance_type == vesting_balance_type::unspecified);
|
FC_ASSERT( op.balance_type == vesting_balance_type::unspecified);
|
||||||
|
|
||||||
|
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();
|
return void_result();
|
||||||
|
|
||||||
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
} FC_CAPTURE_AND_RETHROW( (op) ) }
|
||||||
|
|
||||||
struct init_policy_visitor
|
struct init_policy_visitor
|
||||||
|
|
@ -79,6 +83,17 @@ struct init_policy_visitor
|
||||||
policy.coin_seconds_earned_last_update = now;
|
policy.coin_seconds_earned_last_update = now;
|
||||||
p = policy;
|
p = policy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void operator()( const dormant_vesting_policy_initializer& i )const
|
||||||
|
{
|
||||||
|
dormant_vesting_policy policy;
|
||||||
|
policy.dormant_mode = i.dormant;
|
||||||
|
policy.begin_timestamp = i.begin_timestamp;
|
||||||
|
policy.vesting_cliff_seconds = i.vesting_cliff_seconds;
|
||||||
|
policy.vesting_duration_seconds = i.vesting_duration_seconds;
|
||||||
|
policy.begin_balance = init_balance;
|
||||||
|
p = policy;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op )
|
object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op )
|
||||||
|
|
@ -105,6 +120,14 @@ object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance
|
||||||
p.vesting_duration_seconds = gpo.parameters.gpos_subperiod();
|
p.vesting_duration_seconds = gpo.parameters.gpos_subperiod();
|
||||||
obj.policy = p;
|
obj.policy = p;
|
||||||
}
|
}
|
||||||
|
if(op.balance_type == vesting_balance_type::son)
|
||||||
|
{
|
||||||
|
const auto &gpo = d.get_global_properties();
|
||||||
|
// forcing son dormant policy
|
||||||
|
dormant_vesting_policy p;
|
||||||
|
p.dormant_mode = true;
|
||||||
|
obj.policy = p;
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
op.policy.visit(init_policy_visitor(obj.policy, op.amount.amount, now));
|
op.policy.visit(init_policy_visitor(obj.policy, op.amount.amount, now));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,6 +157,59 @@ bool cdd_vesting_policy::is_withdraw_allowed(const vesting_policy_context& ctx)c
|
||||||
return (ctx.amount <= get_allowed_withdraw(ctx));
|
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;
|
||||||
|
|
||||||
|
if( !dormant_mode && ctx.now > begin_timestamp)
|
||||||
|
{
|
||||||
|
const auto elapsed_seconds = (ctx.now - begin_timestamp).to_seconds();
|
||||||
|
assert( elapsed_seconds > 0 );
|
||||||
|
|
||||||
|
if( elapsed_seconds >= vesting_cliff_seconds )
|
||||||
|
{
|
||||||
|
share_type total_vested = 0;
|
||||||
|
if( elapsed_seconds < vesting_duration_seconds )
|
||||||
|
{
|
||||||
|
total_vested = (fc::uint128_t( begin_balance.value ) * elapsed_seconds / vesting_duration_seconds).to_uint64();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
total_vested = begin_balance;
|
||||||
|
}
|
||||||
|
assert( total_vested >= 0 );
|
||||||
|
|
||||||
|
const share_type withdrawn_already = begin_balance - ctx.balance.amount;
|
||||||
|
assert( withdrawn_already >= 0 );
|
||||||
|
|
||||||
|
allowed_withdraw = total_vested - withdrawn_already;
|
||||||
|
assert( 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) \
|
#define VESTING_VISITOR(NAME, MAYBE_CONST) \
|
||||||
struct NAME ## _visitor \
|
struct NAME ## _visitor \
|
||||||
{ \
|
{ \
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
#include <graphene/chain/son_object.hpp>
|
#include <graphene/chain/son_object.hpp>
|
||||||
#include <graphene/chain/son_evaluator.hpp>
|
#include <graphene/chain/son_evaluator.hpp>
|
||||||
|
#include <graphene/chain/vesting_balance_object.hpp>
|
||||||
|
|
||||||
using namespace graphene::chain;
|
using namespace graphene::chain;
|
||||||
using namespace graphene::chain::test;
|
using namespace graphene::chain::test;
|
||||||
|
|
@ -12,9 +13,6 @@ BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture )
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE( create_son_test ) {
|
BOOST_AUTO_TEST_CASE( create_son_test ) {
|
||||||
generate_blocks(HARDFORK_SON_TIME);
|
generate_blocks(HARDFORK_SON_TIME);
|
||||||
while (db.head_block_time() <= HARDFORK_SON_TIME) {
|
|
||||||
generate_block();
|
|
||||||
}
|
|
||||||
generate_block();
|
generate_block();
|
||||||
set_expiration(db, trx);
|
set_expiration(db, trx);
|
||||||
|
|
||||||
|
|
@ -37,29 +35,50 @@ BOOST_AUTO_TEST_CASE( create_son_test ) {
|
||||||
op.owner = alice_id;
|
op.owner = alice_id;
|
||||||
op.amount = asset(10);
|
op.amount = asset(10);
|
||||||
op.balance_type = vesting_balance_type::son;
|
op.balance_type = vesting_balance_type::son;
|
||||||
|
|
||||||
trx.operations.push_back(op);
|
trx.operations.push_back(op);
|
||||||
set_expiration(db, trx);
|
|
||||||
processed_transaction ptx = PUSH_TX(db, trx, ~0);
|
// amount in the son balance need to be at least 50
|
||||||
|
GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::exception );
|
||||||
|
|
||||||
|
op.amount = asset(50);
|
||||||
trx.clear();
|
trx.clear();
|
||||||
|
trx.operations.push_back(op);
|
||||||
|
|
||||||
|
processed_transaction ptx = PUSH_TX(db, trx, ~0);
|
||||||
|
|
||||||
deposit = ptx.operation_results[0].get<object_id_type>();
|
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);
|
||||||
|
auto now = db.head_block_time();
|
||||||
|
BOOST_CHECK_EQUAL(deposit(db).is_withdraw_allowed(now, asset(50)), 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;
|
vesting_balance_create_operation op;
|
||||||
op.creator = alice_id;
|
op.creator = alice_id;
|
||||||
op.owner = alice_id;
|
op.owner = alice_id;
|
||||||
op.amount = asset(10);
|
op.amount = asset(1);
|
||||||
op.balance_type = vesting_balance_type::son;
|
op.balance_type = vesting_balance_type::unspecified;
|
||||||
|
|
||||||
|
op.validate();
|
||||||
|
|
||||||
trx.operations.push_back(op);
|
trx.operations.push_back(op);
|
||||||
set_expiration(db, trx);
|
trx.validate();
|
||||||
processed_transaction ptx = PUSH_TX(db, trx, ~0);
|
processed_transaction ptx = PUSH_TX(db, trx, ~0);
|
||||||
trx.clear();
|
trx.clear();
|
||||||
payment = ptx.operation_results[0].get<object_id_type>();
|
payment = ptx.operation_results[0].get<object_id_type>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generate_block();
|
||||||
|
set_expiration(db, trx);
|
||||||
|
|
||||||
// alice became son
|
// alice became son
|
||||||
{
|
{
|
||||||
son_create_operation op;
|
son_create_operation op;
|
||||||
|
|
@ -115,6 +134,9 @@ BOOST_AUTO_TEST_CASE( delete_son_test ) {
|
||||||
INVOKE(create_son_test);
|
INVOKE(create_son_test);
|
||||||
GET_ACTOR(alice);
|
GET_ACTOR(alice);
|
||||||
|
|
||||||
|
auto deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
|
||||||
|
BOOST_CHECK_EQUAL(deposit_vesting.policy.get<dormant_vesting_policy>().dormant_mode, true); // dormant while active
|
||||||
|
|
||||||
{
|
{
|
||||||
son_delete_operation op;
|
son_delete_operation op;
|
||||||
op.owner_account = alice_id;
|
op.owner_account = alice_id;
|
||||||
|
|
@ -128,6 +150,20 @@ BOOST_AUTO_TEST_CASE( delete_son_test ) {
|
||||||
|
|
||||||
const auto& idx = db.get_index_type<son_member_index>().indices().get<by_account>();
|
const auto& idx = db.get_index_type<son_member_index>().indices().get<by_account>();
|
||||||
BOOST_REQUIRE( idx.empty() );
|
BOOST_REQUIRE( idx.empty() );
|
||||||
|
|
||||||
|
deposit_vesting = db.get<vesting_balance_object>(vesting_balance_id_type(0));
|
||||||
|
BOOST_CHECK_EQUAL(deposit_vesting.policy.get<dormant_vesting_policy>().dormant_mode, false); // not sleeping anymore
|
||||||
|
|
||||||
|
auto 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
|
||||||
}
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner
|
BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue