From b694722bf2ab193fc53867b193717c4b3a6c9955 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 13:58:37 +0200 Subject: [PATCH 01/10] Implement rex maturity changes ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/132 --- .../include/eosio.system/eosio.system.hpp | 19 ++++++++++++++++++- contracts/eosio.system/src/eosio.system.cpp | 3 ++- contracts/eosio.system/src/rex.cpp | 19 +++++++++++++++++-- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index d3dfc4b7..ddaf7384 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -539,6 +539,14 @@ namespace eosiosystem { typedef eosio::multi_index< "rexqueue"_n, rex_order, indexed_by<"bytime"_n, const_mem_fun>> rex_order_table; + struct [[eosio::table("rexmaturity"),eosio::contract("eosio.system")]] rex_maturity { + uint32_t num_of_maturity_buckets = 5; + + EOSLIB_SERIALIZE( rex_maturity, (num_of_maturity_buckets) ) + }; + + typedef eosio::singleton<"rexmaturity"_n, rex_maturity> rex_maturity_singleton; + struct rex_order_outcome { bool success; asset proceeds; @@ -720,6 +728,7 @@ namespace eosiosystem { rex_fund_table _rexfunds; rex_balance_table _rexbalance; rex_order_table _rexorders; + rex_maturity_singleton _rexmaturity; public: static constexpr eosio::name active_permission{"active"_n}; @@ -1079,6 +1088,14 @@ namespace eosiosystem { [[eosio::action]] void closerex( const name& owner ); + /** + * Facilitates the modification of REX maturity buckets + * + * @param num_of_maturity_buckets - used to calculate maturity time of purchase REX tokens from end of the day UTC. + */ + [[eosio::action]] + void rexmaturity(const uint32_t num_of_maturity_buckets); + /** * Undelegate bandwidth action, decreases the total tokens delegated by `from` to `receiver` and/or * frees the memory associated with the delegation if there is nothing @@ -1569,7 +1586,7 @@ namespace eosiosystem { bool rex_loans_available()const; bool rex_system_initialized()const { return _rexpool.begin() != _rexpool.end(); } bool rex_available()const { return rex_system_initialized() && _rexpool.begin()->total_rex.amount > 0; } - static time_point_sec get_rex_maturity(); + static time_point_sec get_rex_maturity(const name& system_account_name = "eosio"_n ); asset add_to_rex_balance( const name& owner, const asset& payment, const asset& rex_received ); asset add_to_rex_pool( const asset& payment ); void add_to_rex_return_pool( const asset& fee ); diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 527b0324..ab8c7183 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -30,7 +30,8 @@ namespace eosiosystem { _rexretbuckets(get_self(), get_self().value), _rexfunds(get_self(), get_self().value), _rexbalance(get_self(), get_self().value), - _rexorders(get_self(), get_self().value) + _rexorders(get_self(), get_self().value), + _rexmaturity(get_self(), get_self().value) { _gstate = _global.exists() ? _global.get() : get_default_parameters(); _gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{}; diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 99542c13..d1198b0c 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -8,6 +8,20 @@ namespace eosiosystem { using eosio::token; using eosio::seconds; + void system_contract::rexmaturity(const uint32_t num_of_maturity_buckets) + { + require_auth(get_self()); + + check(num_of_maturity_buckets > 0, "num_of_maturity_buckets must be positive"); + check(num_of_maturity_buckets <= 30, "num_of_maturity_buckets must be less than or equal to 30"); + + auto state = _rexmaturity.get_or_default(); + if ( _rexmaturity.exists() ) check(state.num_of_maturity_buckets != num_of_maturity_buckets, "num_of_maturity_buckets is the same as the current value"); + + state.num_of_maturity_buckets = num_of_maturity_buckets; + _rexmaturity.set(state, get_self()); + } + void system_contract::deposit( const name& owner, const asset& amount ) { require_auth( owner ); @@ -959,9 +973,10 @@ namespace eosiosystem { * * @return time_point_sec */ - time_point_sec system_contract::get_rex_maturity() + time_point_sec system_contract::get_rex_maturity( const name& system_account_name ) { - const uint32_t num_of_maturity_buckets = 5; + rex_maturity_singleton _rexmaturity(system_account_name, system_account_name.value); + const uint32_t num_of_maturity_buckets = _rexmaturity.get_or_default().num_of_maturity_buckets; // default 5 static const uint32_t now = current_time_point().sec_since_epoch(); static const uint32_t r = now % seconds_per_day; static const time_point_sec rms{ now - r + num_of_maturity_buckets * seconds_per_day }; From a8afceebd5c39cfe6f52ea9fb3733ba7687c0a78 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 14:09:36 +0200 Subject: [PATCH 02/10] Remove voting requirements for rex ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/133 --- .../include/eosio.system/eosio.system.hpp | 8 -------- contracts/eosio.system/src/rex.cpp | 15 --------------- contracts/eosio.system/src/voting.cpp | 4 ---- tests/eosio.system_tests.cpp | 9 --------- 4 files changed, 36 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index ddaf7384..5e514e89 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -891,9 +891,6 @@ namespace eosiosystem { * @param from - owner account name, * @param amount - amount of tokens taken out of 'from' REX fund. * - * @pre A voting requirement must be satisfied before action can be executed. - * @pre User must vote for at least 21 producers or delegate vote to proxy before buying REX. - * * @post User votes are updated following this action. * @post Tokens used in purchase are added to user's voting power. * @post Bought REX cannot be sold before 4 days counting from end of day of purchase. @@ -910,9 +907,6 @@ namespace eosiosystem { * @param from_net - amount of tokens to be unstaked from NET bandwidth and used for REX purchase, * @param from_cpu - amount of tokens to be unstaked from CPU bandwidth and used for REX purchase. * - * @pre A voting requirement must be satisfied before action can be executed. - * @pre User must vote for at least 21 producers or delegate vote to proxy before buying REX. - * * @post User votes are updated following this action. * @post Tokens used in purchase are added to user's voting power. * @post Bought REX cannot be sold before 4 days counting from end of day of purchase. @@ -1569,8 +1563,6 @@ namespace eosiosystem { void runrex( uint16_t max ); void update_rex_pool(); void update_resource_limits( const name& from, const name& receiver, int64_t delta_net, int64_t delta_cpu ); - void check_voting_requirement( const name& owner, - const char* error_msg = "must vote for at least 21 producers or for a proxy before buying REX" )const; rex_order_outcome fill_rex_order( const rex_balance_table::const_iterator& bitr, const asset& rex ); asset update_rex_account( const name& owner, const asset& proceeds, const asset& unstake_quant, bool force_vote_update = false ); void channel_to_rex( const name& from, const asset& amount, bool required = false ); diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index d1198b0c..46679c1c 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -57,7 +57,6 @@ namespace eosiosystem { check( amount.symbol == core_symbol(), "asset must be core token" ); check( 0 < amount.amount, "must use positive amount" ); - check_voting_requirement( from ); transfer_from_fund( from, amount ); const asset rex_received = add_to_rex_pool( amount ); const asset delta_rex_stake = add_to_rex_balance( from, amount, rex_received ); @@ -75,7 +74,6 @@ namespace eosiosystem { check( from_net.symbol == core_symbol() && from_cpu.symbol == core_symbol(), "asset must be core token" ); check( (0 <= from_net.amount) && (0 <= from_cpu.amount) && (0 < from_net.amount || 0 < from_cpu.amount), "must unstake a positive amount to buy rex" ); - check_voting_requirement( owner ); { del_bandwidth_table dbw_table( get_self(), owner.value ); @@ -425,19 +423,6 @@ namespace eosiosystem { } } - /** - * @brief Checks if account satisfies voting requirement (voting for a proxy or 21 producers) - * for buying REX - * - * @param owner - account buying or already holding REX tokens - * @err_msg - error message - */ - void system_contract::check_voting_requirement( const name& owner, const char* error_msg )const - { - auto vitr = _voters.find( owner.value ); - check( vitr != _voters.end() && ( vitr->proxy || 21 <= vitr->producers.size() ), error_msg ); - } - /** * @brief Checks if CPU and Network loans are available * diff --git a/contracts/eosio.system/src/voting.cpp b/contracts/eosio.system/src/voting.cpp index 9f47d23b..d360ef82 100644 --- a/contracts/eosio.system/src/voting.cpp +++ b/contracts/eosio.system/src/voting.cpp @@ -208,10 +208,6 @@ namespace eosiosystem { vote_stake_updater( voter_name ); update_votes( voter_name, proxy, producers, true ); - auto rex_itr = _rexbalance.find( voter_name.value ); - if( rex_itr != _rexbalance.end() && rex_itr->rex_balance.amount > 0 ) { - check_voting_requirement( voter_name, "voter holding REX tokens must vote for at least 21 producers or for a proxy" ); - } } void system_contract::voteupdate( const name& voter_name ) { diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 37d8e9cf..90890aa2 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -4165,8 +4165,6 @@ BOOST_FIXTURE_TEST_CASE( unstake_buy_rex, eosio_system_tester, * boost::unit_tes BOOST_REQUIRE_EQUAL( get_net_limit( alice ), init_net_limit + net_stake.get_amount() ); BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 20) ) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("must vote for at least 21 producers or for a proxy before buying REX"), - unstaketorex( alice, alice, net_stake, cpu_stake ) ); BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); const asset init_eosio_stake_balance = get_balance( "eosio.stake"_n ); @@ -5188,8 +5186,6 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to } } - BOOST_REQUIRE_EQUAL( wasm_assert_msg("voter holding REX tokens must vote for at least 21 producers or for a proxy"), - vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 20) ) ); BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); @@ -5225,8 +5221,6 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance( alice ) ) ); BOOST_REQUIRE_EQUAL( 0, get_rex_balance( alice ).get_amount() ); BOOST_REQUIRE_EQUAL( success(), vote( alice, { producer_names[0], producer_names[4] } ) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("must vote for at least 21 producers or for a proxy before buying REX"), - buyrex( alice, core_sym::from_string("1.0000") ) ); } FC_LOG_AND_RETHROW() @@ -5531,9 +5525,6 @@ BOOST_FIXTURE_TEST_CASE( b1_vesting, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("b1 can only claim their tokens over 10 years"), unstake( b1, b1, final_amount, final_amount ) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("must vote for at least 21 producers or for a proxy before buying REX"), - unstaketorex( b1, b1, final_amount - small_amount, final_amount - small_amount ) ); - BOOST_REQUIRE_EQUAL( error("missing authority of eosio"), vote( b1, { }, "proxyaccount"_n ) ); BOOST_REQUIRE_EQUAL( success(), unstake( b1, b1, final_amount - small_amount, final_amount - small_amount ) ); From 67c2429985dc0f8640089b026f9dee440381f7f9 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 14:55:20 +0200 Subject: [PATCH 03/10] Move to savings when buying REX ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 --- contracts/eosio.system/src/rex.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 46679c1c..ab334094 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -62,6 +62,8 @@ namespace eosiosystem { const asset delta_rex_stake = add_to_rex_balance( from, amount, rex_received ); runrex(2); update_rex_account( from, asset( 0, core_symbol() ), delta_rex_stake ); + mvtosavings( from, rex_received ); + // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); buyrex_act.send( rex_received ); From 6d791adc113c081894fe582d088d15c7175ab569 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 16:18:03 +0200 Subject: [PATCH 04/10] Move to savings when buying REX & sell REX when matured ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 & https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 --- .../include/eosio.system/eosio.system.hpp | 10 ++++++++++ contracts/eosio.system/src/rex.cpp | 19 ++++++++++++++++++- 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index 5e514e89..930c95bb 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -24,6 +24,15 @@ // be set to 0. #define CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX 1 +#ifdef MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS +#undef MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS +#endif +// MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS macro determines whether matured REX is sold immediately and buying REX is moved immediately to REX savings. +// In order to enable this behavior, the macro must be set to 1. +// https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 +// https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 +#define MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS 0 + namespace eosiosystem { using eosio::asset; @@ -1588,6 +1597,7 @@ namespace eosiosystem { int64_t read_rex_savings( const rex_balance_table::const_iterator& bitr ); void put_rex_savings( const rex_balance_table::const_iterator& bitr, int64_t rex ); void update_rex_stake( const name& voter ); + void sell_rex( const name& from, const asset& rex ); void add_loan_to_rex_pool( const asset& payment, int64_t rented_tokens, bool new_loan ); void remove_loan_from_rex_pool( const rex_loan& loan ); diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index ab334094..b1d883fa 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -62,7 +62,10 @@ namespace eosiosystem { const asset delta_rex_stake = add_to_rex_balance( from, amount, rex_received ); runrex(2); update_rex_account( from, asset( 0, core_symbol() ), delta_rex_stake ); - mvtosavings( from, rex_received ); + + #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS + mvtosavings( from, rex_received ); + #endif // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); @@ -103,6 +106,11 @@ namespace eosiosystem { auto rex_stake_delta = add_to_rex_balance( owner, payment, rex_received ); runrex(2); update_rex_account( owner, asset( 0, core_symbol() ), rex_stake_delta - payment, true ); + + #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS + mvtosavings( owner, rex_received ); + #endif + // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); buyrex_act.send( rex_received ); @@ -111,7 +119,11 @@ namespace eosiosystem { void system_contract::sellrex( const name& from, const asset& rex ) { require_auth( from ); + sell_rex( from, rex ); + } + void system_contract::sell_rex( const name& from, const asset& rex ) + { runrex(2); auto bitr = _rexbalance.require_find( from.value, "user must first buyrex" ); @@ -983,6 +995,11 @@ namespace eosiosystem { rb.matured_rex += rb.rex_maturities.front().second; rb.rex_maturities.erase(rb.rex_maturities.begin()); } + #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS + if ( rb.matured_rex > 0 ) { + sell_rex(rb.owner, asset(rb.matured_rex, rex_symbol)); + } + #endif }); } From 85ef99cc1d4f8f7fe12a0b84cbafc905313574b4 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Wed, 8 May 2024 15:59:34 +0200 Subject: [PATCH 05/10] make rex maturity settings as actions --- .../include/eosio.system/eosio.system.hpp | 20 +++--- contracts/eosio.system/src/rex.cpp | 66 ++++++++++++++----- 2 files changed, 59 insertions(+), 27 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index 930c95bb..1906c1bc 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -24,15 +24,6 @@ // be set to 0. #define CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX 1 -#ifdef MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS -#undef MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS -#endif -// MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS macro determines whether matured REX is sold immediately and buying REX is moved immediately to REX savings. -// In order to enable this behavior, the macro must be set to 1. -// https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 -// https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 -#define MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS 0 - namespace eosiosystem { using eosio::asset; @@ -550,8 +541,8 @@ namespace eosiosystem { struct [[eosio::table("rexmaturity"),eosio::contract("eosio.system")]] rex_maturity { uint32_t num_of_maturity_buckets = 5; - - EOSLIB_SERIALIZE( rex_maturity, (num_of_maturity_buckets) ) + bool sell_matured_rex = false; + bool buy_rex_to_savings = false; }; typedef eosio::singleton<"rexmaturity"_n, rex_maturity> rex_maturity_singleton; @@ -1095,9 +1086,13 @@ namespace eosiosystem { * Facilitates the modification of REX maturity buckets * * @param num_of_maturity_buckets - used to calculate maturity time of purchase REX tokens from end of the day UTC. + * @param sell_matured_rex - if true, matured REX is sold immediately. + * https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + * @param buy_rex_to_savings - if true, buying REX is moved immediately to REX savings. + * https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 */ [[eosio::action]] - void rexmaturity(const uint32_t num_of_maturity_buckets); + void rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ); /** * Undelegate bandwidth action, decreases the total tokens delegated by `from` to `receiver` and/or @@ -1592,6 +1587,7 @@ namespace eosiosystem { asset add_to_rex_pool( const asset& payment ); void add_to_rex_return_pool( const asset& fee ); void process_rex_maturities( const rex_balance_table::const_iterator& bitr ); + void process_sell_matured_rex( const name owner ); void consolidate_rex_balance( const rex_balance_table::const_iterator& bitr, const asset& rex_in_sell_order ); int64_t read_rex_savings( const rex_balance_table::const_iterator& bitr ); diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index b1d883fa..0c87fa31 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -8,17 +8,19 @@ namespace eosiosystem { using eosio::token; using eosio::seconds; - void system_contract::rexmaturity(const uint32_t num_of_maturity_buckets) + void system_contract::rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) { require_auth(get_self()); - check(num_of_maturity_buckets > 0, "num_of_maturity_buckets must be positive"); - check(num_of_maturity_buckets <= 30, "num_of_maturity_buckets must be less than or equal to 30"); - auto state = _rexmaturity.get_or_default(); - if ( _rexmaturity.exists() ) check(state.num_of_maturity_buckets != num_of_maturity_buckets, "num_of_maturity_buckets is the same as the current value"); - state.num_of_maturity_buckets = num_of_maturity_buckets; + check(*num_of_maturity_buckets > 0, "num_of_maturity_buckets must be positive"); + check(*num_of_maturity_buckets <= 30, "num_of_maturity_buckets must be less than or equal to 30"); + if ( _rexmaturity.exists() && num_of_maturity_buckets ) check(state.num_of_maturity_buckets != *num_of_maturity_buckets, "num_of_maturity_buckets is the same as the current value"); + + if ( num_of_maturity_buckets ) state.num_of_maturity_buckets = *num_of_maturity_buckets; + if ( sell_matured_rex ) state.sell_matured_rex = *sell_matured_rex; + if ( buy_rex_to_savings ) state.buy_rex_to_savings = *buy_rex_to_savings; _rexmaturity.set(state, get_self()); } @@ -63,9 +65,18 @@ namespace eosiosystem { runrex(2); update_rex_account( from, asset( 0, core_symbol() ), delta_rex_stake ); - #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS + // buying REX immediately moves to REX savings + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 + const auto rex_maturity_state = _rexmaturity.get_or_default(); + if ( rex_maturity_state.buy_rex_to_savings ) { mvtosavings( from, rex_received ); - #endif + } + + // sell any matured REX + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + if ( rex_maturity_state.sell_matured_rex ) { + process_sell_matured_rex( from ); + } // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); @@ -107,9 +118,18 @@ namespace eosiosystem { runrex(2); update_rex_account( owner, asset( 0, core_symbol() ), rex_stake_delta - payment, true ); - #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS + // unstake to REX immediately moves to REX savings + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 + const auto rex_maturity_state = _rexmaturity.get_or_default(); + if ( rex_maturity_state.buy_rex_to_savings ) { mvtosavings( owner, rex_received ); - #endif + } + + // sell any matured REX + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + if ( rex_maturity_state.sell_matured_rex ) { + process_sell_matured_rex( owner ); + } // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); @@ -120,6 +140,13 @@ namespace eosiosystem { { require_auth( from ); sell_rex( from, rex ); + + // sell any remaining matured REX + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + const auto rex_maturity_state = _rexmaturity.get_or_default(); + if ( rex_maturity_state.sell_matured_rex ) { + process_sell_matured_rex( from ); + } } void system_contract::sell_rex( const name& from, const asset& rex ) @@ -995,14 +1022,23 @@ namespace eosiosystem { rb.matured_rex += rb.rex_maturities.front().second; rb.rex_maturities.erase(rb.rex_maturities.begin()); } - #if MATURED_REX_SOLD_AND_BUY_REX_TO_SAVINGS - if ( rb.matured_rex > 0 ) { - sell_rex(rb.owner, asset(rb.matured_rex, rex_symbol)); - } - #endif }); } + /** + * @brief Sells matured REX tokens + * https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + * + * @param owner - owner account name + */ + void system_contract::process_sell_matured_rex( const name owner ) + { + const auto itr = _rexbalance.find( owner.value ); + if ( itr->matured_rex > 0 ) { + sell_rex(owner, asset(itr->matured_rex, rex_symbol)); + } + } + /** * @brief Consolidates REX maturity buckets into one * From 4358d70ecd7267ab99a1ef624ba97f9f9b1056d8 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Wed, 8 May 2024 16:28:49 +0200 Subject: [PATCH 06/10] refactor process_sell_matured_rex --- contracts/eosio.system/src/rex.cpp | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 0c87fa31..4a43474f 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -71,12 +71,7 @@ namespace eosiosystem { if ( rex_maturity_state.buy_rex_to_savings ) { mvtosavings( from, rex_received ); } - - // sell any matured REX - // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 - if ( rex_maturity_state.sell_matured_rex ) { - process_sell_matured_rex( from ); - } + process_sell_matured_rex( from ); // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); @@ -124,12 +119,7 @@ namespace eosiosystem { if ( rex_maturity_state.buy_rex_to_savings ) { mvtosavings( owner, rex_received ); } - - // sell any matured REX - // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 - if ( rex_maturity_state.sell_matured_rex ) { - process_sell_matured_rex( owner ); - } + process_sell_matured_rex( owner ); // dummy action added so that amount of REX tokens purchased shows up in action trace rex_results::buyresult_action buyrex_act( rex_account, std::vector{ } ); @@ -140,13 +130,7 @@ namespace eosiosystem { { require_auth( from ); sell_rex( from, rex ); - - // sell any remaining matured REX - // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 - const auto rex_maturity_state = _rexmaturity.get_or_default(); - if ( rex_maturity_state.sell_matured_rex ) { - process_sell_matured_rex( from ); - } + process_sell_matured_rex( from ); } void system_contract::sell_rex( const name& from, const asset& rex ) @@ -1033,6 +1017,9 @@ namespace eosiosystem { */ void system_contract::process_sell_matured_rex( const name owner ) { + const auto rex_maturity_state = _rexmaturity.get_or_default(); + if ( rex_maturity_state.sell_matured_rex == false ) return; // skip selling matured REX + const auto itr = _rexbalance.find( owner.value ); if ( itr->matured_rex > 0 ) { sell_rex(owner, asset(itr->matured_rex, rex_symbol)); From 8028ff7c581179dcb05f786e888c2f6485b041f8 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Thu, 9 May 2024 22:25:55 +0200 Subject: [PATCH 07/10] add matured rex tests --- contracts/eosio.system/src/rex.cpp | 1 - tests/eosio.system_rex_matured_tests.cpp | 60 ++++++++++++++++++++++++ tests/eosio.system_tester.hpp | 4 ++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 tests/eosio.system_rex_matured_tests.cpp diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 4a43474f..325541d2 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -16,7 +16,6 @@ namespace eosiosystem { check(*num_of_maturity_buckets > 0, "num_of_maturity_buckets must be positive"); check(*num_of_maturity_buckets <= 30, "num_of_maturity_buckets must be less than or equal to 30"); - if ( _rexmaturity.exists() && num_of_maturity_buckets ) check(state.num_of_maturity_buckets != *num_of_maturity_buckets, "num_of_maturity_buckets is the same as the current value"); if ( num_of_maturity_buckets ) state.num_of_maturity_buckets = *num_of_maturity_buckets; if ( sell_matured_rex ) state.sell_matured_rex = *sell_matured_rex; diff --git a/tests/eosio.system_rex_matured_tests.cpp b/tests/eosio.system_rex_matured_tests.cpp new file mode 100644 index 00000000..1a753561 --- /dev/null +++ b/tests/eosio.system_rex_matured_tests.cpp @@ -0,0 +1,60 @@ +#include + +#include "eosio.system_tester.hpp" + +using namespace eosio_system; + +BOOST_AUTO_TEST_SUITE(eosio_system_rex_tests) + +bool within_error(int64_t a, int64_t b, int64_t err) { return std::abs(a - b) <= err; }; +bool within_one(int64_t a, int64_t b) { return within_error(a, b, 1); } + +BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { + // @param num_of_maturity_buckets - used to calculate maturity time of purchase REX tokens from end of the day UTC. + // @param sell_matured_rex - if true, matured REX is sold immediately. + // @param buy_rex_to_savings - if true, buying REX is moved immediately to REX savings. + // + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/132 + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 + // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, true ) ); + + const int64_t ratio = 10000; + const asset init_rent = core_sym::from_string("20000.0000"); + const asset init_balance = core_sym::from_string("1000.0000"); + const std::vector accounts = { "alice"_n, "bob"_n }; + account_name alice = accounts[0], bob = accounts[1]; + setup_rex_accounts( accounts, init_balance ); + + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( alice, core_sym::from_string("2.5000") ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( alice, asset::from_string("25000.0000 REX") ) ); + + produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( alice, asset::from_string("25000.0000 REX") ) ); + produce_block(fc::days(16)); + + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( alice, asset::from_string("25000.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("13.0000") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("13.0000"), get_rex_vote_stake( alice ) ); + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, core_sym::from_string("17.0000") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), get_rex_vote_stake( alice ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("970.0000"), get_rex_fund(alice) ); + BOOST_REQUIRE_EQUAL( get_rex_balance(alice).get_amount(), ratio * asset::from_string("30.0000 REX").get_amount() ); + auto rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_unlent"].as() ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); + BOOST_REQUIRE_EQUAL( init_rent, rex_pool["total_rent"].as() ); + BOOST_REQUIRE_EQUAL( get_rex_balance(alice), rex_pool["total_rex"].as() ); + + // set `buy_rex_to_savings=false` to test buying REX without moving it to REX savings + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, false ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( bob, core_sym::from_string("2.5000") ) ); + produce_blocks(2); + produce_block(fc::days(21)); + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( bob, asset::from_string("25000.0000 REX") ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 93a8eb24..ef36ef67 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -874,6 +874,10 @@ class eosio_system_tester : public TESTER { return push_action( name(owner), "closerex"_n, mvo()("owner", owner) ); } + action_result rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) { + return push_action( "eosio"_n, "rexmaturity"_n, mvo()("num_of_maturity_buckets", num_of_maturity_buckets)("sell_matured_rex", sell_matured_rex)("buy_rex_to_savings", buy_rex_to_savings) ); + } + fc::variant get_last_loan(bool cpu) { vector data; const auto& db = control->db(); From 981608a86e10a4eece39b69eb8dc11eb0a98f605 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Sat, 11 May 2024 11:52:21 +0200 Subject: [PATCH 08/10] update rex tests --- .../include/eosio.system/eosio.system.hpp | 1 + contracts/eosio.system/src/rex.cpp | 29 ++++++----- tests/eosio.system_rex_matured_tests.cpp | 49 ++++++++++++++++--- 3 files changed, 60 insertions(+), 19 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index 1906c1bc..b876bd10 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -1588,6 +1588,7 @@ namespace eosiosystem { void add_to_rex_return_pool( const asset& fee ); void process_rex_maturities( const rex_balance_table::const_iterator& bitr ); void process_sell_matured_rex( const name owner ); + void process_buy_rex_to_savings( const name owner, const asset rex ); void consolidate_rex_balance( const rex_balance_table::const_iterator& bitr, const asset& rex_in_sell_order ); int64_t read_rex_savings( const rex_balance_table::const_iterator& bitr ); diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 325541d2..3c89d1e7 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -64,12 +64,7 @@ namespace eosiosystem { runrex(2); update_rex_account( from, asset( 0, core_symbol() ), delta_rex_stake ); - // buying REX immediately moves to REX savings - // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 - const auto rex_maturity_state = _rexmaturity.get_or_default(); - if ( rex_maturity_state.buy_rex_to_savings ) { - mvtosavings( from, rex_received ); - } + process_buy_rex_to_savings( from, rex_received ); process_sell_matured_rex( from ); // dummy action added so that amount of REX tokens purchased shows up in action trace @@ -112,12 +107,7 @@ namespace eosiosystem { runrex(2); update_rex_account( owner, asset( 0, core_symbol() ), rex_stake_delta - payment, true ); - // unstake to REX immediately moves to REX savings - // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 - const auto rex_maturity_state = _rexmaturity.get_or_default(); - if ( rex_maturity_state.buy_rex_to_savings ) { - mvtosavings( owner, rex_received ); - } + process_buy_rex_to_savings( owner, rex_received ); process_sell_matured_rex( owner ); // dummy action added so that amount of REX tokens purchased shows up in action trace @@ -1025,6 +1015,21 @@ namespace eosiosystem { } } + /** + * @brief Move new REX tokens to savings + * https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 + * + * @param owner - owner account name + * @param rex - amount of REX tokens to be moved to savings + */ + void system_contract::process_buy_rex_to_savings( const name owner, const asset rex ) + { + const auto rex_maturity_state = _rexmaturity.get_or_default(); + if ( rex_maturity_state.buy_rex_to_savings && rex.amount > 0 ) { + mvtosavings( owner, rex ); + } + } + /** * @brief Consolidates REX maturity buckets into one * diff --git a/tests/eosio.system_rex_matured_tests.cpp b/tests/eosio.system_rex_matured_tests.cpp index 1a753561..cc6320cc 100644 --- a/tests/eosio.system_rex_matured_tests.cpp +++ b/tests/eosio.system_rex_matured_tests.cpp @@ -17,18 +17,18 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/132 // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/134 // https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, true ) ); + // setup accounts const int64_t ratio = 10000; const asset init_rent = core_sym::from_string("20000.0000"); const asset init_balance = core_sym::from_string("1000.0000"); - const std::vector accounts = { "alice"_n, "bob"_n }; - account_name alice = accounts[0], bob = accounts[1]; + const std::vector accounts = { "alice"_n, "bob"_n, "charly"_n, "david"_n }; + account_name alice = accounts[0], bob = accounts[1], charly = accounts[2], david = accounts[3]; setup_rex_accounts( accounts, init_balance ); + // 1. set `num_of_maturity_buckets=21` to test increasing maturity time of buying REX tokens. + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, false, false ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( alice, core_sym::from_string("2.5000") ) ); - BOOST_REQUIRE_EQUAL( success(), mvfrsavings( alice, asset::from_string("25000.0000 REX") ) ); - produce_blocks(2); produce_block(fc::days(5)); BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( alice, asset::from_string("25000.0000 REX") ) ); @@ -48,12 +48,47 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( init_rent, rex_pool["total_rent"].as() ); BOOST_REQUIRE_EQUAL( get_rex_balance(alice), rex_pool["total_rex"].as() ); - // set `buy_rex_to_savings=false` to test buying REX without moving it to REX savings + // 2. set `buy_rex_to_savings=false` to test buying REX without moving it to REX savings BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, false ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( bob, core_sym::from_string("2.5000") ) ); produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( bob, asset::from_string("25000.0000 REX") ) ); + produce_block(fc::days(16)); + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_rex_vote_stake( bob ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( bob, core_sym::from_string("1.0000") ) ); // will also triggers sell matured REX + BOOST_REQUIRE_EQUAL( core_sym::from_string("1.0000"), get_rex_vote_stake( bob ) ); + + // 3. set `sell_matured_rex=false` to test selling matured REX + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, false, true ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( charly, core_sym::from_string("2.5000") ) ); // when buying REX, it will automatically be moved to REX savings + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( charly, asset::from_string("25000.0000 REX") ) ); // move REX from savings to initiate matured REX unstaking process + produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( charly, asset::from_string("25000.0000 REX") ) ); + produce_block(fc::days(16)); + BOOST_REQUIRE_EQUAL( success(), updaterex( charly ) ); // triggers sell matured REX (any REX action causes sell matured REX) + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( charly, asset::from_string("25000.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), get_rex_vote_stake( charly ) ); + BOOST_REQUIRE_EQUAL( init_balance, get_rex_fund( charly ) ); + + // 4. legacy holders with matured REX + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 5, false, false ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( david, core_sym::from_string("2.5000") ) ); // legacy 5 days maturity + produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, true ) ); + BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( david, core_sym::from_string("1.0000") ) ); // new 21 days maturity & triggers sell matured REX + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( david, asset::from_string("10000.0000 REX") ) ); // must move REX from savings to initiate matured REX unstaking process + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( david, asset::from_string("25000.0000 REX") ) ); // already sold when previously buying REX + produce_blocks(2); + produce_block(fc::days(5)); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( david, asset::from_string("10000.0000 REX") ) ); // 21 day REX not matured yet + produce_blocks(2); produce_block(fc::days(21)); - BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( bob, asset::from_string("25000.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("1.0000"), get_sellrex_result( david, asset::from_string("10000.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), get_rex_vote_stake( david ) ); + BOOST_REQUIRE_EQUAL( init_balance, get_rex_fund( david ) ); } FC_LOG_AND_RETHROW() From 2e865e5729a576c3df78f897e953849d5b2d657d Mon Sep 17 00:00:00 2001 From: Nathan James Date: Thu, 13 Jun 2024 12:39:03 +0100 Subject: [PATCH 09/10] rex2 docs update, ricardians, tests, rename maturity setter --- .../include/eosio.system/eosio.system.hpp | 12 +++--- .../ricardian/eosio.system.contracts.md.in | 31 +++++++++++++- contracts/eosio.system/src/rex.cpp | 4 +- tests/eosio.system_rex_matured_tests.cpp | 42 ++++++++++++------- tests/eosio.system_tester.hpp | 11 ++--- 5 files changed, 70 insertions(+), 30 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index e3c99d8d..52056b01 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -933,7 +933,7 @@ namespace eosiosystem { * * @post User votes are updated following this action. * @post Tokens used in purchase are added to user's voting power. - * @post Bought REX cannot be sold before 4 days counting from end of day of purchase. + * @post Bought REX cannot be sold before {num_of_maturity_buckets} days counting from end of day of purchase. */ [[eosio::action]] void buyrex( const name& from, const asset& amount ); @@ -949,7 +949,7 @@ namespace eosiosystem { * * @post User votes are updated following this action. * @post Tokens used in purchase are added to user's voting power. - * @post Bought REX cannot be sold before 4 days counting from end of day of purchase. + * @post Bought REX cannot be sold before {num_of_maturity_buckets} days counting from end of day of purchase. */ [[eosio::action]] void unstaketorex( const name& owner, const name& receiver, const asset& from_net, const asset& from_cpu ); @@ -1078,7 +1078,7 @@ namespace eosiosystem { void rexexec( const name& user, uint16_t max ); /** - * Consolidate action, consolidates REX maturity buckets into one bucket that can be sold after 4 days + * Consolidate action, consolidates REX maturity buckets into one bucket that can be sold after {num_of_maturity_buckets} days * starting from the end of the day. * * @param owner - REX owner account name. @@ -1090,7 +1090,7 @@ namespace eosiosystem { * Mvtosavings action, moves a specified amount of REX into savings bucket. REX savings bucket * never matures. In order for it to be sold, it has to be moved explicitly * out of that bucket. Then the moved amount will have the regular maturity - * period of 4 days starting from the end of the day. + * period of {num_of_maturity_buckets} days starting from the end of the day. * * @param owner - REX owner account name. * @param rex - amount of REX to be moved. @@ -1100,7 +1100,7 @@ namespace eosiosystem { /** * Mvfrsavings action, moves a specified amount of REX out of savings bucket. The moved amount - * will have the regular REX maturity period of 4 days. + * will have the regular REX maturity period of {num_of_maturity_buckets} days. * * @param owner - REX owner account name. * @param rex - amount of REX to be moved. @@ -1132,7 +1132,7 @@ namespace eosiosystem { * https://github.com/eosnetworkfoundation/eos-system-contracts/issues/135 */ [[eosio::action]] - void rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ); + void setrexmature(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ); /** * Donatetorex action, donates funds to REX, increases REX pool return buckets diff --git a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in index dd5c7101..396f4be9 100644 --- a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in +++ b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in @@ -413,6 +413,27 @@ icon: @ICON_BASE_URL@/@REX_ICON_URI@ Performs REX maintenance by processing a maximum of {{max}} REX sell orders and expired loans. Any account can execute this action. +

setrexmature

+ +--- +spec_version: "0.2.0" +title: Set REX Maturity Settings +summary: 'Sets the options for REX maturity buckets' +icon: @ICON_BASE_URL@/@REX_ICON_URI@ +--- + +{{#if num_of_maturity_buckets}} + Sets the numbers of maturity buckets to '{{num_of_maturity_buckets}}' +{{/if}} + +{{#if sell_matured_rex}} + Sets whether or not to immediately sell matured REX to '{{sell_matured_rex}}' +{{/if}} + +{{#if buy_rex_to_savings}} + Sets whether or not to immediately move purchased REX to savings to '{{buy_rex_to_savings}}' +{{/if}} +

rmvproducer

--- @@ -489,9 +510,15 @@ summary: '{{nowrap from}} sells {{nowrap rex}} tokens' icon: @ICON_BASE_URL@/@REX_ICON_URI@ --- -{{from}} initiates a sell order to sell {{rex}} tokens at the market exchange rate during the time at which the order is ultimately executed. If {{from}} already has an open sell order in the sell queue, {{rex}} will be added to the amount of the sell order without change the position of the sell order within the queue. Once the sell order is executed, proceeds are added to {{from}}’s REX fund, the value of sold REX tokens is deducted from {{from}}’s vote stake, and votes are updated accordingly. +The 'rex' parameter no longer has an effect. + +{{from}} initiates a sell order to sell all of their matured REX tokens at the market exchange rate during the time at which the order is ultimately executed. +If {{from}} already has an open sell order in the sell queue, {{rex}} will be added to the amount of the sell order without change the position of the sell order within the queue. +Once the sell order is executed, proceeds are added to {{from}}’s REX fund, the value of sold REX tokens is deducted from {{from}}’s vote stake, and votes are updated accordingly. -Depending on the market conditions, it may not be possible to fill the entire sell order immediately. In such a case, the sell order is added to the back of a sell queue. A sell order at the front of the sell queue will automatically be executed when the market conditions allow for the entire order to be filled. Regardless of the market conditions, the system is designed to execute this sell order within 30 days. {{from}} can cancel the order at any time before it is filled using the cnclrexorder action. +Depending on the market conditions, it may not be possible to fill the entire sell order immediately. In such a case, the sell order is added to the back of a sell queue. +A sell order at the front of the sell queue will automatically be executed when the market conditions allow for the entire order to be filled. Regardless of the market conditions, +the system is designed to execute this sell order within 30 days. {{from}} can cancel the order at any time before it is filled using the cnclrexorder action.

setabi

diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 79daea25..43bbac5f 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -8,7 +8,7 @@ namespace eosiosystem { using eosio::token; using eosio::seconds; - void system_contract::rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) + void system_contract::setrexmature(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) { require_auth(get_self()); @@ -945,7 +945,7 @@ namespace eosiosystem { } /** - * @brief Calculates maturity time of purchased REX tokens which is 4 days from end + * @brief Calculates maturity time of purchased REX tokens which is {num_of_maturity_buckets} days from end * of the day UTC * * @return time_point_sec diff --git a/tests/eosio.system_rex_matured_tests.cpp b/tests/eosio.system_rex_matured_tests.cpp index cc6320cc..e9e5702b 100644 --- a/tests/eosio.system_rex_matured_tests.cpp +++ b/tests/eosio.system_rex_matured_tests.cpp @@ -22,12 +22,13 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { const int64_t ratio = 10000; const asset init_rent = core_sym::from_string("20000.0000"); const asset init_balance = core_sym::from_string("1000.0000"); - const std::vector accounts = { "alice"_n, "bob"_n, "charly"_n, "david"_n }; - account_name alice = accounts[0], bob = accounts[1], charly = accounts[2], david = accounts[3]; + const std::vector accounts = { "alice"_n, "bob"_n, "charly"_n, "david"_n, "mark"_n }; + account_name alice = accounts[0], bob = accounts[1], charly = accounts[2], david = accounts[3], mark = accounts[4]; setup_rex_accounts( accounts, init_balance ); + // 1. set `num_of_maturity_buckets=21` to test increasing maturity time of buying REX tokens. - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, false, false ) ); + BOOST_REQUIRE_EQUAL( success(), setrexmature( 21, false, false ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( alice, core_sym::from_string("2.5000") ) ); produce_blocks(2); produce_block(fc::days(5)); @@ -42,14 +43,14 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( core_sym::from_string("970.0000"), get_rex_fund(alice) ); BOOST_REQUIRE_EQUAL( get_rex_balance(alice).get_amount(), ratio * asset::from_string("30.0000 REX").get_amount() ); auto rex_pool = get_rex_pool(); - BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_lendable"].as() ); - BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_unlent"].as() ); - BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); - BOOST_REQUIRE_EQUAL( init_rent, rex_pool["total_rent"].as() ); - BOOST_REQUIRE_EQUAL( get_rex_balance(alice), rex_pool["total_rex"].as() ); - - // 2. set `buy_rex_to_savings=false` to test buying REX without moving it to REX savings - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, false ) ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("30.0000"), rex_pool["total_unlent"].as() ); + BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), rex_pool["total_lent"].as() ); + BOOST_REQUIRE_EQUAL( init_rent, rex_pool["total_rent"].as() ); + BOOST_REQUIRE_EQUAL( get_rex_balance(alice), rex_pool["total_rex"].as() ); + + // 2. set `sell_matured_rex=true` and `buy_rex_to_savings=false` to test buying REX without moving it to REX savings + BOOST_REQUIRE_EQUAL( success(), setrexmature( 21, true, false ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( bob, core_sym::from_string("2.5000") ) ); produce_blocks(2); produce_block(fc::days(5)); @@ -59,8 +60,8 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( bob, core_sym::from_string("1.0000") ) ); // will also triggers sell matured REX BOOST_REQUIRE_EQUAL( core_sym::from_string("1.0000"), get_rex_vote_stake( bob ) ); - // 3. set `sell_matured_rex=false` to test selling matured REX - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, false, true ) ); + // 3. set `sell_matured_rex=false` and `buy_rex_to_savings=true` to test selling matured REX + BOOST_REQUIRE_EQUAL( success(), setrexmature( 21, false, true ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( charly, core_sym::from_string("2.5000") ) ); // when buying REX, it will automatically be moved to REX savings BOOST_REQUIRE_EQUAL( success(), mvfrsavings( charly, asset::from_string("25000.0000 REX") ) ); // move REX from savings to initiate matured REX unstaking process produce_blocks(2); @@ -73,12 +74,21 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( init_balance, get_rex_fund( charly ) ); // 4. legacy holders with matured REX - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 5, false, false ) ); + BOOST_REQUIRE_EQUAL( success(), setrexmature( 5, false, false ) ); BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( david, core_sym::from_string("2.5000") ) ); // legacy 5 days maturity + BOOST_REQUIRE_EQUAL( asset::from_string("25000.0000 REX"), get_buyrex_result( mark, core_sym::from_string("2.5000") ) ); produce_blocks(2); produce_block(fc::days(5)); - BOOST_REQUIRE_EQUAL( success(), rexmaturity( 21, true, true ) ); + BOOST_REQUIRE_EQUAL( success(), setrexmature( 21, true, true ) ); BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( david, core_sym::from_string("1.0000") ) ); // new 21 days maturity & triggers sell matured REX + + // 4.1. Test selling less than all their matured rex, and having all of their already matured rex sold regardless + BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( mark, asset::from_string("1.0000 REX") ) ); + BOOST_REQUIRE_EQUAL( get_rex_balance_obj( mark )["vote_stake"].as(), core_sym::from_string("0.0000") ); + BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( mark, core_sym::from_string("1.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( mark, asset::from_string("10000.0000 REX") ) ); + // BOOST_REQUIRE_EQUAL( get_rex_balance_obj( mark )["rex_maturities"].get_array()[0].as(), get_rex_balance_obj( mark )["matured_rex"].as() ); + BOOST_REQUIRE_EQUAL( success(), mvfrsavings( david, asset::from_string("10000.0000 REX") ) ); // must move REX from savings to initiate matured REX unstaking process BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( david, asset::from_string("25000.0000 REX") ) ); // already sold when previously buying REX produce_blocks(2); @@ -90,6 +100,8 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( core_sym::from_string("0.0000"), get_rex_vote_stake( david ) ); BOOST_REQUIRE_EQUAL( init_balance, get_rex_fund( david ) ); + + } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index afe27c72..7b6ca8df 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -796,13 +796,14 @@ class eosio_system_tester : public TESTER { asset get_sellrex_result( const account_name& from, const asset& rex ) { auto trace = base_tester::push_action( config::system_account_name, "sellrex"_n, from, mvo()("from", from)("rex", rex) ); - asset proceeds; + asset proceeds = core_sym::from_string("0.0000"); for ( size_t i = 0; i < trace->action_traces.size(); ++i ) { if ( trace->action_traces[i].act.name == "sellresult"_n ) { + asset _action_proceeds; fc::raw::unpack( trace->action_traces[i].act.data.data(), trace->action_traces[i].act.data.size(), - proceeds ); - return proceeds; + _action_proceeds ); + proceeds += _action_proceeds; } } return proceeds; @@ -930,8 +931,8 @@ class eosio_system_tester : public TESTER { return push_action( name(owner), "closerex"_n, mvo()("owner", owner) ); } - action_result rexmaturity(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) { - return push_action( "eosio"_n, "rexmaturity"_n, mvo()("num_of_maturity_buckets", num_of_maturity_buckets)("sell_matured_rex", sell_matured_rex)("buy_rex_to_savings", buy_rex_to_savings) ); + action_result setrexmature(const std::optional num_of_maturity_buckets, const std::optional sell_matured_rex, const std::optional buy_rex_to_savings ) { + return push_action( "eosio"_n, "setrexmature"_n, mvo()("num_of_maturity_buckets", num_of_maturity_buckets)("sell_matured_rex", sell_matured_rex)("buy_rex_to_savings", buy_rex_to_savings) ); } action_result donatetorex( const account_name& payer, const asset& quantity, const std::string& memo ) { From 126d5f82caa05f8f54abe2f507befb473b467358 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Thu, 13 Jun 2024 12:41:44 +0100 Subject: [PATCH 10/10] remove unecessary tests --- tests/eosio.system_rex_matured_tests.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/eosio.system_rex_matured_tests.cpp b/tests/eosio.system_rex_matured_tests.cpp index e9e5702b..c7699bcd 100644 --- a/tests/eosio.system_rex_matured_tests.cpp +++ b/tests/eosio.system_rex_matured_tests.cpp @@ -84,10 +84,6 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_matured_rex, eosio_system_tester ) try { // 4.1. Test selling less than all their matured rex, and having all of their already matured rex sold regardless BOOST_REQUIRE_EQUAL( core_sym::from_string("2.5000"), get_sellrex_result( mark, asset::from_string("1.0000 REX") ) ); - BOOST_REQUIRE_EQUAL( get_rex_balance_obj( mark )["vote_stake"].as(), core_sym::from_string("0.0000") ); - BOOST_REQUIRE_EQUAL( asset::from_string("10000.0000 REX"), get_buyrex_result( mark, core_sym::from_string("1.0000") ) ); - BOOST_REQUIRE_EQUAL( success(), mvfrsavings( mark, asset::from_string("10000.0000 REX") ) ); - // BOOST_REQUIRE_EQUAL( get_rex_balance_obj( mark )["rex_maturities"].get_array()[0].as(), get_rex_balance_obj( mark )["matured_rex"].as() ); BOOST_REQUIRE_EQUAL( success(), mvfrsavings( david, asset::from_string("10000.0000 REX") ) ); // must move REX from savings to initiate matured REX unstaking process BOOST_REQUIRE_EQUAL( wasm_assert_msg("insufficient available rex"), sellrex( david, asset::from_string("25000.0000 REX") ) ); // already sold when previously buying REX