Issue #53 - flexible vesting balance creation
Use static variant to allow the types of vesting balances to be easily extended and the creation operation allows for many different types of initialization parameters. Added a check that requires a minimum claim date which allows creating of vesting balance objects with a cliff.
This commit is contained in:
parent
8ac4bc1d58
commit
06a9488f8b
7 changed files with 112 additions and 34 deletions
|
|
@ -25,6 +25,10 @@
|
|||
#include <graphene/chain/types.hpp>
|
||||
#include <graphene/chain/asset.hpp>
|
||||
#include <graphene/chain/authority.hpp>
|
||||
#include <graphene/chain/predicate.hpp>
|
||||
|
||||
/// TODO: why does this file depend upon database objects?
|
||||
/// we should remove these headers
|
||||
#include <graphene/chain/asset_object.hpp>
|
||||
#include <graphene/chain/worker_object.hpp>
|
||||
#include <graphene/chain/account_object.hpp>
|
||||
|
|
@ -1184,6 +1188,25 @@ namespace graphene { namespace chain {
|
|||
}
|
||||
};
|
||||
|
||||
struct linear_vesting_policy_initializer
|
||||
{
|
||||
/** while vesting begins on begin_date, none may be claimed before the start_claim time */
|
||||
fc::time_point_sec start_claim;
|
||||
fc::time_point_sec begin_date;
|
||||
uint32_t vesting_seconds = 0;
|
||||
};
|
||||
|
||||
struct ccd_vesting_policy_initializer
|
||||
{
|
||||
/** while coindays may accrue over time, none may be claimed before the start_claim time */
|
||||
fc::time_point_sec start_claim;
|
||||
uint32_t vesting_seconds = 0;
|
||||
ccd_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, ccd_vesting_policy_initializer > vesting_policy_initializer;
|
||||
|
||||
|
||||
/**
|
||||
* @brief Create a vesting balance.
|
||||
* @ingroup operations
|
||||
|
|
@ -1203,11 +1226,11 @@ namespace graphene { namespace chain {
|
|||
*/
|
||||
struct vesting_balance_create_operation
|
||||
{
|
||||
asset fee;
|
||||
account_id_type creator; ///< Who provides funds initially
|
||||
account_id_type owner; ///< Who is able to withdraw the balance
|
||||
asset amount;
|
||||
uint32_t vesting_seconds;
|
||||
asset fee;
|
||||
account_id_type creator; ///< Who provides funds initially
|
||||
account_id_type owner; ///< Who is able to withdraw the balance
|
||||
asset amount;
|
||||
vesting_policy_initializer policy;
|
||||
|
||||
account_id_type fee_payer()const { return creator; }
|
||||
void get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const;
|
||||
|
|
@ -1220,6 +1243,7 @@ namespace graphene { namespace chain {
|
|||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Withdraw from a vesting balance.
|
||||
* @ingroup operations
|
||||
|
|
@ -1585,7 +1609,7 @@ FC_REFLECT( graphene::chain::withdraw_permission_claim_operation, (fee)(withdraw
|
|||
FC_REFLECT( graphene::chain::withdraw_permission_delete_operation, (fee)(withdraw_from_account)(authorized_account)
|
||||
(withdrawal_permission) )
|
||||
|
||||
FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(vesting_seconds) )
|
||||
FC_REFLECT( graphene::chain::vesting_balance_create_operation, (fee)(creator)(owner)(amount)(policy) )
|
||||
FC_REFLECT( graphene::chain::vesting_balance_withdraw_operation, (fee)(vesting_balance)(owner)(amount) )
|
||||
|
||||
FC_REFLECT( graphene::chain::worker_create_operation,
|
||||
|
|
@ -1599,3 +1623,6 @@ FC_REFLECT( graphene::chain::void_result, )
|
|||
FC_REFLECT_TYPENAME( graphene::chain::operation )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::operation_result )
|
||||
FC_REFLECT_TYPENAME( fc::flat_set<graphene::chain::vote_id_type> )
|
||||
FC_REFLECT(graphene::chain::linear_vesting_policy_initializer, (start_claim)(begin_date)(vesting_seconds) )
|
||||
FC_REFLECT(graphene::chain::ccd_vesting_policy_initializer, (start_claim)(vesting_seconds) )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::vesting_policy_initializer )
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@
|
|||
|
||||
namespace graphene { namespace chain {
|
||||
|
||||
bool is_valid_symbol( const string& symbol );
|
||||
bool is_valid_name( const string& s );
|
||||
|
||||
class database;
|
||||
|
||||
/**
|
||||
|
|
@ -66,4 +69,5 @@ typedef static_variant<
|
|||
|
||||
FC_REFLECT( graphene::chain::verify_account_name, (account_id)(account_name) )
|
||||
FC_REFLECT( graphene::chain::verify_symbol, (asset_id)(symbol) )
|
||||
FC_REFLECT_TYPENAME( graphene::chain::predicate )
|
||||
|
||||
|
|
|
|||
|
|
@ -38,20 +38,30 @@ namespace graphene { namespace chain {
|
|||
asset _amount)
|
||||
: balance(_balance), now(_now), amount(_amount) {}
|
||||
|
||||
asset balance;
|
||||
asset balance;
|
||||
fc::time_point_sec now;
|
||||
asset amount;
|
||||
asset amount;
|
||||
};
|
||||
|
||||
/**
|
||||
* Linear vesting balance.
|
||||
* @brief Linear vesting balance with cliff
|
||||
*
|
||||
* This vesting balance type is used to mimic traditional stock vesting contracts where
|
||||
* each day a certain amount vests until it is fully matured.
|
||||
*
|
||||
* @note New funds may not be added to a linear vesting balance.
|
||||
*/
|
||||
struct linear_vesting_policy
|
||||
{
|
||||
uint32_t vesting_seconds; // must be greater than zero
|
||||
uint32_t vesting_seconds = 0; ///< must be greater than zero
|
||||
/** while coindays may accrue over time, none may be claimed before first_claim date */
|
||||
fc::time_point_sec start_claim;
|
||||
/** linear vesting may begin prior to allowing the user to actually claim the funds, this
|
||||
* can be used to create a cliff.
|
||||
*/
|
||||
fc::time_point_sec begin_date;
|
||||
share_type begin_balance; // same asset as balance
|
||||
share_type total_withdrawn; // same asset as balance
|
||||
share_type begin_balance; ///< same asset as balance
|
||||
share_type total_withdrawn; ///< same asset as balance
|
||||
|
||||
asset get_allowed_withdraw(const vesting_policy_context& ctx)const;
|
||||
bool is_deposit_allowed(const vesting_policy_context& ctx)const;
|
||||
|
|
@ -63,10 +73,20 @@ namespace graphene { namespace chain {
|
|||
void on_withdraw(const vesting_policy_context& ctx);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief defines vesting in terms of coin-days accrued which allows for dynamic deposit/withdraw
|
||||
*
|
||||
* The economic effect of this vesting policy is to require a certain amount of "interest" to accrue
|
||||
* before the full balance may be withdrawn. Interest accrues as coindays (balance * length held). If
|
||||
* some of the balance is withdrawn, the remaining balance must be held longer.
|
||||
*/
|
||||
struct cdd_vesting_policy
|
||||
{
|
||||
uint32_t vesting_seconds;
|
||||
uint32_t vesting_seconds = 0;
|
||||
fc::uint128_t coin_seconds_earned;
|
||||
/** while coindays may accrue over time, none may be claimed before first_claim date */
|
||||
fc::time_point_sec start_claim;
|
||||
fc::time_point_sec coin_seconds_earned_last_update;
|
||||
|
||||
/**
|
||||
|
|
@ -97,6 +117,7 @@ namespace graphene { namespace chain {
|
|||
cdd_vesting_policy
|
||||
> vesting_policy;
|
||||
|
||||
|
||||
/**
|
||||
* Vesting balance object is a balance that is locked by the blockchain for a period of time.
|
||||
*/
|
||||
|
|
@ -106,9 +127,9 @@ namespace graphene { namespace chain {
|
|||
static const uint8_t space_id = protocol_ids;
|
||||
static const uint8_t type_id = vesting_balance_object_type;
|
||||
|
||||
account_id_type owner;
|
||||
asset balance;
|
||||
vesting_policy policy;
|
||||
account_id_type owner;
|
||||
asset balance;
|
||||
vesting_policy policy;
|
||||
|
||||
vesting_balance_object() {}
|
||||
|
||||
|
|
@ -138,6 +159,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
FC_REFLECT(graphene::chain::linear_vesting_policy,
|
||||
(vesting_seconds)
|
||||
(start_claim)
|
||||
(begin_date)
|
||||
(begin_balance)
|
||||
(total_withdrawn)
|
||||
|
|
@ -145,6 +167,7 @@ FC_REFLECT(graphene::chain::linear_vesting_policy,
|
|||
|
||||
FC_REFLECT(graphene::chain::cdd_vesting_policy,
|
||||
(vesting_seconds)
|
||||
(start_claim)
|
||||
(coin_seconds_earned)
|
||||
(coin_seconds_earned_last_update)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -697,7 +697,6 @@ void vesting_balance_create_operation::validate()const
|
|||
{
|
||||
FC_ASSERT( fee.amount >= 0 );
|
||||
FC_ASSERT( amount.amount > 0 );
|
||||
FC_ASSERT( vesting_seconds > 0 );
|
||||
}
|
||||
|
||||
void vesting_balance_withdraw_operation::get_required_auth(flat_set<account_id_type>& active_auth_set, flat_set<account_id_type>&)const
|
||||
|
|
|
|||
|
|
@ -36,17 +36,48 @@ object_id_type vesting_balance_create_evaluator::do_evaluate( const vesting_bala
|
|||
FC_ASSERT( d.get_balance( creator_account.id, op.amount.asset_id ) >= op.amount );
|
||||
FC_ASSERT( !op.amount.asset_id(d).is_transfer_restricted() );
|
||||
|
||||
/** we cannot create vesting balances that are market issued due to black swans */
|
||||
FC_ASSERT( !op.amount.asset_id(d).is_market_issued() );
|
||||
|
||||
return object_id_type();
|
||||
}
|
||||
|
||||
struct init_policy_visitor
|
||||
{
|
||||
typedef void result_type;
|
||||
|
||||
init_policy_visitor( vesting_policy& po,
|
||||
const share_type& begin_balance,
|
||||
const fc::time_point_sec& n ):p(po),init_balance(begin_balance),now(n){}
|
||||
|
||||
vesting_policy& p;
|
||||
share_type init_balance;
|
||||
fc::time_point_sec now;
|
||||
|
||||
void operator()( const linear_vesting_policy_initializer& i )const
|
||||
{
|
||||
linear_vesting_policy policy;
|
||||
policy.vesting_seconds = i.vesting_seconds;
|
||||
policy.begin_date = i.begin_date;
|
||||
policy.start_claim = i.start_claim;
|
||||
policy.begin_balance = init_balance;
|
||||
p = policy;
|
||||
}
|
||||
|
||||
void operator()( const ccd_vesting_policy_initializer& i )const
|
||||
{
|
||||
cdd_vesting_policy policy;
|
||||
policy.vesting_seconds = i.vesting_seconds;
|
||||
policy.start_claim = i.start_claim;
|
||||
policy.coin_seconds_earned = 0;
|
||||
policy.coin_seconds_earned_last_update = now;
|
||||
p = policy;
|
||||
}
|
||||
};
|
||||
|
||||
object_id_type vesting_balance_create_evaluator::do_apply( const vesting_balance_create_operation& op )
|
||||
{
|
||||
database& d = db();
|
||||
const time_point_sec now = d.head_block_time();
|
||||
|
||||
FC_ASSERT( d.get_balance( op.creator, op.amount.asset_id ) >= op.amount );
|
||||
d.adjust_balance( op.creator, -op.amount );
|
||||
|
||||
const vesting_balance_object& vbo = d.create< vesting_balance_object >( [&]( vesting_balance_object& obj )
|
||||
|
|
@ -55,16 +86,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;
|
||||
|
||||
cdd_vesting_policy policy;
|
||||
policy.vesting_seconds = op.vesting_seconds;
|
||||
policy.coin_seconds_earned = 0;
|
||||
policy.coin_seconds_earned_last_update = now;
|
||||
|
||||
obj.policy = policy;
|
||||
op.policy.visit( init_policy_visitor( obj.policy, op.amount.amount, now ) );
|
||||
} );
|
||||
|
||||
FC_ASSERT( d.get_balance( op.creator, op.amount.asset_id ) >= op.amount );
|
||||
|
||||
return vbo.id;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ inline bool sum_below_max_shares(const asset& a, const asset& b)
|
|||
|
||||
asset linear_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx) const
|
||||
{
|
||||
if(ctx.now <= start_claim)
|
||||
return asset(0, ctx.balance.asset_id);
|
||||
if(ctx.now <= begin_date)
|
||||
return asset(0, ctx.balance.asset_id);
|
||||
if(vesting_seconds == 0)
|
||||
|
|
@ -96,6 +98,8 @@ void cdd_vesting_policy::update_coin_seconds_earned(const vesting_policy_context
|
|||
|
||||
asset cdd_vesting_policy::get_allowed_withdraw(const vesting_policy_context& ctx)const
|
||||
{
|
||||
if(ctx.now <= start_claim)
|
||||
return asset(0, ctx.balance.asset_id);
|
||||
fc::uint128_t cs_earned = compute_coin_seconds_earned(ctx);
|
||||
fc::uint128_t withdraw_available = cs_earned / vesting_seconds;
|
||||
assert(withdraw_available <= ctx.balance.amount.value);
|
||||
|
|
|
|||
|
|
@ -1395,7 +1395,8 @@ BOOST_AUTO_TEST_CASE( vesting_balance_create_test )
|
|||
op.creator = account_id_type();
|
||||
op.owner = account_id_type();
|
||||
op.amount = test_asset.amount( 100 );
|
||||
op.vesting_seconds = 60*60*24;
|
||||
//op.vesting_seconds = 60*60*24;
|
||||
op.policy = ccd_vesting_policy_initializer{ 60*60*24 };
|
||||
|
||||
// Fee must be non-negative
|
||||
REQUIRE_OP_VALIDATION_SUCCESS( op, fee, core.amount( 1 ) );
|
||||
|
|
@ -1407,10 +1408,6 @@ BOOST_AUTO_TEST_CASE( vesting_balance_create_test )
|
|||
REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount( 0 ) );
|
||||
REQUIRE_OP_VALIDATION_FAILURE( op, amount, core.amount( -1 ) );
|
||||
|
||||
// Min vesting period must be at least 1 sec
|
||||
REQUIRE_OP_VALIDATION_SUCCESS( op, vesting_seconds, 1 );
|
||||
REQUIRE_OP_VALIDATION_FAILURE( op, vesting_seconds, 0 );
|
||||
|
||||
// Setup world state we will need to test actual evaluation
|
||||
const account_object& alice_account = create_account( "alice" );
|
||||
const account_object& bob_account = create_account( "bob" );
|
||||
|
|
@ -1488,7 +1485,7 @@ BOOST_AUTO_TEST_CASE( vesting_balance_withdraw_test )
|
|||
create_op.creator = creator;
|
||||
create_op.owner = owner;
|
||||
create_op.amount = amount;
|
||||
create_op.vesting_seconds = vesting_seconds;
|
||||
create_op.policy = ccd_vesting_policy_initializer( vesting_seconds );
|
||||
tx.operations.push_back( create_op );
|
||||
|
||||
processed_transaction ptx = PUSH_TX( db, tx, ~0 );
|
||||
|
|
|
|||
Loading…
Reference in a new issue