From b247561344ff59b5061ca8e99f0104dc4c755dec Mon Sep 17 00:00:00 2001 From: Anton Korpusenko Date: Tue, 18 Sep 2018 11:04:43 +0300 Subject: [PATCH] Added votable auction window. Added returning of auction rewards to reward poll. #898 Merge pull request #937 from GolosChain/golos-v0.18.4 Golos v0.18.4 Add chain_properties_19 #295 Merge pull request #938 from GolosChain/295-referral-program chain_properties_19 #295 Made auction window votable. Fixed rewards calculations. #898 Merge branch 'master' into golos-v0.19.0 Fix naming in chain_properties_19 #295 Merge pull request #939 from GolosChain/295-chain-propertie Fix naming in chain_properties_19 #295 Updated with current 19.0 Added auction_window_size to wallet update_chain_properties. #898 Fixed some codestyle errors. Changed auction_window_weight to uint128_t. #898 Added HF19 checks. Fixed auction_window_size in update_chain_properties. #898 Added auction_window_max_size constant to config. Added HF19 checks in steem_evaluator. #898 Fixed some comment reward logic in tests. #898 Referral program, ASSERT_REQ_HF improved, HF 19 #295 Merge pull request #942 from GolosChain/295-referral-program-impl2 Referral program implemented #295 Update with current 19.0. #898 Fixed auction window tokens return to reward fund. #898 Renamed modify reward fund function. #898 --- libraries/api/account_api_object.cpp | 7 + libraries/api/chain_api_properties.cpp | 6 + libraries/api/discussion_helper.cpp | 6 + .../include/golos/api/account_api_object.hpp | 8 +- .../golos/api/chain_api_properties.hpp | 8 +- .../include/golos/api/comment_api_object.hpp | 2 + .../chain/chain_properties_evaluators.cpp | 24 +- libraries/chain/database.cpp | 29 +- libraries/chain/hardfork.d/0-preamble.hf | 2 +- libraries/chain/hardfork.d/0_19.hf | 14 + .../include/golos/chain/account_object.hpp | 34 +- .../include/golos/chain/comment_object.hpp | 2 + .../chain/include/golos/chain/database.hpp | 9 + .../chain/include/golos/chain/db_with.hpp | 28 ++ .../chain/include/golos/chain/evaluator.hpp | 8 + .../include/golos/chain/steem_evaluator.hpp | 6 +- .../include/golos/chain/witness_objects.hpp | 2 +- libraries/chain/steem_evaluator.cpp | 154 ++++++++- .../include/golos/protocol/config.hpp | 52 ++- .../include/golos/protocol/exceptions.hpp | 28 +- .../include/golos/protocol/operations.hpp | 1 + .../golos/protocol/steem_operations.hpp | 84 ++++- .../golos/protocol/validate_helper.hpp | 2 +- libraries/protocol/steem_operations.cpp | 60 +++- .../wallet/include/golos/wallet/wallet.hpp | 42 ++- libraries/wallet/wallet.cpp | 116 +++++-- .../plugins/mongo_db/mongo_db_operations.hpp | 1 + .../golos/plugins/mongo_db/mongo_db_state.hpp | 1 + .../plugins/mongo_db/mongo_db_writer.hpp | 2 +- plugins/mongo_db/mongo_db_operations.cpp | 14 + plugins/mongo_db/mongo_db_state.cpp | 86 +++-- .../private_message_plugin.cpp | 7 +- tests/common/comment_reward.hpp | 14 + tests/common/database_fixture.hpp | 2 +- tests/tests/operation_tests.cpp | 324 ++++++++++++++++++ 35 files changed, 1022 insertions(+), 163 deletions(-) create mode 100644 libraries/chain/hardfork.d/0_19.hf diff --git a/libraries/api/account_api_object.cpp b/libraries/api/account_api_object.cpp index 6f6b56d6a9..5b4584ff4a 100644 --- a/libraries/api/account_api_object.cpp +++ b/libraries/api/account_api_object.cpp @@ -68,6 +68,13 @@ account_api_object::account_api_object(const account_object& a, const golos::cha lifetime_market_bandwidth = market->lifetime_bandwidth; last_market_bandwidth_update = market->last_bandwidth_update; } + + if (db.head_block_time() < a.referral_end_date) { + referrer_account = a.referrer_account; + referrer_interest_rate = a.referrer_interest_rate; + referral_end_date = a.referral_end_date; + referral_break_fee = a.referral_break_fee; + } } account_api_object::account_api_object() = default; diff --git a/libraries/api/chain_api_properties.cpp b/libraries/api/chain_api_properties.cpp index 92295a6e1a..f658efcf4c 100644 --- a/libraries/api/chain_api_properties.cpp +++ b/libraries/api/chain_api_properties.cpp @@ -15,6 +15,12 @@ namespace golos { namespace api { create_account_delegation_time = src.create_account_delegation_time; min_delegation = src.min_delegation; } + if (db.has_hardfork(STEEMIT_HARDFORK_0_19)) { + max_referral_interest_rate = src.max_referral_interest_rate; + max_referral_term_sec = src.max_referral_term_sec; + max_referral_break_fee = src.max_referral_break_fee; + auction_window_size = src.auction_window_size; + } } } } // golos::api diff --git a/libraries/api/discussion_helper.cpp b/libraries/api/discussion_helper.cpp index a901d7664d..a62c7b5306 100644 --- a/libraries/api/discussion_helper.cpp +++ b/libraries/api/discussion_helper.cpp @@ -112,6 +112,7 @@ namespace golos { namespace api { d.allow_replies = o.allow_replies; d.allow_votes = o.allow_votes; d.allow_curation_rewards = o.allow_curation_rewards; + d.auction_window_weight = o.auction_window_weight; for (auto& route : o.beneficiaries) { d.beneficiaries.push_back(route); @@ -191,6 +192,11 @@ namespace golos { namespace api { break; } } + + if (db.has_hardfork(STEEMIT_HARDFORK_0_19__898)) { + auto reward_fund_claim = ((max_rewards.value * d.auction_window_weight) / total_weight).to_uint64(); + unclaimed_rewards -= reward_fund_claim; + } } } else { unclaimed_rewards = 0; diff --git a/libraries/api/include/golos/api/account_api_object.hpp b/libraries/api/include/golos/api/account_api_object.hpp index 6c3e09e993..962a865b28 100644 --- a/libraries/api/include/golos/api/account_api_object.hpp +++ b/libraries/api/include/golos/api/account_api_object.hpp @@ -95,6 +95,11 @@ struct account_api_object { set witness_votes; fc::optional reputation; + + account_name_type referrer_account; + uint16_t referrer_interest_rate = 0; + time_point_sec referral_end_date = time_point_sec::min(); + asset referral_break_fee = asset(0, STEEM_SYMBOL); }; } } // golos::api @@ -113,6 +118,7 @@ FC_REFLECT((golos::api::account_api_object), (average_bandwidth)(average_market_bandwidth)(lifetime_bandwidth)(lifetime_market_bandwidth) (last_bandwidth_update)(last_market_bandwidth_update) (last_post)(last_root_post)(post_bandwidth) - (witness_votes)(reputation)) + (witness_votes)(reputation) + (referrer_account)(referrer_interest_rate)(referral_end_date)(referral_break_fee)) #endif //GOLOS_ACCOUNT_API_OBJ_HPP diff --git a/libraries/api/include/golos/api/chain_api_properties.hpp b/libraries/api/include/golos/api/chain_api_properties.hpp index 7f60db0ef2..a6c73c6d5e 100644 --- a/libraries/api/include/golos/api/chain_api_properties.hpp +++ b/libraries/api/include/golos/api/chain_api_properties.hpp @@ -20,6 +20,11 @@ namespace golos { namespace api { fc::optional create_account_min_delegation; fc::optional create_account_delegation_time; fc::optional min_delegation; + + fc::optional max_referral_interest_rate; + fc::optional max_referral_term_sec; + fc::optional max_referral_break_fee; + fc::optional auction_window_size; }; } } // golos::api @@ -28,4 +33,5 @@ FC_REFLECT( (golos::api::chain_api_properties), (account_creation_fee)(maximum_block_size)(sbd_interest_rate) (create_account_min_golos_fee)(create_account_min_delegation) - (create_account_delegation_time)(min_delegation)) + (create_account_delegation_time)(min_delegation) + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)(auction_window_size)) diff --git a/libraries/api/include/golos/api/comment_api_object.hpp b/libraries/api/include/golos/api/comment_api_object.hpp index 10d0bc2336..a24ed7112c 100644 --- a/libraries/api/include/golos/api/comment_api_object.hpp +++ b/libraries/api/include/golos/api/comment_api_object.hpp @@ -61,6 +61,8 @@ namespace golos { namespace api { comment_mode mode = not_set; comment_object::id_type root_comment; + + uint128_t auction_window_weight = 0; string root_title; diff --git a/libraries/chain/chain_properties_evaluators.cpp b/libraries/chain/chain_properties_evaluators.cpp index 0d0cca5e91..0479a6ee83 100644 --- a/libraries/chain/chain_properties_evaluators.cpp +++ b/libraries/chain/chain_properties_evaluators.cpp @@ -40,14 +40,24 @@ namespace golos { namespace chain { } } - struct chain_properties_convert { - using result_type = chain_properties_18; + struct chain_properties_update { + using result_type = void; + + const database& _db; + chain_properties& _wprops; + + chain_properties_update(const database& db, chain_properties& wprops) + : _db(db), _wprops(wprops) { + } + + result_type operator()(const chain_properties_19& p) const { + ASSERT_REQ_HF(STEEMIT_HARDFORK_0_19__295, "chain_properties_19"); + _wprops = p; + } template result_type operator()(Props&& p) const { - result_type r; - r = p; - return r; + _wprops = p; } }; @@ -58,13 +68,13 @@ namespace golos { namespace chain { auto itr = idx.find(o.owner); if (itr != idx.end()) { _db.modify(*itr, [&](witness_object& w) { - w.props = o.props.visit(chain_properties_convert()); + o.props.visit(chain_properties_update(_db, w.props)); }); } else { _db.create([&](witness_object& w) { w.owner = o.owner; w.created = _db.head_block_time(); - w.props = o.props.visit(chain_properties_convert()); + o.props.visit(chain_properties_update(_db, w.props)); }); } } diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 3da8706bde..e08d21fd96 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -1145,7 +1145,7 @@ namespace golos { namespace chain { signed_block pending_block; - with_strong_write_lock([&]() { + with_strong_write_lock([&]() { detail::with_generating(*this, [&]() { // // The following code throws away existing pending_tx_session and // rebuilds it by re-applying pending transactions. @@ -1198,7 +1198,7 @@ namespace golos { namespace chain { } _pending_tx_session.reset(); - }); + }); }); // We have temporarily broken the invariant that // _pending_tx_session is the result of applying _pending_tx, as @@ -1867,16 +1867,16 @@ namespace golos { namespace chain { } void database::update_median_witness_props() { - const witness_schedule_object &wso = get_witness_schedule_object(); + const witness_schedule_object& wso = get_witness_schedule_object(); /// fetch all witness objects - vector active; + vector active; active.reserve(wso.num_scheduled_witnesses); for (int i = 0; i < wso.num_scheduled_witnesses; i++) { active.push_back(&get_witness(wso.current_shuffled_witnesses[i])); } - chain_properties_18 median_props; + chain_properties_19 median_props; auto calc_median = [&](auto&& param) { std::nth_element( @@ -1895,12 +1895,16 @@ namespace golos { namespace chain { calc_median(&chain_properties_18::create_account_min_delegation); calc_median(&chain_properties_18::create_account_delegation_time); calc_median(&chain_properties_18::min_delegation); + calc_median(&chain_properties_19::auction_window_size); + calc_median(&chain_properties_19::max_referral_interest_rate); + calc_median(&chain_properties_19::max_referral_term_sec); + calc_median(&chain_properties_19::max_referral_break_fee); modify(wso, [&](witness_schedule_object &_wso) { _wso.median_props = median_props; }); - modify(get_dynamic_global_properties(), [&](dynamic_global_property_object &_dgpo) { + modify(get_dynamic_global_properties(), [&](dynamic_global_property_object& _dgpo) { _dgpo.maximum_block_size = median_props.maximum_block_size; _dgpo.sbd_interest_rate = median_props.sbd_interest_rate; }); @@ -2283,6 +2287,13 @@ namespace golos { namespace chain { unclaimed_rewards = 0; } + else if (has_hardfork(STEEMIT_HARDFORK_0_19__898) && c.total_vote_weight > 0) { + auto reward_fund_claim = (max_rewards.value * c.auction_window_weight) / total_weight; + unclaimed_rewards -= reward_fund_claim.to_uint64(); + modify(get_dynamic_global_properties(), [&](dynamic_global_property_object &props) { + props.total_reward_fund_steem += asset(reward_fund_claim.to_uint64(), STEEM_SYMBOL); + }); + } return unclaimed_rewards; } FC_CAPTURE_AND_RETHROW() @@ -2927,6 +2938,7 @@ namespace golos { namespace chain { _my->_evaluator_registry.register_evaluator(); _my->_evaluator_registry.register_evaluator(); _my->_evaluator_registry.register_evaluator(); + _my->_evaluator_registry.register_evaluator(); } void database::set_custom_operation_interpreter(const std::string &id, std::shared_ptr registry) { @@ -4362,6 +4374,9 @@ namespace golos { namespace chain { FC_ASSERT(STEEMIT_HARDFORK_0_18 == 18, "Invalid hardfork configuration"); _hardfork_times[STEEMIT_HARDFORK_0_18] = fc::time_point_sec(STEEMIT_HARDFORK_0_18_TIME); _hardfork_versions[STEEMIT_HARDFORK_0_18] = STEEMIT_HARDFORK_0_18_VERSION; + FC_ASSERT(STEEMIT_HARDFORK_0_19 == 19, "Invalid hardfork configuration"); + _hardfork_times[STEEMIT_HARDFORK_0_19] = fc::time_point_sec(STEEMIT_HARDFORK_0_19_TIME); + _hardfork_versions[STEEMIT_HARDFORK_0_19] = STEEMIT_HARDFORK_0_19_VERSION; const auto &hardforks = get_hardfork_property_object(); FC_ASSERT( @@ -4615,6 +4630,8 @@ namespace golos { namespace chain { break; case STEEMIT_HARDFORK_0_18: break; + case STEEMIT_HARDFORK_0_19: + break; default: break; } diff --git a/libraries/chain/hardfork.d/0-preamble.hf b/libraries/chain/hardfork.d/0-preamble.hf index 589223ca80..540ba568b1 100644 --- a/libraries/chain/hardfork.d/0-preamble.hf +++ b/libraries/chain/hardfork.d/0-preamble.hf @@ -51,4 +51,4 @@ FC_REFLECT((golos::chain::hardfork_property_object), (next_hardfork)(next_hardfork_time)) CHAINBASE_SET_INDEX_TYPE( golos::chain::hardfork_property_object, golos::chain::hardfork_property_index) -#define STEEMIT_NUM_HARDFORKS 18 +#define STEEMIT_NUM_HARDFORKS 19 diff --git a/libraries/chain/hardfork.d/0_19.hf b/libraries/chain/hardfork.d/0_19.hf new file mode 100644 index 0000000000..ff6aae249f --- /dev/null +++ b/libraries/chain/hardfork.d/0_19.hf @@ -0,0 +1,14 @@ +#ifndef STEEMIT_HARDFORK_0_19 +#define STEEMIT_HARDFORK_0_19 19 +#define STEEMIT_HARDFORK_0_19__898 (STEEMIT_HARDFORK_0_19) // Add votable auction window size +#define STEEMIT_HARDFORK_0_19__295 (STEEMIT_HARDFORK_0_19) // Referral program implemented + +#ifdef STEEMIT_BUILD_TESTNET +#define STEEMIT_HARDFORK_0_19_TIME 1534755600 // 20 aug 2018 12:00:00 MSK +#else +#define STEEMIT_HARDFORK_0_19_TIME 1543654800 // 01 dec 2018 12:00:00 MSK - fake date +#endif + +#define STEEMIT_HARDFORK_0_19_VERSION hardfork_version( 0, 19 ) + +#endif diff --git a/libraries/chain/include/golos/chain/account_object.hpp b/libraries/chain/include/golos/chain/account_object.hpp index d3ce6b3de5..632aa04214 100644 --- a/libraries/chain/include/golos/chain/account_object.hpp +++ b/libraries/chain/include/golos/chain/account_object.hpp @@ -102,6 +102,11 @@ class account_object time_point_sec last_post; + account_name_type referrer_account; + uint16_t referrer_interest_rate = 0; + time_point_sec referral_end_date = time_point_sec::min(); + asset referral_break_fee = asset(0, STEEM_SYMBOL); + /// This function should be used only when the account votes for a witness directly share_type witness_vote_weight() const { return std::accumulate(proxied_vsf_votes.begin(), @@ -495,20 +500,21 @@ change_recovery_account_request_index; FC_REFLECT((golos::chain::account_object), - (id)(name)(memo_key)(proxy)(last_account_update) - (created)(mined) - (owner_challenged)(active_challenged)(last_owner_proved)(last_active_proved)(recovery_account)(last_account_recovery)(reset_account) - (comment_count)(lifetime_vote_count)(post_count)(can_vote)(voting_power)(last_vote_time) - (balance) - (savings_balance) - (sbd_balance)(sbd_seconds)(sbd_seconds_last_update)(sbd_last_interest_payment) - (savings_sbd_balance)(savings_sbd_seconds)(savings_sbd_seconds_last_update)(savings_sbd_last_interest_payment)(savings_withdraw_requests) - (vesting_shares)(delegated_vesting_shares)(received_vesting_shares) - (vesting_withdraw_rate)(next_vesting_withdrawal)(withdrawn)(to_withdraw)(withdraw_routes) - (curation_rewards) - (posting_rewards) - (proxied_vsf_votes)(witnesses_voted_for) - (last_post) + (id)(name)(memo_key)(proxy)(last_account_update) + (created)(mined) + (owner_challenged)(active_challenged)(last_owner_proved)(last_active_proved)(recovery_account)(last_account_recovery)(reset_account) + (comment_count)(lifetime_vote_count)(post_count)(can_vote)(voting_power)(last_vote_time) + (balance) + (savings_balance) + (sbd_balance)(sbd_seconds)(sbd_seconds_last_update)(sbd_last_interest_payment) + (savings_sbd_balance)(savings_sbd_seconds)(savings_sbd_seconds_last_update)(savings_sbd_last_interest_payment)(savings_withdraw_requests) + (vesting_shares)(delegated_vesting_shares)(received_vesting_shares) + (vesting_withdraw_rate)(next_vesting_withdrawal)(withdrawn)(to_withdraw)(withdraw_routes) + (curation_rewards) + (posting_rewards) + (proxied_vsf_votes)(witnesses_voted_for) + (last_post) + (referrer_account)(referrer_interest_rate)(referral_end_date)(referral_break_fee) ) CHAINBASE_SET_INDEX_TYPE(golos::chain::account_object, golos::chain::account_index) diff --git a/libraries/chain/include/golos/chain/comment_object.hpp b/libraries/chain/include/golos/chain/comment_object.hpp index 24660d3aba..fb6380695a 100644 --- a/libraries/chain/include/golos/chain/comment_object.hpp +++ b/libraries/chain/include/golos/chain/comment_object.hpp @@ -89,6 +89,8 @@ namespace golos { id_type root_comment; + uint128_t auction_window_weight = 0; + comment_mode mode = first_payout; asset max_accepted_payout = asset(1000000000, SBD_SYMBOL); /// SBD value of the maximum payout this post will receive diff --git a/libraries/chain/include/golos/chain/database.hpp b/libraries/chain/include/golos/chain/database.hpp index 0ebc031fc3..c51c367cdb 100644 --- a/libraries/chain/include/golos/chain/database.hpp +++ b/libraries/chain/include/golos/chain/database.hpp @@ -48,7 +48,16 @@ namespace golos { namespace chain { _is_producing = p; } + bool is_generating() const { + return _is_generating; + } + + void set_generating(bool p) { + _is_generating = p; + } + bool _is_producing = false; + bool _is_generating = false; bool _is_testing = false; ///< set for tests to avoid low free memory spam bool _log_hardforks = true; diff --git a/libraries/chain/include/golos/chain/db_with.hpp b/libraries/chain/include/golos/chain/db_with.hpp index 5aa16277f6..65820fc9bc 100644 --- a/libraries/chain/include/golos/chain/db_with.hpp +++ b/libraries/chain/include/golos/chain/db_with.hpp @@ -82,6 +82,21 @@ namespace golos { database &_db; }; + /** + * Class is used to help the with_generating implementation + */ + struct generating_helper final { + generating_helper(database& db): _db(db) { + _db.set_generating(true); + } + + ~generating_helper() { + _db.set_generating(false); + } + + database &_db; + }; + /** * Empty pending_transactions, call callback, * then reset pending_transactions after callback is done. @@ -111,6 +126,19 @@ namespace golos { producing_helper restorer(db); callback(); } + + /** + * Set generating flag to true, call callback, then set generating flag to false + */ + template + void with_generating( + database& db, + Lambda callback + ) { + generating_helper restorer(db); + callback(); + } + } } } // golos::chain::detail diff --git a/libraries/chain/include/golos/chain/evaluator.hpp b/libraries/chain/include/golos/chain/evaluator.hpp index f97f697dea..5db6ea0b14 100644 --- a/libraries/chain/include/golos/chain/evaluator.hpp +++ b/libraries/chain/include/golos/chain/evaluator.hpp @@ -3,6 +3,14 @@ #include #include +#define ASSERT_REQ_HF_IN_DB(HF, FEATURE, DATABASE) \ + GOLOS_ASSERT(DATABASE.has_hardfork(HF), unsupported_operation, \ + "${feature} is not enabled until HF ${hardfork}", \ + ("feature",FEATURE)("hardfork",BOOST_PP_STRINGIZE(HF))); + +#define ASSERT_REQ_HF(HF, FEATURE) \ + ASSERT_REQ_HF_IN_DB(HF, FEATURE, _db) + namespace golos { namespace chain { diff --git a/libraries/chain/include/golos/chain/steem_evaluator.hpp b/libraries/chain/include/golos/chain/steem_evaluator.hpp index 0522ef2552..0f8cfc660e 100644 --- a/libraries/chain/include/golos/chain/steem_evaluator.hpp +++ b/libraries/chain/include/golos/chain/steem_evaluator.hpp @@ -4,11 +4,6 @@ #include #include -#define ASSERT_REQ_HF(HF, FEATURE) \ - GOLOS_ASSERT(db().has_hardfork(HF), unsupported_operation, \ - "${feature} is not enabled until HF ${hardfork}", \ - ("feature",FEATURE)("hardfork",BOOST_PP_STRINGIZE(HF))); - namespace golos { namespace chain { using namespace golos::protocol; @@ -56,6 +51,7 @@ namespace golos { namespace chain { DEFINE_EVALUATOR(delegate_vesting_shares) DEFINE_EVALUATOR(proposal_delete) DEFINE_EVALUATOR(chain_properties_update) + DEFINE_EVALUATOR(break_free_referral) class proposal_create_evaluator: public evaluator_impl { public: diff --git a/libraries/chain/include/golos/chain/witness_objects.hpp b/libraries/chain/include/golos/chain/witness_objects.hpp index 845da5409b..e6e6573510 100644 --- a/libraries/chain/include/golos/chain/witness_objects.hpp +++ b/libraries/chain/include/golos/chain/witness_objects.hpp @@ -17,7 +17,7 @@ namespace golos { namespace chain { using golos::protocol::asset; using golos::protocol::asset_symbol_type; - using chain_properties = golos::protocol::chain_properties_18; + using chain_properties = golos::protocol::chain_properties_19; /** * All witnesses with at least 1% net positive approval and diff --git a/libraries/chain/steem_evaluator.cpp b/libraries/chain/steem_evaluator.cpp index 98f50681cc..7b103dbdcf 100644 --- a/libraries/chain/steem_evaluator.cpp +++ b/libraries/chain/steem_evaluator.cpp @@ -233,6 +233,39 @@ namespace golos { namespace chain { } } + struct account_create_with_delegation_extension_visitor { + account_create_with_delegation_extension_visitor(const account_object& a, database& db) + : _a(a), _db(db) { + } + + using result_type = void; + + const account_object& _a; + database& _db; + + void operator()(const account_referral_options& aro) const { + ASSERT_REQ_HF(STEEMIT_HARDFORK_0_19__295, "account_referral_options"); + + _db.get_account(aro.referrer); + + const auto& median_props = _db.get_witness_schedule_object().median_props; + + GOLOS_CHECK_LIMIT_PARAM(aro.interest_rate, median_props.max_referral_interest_rate); + + GOLOS_CHECK_PARAM(aro.end_date, aro.end_date >= _db.head_block_time()); + GOLOS_CHECK_LIMIT_PARAM(aro.end_date, _db.head_block_time() + median_props.max_referral_term_sec); + + GOLOS_CHECK_LIMIT_PARAM(aro.break_fee, median_props.max_referral_break_fee); + + _db.modify(_a, [&](account_object& a) { + a.referrer_account = aro.referrer; + a.referrer_interest_rate = aro.interest_rate; + a.referral_end_date = aro.end_date; + a.referral_break_fee = aro.break_fee; + }); + } + }; + void account_create_with_delegation_evaluator::do_apply(const account_create_with_delegation_operation& o) { const auto& creator = _db.get_account(o.creator); GOLOS_CHECK_BALANCE(creator, MAIN_BALANCE, o.fee); @@ -304,6 +337,10 @@ namespace golos { namespace chain { if (o.fee.amount > 0) { _db.create_vesting(new_account, o.fee); } + + for (auto& e : o.extensions) { + e.visit(account_create_with_delegation_extension_visitor(new_account, _db)); + } } void account_update_evaluator::do_apply(const account_update_operation &o) { @@ -445,35 +482,58 @@ namespace golos { namespace chain { } struct comment_options_extension_visitor { - comment_options_extension_visitor(const comment_object &c, database &db) - : _c(c), _db(db) { + comment_options_extension_visitor(const comment_object& c, database& db) + : _c(c), _db(db), _a(db.get_account(_c.author)) { } using result_type = void; - const comment_object &_c; - database &_db; + const comment_object& _c; + database& _db; + const account_object& _a; - void operator()(const comment_payout_beneficiaries &cpb) const { + void operator()(const comment_payout_beneficiaries& cpb) const { if (_db.is_producing()) { GOLOS_CHECK_LOGIC(cpb.beneficiaries.size() <= STEEMIT_MAX_COMMENT_BENEFICIARIES, - logic_exception::cannot_specify_more_beneficiaries, - "Cannot specify more than ${m} beneficiaries.", ("m", STEEMIT_MAX_COMMENT_BENEFICIARIES)); + logic_exception::cannot_specify_more_beneficiaries, + "Cannot specify more than ${m} beneficiaries.", ("m", STEEMIT_MAX_COMMENT_BENEFICIARIES)); } - GOLOS_CHECK_LOGIC(_c.beneficiaries.size() == 0, + uint16_t total_weight = 0; + + if (_c.beneficiaries.size() == 1 + && _c.beneficiaries.front().account == _a.referrer_account) { + total_weight += _c.beneficiaries[0].weight; + auto& referrer = _a.referrer_account; + const auto& itr = std::find_if(cpb.beneficiaries.begin(), cpb.beneficiaries.end(), + [&referrer](const beneficiary_route_type& benef) { + return benef.account == referrer; + }); + GOLOS_CHECK_LOGIC(itr == cpb.beneficiaries.end(), + logic_exception::beneficiaries_should_be_unique, + "Comment already has '${referrer}' as a referrer-beneficiary.", ("referrer",referrer)); + } else { + GOLOS_CHECK_LOGIC(_c.beneficiaries.size() == 0, logic_exception::comment_already_has_beneficiaries, "Comment already has beneficiaries specified."); + } + GOLOS_CHECK_LOGIC(_c.abs_rshares == 0, - logic_exception::comment_must_not_have_been_voted, - "Comment must not have been voted on before specifying beneficiaries."); + logic_exception::comment_must_not_have_been_voted, + "Comment must not have been voted on before specifying beneficiaries."); - _db.modify(_c, [&](comment_object &c) { - for (auto &b : cpb.beneficiaries) { + _db.modify(_c, [&](comment_object& c) { + for (auto& b : cpb.beneficiaries) { _db.get_account(b.account); // check beneficiary exists c.beneficiaries.push_back(b); + total_weight += b.weight; } }); + + GOLOS_CHECK_PARAM("beneficiaries", { + GOLOS_CHECK_VALUE(total_weight <= STEEMIT_100_PERCENT, + "Cannot allocate more than 100% of rewards to a comment"); + }); } }; @@ -507,14 +567,14 @@ namespace golos { namespace chain { logic_exception::comment_cannot_accept_greater_percent_GBG, "A comment cannot accept a greater percent SBD."); - _db.modify(comment, [&](comment_object &c) { + _db.modify(comment, [&](comment_object& c) { c.max_accepted_payout = o.max_accepted_payout; c.percent_steem_dollars = o.percent_steem_dollars; c.allow_votes = o.allow_votes; c.allow_curation_rewards = o.allow_curation_rewards; }); - for (auto &e : o.extensions) { + for (auto& e : o.extensions) { e.visit(comment_options_extension_visitor(comment, _db)); } } @@ -532,10 +592,11 @@ namespace golos { namespace chain { const auto &auth = _db.get_account(o.author); /// prove it exists - if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) + if (_db.has_hardfork(STEEMIT_HARDFORK_0_10)) { GOLOS_CHECK_LOGIC(!(auth.owner_challenged || auth.active_challenged), logic_exception::account_is_currently_challenged, "Operation cannot be processed because account is currently challenged."); + } comment_id_type id; @@ -626,6 +687,8 @@ namespace golos { namespace chain { a.post_count++; }); + bool referrer_to_delete = false; + _db.create([&](comment_object &com) { if (_db.has_hardfork(STEEMIT_HARDFORK_0_1)) { GOLOS_CHECK_OP_PARAM(o, parent_permlink, validate_permlink_0_1(o.parent_permlink)); @@ -656,14 +719,31 @@ namespace golos { namespace chain { com.cashout_time = fc::time_point_sec::maximum(); } - if (_db.has_hardfork( STEEMIT_HARDFORK_0_17__431)) { + if (_db.has_hardfork(STEEMIT_HARDFORK_0_17__431)) { com.cashout_time = com.created + STEEMIT_CASHOUT_WINDOW_SECONDS; } + if (auth.referrer_account != account_name_type()) { + if (_db.head_block_time() < auth.referral_end_date) { + com.beneficiaries.push_back(beneficiary_route_type(auth.referrer_account, + auth.referrer_interest_rate)); + } else { + referrer_to_delete = true; + } + } }); + if (referrer_to_delete) { + _db.modify(auth, [&](account_object& a) { + a.referrer_account = account_name_type(); + a.referrer_interest_rate = 0; + a.referral_end_date = time_point_sec::min(); + a.referral_break_fee.amount = 0; + }); + } + while (parent) { - _db.modify(*parent, [&](comment_object &p) { + _db.modify(*parent, [&](comment_object& p) { p.children++; }); if (parent->parent_author != STEEMIT_ROOT_POST_PARENT) { @@ -1428,14 +1508,28 @@ namespace golos { namespace chain { fc::time_point_sec(STEEMIT_HARDFORK_0_6_REVERSE_AUCTION_TIME)) /// start enforcing this prior to the hardfork { /// discount weight by time + uint32_t auction_window = STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS; + + if (_db.has_hardfork(STEEMIT_HARDFORK_0_19__898)) { + const witness_schedule_object& wso = _db.get_witness_schedule_object(); + auction_window = wso.median_props.auction_window_size; + } + uint128_t w(max_vote_weight); uint64_t delta_t = std::min(uint64_t(( cv.last_update - - comment.created).to_seconds()), uint64_t(STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS)); + comment.created).to_seconds()), uint64_t(auction_window)); w *= delta_t; - w /= STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS; + w /= auction_window; cv.weight = w.to_uint64(); + + if (_db.has_hardfork(STEEMIT_HARDFORK_0_19__898)) { + _db.modify(comment, [&](comment_object &o) { + o.auction_window_weight += max_vote_weight - w.to_uint64(); + }); + } + } } else { cv.weight = 0; @@ -2288,4 +2382,26 @@ namespace golos { namespace chain { } } + void break_free_referral_evaluator::do_apply(const break_free_referral_operation& op) { + ASSERT_REQ_HF(STEEMIT_HARDFORK_0_19__295, "break_free_referral_operation"); + + const auto& referral = _db.get_account(op.referral); + const auto& referrer = _db.get_account(referral.referrer_account); + + GOLOS_CHECK_LOGIC(referral.referral_break_fee.amount != 0, + logic_exception::no_right_to_break_referral, + "This referral account has no right to break referral"); + + GOLOS_CHECK_BALANCE(referral, MAIN_BALANCE, referral.referral_break_fee); + _db.adjust_balance(referral, -referral.referral_break_fee); + _db.adjust_balance(referrer, referral.referral_break_fee); + + _db.modify(referral, [&](account_object& a) { + a.referrer_account = account_name_type(); + a.referrer_interest_rate = 0; + a.referral_end_date = time_point_sec::min(); + a.referral_break_fee.amount = 0; + }); + } + } } // golos::chain diff --git a/libraries/protocol/include/golos/protocol/config.hpp b/libraries/protocol/include/golos/protocol/config.hpp index 607ad26178..76023e092c 100644 --- a/libraries/protocol/include/golos/protocol/config.hpp +++ b/libraries/protocol/include/golos/protocol/config.hpp @@ -3,7 +3,7 @@ */ #pragma once -#define STEEMIT_BLOCKCHAIN_VERSION (version(0, 18, 4)) +#define STEEMIT_BLOCKCHAIN_VERSION (version(0, 19, 0)) #define STEEMIT_BLOCKCHAIN_HARDFORK_VERSION (hardfork_version(STEEMIT_BLOCKCHAIN_VERSION)) #ifdef STEEMIT_BUILD_TESTNET @@ -71,6 +71,7 @@ #define STEEMIT_MAX_VOTE_CHANGES 5 #define STEEMIT_UPVOTE_LOCKOUT (fc::minutes(1)) #define STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS (60*30) /// 30 minutes +#define STEEMIT_MAX_AUCTION_WINDOW_SIZE_SECONDS (24 * 60 * 60) /// 24 hours #define STEEMIT_MIN_VOTE_INTERVAL_SEC 3 #define STEEMIT_MAX_COMMENT_BENEFICIARIES 8 @@ -112,8 +113,15 @@ #define GOLOS_CREATE_ACCOUNT_DELEGATION_TIME (fc::days(1)) #define GOLOS_MIN_DELEGATION_MULTIPLIER 10 -#define STEEMIT_MINING_REWARD asset( 666, STEEM_SYMBOL ) -#define STEEMIT_MINING_REWARD_PRE_HF_16 asset( 1000, STEEM_SYMBOL ) +#define GOLOS_DEFAULT_REFERRAL_INTEREST_RATE (10*STEEMIT_1_PERCENT) // 10% +#define GOLOS_MAX_REFERRAL_INTEREST_RATE STEEMIT_100_PERCENT +#define GOLOS_DEFAULT_REFERRAL_TERM_SEC (60*60*24*30*6) +#define GOLOS_MAX_REFERRAL_TERM_SEC (60*60*24*30*12) +#define GOLOS_DEFAULT_REFERRAL_BREAK_FEE asset(100, STEEM_SYMBOL) +#define GOLOS_MAX_REFERRAL_BREAK_FEE asset(100, STEEM_SYMBOL) + +#define STEEMIT_MINING_REWARD asset(666, STEEM_SYMBOL) +#define STEEMIT_MINING_REWARD_PRE_HF_16 asset(1000, STEEM_SYMBOL) #define STEEMIT_EQUIHASH_N 140 #define STEEMIT_EQUIHASH_K 6 @@ -121,16 +129,16 @@ #define STEEMIT_MIN_LIQUIDITY_REWARD_PERIOD_SEC (fc::seconds(60)) // 1 minute required on books to receive volume #define STEEMIT_LIQUIDITY_REWARD_PERIOD_SEC (60*60) #define STEEMIT_LIQUIDITY_REWARD_BLOCKS (STEEMIT_LIQUIDITY_REWARD_PERIOD_SEC/STEEMIT_BLOCK_INTERVAL) -#define STEEMIT_MIN_LIQUIDITY_REWARD (asset( 1000*STEEMIT_LIQUIDITY_REWARD_BLOCKS, STEEM_SYMBOL )) // Minumum reward to be paid out to liquidity providers -#define STEEMIT_MIN_CONTENT_REWARD asset( 1500, STEEM_SYMBOL ) -#define STEEMIT_MIN_CURATE_REWARD asset( 500, STEEM_SYMBOL ) +#define STEEMIT_MIN_LIQUIDITY_REWARD (asset(1000*STEEMIT_LIQUIDITY_REWARD_BLOCKS, STEEM_SYMBOL)) // Minumum reward to be paid out to liquidity providers +#define STEEMIT_MIN_CONTENT_REWARD asset(1500, STEEM_SYMBOL) +#define STEEMIT_MIN_CURATE_REWARD asset(500, STEEM_SYMBOL) #define STEEMIT_MIN_PRODUCER_REWARD STEEMIT_MINING_REWARD #define STEEMIT_MIN_PRODUCER_REWARD_PRE_HF_16 STEEMIT_MINING_REWARD_PRE_HF_16 #define STEEMIT_MIN_POW_REWARD STEEMIT_MINING_REWARD #define STEEMIT_MIN_POW_REWARD_PRE_HF_16 STEEMIT_MINING_REWARD_PRE_HF_16 -#define STEEMIT_ACTIVE_CHALLENGE_FEE asset( 2000, STEEM_SYMBOL ) -#define STEEMIT_OWNER_CHALLENGE_FEE asset( 30000, STEEM_SYMBOL ) +#define STEEMIT_ACTIVE_CHALLENGE_FEE asset(2000, STEEM_SYMBOL) +#define STEEMIT_OWNER_CHALLENGE_FEE asset(30000, STEEM_SYMBOL) #define STEEMIT_ACTIVE_CHALLENGE_COOLDOWN fc::days(1) #define STEEMIT_OWNER_CHALLENGE_COOLDOWN fc::days(1) @@ -169,7 +177,7 @@ #define STEEMIT_PRODUCER_APR_PERCENT 750 #define STEEMIT_POW_APR_PERCENT 750 -#define STEEMIT_MIN_PAYOUT_SBD (asset(20,SBD_SYMBOL)) +#define STEEMIT_MIN_PAYOUT_SBD (asset(20, SBD_SYMBOL)) #define STEEMIT_SBD_STOP_PERCENT (5*STEEMIT_1_PERCENT ) // Stop printing SBD at 5% Market Cap #define STEEMIT_SBD_START_PERCENT (2*STEEMIT_1_PERCENT) // Start reducing printing of SBD at 2% Market Cap @@ -205,7 +213,7 @@ #define STEEMIT_MAX_UNDO_HISTORY 10000 #define STEEMIT_MIN_TRANSACTION_EXPIRATION_LIMIT (STEEMIT_BLOCK_INTERVAL * 5) // 5 transactions per block -#define STEEMIT_BLOCKCHAIN_PRECISION uint64_t( 1000 ) +#define STEEMIT_BLOCKCHAIN_PRECISION uint64_t(1000) #define STEEMIT_BLOCKCHAIN_PRECISION_DIGITS 3 #define STEEMIT_MAX_INSTANCE_ID (uint64_t(-1)>>16) @@ -279,6 +287,7 @@ #define STEEMIT_MAX_VOTE_CHANGES 5 #define STEEMIT_UPVOTE_LOCKOUT (fc::minutes(1)) #define STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS (60*30) /// 30 minutes +#define STEEMIT_MAX_AUCTION_WINDOW_SIZE_SECONDS (24 * 60 * 60) /// 24 hours #define STEEMIT_MIN_VOTE_INTERVAL_SEC 3 #define STEEMIT_MAX_COMMENT_BENEFICIARIES 64 @@ -321,8 +330,15 @@ #define GOLOS_CREATE_ACCOUNT_DELEGATION_TIME (fc::days(30)) #define GOLOS_MIN_DELEGATION_MULTIPLIER 10 -#define STEEMIT_MINING_REWARD asset( 666, STEEM_SYMBOL ) -#define STEEMIT_MINING_REWARD_PRE_HF_16 asset( 1000, STEEM_SYMBOL ) +#define GOLOS_DEFAULT_REFERRAL_INTEREST_RATE (10*STEEMIT_1_PERCENT) // 10% +#define GOLOS_MAX_REFERRAL_INTEREST_RATE STEEMIT_100_PERCENT +#define GOLOS_DEFAULT_REFERRAL_TERM_SEC (60*60*24*30*6) +#define GOLOS_MAX_REFERRAL_TERM_SEC (60*60*24*30*12) +#define GOLOS_DEFAULT_REFERRAL_BREAK_FEE asset(100, STEEM_SYMBOL) +#define GOLOS_MAX_REFERRAL_BREAK_FEE asset(100, STEEM_SYMBOL) + +#define STEEMIT_MINING_REWARD asset(666, STEEM_SYMBOL) +#define STEEMIT_MINING_REWARD_PRE_HF_16 asset(1000, STEEM_SYMBOL) #define STEEMIT_EQUIHASH_N 140 #define STEEMIT_EQUIHASH_K 6 @@ -330,16 +346,16 @@ #define STEEMIT_MIN_LIQUIDITY_REWARD_PERIOD_SEC (fc::seconds(60)) // 1 minute required on books to receive volume #define STEEMIT_LIQUIDITY_REWARD_PERIOD_SEC (60*60) #define STEEMIT_LIQUIDITY_REWARD_BLOCKS (STEEMIT_LIQUIDITY_REWARD_PERIOD_SEC/STEEMIT_BLOCK_INTERVAL) -#define STEEMIT_MIN_LIQUIDITY_REWARD (asset( 1000*STEEMIT_LIQUIDITY_REWARD_BLOCKS, STEEM_SYMBOL )) // Minumum reward to be paid out to liquidity providers -#define STEEMIT_MIN_CONTENT_REWARD asset( 1500, STEEM_SYMBOL ) -#define STEEMIT_MIN_CURATE_REWARD asset( 500, STEEM_SYMBOL ) +#define STEEMIT_MIN_LIQUIDITY_REWARD (asset(1000*STEEMIT_LIQUIDITY_REWARD_BLOCKS, STEEM_SYMBOL)) // Minumum reward to be paid out to liquidity providers +#define STEEMIT_MIN_CONTENT_REWARD asset(1500, STEEM_SYMBOL) +#define STEEMIT_MIN_CURATE_REWARD asset(500, STEEM_SYMBOL) #define STEEMIT_MIN_PRODUCER_REWARD STEEMIT_MINING_REWARD #define STEEMIT_MIN_PRODUCER_REWARD_PRE_HF_16 STEEMIT_MINING_REWARD_PRE_HF_16 #define STEEMIT_MIN_POW_REWARD STEEMIT_MINING_REWARD #define STEEMIT_MIN_POW_REWARD_PRE_HF_16 STEEMIT_MINING_REWARD_PRE_HF_16 -#define STEEMIT_ACTIVE_CHALLENGE_FEE asset( 2000, STEEM_SYMBOL ) -#define STEEMIT_OWNER_CHALLENGE_FEE asset( 30000, STEEM_SYMBOL ) +#define STEEMIT_ACTIVE_CHALLENGE_FEE asset(2000, STEEM_SYMBOL) +#define STEEMIT_OWNER_CHALLENGE_FEE asset(30000, STEEM_SYMBOL) #define STEEMIT_ACTIVE_CHALLENGE_COOLDOWN fc::days(1) #define STEEMIT_OWNER_CHALLENGE_COOLDOWN fc::days(1) @@ -414,7 +430,7 @@ #define STEEMIT_MAX_UNDO_HISTORY 10000 #define STEEMIT_MIN_TRANSACTION_EXPIRATION_LIMIT (STEEMIT_BLOCK_INTERVAL * 5) // 5 transactions per block -#define STEEMIT_BLOCKCHAIN_PRECISION uint64_t( 1000 ) +#define STEEMIT_BLOCKCHAIN_PRECISION uint64_t(1000) #define STEEMIT_BLOCKCHAIN_PRECISION_DIGITS 3 #define STEEMIT_MAX_INSTANCE_ID (uint64_t(-1)>>16) diff --git a/libraries/protocol/include/golos/protocol/exceptions.hpp b/libraries/protocol/include/golos/protocol/exceptions.hpp index 15514068ff..e8e5b6d75d 100644 --- a/libraries/protocol/include/golos/protocol/exceptions.hpp +++ b/libraries/protocol/include/golos/protocol/exceptions.hpp @@ -229,6 +229,7 @@ namespace golos { comment_cannot_accept_greater_percent_GBG, cannot_specify_more_beneficiaries, comment_already_has_beneficiaries, + beneficiaries_should_be_unique, comment_must_not_have_been_voted, // withdraw_vesting @@ -238,7 +239,7 @@ namespace golos { reached_maxumum_number_of_routes, more_100percent_allocated_to_destinations, - //account_create_with_delegation + // account_create_with_delegation not_enough_delegation, // challenge_authority_operation @@ -273,16 +274,19 @@ namespace golos { authority_does_not_match_request, no_recent_authority_in_history, - //set_reset_account_operation + // set_reset_account_operation cannot_set_same_reset_account, - //delegate_vesting_shares + // break_free_referral_operation + no_right_to_break_referral, + + // delegate_vesting_shares cannot_delegate_to_yourself, delegation_difference_too_low, delegation_limited_by_voting_power, cannot_delegate_below_minimum, - //proposals and transactions + // proposals and transactions proposal_depth_too_high, tx_with_both_posting_active_ops, @@ -489,6 +493,7 @@ FC_REFLECT_ENUM(golos::logic_exception::error_types, (comment_cannot_accept_greater_percent_GBG) (cannot_specify_more_beneficiaries) (comment_already_has_beneficiaries) + (beneficiaries_should_be_unique) (comment_must_not_have_been_voted) // withdraw_vesting @@ -498,12 +503,12 @@ FC_REFLECT_ENUM(golos::logic_exception::error_types, (reached_maxumum_number_of_routes) (more_100percent_allocated_to_destinations) - //challenge_authority_operation + // challenge_authority_operation (cannot_challenge_yourself) //prove_authority_evaluator (account_is_not_challeneged) - //escrow + // escrow (escrow_no_amount_set) (escrow_wrong_time_limits) (escrow_time_in_past) @@ -530,19 +535,22 @@ FC_REFLECT_ENUM(golos::logic_exception::error_types, (authority_does_not_match_request) (no_recent_authority_in_history) - //set_reset_account_operation + // set_reset_account_operation (cannot_set_same_reset_account) - //delegate_vesting_shares + // break_free_referral_operation + (no_right_to_break_referral) + + // delegate_vesting_shares (cannot_delegate_to_yourself) (delegation_difference_too_low) (delegation_limited_by_voting_power) (cannot_delegate_below_minimum) - //account_create_with_delegation + // account_create_with_delegation (not_enough_delegation) - //proposals + // proposals (proposal_depth_too_high) (tx_with_both_posting_active_ops) diff --git a/libraries/protocol/include/golos/protocol/operations.hpp b/libraries/protocol/include/golos/protocol/operations.hpp index 0bed9b25df..2580ab951a 100644 --- a/libraries/protocol/include/golos/protocol/operations.hpp +++ b/libraries/protocol/include/golos/protocol/operations.hpp @@ -66,6 +66,7 @@ namespace golos { namespace protocol { proposal_update_operation, proposal_delete_operation, chain_properties_update_operation, + break_free_referral_operation, /// virtual operations below this point fill_convert_request_operation, diff --git a/libraries/protocol/include/golos/protocol/steem_operations.hpp b/libraries/protocol/include/golos/protocol/steem_operations.hpp index 0a131cc642..afea4831f4 100644 --- a/libraries/protocol/include/golos/protocol/steem_operations.hpp +++ b/libraries/protocol/include/golos/protocol/steem_operations.hpp @@ -25,6 +25,21 @@ namespace golos { namespace protocol { } }; + struct account_referral_options { + account_name_type referrer; + uint16_t interest_rate; + time_point_sec end_date; + asset break_fee; + + void validate() const; + }; + + using account_create_with_delegation_extension = static_variant< + account_referral_options + >; + + using account_create_with_delegation_extensions_type = flat_set; + struct account_create_with_delegation_operation: public base_operation { asset fee; asset delegation; @@ -36,7 +51,7 @@ namespace golos { namespace protocol { public_key_type memo_key; string json_metadata; - extensions_type extensions; + account_create_with_delegation_extensions_type extensions; void validate() const; void get_required_active_authorities(flat_set& a) const { @@ -115,7 +130,7 @@ namespace golos { namespace protocol { }; struct comment_payout_beneficiaries { - vector beneficiaries; + vector beneficiaries; void validate() const; }; @@ -419,6 +434,7 @@ namespace golos { namespace protocol { }; struct chain_properties_18; + struct chain_properties_19; /** * Witnesses must vote on how to set certain chain properties to ensure a smooth @@ -496,6 +512,47 @@ namespace golos { namespace protocol { chain_properties_18& operator=(const chain_properties_18&) = default; }; + /** + * Users can invite referrals, and they will pay some percent of rewards to their referrers. + * Referral can break paying for some fee. + */ + struct chain_properties_19: public chain_properties_18 { + + /** + * Auction window size + */ + uint32_t auction_window_size = STEEMIT_REVERSE_AUCTION_WINDOW_SECONDS; + + /** + * Maximum percent of referral deductions + */ + uint16_t max_referral_interest_rate = GOLOS_DEFAULT_REFERRAL_INTEREST_RATE; + + /** + * Maximum term of referral deductions + */ + uint32_t max_referral_term_sec = GOLOS_DEFAULT_REFERRAL_TERM_SEC; + + /** + * Fee for breaking referral deductions by referral + */ + asset max_referral_break_fee = GOLOS_DEFAULT_REFERRAL_BREAK_FEE; + + void validate() const; + + chain_properties_19& operator=(const chain_properties_17& src) { + chain_properties_18::operator=(src); + return *this; + } + + chain_properties_19& operator=(const chain_properties_18& src) { + chain_properties_18::operator=(src); + return *this; + } + + chain_properties_19& operator=(const chain_properties_19&) = default; + }; + inline chain_properties_17& chain_properties_17::operator=(const chain_properties_18& src) { account_creation_fee = src.account_creation_fee; maximum_block_size = src.maximum_block_size; @@ -505,7 +562,8 @@ namespace golos { namespace protocol { using versioned_chain_properties = fc::static_variant< chain_properties_17, - chain_properties_18 + chain_properties_18, + chain_properties_19 >; /** @@ -1118,6 +1176,18 @@ namespace golos { namespace protocol { a.insert(delegator); } }; + + class break_free_referral_operation : public base_operation { + public: + account_name_type referral; + + extensions_type extensions; ///< Extensions. Not currently used. + + void validate() const; + void get_required_active_authorities(flat_set& a) const { + a.insert(referral); + } + }; } } // golos::protocol @@ -1141,9 +1211,12 @@ FC_REFLECT( (golos::protocol::chain_properties_17), (account_creation_fee)(maximum_block_size)(sbd_interest_rate)) FC_REFLECT_DERIVED( - (golos::protocol::chain_properties_18),((golos::protocol::chain_properties_17)), + (golos::protocol::chain_properties_18), ((golos::protocol::chain_properties_17)), (create_account_min_golos_fee)(create_account_min_delegation) (create_account_delegation_time)(min_delegation)) +FC_REFLECT_DERIVED( + (golos::protocol::chain_properties_19), ((golos::protocol::chain_properties_18)), + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)(auction_window_size)) FC_REFLECT_TYPENAME((golos::protocol::versioned_chain_properties)) @@ -1161,6 +1234,8 @@ FC_REFLECT((golos::protocol::account_create_operation), (memo_key) (json_metadata)) +FC_REFLECT((golos::protocol::account_referral_options), (referrer)(interest_rate)(end_date)(break_fee)) +FC_REFLECT_TYPENAME((golos::protocol::account_create_with_delegation_extension)); FC_REFLECT((golos::protocol::account_create_with_delegation_operation), (fee)(delegation)(creator)(new_account_name)(owner)(active)(posting)(memo_key)(json_metadata)(extensions)); @@ -1209,3 +1284,4 @@ FC_REFLECT((golos::protocol::change_recovery_account_operation), (account_to_rec FC_REFLECT((golos::protocol::decline_voting_rights_operation), (account)(decline)); FC_REFLECT((golos::protocol::delegate_vesting_shares_operation), (delegator)(delegatee)(vesting_shares)); FC_REFLECT((golos::protocol::chain_properties_update_operation), (owner)(props)); +FC_REFLECT((golos::protocol::break_free_referral_operation), (referral)(extensions)); diff --git a/libraries/protocol/include/golos/protocol/validate_helper.hpp b/libraries/protocol/include/golos/protocol/validate_helper.hpp index 0a0898c07b..73ed6e4165 100644 --- a/libraries/protocol/include/golos/protocol/validate_helper.hpp +++ b/libraries/protocol/include/golos/protocol/validate_helper.hpp @@ -39,7 +39,7 @@ #define GOLOS_CHECK_VALUE_GE(F, X) GOLOS_CHECK_VALUE_I(F, >=, X) #define GOLOS_CHECK_VALUE_LE(F, X) GOLOS_CHECK_VALUE_I(F, <=, X) #define GOLOS_CHECK_VALUE_LEGE(F, L, H) \ - GOLOS_CHECK_VALUE((L) <= F && F <= (H) , MUST_BE(F, "between " FC_STRINGIZE(L) " and" FC_STRINGIZE(H))) + GOLOS_CHECK_VALUE((L) <= F && F <= (H), MUST_BE(F, "between " FC_STRINGIZE(L) " and " FC_STRINGIZE(H))) // check asset type #define GOLOS_CHECK_ASSET_TYPE(X, NAME) GOLOS_CHECK_ASSET_##NAME(X); diff --git a/libraries/protocol/steem_operations.cpp b/libraries/protocol/steem_operations.cpp index f82f18fcdc..87bceb6d20 100644 --- a/libraries/protocol/steem_operations.cpp +++ b/libraries/protocol/steem_operations.cpp @@ -39,6 +39,25 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM(json_metadata, validate_account_json_metadata(json_metadata)); } + struct account_create_with_delegation_extension_validate_visitor { + account_create_with_delegation_extension_validate_visitor() { + } + + using result_type = void; + + void operator()(const account_referral_options& aro) const { + aro.validate(); + } + }; + + void account_referral_options::validate() const { + validate_account_name(referrer); + GOLOS_CHECK_PARAM(break_fee, { + GOLOS_CHECK_VALUE(break_fee.symbol == STEEM_SYMBOL, "Break fee in GOLOS only is allowed."); + GOLOS_CHECK_VALUE(break_fee.amount >= 0, "Negative break fee is not allowed."); + }); + } + void account_create_with_delegation_operation::validate() const { GOLOS_CHECK_PARAM_ACCOUNT(new_account_name); GOLOS_CHECK_PARAM_ACCOUNT(creator); @@ -48,6 +67,10 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM_VALIDATE(active); GOLOS_CHECK_PARAM_VALIDATE(posting); GOLOS_CHECK_PARAM(json_metadata, validate_account_json_metadata(json_metadata)); + + for (auto& e : extensions) { + e.visit(account_create_with_delegation_extension_validate_visitor()); + } } void account_update_operation::validate() const { @@ -112,21 +135,22 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM(beneficiaries, { GOLOS_CHECK_VALUE(beneficiaries.size(), "Must specify at least one beneficiary"); GOLOS_CHECK_VALUE(beneficiaries.size() < 128, - "Cannot specify more than 127 beneficiaries."); // Require size serializtion fits in one byte. - - for (auto beneficiar: beneficiaries) { - validate_account_name(beneficiaries[0].account); - GOLOS_CHECK_VALUE(beneficiar.weight <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to one account"); - sum += beneficiar.weight; - GOLOS_CHECK_VALUE(sum <= STEEMIT_100_PERCENT, - "Cannot allocate more than 100% of rewards to a comment"); + "Cannot specify more than 127 beneficiaries."); // Require size serialization fits in one byte. + + for (auto& beneficiary : beneficiaries) { + validate_account_name(beneficiary.account); + GOLOS_CHECK_VALUE(beneficiary.weight <= STEEMIT_100_PERCENT, + "Cannot allocate more than 100% of rewards to one account"); + sum += beneficiary.weight; } + GOLOS_CHECK_VALUE(sum <= STEEMIT_100_PERCENT, + "Cannot allocate more than 100% of rewards to a comment"); + for (size_t i = 1; i < beneficiaries.size(); i++) { GOLOS_CHECK_VALUE(beneficiaries[i - 1] < beneficiaries[i], - "Benficiaries ${first} and ${second} not in sorted order (account ascending)", - ("first", beneficiaries[i-1].account)("second", beneficiaries[i].account)); + "Benficiaries ${first} and ${second} not in sorted order (account ascending)", + ("first", beneficiaries[i-1].account)("second", beneficiaries[i].account)); } }); } @@ -136,7 +160,7 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM(percent_steem_dollars, GOLOS_CHECK_VALUE_LE(percent_steem_dollars, STEEMIT_100_PERCENT)); GOLOS_CHECK_PARAM(max_accepted_payout, GOLOS_CHECK_ASSET_GE0(max_accepted_payout, GBG)); GOLOS_CHECK_PARAM(permlink, validate_permlink(permlink)); - for (auto &e : extensions) { + for (auto& e : extensions) { e.visit(comment_options_extension_validate_visitor()); } } @@ -226,6 +250,14 @@ namespace golos { namespace protocol { (GOLOS_CREATE_ACCOUNT_DELEGATION_TIME).to_seconds() / 2); } + void chain_properties_19::validate() const { + chain_properties_18::validate(); + GOLOS_CHECK_VALUE_LEGE(auction_window_size, 0, STEEMIT_MAX_AUCTION_WINDOW_SIZE_SECONDS); + GOLOS_CHECK_VALUE_LE(max_referral_interest_rate, GOLOS_MAX_REFERRAL_INTEREST_RATE); + GOLOS_CHECK_VALUE_LE(max_referral_term_sec, GOLOS_MAX_REFERRAL_TERM_SEC); + GOLOS_CHECK_VALUE_LEGE(max_referral_break_fee.amount, 0, GOLOS_MAX_REFERRAL_BREAK_FEE.amount); + } + void witness_update_operation::validate() const { GOLOS_CHECK_PARAM_ACCOUNT(owner); GOLOS_CHECK_PARAM(url, { @@ -622,4 +654,8 @@ namespace golos { namespace protocol { GOLOS_CHECK_PARAM(vesting_shares, GOLOS_CHECK_ASSET_GE0(vesting_shares, GESTS)); } + void break_free_referral_operation::validate() const { + GOLOS_CHECK_PARAM_ACCOUNT(referral); + } + } } // golos::protocol diff --git a/libraries/wallet/include/golos/wallet/wallet.hpp b/libraries/wallet/include/golos/wallet/wallet.hpp index 0a93512ca3..4168f84317 100644 --- a/libraries/wallet/include/golos/wallet/wallet.hpp +++ b/libraries/wallet/include/golos/wallet/wallet.hpp @@ -44,6 +44,12 @@ namespace golos { namespace wallet { fc::optional create_account_min_delegation; fc::optional create_account_delegation_time; fc::optional min_delegation; + + fc::optional max_referral_interest_rate; + fc::optional max_referral_term_sec; + fc::optional max_referral_break_fee; + fc::optional auction_window_size; + }; struct optional_private_box_query { @@ -531,10 +537,10 @@ namespace golos { namespace wallet { public_key_type active, public_key_type posting, public_key_type memo, - bool broadcast )const; + bool broadcast) const; /** - * This method will genrate new owner, active, posting and memo keys for the new account which + * This method will generate new owner, active, posting and memo keys for the new account which * will be controlable by this wallet. There is a fee associated with account creation * that is paid by the creator. The current account creation fee can be found with the * 'info' wallet command. @@ -582,6 +588,33 @@ namespace golos { namespace wallet { public_key_type memo, bool broadcast) const; + /** + * This method will generate new owner, active, posting and memo keys for the new account which + * will be controlable by this wallet. There is a fee associated with account creation + * that is paid by the creator. The current account creation fee can be found with the + * 'info' wallet command. + * + * These accounts are created with combination of GOLOS and delegated GP, and with the referral duty. + * + * @param creator The account creating the new account + * @param steem_fee The amount of the fee to be paid with GOLOS + * @param delegated_vests The amount of the fee to be paid with delegation + * @param new_account_name The name of the new account + * @param json_meta JSON Metadata associated with the new account + * @param referral_options Options of the new account as the referral + * @param broadcast true if you wish to broadcast the transaction + */ + annotated_signed_transaction create_account_referral( + string creator, asset steem_fee, asset delegated_vests, string new_account_name, string json_meta, + account_referral_options referral_options, bool broadcast); + + /** + * This method pays the break fee to remove the referral duty from an account. + * + * @param referral The name of the referral account + */ + annotated_signed_transaction break_free_referral(string referral, bool broadcast); + /** * This method updates the keys of an existing account. * @@ -1392,12 +1425,14 @@ FC_API( golos::wallet::wallet_api, (create_account_with_keys) (create_account_delegated) (create_account_with_keys_delegated) + (create_account_referral) (update_account) (update_account_auth_key) (update_account_auth_account) (update_account_auth_threshold) (update_account_meta) (update_account_memo_key) + (break_free_referral) (delegate_vesting_shares) (update_witness) (update_chain_properties) @@ -1488,7 +1523,8 @@ FC_REFLECT( FC_REFLECT((golos::wallet::optional_chain_props), (account_creation_fee)(maximum_block_size)(sbd_interest_rate) (create_account_min_golos_fee)(create_account_min_delegation) - (create_account_delegation_time)(min_delegation)) + (create_account_delegation_time)(min_delegation) + (max_referral_interest_rate)(max_referral_term_sec)(max_referral_break_fee)(auction_window_size)) FC_REFLECT( (golos::wallet::message_body), diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 631c810f06..accf711612 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -326,6 +326,12 @@ namespace golos { namespace wallet { result["create_account_delegation_time"] = median_props.create_account_delegation_time; result["min_delegation"] = median_props.min_delegation; } + if (hf >= hardfork_version(0, STEEMIT_HARDFORK_0_19)) { + result["auction_window_size"] = median_props.auction_window_size; + result["max_referral_interest_rate"] = median_props.max_referral_interest_rate; + result["max_referral_term_sec"] = median_props.max_referral_term_sec; + result["max_referral_break_fee"] = median_props.max_referral_break_fee; + } return result; } @@ -1694,10 +1700,10 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } -/** - * This method will generate new owner, active, posting and memo keys for the new account - * which will be controlable by this wallet. - */ + /** + * This method will generate new owner, active, posting and memo keys for the new account + * which will be controlable by this wallet. + */ annotated_signed_transaction wallet_api::create_account_delegated( string creator, asset steem_fee, asset delegated_vests, string new_account_name, string json_meta, bool broadcast @@ -1718,11 +1724,12 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st } FC_CAPTURE_AND_RETHROW((creator)(new_account_name)(json_meta)); } -/** - * This method is used by faucets to create new accounts for other users which must - * provide their desired keys. The resulting account may not be controllable by this - * wallet. - */ + + /** + * This method is used by faucets to create new accounts for other users which must + * provide their desired keys. The resulting account may not be controllable by this + * wallet. + */ annotated_signed_transaction wallet_api::create_account_with_keys_delegated( string creator, asset steem_fee, @@ -1756,6 +1763,64 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st FC_CAPTURE_AND_RETHROW((creator)(new_account_name)(json_meta)(owner)(active)(posting)(memo)(broadcast)); } + /** + * This method will generate new owner, active, posting and memo keys for the new account + * which will be controlable by this wallet. Also it will add the referral duty to the new account. + */ + annotated_signed_transaction wallet_api::create_account_referral( + string creator, asset steem_fee, asset delegated_vests, string new_account_name, + string json_meta, account_referral_options referral_options, bool broadcast + ) { + try { + WALLET_CHECK_UNLOCKED(); + auto owner = suggest_brain_key(); + auto active = suggest_brain_key(); + auto posting = suggest_brain_key(); + auto memo = suggest_brain_key(); + import_key(owner.wif_priv_key); + import_key(active.wif_priv_key); + import_key(posting.wif_priv_key); + import_key(memo.wif_priv_key); + + account_create_with_delegation_operation op; + op.creator = creator; + op.new_account_name = new_account_name; + op.owner = authority(1, owner.pub_key, 1); + op.active = authority(1, active.pub_key, 1); + op.posting = authority(1, posting.pub_key, 1); + op.memo_key = memo.pub_key; + op.json_metadata = json_meta; + op.fee = steem_fee; + op.delegation = delegated_vests; + + op.extensions.insert(referral_options); + + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + FC_CAPTURE_AND_RETHROW((creator)(new_account_name)(json_meta)); + } + + /** + * This method pays the break fee to remove the referral duty from an account. + */ + annotated_signed_transaction wallet_api::break_free_referral(string referral, bool broadcast) { + try { + WALLET_CHECK_UNLOCKED(); + + break_free_referral_operation op; + op.referral = referral; + + signed_transaction tx; + tx.operations.push_back(op); + tx.validate(); + return my->sign_transaction(tx, broadcast); + } + FC_CAPTURE_AND_RETHROW((referral)); + } + /** * This method is used by faucets to create new accounts for other users which must * provide their desired keys. The resulting account may not be controllable by this @@ -2164,7 +2229,7 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st signed_transaction tx; chain_properties_update_operation op; chain_api_properties ap; - chain_properties p; + chain_properties_18 p; // copy defaults in case of missing witness object ap.account_creation_fee = p.account_creation_fee; @@ -2176,20 +2241,31 @@ fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_st FC_ASSERT(wit->owner == witness_account_name); ap = wit->props; } -#define SET_PROP(X) {p.X = !!props.X ? *(props.X) : ap.X;} - SET_PROP(account_creation_fee); - SET_PROP(maximum_block_size); - SET_PROP(sbd_interest_rate); +#define SET_PROP(cp, X) {cp.X = !!props.X ? *(props.X) : ap.X;} + SET_PROP(p, account_creation_fee); + SET_PROP(p, maximum_block_size); + SET_PROP(p, sbd_interest_rate); #undef SET_PROP -#define SET_PROP(X) {if (!!props.X) p.X = *(props.X); else if (!!ap.X) p.X = *(ap.X);} - SET_PROP(create_account_min_golos_fee); - SET_PROP(create_account_min_delegation); - SET_PROP(create_account_delegation_time); - SET_PROP(min_delegation); +#define SET_PROP(cp, X) {if (!!props.X) cp.X = *(props.X); else if (!!ap.X) cp.X = *(ap.X);} + SET_PROP(p, create_account_min_golos_fee); + SET_PROP(p, create_account_min_delegation); + SET_PROP(p, create_account_delegation_time); + SET_PROP(p, min_delegation); + op.props = p; + auto hf = my->_remote_database_api->get_hardfork_version(); + if (hf >= hardfork_version(0, STEEMIT_HARDFORK_0_19) || !!props.max_referral_interest_rate + || !!props.max_referral_term_sec || !!props.max_referral_break_fee) { + chain_properties_19 p19; + p19 = p; + SET_PROP(p19, max_referral_interest_rate); + SET_PROP(p19, max_referral_term_sec); + SET_PROP(p19, max_referral_break_fee); + SET_PROP(p19, auction_window_size); + op.props = p19; + } #undef SET_PROP op.owner = witness_account_name; - op.props = p; tx.operations.push_back(op); tx.validate(); diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp index c24d9c5af2..c64d400052 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_operations.hpp @@ -59,6 +59,7 @@ namespace mongo_db { result_type operator()(const delegate_vesting_shares_operation& op); result_type operator()(const account_create_with_delegation_operation& op); result_type operator()(const account_metadata_operation& op); + result_type operator()(const break_free_referral_operation& op); result_type operator()(const proposal_create_operation& op); result_type operator()(const proposal_update_operation& op); result_type operator()(const proposal_delete_operation& op); diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp index 987814cd1b..e71a0ab637 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_state.hpp @@ -34,6 +34,7 @@ namespace mongo_db { result_type operator()(const account_create_operation& op); result_type operator()(const account_create_with_delegation_operation& op); result_type operator()(const account_metadata_operation& op); + result_type operator()(const break_free_referral_operation& op); result_type operator()(const account_update_operation& op); result_type operator()(const witness_update_operation& op); result_type operator()(const account_witness_vote_operation& op); diff --git a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_writer.hpp b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_writer.hpp index 06e1046d93..b4025d12cb 100644 --- a/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_writer.hpp +++ b/plugins/mongo_db/include/golos/plugins/mongo_db/mongo_db_writer.hpp @@ -91,7 +91,7 @@ namespace mongo_db { std::unordered_map indexes; // Prevent repeative create_index() calls. Only in current session - golos::chain::database &_db; + golos::chain::database& _db; }; }}} diff --git a/plugins/mongo_db/mongo_db_operations.cpp b/plugins/mongo_db/mongo_db_operations.cpp index f65ace3285..3892b07008 100644 --- a/plugins/mongo_db/mongo_db_operations.cpp +++ b/plugins/mongo_db/mongo_db_operations.cpp @@ -549,37 +549,51 @@ namespace mongo_db { } // + auto operation_writer::operator()(const delegate_vesting_shares_operation& op) -> result_type { result_type body; return body; } + auto operation_writer::operator()(const account_create_with_delegation_operation& op) -> result_type { result_type body; return body; } + auto operation_writer::operator()(const account_metadata_operation& op) -> result_type { result_type body; return body; } + + auto operation_writer::operator()(const break_free_referral_operation& op) -> result_type { + result_type body; + + return body; + } + auto operation_writer::operator()(const proposal_create_operation& op) -> result_type { result_type body; return body; } + auto operation_writer::operator()(const proposal_update_operation& op) -> result_type { result_type body; return body; } + auto operation_writer::operator()(const proposal_delete_operation& op) -> result_type { result_type body; return body; } + // + auto operation_writer::operator()(const fill_convert_request_operation& op) -> result_type { result_type body; diff --git a/plugins/mongo_db/mongo_db_state.cpp b/plugins/mongo_db/mongo_db_state.cpp index d6312acb56..9e24cd78fe 100644 --- a/plugins/mongo_db/mongo_db_state.cpp +++ b/plugins/mongo_db/mongo_db_state.cpp @@ -272,6 +272,11 @@ namespace mongo_db { format_value(body, "last_post", account.last_post); + format_value(body, "referrer_account", account.referrer_account); + format_value(body, "referrer_interest_rate", account.referrer_interest_rate); + format_value(body, "referral_end_date", account.referral_end_date); + format_value(body, "referral_break_fee", account.referral_break_fee); + auto account_metadata = db_.find(account.name); if (account_metadata != nullptr) { format_json(body, "json_metadata", account_metadata->json_metadata); @@ -500,7 +505,7 @@ namespace mongo_db { } } - void state_writer::format_escrow(const escrow_object &escrow) { + void state_writer::format_escrow(const escrow_object& escrow) { try { auto oid = std::string(escrow.from).append("/").append(std::to_string(escrow.escrow_id)); auto oid_hash = hash_oid(oid); @@ -549,7 +554,7 @@ namespace mongo_db { } } - void state_writer::format_escrow(const account_name_type &name, uint32_t escrow_id) { + void state_writer::format_escrow(const account_name_type& name, uint32_t escrow_id) { try { auto& escrow = db_.get_escrow(name, escrow_id); format_escrow(escrow); @@ -705,8 +710,8 @@ namespace mongo_db { void state_writer::format_liquidity_reward_balance(const account_name_type& owner) { try { - auto &obj_owner = db_.get_account(owner); - const auto &ridx = db_.get_index().indices().get(); + auto& obj_owner = db_.get_account(owner); + const auto& ridx = db_.get_index().indices().get(); auto itr = ridx.find(obj_owner.id); if (ridx.end() != itr) { format_liquidity_reward_balance(*itr, owner); @@ -733,7 +738,7 @@ namespace mongo_db { document comment_index; comment_index << "comment" << 1; doc.indexes_to_create.push_back(std::move(comment_index)); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -839,7 +844,7 @@ namespace mongo_db { format_account(op.from); format_account(op.to); try { - const auto &dgp = db_.get_dynamic_global_properties(); + const auto& dgp = db_.get_dynamic_global_properties(); auto doc = create_document("transfer_to_vesting", "", ""); auto& body = doc.doc; @@ -896,7 +901,7 @@ namespace mongo_db { auto doc = create_document("limit_order_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -931,7 +936,7 @@ namespace mongo_db { try { format_account(op.owner); - const auto &by_owner_idx = db_.get_index().indices().get(); + const auto& by_owner_idx = db_.get_index().indices().get(); auto itr = by_owner_idx.find(boost::make_tuple(op.owner, op.requestid)); if (itr != by_owner_idx.end()) { auto oid = std::string(op.owner).append("/").append(std::to_string(op.requestid)); @@ -985,9 +990,20 @@ namespace mongo_db { format_account(op.account); } + auto state_writer::operator()(const break_free_referral_operation& op) -> result_type { + try { + const auto& referral = db_.get_account(op.referral); + format_account(referral); + format_account(referral.referrer_account); + } + catch (...) { + // ilog("Unknown exception during formatting referral account or referrer account."); + } + } + auto state_writer::operator()(const witness_update_operation& op) -> result_type { try { - const auto &witness = db_.get_witness(op.owner); + const auto& witness = db_.get_witness(op.owner); format_witness(witness); } catch (...) { @@ -997,8 +1013,8 @@ namespace mongo_db { auto state_writer::operator()(const account_witness_vote_operation& op) -> result_type { try { - const auto &voter = db_.get_account(op.account); - const auto &witness = db_.get_witness(op.witness); + const auto& voter = db_.get_account(op.account); + const auto& witness = db_.get_witness(op.witness); format_account(voter); format_witness(witness); @@ -1006,12 +1022,12 @@ namespace mongo_db { auto oid = op.witness + "/" + op.account; auto oid_hash = hash_oid(oid); - const auto &by_account_witness_idx = db_.get_index().indices().get(); + const auto& by_account_witness_idx = db_.get_index().indices().get(); auto itr = by_account_witness_idx.find(boost::make_tuple(voter.id, witness.id)); if (itr != by_account_witness_idx.end()) { auto doc = create_document("witness_vote_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1042,22 +1058,22 @@ namespace mongo_db { format_account(worker_account); format_account_authority(worker_account); format_witness(worker_account); - const auto &dgp = db_.get_dynamic_global_properties(); - const auto &inc_witness = db_.get_account(dgp.current_witness); + const auto& dgp = db_.get_dynamic_global_properties(); + const auto& inc_witness = db_.get_account(dgp.current_witness); format_account(inc_witness); //format_global_property_object(); } auto state_writer::operator()(const pow2_operation& op) -> result_type { - const auto &dgp = db_.get_dynamic_global_properties(); - const auto &inc_witness = db_.get_account(dgp.current_witness); + const auto& dgp = db_.get_dynamic_global_properties(); + const auto& inc_witness = db_.get_account(dgp.current_witness); format_account(inc_witness); account_name_type worker_account; if (db_.has_hardfork(STEEMIT_HARDFORK_0_16__551)) { - const auto &work = op.work.get(); + const auto& work = op.work.get(); worker_account = work.input.worker_account; } else { - const auto &work = op.work.get(); + const auto& work = op.work.get(); worker_account = work.input.worker_account; } format_account(worker_account); @@ -1089,7 +1105,7 @@ namespace mongo_db { boost::make_tuple(from_account.id, to_account.id)); auto doc = create_document("withdraw_vesting_route_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1121,7 +1137,7 @@ namespace mongo_db { auto doc = create_document("limit_order_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1155,7 +1171,7 @@ namespace mongo_db { auto state_writer::operator()(const request_account_recovery_operation& op) -> result_type { try { - const auto &recovery_request_idx = db_.get_index().indices().get(); + const auto& recovery_request_idx = db_.get_index().indices().get(); auto request = recovery_request_idx.find(op.account_to_recover); auto oid = request->account_to_recover; auto oid_hash = hash_oid(oid); @@ -1204,7 +1220,7 @@ namespace mongo_db { auto state_writer::operator()(const change_recovery_account_operation& op) -> result_type { try { - const auto &change_recovery_idx = db_.get_index().indices().get(); + const auto& change_recovery_idx = db_.get_index().indices().get(); auto request = change_recovery_idx.find(op.account_to_recover); auto oid = request->account_to_recover; auto oid_hash = hash_oid(oid); @@ -1260,7 +1276,7 @@ namespace mongo_db { auto state_writer::operator()(const escrow_release_operation& op) -> result_type { format_account(op.receiver); try { - auto &escrow = db_.get_escrow(op.from, op.escrow_id); + auto& escrow = db_.get_escrow(op.from, op.escrow_id); format_escrow(escrow); if (escrow.steem_balance.amount == 0 && escrow.sbd_balance.amount == 0) { auto oid = std::string(op.from).append("/").append(std::to_string(op.escrow_id)); @@ -1358,8 +1374,8 @@ namespace mongo_db { auto state_writer::operator()(const decline_voting_rights_operation& op) -> result_type { try { if (op.decline) { - const auto &account = db_.get_account(op.account); - const auto &request_idx = db_.get_index().indices().get(); + const auto& account = db_.get_account(op.account); + const auto& request_idx = db_.get_index().indices().get(); auto itr = request_idx.find(account.id); if (itr != request_idx.end()) { auto oid = op.account; @@ -1367,7 +1383,7 @@ namespace mongo_db { auto doc = create_document("decline_voting_rights_request_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1494,8 +1510,8 @@ namespace mongo_db { auto state_writer::operator()(const liquidity_reward_operation& op) -> result_type { try { - auto &owner = db_.get_account(op.owner); - const auto &ridx = db_.get_index().indices().get(); + auto& owner = db_.get_account(op.owner); + const auto& ridx = db_.get_index().indices().get(); auto itr = ridx.find(owner.id); if (ridx.end() != itr) { format_liquidity_reward_balance(*itr, op.owner); @@ -1536,7 +1552,7 @@ namespace mongo_db { auto doc = create_document("withdraw_vesting_route_object", "_id", oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1624,7 +1640,7 @@ namespace mongo_db { auto comment_oid_hash = hash_oid(comment_oid); auto doc = create_document("author_reward", "_id", comment_oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1657,7 +1673,7 @@ namespace mongo_db { document comment_index; comment_index << "comment" << 1; doc.indexes_to_create.push_back(std::move(comment_index)); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1685,7 +1701,7 @@ namespace mongo_db { auto comment_oid_hash = hash_oid(comment_oid); auto doc = create_document("comment_reward", "_id", comment_oid_hash); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1715,7 +1731,7 @@ namespace mongo_db { document comment_index; comment_index << "comment" << 1; doc.indexes_to_create.push_back(std::move(comment_index)); - auto &body = doc.doc; + auto& body = doc.doc; body << "$set" << open_document; @@ -1743,7 +1759,7 @@ namespace mongo_db { auto state_writer::operator()(const chain_properties_update_operation& op) -> result_type { try { db_.get_account(op.owner); // verify owner exists - const auto &witness = db_.get_witness(op.owner); + const auto& witness = db_.get_witness(op.owner); format_witness(witness); } catch (...) { diff --git a/plugins/private_message/private_message_plugin.cpp b/plugins/private_message/private_message_plugin.cpp index a19405aa14..826e35eb85 100644 --- a/plugins/private_message/private_message_plugin.cpp +++ b/plugins/private_message/private_message_plugin.cpp @@ -286,7 +286,7 @@ namespace golos { namespace plugins { namespace private_message { } bool private_message_plugin::private_message_plugin_impl::can_call_callbacks() const { - return !db_.is_producing() && !callbacks_.empty(); + return !db_.is_producing() && !db_.is_generating() && !callbacks_.empty(); } void private_message_plugin::private_message_plugin_impl::call_callbacks( @@ -300,8 +300,9 @@ namespace golos { namespace plugins { namespace private_message { (!info.query.select_events.empty() && !info.query.select_events.count(event)) || info.query.filter_accounts.count(from) || info.query.filter_accounts.count(to) || - (to.size() && !info.query.select_accounts.empty() && !info.query.select_accounts.count(to)) || - (from.size() && !info.query.select_accounts.empty() && !info.query.select_accounts.count(from)) + (!info.query.select_accounts.empty() && + !info.query.select_accounts.count(to) && + !info.query.select_accounts.count(from)) ) { ++itr; continue; diff --git a/tests/common/comment_reward.hpp b/tests/common/comment_reward.hpp index 056e898814..1947c44340 100644 --- a/tests/common/comment_reward.hpp +++ b/tests/common/comment_reward.hpp @@ -59,6 +59,10 @@ namespace golos { namespace chain { return vesting; } + void modify_reward_fund(asset& value) { + reward_fund_ += value; + } + private: void process_funds() { int64_t inflation_rate = STEEMIT_INFLATION_RATE_START_PERCENT; @@ -190,6 +194,16 @@ namespace golos { namespace chain { total_vote_payouts_ += payout; } + if (db_.has_hardfork(STEEMIT_HARDFORK_0_19__898) && comment_.total_vote_weight > 0) { + auto reward_fund_claim = (vote_rewards_fund_ * comment_.auction_window_weight) / total_weight; + + comment_rewards_ -= reward_fund_claim.to_uint64(); + + auto tokes_back_to_reward_fund = asset(reward_fund_claim.to_uint64(), STEEM_SYMBOL); + fund_.modify_reward_fund(tokes_back_to_reward_fund); + } + + comment_rewards_ -= total_vote_rewards_; } diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 1004f9085e..f5849456c0 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -236,7 +236,7 @@ SIMPLE_PROTOCOL_ERROR_VALIDATOR(tx_missing_other_auth); #define GET_ACTOR(name) \ fc::ecc::private_key name ## _private_key = generate_private_key(BOOST_PP_STRINGIZE(name)); \ - const account_object& name = get_account(BOOST_PP_STRINGIZE(name)); \ + const account_object& name = db->get_account(BOOST_PP_STRINGIZE(name)); \ account_id_type name ## _id = name.id; (void)name ## _id; #define ACTORS_IMPL(r, data, elem) ACTOR(elem) diff --git a/tests/tests/operation_tests.cpp b/tests/tests/operation_tests.cpp index ed96a8b676..4122b71641 100644 --- a/tests/tests/operation_tests.cpp +++ b/tests/tests/operation_tests.cpp @@ -6944,5 +6944,329 @@ BOOST_FIXTURE_TEST_SUITE(operation_tests, clean_database_fixture) BOOST_AUTO_TEST_SUITE_END() // account_metadata + + BOOST_AUTO_TEST_SUITE(referral) + + BOOST_AUTO_TEST_CASE(referral_validate_create_account) { + try { + BOOST_TEST_MESSAGE("Testing: referral_validate_create_account"); + + BOOST_TEST_MESSAGE("--- Test failure with non-GOLOS break fee"); + + auto testref_private_key = generate_private_key("temp_key"); + + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "testref"; + cr.owner = authority(1, testref_private_key.get_public_key(), 1); + cr.active = authority(1, testref_private_key.get_public_key(), 1); + cr.posting = authority(1, testref_private_key.get_public_key(), 1); + cr.memo_key = testref_private_key.get_public_key(); + + account_referral_options aro; + aro.referrer = "bob"; + aro.interest_rate = 900; + auto end_date = db->head_block_time() + GOLOS_DEFAULT_REFERRAL_TERM_SEC; + aro.end_date = end_date; + aro.break_fee = asset(100, VESTS_SYMBOL); + cr.extensions.insert(aro); + + GOLOS_CHECK_ERROR_PROPS(cr.validate(), + CHECK_ERROR(invalid_parameter, "break_fee")); + + BOOST_TEST_MESSAGE("--- Test failure with negative break fee"); + + aro.break_fee = asset(-100, STEEM_SYMBOL); + + cr.extensions.clear(); + cr.extensions.insert(aro); + + GOLOS_CHECK_ERROR_PROPS(cr.validate(), + CHECK_ERROR(invalid_parameter, "break_fee")); + + BOOST_TEST_MESSAGE("--- Test success with correct break fee"); + + aro.break_fee = asset(100, STEEM_SYMBOL); + + cr.extensions.clear(); + cr.extensions.insert(aro); + + CHECK_OP_VALID(cr); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(referral_create_account_comment) { + try { + BOOST_TEST_MESSAGE("Testing: referral_create_account_comment"); + + ACTOR(bob); + generate_blocks(1); + + fund("bob", ASSET_GOLOS(1000)); + + BOOST_TEST_MESSAGE("--- Test creation of referral account"); + + auto testref_private_key = generate_private_key("temp_key"); + + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "testref"; + cr.owner = authority(1, testref_private_key.get_public_key(), 1); + cr.active = authority(1, testref_private_key.get_public_key(), 1); + cr.posting = authority(1, testref_private_key.get_public_key(), 1); + cr.memo_key = testref_private_key.get_public_key(); + + account_referral_options aro; + aro.referrer = "bob"; + aro.interest_rate = 900; + auto end_date = db->head_block_time() + GOLOS_DEFAULT_REFERRAL_TERM_SEC; + aro.end_date = end_date; + aro.break_fee = asset(100, STEEM_SYMBOL); + cr.extensions.insert(aro); + + signed_transaction tx; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); + generate_blocks(1); + + const auto& testref_acc = db->get_account("testref"); + BOOST_CHECK_EQUAL(testref_acc.referrer_account, "bob"); + BOOST_CHECK_EQUAL(testref_acc.referrer_interest_rate, 900); + BOOST_CHECK_EQUAL(testref_acc.referral_end_date, end_date); + BOOST_CHECK_EQUAL(testref_acc.referral_break_fee.amount, 100); + + fund("testref", ASSET_GOLOS(100)); + + BOOST_TEST_MESSAGE("--- Test posting of comment by referral"); + + tx.operations.clear(); + tx.signatures.clear(); + + comment_operation co; + co.author = "testref"; + co.permlink = "foo"; + co.title = "bar"; + co.body = "foo bar"; + co.parent_author = ""; + co.parent_permlink = "ipsum"; + + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(co); + + tx.sign(testref_private_key, db->get_chain_id()); + db->push_transaction(tx, 0); + generate_blocks(1); + + const auto& testref_com = db->get_comment("testref", string("foo")); + const auto& referrer_find = std::find_if(testref_com.beneficiaries.begin(), + testref_com.beneficiaries.end(), [&testref_acc](const beneficiary_route_type& benef) { + return benef.account == testref_acc.referrer_account; + }); + BOOST_CHECK(referrer_find != testref_com.beneficiaries.end()); + + BOOST_TEST_MESSAGE("--- Test breaking of referral account"); + + tx.operations.clear(); + tx.signatures.clear(); + + break_free_referral_operation op; + op.referral = "testref"; + + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(op); + + tx.sign(testref_private_key, db->get_chain_id()); + db->push_transaction(tx, 0); + + const auto& testref_free_acc = db->get_account("testref"); + BOOST_CHECK_EQUAL(testref_free_acc.referrer_account, account_name_type()); + BOOST_CHECK_EQUAL(testref_free_acc.referrer_interest_rate, 0); + BOOST_CHECK_EQUAL(testref_free_acc.referral_end_date, time_point_sec::min()); + BOOST_CHECK_EQUAL(testref_free_acc.referral_break_fee.amount, 0); + + validate_database(); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(referral_create_break_account_comment) { + try { + BOOST_TEST_MESSAGE("Testing: referral_create_break_account_comment"); + + ACTOR(bob); + generate_blocks(1); + + fund("bob", ASSET_GOLOS(1000)); + + BOOST_TEST_MESSAGE("--- Test creation of referral account"); + + auto testref_private_key = generate_private_key("temp_key"); + + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "testref"; + cr.owner = authority(1, testref_private_key.get_public_key(), 1); + cr.active = authority(1, testref_private_key.get_public_key(), 1); + cr.posting = authority(1, testref_private_key.get_public_key(), 1); + cr.memo_key = testref_private_key.get_public_key(); + + account_referral_options aro; + aro.referrer = "bob"; + aro.interest_rate = 900; + auto end_date = db->head_block_time() + GOLOS_DEFAULT_REFERRAL_TERM_SEC; + aro.end_date = end_date; + aro.break_fee = asset(100, STEEM_SYMBOL); + cr.extensions.insert(aro); + + signed_transaction tx; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); + generate_blocks(1); + + const auto& testref_acc = db->get_account("testref"); + BOOST_CHECK_EQUAL(testref_acc.referrer_account, "bob"); + BOOST_CHECK_EQUAL(testref_acc.referrer_interest_rate, 900); + BOOST_CHECK_EQUAL(testref_acc.referral_end_date, end_date); + BOOST_CHECK_EQUAL(testref_acc.referral_break_fee.amount, 100); + + fund("testref", ASSET_GOLOS(100)); + + BOOST_TEST_MESSAGE("--- Test breaking of referral account"); + + tx.operations.clear(); + tx.signatures.clear(); + + break_free_referral_operation op; + op.referral = "testref"; + + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(op); + + tx.sign(testref_private_key, db->get_chain_id()); + db->push_transaction(tx, 0); + + const auto& testref_free_acc = db->get_account("testref"); + BOOST_CHECK_EQUAL(testref_free_acc.referrer_account, account_name_type()); + BOOST_CHECK_EQUAL(testref_free_acc.referrer_interest_rate, 0); + BOOST_CHECK_EQUAL(testref_free_acc.referral_end_date, time_point_sec::min()); + BOOST_CHECK_EQUAL(testref_free_acc.referral_break_fee.amount, 0); + + BOOST_TEST_MESSAGE("--- Test posting of comment by referral account after break"); + + tx.operations.clear(); + tx.signatures.clear(); + + comment_operation co; + co.author = "testref"; + co.permlink = "foo2"; + co.title = "bar"; + co.body = "foo bar"; + co.parent_author = ""; + co.parent_permlink = "ipsum"; + + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(co); + + tx.sign(testref_private_key, db->get_chain_id()); + db->push_transaction(tx, 0); + generate_blocks(1); + + const auto& testref_com2 = db->get_comment("testref", string("foo2")); + const auto& referrer_find2 = std::find_if(testref_com2.beneficiaries.begin(), + testref_com2.beneficiaries.end(), [&testref_acc](const beneficiary_route_type& benef) { + return benef.account == testref_acc.referrer_account; // Using account before break + }); + BOOST_CHECK(referrer_find2 == testref_com2.beneficiaries.end()); + + validate_database(); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_CASE(referral_create_expire_account_comment) { + try { + BOOST_TEST_MESSAGE("Testing: referral_create_expire_account_comment"); + + ACTOR(bob); + generate_blocks(1); + + fund("bob", ASSET_GOLOS(1000)); + + BOOST_TEST_MESSAGE("--- Test creation of referral account"); + + auto testref_private_key = generate_private_key("temp_key"); + + account_create_with_delegation_operation cr; + cr.fee = ASSET_GOLOS(1000); + cr.delegation = ASSET_GESTS(0); + cr.creator = "bob"; + cr.new_account_name = "testref"; + cr.owner = authority(1, testref_private_key.get_public_key(), 1); + cr.active = authority(1, testref_private_key.get_public_key(), 1); + cr.posting = authority(1, testref_private_key.get_public_key(), 1); + cr.memo_key = testref_private_key.get_public_key(); + + account_referral_options aro; + aro.referrer = "bob"; + aro.interest_rate = 900; + auto end_date = db->head_block_time(); // Will expire after generating at least 1 block + aro.end_date = end_date; + aro.break_fee = asset(100, STEEM_SYMBOL); + cr.extensions.insert(aro); + + signed_transaction tx; + BOOST_CHECK_NO_THROW(push_tx_with_ops(tx, bob_private_key, cr)); + generate_blocks(1); + + const auto& testref_acc = db->get_account("testref"); + BOOST_CHECK_EQUAL(testref_acc.referrer_account, "bob"); + BOOST_CHECK_EQUAL(testref_acc.referrer_interest_rate, 900); + BOOST_CHECK_EQUAL(testref_acc.referral_end_date, end_date); + BOOST_CHECK_EQUAL(testref_acc.referral_break_fee.amount, 100); + + fund("testref", ASSET_GOLOS(100)); + + BOOST_TEST_MESSAGE("--- Test posting of comment by referral account after expiration"); + + tx.operations.clear(); + tx.signatures.clear(); + + comment_operation co; + co.author = "testref"; + co.permlink = "foo3"; + co.title = "bar"; + co.body = "foo bar"; + co.parent_author = ""; + co.parent_permlink = "ipsum"; + + co.title = "bar"; + co.body = "foo bar"; + + tx.set_expiration(db->head_block_time() + STEEMIT_MAX_TIME_UNTIL_EXPIRATION); + tx.operations.push_back(co); + + tx.sign(testref_private_key, db->get_chain_id()); + db->push_transaction(tx, 0); + generate_blocks(1); + + const auto& testref_com = db->get_comment("testref", string("foo3")); + const auto& referrer_find = std::find_if(testref_com.beneficiaries.begin(), + testref_com.beneficiaries.end(), [&testref_acc](const beneficiary_route_type& benef) { + return benef.account == testref_acc.referrer_account; // Using account before break + }); + BOOST_CHECK(referrer_find == testref_com.beneficiaries.end()); + + validate_database(); + } + FC_LOG_AND_RETHROW() + } + + BOOST_AUTO_TEST_SUITE_END() // referral + BOOST_AUTO_TEST_SUITE_END() #endif