diff --git a/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md b/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md new file mode 100644 index 0000000000..95990a60a2 --- /dev/null +++ b/.changelog/unreleased/improvements/2295-refactor-apply-inflation.md @@ -0,0 +1,2 @@ +- Move the inflation code for PoS and PGF into their own native modules. + ([\#2295](https://github.com/anoma/namada/pull/2295)) \ No newline at end of file diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index 239b7b35da..4746e88c9b 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -1,23 +1,19 @@ //! Implementation of the `FinalizeBlock` ABCI++ method for the Shell use data_encoding::HEXUPPER; -use namada::core::ledger::inflation; use namada::core::ledger::masp_conversions::update_allowed_conversions; -use namada::core::ledger::pgf::ADDRESS as pgf_address; +use namada::core::ledger::pgf::inflation as pgf_inflation; use namada::ledger::events::EventType; use namada::ledger::gas::{GasMetering, TxGasMeter}; -use namada::ledger::parameters::storage as params_storage; -use namada::ledger::pos::{namada_proof_of_stake, staking_token_address}; +use namada::ledger::pos::namada_proof_of_stake; use namada::ledger::protocol; use namada::ledger::storage::wl_storage::WriteLogAndStorage; use namada::ledger::storage::EPOCH_SWITCH_BLOCKS_DELAY; -use namada::ledger::storage_api::token::credit_tokens; -use namada::ledger::storage_api::{pgf, StorageRead, StorageWrite}; +use namada::ledger::storage_api::StorageRead; use namada::proof_of_stake::{ find_validator_by_raw_hash, read_last_block_proposer_address, - read_pos_params, read_total_stake, write_last_block_proposer_address, + write_last_block_proposer_address, }; -use namada::types::dec::Dec; use namada::types::key::tm_raw_hash_to_string; use namada::types::storage::{BlockHash, BlockResults, Epoch, Header}; use namada::types::transaction::protocol::{ @@ -636,58 +632,6 @@ where /// with respect to the previous epoch. fn apply_inflation(&mut self, current_epoch: Epoch) -> Result<()> { let last_epoch = current_epoch.prev(); - // Get input values needed for the PD controller for PoS. - // Run the PD controllers to calculate new rates. - - let params = read_pos_params(&self.wl_storage)?; - - // Read from Parameters storage - let epochs_per_year: u64 = self - .read_storage_key(¶ms_storage::get_epochs_per_year_key()) - .expect("Epochs per year should exist in storage"); - let pos_p_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_p_key()) - .expect("PoS P-gain factor should exist in storage"); - let pos_d_gain_nom: Dec = self - .read_storage_key(¶ms_storage::get_pos_gain_d_key()) - .expect("PoS D-gain factor should exist in storage"); - - let pos_last_staked_ratio: Dec = self - .read_storage_key(¶ms_storage::get_staked_ratio_key()) - .expect("PoS staked ratio should exist in storage"); - let pos_last_inflation_amount: token::Amount = self - .read_storage_key(¶ms_storage::get_pos_inflation_amount_key()) - .expect("PoS inflation amount should exist in storage"); - // Read from PoS storage - let total_tokens: token::Amount = self - .read_storage_key(&token::minted_balance_key( - &staking_token_address(&self.wl_storage), - )) - .expect("Total NAM balance should exist in storage"); - let pos_locked_supply = - read_total_stake(&self.wl_storage, ¶ms, last_epoch)?; - let pos_locked_ratio_target = params.target_staked_ratio; - let pos_max_inflation_rate = params.max_inflation_rate; - - // Run rewards PD controller - let pos_controller = inflation::RewardsController { - locked_tokens: pos_locked_supply.raw_amount(), - total_tokens: total_tokens.raw_amount(), - total_native_tokens: total_tokens.raw_amount(), - locked_ratio_target: pos_locked_ratio_target, - locked_ratio_last: pos_last_staked_ratio, - max_reward_rate: pos_max_inflation_rate, - last_inflation_amount: pos_last_inflation_amount.raw_amount(), - p_gain_nom: pos_p_gain_nom, - d_gain_nom: pos_d_gain_nom, - epochs_per_year, - }; - - // Run the rewards controllers - let inflation::ValsToUpdate { - locked_ratio, - inflation, - } = pos_controller.run(); // Get the number of blocks in the last epoch let first_block_of_last_epoch = self @@ -700,116 +644,15 @@ where let num_blocks_in_last_epoch = self.wl_storage.storage.block.height.0 - first_block_of_last_epoch; - let staking_token = staking_token_address(&self.wl_storage); - - let inflation = token::Amount::from_uint(inflation, 0) - .expect("Should not fail Uint -> Amount conversion"); - namada_proof_of_stake::update_rewards_products_and_mint_inflation( + // PoS inflation + namada_proof_of_stake::apply_inflation( &mut self.wl_storage, - ¶ms, last_epoch, num_blocks_in_last_epoch, - inflation, - &staking_token, - ) - .expect( - "Must be able to update PoS rewards products and mint inflation", - ); - - // Write new rewards parameters that will be used for the inflation of - // the current new epoch - self.wl_storage - .write(¶ms_storage::get_pos_inflation_amount_key(), inflation) - .expect("unable to write new reward rate"); - self.wl_storage - .write(¶ms_storage::get_staked_ratio_key(), locked_ratio) - .expect("unable to write new locked ratio"); - - // Pgf inflation - let pgf_parameters = pgf::get_parameters(&self.wl_storage)?; - - let pgf_pd_rate = - pgf_parameters.pgf_inflation_rate / Dec::from(epochs_per_year); - let pgf_inflation = Dec::from(total_tokens) * pgf_pd_rate; - let pgf_inflation_amount = token::Amount::from(pgf_inflation); - - credit_tokens( - &mut self.wl_storage, - &staking_token, - &pgf_address, - pgf_inflation_amount, )?; - tracing::info!( - "Minting {} tokens for PGF rewards distribution into the PGF \ - account.", - pgf_inflation_amount.to_string_native() - ); - - let mut pgf_fundings = pgf::get_payments(&self.wl_storage)?; - // we want to pay first the oldest fundings - pgf_fundings.sort_by(|a, b| a.id.cmp(&b.id)); - - for funding in pgf_fundings { - if storage_api::token::transfer( - &mut self.wl_storage, - &staking_token, - &pgf_address, - &funding.detail.target, - funding.detail.amount, - ) - .is_ok() - { - tracing::info!( - "Paying {} tokens for {} project.", - funding.detail.amount.to_string_native(), - &funding.detail.target, - ); - } else { - tracing::warn!( - "Failed to pay {} tokens for {} project.", - funding.detail.amount.to_string_native(), - &funding.detail.target, - ); - } - } - - // Pgf steward inflation - let stewards = pgf::get_stewards(&self.wl_storage)?; - let pgf_stewards_pd_rate = - pgf_parameters.stewards_inflation_rate / Dec::from(epochs_per_year); - let pgf_steward_inflation = - Dec::from(total_tokens) * pgf_stewards_pd_rate; - - for steward in stewards { - for (address, percentage) in steward.reward_distribution { - let pgf_steward_reward = pgf_steward_inflation - .checked_mul(&percentage) - .unwrap_or_default(); - let reward_amount = token::Amount::from(pgf_steward_reward); - - if credit_tokens( - &mut self.wl_storage, - &staking_token, - &address, - reward_amount, - ) - .is_ok() - { - tracing::info!( - "Minting {} tokens for steward {}.", - reward_amount.to_string_native(), - address, - ); - } else { - tracing::warn!( - "Failed minting {} tokens for steward {}.", - reward_amount.to_string_native(), - address, - ); - } - } - } + // Pgf inflation + pgf_inflation::apply_inflation(&mut self.wl_storage)?; Ok(()) } @@ -969,14 +812,14 @@ mod test_finalize_block { }; use namada::proof_of_stake::{ enqueued_slashes_handle, get_num_consensus_validators, - read_consensus_validator_set_addresses_with_stake, + read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, rewards_accumulator_handle, unjail_validator, validator_consensus_key_handle, validator_rewards_products_handle, validator_slashes_handle, validator_state_handle, write_pos_params, ADDRESS as pos_address, }; use namada::proto::{Code, Data, Section, Signature}; - use namada::types::dec::POS_DECIMAL_PRECISION; + use namada::types::dec::{Dec, POS_DECIMAL_PRECISION}; use namada::types::ethereum_events::{EthAddress, Uint as ethUint}; use namada::types::hash::Hash; use namada::types::keccak::KeccakHash; @@ -2504,7 +2347,7 @@ mod test_finalize_block { let delegator = address::testing::gen_implicit_address(); let del_amount = init_stake; let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &delegator, @@ -2629,21 +2472,21 @@ mod test_finalize_block { // Give the validators some tokens for txs let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator1.address, init_stake, ) .unwrap(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator2.address, init_stake, ) .unwrap(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &validator3.address, @@ -4001,7 +3844,7 @@ mod test_finalize_block { let delegator = address::testing::gen_implicit_address(); let del_1_amount = token::Amount::native_whole(67_231); let staking_token = shell.wl_storage.storage.native_token.clone(); - credit_tokens( + storage_api::token::credit_tokens( &mut shell.wl_storage, &staking_token, &delegator, @@ -5270,7 +5113,8 @@ mod test_finalize_block { misbehaviors: Option>, ) -> (Epoch, token::Amount) { let current_epoch = shell.wl_storage.storage.block.epoch; - let staking_token = staking_token_address(&shell.wl_storage); + let staking_token = + namada_proof_of_stake::staking_token_address(&shell.wl_storage); // NOTE: assumed that the only change in pos address balance by // advancing to the next epoch is minted inflation - no change occurs diff --git a/core/src/ledger/pgf/inflation.rs b/core/src/ledger/pgf/inflation.rs new file mode 100644 index 0000000000..30dd4191fd --- /dev/null +++ b/core/src/ledger/pgf/inflation.rs @@ -0,0 +1,104 @@ +//! PGF lib code. + +use crate::ledger::parameters::storage as params_storage; +use crate::ledger::storage_api::pgf::{ + get_parameters, get_payments, get_stewards, +}; +use crate::ledger::storage_api::token::credit_tokens; +use crate::ledger::storage_api::{self, StorageRead, StorageWrite}; +use crate::types::dec::Dec; +use crate::types::token; + +/// Apply the PGF inflation. +pub fn apply_inflation(storage: &mut S) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + let pgf_parameters = get_parameters(storage)?; + let staking_token = storage.get_native_token()?; + + let epochs_per_year: u64 = storage + .read(¶ms_storage::get_epochs_per_year_key())? + .expect("Epochs per year should exist in storage"); + let total_tokens: token::Amount = storage + .read(&token::minted_balance_key(&staking_token))? + .expect("Total NAM balance should exist in storage"); + + let pgf_pd_rate = + pgf_parameters.pgf_inflation_rate / Dec::from(epochs_per_year); + let pgf_inflation = Dec::from(total_tokens) * pgf_pd_rate; + let pgf_inflation_amount = token::Amount::from(pgf_inflation); + + credit_tokens( + storage, + &staking_token, + &super::ADDRESS, + pgf_inflation_amount, + )?; + + tracing::info!( + "Minting {} tokens for PGF rewards distribution into the PGF account.", + pgf_inflation_amount.to_string_native() + ); + + let mut pgf_fundings = get_payments(storage)?; + // we want to pay first the oldest fundings + pgf_fundings.sort_by(|a, b| a.id.cmp(&b.id)); + + for funding in pgf_fundings { + if storage_api::token::transfer( + storage, + &staking_token, + &super::ADDRESS, + &funding.detail.target, + funding.detail.amount, + ) + .is_ok() + { + tracing::info!( + "Paying {} tokens for {} project.", + funding.detail.amount.to_string_native(), + &funding.detail.target, + ); + } else { + tracing::warn!( + "Failed to pay {} tokens for {} project.", + funding.detail.amount.to_string_native(), + &funding.detail.target, + ); + } + } + + // Pgf steward inflation + let stewards = get_stewards(storage)?; + let pgf_stewards_pd_rate = + pgf_parameters.stewards_inflation_rate / Dec::from(epochs_per_year); + let pgf_steward_inflation = Dec::from(total_tokens) * pgf_stewards_pd_rate; + + for steward in stewards { + for (address, percentage) in steward.reward_distribution { + let pgf_steward_reward = pgf_steward_inflation + .checked_mul(&percentage) + .unwrap_or_default(); + let reward_amount = token::Amount::from(pgf_steward_reward); + + if credit_tokens(storage, &staking_token, &address, reward_amount) + .is_ok() + { + tracing::info!( + "Minting {} tokens for steward {}.", + reward_amount.to_string_native(), + address, + ); + } else { + tracing::warn!( + "Failed minting {} tokens for steward {}.", + reward_amount.to_string_native(), + address, + ); + } + } + } + + Ok(()) +} diff --git a/core/src/ledger/pgf/mod.rs b/core/src/ledger/pgf/mod.rs index b7f8e3cecb..5d7d7eef24 100644 --- a/core/src/ledger/pgf/mod.rs +++ b/core/src/ledger/pgf/mod.rs @@ -4,6 +4,8 @@ use crate::types::address::{Address, InternalAddress}; /// Pgf CLI pub mod cli; +/// Pgf inflation code +pub mod inflation; /// Pgf parameters pub mod parameters; /// Pgf storage diff --git a/proof_of_stake/src/lib.rs b/proof_of_stake/src/lib.rs index 3d79a345e9..3aa632b4f2 100644 --- a/proof_of_stake/src/lib.rs +++ b/proof_of_stake/src/lib.rs @@ -24,6 +24,8 @@ use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; use borsh::BorshDeserialize; pub use error::*; +use namada_core::ledger::inflation; +use namada_core::ledger::parameters::storage as params_storage; use namada_core::ledger::storage_api::collections::lazy_map::{ Collectable, LazyMap, NestedMap, NestedSubKey, SubKey, }; @@ -6466,3 +6468,81 @@ where } Ok(()) } + +/// Apply inflation to the Proof of Stake system. +pub fn apply_inflation( + storage: &mut S, + last_epoch: Epoch, + num_blocks_in_last_epoch: u64, +) -> storage_api::Result<()> +where + S: StorageRead + StorageWrite, +{ + // Read from Parameters storage + let epochs_per_year: u64 = storage + .read(¶ms_storage::get_epochs_per_year_key())? + .expect("Epochs per year should exist in storage"); + let pos_p_gain_nom: Dec = storage + .read(¶ms_storage::get_pos_gain_p_key())? + .expect("PoS P-gain factor should exist in storage"); + let pos_d_gain_nom: Dec = storage + .read(¶ms_storage::get_pos_gain_d_key())? + .expect("PoS D-gain factor should exist in storage"); + + let pos_last_staked_ratio: Dec = storage + .read(¶ms_storage::get_staked_ratio_key())? + .expect("PoS staked ratio should exist in storage"); + let pos_last_inflation_amount: token::Amount = storage + .read(¶ms_storage::get_pos_inflation_amount_key())? + .expect("PoS inflation amount should exist in storage"); + + // Read from PoS storage + let params = read_pos_params(storage)?; + let staking_token = staking_token_address(storage); + + let total_tokens: token::Amount = storage + .read(&token::minted_balance_key(&staking_token))? + .expect("Total NAM balance should exist in storage"); + let pos_locked_supply = read_total_stake(storage, ¶ms, last_epoch)?; + let pos_locked_ratio_target = params.target_staked_ratio; + let pos_max_inflation_rate = params.max_inflation_rate; + + // Run rewards PD controller + let pos_controller = inflation::RewardsController { + locked_tokens: pos_locked_supply.raw_amount(), + total_tokens: total_tokens.raw_amount(), + total_native_tokens: total_tokens.raw_amount(), + locked_ratio_target: pos_locked_ratio_target, + locked_ratio_last: pos_last_staked_ratio, + max_reward_rate: pos_max_inflation_rate, + last_inflation_amount: pos_last_inflation_amount.raw_amount(), + p_gain_nom: pos_p_gain_nom, + d_gain_nom: pos_d_gain_nom, + epochs_per_year, + }; + // Run the rewards controllers + let inflation::ValsToUpdate { + locked_ratio, + inflation, + } = pos_controller.run(); + + let inflation = + token::Amount::from_uint(inflation, 0).into_storage_result()?; + + update_rewards_products_and_mint_inflation( + storage, + ¶ms, + last_epoch, + num_blocks_in_last_epoch, + inflation, + &staking_token, + )?; + + // Write new rewards parameters that will be used for the inflation of + // the current new epoch + storage + .write(¶ms_storage::get_pos_inflation_amount_key(), inflation)?; + storage.write(¶ms_storage::get_staked_ratio_key(), locked_ratio)?; + + Ok(()) +}