diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 059483154e..8443ec102e 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -2307,7 +2307,7 @@ namespace golos { namespace chain { const auto& delegator = get_account(dvir.account); asset delegator_vesting = create_vesting(delegator, asset(delegator_claim, STEEM_SYMBOL)); - if (dvir.payout_strategy == to_delegated_vesting) { + if (dvir.payout_strategy == delegator_payout_strategy::to_delegated_vesting) { auto vdo_itr = vdo_idx.find(std::make_tuple(delegatee.name, dvir.account)); if (vdo_itr != vdo_idx.end()) { modify(delegator, [&](account_object& a) { diff --git a/libraries/chain/hardfork.d/0_21.hf b/libraries/chain/hardfork.d/0_21.hf index 08ed2d13fb..2be5d222b5 100644 --- a/libraries/chain/hardfork.d/0_21.hf +++ b/libraries/chain/hardfork.d/0_21.hf @@ -4,6 +4,7 @@ #define STEEMIT_HARDFORK_0_21__1008 (STEEMIT_HARDFORK_0_21) // Remove limit on max delegate interest witness prop #define STEEMIT_HARDFORK_0_21__1009 (STEEMIT_HARDFORK_0_21) // Remove limit on min curation percent witness prop #define STEEMIT_HARDFORK_0_21__1010 (STEEMIT_HARDFORK_0_21) // Fix post bandwidth +#define STEEMIT_HARDFORK_0_21__1045 (STEEMIT_HARDFORK_0_21) // Two payout strategies for vesting delegations with interest #ifdef STEEMIT_BUILD_TESTNET #define STEEMIT_HARDFORK_0_21_TIME 1547787600 // 18 jan 2019 12:00:00 MSK diff --git a/libraries/chain/include/golos/chain/account_object.hpp b/libraries/chain/include/golos/chain/account_object.hpp index fe6f1ad664..319a3e3ae5 100644 --- a/libraries/chain/include/golos/chain/account_object.hpp +++ b/libraries/chain/include/golos/chain/account_object.hpp @@ -209,7 +209,7 @@ class vesting_delegation_object: public object + struct delegate_vesting_shares_with_interest_extension_validator { + delegate_vesting_shares_with_interest_extension_validator(const vesting_delegation_object* vdo, database& db) + : _vdo(vdo), _db(db) { + } + + using result_type = void; + + const vesting_delegation_object* _vdo; + database& _db; + + result_type operator()(const delegate_delegator_payout_strategy& ddps) const { + ASSERT_REQ_HF(STEEMIT_HARDFORK_0_21__1045, "delegate_delegator_payout_strategy"); + + if (_vdo) { + GOLOS_CHECK_LOGIC(_vdo->payout_strategy == ddps.strategy, + logic_exception::cannot_change_delegator_payout_strategy, + "Cannot change payout strategy of already created delegation"); + } + } + }; + + struct delegate_vesting_shares_with_interest_extension_visitor { + delegate_vesting_shares_with_interest_extension_visitor(const vesting_delegation_object* vdo, database& db) + : _vdo(vdo), _db(db) { + } + + using result_type = void; + + const vesting_delegation_object* _vdo; + database& _db; + + result_type operator()(const delegate_delegator_payout_strategy& ddps) const { + if (!_vdo) { + return; + } + + _db.modify(*_vdo, [&](vesting_delegation_object& _vdo) { + _vdo.payout_strategy = ddps.strategy; + }); + } + }; + +template void delegate_vesting_shares( database& _db, const chain_properties& median_props, const Operation& op, - CreateVdo&& create_vdo, ValidateWithVdo&& validate_with_vdo + const delegate_vesting_shares_with_interest_extensions_type* extensions, uint16_t interest_rate ) { const auto& delegator = _db.get_account(op.delegator); const auto& delegatee = _db.get_account(op.delegatee); auto delegation = _db.find(std::make_tuple(op.delegator, op.delegatee)); if (delegation) { - validate_with_vdo(*delegation); + GOLOS_CHECK_LOGIC(delegation->interest_rate == interest_rate, + logic_exception::cannot_change_delegator_interest_rate, + "Cannot change interest rate of already created delegation"); + } + + if (extensions) { + for (auto& e : *extensions) { + e.visit(delegate_vesting_shares_with_interest_extension_validator(delegation, _db)); + } } const auto v_share_price = _db.get_dynamic_global_properties().get_vesting_share_price(); @@ -2492,12 +2542,14 @@ void delegate_vesting_shares( }); if (increasing) { - auto delegated = delegator.delegated_vesting_shares; GOLOS_CHECK_BALANCE(delegator, AVAILABLE_VESTING, delta); + auto elapsed_seconds = (now - delegator.last_vote_time).to_seconds(); auto regenerated_power = (STEEMIT_100_PERCENT * elapsed_seconds) / STEEMIT_VOTE_REGENERATION_SECONDS; auto current_power = std::min(delegator.voting_power + regenerated_power, STEEMIT_100_PERCENT); auto max_allowed = (uint128_t(delegator.vesting_shares.amount) * current_power / STEEMIT_100_PERCENT).to_uint64(); + + auto delegated = delegator.delegated_vesting_shares; GOLOS_CHECK_LOGIC(delegated + delta <= asset(max_allowed, VESTS_SYMBOL), logic_exception::delegation_limited_by_voting_power, "Account allowed to delegate a maximum of ${v} with current voting power = ${p}", @@ -2510,17 +2562,26 @@ void delegate_vesting_shares( "Account must delegate a minimum of ${v}", ("v",min_delegation)("vesting_shares",op.vesting_shares)); }); - _db.create([&](vesting_delegation_object& o) { + + delegation = &_db.create([&](vesting_delegation_object& o) { o.delegator = op.delegator; o.delegatee = op.delegatee; o.vesting_shares = op.vesting_shares; o.min_delegation_time = now; - create_vdo(o); + o.interest_rate = interest_rate; + }); + } else { + _db.modify(*delegation, [&](vesting_delegation_object& o) { + o.vesting_shares = op.vesting_shares; }); } + _db.modify(delegator, [&](account_object& a) { a.delegated_vesting_shares += delta; }); + _db.modify(delegatee, [&](account_object& a) { + a.received_vesting_shares += delta; + }); } else { GOLOS_CHECK_OP_PARAM(op, vesting_shares, { GOLOS_CHECK_LOGIC(op.vesting_shares.amount == 0 || op.vesting_shares >= min_delegation, @@ -2528,23 +2589,29 @@ void delegate_vesting_shares( "Delegation must be removed or leave minimum delegation amount of ${v}", ("v",min_delegation)("vesting_shares",op.vesting_shares)); }); + _db.create([&](vesting_delegation_expiration_object& o) { o.delegator = op.delegator; o.vesting_shares = -delta; o.expiration = std::max(now + STEEMIT_CASHOUT_WINDOW_SECONDS, delegation->min_delegation_time); }); - } - _db.modify(delegatee, [&](account_object& a) { - a.received_vesting_shares += delta; - }); - if (delegation) { - if (op.vesting_shares.amount > 0) { + _db.modify(delegatee, [&](account_object& a) { + a.received_vesting_shares += delta; + }); + + if (op.vesting_shares.amount == 0) { + _db.remove(*delegation); + } else { _db.modify(*delegation, [&](vesting_delegation_object& o) { o.vesting_shares = op.vesting_shares; }); - } else { - _db.remove(*delegation); + } + } + + if (extensions) { + for (auto& e : *extensions) { + e.visit(delegate_vesting_shares_with_interest_extension_visitor(delegation, _db)); } } } @@ -2552,7 +2619,7 @@ void delegate_vesting_shares( void delegate_vesting_shares_evaluator::do_apply(const delegate_vesting_shares_operation& op) { const auto& median_props = _db.get_witness_schedule_object().median_props; - delegate_vesting_shares(_db, median_props, op, [&](auto&){}, [&](auto&){}); + delegate_vesting_shares(_db, median_props, op, nullptr, 0); } void break_free_referral_evaluator::do_apply(const break_free_referral_operation& op) { @@ -2584,17 +2651,7 @@ void delegate_vesting_shares( GOLOS_CHECK_LIMIT_PARAM(op.interest_rate, median_props.max_delegated_vesting_interest_rate); - delegate_vesting_shares(_db, median_props, op, [&](auto& o) { - o.interest_rate = op.interest_rate; - o.payout_strategy = op.payout_strategy; - }, [&](auto& o) { - GOLOS_CHECK_LOGIC(o.interest_rate == op.interest_rate, - logic_exception::cannot_change_delegator_interest_rate, - "Cannot change interest rate of already created delegation"); - GOLOS_CHECK_LOGIC(o.payout_strategy == op.payout_strategy, - logic_exception::cannot_change_delegator_payout_strategy, - "Cannot change payout strategy of already created delegation"); - }); + delegate_vesting_shares(_db, median_props, op, &op.extensions, op.interest_rate); } void reject_vesting_shares_delegation_evaluator::do_apply(const reject_vesting_shares_delegation_operation& op) { diff --git a/libraries/protocol/include/golos/protocol/steem_operations.hpp b/libraries/protocol/include/golos/protocol/steem_operations.hpp index fd57873542..20cef98702 100644 --- a/libraries/protocol/include/golos/protocol/steem_operations.hpp +++ b/libraries/protocol/include/golos/protocol/steem_operations.hpp @@ -1348,20 +1348,38 @@ namespace golos { namespace protocol { } }; - enum delegator_payout_strategy { + enum class delegator_payout_strategy { to_delegator, to_delegated_vesting, _size }; + struct delegate_delegator_payout_strategy { + delegate_delegator_payout_strategy() { + } + + delegate_delegator_payout_strategy(delegator_payout_strategy strat) + : strategy(strat) { + } + + delegator_payout_strategy strategy = delegator_payout_strategy::to_delegator; + + void validate() const; + }; + + using delegate_vesting_shares_with_interest_extension = static_variant< + delegate_delegator_payout_strategy + >; + + using delegate_vesting_shares_with_interest_extensions_type = flat_set; + class delegate_vesting_shares_with_interest_operation : public base_operation { public: account_name_type delegator; ///< The account delegating vesting shares account_name_type delegatee; ///< The account receiving vesting shares asset vesting_shares; ///< The amount of vesting shares delegated uint16_t interest_rate = STEEMIT_DEFAULT_DELEGATED_VESTING_INTEREST_RATE; ///< The interest rate wanted by delegator - delegator_payout_strategy payout_strategy = to_delegator; ///< The strategy of delegator vesting payouts - extensions_type extensions; ///< Extensions. Not currently used. + delegate_vesting_shares_with_interest_extensions_type extensions; ///< Extensions. void validate() const; void get_required_active_authorities(flat_set& a) const { @@ -1490,5 +1508,7 @@ FC_REFLECT((golos::protocol::chain_properties_update_operation), (owner)(props)) FC_REFLECT((golos::protocol::break_free_referral_operation), (referral)(extensions)); FC_REFLECT_ENUM(golos::protocol::delegator_payout_strategy, (to_delegator)(to_delegated_vesting)(_size)) +FC_REFLECT((golos::protocol::delegate_delegator_payout_strategy), (strategy)) +FC_REFLECT_TYPENAME((golos::protocol::delegate_vesting_shares_with_interest_extension)); FC_REFLECT((golos::protocol::delegate_vesting_shares_with_interest_operation), (delegator)(delegatee)(vesting_shares)(interest_rate)(extensions)); FC_REFLECT((golos::protocol::reject_vesting_shares_delegation_operation), (delegator)(delegatee)(extensions)); diff --git a/libraries/protocol/steem_operations.cpp b/libraries/protocol/steem_operations.cpp index d743104fad..a9b66ed265 100644 --- a/libraries/protocol/steem_operations.cpp +++ b/libraries/protocol/steem_operations.cpp @@ -727,12 +727,33 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM_ACCOUNT(referral); } + struct delegate_vesting_shares_with_interest_extension_validate_visitor { + delegate_vesting_shares_with_interest_extension_validate_visitor() { + } + + using result_type = void; + + void operator()(const delegate_delegator_payout_strategy& ddps) const { + ddps.validate(); + } + }; + + void delegate_delegator_payout_strategy::validate() const { + GOLOS_CHECK_PARAM(strategy, { + GOLOS_CHECK_VALUE(strategy < delegator_payout_strategy::_size, "This value is reserved"); + }); + } + void delegate_vesting_shares_with_interest_operation::validate() const { GOLOS_CHECK_PARAM_ACCOUNT(delegator); GOLOS_CHECK_PARAM_ACCOUNT(delegatee); GOLOS_CHECK_LOGIC(delegator != delegatee, logic_exception::cannot_delegate_to_yourself, "You cannot delegate GESTS to yourself"); GOLOS_CHECK_PARAM(vesting_shares, GOLOS_CHECK_ASSET_GE0(vesting_shares, GESTS)); + + for (auto& e : extensions) { + e.visit(delegate_vesting_shares_with_interest_extension_validate_visitor()); + } } void reject_vesting_shares_delegation_operation::validate() const { diff --git a/libraries/wallet/include/golos/wallet/wallet.hpp b/libraries/wallet/include/golos/wallet/wallet.hpp index 99b7635df2..04b32a4834 100644 --- a/libraries/wallet/include/golos/wallet/wallet.hpp +++ b/libraries/wallet/include/golos/wallet/wallet.hpp @@ -729,9 +729,10 @@ namespace golos { namespace wallet { * @param delegatee The name of the account receiving GESTS * @param vesting_shares The amount of GESTS to delegate * @param interest_rate The interest rate wanted by delegator + * @param payout_strategy The strategy of payout wanted by delegator * @param broadcast true if you wish to broadcast the transaction */ - annotated_signed_transaction delegate_vesting_shares_with_interest(string delegator, string delegatee, asset vesting_shares, uint16_t interest_rate, bool broadcast); + annotated_signed_transaction delegate_vesting_shares_with_interest(string delegator, string delegatee, asset vesting_shares, uint16_t interest_rate, delegator_payout_strategy payout_strategy, bool broadcast); /** diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 00e2dd7dc7..34f76bd4d7 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -2154,7 +2154,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st return my->sign_transaction(tx, broadcast); } - annotated_signed_transaction wallet_api::delegate_vesting_shares_with_interest(string delegator, string delegatee, asset vesting_shares, uint16_t interest_rate, bool broadcast) { + annotated_signed_transaction wallet_api::delegate_vesting_shares_with_interest(string delegator, string delegatee, asset vesting_shares, uint16_t interest_rate, delegator_payout_strategy payout_strategy, bool broadcast) { WALLET_CHECK_UNLOCKED(); delegate_vesting_shares_with_interest_operation op; @@ -2163,6 +2163,15 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st op.vesting_shares = vesting_shares; op.interest_rate = interest_rate; + auto hf = my->_remote_database_api->get_hardfork_version(); + if (hf >= hardfork_version(0, STEEMIT_HARDFORK_0_21)) { + delegate_delegator_payout_strategy ddps; + ddps.strategy = payout_strategy; + op.extensions.insert(ddps); + } else { + FC_ASSERT(payout_strategy == delegator_payout_strategy::to_delegator, "Before HF21 enabled only to_delegator payout strategy"); + } + signed_transaction tx; tx.operations.push_back(op); tx.validate(); diff --git a/plugins/account_history/plugin.cpp b/plugins/account_history/plugin.cpp index 64657016e1..2f40f5e1b4 100644 --- a/plugins/account_history/plugin.cpp +++ b/plugins/account_history/plugin.cpp @@ -486,6 +486,14 @@ if (options.count(name)) { \ insert_receiver(op.account); } + void operator()(const delegate_vesting_shares_with_interest_operation& op) { + insert_pair(op.delegator, op.delegatee); + } + + void operator()(const reject_vesting_shares_delegation_operation& op) { + insert_pair(op.delegatee, op.delegator); + } + // todo: proposal tx signers are receivers void operator()(const proposal_create_operation& op) { insert_dual(op.author); diff --git a/plugins/mongo_db/mongo_db_state.cpp b/plugins/mongo_db/mongo_db_state.cpp index 3a920918f0..845be0df21 100644 --- a/plugins/mongo_db/mongo_db_state.cpp +++ b/plugins/mongo_db/mongo_db_state.cpp @@ -486,6 +486,18 @@ namespace mongo_db { format_value(body, "delegatee", delegation.delegatee); format_value(body, "vesting_shares", delegation.vesting_shares); format_value(body, "interest_rate", delegation.interest_rate); + + std::string payout_strategy; + switch (delegation.payout_strategy) { + case delegator_payout_strategy::to_delegator: + payout_strategy = "to_delegator"; + break; + case delegator_payout_strategy::to_delegated_vesting: + payout_strategy = "to_delegated_vesting"; + break; + } + format_value(body, "payout_strategy", payout_strategy); + format_value(body, "min_delegation_time", delegation.min_delegation_time); format_value(body, "timestamp", state_block.timestamp); diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index 19039572a0..71d1f0150e 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -6973,18 +6973,21 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) signed_transaction tx; + delegate_delegator_payout_strategy ddps; + ddps.strategy = delegator_payout_strategy::to_delegated_vesting; + delegate_vesting_shares_with_interest_operation op; op.vesting_shares = ASSET_GESTS(50000); op.delegator = "carol"; op.delegatee = "bob"; - op.payout_strategy = to_delegated_vesting; + op.extensions.insert(ddps); BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, carol_private_key, op)); generate_block(); - tx.operations.clear(); - tx.signatures.clear(); - op.payout_strategy = to_delegator; op.delegator = "dave"; + ddps.strategy = delegator_payout_strategy::to_delegator; + op.extensions.clear(); + op.extensions.insert(ddps); BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, dave_private_key, op)); generate_block(); tx.operations.clear();