Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adjust chain inflation #84

Merged
merged 4 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions contracts/eosio.system/include/eosio.system/eosio.system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,15 @@ namespace eosiosystem {
indexed_by<"byexpires"_n, const_mem_fun<powerup_order, uint64_t, &powerup_order::by_expires>>
> powerup_order_table;

struct [[eosio::table("burn.state"),eosio::contract("eosio.system")]] burn_state {

uint64_t usecs_burn_period = useconds_per_day;
time_point last_burn_time;

uint64_t primary_key()const { return 0; }
};

typedef eosio::singleton<"burn.state"_n, burn_state> burn_state_singleton;
/**
* The `eosio.system` smart contract is provided by `block.one` as a sample system contract, and it defines the structures and actions needed for blockchain's core functionality.
*
Expand Down
35 changes: 33 additions & 2 deletions contracts/eosio.system/src/producer_pay.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,15 +79,27 @@ namespace eosiosystem {
const auto usecs_since_last_fill = (ct - _gstate.last_pervote_bucket_fill).count();

if( usecs_since_last_fill > 0 && _gstate.last_pervote_bucket_fill > time_point() ) {
burn_state_singleton burn_state_sing{ get_self(), 0 };
auto burn_state = burn_state_sing.get_or_default();

auto current_fees = eosio::token::get_balance(token_account, fees_account, core_symbol().code() );
auto new_tokens = static_cast<int64_t>( (continuous_rate * double(token_supply.amount) * double(usecs_since_last_fill)) / double(useconds_per_year) );
quocle108 marked this conversation as resolved.
Show resolved Hide resolved
auto fees_to_use = std::min( new_tokens, current_fees.amount );
auto issue_tokens = new_tokens - fees_to_use;
// needs to be 2/5 Savings, 2/5 Voters, 1/5 producers
auto to_per_block_pay = new_tokens / 5;
auto to_voters = 2 * to_per_block_pay;
auto to_savings = new_tokens - (to_voters + to_per_block_pay);

{
token::issue_action issue_act{ token_account, { {get_self(), active_permission} } };
issue_act.send( get_self(), asset(new_tokens, core_symbol()), "issue tokens for producer pay and savings" );
if(issue_tokens > 0){
token::issue_action issue_act{ token_account, { {get_self(), active_permission} } };
issue_act.send( get_self(), asset(issue_tokens, core_symbol()), "issue tokens for producer pay and savings" );
}
if(fees_to_use > 0){
token::transfer_action transfer_act{ token_account, { {fees_account, active_permission} } };
transfer_act.send( fees_account, get_self(), asset(fees_to_use, core_symbol()), "collect tokenomic fees" );
}
}
{
token::transfer_action transfer_act{ token_account, { {get_self(), active_permission} } };
Expand All @@ -99,6 +111,25 @@ namespace eosiosystem {
_gstate.perblock_bucket += to_per_block_pay;
_gstate.voters_bucket += to_voters;
_gstate.last_pervote_bucket_fill = ct;

// burn remaining tokenomic fees
if (!burn_state_sing.exists()) {
quocle108 marked this conversation as resolved.
Show resolved Hide resolved
burn_state.last_burn_time = current_time_point();
burn_state_sing.set(burn_state, get_self());
}
auto burn_flag = (current_time_point() - burn_state.last_burn_time).count() >= (burn_state.usecs_burn_period);

auto burn_fees = current_fees.amount - fees_to_use;
if(burn_flag && burn_fees > 0){
quocle108 marked this conversation as resolved.
Show resolved Hide resolved
token::transfer_action transfer_act{ token_account, { {fees_account, active_permission} } };
transfer_act.send( fees_account, get_self(), asset(burn_fees, core_symbol()), "burn tokenomic fees" );

token::retire_action retire_act{ token_account, { {get_self(), active_permission} } };
retire_act.send( asset(burn_fees, core_symbol()), "burn tokenomic fees" );

burn_state.last_burn_time = current_time_point();
burn_state_sing.set(burn_state, get_self());
}
}
}

Expand Down
14 changes: 13 additions & 1 deletion tests/eosio.system_tester.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class eosio_system_tester : public TESTER {
}
}

void remaining_setup() {
void remaining_setup(symbol core_symbol = symbol{CORE_SYM}) {
produce_blocks();

// Assumes previous setup steps were done with core token symbol set to CORE_SYM
Expand All @@ -93,6 +93,10 @@ class eosio_system_tester : public TESTER {
create_account_with_resources( "carol1111111"_n, config::system_account_name, core_sym::from_string("1.0000"), false );

BOOST_REQUIRE_EQUAL( core_sym::from_string("1000000000.0000"), get_balance("eosio") + get_balance("eosio.ramfee") + get_balance("eosio.stake") + get_balance("eosio.ram") );

// open ram for eosio.fees account
open( "eosio.fees"_n, core_symbol );
BOOST_REQUIRE_EQUAL( asset(0, core_symbol), get_balance( "eosio.fees", core_symbol ) );
}

enum class setup_level {
Expand Down Expand Up @@ -836,6 +840,14 @@ class eosio_system_tester : public TESTER {
);
}

void open( const name& owner, const symbol& symbolname, const name& manager = config::system_account_name) {
base_tester::push_action( "eosio.token"_n, "open"_n, manager, mutable_variant_object()
("owner", owner)
("symbol", symbolname )
("ram_payer", manager)
);
}

void transfer( const name& from, std::string_view to, const asset& amount, const name& manager = config::system_account_name ) {
transfer( from, name(to), amount, manager );
}
Expand Down
201 changes: 201 additions & 0 deletions tests/eosio.system_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2263,6 +2263,207 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester, * boost::unit_test::t

} FC_LOG_AND_RETHROW()


BOOST_FIXTURE_TEST_CASE(adjust_chain_inflation, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try {

const double continuous_rate = 0.04879;
const double usecs_per_year = 52 * 7 * 24 * 3600 * 1000000ll;
const double secs_per_year = 52 * 7 * 24 * 3600;
const int64_t useconds_per_day = (int64_t)24 * 3600 * 1000'000ll;

const asset large_asset = core_sym::from_string("80.0000");
create_account_with_resources( "defproducera"_n, config::system_account_name, core_sym::from_string("1.0000"), false, large_asset, large_asset );
create_account_with_resources( "defproducerb"_n, config::system_account_name, core_sym::from_string("1.0000"), false, large_asset, large_asset );
create_account_with_resources( "defproducerc"_n, config::system_account_name, core_sym::from_string("1.0000"), false, large_asset, large_asset );

create_account_with_resources( "producvotera"_n, config::system_account_name, core_sym::from_string("1.0000"), false, large_asset, large_asset );
create_account_with_resources( "producvoterb"_n, config::system_account_name, core_sym::from_string("1.0000"), false, large_asset, large_asset );

BOOST_REQUIRE_EQUAL(success(), regproducer("defproducera"_n));
BOOST_REQUIRE_EQUAL(success(), regproducer("defproducerb"_n));
produce_block(fc::hours(24));
auto prod = get_producer_info( "defproducera"_n );
BOOST_REQUIRE_EQUAL("defproducera", prod["owner"].as_string());
BOOST_REQUIRE_EQUAL(0, prod["total_votes"].as_double());

transfer( config::system_account_name, "producvotera", core_sym::from_string("400000000.0000"), config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("producvotera", core_sym::from_string("100000000.0000"), core_sym::from_string("100000000.0000")));
BOOST_REQUIRE_EQUAL(success(), vote( "producvotera"_n, { "defproducera"_n }));

// register producerb
transfer( config::system_account_name, "producvoterb", core_sym::from_string("400000000.0000"), config::system_account_name);
BOOST_REQUIRE_EQUAL(success(), stake("producvoterb", core_sym::from_string("100000000.0000"), core_sym::from_string("100000000.0000")));
BOOST_REQUIRE_EQUAL(success(), vote( "producvoterb"_n, { "defproducerb"_n }));

// Reduce Inflation Rate
// Simulates a scenario where the tokenomics can adjust the inflation rate.
{
produce_blocks(50);

const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = microseconds_since_epoch_of_iso_string( initial_global_state["last_pervote_bucket_fill"] );
const int64_t initial_pervote_bucket = initial_global_state["pervote_bucket"].as<int64_t>();
const int64_t initial_perblock_bucket = initial_global_state["perblock_bucket"].as<int64_t>();
const int64_t initial_savings = get_balance("eosio.saving"_n).get_amount();

prod = get_producer_info("defproducera");
const uint32_t unpaid_blocks = prod["unpaid_blocks"].as<uint32_t>();
BOOST_REQUIRE(1 < unpaid_blocks);


const asset initial_supply = get_token_supply();
const asset initial_balance = get_balance("defproducera"_n);
transfer(config::system_account_name, "eosio.fees", core_sym::from_string("1.0000"), config::system_account_name);
const asset init_account_fees = get_balance("eosio.fees"_n);
BOOST_TEST_MESSAGE("init_account_fees: " << init_account_fees.get_amount());

BOOST_REQUIRE_EQUAL(success(), push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));

const auto global_state = get_global_state();
const uint64_t claim_time = microseconds_since_epoch_of_iso_string( global_state["last_pervote_bucket_fill"] );
const int64_t pervote_bucket = global_state["pervote_bucket"].as<int64_t>();
const int64_t perblock_bucket = global_state["perblock_bucket"].as<int64_t>();
const int64_t savings = get_balance("eosio.saving"_n).get_amount();

prod = get_producer_info("defproducera");
const asset supply = get_token_supply();
const asset balance = get_balance("defproducera"_n);
const asset account_fees = get_balance("eosio.fees"_n);
BOOST_REQUIRE_EQUAL(0, account_fees.get_amount());

BOOST_REQUIRE_EQUAL(claim_time, microseconds_since_epoch_of_iso_string( prod["last_claim_time"] ));

auto usecs_between_fills = claim_time - initial_claim_time;
int32_t secs_between_fills = usecs_between_fills/1000000;
uint64_t new_tokens = (initial_supply.get_amount() * double(secs_between_fills) * continuous_rate) / secs_per_year;
BOOST_REQUIRE_EQUAL(0, initial_savings);
BOOST_REQUIRE_EQUAL(0, initial_perblock_bucket);
BOOST_REQUIRE_EQUAL(0, initial_pervote_bucket);

BOOST_REQUIRE_EQUAL(new_tokens - init_account_fees.get_amount(), supply.get_amount() - initial_supply.get_amount());
BOOST_REQUIRE_EQUAL(int64_t(new_tokens - (new_tokens / 5) * 3), savings - initial_savings);
BOOST_REQUIRE_EQUAL(int64_t(new_tokens / 5), balance.get_amount() - initial_balance.get_amount());

int64_t from_perblock_bucket = int64_t( initial_supply.get_amount() * double(secs_between_fills) * (continuous_rate / 5.) / secs_per_year ) ;
int64_t from_pervote_bucket = 0;


if (from_pervote_bucket >= 100 * 10000) {
BOOST_REQUIRE_EQUAL(from_perblock_bucket + from_pervote_bucket, balance.get_amount() - initial_balance.get_amount());
BOOST_REQUIRE_EQUAL(0, pervote_bucket);
} else {
BOOST_REQUIRE_EQUAL(from_perblock_bucket, balance.get_amount() - initial_balance.get_amount());
BOOST_REQUIRE_EQUAL(from_pervote_bucket, pervote_bucket);
}
}

// Wait 1 day
produce_block(fc::seconds(24 * 3600));

auto get_burn_state = [this]() -> fc::variant {
vector<char> data = get_row_by_account( config::system_account_name, {},
"burn.state"_n, "burn.state"_n );
BOOST_REQUIRE( !data.empty() );
return abi_ser.binary_to_variant("burn_state", data, abi_serializer::create_yield_function(abi_serializer_max_time));
};

// No Inflation Rate Adjustment with Token Burn
// Evaluates the situation where the tokenomics covers inflation rate and tests the burning mechanism for remaining tokens.
{
produce_blocks(100);

const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = microseconds_since_epoch_of_iso_string( initial_global_state["last_pervote_bucket_fill"] );
const int64_t initial_pervote_bucket = initial_global_state["pervote_bucket"].as<int64_t>();
const int64_t initial_perblock_bucket = initial_global_state["perblock_bucket"].as<int64_t>();
const int64_t initial_savings = get_balance("eosio.saving"_n).get_amount();

prod = get_producer_info("defproducera");
const uint32_t unpaid_blocks = prod["unpaid_blocks"].as<uint32_t>();
BOOST_REQUIRE(1 < unpaid_blocks);

const asset initial_supply = get_token_supply();
transfer(config::system_account_name, "eosio.fees", core_sym::from_string("200000.0000"), config::system_account_name);
const asset init_account_fees = get_balance("eosio.fees"_n);

BOOST_REQUIRE_EQUAL(success(), push_action("defproducera"_n, "claimrewards"_n, mvo()("owner", "defproducera")));

const auto global_state = get_global_state();
const uint64_t claim_time = microseconds_since_epoch_of_iso_string( global_state["last_pervote_bucket_fill"] );
const int64_t pervote_bucket = global_state["pervote_bucket"].as<int64_t>();
const int64_t perblock_bucket = global_state["perblock_bucket"].as<int64_t>();
const int64_t savings = get_balance("eosio.saving"_n).get_amount();

const auto burn_state = get_burn_state();
BOOST_REQUIRE_EQUAL(claim_time, microseconds_since_epoch_of_iso_string( burn_state["last_burn_time"]) );
BOOST_REQUIRE_EQUAL(useconds_per_day, burn_state["usecs_burn_period"].as<int64_t>() );

prod = get_producer_info("defproducera");
const asset supply = get_token_supply();
const asset account_fees = get_balance("eosio.fees"_n);
BOOST_REQUIRE_EQUAL(account_fees.get_amount(), 0);

BOOST_REQUIRE_EQUAL(claim_time, microseconds_since_epoch_of_iso_string( prod["last_claim_time"] ));

auto usecs_between_fills = claim_time - initial_claim_time;
int32_t secs_between_fills = usecs_between_fills/1000000;
int64_t new_tokens = (initial_supply.get_amount() * double(usecs_between_fills) * continuous_rate) / usecs_per_year;

BOOST_REQUIRE_EQUAL(init_account_fees.get_amount() - new_tokens, initial_supply.get_amount() - supply.get_amount());
BOOST_REQUIRE_EQUAL(int64_t(new_tokens - (new_tokens / 5) * 3), savings - initial_savings);

}

// No Inflation Adjustment without Token Burn
// Tests the scenario where no inflation adjustment is needed, and ensures that the tokens are not burned if the burn period has not exceeded.
{
produce_blocks(100);

const auto initial_global_state = get_global_state();
const uint64_t initial_claim_time = microseconds_since_epoch_of_iso_string( initial_global_state["last_pervote_bucket_fill"] );
const int64_t initial_pervote_bucket = initial_global_state["pervote_bucket"].as<int64_t>();
const int64_t initial_perblock_bucket = initial_global_state["perblock_bucket"].as<int64_t>();
const int64_t initial_savings = get_balance("eosio.saving"_n).get_amount();

prod = get_producer_info("defproducerb");
const uint32_t unpaid_blocks = prod["unpaid_blocks"].as<uint32_t>();
BOOST_REQUIRE(1 < unpaid_blocks);

const asset initial_supply = get_token_supply();
transfer(config::system_account_name, "eosio.fees", core_sym::from_string("200000.0000"), config::system_account_name);
const asset init_account_fees = get_balance("eosio.fees"_n);

BOOST_REQUIRE_EQUAL(success(), push_action("defproducerb"_n, "claimrewards"_n, mvo()("owner", "defproducerb")));

const auto global_state = get_global_state();
const uint64_t claim_time = microseconds_since_epoch_of_iso_string( global_state["last_pervote_bucket_fill"] );
const int64_t pervote_bucket = global_state["pervote_bucket"].as<int64_t>();
const int64_t perblock_bucket = global_state["perblock_bucket"].as<int64_t>();
const int64_t savings = get_balance("eosio.saving"_n).get_amount();
const uint32_t tot_unpaid_blocks = global_state["total_unpaid_blocks"].as<uint32_t>();

const auto burn_state = get_burn_state();
BOOST_REQUIRE_NE(claim_time, microseconds_since_epoch_of_iso_string( burn_state["last_burn_time"]) );
BOOST_REQUIRE_EQUAL(useconds_per_day, burn_state["usecs_burn_period"].as<int64_t>() );

prod = get_producer_info("defproducerb");

const asset supply = get_token_supply();
const asset account_fees = get_balance("eosio.fees"_n);

BOOST_REQUIRE_EQUAL(claim_time, microseconds_since_epoch_of_iso_string( prod["last_claim_time"] ));

auto usecs_between_fills = claim_time - initial_claim_time;
int32_t secs_between_fills = usecs_between_fills/1000000;
int64_t new_tokens = (initial_supply.get_amount() * double(usecs_between_fills) * continuous_rate) / usecs_per_year;

BOOST_REQUIRE_EQUAL(0, supply.get_amount() - initial_supply.get_amount());
BOOST_REQUIRE_EQUAL(new_tokens, init_account_fees.get_amount() - account_fees.get_amount());
BOOST_REQUIRE_EQUAL(int64_t(new_tokens - (new_tokens / 5) * 3), savings - initial_savings);
}

} FC_LOG_AND_RETHROW()

BOOST_FIXTURE_TEST_CASE(multiple_producer_pay, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try {

auto within_one = [](int64_t a, int64_t b) -> bool { return std::abs( a - b ) <= 1; };
Expand Down