From d664b5a365dfb2181f5162034cbffb73cd98f722 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 23:09:41 +0200 Subject: [PATCH 01/13] implement unvest action ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/131 --- .../include/eosio.system/eosio.system.hpp | 14 +- .../eosio.system/src/delegate_bandwidth.cpp | 182 ++++++++++-------- tests/eosio.system_tester.hpp | 7 + tests/eosio.system_tests.cpp | 8 + 4 files changed, 132 insertions(+), 79 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..4cc766aa 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -1429,6 +1429,16 @@ namespace eosiosystem { [[eosio::action]] void setinflation( int64_t annual_rate, int64_t inflation_pay_factor, int64_t votepay_factor ); + /** + * Facilitates the removal of vested staked tokens from an account, ensuring that these tokens are reallocated to the system's pool. + * + * @param account - the target account from which tokens are to be unvested. + * @param unvest_net_quantity - the amount of NET tokens to unvest. + * @param unvest_cpu_quantity - the amount of CPU tokens to unvest. + */ + [[eosio::action]] + void unvest(const name account, const asset unvest_net_quantity, const asset unvest_cpu_quantity); + /** * Configure the `power` market. The market becomes available the first time this * action is invoked. @@ -1588,10 +1598,12 @@ namespace eosiosystem { // defined in delegate_bandwidth.cpp void changebw( name from, const name& receiver, const asset& stake_net_quantity, const asset& stake_cpu_quantity, bool transfer ); - void update_voting_power( const name& voter, const asset& total_update ); + int64_t update_voting_power( const name& voter, const asset& total_update ); void set_resource_ram_bytes_limits( const name& owner ); int64_t reduce_ram( const name& owner, int64_t bytes ); int64_t add_ram( const name& owner, int64_t bytes ); + void update_stake_delegated( const name from, const name receiver, const asset stake_net_delta, const asset stake_cpu_delta ); + void update_user_resources( const name from, const name receiver, const asset stake_net_delta, const asset stake_cpu_delta ); // defined in voting.cpp void register_producer( const name& producer, const eosio::block_signing_authority& producer_authority, const std::string& url, uint16_t location ); diff --git a/contracts/eosio.system/src/delegate_bandwidth.cpp b/contracts/eosio.system/src/delegate_bandwidth.cpp index f9696edc..3c774bb4 100644 --- a/contracts/eosio.system/src/delegate_bandwidth.cpp +++ b/contracts/eosio.system/src/delegate_bandwidth.cpp @@ -264,77 +264,8 @@ namespace eosiosystem { from = receiver; } - // update stake delegated from "from" to "receiver" - { - del_bandwidth_table del_tbl( get_self(), from.value ); - auto itr = del_tbl.find( receiver.value ); - if( itr == del_tbl.end() ) { - itr = del_tbl.emplace( from, [&]( auto& dbo ){ - dbo.from = from; - dbo.to = receiver; - dbo.net_weight = stake_net_delta; - dbo.cpu_weight = stake_cpu_delta; - }); - } - else { - del_tbl.modify( itr, same_payer, [&]( auto& dbo ){ - dbo.net_weight += stake_net_delta; - dbo.cpu_weight += stake_cpu_delta; - }); - } - check( 0 <= itr->net_weight.amount, "insufficient staked net bandwidth" ); - check( 0 <= itr->cpu_weight.amount, "insufficient staked cpu bandwidth" ); - if ( itr->is_empty() ) { - del_tbl.erase( itr ); - } - } // itr can be invalid, should go out of scope - - // update totals of "receiver" - { - user_resources_table totals_tbl( get_self(), receiver.value ); - auto tot_itr = totals_tbl.find( receiver.value ); - if( tot_itr == totals_tbl.end() ) { - tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { - tot.owner = receiver; - tot.net_weight = stake_net_delta; - tot.cpu_weight = stake_cpu_delta; - }); - } else { - totals_tbl.modify( tot_itr, from == receiver ? from : same_payer, [&]( auto& tot ) { - tot.net_weight += stake_net_delta; - tot.cpu_weight += stake_cpu_delta; - }); - } - check( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); - check( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); - - { - bool ram_managed = false; - bool net_managed = false; - bool cpu_managed = false; - - auto voter_itr = _voters.find( receiver.value ); - if( voter_itr != _voters.end() ) { - ram_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ); - net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); - cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); - } - - if( !(net_managed && cpu_managed) ) { - int64_t ram_bytes, net, cpu; - get_resource_limits( receiver, ram_bytes, net, cpu ); - - set_resource_limits( receiver, - ram_managed ? ram_bytes : std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), - net_managed ? net : tot_itr->net_weight.amount, - cpu_managed ? cpu : tot_itr->cpu_weight.amount ); - } - } - - if ( tot_itr->is_empty() ) { - totals_tbl.erase( tot_itr ); - } - } // tot_itr can be invalid, should go out of scope + update_stake_delegated( from, receiver, stake_net_delta, stake_cpu_delta ); + update_user_resources( from, receiver, stake_net_delta, stake_cpu_delta ); // create refund or update from existing refund if ( stake_account != source_stake_from ) { //for eosio both transfer and refund make no sense @@ -406,10 +337,84 @@ namespace eosiosystem { } vote_stake_updater( from ); - update_voting_power( from, stake_net_delta + stake_cpu_delta ); + const int64_t staked = update_voting_power( from, stake_net_delta + stake_cpu_delta ); + if ( from == "b1"_n ) { + validate_b1_vesting( staked ); + } + } + + void system_contract::update_stake_delegated( const name from, const name receiver, const asset stake_net_delta, const asset stake_cpu_delta ) + { + del_bandwidth_table del_tbl( get_self(), from.value ); + auto itr = del_tbl.find( receiver.value ); + if( itr == del_tbl.end() ) { + itr = del_tbl.emplace( from, [&]( auto& dbo ){ + dbo.from = from; + dbo.to = receiver; + dbo.net_weight = stake_net_delta; + dbo.cpu_weight = stake_cpu_delta; + }); + } else { + del_tbl.modify( itr, same_payer, [&]( auto& dbo ){ + dbo.net_weight += stake_net_delta; + dbo.cpu_weight += stake_cpu_delta; + }); + } + check( 0 <= itr->net_weight.amount, "insufficient staked net bandwidth" ); + check( 0 <= itr->cpu_weight.amount, "insufficient staked cpu bandwidth" ); + if ( itr->is_empty() ) { + del_tbl.erase( itr ); + } } - void system_contract::update_voting_power( const name& voter, const asset& total_update ) + void system_contract::update_user_resources( const name from, const name receiver, const asset stake_net_delta, const asset stake_cpu_delta ) + { + user_resources_table totals_tbl( get_self(), receiver.value ); + auto tot_itr = totals_tbl.find( receiver.value ); + if( tot_itr == totals_tbl.end() ) { + tot_itr = totals_tbl.emplace( from, [&]( auto& tot ) { + tot.owner = receiver; + tot.net_weight = stake_net_delta; + tot.cpu_weight = stake_cpu_delta; + }); + } else { + totals_tbl.modify( tot_itr, from == receiver ? from : same_payer, [&]( auto& tot ) { + tot.net_weight += stake_net_delta; + tot.cpu_weight += stake_cpu_delta; + }); + } + check( 0 <= tot_itr->net_weight.amount, "insufficient staked total net bandwidth" ); + check( 0 <= tot_itr->cpu_weight.amount, "insufficient staked total cpu bandwidth" ); + + { + bool ram_managed = false; + bool net_managed = false; + bool cpu_managed = false; + + auto voter_itr = _voters.find( receiver.value ); + if( voter_itr != _voters.end() ) { + ram_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::ram_managed ); + net_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::net_managed ); + cpu_managed = has_field( voter_itr->flags1, voter_info::flags1_fields::cpu_managed ); + } + + if( !(net_managed && cpu_managed) ) { + int64_t ram_bytes, net, cpu; + get_resource_limits( receiver, ram_bytes, net, cpu ); + + set_resource_limits( receiver, + ram_managed ? ram_bytes : std::max( tot_itr->ram_bytes + ram_gift_bytes, ram_bytes ), + net_managed ? net : tot_itr->net_weight.amount, + cpu_managed ? cpu : tot_itr->cpu_weight.amount ); + } + } + + if ( tot_itr->is_empty() ) { + totals_tbl.erase( tot_itr ); + } // tot_itr can be invalid, should go out of scope + } + + int64_t system_contract::update_voting_power( const name& voter, const asset& total_update ) { auto voter_itr = _voters.find( voter.value ); if( voter_itr == _voters.end() ) { @@ -425,13 +430,10 @@ namespace eosiosystem { check( 0 <= voter_itr->staked, "stake for voting cannot be negative" ); - if( voter == "b1"_n ) { - validate_b1_vesting( voter_itr->staked ); - } - if( voter_itr->producers.size() || voter_itr->proxy ) { update_votes( voter, voter_itr->proxy, voter_itr->producers, false ); } + return voter_itr->staked; } void system_contract::delegatebw( const name& from, const name& receiver, @@ -460,7 +462,6 @@ namespace eosiosystem { changebw( from, receiver, -unstake_net_quantity, -unstake_cpu_quantity, false); } // undelegatebw - void system_contract::refund( const name& owner ) { require_auth( owner ); @@ -474,5 +475,30 @@ namespace eosiosystem { refunds_tbl.erase( req ); } + void system_contract::unvest(const name account, const asset unvest_net_quantity, const asset unvest_cpu_quantity) + { + require_auth( get_self() ); + + const asset stake_delta = unvest_net_quantity + unvest_cpu_quantity; + asset zero_asset( 0, core_symbol() ); + check( unvest_cpu_quantity >= zero_asset, "must unvest a positive amount" ); + check( unvest_net_quantity >= zero_asset, "must unvest a positive amount" ); + check( stake_delta.amount > 0, "must unvest a positive amount" ); + check( account == "b1"_n, "only b1 account can unvest"); + + // reduce staked from account + update_voting_power( account, -stake_delta ); + update_stake_delegated( account, account, -unvest_net_quantity, -unvest_cpu_quantity ); + update_user_resources( account, account, -unvest_net_quantity, -unvest_cpu_quantity ); + vote_stake_updater( account ); + + // transfer unvested tokens to `eosio` + token::transfer_action transfer_act{ token_account, { {stake_account, active_permission} } }; + transfer_act.send( stake_account, get_self(), stake_delta, "unvest" ); + + // retire unvested tokens + token::retire_action retire_act{ token_account, { {"eosio"_n, active_permission} } }; + retire_act.send( stake_delta, "unvest" ); + } // unvest } //namespace eosiosystem diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 93a8eb24..691a6b11 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -643,6 +643,13 @@ class eosio_system_tester : public TESTER { ("unstake_cpu_quantity", cpu) ); } + action_result unvest( const account_name& account, const asset& net, const asset& cpu ) { + return push_action( "eosio"_n, "unvest"_n, mvo() + ("account", account) + ("unvest_net_quantity", net) + ("unvest_cpu_quantity", cpu) + ); + } action_result unstake( std::string_view from, std::string_view to, const asset& net, const asset& cpu ) { return unstake( account_name(from), account_name(to), net, cpu ); } diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 37d8e9cf..c90f54ad 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -5547,6 +5547,14 @@ 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, small_amount, small_amount ) ); + const int64_t before = get_voter_info( b1 )["staked"].as(); + BOOST_REQUIRE_EQUAL( before, 646703490000 ); + + BOOST_REQUIRE_EQUAL( success(), unvest( b1, stake_amount - final_amount, stake_amount - final_amount ) ); + const int64_t after = get_voter_info( b1 )["staked"].as(); + + BOOST_REQUIRE_EQUAL( after, 0 ); + } FC_LOG_AND_RETHROW() From 7fb34dd6de2d75ace857106df47ddd1e330fc5ec Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Fri, 3 May 2024 23:16:50 +0200 Subject: [PATCH 02/13] add supply tests --- tests/eosio.system_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index c90f54ad..5d35a278 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -5548,12 +5548,15 @@ BOOST_FIXTURE_TEST_CASE( b1_vesting, eosio_system_tester ) try { unstake( b1, b1, small_amount, small_amount ) ); const int64_t before = get_voter_info( b1 )["staked"].as(); + const asset before_supply = get_token_supply(); BOOST_REQUIRE_EQUAL( before, 646703490000 ); BOOST_REQUIRE_EQUAL( success(), unvest( b1, stake_amount - final_amount, stake_amount - final_amount ) ); const int64_t after = get_voter_info( b1 )["staked"].as(); + const asset after_supply = get_token_supply(); BOOST_REQUIRE_EQUAL( after, 0 ); + BOOST_REQUIRE_EQUAL( after_supply.get_amount() - before_supply.get_amount(), -646703490000 ); } FC_LOG_AND_RETHROW() From 812ffa6579f64738d03faa23a7dedcff10cbd88f Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Sat, 4 May 2024 16:24:00 +0200 Subject: [PATCH 03/13] Implement schedules ref: https://github.com/eosnetworkfoundation/eos-system-contracts/issues/136 --- .../include/eosio.system/eosio.system.hpp | 46 ++++++++++++++++ .../ricardian/eosio.system.contracts.md.in | 38 ++++++++++++- contracts/eosio.system/src/eosio.system.cpp | 47 ++++++++++++++++ contracts/eosio.system/src/producer_pay.cpp | 1 + tests/eosio.system_schedules_tests.cpp | 54 +++++++++++++++++++ tests/eosio.system_tester.hpp | 22 ++++++++ 6 files changed, 207 insertions(+), 1 deletion(-) create mode 100644 tests/eosio.system_schedules_tests.cpp diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index 4cc766aa..dea99d85 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -197,6 +197,17 @@ namespace eosiosystem { EOSLIB_SERIALIZE( eosio_global_state4, (continuous_rate)(inflation_pay_factor)(votepay_factor) ) }; + // Defines the schedule for pre-determined annual rate changes. + struct [[eosio::table, eosio::contract("eosio.system")]] schedules_info { + time_point_sec start_time; + int64_t annual_rate; + + uint64_t primary_key() const { return start_time.sec_since_epoch(); } + + // explicit serialization macro is not necessary, used here only to improve compilation time + EOSLIB_SERIALIZE( schedules_info, (start_time)(annual_rate) ) + }; + inline eosio::block_signing_authority convert_to_block_signing_authority( const eosio::public_key& producer_key ) { return eosio::block_signing_authority_v0{ .threshold = 1, .keys = {{producer_key, 1}} }; } @@ -329,6 +340,7 @@ namespace eosiosystem { typedef eosio::multi_index< "producers2"_n, producer_info2 > producers_table2; + typedef eosio::multi_index< "schedules"_n, schedules_info > schedules_table; typedef eosio::singleton< "global"_n, eosio_global_state > global_state_singleton; @@ -713,6 +725,7 @@ namespace eosiosystem { eosio_global_state2 _gstate2; eosio_global_state3 _gstate3; eosio_global_state4 _gstate4; + schedules_table _schedules; rammarket _rammarket; rex_pool_table _rexpool; rex_return_pool_table _rexretpool; @@ -1429,6 +1442,35 @@ namespace eosiosystem { [[eosio::action]] void setinflation( int64_t annual_rate, int64_t inflation_pay_factor, int64_t votepay_factor ); + /** + * Set the schedule for pre-determined annual rate changes. + * + * @param start_time - the time to start the schedule. + * @param annual_rate - the annual inflation rate of the core token supply. + * (eg. For 5% Annual inflation => annual_rate=500 + * For 1.5% Annual inflation => annual_rate=150 + */ + [[eosio::action]] + void setschedule( const time_point_sec start_time, int64_t annual_rate ); + + /** + * Delete the schedule for pre-determined annual rate changes. + * + * @param start_time - the time to start the schedule. + */ + [[eosio::action]] + void delschedule( const time_point_sec start_time ); + + /** + * Executes the next schedule for pre-determined annual rate changes. + * + * Start time of the schedule must be in the past. + * + * Can be executed by any account. + */ + [[eosio::action]] + void execschedule(); + /** * Facilitates the removal of vested staked tokens from an account, ensuring that these tokens are reallocated to the system's pool. * @@ -1542,6 +1584,9 @@ namespace eosiosystem { using cfgpowerup_action = eosio::action_wrapper<"cfgpowerup"_n, &system_contract::cfgpowerup>; using powerupexec_action = eosio::action_wrapper<"powerupexec"_n, &system_contract::powerupexec>; using powerup_action = eosio::action_wrapper<"powerup"_n, &system_contract::powerup>; + using execschedule_action = eosio::action_wrapper<"execschedule"_n, &system_contract::execschedule>; + using setschedule_action = eosio::action_wrapper<"setschedule"_n, &system_contract::setschedule>; + using delschedule_action = eosio::action_wrapper<"delschedule"_n, &system_contract::delschedule>; private: // Implementation details: @@ -1557,6 +1602,7 @@ namespace eosiosystem { static eosio_global_state4 get_default_inflation_parameters(); symbol core_symbol()const; void update_ram_supply(); + bool execute_next_schedule(); // defined in rex.cpp void runrex( uint16_t max ); diff --git a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in index d82a209b..a74ed9ec 100644 --- a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in +++ b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in @@ -752,4 +752,40 @@ summary: 'User may powerup to reserve resources' icon: @ICON_BASE_URL@/@RESOURCE_ICON_URI@ --- -Users may use the powerup action to reserve resources. \ No newline at end of file +Users may use the powerup action to reserve resources. + +

setschedule

+ +--- +spec_version: "0.2.0" +title: Set Annual Rate Schedule +summary: 'Set annual rate parameters' +icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ +--- + +{{$action.account}} sets a pre-determined inflation schedule to adjust parameters as follows: + +* Start time of the schedule: {{start_time}} +* Annual inflation rate (in units of a hundredth of a percent): {{annual_rate}} + +

delschedule

+ +--- +spec_version: "0.2.0" +title: Delete Annual Rate Schedule +summary: 'Delete annual rate schedule' +icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ +--- + +{{$action.account}} to delete a pre-determined inflation schedule from {{start_time}} start time. + +

execschedule

+ +--- +spec_version: "0.2.0" +title: Execute Next Annual Rate Schedule +summary: 'Execute next annual rate schedule' +icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ +--- + +{{$action.account}} to execute the next upcoming annual rate schedule. diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 527b0324..80a4cd65 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -24,6 +24,7 @@ namespace eosiosystem { _global2(get_self(), get_self().value), _global3(get_self(), get_self().value), _global4(get_self(), get_self().value), + _schedules(get_self(), get_self().value), _rammarket(get_self(), get_self().value), _rexpool(get_self(), get_self().value), _rexretpool(get_self(), get_self().value), @@ -423,6 +424,52 @@ namespace eosiosystem { _global4.set( _gstate4, get_self() ); } + void system_contract::setschedule( const time_point_sec start_time, int64_t annual_rate ) + { + require_auth( get_self() ); + + check(annual_rate >= 0, "annual_rate can't be negative"); + + auto itr = _schedules.find( start_time.sec_since_epoch() ); + + if( itr == _schedules.end() ) { + _schedules.emplace( get_self(), [&]( auto& s ) { + s.start_time = start_time; + s.annual_rate = annual_rate; + }); + } else { + _schedules.modify( itr, same_payer, [&]( auto& s ) { + check( annual_rate != s.annual_rate, "annual_rate was not modified"); + s.annual_rate = annual_rate; + }); + } + } + + void system_contract::delschedule( const time_point_sec start_time ) + { + require_auth( get_self() ); + + auto itr = _schedules.require_find( start_time.sec_since_epoch(), "schedule not found" ); + _schedules.erase( itr ); + } + + void system_contract::execschedule() + { + check(execute_next_schedule(), "no schedule to execute"); + } + + bool system_contract::execute_next_schedule() + { + auto itr = _schedules.begin(); + if ( itr->annual_rate == 0 ) return false; // row is empty + if ( current_time_point().sec_since_epoch() >= itr->start_time.sec_since_epoch() ) { + _gstate4.continuous_rate = get_continuous_rate(itr->annual_rate); + _schedules.erase( itr ); + return true; + } + return false; + } + /** * Called after a new account is created. This code enforces resource-limits rules * for new accounts as well as new account naming conventions. diff --git a/contracts/eosio.system/src/producer_pay.cpp b/contracts/eosio.system/src/producer_pay.cpp index e1a98dd0..0072472b 100644 --- a/contracts/eosio.system/src/producer_pay.cpp +++ b/contracts/eosio.system/src/producer_pay.cpp @@ -76,6 +76,7 @@ namespace eosiosystem { void system_contract::claimrewards( const name& owner ) { require_auth( owner ); + execute_next_schedule(); const auto& prod = _producers.get( owner.value ); check( prod.active(), "producer does not have an active key" ); diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp new file mode 100644 index 00000000..5d21ae90 --- /dev/null +++ b/tests/eosio.system_schedules_tests.cpp @@ -0,0 +1,54 @@ +#include + +#include "eosio.system_tester.hpp" + +using namespace eosio_system; + +BOOST_AUTO_TEST_SUITE(eosio_system_vesting_tests) + +BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { + + const std::vector accounts = { "alice"_n }; + create_accounts_with_resources( accounts ); + const account_name alice = accounts[0]; + + const uint32_t YEAR = 86400 * 365; + const uint32_t initial_start_time = 1577836856; // 2020-01-01T00:00 + const time_point_sec start_time = time_point_sec(initial_start_time); + + // action validation + BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 500) ); // 5% annual rate + BOOST_REQUIRE_EQUAL( wasm_assert_msg("annual_rate was not modified"), setschedule(start_time, 500) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("annual_rate can't be negative"), setschedule(start_time, -1) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(time_point_sec(0)) ); + BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 200) ); // allow override existing schedules + BOOST_REQUIRE_EQUAL( success(), delschedule(start_time) ); + + // set 3 future schedules + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(start_time), 200) ); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 4), 100) ); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 8), 50) ); + + // current state prior to first schedule + const double before = get_global_state4()["continuous_rate"].as_double(); + BOOST_REQUIRE_EQUAL( before, 0.048790164169432007 ); // 5% annual rate + const uint32_t current_time_sec = control->pending_block_time().sec_since_epoch(); + BOOST_REQUIRE_EQUAL( current_time_sec, initial_start_time ); // 2020-01-01T00:00 + + // advance to halvening schedules + produce_block( fc::days(365 * 4) ); // advance to year 4 + BOOST_REQUIRE_EQUAL( control->pending_block_time().sec_since_epoch(), initial_start_time + YEAR * 4 ); // 2024-01-01T00:00 + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.019802627296179712 ); // 2% annual rate + + produce_block( fc::days(365 * 4) ); // advanced to year 8 + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0099503308531680833 ); // 1% annual rate + + produce_block( fc::days(365 * 4) ); // advanced to year 12 + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0049875415110390738 ); // 0.5% annual rate + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 691a6b11..7a479ad2 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -1271,6 +1271,11 @@ class eosio_system_tester : public TESTER { return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state3", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); } + fc::variant get_global_state4() { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, "global4"_n, "global4"_n ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state4", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + } + fc::variant get_refund_request( name account ) { vector data = get_row_by_account( config::system_account_name, account, "refunds"_n, account ); return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "refund_request", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); @@ -1406,6 +1411,23 @@ class eosio_system_tester : public TESTER { ); } + action_result setschedule( const time_point_sec start_time, int64_t annual_rate ) { + return push_action( "eosio"_n, "setschedule"_n, mvo() + ("start_time", start_time) + ("annual_rate", annual_rate) + ); + } + + action_result delschedule( const time_point_sec start_time ) { + return push_action( "eosio"_n, "delschedule"_n, mvo() + ("start_time", start_time) + ); + } + + action_result execschedule( const name executor ) { + return push_action( executor, "execschedule"_n, mvo()); + } + abi_serializer abi_ser; abi_serializer token_abi_ser; }; From 6e79c48ebc13eed612302ed35523540f6067a84c Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Sat, 4 May 2024 16:38:25 +0200 Subject: [PATCH 04/13] add schedule with 0% annual rate --- contracts/eosio.system/src/eosio.system.cpp | 3 ++- tests/eosio.system_schedules_tests.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 80a4cd65..edb07ecd 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -461,7 +461,8 @@ namespace eosiosystem { bool system_contract::execute_next_schedule() { auto itr = _schedules.begin(); - if ( itr->annual_rate == 0 ) return false; // row is empty + if (itr == _schedules.end()) return false; // no schedules to execute + if ( current_time_point().sec_since_epoch() >= itr->start_time.sec_since_epoch() ) { _gstate4.continuous_rate = get_continuous_rate(itr->annual_rate); _schedules.erase( itr ); diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index 5d21ae90..b617a525 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -24,10 +24,11 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 200) ); // allow override existing schedules BOOST_REQUIRE_EQUAL( success(), delschedule(start_time) ); - // set 3 future schedules + // set 4 future schedules BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(start_time), 200) ); BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 4), 100) ); BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 8), 50) ); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 12), 0) ); // current state prior to first schedule const double before = get_global_state4()["continuous_rate"].as_double(); @@ -49,6 +50,10 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0049875415110390738 ); // 0.5% annual rate + produce_block( fc::days(365 * 4) ); // advanced to year 16 + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% annual rate + } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() From 669d9664ba13b511a44bbf389ea2a064e3ec0687 Mon Sep 17 00:00:00 2001 From: Denis Carriere Date: Sat, 4 May 2024 16:43:06 +0200 Subject: [PATCH 05/13] add no schedule to execute tests --- tests/eosio.system_schedules_tests.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index b617a525..f3ec14e1 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -54,6 +54,9 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% annual rate + // no more schedules + BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); + } FC_LOG_AND_RETHROW() BOOST_AUTO_TEST_SUITE_END() From a9a79e17d132cb40741a4eda2ecbc4eb29c2f93f Mon Sep 17 00:00:00 2001 From: Nathan James Date: Wed, 8 May 2024 16:21:53 +0100 Subject: [PATCH 06/13] direct continuous rate modification, extending tests --- .../include/eosio.system/eosio.system.hpp | 7 +- .../ricardian/eosio.system.contracts.md.in | 2 +- contracts/eosio.system/src/eosio.system.cpp | 13 +-- tests/eosio.system_schedules_tests.cpp | 83 +++++++++++++------ tests/eosio.system_tester.hpp | 9 +- 5 files changed, 77 insertions(+), 37 deletions(-) diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index dea99d85..ad3e8f6c 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -200,12 +200,12 @@ namespace eosiosystem { // Defines the schedule for pre-determined annual rate changes. struct [[eosio::table, eosio::contract("eosio.system")]] schedules_info { time_point_sec start_time; - int64_t annual_rate; + double continuous_rate; uint64_t primary_key() const { return start_time.sec_since_epoch(); } // explicit serialization macro is not necessary, used here only to improve compilation time - EOSLIB_SERIALIZE( schedules_info, (start_time)(annual_rate) ) + EOSLIB_SERIALIZE( schedules_info, (start_time)(continuous_rate) ) }; inline eosio::block_signing_authority convert_to_block_signing_authority( const eosio::public_key& producer_key ) { @@ -1451,7 +1451,7 @@ namespace eosiosystem { * For 1.5% Annual inflation => annual_rate=150 */ [[eosio::action]] - void setschedule( const time_point_sec start_time, int64_t annual_rate ); + void setschedule( const time_point_sec start_time, double continuous_rate ); /** * Delete the schedule for pre-determined annual rate changes. @@ -1587,6 +1587,7 @@ namespace eosiosystem { using execschedule_action = eosio::action_wrapper<"execschedule"_n, &system_contract::execschedule>; using setschedule_action = eosio::action_wrapper<"setschedule"_n, &system_contract::setschedule>; using delschedule_action = eosio::action_wrapper<"delschedule"_n, &system_contract::delschedule>; + using unvest_action = eosio::action_wrapper<"unvest"_n, &system_contract::unvest>; private: // Implementation details: diff --git a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in index a74ed9ec..56ff0434 100644 --- a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in +++ b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in @@ -766,7 +766,7 @@ icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ {{$action.account}} sets a pre-determined inflation schedule to adjust parameters as follows: * Start time of the schedule: {{start_time}} -* Annual inflation rate (in units of a hundredth of a percent): {{annual_rate}} +* The continuous rate of inflation: {{continuous_rate}}

delschedule

diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index edb07ecd..4024d75f 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -424,23 +424,24 @@ namespace eosiosystem { _global4.set( _gstate4, get_self() ); } - void system_contract::setschedule( const time_point_sec start_time, int64_t annual_rate ) + void system_contract::setschedule( const time_point_sec start_time, double continuous_rate ) { require_auth( get_self() ); - check(annual_rate >= 0, "annual_rate can't be negative"); + check(continuous_rate >= 0, "continuous_rate can't be negative"); + check(continuous_rate <= 1, "continuous_rate can't be over 100%"); + check(start_time.sec_since_epoch() >= current_time_point().sec_since_epoch(), "start_time cannot be in the past"); auto itr = _schedules.find( start_time.sec_since_epoch() ); if( itr == _schedules.end() ) { _schedules.emplace( get_self(), [&]( auto& s ) { s.start_time = start_time; - s.annual_rate = annual_rate; + s.continuous_rate = continuous_rate; }); } else { _schedules.modify( itr, same_payer, [&]( auto& s ) { - check( annual_rate != s.annual_rate, "annual_rate was not modified"); - s.annual_rate = annual_rate; + s.continuous_rate = continuous_rate; }); } } @@ -464,7 +465,7 @@ namespace eosiosystem { if (itr == _schedules.end()) return false; // no schedules to execute if ( current_time_point().sec_since_epoch() >= itr->start_time.sec_since_epoch() ) { - _gstate4.continuous_rate = get_continuous_rate(itr->annual_rate); + _gstate4.continuous_rate = itr->continuous_rate; _schedules.erase( itr ); return true; } diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index f3ec14e1..49fd0109 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -8,51 +8,84 @@ BOOST_AUTO_TEST_SUITE(eosio_system_vesting_tests) BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { + + auto check_schedule = [&](time_point_sec time, double rate){ + auto schedule = get_vesting_schedule(time.sec_since_epoch()); + REQUIRE_MATCHING_OBJECT( schedule, mvo() + ("start_time", time) + ("continuous_rate", rate) + ); + }; + const std::vector accounts = { "alice"_n }; create_accounts_with_resources( accounts ); const account_name alice = accounts[0]; const uint32_t YEAR = 86400 * 365; - const uint32_t initial_start_time = 1577836856; // 2020-01-01T00:00 - const time_point_sec start_time = time_point_sec(initial_start_time); + uint32_t initial_start_time = control->pending_block_time().sec_since_epoch(); // 1577836853 (2020-01-01T00:00:56.000Z) + time_point_sec start_time = time_point_sec(initial_start_time); + + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("continuous_rate can't be over 100%"), setschedule(time_point_sec(0), 1.00001) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("continuous_rate can't be negative"), setschedule(start_time, -1) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(start_time) ); // action validation - BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 500) ); // 5% annual rate - BOOST_REQUIRE_EQUAL( wasm_assert_msg("annual_rate was not modified"), setschedule(start_time, 500) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("annual_rate can't be negative"), setschedule(start_time, -1) ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(time_point_sec(0)) ); - BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 200) ); // allow override existing schedules + BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 0.05) ); + check_schedule(start_time, 0.05); + + // allow override existing schedules + BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 0.02) ); + check_schedule(start_time, 0.02); + + // You should no longer be able to modify something that has passed the + // execution date, or add new schedules in the past + BOOST_REQUIRE_EQUAL( wasm_assert_msg("start_time cannot be in the past"), setschedule(start_time, 0.05) ); + + // Should be able to delete schedules, even in the past BOOST_REQUIRE_EQUAL( success(), delschedule(start_time) ); - - // set 4 future schedules - BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(start_time), 200) ); - BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 4), 100) ); - BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 8), 50) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(start_time) ); + + // Resetting timers to make math clean + initial_start_time = control->pending_block_time().sec_since_epoch(); + start_time = time_point_sec(initial_start_time); + + + // // set 4 future schedules + BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 0.02) ); + check_schedule(start_time, 0.02); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 4), 0.01) ); + check_schedule(time_point_sec(initial_start_time + YEAR * 4), 0.01); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 8), 0.005) ); + check_schedule(time_point_sec(initial_start_time + YEAR * 8), 0.005); BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 12), 0) ); + check_schedule(time_point_sec(initial_start_time + YEAR * 12), 0); - // current state prior to first schedule + // current state prior to first schedule change execution const double before = get_global_state4()["continuous_rate"].as_double(); - BOOST_REQUIRE_EQUAL( before, 0.048790164169432007 ); // 5% annual rate - const uint32_t current_time_sec = control->pending_block_time().sec_since_epoch(); - BOOST_REQUIRE_EQUAL( current_time_sec, initial_start_time ); // 2020-01-01T00:00 + BOOST_REQUIRE_EQUAL( before, 0.048790164169432007 ); // 5% continuous rate - // advance to halvening schedules - produce_block( fc::days(365 * 4) ); // advance to year 4 - BOOST_REQUIRE_EQUAL( control->pending_block_time().sec_since_epoch(), initial_start_time + YEAR * 4 ); // 2024-01-01T00:00 - BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); - BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.019802627296179712 ); // 2% annual rate + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.02 ); - produce_block( fc::days(365 * 4) ); // advanced to year 8 + // Cannot execute schedule before its time is due + // we did 5 actions so we're late 2.5s late (5 blocks / 2 blocks per second) + produce_block( fc::seconds(YEAR * 4) - fc::milliseconds(3500) ); // advance to year 4 + BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); + + // Can execute this schedule 1 second after, as that is its time + produce_block( fc::seconds(1) ); // advance to year 4 + BOOST_REQUIRE_EQUAL( control->pending_block_time().sec_since_epoch(), initial_start_time + YEAR * 4 ); // 2024-01-01T00:00 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); - BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0099503308531680833 ); // 1% annual rate + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.01 ); // 1% continuous rate produce_block( fc::days(365 * 4) ); // advanced to year 12 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); - BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0049875415110390738 ); // 0.5% annual rate + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.005 ); // 0.5% continuous rate produce_block( fc::days(365 * 4) ); // advanced to year 16 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); - BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% annual rate + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% continuous rate // no more schedules BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index 7a479ad2..ff22667e 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -1411,10 +1411,10 @@ class eosio_system_tester : public TESTER { ); } - action_result setschedule( const time_point_sec start_time, int64_t annual_rate ) { + action_result setschedule( const time_point_sec start_time, double continuous_rate ) { return push_action( "eosio"_n, "setschedule"_n, mvo() ("start_time", start_time) - ("annual_rate", annual_rate) + ("continuous_rate", continuous_rate) ); } @@ -1428,6 +1428,11 @@ class eosio_system_tester : public TESTER { return push_action( executor, "execschedule"_n, mvo()); } + fc::variant get_vesting_schedule( uint64_t time ) { + vector data = get_row_by_account( "eosio"_n, "eosio"_n, "schedules"_n, account_name(time) ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "schedules_info", data, abi_serializer::create_yield_function(abi_serializer_max_time) ); + } + abi_serializer abi_ser; abi_serializer token_abi_ser; }; From 5a9176dbb85485951b5d280ec0630e16782c3cf5 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Wed, 8 May 2024 16:23:55 +0100 Subject: [PATCH 07/13] year typo in versting schedule test --- tests/eosio.system_schedules_tests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index 49fd0109..09fbccf1 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -74,7 +74,7 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); // Can execute this schedule 1 second after, as that is its time - produce_block( fc::seconds(1) ); // advance to year 4 + produce_block( fc::seconds(1) ); // advance to year 8 BOOST_REQUIRE_EQUAL( control->pending_block_time().sec_since_epoch(), initial_start_time + YEAR * 4 ); // 2024-01-01T00:00 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.01 ); // 1% continuous rate From c550e955ccffc5744fa311545286aa092326f986 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Wed, 8 May 2024 16:24:57 +0100 Subject: [PATCH 08/13] year typo in versting schedule test --- tests/eosio.system_schedules_tests.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index 09fbccf1..7fdac096 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -74,16 +74,16 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); // Can execute this schedule 1 second after, as that is its time - produce_block( fc::seconds(1) ); // advance to year 8 + produce_block( fc::seconds(1) ); // advance to year 4 BOOST_REQUIRE_EQUAL( control->pending_block_time().sec_since_epoch(), initial_start_time + YEAR * 4 ); // 2024-01-01T00:00 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.01 ); // 1% continuous rate - produce_block( fc::days(365 * 4) ); // advanced to year 12 + produce_block( fc::days(365 * 4) ); // advanced to year 8 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.005 ); // 0.5% continuous rate - produce_block( fc::days(365 * 4) ); // advanced to year 16 + produce_block( fc::days(365 * 4) ); // advanced to year 12 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% continuous rate From ff27f8cb6d64d6e7d429a3326dea1978cc8e721e Mon Sep 17 00:00:00 2001 From: Nathan James Date: Thu, 9 May 2024 15:51:07 +0100 Subject: [PATCH 09/13] extend unvest tests, enforce only unvested amount, and allow claiming vested remainders --- .../eosio.system/src/delegate_bandwidth.cpp | 40 ++++++---- tests/eosio.system_schedules_tests.cpp | 15 +++- tests/eosio.system_tests.cpp | 78 ++++++++++++------- 3 files changed, 88 insertions(+), 45 deletions(-) diff --git a/contracts/eosio.system/src/delegate_bandwidth.cpp b/contracts/eosio.system/src/delegate_bandwidth.cpp index 3c774bb4..92c7f576 100644 --- a/contracts/eosio.system/src/delegate_bandwidth.cpp +++ b/contracts/eosio.system/src/delegate_bandwidth.cpp @@ -241,13 +241,25 @@ namespace eosiosystem { } } - void validate_b1_vesting( int64_t stake ) { + std::pair get_b1_vesting_info() { const int64_t base_time = 1527811200; /// Friday, June 1, 2018 12:00:00 AM UTC const int64_t current_time = 1638921540; /// Tuesday, December 7, 2021 11:59:00 PM UTC - const int64_t max_claimable = 100'000'000'0000ll; - const int64_t claimable = int64_t(max_claimable * double(current_time - base_time) / (10*seconds_per_year) ); + const int64_t total_vesting = 100'000'000'0000ll; + const int64_t vested = int64_t(total_vesting * double(current_time - base_time) / (10*seconds_per_year) ); + return { total_vesting, vested }; + } + + + void validate_b1_vesting( int64_t new_stake, asset stake_change ) { + const auto [total_vesting, vested] = get_b1_vesting_info(); + auto unvestable = total_vesting - vested; + + auto hasAlreadyUnvested = new_stake < unvestable + && stake_change.amount < 0 + && new_stake + std::abs(stake_change.amount) < unvestable; + if(hasAlreadyUnvested) return; - check( max_claimable - claimable <= stake, "b1 can only claim their tokens over 10 years" ); + check( new_stake >= unvestable, "b1 can only claim what has already vested" ); } void system_contract::changebw( name from, const name& receiver, @@ -339,7 +351,7 @@ namespace eosiosystem { vote_stake_updater( from ); const int64_t staked = update_voting_power( from, stake_net_delta + stake_cpu_delta ); if ( from == "b1"_n ) { - validate_b1_vesting( staked ); + validate_b1_vesting( staked, stake_net_delta + stake_cpu_delta ); } } @@ -479,26 +491,28 @@ namespace eosiosystem { { require_auth( get_self() ); - const asset stake_delta = unvest_net_quantity + unvest_cpu_quantity; - asset zero_asset( 0, core_symbol() ); - check( unvest_cpu_quantity >= zero_asset, "must unvest a positive amount" ); - check( unvest_net_quantity >= zero_asset, "must unvest a positive amount" ); - check( stake_delta.amount > 0, "must unvest a positive amount" ); check( account == "b1"_n, "only b1 account can unvest"); + check( unvest_cpu_quantity.amount >= 0, "must unvest a positive amount" ); + check( unvest_net_quantity.amount >= 0, "must unvest a positive amount" ); + + const auto [total_vesting, vested] = get_b1_vesting_info(); + const asset unvesting = unvest_net_quantity + unvest_cpu_quantity; + check( unvesting.amount <= total_vesting - vested , "can only unvest what is not already vested"); + // reduce staked from account - update_voting_power( account, -stake_delta ); + update_voting_power( account, -unvesting ); update_stake_delegated( account, account, -unvest_net_quantity, -unvest_cpu_quantity ); update_user_resources( account, account, -unvest_net_quantity, -unvest_cpu_quantity ); vote_stake_updater( account ); // transfer unvested tokens to `eosio` token::transfer_action transfer_act{ token_account, { {stake_account, active_permission} } }; - transfer_act.send( stake_account, get_self(), stake_delta, "unvest" ); + transfer_act.send( stake_account, get_self(), unvesting, "unvest" ); // retire unvested tokens token::retire_action retire_act{ token_account, { {"eosio"_n, active_permission} } }; - retire_act.send( stake_delta, "unvest" ); + retire_act.send( unvesting, "unvest" ); } // unvest } //namespace eosiosystem diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index 7fdac096..eee8c491 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -58,8 +58,10 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { check_schedule(time_point_sec(initial_start_time + YEAR * 4), 0.01); BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 8), 0.005) ); check_schedule(time_point_sec(initial_start_time + YEAR * 8), 0.005); - BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 12), 0) ); - check_schedule(time_point_sec(initial_start_time + YEAR * 12), 0); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 12), 0.0005) ); + check_schedule(time_point_sec(initial_start_time + YEAR * 12), 0.0005); + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(initial_start_time + YEAR * 13), 0) ); + check_schedule(time_point_sec(initial_start_time + YEAR * 13), 0); // current state prior to first schedule change execution const double before = get_global_state4()["continuous_rate"].as_double(); @@ -69,8 +71,9 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.02 ); // Cannot execute schedule before its time is due - // we did 5 actions so we're late 2.5s late (5 blocks / 2 blocks per second) - produce_block( fc::seconds(YEAR * 4) - fc::milliseconds(3500) ); // advance to year 4 + // (we did 6 actions so we're late 3s late) + auto late = fc::seconds(3); + produce_block( fc::seconds(YEAR * 4) - fc::seconds(1) - late ); // advance to year 4 BOOST_REQUIRE_EQUAL( wasm_assert_msg("no schedule to execute"), execschedule(alice) ); // Can execute this schedule 1 second after, as that is its time @@ -85,6 +88,10 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { produce_block( fc::days(365 * 4) ); // advanced to year 12 BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); + BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0005 ); // 0.05% continuous rate + + produce_block( fc::days(365 * 1) ); // advanced to year 13 + BOOST_REQUIRE_EQUAL( success(), execschedule(alice) ); BOOST_REQUIRE_EQUAL( get_global_state4()["continuous_rate"].as_double(), 0.0 ); // 0% continuous rate // no more schedules diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 7d52db53..bdd55cf1 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -5538,52 +5538,74 @@ BOOST_FIXTURE_TEST_CASE( b1_vesting, eosio_system_tester ) try { create_accounts_with_resources( { b1 }, alice ); const asset stake_amount = core_sym::from_string("50000000.0000"); - const asset final_amount = core_sym::from_string("17664825.5000"); - const asset small_amount = core_sym::from_string("1000.0000"); issue_and_transfer( b1, stake_amount + stake_amount + stake_amount, config::system_account_name ); stake( b1, b1, stake_amount, stake_amount ); BOOST_REQUIRE_EQUAL( 2 * stake_amount.get_amount(), get_voter_info( b1 )["staked"].as() ); - BOOST_REQUIRE_EQUAL( success(), unstake( b1, b1, small_amount, small_amount ) ); + // The code has changed since the tests were originally written, and B1's vesting is no longer based + // on the time of the block, but is a fixed amount instead. + // The total amount of possible vested is 35329651.2515, meaning there is 64670348.7485 + // left which will not be vested. + // These tests now reflect the new behavior. - produce_block( fc::days(4) ); - - BOOST_REQUIRE_EQUAL( success(), push_action( b1, "refund"_n, mvo()("owner", b1) ) ); - - BOOST_REQUIRE_EQUAL( 2 * ( stake_amount.get_amount() - small_amount.get_amount() ), - get_voter_info( b1 )["staked"].as() ); - - BOOST_REQUIRE_EQUAL( wasm_assert_msg("b1 can only claim their tokens over 10 years"), - unstake( b1, b1, final_amount, final_amount ) ); + const asset vested = core_sym::from_string("35329651.2515"); + const asset unvestable = core_sym::from_string("64670348.7485"); + const asset oneToken = core_sym::from_string("1.0000"); + const asset zero = core_sym::from_string("0.0000"); + // Can't use rex 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 ) ); - + unstaketorex( b1, b1, vested, zero ) ); 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 ) ); - - produce_block( fc::days(4) ); + // Can't take what isn't vested + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("b1 can only claim what has already vested"), + unstake( b1, b1, stake_amount, stake_amount ) + ); + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("b1 can only claim what has already vested"), + unstake( b1, b1, stake_amount, zero ) + ); + // Taking the vested amount - 1 token + BOOST_REQUIRE_EQUAL( success(), unstake( b1, b1, vested-oneToken, zero ) ); + produce_block( fc::days(4) ); BOOST_REQUIRE_EQUAL( success(), push_action( b1, "refund"_n, mvo()("owner", b1) ) ); + BOOST_REQUIRE_EQUAL(unvestable.get_amount() + oneToken.get_amount(), + get_voter_info( b1 )["staked"].as() ); + + // Can't take 2 tokens, only 1 is vested + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("b1 can only claim what has already vested"), + unstake( b1, b1, oneToken, oneToken ) + ); - produce_block( fc::days( 5 * 364 ) ); + // Can't unvest the 1 token, as it's already unvested + BOOST_REQUIRE_EQUAL( + wasm_assert_msg("can only unvest what is not already vested"), + unvest( b1, (stake_amount - vested) + oneToken, stake_amount ) + ); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("b1 can only claim their tokens over 10 years"), - unstake( b1, b1, small_amount, small_amount ) ); + auto supply_before = get_token_supply(); - const int64_t before = get_voter_info( b1 )["staked"].as(); - const asset before_supply = get_token_supply(); - BOOST_REQUIRE_EQUAL( before, 646703490000 ); + // Unvesting the remaining unvested tokens + BOOST_REQUIRE_EQUAL( success(), unvest( b1, stake_amount - vested, stake_amount ) ); + BOOST_REQUIRE_EQUAL(oneToken.get_amount(), get_voter_info( b1 )["staked"].as() ); - BOOST_REQUIRE_EQUAL( success(), unvest( b1, stake_amount - final_amount, stake_amount - final_amount ) ); - const int64_t after = get_voter_info( b1 )["staked"].as(); - const asset after_supply = get_token_supply(); + // Should have retired the unvestable tokens + BOOST_REQUIRE_EQUAL( + get_token_supply(), + supply_before-unvestable + ); - BOOST_REQUIRE_EQUAL( after, 0 ); - BOOST_REQUIRE_EQUAL( after_supply.get_amount() - before_supply.get_amount(), -646703490000 ); + // B1 can take the last token, even after unvesting has occurred + BOOST_REQUIRE_EQUAL( success(), unstake( b1, b1, oneToken, zero ) ); + produce_block( fc::days(4) ); + BOOST_REQUIRE_EQUAL( success(), push_action( b1, "refund"_n, mvo()("owner", b1) ) ); + BOOST_REQUIRE_EQUAL(0, get_voter_info( b1 )["staked"].as() ); } FC_LOG_AND_RETHROW() From 4239d2097b4881e126daec91069f09834f87b896 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Fri, 10 May 2024 12:53:30 +0100 Subject: [PATCH 10/13] allow setting a schedule in the past or msigs will break --- contracts/eosio.system/src/eosio.system.cpp | 1 - tests/eosio.system_schedules_tests.cpp | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 4024d75f..702b6dbc 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -430,7 +430,6 @@ namespace eosiosystem { check(continuous_rate >= 0, "continuous_rate can't be negative"); check(continuous_rate <= 1, "continuous_rate can't be over 100%"); - check(start_time.sec_since_epoch() >= current_time_point().sec_since_epoch(), "start_time cannot be in the past"); auto itr = _schedules.find( start_time.sec_since_epoch() ); diff --git a/tests/eosio.system_schedules_tests.cpp b/tests/eosio.system_schedules_tests.cpp index eee8c491..17fd69ee 100644 --- a/tests/eosio.system_schedules_tests.cpp +++ b/tests/eosio.system_schedules_tests.cpp @@ -26,11 +26,12 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { time_point_sec start_time = time_point_sec(initial_start_time); - BOOST_REQUIRE_EQUAL( wasm_assert_msg("continuous_rate can't be over 100%"), setschedule(time_point_sec(0), 1.00001) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("continuous_rate can't be over 100%"), setschedule(start_time, 1.00001) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("continuous_rate can't be negative"), setschedule(start_time, -1) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(start_time) ); // action validation + BOOST_REQUIRE_EQUAL( success(), setschedule(time_point_sec(0), 0.05) ); BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 0.05) ); check_schedule(start_time, 0.05); @@ -38,12 +39,10 @@ BOOST_FIXTURE_TEST_CASE(set_schedules, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( success(), setschedule(start_time, 0.02) ); check_schedule(start_time, 0.02); - // You should no longer be able to modify something that has passed the - // execution date, or add new schedules in the past - BOOST_REQUIRE_EQUAL( wasm_assert_msg("start_time cannot be in the past"), setschedule(start_time, 0.05) ); // Should be able to delete schedules, even in the past BOOST_REQUIRE_EQUAL( success(), delschedule(start_time) ); + BOOST_REQUIRE_EQUAL( success(), delschedule(time_point_sec(0)) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("schedule not found"), delschedule(start_time) ); // Resetting timers to make math clean From 9e3ac02d3caa801ac8bef6e44e4e5fccfcb0c83a Mon Sep 17 00:00:00 2001 From: Nathan James Date: Fri, 10 May 2024 13:12:51 +0100 Subject: [PATCH 11/13] expose new action to allow setting pay factors without affecting continuous_rate --- .cspell/custom-dictionary.txt | 1 + .../include/eosio.system/eosio.system.hpp | 16 ++++++++++++++++ .../ricardian/eosio.system.contracts.md.in | 14 ++++++++++++++ contracts/eosio.system/src/eosio.system.cpp | 13 +++++++++++++ tests/eosio.system_tester.hpp | 7 +++++++ tests/eosio.system_tests.cpp | 5 +++++ 6 files changed, 56 insertions(+) diff --git a/.cspell/custom-dictionary.txt b/.cspell/custom-dictionary.txt index adc5ba9f..25e9e469 100644 --- a/.cspell/custom-dictionary.txt +++ b/.cspell/custom-dictionary.txt @@ -93,6 +93,7 @@ setacctram setalimits setcode setinflation +setpayfactor setparams setpriv setram diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index ad3e8f6c..04665412 100644 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -1442,6 +1442,21 @@ namespace eosiosystem { [[eosio::action]] void setinflation( int64_t annual_rate, int64_t inflation_pay_factor, int64_t votepay_factor ); + /** + * Change how inflated or vested tokens will be distributed based on the following structure. + * + * @param inflation_pay_factor - Inverse of the fraction of the inflation used to reward block producers. + * The remaining inflation will be sent to the `eosio.saving` account. + * (eg. For 20% of inflation going to block producer rewards => inflation_pay_factor = 50000 + * For 100% of inflation going to block producer rewards => inflation_pay_factor = 10000). + * @param votepay_factor - Inverse of the fraction of the block producer rewards to be distributed proportional to blocks produced. + * The remaining rewards will be distributed proportional to votes received. + * (eg. For 25% of block producer rewards going towards block pay => votepay_factor = 40000 + * For 75% of block producer rewards going towards block pay => votepay_factor = 13333). + */ + [[eosio::action]] + void setpayfactor( int64_t inflation_pay_factor, int64_t votepay_factor ); + /** * Set the schedule for pre-determined annual rate changes. * @@ -1581,6 +1596,7 @@ namespace eosiosystem { using setalimits_action = eosio::action_wrapper<"setalimits"_n, &system_contract::setalimits>; using setparams_action = eosio::action_wrapper<"setparams"_n, &system_contract::setparams>; using setinflation_action = eosio::action_wrapper<"setinflation"_n, &system_contract::setinflation>; + using setpayfactor_action = eosio::action_wrapper<"setpayfactor"_n, &system_contract::setpayfactor>; using cfgpowerup_action = eosio::action_wrapper<"cfgpowerup"_n, &system_contract::cfgpowerup>; using powerupexec_action = eosio::action_wrapper<"powerupexec"_n, &system_contract::powerupexec>; using powerup_action = eosio::action_wrapper<"powerup"_n, &system_contract::powerup>; diff --git a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in index 56ff0434..6fb0b223 100644 --- a/contracts/eosio.system/ricardian/eosio.system.contracts.md.in +++ b/contracts/eosio.system/ricardian/eosio.system.contracts.md.in @@ -626,6 +626,20 @@ icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ * Fraction of inflation used to reward block producers: 10000/{{inflation_pay_factor}} * Fraction of block producer rewards to be distributed proportional to blocks produced: 10000/{{votepay_factor}} +

setpayfactor

+ +--- +spec_version: "0.2.0" +title: Set Pay Factors +summary: 'Set pay factors' +icon: @ICON_BASE_URL@/@ADMIN_ICON_URI@ +--- + +{{$action.account}} sets the inflation parameters as follows: + +* Fraction of inflation used to reward block producers: 10000/{{inflation_pay_factor}} +* Fraction of block producer rewards to be distributed proportional to blocks produced: 10000/{{votepay_factor}} +

undelegatebw

--- diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 702b6dbc..68d25617 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -424,6 +424,19 @@ namespace eosiosystem { _global4.set( _gstate4, get_self() ); } + void system_contract::setpayfactor( int64_t inflation_pay_factor, int64_t votepay_factor ) { + require_auth(get_self()); + if ( inflation_pay_factor < pay_factor_precision ) { + check( false, "inflation_pay_factor must not be less than " + std::to_string(pay_factor_precision) ); + } + if ( votepay_factor < pay_factor_precision ) { + check( false, "votepay_factor must not be less than " + std::to_string(pay_factor_precision) ); + } + _gstate4.inflation_pay_factor = inflation_pay_factor; + _gstate4.votepay_factor = votepay_factor; + _global4.set( _gstate4, get_self() ); + } + void system_contract::setschedule( const time_point_sec start_time, double continuous_rate ) { require_auth( get_self() ); diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index fda0609b..28d6d94a 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -1433,6 +1433,13 @@ class eosio_system_tester : public TESTER { ); } + action_result setpayfactor( int64_t inflation_pay_factor, int64_t votepay_factor ) { + return push_action( "eosio"_n, "setpayfactor"_n, mvo() + ("inflation_pay_factor", inflation_pay_factor) + ("votepay_factor", votepay_factor) + ); + } + action_result setschedule( const time_point_sec start_time, double continuous_rate ) { return push_action( "eosio"_n, "setschedule"_n, mvo() ("start_time", start_time) diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index bdd55cf1..491d8eb2 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -1672,6 +1672,11 @@ BOOST_FIXTURE_TEST_CASE(change_inflation, eosio_system_tester) try { setinflation(1, 9999, 10000) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("votepay_factor must not be less than 10000"), setinflation(1, 10000, 9999) ); + + BOOST_REQUIRE_EQUAL( wasm_assert_msg("inflation_pay_factor must not be less than 10000"), + setpayfactor(1, 9999, 10000) ); + BOOST_REQUIRE_EQUAL( wasm_assert_msg("votepay_factor must not be less than 10000"), + setpayfactor(1, 10000, 9999) ); } { From b188adc3e0534b2e4432c96ca9e19597eb1ee38c Mon Sep 17 00:00:00 2001 From: Nathan James Date: Fri, 10 May 2024 13:15:53 +0100 Subject: [PATCH 12/13] fixes for setpayfactor tests --- tests/eosio.system_tests.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 491d8eb2..3987f53b 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -1673,10 +1673,12 @@ BOOST_FIXTURE_TEST_CASE(change_inflation, eosio_system_tester) try { BOOST_REQUIRE_EQUAL( wasm_assert_msg("votepay_factor must not be less than 10000"), setinflation(1, 10000, 9999) ); + BOOST_REQUIRE_EQUAL( success(), + setpayfactor(10000, 10000) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("inflation_pay_factor must not be less than 10000"), - setpayfactor(1, 9999, 10000) ); + setpayfactor(9999, 10000) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("votepay_factor must not be less than 10000"), - setpayfactor(1, 10000, 9999) ); + setpayfactor(10000, 9999) ); } { From 6a175c051c83f1565b8752204396694a7e9c0047 Mon Sep 17 00:00:00 2001 From: Nathan James Date: Mon, 13 May 2024 12:26:02 +0100 Subject: [PATCH 13/13] set global state --- contracts/eosio.system/src/eosio.system.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 68d25617..7637e5f5 100644 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -478,6 +478,7 @@ namespace eosiosystem { if ( current_time_point().sec_since_epoch() >= itr->start_time.sec_since_epoch() ) { _gstate4.continuous_rate = itr->continuous_rate; + _global4.set( _gstate4, get_self() ); _schedules.erase( itr ); return true; }