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:
Daniel Larimer 2015-06-23 10:12:53 -04:00
parent 8ac4bc1d58
commit 06a9488f8b
7 changed files with 112 additions and 34 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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