diff --git a/libraries/chain/include/graphene/chain/operations.hpp b/libraries/chain/include/graphene/chain/operations.hpp index 8a322a50..05187a10 100644 --- a/libraries/chain/include/graphene/chain/operations.hpp +++ b/libraries/chain/include/graphene/chain/operations.hpp @@ -25,6 +25,10 @@ #include #include #include +#include + +/// TODO: why does this file depend upon database objects? +/// we should remove these headers #include #include #include @@ -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& active_auth_set, flat_set&)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 ) +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 ) diff --git a/libraries/chain/include/graphene/chain/predicate.hpp b/libraries/chain/include/graphene/chain/predicate.hpp index a05d51ab..120caf4b 100644 --- a/libraries/chain/include/graphene/chain/predicate.hpp +++ b/libraries/chain/include/graphene/chain/predicate.hpp @@ -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 ) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 22c10b67..d8019d7d 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -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) ) diff --git a/libraries/chain/operations.cpp b/libraries/chain/operations.cpp index 30cf15d3..6167f17d 100644 --- a/libraries/chain/operations.cpp +++ b/libraries/chain/operations.cpp @@ -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& active_auth_set, flat_set&)const diff --git a/libraries/chain/vesting_balance_evaluator.cpp b/libraries/chain/vesting_balance_evaluator.cpp index af521f80..4d1d7ed2 100644 --- a/libraries/chain/vesting_balance_evaluator.cpp +++ b/libraries/chain/vesting_balance_evaluator.cpp @@ -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; } diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index bfc91de5..f57920c2 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -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); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index b96088d6..0ddde5e4 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -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 );