diff --git a/CMakeLists.txt b/CMakeLists.txt index e21c73d0c..5783e8319 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ project(eosio_contracts) set(VERSION_MAJOR 1) set(VERSION_MINOR 8) -set(VERSION_PATCH 2) +set(VERSION_PATCH 3) #set(VERSION_SUFFIX develop) if (VERSION_SUFFIX) diff --git a/README.md b/README.md index b98bf5db5..34fb56efd 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # eosio.contracts -## Version : 1.8.2 +## Version : 1.8.3 The design of the EOSIO blockchain calls for a number of smart contracts that are run at a privileged permission level in order to support functions such as block producer registration and voting, token staking for CPU and network bandwidth, RAM purchasing, multi-sig, etc. These smart contracts are referred to as the bios, system, msig, wrap (formerly known as sudo) and token contracts. diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index a952edd98..3cfce3f42 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -59,8 +59,10 @@ namespace eosiosystem { static constexpr uint32_t seconds_per_year = 52 * 7 * 24 * 3600; static constexpr uint32_t seconds_per_day = 24 * 3600; + static constexpr uint32_t seconds_per_hour = 3600; static constexpr int64_t useconds_per_year = int64_t(seconds_per_year) * 1000'000ll; static constexpr int64_t useconds_per_day = int64_t(seconds_per_day) * 1000'000ll; + static constexpr int64_t useconds_per_hour = int64_t(seconds_per_hour) * 1000'000ll; static constexpr uint32_t blocks_per_day = 2 * seconds_per_day; // half seconds per day static constexpr int64_t min_activated_stake = 150'000'000'0000; @@ -333,6 +335,45 @@ namespace eosiosystem { typedef eosio::multi_index< "rexpool"_n, rex_pool > rex_pool_table; + // `rex_return_pool` structure underlying the rex return pool table. A rex return pool table entry is defined by: + // - `version` defaulted to zero, + // - `last_dist_time` the last time proceeds from renting, ram fees, and name bids were added to the rex pool, + // - `pending_bucket_time` timestamp of the pending 12-hour return bucket, + // - `oldest_bucket_time` cached timestamp of the oldest 12-hour return bucket, + // - `pending_bucket_proceeds` proceeds in the pending 12-hour return bucket, + // - `current_rate_of_increase` the current rate per dist_interval at which proceeds are added to the rex pool, + // - `proceeds` the maximum amount of proceeds that can be added to the rex pool at any given time + struct [[eosio::table,eosio::contract("eosio.system")]] rex_return_pool { + uint8_t version = 0; + time_point_sec last_dist_time; + time_point_sec pending_bucket_time = time_point_sec::maximum(); + time_point_sec oldest_bucket_time = time_point_sec::min(); + int64_t pending_bucket_proceeds = 0; + int64_t current_rate_of_increase = 0; + int64_t proceeds = 0; + + static constexpr uint32_t total_intervals = 30 * 144; // 30 days + static constexpr uint32_t dist_interval = 10 * 60; // 10 minutes + static constexpr uint8_t hours_per_bucket = 12; + static_assert( total_intervals * dist_interval == 30 * seconds_per_day ); + + uint64_t primary_key()const { return 0; } + }; + + typedef eosio::multi_index< "rexretpool"_n, rex_return_pool > rex_return_pool_table; + + // `rex_return_buckets` structure underlying the rex return buckets table. A rex return buckets table is defined by: + // - `version` defaulted to zero, + // - `return_buckets` buckets of proceeds accumulated in 12-hour intervals + struct [[eosio::table,eosio::contract("eosio.system")]] rex_return_buckets { + uint8_t version = 0; + std::map return_buckets; + + uint64_t primary_key()const { return 0; } + }; + + typedef eosio::multi_index< "retbuckets"_n, rex_return_buckets > rex_return_buckets_table; + // `rex_fund` structure underlying the rex fund table. A rex fund table entry is defined by: // - `version` defaulted to zero, // - `owner` the owner of the rex fund, @@ -430,22 +471,24 @@ namespace eosiosystem { class [[eosio::contract("eosio.system")]] system_contract : public native { private: - voters_table _voters; - producers_table _producers; - producers_table2 _producers2; - global_state_singleton _global; - global_state2_singleton _global2; - global_state3_singleton _global3; - global_state4_singleton _global4; - eosio_global_state _gstate; - eosio_global_state2 _gstate2; - eosio_global_state3 _gstate3; - eosio_global_state4 _gstate4; - rammarket _rammarket; - rex_pool_table _rexpool; - rex_fund_table _rexfunds; - rex_balance_table _rexbalance; - rex_order_table _rexorders; + voters_table _voters; + producers_table _producers; + producers_table2 _producers2; + global_state_singleton _global; + global_state2_singleton _global2; + global_state3_singleton _global3; + global_state4_singleton _global4; + eosio_global_state _gstate; + eosio_global_state2 _gstate2; + eosio_global_state3 _gstate3; + eosio_global_state4 _gstate4; + rammarket _rammarket; + rex_pool_table _rexpool; + rex_return_pool_table _rexretpool; + rex_return_buckets_table _rexretbuckets; + rex_fund_table _rexfunds; + rex_balance_table _rexbalance; + rex_order_table _rexorders; public: static constexpr eosio::name active_permission{"active"_n}; @@ -1121,6 +1164,7 @@ namespace eosiosystem { // defined in rex.cpp 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; @@ -1142,6 +1186,7 @@ namespace eosiosystem { static time_point_sec get_rex_maturity(); 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 ); void process_rex_maturities( const rex_balance_table::const_iterator& bitr ); void consolidate_rex_balance( const rex_balance_table::const_iterator& bitr, const asset& rex_in_sell_order ); diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index b05c5f011..202210c7d 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -26,11 +26,12 @@ namespace eosiosystem { _global4(get_self(), get_self().value), _rammarket(get_self(), get_self().value), _rexpool(get_self(), get_self().value), + _rexretpool(get_self(), get_self().value), + _rexretbuckets(get_self(), get_self().value), _rexfunds(get_self(), get_self().value), _rexbalance(get_self(), get_self().value), _rexorders(get_self(), get_self().value) { - //print( "construct system\n" ); _gstate = _global.exists() ? _global.get() : get_default_parameters(); _gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{}; _gstate3 = _global3.exists() ? _global3.get() : eosio_global_state3{}; diff --git a/contracts/eosio.system/src/rex.cpp b/contracts/eosio.system/src/rex.cpp index 4a4596e25..8e9d881f3 100644 --- a/contracts/eosio.system/src/rex.cpp +++ b/contracts/eosio.system/src/rex.cpp @@ -6,6 +6,7 @@ namespace eosiosystem { using eosio::current_time_point; using eosio::token; + using eosio::seconds; void system_contract::deposit( const name& owner, const asset& amount ) { @@ -452,15 +453,13 @@ namespace eosiosystem { */ void system_contract::add_loan_to_rex_pool( const asset& payment, int64_t rented_tokens, bool new_loan ) { + add_to_rex_return_pool( payment ); _rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& rt ) { // add payment to total_rent rt.total_rent.amount += payment.amount; // move rented_tokens from total_unlent to total_lent rt.total_unlent.amount -= rented_tokens; rt.total_lent.amount += rented_tokens; - // add payment to total_unlent - rt.total_unlent.amount += payment.amount; - rt.total_lendable.amount = rt.total_unlent.amount + rt.total_lent.amount; // increment loan_num if a new loan is being created if ( new_loan ) { rt.loan_num++; @@ -513,6 +512,8 @@ namespace eosiosystem { { check( rex_system_initialized(), "rex system not initialized yet" ); + update_rex_pool(); + const auto& pool = _rexpool.begin(); auto process_expired_loan = [&]( auto& idx, const auto& itr ) -> std::pair { @@ -616,6 +617,108 @@ namespace eosiosystem { } + /** + * @brief Adds returns from the REX return pool to the REX pool + */ + void system_contract::update_rex_pool() + { + auto get_elapsed_intervals = [&]( const time_point_sec& t1, const time_point_sec& t0 ) -> uint32_t { + return ( t1.sec_since_epoch() - t0.sec_since_epoch() ) / rex_return_pool::dist_interval; + }; + + const time_point_sec ct = current_time_point(); + const uint32_t cts = ct.sec_since_epoch(); + const time_point_sec effective_time{cts - cts % rex_return_pool::dist_interval}; + + const auto ret_pool_elem = _rexretpool.begin(); + const auto ret_buckets_elem = _rexretbuckets.begin(); + + if ( ret_pool_elem == _rexretpool.end() || effective_time <= ret_pool_elem->last_dist_time ) { + return; + } + + const int64_t current_rate = ret_pool_elem->current_rate_of_increase; + const uint32_t elapsed_intervals = get_elapsed_intervals( effective_time, ret_pool_elem->last_dist_time ); + int64_t change_estimate = current_rate * elapsed_intervals; + + { + const bool new_return_bucket = ret_pool_elem->pending_bucket_time <= effective_time; + int64_t new_bucket_rate = 0; + time_point_sec new_bucket_time = time_point_sec::min(); + _rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) { + if ( new_return_bucket ) { + int64_t remainder = rp.pending_bucket_proceeds % rex_return_pool::total_intervals; + new_bucket_rate = ( rp.pending_bucket_proceeds - remainder ) / rex_return_pool::total_intervals; + new_bucket_time = rp.pending_bucket_time; + rp.current_rate_of_increase += new_bucket_rate; + change_estimate += remainder + new_bucket_rate * get_elapsed_intervals( effective_time, rp.pending_bucket_time ); + rp.pending_bucket_proceeds = 0; + rp.pending_bucket_time = time_point_sec::maximum(); + if ( new_bucket_time < rp.oldest_bucket_time ) { + rp.oldest_bucket_time = new_bucket_time; + } + } + rp.proceeds -= change_estimate; + rp.last_dist_time = effective_time; + }); + + if ( new_return_bucket ) { + _rexretbuckets.modify( ret_buckets_elem, same_payer, [&]( auto& rb ) { + rb.return_buckets[new_bucket_time] = new_bucket_rate; + }); + } + } + + const time_point_sec time_threshold = effective_time - seconds(rex_return_pool::total_intervals * rex_return_pool::dist_interval); + if ( ret_pool_elem->oldest_bucket_time <= time_threshold ) { + int64_t expired_rate = 0; + int64_t surplus = 0; + _rexretbuckets.modify( ret_buckets_elem, same_payer, [&]( auto& rb ) { + auto& return_buckets = rb.return_buckets; + auto iter = return_buckets.begin(); + while ( iter != return_buckets.end() && iter->first <= time_threshold ) { + auto next = iter; + ++next; + const uint32_t overtime = get_elapsed_intervals( effective_time, + iter->first + seconds(rex_return_pool::total_intervals * rex_return_pool::dist_interval) ); + surplus += iter->second * overtime; + expired_rate += iter->second; + return_buckets.erase(iter); + iter = next; + } + }); + + _rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) { + if ( !ret_buckets_elem->return_buckets.empty() ) { + rp.oldest_bucket_time = ret_buckets_elem->return_buckets.begin()->first; + } else { + rp.oldest_bucket_time = time_point_sec::min(); + } + if ( expired_rate > 0) { + rp.current_rate_of_increase -= expired_rate; + } + if ( surplus > 0 ) { + change_estimate -= surplus; + rp.proceeds += surplus; + } + }); + } + + if ( change_estimate > 0 && ret_pool_elem->proceeds < 0 ) { + _rexretpool.modify( ret_pool_elem, same_payer, [&]( auto& rp ) { + change_estimate += rp.proceeds; + rp.proceeds = 0; + }); + } + + if ( change_estimate > 0 ) { + _rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& pool ) { + pool.total_unlent.amount += change_estimate; + pool.total_lendable = pool.total_unlent + pool.total_lent; + }); + } + } + template int64_t system_contract::rent_rex( T& table, const name& from, const name& receiver, const asset& payment, const asset& fund ) { @@ -678,7 +781,7 @@ namespace eosiosystem { asset stake_change( 0, core_symbol() ); bool success = false; - const int64_t unlent_lower_bound = ( uint128_t(2) * rexitr->total_lent.amount ) / 10; + const int64_t unlent_lower_bound = rexitr->total_lent.amount / 10; const int64_t available_unlent = rexitr->total_unlent.amount - unlent_lower_bound; // available_unlent <= 0 is possible if ( proceeds.amount <= available_unlent ) { const int64_t init_vote_stake_amount = bitr->vote_stake.amount; @@ -818,10 +921,7 @@ namespace eosiosystem { { #if CHANNEL_RAM_AND_NAMEBID_FEES_TO_REX if ( rex_available() ) { - _rexpool.modify( _rexpool.begin(), same_payer, [&]( auto& rp ) { - rp.total_unlent.amount += amount.amount; - rp.total_lendable.amount += amount.amount; - }); + add_to_rex_return_pool( amount ); // inline transfer to rex_account token::transfer_action transfer_act{ token_account, { from, active_permission } }; transfer_act.send( from, rex_account, amount, @@ -961,6 +1061,42 @@ namespace eosiosystem { return rex_received; } + /** + * @brief Adds an amount of core tokens to the REX return pool + * + * @param fee - amount to be added + */ + void system_contract::add_to_rex_return_pool( const asset& fee ) + { + update_rex_pool(); + if ( fee.amount <= 0 ) { + return; + } + + const time_point_sec ct = current_time_point(); + const uint32_t cts = ct.sec_since_epoch(); + const uint32_t bucket_interval = rex_return_pool::hours_per_bucket * seconds_per_hour; + const time_point_sec effective_time{cts - cts % bucket_interval + bucket_interval}; + const auto return_pool_elem = _rexretpool.begin(); + if ( return_pool_elem == _rexretpool.end() ) { + _rexretpool.emplace( get_self(), [&]( auto& rp ) { + rp.last_dist_time = effective_time; + rp.pending_bucket_proceeds = fee.amount; + rp.pending_bucket_time = effective_time; + rp.proceeds = fee.amount; + }); + _rexretbuckets.emplace( get_self(), [&]( auto& rb ) { } ); + } else { + _rexretpool.modify( return_pool_elem, same_payer, [&]( auto& rp ) { + rp.pending_bucket_proceeds += fee.amount; + rp.proceeds += fee.amount; + if ( rp.pending_bucket_time == time_point_sec::maximum() ) { + rp.pending_bucket_time = effective_time; + } + }); + } + } + /** * @brief Updates owner REX balance upon buying REX tokens * diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 179d4b0fa..809361e8e 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -638,6 +638,48 @@ class eosio_system_tester : public TESTER { return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_pool", data, abi_serializer_max_time ); } + fc::variant get_rex_return_pool() const { + vector data; + const auto& db = control->db(); + namespace chain = eosio::chain; + const auto* t_id = db.find( boost::make_tuple( config::system_account_name, config::system_account_name, N(rexretpool) ) ); + if ( !t_id ) { + return fc::variant(); + } + + const auto& idx = db.get_index(); + + auto itr = idx.lower_bound( boost::make_tuple( t_id->id, 0 ) ); + if ( itr == idx.end() || itr->t_id != t_id->id || 0 != itr->primary_key ) { + return fc::variant(); + } + + data.resize( itr->value.size() ); + memcpy( data.data(), itr->value.data(), data.size() ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_return_pool", data, abi_serializer_max_time ); + } + + fc::variant get_rex_return_buckets() const { + vector data; + const auto& db = control->db(); + namespace chain = eosio::chain; + const auto* t_id = db.find( boost::make_tuple( config::system_account_name, config::system_account_name, N(retbuckets) ) ); + if ( !t_id ) { + return fc::variant(); + } + + const auto& idx = db.get_index(); + + auto itr = idx.lower_bound( boost::make_tuple( t_id->id, 0 ) ); + if ( itr == idx.end() || itr->t_id != t_id->id || 0 != itr->primary_key ) { + return fc::variant(); + } + + data.resize( itr->value.size() ); + memcpy( data.data(), itr->value.data(), data.size() ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "rex_return_buckets", data, abi_serializer_max_time ); + } + void setup_rex_accounts( const std::vector& accounts, const asset& init_balance, const asset& net = core_sym::from_string("80.0000"), diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 1eed42e40..7021e927b 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -27,7 +27,8 @@ using namespace eosio_system; BOOST_AUTO_TEST_SUITE(eosio_system_tests) -bool within_one(int64_t a, int64_t b) { return std::abs(a - b) <= 1; } +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( buysell, eosio_system_tester ) try { @@ -3613,6 +3614,7 @@ BOOST_FIXTURE_TEST_CASE( rex_auth, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE( buy_sell_rex, eosio_system_tester ) try { const int64_t ratio = 10000; @@ -3850,13 +3852,15 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( ratio * init_tot_lendable.get_amount(), rex_pool["total_rex"].as().get_amount() ); BOOST_REQUIRE_EQUAL( rex_pool["total_rex"].as(), get_rex_balance(alice) ); + BOOST_REQUIRE( get_rex_return_pool().is_null() ); + { // bob rents cpu for carol const asset fee = core_sym::from_string("17.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, carol, fee ) ); BOOST_REQUIRE_EQUAL( init_balance - fee, get_rex_fund(bob) ); rex_pool = get_rex_pool(); - BOOST_REQUIRE_EQUAL( init_tot_lendable + fee, rex_pool["total_lendable"].as() ); // 65 + 17 + BOOST_REQUIRE_EQUAL( init_tot_lendable, rex_pool["total_lendable"].as() ); // 65 BOOST_REQUIRE_EQUAL( init_tot_rent + fee, rex_pool["total_rent"].as() ); // 100 + 17 int64_t expected_total_lent = bancor_convert( init_tot_rent.get_amount(), init_tot_unlent.get_amount(), fee.get_amount() ); BOOST_REQUIRE_EQUAL( expected_total_lent, @@ -3864,6 +3868,10 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( rex_pool["total_lent"].as() + rex_pool["total_unlent"].as(), rex_pool["total_lendable"].as() ); + auto rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE( !rex_return_pool.is_null() ); + BOOST_REQUIRE_EQUAL( 0, rex_return_pool["current_rate_of_increase"].as() ); + // test that carol's resource limits have been updated properly BOOST_REQUIRE_EQUAL( expected_total_lent, get_cpu_limit( carol ) - init_cpu_limit ); BOOST_REQUIRE_EQUAL( 0, get_net_limit( carol ) - init_net_limit ); @@ -3878,11 +3886,19 @@ BOOST_FIXTURE_TEST_CASE( buy_rent_rex, eosio_system_tester ) try { produce_block( fc::days(20) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance(alice) ) ); BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); + + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE( !rex_return_pool.is_null() ); + int64_t rate = fee.get_amount() / (30 * 144); + BOOST_REQUIRE_EQUAL( rate, rex_return_pool["current_rate_of_increase"].as() ); + produce_block( fc::days(10) ); // alice is finally able to sellrex, she gains the fee paid by bob BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance(alice) ) ); BOOST_REQUIRE_EQUAL( 0, get_rex_balance(alice).get_amount() ); - BOOST_REQUIRE_EQUAL( init_balance + fee, get_rex_fund(alice) ); + auto expected_rex_fund = (init_balance + fee).get_amount(); + auto actual_rex_fund = get_rex_fund(alice).get_amount(); + BOOST_REQUIRE_EQUAL( expected_rex_fund, actual_rex_fund ); // test that carol's resource limits have been updated properly when loan expires BOOST_REQUIRE_EQUAL( init_cpu_limit, get_cpu_limit( carol ) ); BOOST_REQUIRE_EQUAL( init_net_limit, get_net_limit( carol ) ); @@ -3940,7 +3956,7 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_sell_rex, eosio_system_tester ) try { const asset fee = core_sym::from_string("7.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, carol, fee ) ); rex_pool = get_rex_pool(); - BOOST_REQUIRE_EQUAL( init_tot_lendable + fee, rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( init_tot_lendable, rex_pool["total_lendable"].as() ); BOOST_REQUIRE_EQUAL( init_tot_rent + fee, rex_pool["total_rent"].as() ); produce_block( fc::days(5) ); @@ -3977,8 +3993,8 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { setup_rex_accounts( accounts, init_balance ); const auto purchase1 = core_sym::from_string("50000.0000"); - const auto purchase2 = core_sym::from_string("235500.0000"); - const auto purchase3 = core_sym::from_string("234500.0000"); + const auto purchase2 = core_sym::from_string("105500.0000"); + const auto purchase3 = core_sym::from_string("104500.0000"); const auto init_stake = get_voter_info(alice)["staked"].as(); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, purchase1) ); BOOST_REQUIRE_EQUAL( success(), buyrex( bob, purchase2) ); @@ -3997,27 +4013,26 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( core_sym::from_string("20000.0000"), get_rex_pool()["total_rent"].as() ); for (uint8_t i = 0; i < 4; ++i) { - BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, emily, core_sym::from_string("20000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, emily, core_sym::from_string("12000.0000") ) ); } - const asset rent_payment = core_sym::from_string("40000.0000"); + produce_block( fc::days(25) ); + const asset rent_payment = core_sym::from_string("46000.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( frank, frank, rent_payment, rent_payment ) ); + produce_block( fc::days(4) ); + + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); + const auto init_rex_pool = get_rex_pool(); const int64_t total_lendable = init_rex_pool["total_lendable"].as().get_amount(); const int64_t total_rex = init_rex_pool["total_rex"].as().get_amount(); const int64_t init_alice_rex_stake = ( eosio::chain::uint128_t(init_alice_rex.get_amount()) * total_lendable ) / total_rex; - produce_block( fc::days(5) ); - - BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( 3 * get_rex_balance(alice).get_amount() / 4, symbol(SY(4,REX)) ) ) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( 3 * get_rex_balance(alice).get_amount() / 4, symbol{SY(4,REX)} ) ) ); BOOST_TEST_REQUIRE( within_one( init_alice_rex.get_amount() / 4, get_rex_balance(alice).get_amount() ) ); - BOOST_TEST_REQUIRE( within_one( init_alice_rex_stake / 4, get_rex_vote_stake( alice ).get_amount() ) ); - BOOST_TEST_REQUIRE( within_one( init_alice_rex_stake / 4, get_voter_info(alice)["staked"].as() - init_stake ) ); - - produce_block( fc::days(5) ); init_alice_rex = get_rex_balance(alice); BOOST_REQUIRE_EQUAL( success(), sellrex( bob, get_rex_balance(bob) ) ); @@ -4039,8 +4054,8 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( init_carol_rex, get_rex_order(carol)["rex_requested"].as() ); BOOST_REQUIRE_EQUAL( 0, get_rex_order(carol)["proceeds"].as().get_amount() ); - // wait for 30 days minus 1 hour - produce_block( fc::hours(19*24 + 23) ); + // wait for a total of 30 days minus 1 hour + produce_block( fc::hours(23) ); BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); BOOST_REQUIRE_EQUAL( true, get_rex_order(alice)["is_open"].as() ); BOOST_REQUIRE_EQUAL( true, get_rex_order(bob)["is_open"].as() ); @@ -4063,21 +4078,28 @@ BOOST_FIXTURE_TEST_CASE( buy_sell_claim_rex, eosio_system_tester ) try { } { + BOOST_REQUIRE_EQUAL( false, get_rex_order(bob)["is_open"].as() ); + BOOST_REQUIRE_EQUAL( init_bob_rex, get_rex_order(bob)["rex_requested"].as() ); + BOOST_TEST_REQUIRE ( 0 < get_rex_order(bob)["proceeds"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( true, get_rex_order(alice)["is_open"].as() ); BOOST_REQUIRE_EQUAL( init_alice_rex, get_rex_order(alice)["rex_requested"].as() ); BOOST_REQUIRE_EQUAL( 0, get_rex_order(alice)["proceeds"].as().get_amount() ); - BOOST_REQUIRE_EQUAL( false, get_rex_order(bob)["is_open"].as() ); - BOOST_REQUIRE_EQUAL( init_bob_rex, get_rex_order(bob)["rex_requested"].as() ); - BOOST_REQUIRE ( 0 < get_rex_order(bob)["proceeds"].as().get_amount() ); - BOOST_REQUIRE_EQUAL( true, get_rex_order(carol)["is_open"].as() ); BOOST_REQUIRE_EQUAL( init_carol_rex, get_rex_order(carol)["rex_requested"].as() ); BOOST_REQUIRE_EQUAL( 0, get_rex_order(carol)["proceeds"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("rex loans are currently not available"), rentcpu( frank, frank, core_sym::from_string("1.0000") ) ); } + produce_blocks(2); + produce_block( fc::hours(13) ); + produce_blocks(2); + produce_block( fc::days(30) ); + produce_blocks(2); + { auto trace1 = base_tester::push_action( config::system_account_name, N(updaterex), bob, mvo()("owner", bob) ); auto trace2 = base_tester::push_action( config::system_account_name, N(updaterex), carol, mvo()("owner", carol) ); @@ -4205,7 +4227,8 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { } // wait for 30 days, frank's loan will be renewed at the current price - produce_block( fc::hours(30*24 + 1) ); + produce_block( fc::minutes(30*24*60 - 1) ); + BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); rex_pool = get_rex_pool(); { int64_t unlent_tokens = bancor_convert( rex_pool["total_unlent"].as().get_amount(), @@ -4217,6 +4240,8 @@ BOOST_FIXTURE_TEST_CASE( rex_loans, eosio_system_tester ) try { payment.get_amount() ); } + produce_block( fc::minutes(2) ); + BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset::from_string("1.0000 REX") ) ); loan_info = get_cpu_loan(1); @@ -4301,12 +4326,20 @@ BOOST_FIXTURE_TEST_CASE( ramfee_namebid_to_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( get_balance( N(eosio.rex) ), cur_rex_balance + core_sym::from_string("0.3500") ); cur_rex_balance = get_balance( N(eosio.rex) ); + + produce_blocks( 1 ); + produce_block( fc::hours(30*24 + 12) ); + produce_blocks( 1 ); + + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); auto cur_rex_pool = get_rex_pool(); - BOOST_REQUIRE_EQUAL( cur_rex_balance, cur_rex_pool["total_unlent"].as() ); - BOOST_REQUIRE_EQUAL( 0, cur_rex_pool["total_lent"].as().get_amount() ); - BOOST_REQUIRE_EQUAL( cur_rex_balance, cur_rex_pool["total_lendable"].as() ); - BOOST_REQUIRE_EQUAL( 0, cur_rex_pool["namebid_proceeds"].as().get_amount() ); + BOOST_TEST_REQUIRE( within_one( cur_rex_balance.get_amount(), cur_rex_pool["total_unlent"].as().get_amount() ) ); + BOOST_TEST_REQUIRE( cur_rex_balance >= cur_rex_pool["total_unlent"].as() ); + BOOST_REQUIRE_EQUAL( 0, cur_rex_pool["total_lent"].as().get_amount() ); + BOOST_TEST_REQUIRE( within_one( cur_rex_balance.get_amount(), cur_rex_pool["total_lendable"].as().get_amount() ) ); + BOOST_TEST_REQUIRE( cur_rex_balance >= cur_rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( 0, cur_rex_pool["namebid_proceeds"].as().get_amount() ); // required for closing namebids cross_15_percent_threshold(); @@ -4328,8 +4361,13 @@ BOOST_FIXTURE_TEST_CASE( ramfee_namebid_to_rex, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( 0, get_balance( N(eosio.names) ).get_amount() ); cur_rex_balance = get_balance( N(eosio.rex) ); + produce_block( fc::hours(30*24 + 13) ); + produce_blocks( 1 ); + + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); BOOST_REQUIRE_EQUAL( cur_rex_balance, get_rex_pool()["total_lendable"].as() ); - BOOST_REQUIRE_EQUAL( cur_rex_balance, get_rex_pool()["total_unlent"].as() ); + BOOST_REQUIRE_EQUAL( get_rex_pool()["total_lendable"].as(), + get_rex_pool()["total_unlent"].as() ); } FC_LOG_AND_RETHROW() @@ -4634,7 +4672,7 @@ BOOST_FIXTURE_TEST_CASE( rex_savings, eosio_system_tester ) try { produce_block( fc::days(5) ); - BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, core_sym::from_string("2000.0000") ) ); + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, core_sym::from_string("3000.0000") ) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( 9 * rex_bucket_amount / 10, rex_sym ) ) ); BOOST_REQUIRE_EQUAL( rex_bucket, get_rex_balance( alice ) ); BOOST_REQUIRE_EQUAL( success(), mvtosavings( alice, asset( rex_bucket_amount / 10, rex_sym ) ) ); @@ -4705,15 +4743,17 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to const int64_t init_stake = get_voter_info( alice )["staked"].as(); + // alice buys rex const asset payment = core_sym::from_string("25000.0000"); BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment ) ); BOOST_REQUIRE_EQUAL( payment, get_rex_vote_stake(alice) ); BOOST_REQUIRE_EQUAL( get_rex_vote_stake(alice).get_amount(), get_voter_info(alice)["staked"].as() - init_stake ); + // emily rents cpu const asset fee = core_sym::from_string("50.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, bob, fee ) ); BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); - BOOST_REQUIRE_EQUAL( payment + fee, get_rex_vote_stake(alice) ); + BOOST_REQUIRE_EQUAL( payment, get_rex_vote_stake(alice) ); BOOST_REQUIRE_EQUAL( get_rex_vote_stake(alice).get_amount(), get_voter_info( alice )["staked"].as() - init_stake ); // create accounts {defproducera, defproducerb, ..., defproducerz} and register as producers @@ -4743,27 +4783,28 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to == get_producer_info(producer_names[20])["total_votes"].as() ); BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); - produce_block( fc::days(20) ); + produce_block( fc::days(10) ); BOOST_TEST_REQUIRE( get_producer_info(producer_names[20])["total_votes"].as() < stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) ); + BOOST_REQUIRE_EQUAL( success(), updaterex( alice ) ); BOOST_TEST_REQUIRE( stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) == get_producer_info(producer_names[20])["total_votes"].as() ); + produce_block( fc::hours(19 * 24 + 23) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); const asset init_rex = get_rex_balance( alice ); const auto current_rex_pool = get_rex_pool(); const int64_t total_lendable = current_rex_pool["total_lendable"].as().get_amount(); const int64_t total_rex = current_rex_pool["total_rex"].as().get_amount(); const int64_t init_alice_rex_stake = ( eosio::chain::uint128_t(init_rex.get_amount()) * total_lendable ) / total_rex; - produce_block( fc::days(5) ); const asset rex_sell_amount( get_rex_balance(alice).get_amount() / 4, symbol( SY(4,REX) ) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, rex_sell_amount ) ); - BOOST_REQUIRE_EQUAL( init_rex, get_rex_balance( alice ) + rex_sell_amount ); - BOOST_REQUIRE_EQUAL( 3 * init_alice_rex_stake, 4 * get_rex_vote_stake( alice ).get_amount() ); + BOOST_REQUIRE_EQUAL( init_rex, get_rex_balance(alice) + rex_sell_amount ); + BOOST_REQUIRE_EQUAL( 3 * init_alice_rex_stake, 4 * get_rex_vote_stake(alice).get_amount() ); BOOST_REQUIRE_EQUAL( get_voter_info( alice )["staked"].as(), init_stake + get_rex_vote_stake(alice).get_amount() ); BOOST_TEST_REQUIRE( stake2votes( asset( get_voter_info( alice )["staked"].as(), symbol{CORE_SYM} ) ) == get_producer_info(producer_names[0])["total_votes"].as() ); - produce_block( fc::days(31) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, get_rex_balance( alice ) ) ); BOOST_REQUIRE_EQUAL( 0, get_rex_balance( alice ).get_amount() ); @@ -4774,7 +4815,7 @@ BOOST_FIXTURE_TEST_CASE( update_rex, eosio_system_tester, * boost::unit_test::to } FC_LOG_AND_RETHROW() -BOOST_FIXTURE_TEST_CASE( update_rex_vote, eosio_system_tester, * boost::unit_test::tolerance(1e-10) ) try { +BOOST_FIXTURE_TEST_CASE( update_rex_vote, eosio_system_tester, * boost::unit_test::tolerance(1e-8) ) try { cross_15_percent_threshold(); @@ -4809,20 +4850,23 @@ BOOST_FIXTURE_TEST_CASE( update_rex_vote, eosio_system_tester, * boost::unit_tes BOOST_REQUIRE_EQUAL( get_rex_vote_stake(alice).get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); BOOST_REQUIRE_EQUAL( purchase, get_rex_pool()["total_lendable"].as() ); - BOOST_REQUIRE_EQUAL( success(), - vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); + BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); BOOST_REQUIRE_EQUAL( purchase, get_rex_vote_stake(alice) ); BOOST_REQUIRE_EQUAL( purchase.get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); const auto init_rex_pool = get_rex_pool(); const asset rent = core_sym::from_string("25.0000"); BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, bob, rent ) ); + + produce_block( fc::days(31) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); const auto curr_rex_pool = get_rex_pool(); - BOOST_REQUIRE_EQUAL( curr_rex_pool["total_lendable"].as(), init_rex_pool["total_lendable"].as() + rent ); - BOOST_REQUIRE_EQUAL( success(), - vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); - BOOST_REQUIRE_EQUAL( (purchase + rent).get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ); - BOOST_REQUIRE_EQUAL( purchase + rent, get_rex_vote_stake(alice) ); + BOOST_TEST_REQUIRE( within_one( curr_rex_pool["total_lendable"].as().get_amount(), + init_rex_pool["total_lendable"].as().get_amount() + rent.get_amount() ) ); + + BOOST_REQUIRE_EQUAL( success(), vote( alice, std::vector(producer_names.begin(), producer_names.begin() + 21) ) ); + BOOST_TEST_REQUIRE( within_one( (purchase + rent).get_amount(), get_voter_info(alice)["staked"].as() - init_stake_amount ) ); + BOOST_TEST_REQUIRE( within_one( (purchase + rent).get_amount(), get_rex_vote_stake(alice).get_amount() ) ); BOOST_TEST_REQUIRE ( stake2votes(purchase + rent + init_stake) == get_producer_info(producer_names[0])["total_votes"].as_double() ); BOOST_TEST_REQUIRE ( stake2votes(purchase + rent + init_stake) == @@ -4832,14 +4876,18 @@ BOOST_FIXTURE_TEST_CASE( update_rex_vote, eosio_system_tester, * boost::unit_tes const asset to_cpu_stake = core_sym::from_string("40.0000"); transfer( config::system_account_name, alice, to_net_stake + to_cpu_stake, config::system_account_name ); BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, bob, rent ) ); + produce_block( fc::hours(30 * 24 + 13) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); BOOST_REQUIRE_EQUAL( success(), stake( alice, alice, to_net_stake, to_cpu_stake ) ); - BOOST_REQUIRE_EQUAL( purchase + rent + rent, get_rex_vote_stake(alice) ); + BOOST_REQUIRE_EQUAL( purchase + rent + rent, get_rex_vote_stake(alice) ); BOOST_TEST_REQUIRE ( stake2votes(init_stake + purchase + rent + rent + to_net_stake + to_cpu_stake) == get_producer_info(producer_names[0])["total_votes"].as_double() ); BOOST_REQUIRE_EQUAL( success(), rentcpu( emily, bob, rent ) ); + produce_block( fc::hours(30 * 24 + 13) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( alice, 1 ) ); BOOST_REQUIRE_EQUAL( success(), unstake( alice, alice, to_net_stake, to_cpu_stake ) ); BOOST_REQUIRE_EQUAL( purchase + rent + rent + rent, get_rex_vote_stake(alice) ); - BOOST_TEST_REQUIRE ( stake2votes(init_stake + purchase + rent + rent + rent) == + BOOST_TEST_REQUIRE ( stake2votes(init_stake + get_rex_vote_stake(alice) ) == get_producer_info(producer_names[0])["total_votes"].as_double() ); } FC_LOG_AND_RETHROW() @@ -4896,11 +4944,11 @@ BOOST_FIXTURE_TEST_CASE( rex_lower_bound, eosio_system_tester ) try { const int64_t tot_lent = rex_pool["total_lent"].as().get_amount(); const int64_t tot_lendable = rex_pool["total_lendable"].as().get_amount(); double rex_per_eos = double(tot_rex) / double(tot_lendable); - int64_t sell_amount = rex_per_eos * ( tot_unlent - 0.19 * tot_lent ); + int64_t sell_amount = rex_per_eos * ( tot_unlent - 0.09 * tot_lent ); produce_block( fc::days(5) ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( sell_amount, rex_sym ) ) ); BOOST_REQUIRE_EQUAL( success(), cancelrexorder( alice ) ); - sell_amount = rex_per_eos * ( tot_unlent - 0.2 * tot_lent ); + sell_amount = rex_per_eos * ( tot_unlent - 0.1 * tot_lent ); BOOST_REQUIRE_EQUAL( success(), sellrex( alice, asset( sell_amount, rex_sym ) ) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("no sellrex order is scheduled"), cancelrexorder( alice ) ); @@ -5105,6 +5153,170 @@ BOOST_FIXTURE_TEST_CASE( b1_vesting, eosio_system_tester ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( rex_return, eosio_system_tester ) try { + + constexpr uint32_t total_intervals = 30 * 144; + constexpr uint32_t dist_interval = 10 * 60; + BOOST_REQUIRE_EQUAL( true, get_rex_return_pool().is_null() ); + + const asset init_balance = core_sym::from_string("100000.0000"); + const std::vector accounts = { N(aliceaccount), N(bobbyaccount) }; + account_name alice = accounts[0], bob = accounts[1]; + setup_rex_accounts( accounts, init_balance ); + + const asset payment = core_sym::from_string("100000.0000"); + { + BOOST_REQUIRE_EQUAL( success(), buyrex( alice, payment ) ); + auto rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( payment, rex_pool["total_lendable"].as() ); + BOOST_REQUIRE_EQUAL( payment, rex_pool["total_unlent"].as() ); + + BOOST_REQUIRE_EQUAL( true, get_rex_return_pool().is_null() ); + } + + { + const asset fee = core_sym::from_string("30.0000"); + const uint32_t bucket_interval_sec = fc::hours(12).to_seconds(); + const uint32_t current_time_sec = control->pending_block_time().sec_since_epoch(); + const time_point_sec expected_pending_bucket_time{current_time_sec - current_time_sec % bucket_interval_sec + bucket_interval_sec}; + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, fee ) ); + auto rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( false, rex_return_pool.is_null() ); + BOOST_REQUIRE_EQUAL( 0, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + BOOST_REQUIRE_EQUAL( expected_pending_bucket_time.sec_since_epoch(), + rex_return_pool["pending_bucket_time"].as().sec_since_epoch() ); + int32_t t0 = rex_return_pool["pending_bucket_time"].as().sec_since_epoch(); + + produce_block( fc::hours(13) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + rex_return_pool = get_rex_return_pool(); + int64_t rate = fee.get_amount() / total_intervals; + BOOST_REQUIRE_EQUAL( rate, rex_return_pool["current_rate_of_increase"].as() ); + + int32_t t1 = rex_return_pool["last_dist_time"].as().sec_since_epoch(); + int64_t change = rate * ((t1-t0) / dist_interval) + fee.get_amount() % total_intervals; + int64_t expected = payment.get_amount() + change; + + auto rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( expected, rex_pool["total_lendable"].as().get_amount() ); + + produce_blocks( 1 ); + produce_block( fc::days(25) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( rate, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_REQUIRE_EQUAL( 1, get_rex_return_buckets()["return_buckets"].get_array().size() ); + int64_t t2 = rex_return_pool["last_dist_time"].as().sec_since_epoch(); + change = rate * ((t2-t0) / dist_interval) + fee.get_amount() % total_intervals; + expected = payment.get_amount() + change; + + rex_pool = get_rex_pool(); + BOOST_REQUIRE_EQUAL( expected, rex_pool["total_lendable"].as().get_amount() ); + + produce_blocks( 1 ); + produce_block( fc::days(5) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( 0, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + + rex_pool = get_rex_pool(); + expected = payment.get_amount() + fee.get_amount(); + BOOST_REQUIRE_EQUAL( expected, rex_pool["total_lendable"].as().get_amount() ); + BOOST_REQUIRE_EQUAL( rex_pool["total_lendable"].as(), + rex_pool["total_unlent"].as() ); + } + + produce_block( fc::hours(1) ); + + { + const asset init_lendable = get_rex_pool()["total_lendable"].as(); + const asset fee = core_sym::from_string("15.0000"); + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, fee ) ); + auto rex_return_pool = get_rex_return_pool(); + uint32_t t0 = rex_return_pool["last_dist_time"].as().sec_since_epoch(); + produce_block( fc::hours(1) ); + BOOST_REQUIRE_EQUAL( success(), rentnet( bob, bob, fee ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( 0, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + uint32_t t1 = rex_return_pool["last_dist_time"].as().sec_since_epoch(); + BOOST_REQUIRE_EQUAL( t1, t0 + 6 * dist_interval ); + + produce_block( fc::hours(12) ); + BOOST_REQUIRE_EQUAL( success(), rentnet( bob, bob, fee ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( 1, get_rex_return_buckets()["return_buckets"].get_array().size() ); + int64_t rate = 2 * fee.get_amount() / total_intervals; + BOOST_REQUIRE_EQUAL( rate, rex_return_pool["current_rate_of_increase"].as() ); + produce_block( fc::hours(8) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( rate, rex_return_pool["current_rate_of_increase"].as() ); + + produce_block( fc::days(30) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( fee.get_amount() / total_intervals, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_TEST_REQUIRE( (init_lendable + fee + fee).get_amount() < get_rex_pool()["total_lendable"].as().get_amount() ); + + produce_block( fc::days(1) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + rex_return_pool = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( 0, rex_return_pool["current_rate_of_increase"].as() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + BOOST_REQUIRE_EQUAL( init_lendable.get_amount() + 3 * fee.get_amount(), + get_rex_pool()["total_lendable"].as().get_amount() ); + } + + { + const asset fee = core_sym::from_string("25.0000"); + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, fee ) ); + produce_block( fc::hours(13) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + auto rex_pool_0 = get_rex_pool(); + auto rex_return_pool_0 = get_rex_return_pool(); + produce_block( fc::minutes(2) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + auto rex_pool_1 = get_rex_pool(); + auto rex_return_pool_1 = get_rex_return_pool(); + BOOST_REQUIRE_EQUAL( rex_return_pool_0["last_dist_time"].as().sec_since_epoch(), + rex_return_pool_1["last_dist_time"].as().sec_since_epoch() ); + BOOST_REQUIRE_EQUAL( rex_pool_0["total_lendable"].as(), + rex_pool_1["total_lendable"].as() ); + produce_block( fc::minutes(9) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + auto rex_pool_2 = get_rex_pool(); + auto rex_return_pool_2 = get_rex_return_pool(); + BOOST_TEST_REQUIRE( rex_return_pool_1["last_dist_time"].as().sec_since_epoch() < + rex_return_pool_2["last_dist_time"].as().sec_since_epoch() ); + BOOST_TEST_REQUIRE( rex_pool_1["total_lendable"].as().get_amount() < + rex_pool_2["total_lendable"].as().get_amount() ); + produce_block( fc::days(31) ); + produce_blocks( 1 ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_pool()["current_rate_of_increase"].as() ); + } + + { + const asset fee = core_sym::from_string("30.0000"); + for ( uint8_t i = 0; i < 5; ++i ) { + BOOST_REQUIRE_EQUAL( success(), rentcpu( bob, bob, fee ) ); + produce_block( fc::days(1) ); + } + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + BOOST_REQUIRE_EQUAL( 5, get_rex_return_buckets()["return_buckets"].get_array().size() ); + produce_block( fc::days(30) ); + BOOST_REQUIRE_EQUAL( success(), rexexec( bob, 1 ) ); + BOOST_REQUIRE_EQUAL( 0, get_rex_return_buckets()["return_buckets"].get_array().size() ); + } + +} FC_LOG_AND_RETHROW() + + BOOST_AUTO_TEST_CASE( setabi_bios ) try { validating_tester t( validating_tester::default_config() ); t.execute_setup_policy( setup_policy::preactivate_feature_only );