From 7a317ad4c964c37357bb589d6d37dd45b684bdf4 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 24 Nov 2023 09:38:28 +0100 Subject: [PATCH 01/21] Pallet inflation --- Cargo.lock | 19 +++ pallets/dapp-staking-v3/src/lib.rs | 4 +- pallets/pallet-inflation/Cargo.toml | 51 +++++++++ pallets/pallet-inflation/src/lib.rs | 172 ++++++++++++++++++++++++++++ 4 files changed, 245 insertions(+), 1 deletion(-) create mode 100644 pallets/pallet-inflation/Cargo.toml create mode 100644 pallets/pallet-inflation/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fe18d417f9..eaf6cf8260 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7650,6 +7650,25 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-inflation" +version = "0.1.0" +dependencies = [ + "astar-primitives", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "serde", + "sp-arithmetic", + "sp-core", + "sp-runtime", + "sp-std", +] + [[package]] name = "pallet-insecure-randomness-collective-flip" version = "4.0.0-dev" diff --git a/pallets/dapp-staking-v3/src/lib.rs b/pallets/dapp-staking-v3/src/lib.rs index 9f48f21b96..392b05bd94 100644 --- a/pallets/dapp-staking-v3/src/lib.rs +++ b/pallets/dapp-staking-v3/src/lib.rs @@ -1502,7 +1502,9 @@ pub mod pallet { // Remove expired ledger stake entries, if needed. let threshold_period = Self::oldest_claimable_period(current_period); let mut ledger = Ledger::::get(&account); - ledger.contract_stake_count.saturating_reduce(entries_to_delete as u32); + ledger + .contract_stake_count + .saturating_reduce(entries_to_delete as u32); if ledger.maybe_cleanup_expired(threshold_period) { Self::update_ledger(&account, ledger); } diff --git a/pallets/pallet-inflation/Cargo.toml b/pallets/pallet-inflation/Cargo.toml new file mode 100644 index 0000000000..ae85f1164c --- /dev/null +++ b/pallets/pallet-inflation/Cargo.toml @@ -0,0 +1,51 @@ +[package] +name = "pallet-inflation" +version = "0.1.0" +license = "GPL-3.0" +description = "Manages inflation rate & inflation distribution" +authors.workspace = true +edition.workspace = true +homepage.workspace = true +repository.workspace = true + +[dependencies] +parity-scale-codec = { workspace = true } +serde = { workspace = true } + +astar-primitives = { workspace = true } +frame-support = { workspace = true } +frame-system = { workspace = true } +scale-info = { workspace = true } +sp-arithmetic = { workspace = true } +sp-runtime = { workspace = true } +sp-std = { workspace = true } + +frame-benchmarking = { workspace = true, optional = true } + +[dev-dependencies] +pallet-balances = { workspace = true } +pallet-timestamp = { workspace = true } +sp-core = { workspace = true } + +[features] +default = ["std"] +std = [ + "parity-scale-codec/std", + "sp-core/std", + "scale-info/std", + "sp-std/std", + "serde/std", + "frame-support/std", + "frame-system/std", + "pallet-timestamp/std", + "pallet-balances/std", + "astar-primitives/std", +] +runtime-benchmarks = [ + "frame-benchmarking", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "astar-primitives/runtime-benchmarks", +] +try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs new file mode 100644 index 0000000000..f4572bd219 --- /dev/null +++ b/pallets/pallet-inflation/src/lib.rs @@ -0,0 +1,172 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! TODO + +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +use astar_primitives::{Balance, BlockNumber}; +use frame_support::pallet_prelude::*; +use frame_support::{ + log, + traits::{Currency, Get, Imbalance, OnTimestampSet}, +}; +use frame_system::{ensure_root, pallet_prelude::*}; +use sp_runtime::{ + traits::{CheckedAdd, Zero}, + Perquintill, +}; +use sp_std::vec; + +#[frame_support::pallet] +pub mod pallet { + + use super::*; + + #[pallet::pallet] + pub struct Pallet(PhantomData); + + // Negative imbalance type of this pallet. + pub(crate) type NegativeImbalanceOf = <::Currency as Currency< + ::AccountId, + >>::NegativeImbalance; + + #[pallet::config] + pub trait Config: frame_system::Config { + // TODO: modify this so it doesn't use deprecated trait? + /// The currency trait. + type Currency: Currency; + + /// Handler for 'per-block' payouts. + type PayoutPerBlock: PayoutPerBlock>; + + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// Distribution configuration has been updated. + DistributionConfigurationChanged, + } + + /// Active inflation configuration parameteres. + /// They describe current rewards, when inflation needs to be recalculated, etc. + #[pallet::storage] + pub type InflationConfig = StorageValue<_, InflationConfiguration, ValueQuery>; + + /// Static inflation parameters - used to calculate active inflation configuration at certain points in time. + #[pallet::storage] + pub type InflationParams = StorageValue<_, InflationParameters, ValueQuery>; + + impl OnTimestampSet for Pallet { + fn on_timestamp_set(_moment: Moment) { + Self::payout_block_rewards(); + } + } + + impl Pallet { + /// Payout block rewards to the beneficiaries. + pub(crate) fn payout_block_rewards() { + let config = InflationConfig::::get(); + + let collator_amount = T::Currency::issue(config.collator_reward_per_block); + let treasury_amount = T::Currency::issue(config.treasury_reward_per_block); + + T::PayoutPerBlock::collators(collator_amount); + T::PayoutPerBlock::treasury(treasury_amount); + + // TODO: benchmark this and include it into on_initialize weight cost + } + } +} + +/// Configuration of the inflation. +/// Contains information about rewards, when inflation is recalculated, etc. +#[derive(Encode, Decode, MaxEncodedLen, Default, Copy, Clone, Debug, PartialEq, Eq, TypeInfo)] +pub struct InflationConfiguration { + /// Block number at which the inflation must be recalculated, based on the total issuance at that block. + #[codec(compact)] + pub recalculation_block: BlockNumber, + /// Reward for collator who produced the block. Always deposited the collator in full. + #[codec(compact)] + pub collator_reward_per_block: Balance, + /// Part of the inflation going towards the treasury. Always deposited in full. + #[codec(compact)] + pub treasury_reward_per_block: Balance, + /// dApp reward pool per era - based on this the tier rewards are calculated. + /// There's no guarantee that this whole amount will be minted & distributed. + #[codec(compact)] + pub dapp_reward_pool_per_era: Balance, + /// Base staker reward pool per era - this is always provided to stakers, regardless of the total value staked. + #[codec(compact)] + pub base_staker_reward_pool_per_era: Balance, + /// Adjustabke staker rewards, based on the total value staked. + /// This is provided to the stakers according to formula: 'pool * min(1, total_staked / ideal_staked)'. + #[codec(compact)] + pub adjustable_staker_reward_pool_per_era: Balance, + /// Bonus reward pool per period, for loyal stakers. + #[codec(compact)] + pub bonus_reward_pool_per_period: Balance, +} + +/// Inflation parameters. +/// +/// The parts of the inflation that go towards different purposes must add up to exactly 100%. +#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] +pub struct InflationParameters { + /// Maximum possible inflation rate, based on the total issuance at some point in time. + /// From this value, all the other inflation parameters are derived. + #[codec(compact)] + pub max_inflation_rate: Perquintill, + /// How much of the inflation in total goes towards the treasury. + #[codec(compact)] + pub treasury_part: Perquintill, + /// How much of the inflation in total goes towards collators. + #[codec(compact)] + pub collators_part: Perquintill, + /// How much of the inflation in total goes towards dApp rewards (tier rewards). + #[codec(compact)] + pub dapps_part: Perquintill, + /// How much of the inflation in total goes towards base staker rewards. + #[codec(compact)] + pub base_stakers_part: Perquintill, + /// How much of the inflation in total can go towards adjustable staker rewards. + /// These rewards are adjusted based on the total value staked. + #[codec(compact)] + pub adjustable_stakers_part: Perquintill, + /// How much of the inflation in total goes towards bonus staker rewards (loyalty rewards). + #[codec(compact)] + pub bonus_part: Perquintill, + /// The ideal staking rate, in respect to total issuance. + /// Used to derive exact amount of adjustable staker rewards. + #[codec(compact)] + pub ideal_staking_rate: Perquintill, +} + +/// Defines functions used to payout the beneficiaries of block rewards +pub trait PayoutPerBlock { + /// Payout reward to the treasury. + fn treasury(reward: Imbalance); + + /// Payout reward to the collator responsible for producing the block. + fn collators(reward: Imbalance); +} From fa21dcc1261cd174dc379534620cd3c8b3213463 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 24 Nov 2023 10:33:05 +0100 Subject: [PATCH 02/21] Hooks --- pallets/pallet-inflation/src/lib.rs | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index f4572bd219..9bf607810f 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -77,6 +77,21 @@ pub mod pallet { #[pallet::storage] pub type InflationParams = StorageValue<_, InflationParameters, ValueQuery>; + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(now: BlockNumberFor) -> Weight { + let recalculation_block: BlockNumberFor = + InflationConfig::::get().recalculation_block.into(); + + if recalculation_block <= now { + Self::recalculate_inflation(); + Weight::from_parts(0, 0) + } else { + Weight::from_parts(0, 0) + } + } + } + impl OnTimestampSet for Pallet { fn on_timestamp_set(_moment: Moment) { Self::payout_block_rewards(); @@ -93,9 +108,10 @@ pub mod pallet { T::PayoutPerBlock::collators(collator_amount); T::PayoutPerBlock::treasury(treasury_amount); - // TODO: benchmark this and include it into on_initialize weight cost } + + pub(crate) fn recalculate_inflation() {} } } @@ -162,6 +178,31 @@ pub struct InflationParameters { pub ideal_staking_rate: Perquintill, } +impl InflationParameters { + /// `true` if sum of all percentages is `one whole`, `false` otherwise. + pub fn is_valid(&self) -> bool { + let variables = [ + &self.treasury_part, + &self.collators_part, + &self.dapps_part, + &self.base_stakers_part, + &self.adjustable_stakers_part, + &self.bonus_part, + ]; + + variables + .iter() + .fold(Some(Perquintill::zero()), |acc, part| { + if let Some(acc) = acc { + acc.checked_add(*part) + } else { + None + } + }) + == Some(Perquintill::one()) + } +} + /// Defines functions used to payout the beneficiaries of block rewards pub trait PayoutPerBlock { /// Payout reward to the treasury. From 087cd2b8507d2be0988de9dcdfdc7ea64f17f0c7 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 24 Nov 2023 18:43:09 +0100 Subject: [PATCH 03/21] Functionality, mock & benchmarks --- pallets/pallet-inflation/src/benchmarking.rs | 179 ++++++++++ pallets/pallet-inflation/src/lib.rs | 330 +++++++++++++++++-- pallets/pallet-inflation/src/mock.rs | 174 ++++++++++ 3 files changed, 660 insertions(+), 23 deletions(-) create mode 100644 pallets/pallet-inflation/src/benchmarking.rs create mode 100644 pallets/pallet-inflation/src/mock.rs diff --git a/pallets/pallet-inflation/src/benchmarking.rs b/pallets/pallet-inflation/src/benchmarking.rs new file mode 100644 index 0000000000..65b5906df1 --- /dev/null +++ b/pallets/pallet-inflation/src/benchmarking.rs @@ -0,0 +1,179 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use super::*; + +use frame_benchmarking::v2::*; +use frame_system::{Pallet as System, RawOrigin}; +use sp_runtime::traits::One; + +/// Assert that the last event equals the provided one. +fn assert_last_event(generic_event: ::RuntimeEvent) { + System::::assert_last_event(generic_event.into()); +} + +// Set up initial config in the database, so it's not empty. +fn initial_config() { + // Some dummy inflation params + let params = InflationParameters { + max_inflation_rate: Perquintill::from_percent(7), + treasury_part: Perquintill::from_percent(5), + collators_part: Perquintill::from_percent(3), + dapps_part: Perquintill::from_percent(20), + base_stakers_part: Perquintill::from_percent(25), + adjustable_stakers_part: Perquintill::from_percent(35), + bonus_part: Perquintill::from_percent(12), + ideal_staking_rate: Perquintill::from_percent(50), + }; + assert!(params.is_valid()); + + // Some dummy inflation config + let config = InflationConfiguration { + recalculation_block: 123, + collator_reward_per_block: 11111, + treasury_reward_per_block: 33333, + dapp_reward_pool_per_era: 55555, + base_staker_reward_pool_per_era: 77777, + adjustable_staker_reward_pool_per_era: 99999, + bonus_reward_pool_per_period: 123987, + ideal_staking_rate: Perquintill::from_percent(50), + }; + + // Some dummy inflation tracker + let tracker = InflationTracker { + cap: 1000000, + issued: 30000, + }; + assert!(tracker.issued <= tracker.cap); + + InflationParams::::put(params); + InflationConfig::::put(config); + SafetyInflationTracker::::put(tracker); + + // Create some issuance so it's not zero + let dummy_account = whitelisted_caller(); + T::Currency::make_free_balance_be(&dummy_account, 1_000_000_000_000_000_000_000); +} + +#[benchmarks(where T: Config)] +mod benchmarks { + use super::*; + + #[benchmark] + fn force_set_inflation_params() { + initial_config::(); + + let mut params = InflationParameters::default(); + params.treasury_part = One::one(); + assert!(params.is_valid()); + + #[extrinsic_call] + _(RawOrigin::Root, params); + + assert_last_event::(Event::::InflationParametersForceChanged.into()); + } + + #[benchmark] + fn force_set_inflation_config() { + initial_config::(); + let config = InflationConfiguration::default(); + + #[extrinsic_call] + _(RawOrigin::Root, config.clone()); + + assert_last_event::(Event::::InflationConfigurationForceChanged { config }.into()); + } + + #[benchmark] + fn force_inflation_recalculation() { + initial_config::(); + + #[extrinsic_call] + _(RawOrigin::Root); + + let config = InflationConfig::::get(); + assert_last_event::(Event::::ForcedInflationRecalculation { config }.into()); + } + + #[benchmark] + fn hook_with_recalculation() { + initial_config::(); + + InflationConfig::::mutate(|config| { + config.recalculation_block = 0; + }); + + let block = 1; + #[block] + { + Pallet::::on_initialize(block); + Pallet::::on_finalize(block); + } + + assert!(InflationConfig::::get().recalculation_block > 0); + } + + #[benchmark] + fn hook_without_recalculation() { + initial_config::(); + + InflationConfig::::mutate(|config| { + config.recalculation_block = 2; + }); + let init_config = InflationConfig::::get(); + + let block = 1; + #[block] + { + Pallet::::on_initialize(block); + Pallet::::on_finalize(block); + } + + assert_eq!(InflationConfig::::get(), init_config); + } + + #[benchmark] + fn on_timestamp_set() { + initial_config::(); + let tracker = SafetyInflationTracker::::get(); + + #[block] + { + Pallet::::on_timestamp_set(1); + } + + // The 'sane' assumption is that at least something will be issued for treasury & collators + assert!(SafetyInflationTracker::::get().issued > tracker.issued); + } + + impl_benchmark_test_suite!( + Pallet, + crate::benchmarking::tests::new_test_ext(), + crate::mock::Test, + ); +} + +#[cfg(test)] +mod tests { + use crate::mock; + use frame_support::sp_io::TestExternalities; + + pub fn new_test_ext() -> TestExternalities { + mock::ExternalityBuilder::build() + } +} diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index 9bf607810f..10ab944a1b 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -23,17 +23,18 @@ pub use pallet::*; use astar_primitives::{Balance, BlockNumber}; -use frame_support::pallet_prelude::*; use frame_support::{ - log, - traits::{Currency, Get, Imbalance, OnTimestampSet}, + pallet_prelude::*, + traits::{Currency, OnTimestampSet}, }; use frame_system::{ensure_root, pallet_prelude::*}; -use sp_runtime::{ - traits::{CheckedAdd, Zero}, - Perquintill, -}; -use sp_std::vec; +use sp_runtime::{traits::CheckedAdd, Perquintill, Saturating}; + +#[cfg(any(feature = "runtime-benchmarks"))] +pub mod benchmarking; + +#[cfg(test)] +mod mock; #[frame_support::pallet] pub mod pallet { @@ -49,14 +50,18 @@ pub mod pallet { >>::NegativeImbalance; #[pallet::config] - pub trait Config: frame_system::Config { - // TODO: modify this so it doesn't use deprecated trait? + pub trait Config: frame_system::Config { /// The currency trait. + /// This has been soft-deprecated but it still needs to be used here in order to access `NegativeImbalance` + // which is defined in the currency trait. type Currency: Currency; /// Handler for 'per-block' payouts. type PayoutPerBlock: PayoutPerBlock>; + /// Cycle ('year') configuration - covers periods, subperiods, eras & blocks. + type CycleConfiguration: CycleConfiguration; + /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; } @@ -64,43 +69,154 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub(crate) fn deposit_event)] pub enum Event { - /// Distribution configuration has been updated. - DistributionConfigurationChanged, + /// Inflation parameters have been force changed. This will have effect on the next inflation recalculation. + InflationParametersForceChanged, + /// Inflation configuration has been force changed. This will have an immediate effect from this block. + InflationConfigurationForceChanged { config: InflationConfiguration }, + /// Inflation recalculation has been forced. + ForcedInflationRecalculation { config: InflationConfiguration }, + /// New inflation configuration has been set. + NewInflationConfiguration { config: InflationConfiguration }, + } + + #[pallet::error] + pub enum Error { + /// Sum of all parts must be one whole (100%). + InvalidInflationParameters, } /// Active inflation configuration parameteres. /// They describe current rewards, when inflation needs to be recalculated, etc. #[pallet::storage] + #[pallet::whitelist_storage] pub type InflationConfig = StorageValue<_, InflationConfiguration, ValueQuery>; /// Static inflation parameters - used to calculate active inflation configuration at certain points in time. #[pallet::storage] pub type InflationParams = StorageValue<_, InflationParameters, ValueQuery>; - #[pallet::hooks] - impl Hooks> for Pallet { - fn on_initialize(now: BlockNumberFor) -> Weight { - let recalculation_block: BlockNumberFor = - InflationConfig::::get().recalculation_block.into(); + /// Used to keep track of the approved & issued issuance. + #[pallet::storage] + #[pallet::whitelist_storage] + pub type SafetyInflationTracker = StorageValue<_, InflationTracker, ValueQuery>; - if recalculation_block <= now { - Self::recalculate_inflation(); + #[pallet::hooks] + impl Hooks for Pallet { + fn on_initialize(now: BlockNumber) -> Weight { + // Need to account for weight consumed in `on_timestamp` & `on_finalize`. + if Self::is_recalculation_in_next_block(now, &InflationConfig::::get()) { Weight::from_parts(0, 0) } else { Weight::from_parts(0, 0) } } + + fn on_finalize(now: BlockNumber) { + // Recalculation is done at the block right before the re-calculation is supposed to happen. + // This is to ensure all the rewards are paid out according to the new inflation configuration from next block. + // + // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. + // That's not a big problem, but it would be wrong! + if InflationConfig::::get().recalculation_block <= now { + let config = Self::recalculate_inflation(now); + InflationConfig::::put(config.clone()); + Self::deposit_event(Event::::NewInflationConfiguration { config }); + } + } } impl OnTimestampSet for Pallet { fn on_timestamp_set(_moment: Moment) { - Self::payout_block_rewards(); + let amount = Self::payout_block_rewards(); + + // Update the trakcer, but no check whether an overflow has happened. + // This can modified if needed, but these amounts are supposed to be small & + // collators need to be paid for producing the block. + // TODO: potential discussion topic for the review! + SafetyInflationTracker::::mutate(|tracker| { + tracker.issued.saturating_accrue(amount); + }); } } + #[pallet::call] impl Pallet { + /// Used to force-set the inflation parameters. + /// The parameters must be valid, all parts summing up to one whole (100%), otherwise the call will fail. + /// + /// Must be called by `root` origin. + /// + /// Purpose of the call is testing & handling unforseen circumstances. + #[pallet::call_index(0)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn force_set_inflation_params( + origin: OriginFor, + params: InflationParameters, + ) -> DispatchResult { + ensure_root(origin)?; + + ensure!(params.is_valid(), Error::::InvalidInflationParameters); + InflationParams::::put(params); + + Self::deposit_event(Event::::InflationParametersForceChanged); + + Ok(().into()) + } + + /// Used to force-set the inflation configuration. + /// The parameters aren't checked for validity, since essentially anything can be valid. + /// + /// Must be called by `root` origin. + /// + /// Purpose of the call is testing & handling unforseen circumstances. + #[pallet::call_index(1)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn force_set_inflation_config( + origin: OriginFor, + config: InflationConfiguration, + ) -> DispatchResult { + ensure_root(origin)?; + + InflationConfig::::put(config.clone()); + + Self::deposit_event(Event::::InflationConfigurationForceChanged { config }); + + Ok(().into()) + } + + /// Used to force inflation recalculation. + /// This is done in the same way as it would be done in an appropriate block, but this call forces it. + /// + /// Must be called by `root` origin. + /// + /// Purpose of the call is testing & handling unforseen circumstances. + #[pallet::call_index(2)] + #[pallet::weight(Weight::from_parts(0, 0))] + pub fn force_inflation_recalculation(origin: OriginFor) -> DispatchResult { + ensure_root(origin)?; + + let config = Self::recalculate_inflation(frame_system::Pallet::::block_number()); + InflationConfig::::put(config.clone()); + + Self::deposit_event(Event::::ForcedInflationRecalculation { config }); + + Ok(().into()) + } + } + + impl Pallet { + /// Used to check if inflation recalculation is supposed to happen on the next block. + fn is_recalculation_in_next_block( + now: BlockNumber, + config: &InflationConfiguration, + ) -> bool { + now.saturating_sub(config.recalculation_block) >= 1 + } + /// Payout block rewards to the beneficiaries. - pub(crate) fn payout_block_rewards() { + /// + /// Return the total amount issued. + pub(crate) fn payout_block_rewards() -> Balance { let config = InflationConfig::::get(); let collator_amount = T::Currency::issue(config.collator_reward_per_block); @@ -108,16 +224,112 @@ pub mod pallet { T::PayoutPerBlock::collators(collator_amount); T::PayoutPerBlock::treasury(treasury_amount); - // TODO: benchmark this and include it into on_initialize weight cost + + config.collator_reward_per_block + config.treasury_reward_per_block } - pub(crate) fn recalculate_inflation() {} + // Recalculates the inflation based on the total issuance & inflation parameters. + pub(crate) fn recalculate_inflation(now: BlockNumber) -> InflationConfiguration { + let params = InflationParams::::get(); + let total_issuance = T::Currency::total_issuance(); + + // 1. Calculate maximum emission over the period before the next recalculation. + let max_emission = params.max_inflation_rate * total_issuance; + + // 2. Calculate distribution of max emission between different purposes. + let treasury_emission = params.treasury_part * max_emission; + let collators_emission = params.collators_part * max_emission; + let dapps_emission = params.dapps_part * max_emission; + let base_stakers_emission = params.base_stakers_part * max_emission; + let adjustable_stakers_emission = params.adjustable_stakers_part * max_emission; + let bonus_emission = params.bonus_part * max_emission; + + // 3. Calculate concrete rewards per blocl, era or period + + // 3.1. Collator & Treausry rewards per block + let collator_reward_per_block = + collators_emission / Balance::from(T::CycleConfiguration::blocks_per_cycle()); + let treasury_reward_per_block = + treasury_emission / Balance::from(T::CycleConfiguration::blocks_per_cycle()); + + // 3.2. dApp reward pool per era + let dapp_reward_pool_per_era = dapps_emission + / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); + + // 3.3. Staking reward pools per era + let base_staker_reward_pool_per_era = base_stakers_emission + / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); + let adjustable_staker_reward_pool_per_era = adjustable_stakers_emission + / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); + + // 3.4. Bonus reward pool per period + let bonus_reward_pool_per_period = + bonus_emission / Balance::from(T::CycleConfiguration::periods_per_cycle()); + + // 4. Block at which the inflation must be recalculated. + let recalculation_block = now.saturating_add(T::CycleConfiguration::blocks_per_cycle()); + + // 5. Update the active inflation configuration. + InflationConfiguration { + recalculation_block, + collator_reward_per_block, + treasury_reward_per_block, + dapp_reward_pool_per_era, + base_staker_reward_pool_per_era, + adjustable_staker_reward_pool_per_era, + bonus_reward_pool_per_period, + ideal_staking_rate: params.ideal_staking_rate, + } + } + } + + impl StakingRewardHandler for Pallet { + fn staker_and_dapp_reward_pools(total_value_staked: Balance) -> (Balance, Balance) { + let config = InflationConfig::::get(); + + // First calculate the adjustable part of the staker reward pool, according to formula: + // adjustable_part = max_adjustable_part * min(1, total_staked_percent / ideal_staked_percent) + let total_issuance = T::Currency::total_issuance(); + + // These operations are overflow & zero-division safe. + let staked_ratio = Perquintill::from_rational(total_value_staked, total_issuance); + let adjustment_factor = staked_ratio / config.ideal_staking_rate; + + let adjustable_part = adjustment_factor * config.adjustable_staker_reward_pool_per_era; + let staker_reward_pool = config + .base_staker_reward_pool_per_era + .saturating_add(adjustable_part); + + (staker_reward_pool, config.dapp_reward_pool_per_era) + } + + fn bonus_reward_pool() -> Balance { + InflationConfig::::get().bonus_reward_pool_per_period + } + + fn payout_reward(reward: Balance, account: &T::AccountId) -> Result<(), ()> { + let mut tracker = SafetyInflationTracker::::get(); + + // This is a safety measure to prevent excessive minting. + // TODO: discuss this in review with the team. Is it strict enough? Should we use a different approach? + tracker.issued.saturating_accrue(reward); + ensure!(tracker.issued <= tracker.cap, ()); + SafetyInflationTracker::::put(tracker); + + // This can fail only if the amount is below existential deposit & the account doesn't exist, + // or if the account has no provider references. + // In both cases, the reward is lost but this can be ignored since it's extremelly unlikely + // to appear and doesn't bring any real harm. + T::Currency::deposit_creating(account, reward); + Ok(()) + } } } /// Configuration of the inflation. /// Contains information about rewards, when inflation is recalculated, etc. #[derive(Encode, Decode, MaxEncodedLen, Default, Copy, Clone, Debug, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct InflationConfiguration { /// Block number at which the inflation must be recalculated, based on the total issuance at that block. #[codec(compact)] @@ -142,12 +354,17 @@ pub struct InflationConfiguration { /// Bonus reward pool per period, for loyal stakers. #[codec(compact)] pub bonus_reward_pool_per_period: Balance, + /// The ideal staking rate, in respect to total issuance. + /// Used to derive exact amount of adjustable staker rewards. + #[codec(compact)] + pub ideal_staking_rate: Perquintill, } /// Inflation parameters. /// /// The parts of the inflation that go towards different purposes must add up to exactly 100%. #[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] +#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct InflationParameters { /// Maximum possible inflation rate, based on the total issuance at some point in time. /// From this value, all the other inflation parameters are derived. @@ -203,6 +420,18 @@ impl InflationParameters { } } +/// A safety-measure to ensure we never issue more inflation than we are supposed to. +#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] +pub struct InflationTracker { + /// The amount of inflation 'approved' for issuance so far. + #[codec(compact)] + cap: Balance, + /// The amount of inflation issued so far. + /// Must never exceed the `cap`. + #[codec(compact)] + issued: Balance, +} + /// Defines functions used to payout the beneficiaries of block rewards pub trait PayoutPerBlock { /// Payout reward to the treasury. @@ -211,3 +440,58 @@ pub trait PayoutPerBlock { /// Payout reward to the collator responsible for producing the block. fn collators(reward: Imbalance); } + +// TODO: This should be moved to primitives. +// TODO2: However this ends up looking in the end, we should not duplicate these parameters in the runtime. +// Both the dApp staking & inflation pallet should use the same source. +pub trait CycleConfiguration { + /// How many different periods are there in a cycle (a 'year'). + fn periods_per_cycle() -> u32; + + /// For how many standard era lengths does the voting subperiod last. + fn eras_per_voting_subperiod() -> u32; + + /// How many standard eras are there in the build&earn subperiod. + fn eras_per_build_and_earn_subperiod() -> u32; + + /// How many blocks are there per standard era. + fn blocks_per_era() -> u32; + + /// For how many standard era lengths does the period last. + fn eras_per_period() -> u32 { + Self::eras_per_voting_subperiod().saturating_add(Self::eras_per_build_and_earn_subperiod()) + } + + /// For how many standard era lengths does the cylce (a 'year') last. + fn eras_per_cycle() -> u32 { + Self::eras_per_period().saturating_mul(Self::periods_per_cycle()) + } + + /// How many blocks are there per cycle (a 'year'). + fn blocks_per_cycle() -> u32 { + Self::blocks_per_era().saturating_mul(Self::eras_per_cycle()) + } + + /// For how many standard era lengths do all the build&earn subperiods in a cycle last. + fn build_and_earn_eras_per_cycle() -> u32 { + Self::eras_per_build_and_earn_subperiod().saturating_mul(Self::periods_per_cycle()) + } +} + +// TODO: This should be moved to primitives. +/// Interface for staking reward handler. +/// +/// Provides reward pool values for stakers - normal & bonus rewards, as well as dApp reward pool. +/// Also provides a safe function for paying out rewards. +pub trait StakingRewardHandler { + /// Returns the staker reward pool & dApp reward pool for an era. + /// + /// The total staker reward pool is dynamic and depends on the total value staked. + fn staker_and_dapp_reward_pools(total_value_staked: Balance) -> (Balance, Balance); + + /// Returns the bonus reward pool for a period. + fn bonus_reward_pool() -> Balance; + + /// Attempts to pay out the rewards to the beneficiary. + fn payout_reward(reward: Balance, beneficiary: &AccountId) -> Result<(), ()>; +} diff --git a/pallets/pallet-inflation/src/mock.rs b/pallets/pallet-inflation/src/mock.rs new file mode 100644 index 0000000000..368002fc73 --- /dev/null +++ b/pallets/pallet-inflation/src/mock.rs @@ -0,0 +1,174 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use crate::{self as pallet_inflation, CycleConfiguration, NegativeImbalanceOf, PayoutPerBlock}; + +use frame_support::{ + construct_runtime, parameter_types, + sp_io::TestExternalities, + traits::Currency, + traits::{ConstU128, ConstU32, ConstU64}, + weights::Weight, + PalletId, +}; + +use sp_core::H256; +use sp_runtime::{ + generic::Header, // TODO: create testing primitives & move it there? + traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, +}; + +use astar_primitives::{Balance, BlockNumber}; +pub(crate) type AccountId = u64; // TODO: might also be nice to have this under testing primitives? + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +construct_runtime!( + pub struct Test + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Inflation: pallet_inflation, + } +); + +parameter_types! { + pub const BlockHashCount: BlockNumber = 250; + pub BlockWeights: frame_system::limits::BlockWeights = + frame_system::limits::BlockWeights::simple_max(Weight::from_parts(1024, 0)); +} + +impl frame_system::Config for Test { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type RuntimeOrigin = RuntimeOrigin; + type Index = u64; + type RuntimeCall = RuntimeCall; + type BlockNumber = BlockNumber; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for Test { + type MaxLocks = ConstU32<4>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; + type Balance = Balance; + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ConstU128<1>; + type AccountStore = System; + type WeightInfo = (); + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = ConstU32<0>; + type MaxFreezes = ConstU32<0>; +} + +impl pallet_timestamp::Config for Test { + type Moment = u64; + type OnTimestampSet = (); + type MinimumPeriod = ConstU64<3>; + type WeightInfo = (); +} + +// Dummy accounts used to simulate reward beneficiaries balances +pub(crate) const TREASURY_POT: PalletId = PalletId(*b"moktrsry"); +pub(crate) const COLLATOR_POT: PalletId = PalletId(*b"mokcolat"); + +pub struct DummyPayoutPerBlock; +impl PayoutPerBlock> for DummyPayoutPerBlock { + fn treasury(reward: NegativeImbalanceOf) { + Balances::resolve_creating(&TREASURY_POT.into_account_truncating(), reward); + } + + fn collators(reward: NegativeImbalanceOf) { + Balances::resolve_creating(&COLLATOR_POT.into_account_truncating(), reward); + } +} + +pub struct DummyCycleConfiguration; +impl CycleConfiguration for DummyCycleConfiguration { + fn periods_per_cycle() -> u32 { + 4 + } + + fn eras_per_voting_subperiod() -> u32 { + 2 + } + + fn eras_per_build_and_earn_subperiod() -> u32 { + 14 + } + + fn blocks_per_era() -> u32 { + 10 + } +} + +impl pallet_inflation::Config for Test { + type Currency = Balances; + type PayoutPerBlock = DummyPayoutPerBlock; + type CycleConfiguration = DummyCycleConfiguration; + type RuntimeEvent = RuntimeEvent; +} + +pub struct ExternalityBuilder; + +impl ExternalityBuilder { + pub fn build() -> TestExternalities { + let mut storage = frame_system::GenesisConfig::default() + .build_storage::() + .unwrap(); + + // TODO: set some dummy param & init config values. + + // This will cause some initial issuance + pallet_balances::GenesisConfig:: { + balances: vec![(1, 9000), (2, 800), (3, 10000)], + } + .assimilate_storage(&mut storage) + .ok(); + + let mut ext = TestExternalities::from(storage); + ext.execute_with(|| System::set_block_number(1)); + ext + } +} From ed452b2d2d8556626a779c1ef4c5c0a664eed999 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Fri, 24 Nov 2023 18:46:51 +0100 Subject: [PATCH 04/21] Empty commit since it seems last push didn't work properly From 3f968e40316dfe334fa438a8f618e48e5fb157ec Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 09:55:58 +0100 Subject: [PATCH 05/21] Tests, fixes --- pallets/pallet-inflation/Cargo.toml | 2 +- pallets/pallet-inflation/src/benchmarking.rs | 5 +- pallets/pallet-inflation/src/lib.rs | 6 +- pallets/pallet-inflation/src/mock.rs | 95 +++++++++--- pallets/pallet-inflation/src/tests.rs | 153 +++++++++++++++++++ 5 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 pallets/pallet-inflation/src/tests.rs diff --git a/pallets/pallet-inflation/Cargo.toml b/pallets/pallet-inflation/Cargo.toml index ae85f1164c..516f6642ea 100644 --- a/pallets/pallet-inflation/Cargo.toml +++ b/pallets/pallet-inflation/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "pallet-inflation" version = "0.1.0" -license = "GPL-3.0" +license = "GPL-3.0-or-later" description = "Manages inflation rate & inflation distribution" authors.workspace = true edition.workspace = true diff --git a/pallets/pallet-inflation/src/benchmarking.rs b/pallets/pallet-inflation/src/benchmarking.rs index 65b5906df1..7b3d83375e 100644 --- a/pallets/pallet-inflation/src/benchmarking.rs +++ b/pallets/pallet-inflation/src/benchmarking.rs @@ -70,7 +70,7 @@ fn initial_config() { T::Currency::make_free_balance_be(&dummy_account, 1_000_000_000_000_000_000_000); } -#[benchmarks(where T: Config)] +#[benchmarks] mod benchmarks { use super::*; @@ -137,7 +137,8 @@ mod benchmarks { }); let init_config = InflationConfig::::get(); - let block = 1; + // Has to be at least 2 blocks less than the recaulcation block. + let block = 0; #[block] { Pallet::::on_initialize(block); diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index 10ab944a1b..58619feb44 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -35,6 +35,8 @@ pub mod benchmarking; #[cfg(test)] mod mock; +#[cfg(test)] +mod tests; #[frame_support::pallet] pub mod pallet { @@ -117,7 +119,7 @@ pub mod pallet { // // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. // That's not a big problem, but it would be wrong! - if InflationConfig::::get().recalculation_block <= now { + if Self::is_recalculation_in_next_block(now, &InflationConfig::::get()) { let config = Self::recalculate_inflation(now); InflationConfig::::put(config.clone()); Self::deposit_event(Event::::NewInflationConfiguration { config }); @@ -210,7 +212,7 @@ pub mod pallet { now: BlockNumber, config: &InflationConfiguration, ) -> bool { - now.saturating_sub(config.recalculation_block) >= 1 + config.recalculation_block.saturating_sub(now) <= 1 } /// Payout block rewards to the beneficiaries. diff --git a/pallets/pallet-inflation/src/mock.rs b/pallets/pallet-inflation/src/mock.rs index 368002fc73..d14d2435f0 100644 --- a/pallets/pallet-inflation/src/mock.rs +++ b/pallets/pallet-inflation/src/mock.rs @@ -16,13 +16,17 @@ // You should have received a copy of the GNU General Public License // along with Astar. If not, see . -use crate::{self as pallet_inflation, CycleConfiguration, NegativeImbalanceOf, PayoutPerBlock}; +use crate::{ + self as pallet_inflation, CycleConfiguration, InflationConfig, InflationConfiguration, + InflationParameters, InflationParams, InflationTracker, NegativeImbalanceOf, PayoutPerBlock, + SafetyInflationTracker, +}; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, traits::Currency, - traits::{ConstU128, ConstU32, ConstU64}, + traits::{ConstU128, ConstU32, ConstU64, Hooks}, weights::Weight, PalletId, }; @@ -31,28 +35,45 @@ use sp_core::H256; use sp_runtime::{ generic::Header, // TODO: create testing primitives & move it there? traits::{AccountIdConversion, BlakeTwo256, IdentityLookup}, + Perquintill, }; use astar_primitives::{Balance, BlockNumber}; pub(crate) type AccountId = u64; // TODO: might also be nice to have this under testing primitives? +/// Initial inflation params set by the mock. +pub const INIT_PARAMS: InflationParameters = InflationParameters { + max_inflation_rate: Perquintill::from_percent(7), + treasury_part: Perquintill::from_percent(5), + collators_part: Perquintill::from_percent(3), + dapps_part: Perquintill::from_percent(20), + base_stakers_part: Perquintill::from_percent(25), + adjustable_stakers_part: Perquintill::from_percent(35), + bonus_part: Perquintill::from_percent(12), + ideal_staking_rate: Perquintill::from_percent(50), +}; + +/// Initial inflation config set by the mock. +pub const INIT_CONFIG: InflationConfiguration = InflationConfiguration { + recalculation_block: 100, + collator_reward_per_block: 1000, + treasury_reward_per_block: 1500, + dapp_reward_pool_per_era: 3000, + base_staker_reward_pool_per_era: 5000, + adjustable_staker_reward_pool_per_era: 7000, + bonus_reward_pool_per_period: 4000, + ideal_staking_rate: Perquintill::from_percent(50), +}; + +/// Initial inflation tracker set by the mock. +pub const INIT_TRACKER: InflationTracker = InflationTracker { + cap: 1000000, + issued: 30000, +}; + type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; -construct_runtime!( - pub struct Test - where - Block = Block, - NodeBlock = Block, - UncheckedExtrinsic = UncheckedExtrinsic, - { - System: frame_system, - Balances: pallet_balances, - Timestamp: pallet_timestamp, - Inflation: pallet_inflation, - } -); - parameter_types! { pub const BlockHashCount: BlockNumber = 250; pub BlockWeights: frame_system::limits::BlockWeights = @@ -150,16 +171,27 @@ impl pallet_inflation::Config for Test { type RuntimeEvent = RuntimeEvent; } -pub struct ExternalityBuilder; +construct_runtime!( + pub struct Test + where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + Timestamp: pallet_timestamp, + Inflation: pallet_inflation, + } +); +pub struct ExternalityBuilder; impl ExternalityBuilder { pub fn build() -> TestExternalities { let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); - // TODO: set some dummy param & init config values. - // This will cause some initial issuance pallet_balances::GenesisConfig:: { balances: vec![(1, 9000), (2, 800), (3, 10000)], @@ -168,7 +200,30 @@ impl ExternalityBuilder { .ok(); let mut ext = TestExternalities::from(storage); - ext.execute_with(|| System::set_block_number(1)); + ext.execute_with(|| { + // Set initial pallet inflation values + InflationConfig::::put(INIT_CONFIG); + InflationParams::::put(INIT_PARAMS); + SafetyInflationTracker::::put(INIT_TRACKER); + + System::set_block_number(1) + }); ext } } + +/// Advance to the specified block number. +/// Function assumes first block has been initialized. +pub(crate) fn advance_to_block(n: BlockNumber) { + while System::block_number() < n { + Inflation::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + Inflation::on_initialize(System::block_number()); + } +} + +/// Advance for the specified number of blocks. +/// Function assumes first block has been initialized. +pub(crate) fn advance_for_blocks(n: BlockNumber) { + advance_to_block(System::block_number() + n); +} diff --git a/pallets/pallet-inflation/src/tests.rs b/pallets/pallet-inflation/src/tests.rs new file mode 100644 index 0000000000..f46bf53fd9 --- /dev/null +++ b/pallets/pallet-inflation/src/tests.rs @@ -0,0 +1,153 @@ +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +use super::{pallet::Error, Event, *}; +use frame_support::{ + assert_noop, assert_ok, assert_storage_noop, + traits::{Hooks, OnTimestampSet}, +}; +use mock::*; +use sp_runtime::{ + traits::{AccountIdConversion, BadOrigin, Zero}, + Perquintill, +}; + +#[test] +fn force_set_inflation_params_work() { + ExternalityBuilder::build().execute_with(|| { + let mut new_params = InflationParams::::get(); + new_params.max_inflation_rate = Perquintill::from_percent(20); + assert!(new_params != InflationParams::::get(), "Sanity check"); + + // Execute call, ensure it works + assert_ok!(Inflation::force_set_inflation_params( + RuntimeOrigin::root(), + new_params + )); + System::assert_last_event(Event::InflationParametersForceChanged.into()); + + assert_eq!(InflationParams::::get(), new_params); + }) +} + +#[test] +fn force_set_inflation_params_fails() { + ExternalityBuilder::build().execute_with(|| { + let mut new_params = InflationParams::::get(); + new_params.base_stakers_part = Zero::zero(); + assert!( + !new_params.is_valid(), + "Must be invalid for check to make sense." + ); + + // Make sure it's not possible to force-set invalid params + assert_noop!( + Inflation::force_set_inflation_params(RuntimeOrigin::root(), new_params), + Error::::InvalidInflationParameters + ); + + // Make sure action is privileged + assert_noop!( + Inflation::force_set_inflation_params(RuntimeOrigin::signed(1).into(), new_params,), + BadOrigin + ); + }) +} + +#[test] +fn force_set_inflation_config_work() { + ExternalityBuilder::build().execute_with(|| { + let mut new_config = InflationConfig::::get(); + new_config.recalculation_block = new_config.recalculation_block + 50; + + // Execute call, ensure it works + assert_ok!(Inflation::force_set_inflation_config( + RuntimeOrigin::root(), + new_config + )); + System::assert_last_event( + Event::InflationConfigurationForceChanged { config: new_config }.into(), + ); + + assert_eq!(InflationConfig::::get(), new_config); + }) +} + +#[test] +fn force_set_inflation_config_fails() { + ExternalityBuilder::build().execute_with(|| { + let mut new_config = InflationConfig::::get(); + new_config.recalculation_block = new_config.recalculation_block + 50; + + // Make sure action is privileged + assert_noop!( + Inflation::force_set_inflation_config(RuntimeOrigin::signed(1), new_config), + BadOrigin + ); + }) +} + +#[test] +fn force_inflation_recalculation_work() { + ExternalityBuilder::build().execute_with(|| { + let old_config = InflationConfig::::get(); + + // Execute call, ensure it works + assert_ok!(Inflation::force_inflation_recalculation( + RuntimeOrigin::root(), + )); + + let new_config = InflationConfig::::get(); + assert!( + old_config != new_config, + "Config should change, otherwise test doesn't make sense." + ); + + System::assert_last_event( + Event::ForcedInflationRecalculation { config: new_config }.into(), + ); + }) +} + +#[test] +fn inflation_recalculation_occurs_when_exepcted() { + ExternalityBuilder::build().execute_with(|| { + let init_config = InflationConfig::::get(); + + // Make sure calls before the expected change are storage noops + advance_to_block(init_config.recalculation_block - 3); + assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 3)); + assert_storage_noop!(Inflation::on_initialize( + init_config.recalculation_block - 2 + )); + assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 2)); + assert_storage_noop!(Inflation::on_initialize( + init_config.recalculation_block - 1 + )); + + // One block before recalculation, on_finalize should calculate new inflation config + let init_config = InflationConfig::::get(); + Inflation::on_finalize(init_config.recalculation_block - 1); + assert!( + InflationConfig::::get() != init_config, + "Recalculation should have happened." + ); + + // TODO: should there be an event to mark this? + }) +} From 0a2704155bdcac418854a39c98003af894680a6a Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 11:16:17 +0100 Subject: [PATCH 06/21] More tests, minor fixes&changes --- pallets/pallet-inflation/src/lib.rs | 50 ++++++--- pallets/pallet-inflation/src/mock.rs | 6 -- pallets/pallet-inflation/src/tests.rs | 144 +++++++++++++++++++++++++- 3 files changed, 174 insertions(+), 26 deletions(-) diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index 58619feb44..902267bcb4 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -120,8 +120,13 @@ pub mod pallet { // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. // That's not a big problem, but it would be wrong! if Self::is_recalculation_in_next_block(now, &InflationConfig::::get()) { - let config = Self::recalculate_inflation(now); + let (max_emission, config) = Self::recalculate_inflation(now); InflationConfig::::put(config.clone()); + + SafetyInflationTracker::::mutate(|tracker| { + tracker.cap.saturating_accrue(max_emission); + }); + Self::deposit_event(Event::::NewInflationConfiguration { config }); } } @@ -131,7 +136,7 @@ pub mod pallet { fn on_timestamp_set(_moment: Moment) { let amount = Self::payout_block_rewards(); - // Update the trakcer, but no check whether an overflow has happened. + // Update the tracker, but no check whether an overflow has happened. // This can modified if needed, but these amounts are supposed to be small & // collators need to be paid for producing the block. // TODO: potential discussion topic for the review! @@ -197,9 +202,15 @@ pub mod pallet { pub fn force_inflation_recalculation(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; - let config = Self::recalculate_inflation(frame_system::Pallet::::block_number()); + let (max_emission, config) = + Self::recalculate_inflation(frame_system::Pallet::::block_number()); + InflationConfig::::put(config.clone()); + SafetyInflationTracker::::mutate(|tracker| { + tracker.cap.saturating_accrue(max_emission); + }); + Self::deposit_event(Event::::ForcedInflationRecalculation { config }); Ok(().into()) @@ -218,7 +229,7 @@ pub mod pallet { /// Payout block rewards to the beneficiaries. /// /// Return the total amount issued. - pub(crate) fn payout_block_rewards() -> Balance { + fn payout_block_rewards() -> Balance { let config = InflationConfig::::get(); let collator_amount = T::Currency::issue(config.collator_reward_per_block); @@ -230,8 +241,10 @@ pub mod pallet { config.collator_reward_per_block + config.treasury_reward_per_block } - // Recalculates the inflation based on the total issuance & inflation parameters. - pub(crate) fn recalculate_inflation(now: BlockNumber) -> InflationConfiguration { + /// Recalculates the inflation based on the total issuance & inflation parameters. + /// + /// Returns the maximum total emission for the cycle, and the new inflation configuration. + pub(crate) fn recalculate_inflation(now: BlockNumber) -> (Balance, InflationConfiguration) { let params = InflationParams::::get(); let total_issuance = T::Currency::total_issuance(); @@ -271,17 +284,20 @@ pub mod pallet { // 4. Block at which the inflation must be recalculated. let recalculation_block = now.saturating_add(T::CycleConfiguration::blocks_per_cycle()); - // 5. Update the active inflation configuration. - InflationConfiguration { - recalculation_block, - collator_reward_per_block, - treasury_reward_per_block, - dapp_reward_pool_per_era, - base_staker_reward_pool_per_era, - adjustable_staker_reward_pool_per_era, - bonus_reward_pool_per_period, - ideal_staking_rate: params.ideal_staking_rate, - } + // 5. Return calculated values + ( + max_emission, + InflationConfiguration { + recalculation_block, + collator_reward_per_block, + treasury_reward_per_block, + dapp_reward_pool_per_era, + base_staker_reward_pool_per_era, + adjustable_staker_reward_pool_per_era, + bonus_reward_pool_per_period, + ideal_staking_rate: params.ideal_staking_rate, + }, + ) } } diff --git a/pallets/pallet-inflation/src/mock.rs b/pallets/pallet-inflation/src/mock.rs index d14d2435f0..c8b32408ea 100644 --- a/pallets/pallet-inflation/src/mock.rs +++ b/pallets/pallet-inflation/src/mock.rs @@ -221,9 +221,3 @@ pub(crate) fn advance_to_block(n: BlockNumber) { Inflation::on_initialize(System::block_number()); } } - -/// Advance for the specified number of blocks. -/// Function assumes first block has been initialized. -pub(crate) fn advance_for_blocks(n: BlockNumber) { - advance_to_block(System::block_number() + n); -} diff --git a/pallets/pallet-inflation/src/tests.rs b/pallets/pallet-inflation/src/tests.rs index f46bf53fd9..bee9a85a54 100644 --- a/pallets/pallet-inflation/src/tests.rs +++ b/pallets/pallet-inflation/src/tests.rs @@ -142,12 +142,150 @@ fn inflation_recalculation_occurs_when_exepcted() { // One block before recalculation, on_finalize should calculate new inflation config let init_config = InflationConfig::::get(); + let init_tracker = SafetyInflationTracker::::get(); + let init_total_issuance = Balances::total_issuance(); + + // Finally trigger inflation recalculation. Inflation::on_finalize(init_config.recalculation_block - 1); + + let new_config = InflationConfig::::get(); assert!( - InflationConfig::::get() != init_config, - "Recalculation should have happened." + new_config != init_config, + "Recalculation must happen at this point." + ); + System::assert_last_event(Event::NewInflationConfiguration { config: new_config }.into()); + + assert_eq!( + Balances::total_issuance(), + init_total_issuance, + "Total issuance must not change when inflation is recalculated - nothing is minted until it's needed." + ); + + let new_tracker = SafetyInflationTracker::::get(); + assert_eq!(new_tracker.issued, init_tracker.issued); + assert_eq!(new_tracker.cap, init_tracker.cap + InflationParams::::get().max_inflation_rate * init_total_issuance); + }) +} + +#[test] +fn on_timestamp_set_payout_works() { + ExternalityBuilder::build().execute_with(|| { + // Save initial state, before the payout + let config = InflationConfig::::get(); + let init_tracker = SafetyInflationTracker::::get(); + + let init_issuance = Balances::total_issuance(); + let init_collator_pot = Balances::free_balance(&COLLATOR_POT.into_account_truncating()); + let init_treasury_pot = Balances::free_balance(&TREASURY_POT.into_account_truncating()); + + // Execute payout + Inflation::on_timestamp_set(1); + + // Verify state post payout + let expected_reward = config.collator_reward_per_block + config.treasury_reward_per_block; + + // Balance changes are as expected + assert_eq!(Balances::total_issuance(), init_issuance + expected_reward); + assert_eq!( + Balances::free_balance(&COLLATOR_POT.into_account_truncating()), + init_collator_pot + config.collator_reward_per_block + ); + assert_eq!( + Balances::free_balance(&TREASURY_POT.into_account_truncating()), + init_treasury_pot + config.treasury_reward_per_block + ); + + // Safety tracker has been properly updated + let post_tracker = SafetyInflationTracker::::get(); + assert_eq!(post_tracker.cap, init_tracker.cap); + assert_eq!(post_tracker.issued, init_tracker.issued + expected_reward); + }) +} + +#[test] +fn inflation_parameters_validity_check_works() { + // Params to be used as anchor for the tests + let base_params = INIT_PARAMS; + assert!(base_params.is_valid(), "Sanity check."); + + // Reduction of some param, it should invalidate the whole config + let mut params = base_params; + params.base_stakers_part = params.base_stakers_part - Perquintill::from_percent(1); + assert!(!params.is_valid(), "Sum is below 100%, must fail."); + + // Increase of some param, it should invalidate the whole config + let mut params = base_params; + params.base_stakers_part = params.base_stakers_part + Perquintill::from_percent(1); + assert!(!params.is_valid(), "Sum is above 100%, must fail."); + + // Some param can be zero, as long as sum remains 100% + let mut params = base_params; + params.base_stakers_part = params.base_stakers_part + params.adjustable_stakers_part; + params.adjustable_stakers_part = Zero::zero(); + assert!(params.is_valid()); +} + +#[test] +fn inflation_recalucation_works() { + ExternalityBuilder::build().execute_with(|| { + let total_issuance = Balances::total_issuance(); + let params = InflationParams::::get(); + let now = System::block_number(); + + // Calculate new config + let (max_emission, new_config) = Inflation::recalculate_inflation(now); + + // Verify basics are ok + assert_eq!(max_emission, params.max_inflation_rate * total_issuance); + assert_eq!( + new_config.recalculation_block, + now + ::CycleConfiguration::blocks_per_cycle() ); - // TODO: should there be an event to mark this? + // Verify collator rewards are as expected + assert_eq!( + new_config.collator_reward_per_block, + params.collators_part * max_emission + / Balance::from(::CycleConfiguration::blocks_per_cycle()), + ); + + // Verify treasury rewards are as expected + assert_eq!( + new_config.treasury_reward_per_block, + params.treasury_part * max_emission + / Balance::from(::CycleConfiguration::blocks_per_cycle()), + ); + + // Verify dApp rewards are as expected + assert_eq!( + new_config.dapp_reward_pool_per_era, + params.dapps_part * max_emission + / Balance::from( + ::CycleConfiguration::build_and_earn_eras_per_cycle() + ), + ); + + // Verify base & adjustable staker rewards are as expected + assert_eq!( + new_config.base_staker_reward_pool_per_era, + params.base_stakers_part * max_emission + / Balance::from( + ::CycleConfiguration::build_and_earn_eras_per_cycle() + ), + ); + assert_eq!( + new_config.adjustable_staker_reward_pool_per_era, + params.adjustable_stakers_part * max_emission + / Balance::from( + ::CycleConfiguration::build_and_earn_eras_per_cycle() + ), + ); + + // Verify bonus rewards are as expected + assert_eq!( + new_config.bonus_reward_pool_per_period, + params.bonus_part * max_emission + / Balance::from(::CycleConfiguration::periods_per_cycle()), + ); }) } From b674d1c44e54289545119828e2317f48ebd8b044 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 11:42:06 +0100 Subject: [PATCH 07/21] Zero divison protection, frontier update --- Cargo.lock | 48 ++++++++++++++-------------- pallets/pallet-inflation/src/lib.rs | 49 +++++++++++++++++++++-------- 2 files changed, 60 insertions(+), 37 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eaf6cf8260..82a1a44c31 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3066,7 +3066,7 @@ dependencies = [ [[package]] name = "fc-consensus" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "async-trait", "fp-consensus", @@ -3082,7 +3082,7 @@ dependencies = [ [[package]] name = "fc-db" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "async-trait", "fp-storage", @@ -3102,7 +3102,7 @@ dependencies = [ [[package]] name = "fc-mapping-sync" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fc-db", "fc-storage", @@ -3123,7 +3123,7 @@ dependencies = [ [[package]] name = "fc-rpc" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -3173,7 +3173,7 @@ dependencies = [ [[package]] name = "fc-rpc-core" version = "1.1.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -3186,7 +3186,7 @@ dependencies = [ [[package]] name = "fc-storage" version = "1.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -3338,7 +3338,7 @@ dependencies = [ [[package]] name = "fp-account" version = "1.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "hex", "impl-serde", @@ -3357,7 +3357,7 @@ dependencies = [ [[package]] name = "fp-consensus" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "parity-scale-codec", @@ -3369,7 +3369,7 @@ dependencies = [ [[package]] name = "fp-ethereum" version = "1.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -3383,7 +3383,7 @@ dependencies = [ [[package]] name = "fp-evm" version = "3.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "evm", "frame-support", @@ -3398,7 +3398,7 @@ dependencies = [ [[package]] name = "fp-rpc" version = "3.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -3415,7 +3415,7 @@ dependencies = [ [[package]] name = "fp-self-contained" version = "1.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "frame-support", "parity-scale-codec", @@ -3427,7 +3427,7 @@ dependencies = [ [[package]] name = "fp-storage" version = "2.0.0" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "parity-scale-codec", "serde", @@ -6794,7 +6794,7 @@ dependencies = [ [[package]] name = "pallet-base-fee" version = "1.0.0" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "frame-support", @@ -7226,7 +7226,7 @@ dependencies = [ [[package]] name = "pallet-ethereum" version = "4.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ethereum", "ethereum-types", @@ -7273,7 +7273,7 @@ dependencies = [ [[package]] name = "pallet-evm" version = "6.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "environmental", "evm", @@ -7298,7 +7298,7 @@ dependencies = [ [[package]] name = "pallet-evm-chain-id" version = "1.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "frame-support", "frame-system", @@ -7363,7 +7363,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-blake2" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", ] @@ -7371,7 +7371,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-bn128" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "sp-core", @@ -7406,7 +7406,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-dispatch" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "frame-support", @@ -7416,7 +7416,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-ed25519" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "ed25519-dalek", "fp-evm", @@ -7425,7 +7425,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-modexp" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "num", @@ -7434,7 +7434,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-sha3fips" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "tiny-keccak", @@ -7443,7 +7443,7 @@ dependencies = [ [[package]] name = "pallet-evm-precompile-simple" version = "2.0.0-dev" -source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#718c42a273280d73b71b83ff9ed1fe498dcee8f4" +source = "git+https://github.com/AstarNetwork/frontier?branch=polkadot-v0.9.43#a5481542518ec420352d263adcb2f78835ac9bc2" dependencies = [ "fp-evm", "ripemd", diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index 902267bcb4..272b3a11fd 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -259,27 +259,42 @@ pub mod pallet { let adjustable_stakers_emission = params.adjustable_stakers_part * max_emission; let bonus_emission = params.bonus_part * max_emission; - // 3. Calculate concrete rewards per blocl, era or period + // 3. Calculate concrete rewards per block, era or period + + // 3.0 Convert all 'per cycle' values to the correct type (Balance). + // Also include a safety check that none of the values is zero since this would cause a division by zero. + // The configuration & integration tests must ensure this never happens, so the following code is just an additional safety measure. + let blocks_per_cycle = match T::CycleConfiguration::blocks_per_cycle() { + 0 => Balance::MAX, + blocks_per_cycle => Balance::from(blocks_per_cycle), + }; + + let build_and_earn_eras_per_cycle = + match T::CycleConfiguration::build_and_earn_eras_per_cycle() { + 0 => Balance::MAX, + build_and_earn_eras_per_cycle => Balance::from(build_and_earn_eras_per_cycle), + }; + + let periods_per_cycle = match T::CycleConfiguration::periods_per_cycle() { + 0 => Balance::MAX, + periods_per_cycle => Balance::from(periods_per_cycle), + }; // 3.1. Collator & Treausry rewards per block - let collator_reward_per_block = - collators_emission / Balance::from(T::CycleConfiguration::blocks_per_cycle()); - let treasury_reward_per_block = - treasury_emission / Balance::from(T::CycleConfiguration::blocks_per_cycle()); + let collator_reward_per_block = collators_emission / blocks_per_cycle; + let treasury_reward_per_block = treasury_emission / blocks_per_cycle; // 3.2. dApp reward pool per era - let dapp_reward_pool_per_era = dapps_emission - / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); + let dapp_reward_pool_per_era = dapps_emission / build_and_earn_eras_per_cycle; // 3.3. Staking reward pools per era - let base_staker_reward_pool_per_era = base_stakers_emission - / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); - let adjustable_staker_reward_pool_per_era = adjustable_stakers_emission - / Balance::from(T::CycleConfiguration::build_and_earn_eras_per_cycle()); + let base_staker_reward_pool_per_era = + base_stakers_emission / build_and_earn_eras_per_cycle; + let adjustable_staker_reward_pool_per_era = + adjustable_stakers_emission / build_and_earn_eras_per_cycle; // 3.4. Bonus reward pool per period - let bonus_reward_pool_per_period = - bonus_emission / Balance::from(T::CycleConfiguration::periods_per_cycle()); + let bonus_reward_pool_per_period = bonus_emission / periods_per_cycle; // 4. Block at which the inflation must be recalculated. let recalculation_block = now.saturating_add(T::CycleConfiguration::blocks_per_cycle()); @@ -464,15 +479,23 @@ pub trait PayoutPerBlock { // Both the dApp staking & inflation pallet should use the same source. pub trait CycleConfiguration { /// How many different periods are there in a cycle (a 'year'). + /// + /// This value has to be at least 1. fn periods_per_cycle() -> u32; /// For how many standard era lengths does the voting subperiod last. + /// + /// This value has to be at least 1. fn eras_per_voting_subperiod() -> u32; /// How many standard eras are there in the build&earn subperiod. + /// + /// This value has to be at least 1. fn eras_per_build_and_earn_subperiod() -> u32; /// How many blocks are there per standard era. + /// + /// This value has to be at least 1. fn blocks_per_era() -> u32; /// For how many standard era lengths does the period last. From 69f3c5a4fe4bdbdac07ea365df521ad4c97145e1 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 12:15:42 +0100 Subject: [PATCH 08/21] More tests, minor changes --- pallets/pallet-inflation/src/lib.rs | 4 +- pallets/pallet-inflation/src/mock.rs | 6 +- pallets/pallet-inflation/src/tests.rs | 121 ++++++++++++++++++++++++++ 3 files changed, 126 insertions(+), 5 deletions(-) diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/pallet-inflation/src/lib.rs index 272b3a11fd..52fdd3981f 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/pallet-inflation/src/lib.rs @@ -340,7 +340,7 @@ pub mod pallet { InflationConfig::::get().bonus_reward_pool_per_period } - fn payout_reward(reward: Balance, account: &T::AccountId) -> Result<(), ()> { + fn payout_reward(account: &T::AccountId, reward: Balance) -> Result<(), ()> { let mut tracker = SafetyInflationTracker::::get(); // This is a safety measure to prevent excessive minting. @@ -534,5 +534,5 @@ pub trait StakingRewardHandler { fn bonus_reward_pool() -> Balance; /// Attempts to pay out the rewards to the beneficiary. - fn payout_reward(reward: Balance, beneficiary: &AccountId) -> Result<(), ()>; + fn payout_reward(beneficiary: &AccountId, reward: Balance) -> Result<(), ()>; } diff --git a/pallets/pallet-inflation/src/mock.rs b/pallets/pallet-inflation/src/mock.rs index c8b32408ea..6ce7bef392 100644 --- a/pallets/pallet-inflation/src/mock.rs +++ b/pallets/pallet-inflation/src/mock.rs @@ -148,7 +148,7 @@ impl PayoutPerBlock> for DummyPayoutPerBlock { pub struct DummyCycleConfiguration; impl CycleConfiguration for DummyCycleConfiguration { fn periods_per_cycle() -> u32 { - 4 + 5 } fn eras_per_voting_subperiod() -> u32 { @@ -156,11 +156,11 @@ impl CycleConfiguration for DummyCycleConfiguration { } fn eras_per_build_and_earn_subperiod() -> u32 { - 14 + 17 } fn blocks_per_era() -> u32 { - 10 + 11 } } diff --git a/pallets/pallet-inflation/src/tests.rs b/pallets/pallet-inflation/src/tests.rs index bee9a85a54..7e8a59f0ef 100644 --- a/pallets/pallet-inflation/src/tests.rs +++ b/pallets/pallet-inflation/src/tests.rs @@ -289,3 +289,124 @@ fn inflation_recalucation_works() { ); }) } + +#[test] +fn stakers_and_dapp_reward_pool_is_ok() { + ExternalityBuilder::build().execute_with(|| { + let total_issuance = Balances::total_issuance(); + let config = InflationConfig::::get(); + + // 1st scenario - no staked value + let (staker_pool, dapp_pool) = Inflation::staker_and_dapp_reward_pools(Zero::zero()); + assert_eq!(staker_pool, config.base_staker_reward_pool_per_era); + assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); + + // 2nd scenario - there is some staked value, larger than zero, but less than ideal + let test_rate = config.ideal_staking_rate - Perquintill::from_percent(11); + let (staker_pool, dapp_pool) = + Inflation::staker_and_dapp_reward_pools(test_rate * total_issuance); + + assert_eq!( + staker_pool, + config.base_staker_reward_pool_per_era + + test_rate / config.ideal_staking_rate + * config.adjustable_staker_reward_pool_per_era + ); + assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); + + // 3rd scenario - we're exactly at the ideal staking rate + let (staker_pool, dapp_pool) = + Inflation::staker_and_dapp_reward_pools(config.ideal_staking_rate * total_issuance); + + assert_eq!( + staker_pool, + config.base_staker_reward_pool_per_era + config.adjustable_staker_reward_pool_per_era + ); + assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); + + // 4th scenario - we're above ideal staking rate, should be the same as at the ideal staking rate regarding the pools + let test_rate = config.ideal_staking_rate + Perquintill::from_percent(13); + let (staker_pool, dapp_pool) = + Inflation::staker_and_dapp_reward_pools(test_rate * total_issuance); + + assert_eq!( + staker_pool, + config.base_staker_reward_pool_per_era + config.adjustable_staker_reward_pool_per_era + ); + assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); + }) +} + +#[test] +fn bonus_reward_pool_is_ok() { + ExternalityBuilder::build().execute_with(|| { + let config = InflationConfig::::get(); + + let bonus_pool = Inflation::bonus_reward_pool(); + assert_eq!(bonus_pool, config.bonus_reward_pool_per_period); + }) +} + +#[test] +fn payout_reward_is_ok() { + ExternalityBuilder::build().execute_with(|| { + let init_safety_tracker = SafetyInflationTracker::::get(); + + // Prepare reward payout params + let account = 1; + let reward = init_safety_tracker.cap - init_safety_tracker.issued; + let init_balance = Balances::free_balance(&account); + let init_issuance = Balances::total_issuance(); + + // Payout reward and verify balances are as expected + assert_ok!(Inflation::payout_reward(&account, reward)); + + assert_eq!(Balances::free_balance(&account), init_balance + reward); + assert_eq!(Balances::total_issuance(), init_issuance + reward); + + let post_safety_tracker = SafetyInflationTracker::::get(); + assert_eq!(post_safety_tracker.cap, init_safety_tracker.cap); + assert_eq!( + post_safety_tracker.issued, + init_safety_tracker.issued + reward + ); + }) +} + +#[test] +fn payout_reward_fails_when_cap_is_exceeded() { + ExternalityBuilder::build().execute_with(|| { + let safety_tracker = SafetyInflationTracker::::get(); + + // Prepare reward payout params. Reward must exceed the cap. + let account = 1; + let reward = safety_tracker.cap - safety_tracker.issued + 1; + + // Payout should be a failure, with storage noop. + assert_noop!(Inflation::payout_reward(&account, reward), ()); + }) +} + +#[test] +fn cylcle_configuration_works() { + ExternalityBuilder::build().execute_with(|| { + type CycleConfig = ::CycleConfiguration; + + let eras_per_period = CycleConfig::eras_per_voting_subperiod() + + CycleConfig::eras_per_build_and_earn_subperiod(); + assert_eq!(CycleConfig::eras_per_period(), eras_per_period); + + let eras_per_cycle = eras_per_period * CycleConfig::periods_per_cycle(); + assert_eq!(CycleConfig::eras_per_cycle(), eras_per_cycle); + + let blocks_per_cycle = eras_per_cycle * CycleConfig::blocks_per_era(); + assert_eq!(CycleConfig::blocks_per_cycle(), blocks_per_cycle); + + let build_and_earn_eras_per_cycle = + CycleConfig::eras_per_build_and_earn_subperiod() * CycleConfig::periods_per_cycle(); + assert_eq!( + CycleConfig::build_and_earn_eras_per_cycle(), + build_and_earn_eras_per_cycle + ); + }) +} From 7ce4c7879cd1111e9969818a72908effa2a0adc3 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 12:48:51 +0100 Subject: [PATCH 09/21] Integration & renaming --- Cargo.lock | 1 + Cargo.toml | 1 + .../Cargo.toml | 0 .../src/benchmarking.rs | 0 .../src/lib.rs | 9 ++++ .../src/mock.rs | 0 .../src/tests.rs | 0 runtime/local/Cargo.toml | 4 ++ runtime/local/src/lib.rs | 50 +++++++++++++++++-- 9 files changed, 62 insertions(+), 3 deletions(-) rename pallets/{pallet-inflation => inflation}/Cargo.toml (100%) rename pallets/{pallet-inflation => inflation}/src/benchmarking.rs (100%) rename pallets/{pallet-inflation => inflation}/src/lib.rs (98%) rename pallets/{pallet-inflation => inflation}/src/mock.rs (100%) rename pallets/{pallet-inflation => inflation}/src/tests.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 82a1a44c31..8ea25d6b60 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5545,6 +5545,7 @@ dependencies = [ "pallet-evm-precompile-substrate-ecdsa", "pallet-evm-precompile-xvm", "pallet-grandpa", + "pallet-inflation", "pallet-insecure-randomness-collective-flip", "pallet-preimage", "pallet-proxy", diff --git a/Cargo.toml b/Cargo.toml index 91789f677a..2c9871ccaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -279,6 +279,7 @@ pallet-xc-asset-config = { path = "./pallets/xc-asset-config", default-features pallet-xvm = { path = "./pallets/xvm", default-features = false } pallet-xcm = { path = "./pallets/pallet-xcm", default-features = false } pallet-ethereum-checked = { path = "./pallets/ethereum-checked", default-features = false } +pallet-inflation = { path = "./pallets/inflation", default-features = false } astar-primitives = { path = "./primitives", default-features = false } diff --git a/pallets/pallet-inflation/Cargo.toml b/pallets/inflation/Cargo.toml similarity index 100% rename from pallets/pallet-inflation/Cargo.toml rename to pallets/inflation/Cargo.toml diff --git a/pallets/pallet-inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs similarity index 100% rename from pallets/pallet-inflation/src/benchmarking.rs rename to pallets/inflation/src/benchmarking.rs diff --git a/pallets/pallet-inflation/src/lib.rs b/pallets/inflation/src/lib.rs similarity index 98% rename from pallets/pallet-inflation/src/lib.rs rename to pallets/inflation/src/lib.rs index 52fdd3981f..faeec30ac7 100644 --- a/pallets/pallet-inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -30,6 +30,8 @@ use frame_support::{ use frame_system::{ensure_root, pallet_prelude::*}; use sp_runtime::{traits::CheckedAdd, Perquintill, Saturating}; +// TODO: genesis config! + #[cfg(any(feature = "runtime-benchmarks"))] pub mod benchmarking; @@ -130,6 +132,13 @@ pub mod pallet { Self::deposit_event(Event::::NewInflationConfiguration { config }); } } + + fn integrity_test() { + assert!(T::CycleConfiguration::periods_per_cycle() > 0); + assert!(T::CycleConfiguration::eras_per_voting_subperiod() > 0); + assert!(T::CycleConfiguration::eras_per_build_and_earn_subperiod() > 0); + assert!(T::CycleConfiguration::blocks_per_era() > 0); + } } impl OnTimestampSet for Pallet { diff --git a/pallets/pallet-inflation/src/mock.rs b/pallets/inflation/src/mock.rs similarity index 100% rename from pallets/pallet-inflation/src/mock.rs rename to pallets/inflation/src/mock.rs diff --git a/pallets/pallet-inflation/src/tests.rs b/pallets/inflation/src/tests.rs similarity index 100% rename from pallets/pallet-inflation/src/tests.rs rename to pallets/inflation/src/tests.rs diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 3ecbb31994..47f7e47d1a 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -72,6 +72,7 @@ pallet-chain-extension-dapps-staking = { workspace = true } pallet-chain-extension-xvm = { workspace = true } pallet-custom-signatures = { workspace = true } pallet-dapp-staking-v3 = { workspace = true } +pallet-inflation = {workspace = true} pallet-dapps-staking = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } @@ -120,6 +121,7 @@ std = [ "pallet-custom-signatures/std", "pallet-dapps-staking/std", "pallet-dapp-staking-v3/std", + "pallet-inflation/std", "pallet-base-fee/std", "pallet-ethereum/std", "pallet-evm/std", @@ -190,6 +192,7 @@ runtime-benchmarks = [ "astar-primitives/runtime-benchmarks", "pallet-assets/runtime-benchmarks", "pallet-dapp-staking-v3/runtime-benchmarks", + "pallet-inflation/runtime-benchmarks", ] try-runtime = [ "fp-self-contained/try-runtime", @@ -205,6 +208,7 @@ try-runtime = [ "pallet-custom-signatures/try-runtime", "pallet-dapps-staking/try-runtime", "pallet-dapp-staking-v3/try-runtime", + "pallet-inflation/try-runtime", "pallet-grandpa/try-runtime", "pallet-insecure-randomness-collective-flip/try-runtime", "pallet-sudo/try-runtime", diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 12ec1e4062..e6f7ddd123 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -471,6 +471,12 @@ impl pallet_dapp_staking_v3::BenchmarkHelper> } } +parameter_types! { + pub const StandardEraLength: BlockNumber = 30; // should be 1 minute per standard era + pub const StandardErasPerVotingPeriod: u32 = 2; + pub const StandardErasPerBuildAndEarnPeriod: u32 = 10; +} + impl pallet_dapp_staking_v3::Config for Runtime { type RuntimeEvent = RuntimeEvent; type Currency = Balances; @@ -478,9 +484,9 @@ impl pallet_dapp_staking_v3::Config for Runtime { type ManagerOrigin = frame_system::EnsureRoot; type NativePriceProvider = DummyPriceProvider; type RewardPoolProvider = DummyRewardPoolProvider; - type StandardEraLength = ConstU32<30>; // should be 1 minute per standard era - type StandardErasPerVotingPeriod = ConstU32<2>; - type StandardErasPerBuildAndEarnPeriod = ConstU32<10>; + type StandardEraLength = StandardEraLength; + type StandardErasPerVotingPeriod = StandardErasPerVotingPeriod; + type StandardErasPerBuildAndEarnPeriod = StandardErasPerBuildAndEarnPeriod; type EraRewardSpanLength = ConstU32<8>; type RewardRetentionInPeriods = ConstU32<2>; type MaxNumberOfContracts = ConstU16<100>; @@ -494,6 +500,43 @@ impl pallet_dapp_staking_v3::Config for Runtime { type BenchmarkHelper = BenchmarkHelper>; } +pub struct InflationPayoutPerBlock; +impl pallet_inflation::PayoutPerBlock for InflationPayoutPerBlock { + fn treasury(reward: NegativeImbalance) { + Balances::resolve_creating(&TreasuryPalletId::get().into_account_truncating(), reward); + } + + fn collators(_reward: NegativeImbalance) { + // no collators for local dev node + } +} + +pub struct InflationCycleConfig; +impl pallet_inflation::CycleConfiguration for InflationCycleConfig { + fn periods_per_cycle() -> u32 { + 4 + } + + fn eras_per_voting_subperiod() -> u32 { + StandardErasPerVotingPeriod::get() + } + + fn eras_per_build_and_earn_subperiod() -> u32 { + StandardErasPerBuildAndEarnPeriod::get() + } + + fn blocks_per_era() -> u32 { + StandardEraLength::get() + } +} + +impl pallet_inflation::Config for Runtime { + type Currency = Balances; + type PayoutPerBlock = InflationPayoutPerBlock; + type CycleConfiguration = InflationCycleConfig; + type RuntimeEvent = RuntimeEvent; +} + impl pallet_utility::Config for Runtime { type RuntimeEvent = RuntimeEvent; type RuntimeCall = RuntimeCall; @@ -1062,6 +1105,7 @@ construct_runtime!( DappsStaking: pallet_dapps_staking, DappStaking: pallet_dapp_staking_v3, BlockReward: pallet_block_reward, + Inflation: pallet_inflation, TransactionPayment: pallet_transaction_payment, EVM: pallet_evm, Ethereum: pallet_ethereum, From 439d0c8b2cf32c1ff1a7d82bf9670c7964284b40 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 13:19:54 +0100 Subject: [PATCH 10/21] Genesis integration, more tests --- bin/collator/src/local/chain_spec.rs | 7 +++- pallets/inflation/src/benchmarking.rs | 18 ++++---- pallets/inflation/src/lib.rs | 60 ++++++++++++++++++++++----- pallets/inflation/src/mock.rs | 4 +- pallets/inflation/src/tests.rs | 27 +++++++----- runtime/local/Cargo.toml | 2 +- runtime/local/src/lib.rs | 2 + 7 files changed, 84 insertions(+), 36 deletions(-) diff --git a/bin/collator/src/local/chain_spec.rs b/bin/collator/src/local/chain_spec.rs index 1cde2e4dd7..f7e7c79e4d 100644 --- a/bin/collator/src/local/chain_spec.rs +++ b/bin/collator/src/local/chain_spec.rs @@ -21,8 +21,8 @@ use local_runtime::{ wasm_binary_unwrap, AccountId, AuraConfig, AuraId, BalancesConfig, BaseFeeConfig, BlockRewardConfig, CouncilConfig, DappStakingConfig, DemocracyConfig, EVMConfig, GenesisConfig, - GrandpaConfig, GrandpaId, Precompiles, Signature, SudoConfig, SystemConfig, - TechnicalCommitteeConfig, TreasuryConfig, VestingConfig, AST, + GrandpaConfig, GrandpaId, InflationConfig, InflationParameters, Precompiles, Signature, + SudoConfig, SystemConfig, TechnicalCommitteeConfig, TreasuryConfig, VestingConfig, AST, }; use sc_service::ChainType; use sp_core::{crypto::Ss58Codec, sr25519, Pair, Public}; @@ -213,6 +213,9 @@ fn testnet_genesis( ], slots_per_tier: vec![10, 20, 30, 40], }, + inflation: InflationConfig { + params: InflationParameters::default(), + }, } } diff --git a/pallets/inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs index 7b3d83375e..67dfda1209 100644 --- a/pallets/inflation/src/benchmarking.rs +++ b/pallets/inflation/src/benchmarking.rs @@ -20,7 +20,6 @@ use super::*; use frame_benchmarking::v2::*; use frame_system::{Pallet as System, RawOrigin}; -use sp_runtime::traits::One; /// Assert that the last event equals the provided one. fn assert_last_event(generic_event: ::RuntimeEvent) { @@ -62,7 +61,7 @@ fn initial_config() { assert!(tracker.issued <= tracker.cap); InflationParams::::put(params); - InflationConfig::::put(config); + ActiveInflationConfig::::put(config); SafetyInflationTracker::::put(tracker); // Create some issuance so it's not zero @@ -78,8 +77,7 @@ mod benchmarks { fn force_set_inflation_params() { initial_config::(); - let mut params = InflationParameters::default(); - params.treasury_part = One::one(); + let params = InflationParameters::default(); assert!(params.is_valid()); #[extrinsic_call] @@ -106,7 +104,7 @@ mod benchmarks { #[extrinsic_call] _(RawOrigin::Root); - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); assert_last_event::(Event::::ForcedInflationRecalculation { config }.into()); } @@ -114,7 +112,7 @@ mod benchmarks { fn hook_with_recalculation() { initial_config::(); - InflationConfig::::mutate(|config| { + ActiveInflationConfig::::mutate(|config| { config.recalculation_block = 0; }); @@ -125,17 +123,17 @@ mod benchmarks { Pallet::::on_finalize(block); } - assert!(InflationConfig::::get().recalculation_block > 0); + assert!(ActiveInflationConfig::::get().recalculation_block > 0); } #[benchmark] fn hook_without_recalculation() { initial_config::(); - InflationConfig::::mutate(|config| { + ActiveInflationConfig::::mutate(|config| { config.recalculation_block = 2; }); - let init_config = InflationConfig::::get(); + let init_config = ActiveInflationConfig::::get(); // Has to be at least 2 blocks less than the recaulcation block. let block = 0; @@ -145,7 +143,7 @@ mod benchmarks { Pallet::::on_finalize(block); } - assert_eq!(InflationConfig::::get(), init_config); + assert_eq!(ActiveInflationConfig::::get(), init_config); } #[benchmark] diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index faeec30ac7..a43e0083e5 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -93,7 +93,7 @@ pub mod pallet { /// They describe current rewards, when inflation needs to be recalculated, etc. #[pallet::storage] #[pallet::whitelist_storage] - pub type InflationConfig = StorageValue<_, InflationConfiguration, ValueQuery>; + pub type ActiveInflationConfig = StorageValue<_, InflationConfiguration, ValueQuery>; /// Static inflation parameters - used to calculate active inflation configuration at certain points in time. #[pallet::storage] @@ -104,11 +104,34 @@ pub mod pallet { #[pallet::whitelist_storage] pub type SafetyInflationTracker = StorageValue<_, InflationTracker, ValueQuery>; + #[pallet::genesis_config] + #[cfg_attr(feature = "std", derive(Default))] + pub struct GenesisConfig { + pub params: InflationParameters, + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + assert!(self.params.is_valid()); + + let now = frame_system::Pallet::::block_number(); + let (max_emission, config) = Pallet::::recalculate_inflation(now); + + ActiveInflationConfig::::put(config); + SafetyInflationTracker::::put(InflationTracker { + cap: max_emission, + issued: 0, + }); + InflationParams::::put(self.params); + } + } + #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(now: BlockNumber) -> Weight { // Need to account for weight consumed in `on_timestamp` & `on_finalize`. - if Self::is_recalculation_in_next_block(now, &InflationConfig::::get()) { + if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { Weight::from_parts(0, 0) } else { Weight::from_parts(0, 0) @@ -121,9 +144,9 @@ pub mod pallet { // // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. // That's not a big problem, but it would be wrong! - if Self::is_recalculation_in_next_block(now, &InflationConfig::::get()) { + if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { let (max_emission, config) = Self::recalculate_inflation(now); - InflationConfig::::put(config.clone()); + ActiveInflationConfig::::put(config.clone()); SafetyInflationTracker::::mutate(|tracker| { tracker.cap.saturating_accrue(max_emission); @@ -193,7 +216,7 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - InflationConfig::::put(config.clone()); + ActiveInflationConfig::::put(config.clone()); Self::deposit_event(Event::::InflationConfigurationForceChanged { config }); @@ -214,7 +237,7 @@ pub mod pallet { let (max_emission, config) = Self::recalculate_inflation(frame_system::Pallet::::block_number()); - InflationConfig::::put(config.clone()); + ActiveInflationConfig::::put(config.clone()); SafetyInflationTracker::::mutate(|tracker| { tracker.cap.saturating_accrue(max_emission); @@ -239,7 +262,7 @@ pub mod pallet { /// /// Return the total amount issued. fn payout_block_rewards() -> Balance { - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); let collator_amount = T::Currency::issue(config.collator_reward_per_block); let treasury_amount = T::Currency::issue(config.treasury_reward_per_block); @@ -327,7 +350,7 @@ pub mod pallet { impl StakingRewardHandler for Pallet { fn staker_and_dapp_reward_pools(total_value_staked: Balance) -> (Balance, Balance) { - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); // First calculate the adjustable part of the staker reward pool, according to formula: // adjustable_part = max_adjustable_part * min(1, total_staked_percent / ideal_staked_percent) @@ -346,7 +369,7 @@ pub mod pallet { } fn bonus_reward_pool() -> Balance { - InflationConfig::::get().bonus_reward_pool_per_period + ActiveInflationConfig::::get().bonus_reward_pool_per_period } fn payout_reward(account: &T::AccountId, reward: Balance) -> Result<(), ()> { @@ -405,7 +428,7 @@ pub struct InflationConfiguration { /// Inflation parameters. /// /// The parts of the inflation that go towards different purposes must add up to exactly 100%. -#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] +#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Debug, PartialEq, Eq, TypeInfo)] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct InflationParameters { /// Maximum possible inflation rate, based on the total issuance at some point in time. @@ -462,6 +485,23 @@ impl InflationParameters { } } +// TODO: add test for this +// Default inflation parameters, just to make sure genesis builder is happy +impl Default for InflationParameters { + fn default() -> Self { + Self { + max_inflation_rate: Perquintill::from_percent(7), + treasury_part: Perquintill::from_percent(5), + collators_part: Perquintill::from_percent(3), + dapps_part: Perquintill::from_percent(20), + base_stakers_part: Perquintill::from_percent(25), + adjustable_stakers_part: Perquintill::from_percent(35), + bonus_part: Perquintill::from_percent(12), + ideal_staking_rate: Perquintill::from_percent(50), + } + } +} + /// A safety-measure to ensure we never issue more inflation than we are supposed to. #[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] pub struct InflationTracker { diff --git a/pallets/inflation/src/mock.rs b/pallets/inflation/src/mock.rs index 6ce7bef392..a5ede8594a 100644 --- a/pallets/inflation/src/mock.rs +++ b/pallets/inflation/src/mock.rs @@ -17,7 +17,7 @@ // along with Astar. If not, see . use crate::{ - self as pallet_inflation, CycleConfiguration, InflationConfig, InflationConfiguration, + self as pallet_inflation, ActiveInflationConfig, CycleConfiguration, InflationConfiguration, InflationParameters, InflationParams, InflationTracker, NegativeImbalanceOf, PayoutPerBlock, SafetyInflationTracker, }; @@ -202,7 +202,7 @@ impl ExternalityBuilder { let mut ext = TestExternalities::from(storage); ext.execute_with(|| { // Set initial pallet inflation values - InflationConfig::::put(INIT_CONFIG); + ActiveInflationConfig::::put(INIT_CONFIG); InflationParams::::put(INIT_PARAMS); SafetyInflationTracker::::put(INIT_TRACKER); diff --git a/pallets/inflation/src/tests.rs b/pallets/inflation/src/tests.rs index 7e8a59f0ef..92d5cf7d03 100644 --- a/pallets/inflation/src/tests.rs +++ b/pallets/inflation/src/tests.rs @@ -27,6 +27,11 @@ use sp_runtime::{ Perquintill, }; +#[test] +fn default_params_are_valid() { + assert!(InflationParameters::default().is_valid()); +} + #[test] fn force_set_inflation_params_work() { ExternalityBuilder::build().execute_with(|| { @@ -72,7 +77,7 @@ fn force_set_inflation_params_fails() { #[test] fn force_set_inflation_config_work() { ExternalityBuilder::build().execute_with(|| { - let mut new_config = InflationConfig::::get(); + let mut new_config = ActiveInflationConfig::::get(); new_config.recalculation_block = new_config.recalculation_block + 50; // Execute call, ensure it works @@ -84,14 +89,14 @@ fn force_set_inflation_config_work() { Event::InflationConfigurationForceChanged { config: new_config }.into(), ); - assert_eq!(InflationConfig::::get(), new_config); + assert_eq!(ActiveInflationConfig::::get(), new_config); }) } #[test] fn force_set_inflation_config_fails() { ExternalityBuilder::build().execute_with(|| { - let mut new_config = InflationConfig::::get(); + let mut new_config = ActiveInflationConfig::::get(); new_config.recalculation_block = new_config.recalculation_block + 50; // Make sure action is privileged @@ -105,14 +110,14 @@ fn force_set_inflation_config_fails() { #[test] fn force_inflation_recalculation_work() { ExternalityBuilder::build().execute_with(|| { - let old_config = InflationConfig::::get(); + let old_config = ActiveInflationConfig::::get(); // Execute call, ensure it works assert_ok!(Inflation::force_inflation_recalculation( RuntimeOrigin::root(), )); - let new_config = InflationConfig::::get(); + let new_config = ActiveInflationConfig::::get(); assert!( old_config != new_config, "Config should change, otherwise test doesn't make sense." @@ -127,7 +132,7 @@ fn force_inflation_recalculation_work() { #[test] fn inflation_recalculation_occurs_when_exepcted() { ExternalityBuilder::build().execute_with(|| { - let init_config = InflationConfig::::get(); + let init_config = ActiveInflationConfig::::get(); // Make sure calls before the expected change are storage noops advance_to_block(init_config.recalculation_block - 3); @@ -141,14 +146,14 @@ fn inflation_recalculation_occurs_when_exepcted() { )); // One block before recalculation, on_finalize should calculate new inflation config - let init_config = InflationConfig::::get(); + let init_config = ActiveInflationConfig::::get(); let init_tracker = SafetyInflationTracker::::get(); let init_total_issuance = Balances::total_issuance(); // Finally trigger inflation recalculation. Inflation::on_finalize(init_config.recalculation_block - 1); - let new_config = InflationConfig::::get(); + let new_config = ActiveInflationConfig::::get(); assert!( new_config != init_config, "Recalculation must happen at this point." @@ -171,7 +176,7 @@ fn inflation_recalculation_occurs_when_exepcted() { fn on_timestamp_set_payout_works() { ExternalityBuilder::build().execute_with(|| { // Save initial state, before the payout - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); let init_tracker = SafetyInflationTracker::::get(); let init_issuance = Balances::total_issuance(); @@ -294,7 +299,7 @@ fn inflation_recalucation_works() { fn stakers_and_dapp_reward_pool_is_ok() { ExternalityBuilder::build().execute_with(|| { let total_issuance = Balances::total_issuance(); - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); // 1st scenario - no staked value let (staker_pool, dapp_pool) = Inflation::staker_and_dapp_reward_pools(Zero::zero()); @@ -340,7 +345,7 @@ fn stakers_and_dapp_reward_pool_is_ok() { #[test] fn bonus_reward_pool_is_ok() { ExternalityBuilder::build().execute_with(|| { - let config = InflationConfig::::get(); + let config = ActiveInflationConfig::::get(); let bonus_pool = Inflation::bonus_reward_pool(); assert_eq!(bonus_pool, config.bonus_reward_pool_per_period); diff --git a/runtime/local/Cargo.toml b/runtime/local/Cargo.toml index 47f7e47d1a..e04f05e997 100644 --- a/runtime/local/Cargo.toml +++ b/runtime/local/Cargo.toml @@ -72,13 +72,13 @@ pallet-chain-extension-dapps-staking = { workspace = true } pallet-chain-extension-xvm = { workspace = true } pallet-custom-signatures = { workspace = true } pallet-dapp-staking-v3 = { workspace = true } -pallet-inflation = {workspace = true} pallet-dapps-staking = { workspace = true } pallet-evm-precompile-assets-erc20 = { workspace = true } pallet-evm-precompile-dapps-staking = { workspace = true } pallet-evm-precompile-sr25519 = { workspace = true } pallet-evm-precompile-substrate-ecdsa = { workspace = true } pallet-evm-precompile-xvm = { workspace = true } +pallet-inflation = { workspace = true } pallet-xvm = { workspace = true } # Moonbeam tracing diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index e6f7ddd123..91f91715fb 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -73,6 +73,7 @@ use sp_version::RuntimeVersion; pub use frame_system::Call as SystemCall; pub use pallet_balances::Call as BalancesCall; pub use pallet_grandpa::AuthorityId as GrandpaId; +pub use pallet_inflation::InflationParameters; pub use pallet_timestamp::Call as TimestampCall; use pallet_transaction_payment::CurrencyAdapter; pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; @@ -1238,6 +1239,7 @@ mod benches { [pallet_block_reward, BlockReward] [pallet_ethereum_checked, EthereumChecked] [pallet_dapp_staking_v3, DappStaking] + [pallet_inflation, Inflation] ); } From 612e2427ceae2b82996fb427f0c368b8f181c84d Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 14:18:21 +0100 Subject: [PATCH 11/21] Final integration --- pallets/inflation/src/lib.rs | 36 +++++-- pallets/inflation/src/mock.rs | 1 + pallets/inflation/src/weights.rs | 177 +++++++++++++++++++++++++++++++ runtime/local/src/lib.rs | 1 + 4 files changed, 204 insertions(+), 11 deletions(-) create mode 100644 pallets/inflation/src/weights.rs diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index a43e0083e5..a5de136b91 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -30,7 +30,8 @@ use frame_support::{ use frame_system::{ensure_root, pallet_prelude::*}; use sp_runtime::{traits::CheckedAdd, Perquintill, Saturating}; -// TODO: genesis config! +pub mod weights; +pub use weights::WeightInfo; #[cfg(any(feature = "runtime-benchmarks"))] pub mod benchmarking; @@ -68,6 +69,9 @@ pub mod pallet { /// The overarching event type. type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; } #[pallet::event] @@ -130,12 +134,23 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(now: BlockNumber) -> Weight { - // Need to account for weight consumed in `on_timestamp` & `on_finalize`. - if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { - Weight::from_parts(0, 0) - } else { - Weight::from_parts(0, 0) - } + let recaulcation_weight = + if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { + T::WeightInfo::hook_with_recalculation() + } else { + T::WeightInfo::hook_without_recalculation() + }; + + // Benchmarks won't acount for whitelisted storage access so this needs to be added manually. + // + // SafetyInflationTracker - 1 DB read & write + // ActiveInflationConfig - 1 DB read + let whitelisted_weight = + ::DbWeight::get().reads_writes(2, 1); + + recaulcation_weight + .saturating_add(T::WeightInfo::on_timestamp_set()) + .saturating_add(whitelisted_weight) } fn on_finalize(now: BlockNumber) { @@ -187,7 +202,7 @@ pub mod pallet { /// /// Purpose of the call is testing & handling unforseen circumstances. #[pallet::call_index(0)] - #[pallet::weight(Weight::from_parts(0, 0))] + #[pallet::weight(T::WeightInfo::force_set_inflation_params())] pub fn force_set_inflation_params( origin: OriginFor, params: InflationParameters, @@ -209,7 +224,7 @@ pub mod pallet { /// /// Purpose of the call is testing & handling unforseen circumstances. #[pallet::call_index(1)] - #[pallet::weight(Weight::from_parts(0, 0))] + #[pallet::weight(T::WeightInfo::force_set_inflation_config())] pub fn force_set_inflation_config( origin: OriginFor, config: InflationConfiguration, @@ -230,7 +245,7 @@ pub mod pallet { /// /// Purpose of the call is testing & handling unforseen circumstances. #[pallet::call_index(2)] - #[pallet::weight(Weight::from_parts(0, 0))] + #[pallet::weight(T::WeightInfo::force_inflation_recalculation())] pub fn force_inflation_recalculation(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; @@ -485,7 +500,6 @@ impl InflationParameters { } } -// TODO: add test for this // Default inflation parameters, just to make sure genesis builder is happy impl Default for InflationParameters { fn default() -> Self { diff --git a/pallets/inflation/src/mock.rs b/pallets/inflation/src/mock.rs index a5ede8594a..c8d8d0f864 100644 --- a/pallets/inflation/src/mock.rs +++ b/pallets/inflation/src/mock.rs @@ -169,6 +169,7 @@ impl pallet_inflation::Config for Test { type PayoutPerBlock = DummyPayoutPerBlock; type CycleConfiguration = DummyCycleConfiguration; type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); } construct_runtime!( diff --git a/pallets/inflation/src/weights.rs b/pallets/inflation/src/weights.rs new file mode 100644 index 0000000000..0d87259ae4 --- /dev/null +++ b/pallets/inflation/src/weights.rs @@ -0,0 +1,177 @@ + +// This file is part of Astar. + +// Copyright (C) 2019-2023 Stake Technologies Pte.Ltd. +// SPDX-License-Identifier: GPL-3.0-or-later + +// Astar is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Astar is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Astar. If not, see . + +//! Autogenerated weights for pallet_inflation +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2023-11-27, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `Dinos-MacBook-Pro.local`, CPU: `` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/astar-collator +// benchmark +// pallet +// --chain=dev +// --steps=50 +// --repeat=20 +// --pallet=pallet_inflation +// --extrinsic=* +// --execution=wasm +// --wasm-execution=compiled +// --heap-pages=4096 +// --output=weights.rs +// --template=./scripts/templates/weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for pallet_inflation. +pub trait WeightInfo { + fn force_set_inflation_params() -> Weight; + fn force_set_inflation_config() -> Weight; + fn force_inflation_recalculation() -> Weight; + fn hook_with_recalculation() -> Weight; + fn hook_without_recalculation() -> Weight; + fn on_timestamp_set() -> Weight; +} + +/// Weights for pallet_inflation using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: Inflation InflationParams (r:0 w:1) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn force_set_inflation_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + fn force_set_inflation_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + } + /// Storage: Inflation InflationParams (r:1 w:0) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn force_inflation_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `59` + // Estimated: `1549` + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(16_000_000, 1549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + /// Storage: Inflation InflationParams (r:1 w:0) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn hook_with_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `59` + // Estimated: `1549` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 1549) + .saturating_add(T::DbWeight::get().reads(1_u64)) + } + fn hook_without_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn on_timestamp_set() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 22_000_000 picoseconds. + Weight::from_parts(23_000_000, 3593) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + /// Storage: Inflation InflationParams (r:0 w:1) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn force_set_inflation_params() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } + fn force_set_inflation_config() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(9_000_000, 0) + } + /// Storage: Inflation InflationParams (r:1 w:0) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn force_inflation_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `59` + // Estimated: `1549` + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(16_000_000, 1549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + /// Storage: Inflation InflationParams (r:1 w:0) + /// Proof: Inflation InflationParams (max_values: Some(1), max_size: Some(64), added: 559, mode: MaxEncodedLen) + fn hook_with_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `59` + // Estimated: `1549` + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(15_000_000, 1549) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + } + fn hook_without_recalculation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(3_000_000, 0) + } + /// Storage: System Account (r:1 w:1) + /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) + fn on_timestamp_set() -> Weight { + // Proof Size summary in bytes: + // Measured: `174` + // Estimated: `3593` + // Minimum execution time: 22_000_000 picoseconds. + Weight::from_parts(23_000_000, 3593) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + } +} diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 91f91715fb..e5812e180f 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -536,6 +536,7 @@ impl pallet_inflation::Config for Runtime { type PayoutPerBlock = InflationPayoutPerBlock; type CycleConfiguration = InflationCycleConfig; type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_inflation::weights::SubstrateWeight; } impl pallet_utility::Config for Runtime { From 16732004d491dcfd04858466414795d2373483b9 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 14:25:39 +0100 Subject: [PATCH 12/21] Cleanup deps --- Cargo.lock | 2 -- pallets/inflation/Cargo.toml | 3 --- 2 files changed, 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8ea25d6b60..d4d30ad493 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7664,10 +7664,8 @@ dependencies = [ "parity-scale-codec", "scale-info", "serde", - "sp-arithmetic", "sp-core", "sp-runtime", - "sp-std", ] [[package]] diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml index 516f6642ea..57479560f2 100644 --- a/pallets/inflation/Cargo.toml +++ b/pallets/inflation/Cargo.toml @@ -16,9 +16,7 @@ astar-primitives = { workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } scale-info = { workspace = true } -sp-arithmetic = { workspace = true } sp-runtime = { workspace = true } -sp-std = { workspace = true } frame-benchmarking = { workspace = true, optional = true } @@ -33,7 +31,6 @@ std = [ "parity-scale-codec/std", "sp-core/std", "scale-info/std", - "sp-std/std", "serde/std", "frame-support/std", "frame-system/std", From ec32fab47c32e62fa79be1429a5ee74516c4a9d3 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Mon, 27 Nov 2023 14:41:05 +0100 Subject: [PATCH 13/21] Division with zero test --- pallets/inflation/src/tests.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pallets/inflation/src/tests.rs b/pallets/inflation/src/tests.rs index 92d5cf7d03..dbec665a69 100644 --- a/pallets/inflation/src/tests.rs +++ b/pallets/inflation/src/tests.rs @@ -339,6 +339,20 @@ fn stakers_and_dapp_reward_pool_is_ok() { config.base_staker_reward_pool_per_era + config.adjustable_staker_reward_pool_per_era ); assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); + + // 5th scenario - ideal staking rate is zero, entire adjustable amount is always used. + ActiveInflationConfig::::mutate(|config| { + config.ideal_staking_rate = Zero::zero(); + }); + + let (staker_pool, dapp_pool) = + Inflation::staker_and_dapp_reward_pools(Perquintill::from_percent(5) * total_issuance); + + assert_eq!( + staker_pool, + config.base_staker_reward_pool_per_era + config.adjustable_staker_reward_pool_per_era + ); + assert_eq!(dapp_pool, config.dapp_reward_pool_per_era); }) } From 648e402cae54c7af44ba0f0217300ccd60970b57 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 28 Nov 2023 10:57:56 +0100 Subject: [PATCH 14/21] Comments --- pallets/inflation/src/benchmarking.rs | 2 +- pallets/inflation/src/lib.rs | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/pallets/inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs index 67dfda1209..b317eeb628 100644 --- a/pallets/inflation/src/benchmarking.rs +++ b/pallets/inflation/src/benchmarking.rs @@ -135,7 +135,7 @@ mod benchmarks { }); let init_config = ActiveInflationConfig::::get(); - // Has to be at least 2 blocks less than the recaulcation block. + // Has to be at least 2 blocks less than the recalculation block. let block = 0; #[block] { diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index a5de136b91..b5f55364bc 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -134,7 +134,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(now: BlockNumber) -> Weight { - let recaulcation_weight = + let recalculation_weight = if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { T::WeightInfo::hook_with_recalculation() } else { @@ -148,7 +148,7 @@ pub mod pallet { let whitelisted_weight = ::DbWeight::get().reads_writes(2, 1); - recaulcation_weight + recalculation_weight .saturating_add(T::WeightInfo::on_timestamp_set()) .saturating_add(whitelisted_weight) } @@ -158,7 +158,6 @@ pub mod pallet { // This is to ensure all the rewards are paid out according to the new inflation configuration from next block. // // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. - // That's not a big problem, but it would be wrong! if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { let (max_emission, config) = Self::recalculate_inflation(now); ActiveInflationConfig::::put(config.clone()); @@ -183,7 +182,7 @@ pub mod pallet { fn on_timestamp_set(_moment: Moment) { let amount = Self::payout_block_rewards(); - // Update the tracker, but no check whether an overflow has happened. + // Update the tracker, but no check whether an `issued` has exceeded `cap`. // This can modified if needed, but these amounts are supposed to be small & // collators need to be paid for producing the block. // TODO: potential discussion topic for the review! @@ -450,23 +449,23 @@ pub struct InflationParameters { /// From this value, all the other inflation parameters are derived. #[codec(compact)] pub max_inflation_rate: Perquintill, - /// How much of the inflation in total goes towards the treasury. + /// Portion of the inflation that goes towards the treasury. #[codec(compact)] pub treasury_part: Perquintill, - /// How much of the inflation in total goes towards collators. + /// Portion of the inflation that goes towards collators. #[codec(compact)] pub collators_part: Perquintill, - /// How much of the inflation in total goes towards dApp rewards (tier rewards). + /// Portion of the inflation that goes towards dApp rewards (tier rewards). #[codec(compact)] pub dapps_part: Perquintill, - /// How much of the inflation in total goes towards base staker rewards. + /// Portion of the inflation that goes towards base staker rewards. #[codec(compact)] pub base_stakers_part: Perquintill, /// How much of the inflation in total can go towards adjustable staker rewards. /// These rewards are adjusted based on the total value staked. #[codec(compact)] pub adjustable_stakers_part: Perquintill, - /// How much of the inflation in total goes towards bonus staker rewards (loyalty rewards). + /// Portion of the inflation that goes towards bonus staker rewards (loyalty rewards). #[codec(compact)] pub bonus_part: Perquintill, /// The ideal staking rate, in respect to total issuance. From 90e04a43f2df18759426c69df790f90ce911ec2c Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 28 Nov 2023 11:19:47 +0100 Subject: [PATCH 15/21] More minor changes --- pallets/inflation/src/benchmarking.rs | 4 ++-- pallets/inflation/src/lib.rs | 14 +++++++++----- pallets/inflation/src/mock.rs | 3 ++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pallets/inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs index b317eeb628..638025a4bf 100644 --- a/pallets/inflation/src/benchmarking.rs +++ b/pallets/inflation/src/benchmarking.rs @@ -149,7 +149,7 @@ mod benchmarks { #[benchmark] fn on_timestamp_set() { initial_config::(); - let tracker = SafetyInflationTracker::::get(); + let init_tracker = SafetyInflationTracker::::get(); #[block] { @@ -157,7 +157,7 @@ mod benchmarks { } // The 'sane' assumption is that at least something will be issued for treasury & collators - assert!(SafetyInflationTracker::::get().issued > tracker.issued); + assert!(SafetyInflationTracker::::get().issued > init_tracker.issued); } impl_benchmark_test_suite!( diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index b5f55364bc..5df7c846cc 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -58,7 +58,7 @@ pub mod pallet { pub trait Config: frame_system::Config { /// The currency trait. /// This has been soft-deprecated but it still needs to be used here in order to access `NegativeImbalance` - // which is defined in the currency trait. + /// which is defined in the currency trait. type Currency: Currency; /// Handler for 'per-block' payouts. @@ -114,6 +114,7 @@ pub mod pallet { pub params: InflationParameters, } + /// This should be executed **AFTER** other pallets that cause issuance to increase have been initialized. #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { @@ -158,6 +159,8 @@ pub mod pallet { // This is to ensure all the rewards are paid out according to the new inflation configuration from next block. // // If this was done in `on_initialize`, collator & treasury would receive incorrect rewards for that one block. + // + // This should be done as late as possible, to ensure all operations that modify issuance are done. if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { let (max_emission, config) = Self::recalculate_inflation(now); ActiveInflationConfig::::put(config.clone()); @@ -265,6 +268,8 @@ pub mod pallet { impl Pallet { /// Used to check if inflation recalculation is supposed to happen on the next block. + /// + /// This will be true even if recalculation is overdue, e.g. it should have happened in the current or older block. fn is_recalculation_in_next_block( now: BlockNumber, config: &InflationConfiguration, @@ -365,12 +370,11 @@ pub mod pallet { impl StakingRewardHandler for Pallet { fn staker_and_dapp_reward_pools(total_value_staked: Balance) -> (Balance, Balance) { let config = ActiveInflationConfig::::get(); + let total_issuance = T::Currency::total_issuance(); // First calculate the adjustable part of the staker reward pool, according to formula: // adjustable_part = max_adjustable_part * min(1, total_staked_percent / ideal_staked_percent) - let total_issuance = T::Currency::total_issuance(); - - // These operations are overflow & zero-division safe. + // (These operations are overflow & zero-division safe) let staked_ratio = Perquintill::from_rational(total_value_staked, total_issuance); let adjustment_factor = staked_ratio / config.ideal_staking_rate; @@ -461,7 +465,7 @@ pub struct InflationParameters { /// Portion of the inflation that goes towards base staker rewards. #[codec(compact)] pub base_stakers_part: Perquintill, - /// How much of the inflation in total can go towards adjustable staker rewards. + /// Portion of the inflation that can go towards the adjustable staker rewards. /// These rewards are adjusted based on the total value staked. #[codec(compact)] pub adjustable_stakers_part: Perquintill, diff --git a/pallets/inflation/src/mock.rs b/pallets/inflation/src/mock.rs index c8d8d0f864..3a042cebf4 100644 --- a/pallets/inflation/src/mock.rs +++ b/pallets/inflation/src/mock.rs @@ -207,7 +207,8 @@ impl ExternalityBuilder { InflationParams::::put(INIT_PARAMS); SafetyInflationTracker::::put(INIT_TRACKER); - System::set_block_number(1) + System::set_block_number(1); + Inflation::on_initialize(1); }); ext } From b1a67ae09c3d80c8ee53c9455ff01be3dd67f951 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 28 Nov 2023 11:43:53 +0100 Subject: [PATCH 16/21] Improve test coverage --- pallets/inflation/src/tests.rs | 38 +++++++++++++++++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/pallets/inflation/src/tests.rs b/pallets/inflation/src/tests.rs index dbec665a69..f0c4d2cd41 100644 --- a/pallets/inflation/src/tests.rs +++ b/pallets/inflation/src/tests.rs @@ -19,7 +19,7 @@ use super::{pallet::Error, Event, *}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, - traits::{Hooks, OnTimestampSet}, + traits::{GenesisBuild, Hooks, OnTimestampSet}, }; use mock::*; use sp_runtime::{ @@ -128,6 +128,16 @@ fn force_inflation_recalculation_work() { ); }) } +#[test] +fn force_inflation_fails_due_to_unprivileged_origin() { + ExternalityBuilder::build().execute_with(|| { + // Make sure action is privileged + assert_noop!( + Inflation::force_inflation_recalculation(RuntimeOrigin::signed(1)), + BadOrigin + ); + }) +} #[test] fn inflation_recalculation_occurs_when_exepcted() { @@ -223,6 +233,11 @@ fn inflation_parameters_validity_check_works() { params.base_stakers_part = params.base_stakers_part + Perquintill::from_percent(1); assert!(!params.is_valid(), "Sum is above 100%, must fail."); + // Excessive increase of some param, it should invalidate the whole config + let mut params = base_params; + params.treasury_part = Perquintill::from_percent(100); + assert!(!params.is_valid(), "Sum is above 100%, must fail."); + // Some param can be zero, as long as sum remains 100% let mut params = base_params; params.base_stakers_part = params.base_stakers_part + params.adjustable_stakers_part; @@ -429,3 +444,24 @@ fn cylcle_configuration_works() { ); }) } + +#[test] +fn test_genesis_build() { + ExternalityBuilder::build().execute_with(|| { + let genesis_config = InflationConfig::default(); + assert!(genesis_config.params.is_valid()); + + // Prep actions + ActiveInflationConfig::::kill(); + InflationParams::::kill(); + SafetyInflationTracker::::kill(); + + // Execute genesis build + >::build(&genesis_config); + + // Verify state is as expected + assert_eq!(InflationParams::::get(), genesis_config.params); + assert!(SafetyInflationTracker::::get().cap > 0); + assert!(ActiveInflationConfig::::get().recalculation_block > 0); + }) +} From 36032e764fdffcfa93248aeb1ca358bcb6743458 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Tue, 28 Nov 2023 15:33:29 +0100 Subject: [PATCH 17/21] Integrate into pallet-timestamp --- runtime/local/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index e5812e180f..db03dbe995 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -271,7 +271,7 @@ parameter_types! { impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; - type OnTimestampSet = (Aura, BlockReward); + type OnTimestampSet = (Aura, BlockReward, Inflation); type MinimumPeriod = MinimumPeriod; type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } From 20e3c226b52256edc6598fd1a86ae9e4985db814 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 30 Nov 2023 10:29:44 +0100 Subject: [PATCH 18/21] Remove timestamp --- Cargo.lock | 1 - pallets/inflation/Cargo.toml | 2 -- pallets/inflation/src/benchmarking.rs | 16 ++++--------- pallets/inflation/src/lib.rs | 33 ++++++++++----------------- pallets/inflation/src/mock.rs | 11 +-------- pallets/inflation/src/tests.rs | 16 ++++++------- pallets/inflation/src/weights.rs | 23 ------------------- 7 files changed, 26 insertions(+), 76 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 329acde12a..eca9cc25b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7662,7 +7662,6 @@ dependencies = [ "frame-support", "frame-system", "pallet-balances", - "pallet-timestamp", "parity-scale-codec", "scale-info", "serde", diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml index 57479560f2..864e3bff14 100644 --- a/pallets/inflation/Cargo.toml +++ b/pallets/inflation/Cargo.toml @@ -22,7 +22,6 @@ frame-benchmarking = { workspace = true, optional = true } [dev-dependencies] pallet-balances = { workspace = true } -pallet-timestamp = { workspace = true } sp-core = { workspace = true } [features] @@ -34,7 +33,6 @@ std = [ "serde/std", "frame-support/std", "frame-system/std", - "pallet-timestamp/std", "pallet-balances/std", "astar-primitives/std", ] diff --git a/pallets/inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs index 638025a4bf..c1c564d873 100644 --- a/pallets/inflation/src/benchmarking.rs +++ b/pallets/inflation/src/benchmarking.rs @@ -115,6 +115,7 @@ mod benchmarks { ActiveInflationConfig::::mutate(|config| { config.recalculation_block = 0; }); + let init_tracker = SafetyInflationTracker::::get(); let block = 1; #[block] @@ -124,6 +125,9 @@ mod benchmarks { } assert!(ActiveInflationConfig::::get().recalculation_block > 0); + + // The 'sane' assumption is that at least something will be issued for treasury & collators + assert!(SafetyInflationTracker::::get().issued > init_tracker.issued); } #[benchmark] @@ -134,6 +138,7 @@ mod benchmarks { config.recalculation_block = 2; }); let init_config = ActiveInflationConfig::::get(); + let init_tracker = SafetyInflationTracker::::get(); // Has to be at least 2 blocks less than the recalculation block. let block = 0; @@ -144,17 +149,6 @@ mod benchmarks { } assert_eq!(ActiveInflationConfig::::get(), init_config); - } - - #[benchmark] - fn on_timestamp_set() { - initial_config::(); - let init_tracker = SafetyInflationTracker::::get(); - - #[block] - { - Pallet::::on_timestamp_set(1); - } // The 'sane' assumption is that at least something will be issued for treasury & collators assert!(SafetyInflationTracker::::get().issued > init_tracker.issued); diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index 5df7c846cc..9975dacf38 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -23,10 +23,7 @@ pub use pallet::*; use astar_primitives::{Balance, BlockNumber}; -use frame_support::{ - pallet_prelude::*, - traits::{Currency, OnTimestampSet}, -}; +use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::{ensure_root, pallet_prelude::*}; use sp_runtime::{traits::CheckedAdd, Perquintill, Saturating}; @@ -135,6 +132,16 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(now: BlockNumber) -> Weight { + let amount = Self::payout_block_rewards(); + + // Update the tracker, but no check whether an `issued` has exceeded `cap`. + // This can modified if needed, but these amounts are supposed to be small & + // collators need to be paid for producing the block. + // TODO: potential discussion topic for the review! + SafetyInflationTracker::::mutate(|tracker| { + tracker.issued.saturating_accrue(amount); + }); + let recalculation_weight = if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { T::WeightInfo::hook_with_recalculation() @@ -149,9 +156,7 @@ pub mod pallet { let whitelisted_weight = ::DbWeight::get().reads_writes(2, 1); - recalculation_weight - .saturating_add(T::WeightInfo::on_timestamp_set()) - .saturating_add(whitelisted_weight) + recalculation_weight.saturating_add(whitelisted_weight) } fn on_finalize(now: BlockNumber) { @@ -181,20 +186,6 @@ pub mod pallet { } } - impl OnTimestampSet for Pallet { - fn on_timestamp_set(_moment: Moment) { - let amount = Self::payout_block_rewards(); - - // Update the tracker, but no check whether an `issued` has exceeded `cap`. - // This can modified if needed, but these amounts are supposed to be small & - // collators need to be paid for producing the block. - // TODO: potential discussion topic for the review! - SafetyInflationTracker::::mutate(|tracker| { - tracker.issued.saturating_accrue(amount); - }); - } - } - #[pallet::call] impl Pallet { /// Used to force-set the inflation parameters. diff --git a/pallets/inflation/src/mock.rs b/pallets/inflation/src/mock.rs index 3a042cebf4..c783469f1d 100644 --- a/pallets/inflation/src/mock.rs +++ b/pallets/inflation/src/mock.rs @@ -26,7 +26,7 @@ use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, traits::Currency, - traits::{ConstU128, ConstU32, ConstU64, Hooks}, + traits::{ConstU128, ConstU32, Hooks}, weights::Weight, PalletId, }; @@ -122,14 +122,6 @@ impl pallet_balances::Config for Test { type MaxHolds = ConstU32<0>; type MaxFreezes = ConstU32<0>; } - -impl pallet_timestamp::Config for Test { - type Moment = u64; - type OnTimestampSet = (); - type MinimumPeriod = ConstU64<3>; - type WeightInfo = (); -} - // Dummy accounts used to simulate reward beneficiaries balances pub(crate) const TREASURY_POT: PalletId = PalletId(*b"moktrsry"); pub(crate) const COLLATOR_POT: PalletId = PalletId(*b"mokcolat"); @@ -181,7 +173,6 @@ construct_runtime!( { System: frame_system, Balances: pallet_balances, - Timestamp: pallet_timestamp, Inflation: pallet_inflation, } ); diff --git a/pallets/inflation/src/tests.rs b/pallets/inflation/src/tests.rs index f0c4d2cd41..afc1450e1f 100644 --- a/pallets/inflation/src/tests.rs +++ b/pallets/inflation/src/tests.rs @@ -19,7 +19,7 @@ use super::{pallet::Error, Event, *}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, - traits::{GenesisBuild, Hooks, OnTimestampSet}, + traits::{GenesisBuild, Hooks}, }; use mock::*; use sp_runtime::{ @@ -144,16 +144,16 @@ fn inflation_recalculation_occurs_when_exepcted() { ExternalityBuilder::build().execute_with(|| { let init_config = ActiveInflationConfig::::get(); - // Make sure calls before the expected change are storage noops + // Make sure `on_finalize` calls before the expected change are storage noops advance_to_block(init_config.recalculation_block - 3); assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 3)); - assert_storage_noop!(Inflation::on_initialize( + Inflation::on_initialize( init_config.recalculation_block - 2 - )); + ); assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 2)); - assert_storage_noop!(Inflation::on_initialize( + Inflation::on_initialize( init_config.recalculation_block - 1 - )); + ); // One block before recalculation, on_finalize should calculate new inflation config let init_config = ActiveInflationConfig::::get(); @@ -183,7 +183,7 @@ fn inflation_recalculation_occurs_when_exepcted() { } #[test] -fn on_timestamp_set_payout_works() { +fn on_initialize_reward_payout_works() { ExternalityBuilder::build().execute_with(|| { // Save initial state, before the payout let config = ActiveInflationConfig::::get(); @@ -194,7 +194,7 @@ fn on_timestamp_set_payout_works() { let init_treasury_pot = Balances::free_balance(&TREASURY_POT.into_account_truncating()); // Execute payout - Inflation::on_timestamp_set(1); + Inflation::on_initialize(1); // Verify state post payout let expected_reward = config.collator_reward_per_block + config.treasury_reward_per_block; diff --git a/pallets/inflation/src/weights.rs b/pallets/inflation/src/weights.rs index 0d87259ae4..1cee14c199 100644 --- a/pallets/inflation/src/weights.rs +++ b/pallets/inflation/src/weights.rs @@ -54,7 +54,6 @@ pub trait WeightInfo { fn force_inflation_recalculation() -> Weight; fn hook_with_recalculation() -> Weight; fn hook_without_recalculation() -> Weight; - fn on_timestamp_set() -> Weight; } /// Weights for pallet_inflation using the Substrate node and recommended hardware. @@ -104,17 +103,6 @@ impl WeightInfo for SubstrateWeight { // Minimum execution time: 2_000_000 picoseconds. Weight::from_parts(3_000_000, 0) } - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - fn on_timestamp_set() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(23_000_000, 3593) - .saturating_add(T::DbWeight::get().reads(1_u64)) - .saturating_add(T::DbWeight::get().writes(1_u64)) - } } // For backwards compatibility and tests @@ -163,15 +151,4 @@ impl WeightInfo for () { // Minimum execution time: 2_000_000 picoseconds. Weight::from_parts(3_000_000, 0) } - /// Storage: System Account (r:1 w:1) - /// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen) - fn on_timestamp_set() -> Weight { - // Proof Size summary in bytes: - // Measured: `174` - // Estimated: `3593` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(23_000_000, 3593) - .saturating_add(RocksDbWeight::get().reads(1_u64)) - .saturating_add(RocksDbWeight::get().writes(1_u64)) - } } From 9699e5687425a0eb01dcdfcb3c71b5e0f52da85f Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 30 Nov 2023 10:48:48 +0100 Subject: [PATCH 19/21] Fix --- runtime/local/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/local/src/lib.rs b/runtime/local/src/lib.rs index 215a912375..d0cfdda419 100644 --- a/runtime/local/src/lib.rs +++ b/runtime/local/src/lib.rs @@ -271,7 +271,7 @@ parameter_types! { impl pallet_timestamp::Config for Runtime { /// A timestamp: milliseconds since the unix epoch. type Moment = u64; - type OnTimestampSet = (Aura, BlockReward, Inflation); + type OnTimestampSet = (Aura, BlockReward); type MinimumPeriod = MinimumPeriod; type WeightInfo = pallet_timestamp::weights::SubstrateWeight; } From 7ac11790fa559b8164aba90cf444c44f9ddaf964 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 30 Nov 2023 13:17:16 +0100 Subject: [PATCH 20/21] review comments --- Cargo.lock | 1 + pallets/inflation/Cargo.toml | 2 + pallets/inflation/src/benchmarking.rs | 20 ++-- pallets/inflation/src/lib.rs | 142 +++++++++++--------------- pallets/inflation/src/mock.rs | 11 +- pallets/inflation/src/tests.rs | 66 ++++++------ 6 files changed, 110 insertions(+), 132 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index eca9cc25b0..c37290c4a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7661,6 +7661,7 @@ dependencies = [ "frame-benchmarking", "frame-support", "frame-system", + "log", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml index 864e3bff14..f11bf132b5 100644 --- a/pallets/inflation/Cargo.toml +++ b/pallets/inflation/Cargo.toml @@ -11,6 +11,7 @@ repository.workspace = true [dependencies] parity-scale-codec = { workspace = true } serde = { workspace = true } +log = { workspace = true } astar-primitives = { workspace = true } frame-support = { workspace = true } @@ -28,6 +29,7 @@ sp-core = { workspace = true } default = ["std"] std = [ "parity-scale-codec/std", + "log/std", "sp-core/std", "scale-info/std", "serde/std", diff --git a/pallets/inflation/src/benchmarking.rs b/pallets/inflation/src/benchmarking.rs index c1c564d873..113c5f32a7 100644 --- a/pallets/inflation/src/benchmarking.rs +++ b/pallets/inflation/src/benchmarking.rs @@ -42,8 +42,12 @@ fn initial_config() { assert!(params.is_valid()); // Some dummy inflation config + let total_issuance = T::Currency::total_issuance(); + let issuance_safety_cap = + total_issuance.saturating_add(params.max_inflation_rate * total_issuance); let config = InflationConfiguration { recalculation_block: 123, + issuance_safety_cap, collator_reward_per_block: 11111, treasury_reward_per_block: 33333, dapp_reward_pool_per_era: 55555, @@ -53,16 +57,8 @@ fn initial_config() { ideal_staking_rate: Perquintill::from_percent(50), }; - // Some dummy inflation tracker - let tracker = InflationTracker { - cap: 1000000, - issued: 30000, - }; - assert!(tracker.issued <= tracker.cap); - InflationParams::::put(params); ActiveInflationConfig::::put(config); - SafetyInflationTracker::::put(tracker); // Create some issuance so it's not zero let dummy_account = whitelisted_caller(); @@ -115,7 +111,7 @@ mod benchmarks { ActiveInflationConfig::::mutate(|config| { config.recalculation_block = 0; }); - let init_tracker = SafetyInflationTracker::::get(); + let init_issuance = T::Currency::total_issuance(); let block = 1; #[block] @@ -127,7 +123,7 @@ mod benchmarks { assert!(ActiveInflationConfig::::get().recalculation_block > 0); // The 'sane' assumption is that at least something will be issued for treasury & collators - assert!(SafetyInflationTracker::::get().issued > init_tracker.issued); + assert!(T::Currency::total_issuance() > init_issuance); } #[benchmark] @@ -138,7 +134,7 @@ mod benchmarks { config.recalculation_block = 2; }); let init_config = ActiveInflationConfig::::get(); - let init_tracker = SafetyInflationTracker::::get(); + let init_issuance = T::Currency::total_issuance(); // Has to be at least 2 blocks less than the recalculation block. let block = 0; @@ -151,7 +147,7 @@ mod benchmarks { assert_eq!(ActiveInflationConfig::::get(), init_config); // The 'sane' assumption is that at least something will be issued for treasury & collators - assert!(SafetyInflationTracker::::get().issued > init_tracker.issued); + assert!(T::Currency::total_issuance() > init_issuance); } impl_benchmark_test_suite!( diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index 9975dacf38..e19841c71d 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -25,7 +25,7 @@ pub use pallet::*; use astar_primitives::{Balance, BlockNumber}; use frame_support::{pallet_prelude::*, traits::Currency}; use frame_system::{ensure_root, pallet_prelude::*}; -use sp_runtime::{traits::CheckedAdd, Perquintill, Saturating}; +use sp_runtime::{traits::CheckedAdd, Perquintill}; pub mod weights; pub use weights::WeightInfo; @@ -100,11 +100,6 @@ pub mod pallet { #[pallet::storage] pub type InflationParams = StorageValue<_, InflationParameters, ValueQuery>; - /// Used to keep track of the approved & issued issuance. - #[pallet::storage] - #[pallet::whitelist_storage] - pub type SafetyInflationTracker = StorageValue<_, InflationTracker, ValueQuery>; - #[pallet::genesis_config] #[cfg_attr(feature = "std", derive(Default))] pub struct GenesisConfig { @@ -118,13 +113,9 @@ pub mod pallet { assert!(self.params.is_valid()); let now = frame_system::Pallet::::block_number(); - let (max_emission, config) = Pallet::::recalculate_inflation(now); + let config = Pallet::::recalculate_inflation(now); ActiveInflationConfig::::put(config); - SafetyInflationTracker::::put(InflationTracker { - cap: max_emission, - issued: 0, - }); InflationParams::::put(self.params); } } @@ -132,15 +123,7 @@ pub mod pallet { #[pallet::hooks] impl Hooks for Pallet { fn on_initialize(now: BlockNumber) -> Weight { - let amount = Self::payout_block_rewards(); - - // Update the tracker, but no check whether an `issued` has exceeded `cap`. - // This can modified if needed, but these amounts are supposed to be small & - // collators need to be paid for producing the block. - // TODO: potential discussion topic for the review! - SafetyInflationTracker::::mutate(|tracker| { - tracker.issued.saturating_accrue(amount); - }); + Self::payout_block_rewards(); let recalculation_weight = if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { @@ -151,10 +134,8 @@ pub mod pallet { // Benchmarks won't acount for whitelisted storage access so this needs to be added manually. // - // SafetyInflationTracker - 1 DB read & write // ActiveInflationConfig - 1 DB read - let whitelisted_weight = - ::DbWeight::get().reads_writes(2, 1); + let whitelisted_weight = ::DbWeight::get().reads(1); recalculation_weight.saturating_add(whitelisted_weight) } @@ -167,13 +148,9 @@ pub mod pallet { // // This should be done as late as possible, to ensure all operations that modify issuance are done. if Self::is_recalculation_in_next_block(now, &ActiveInflationConfig::::get()) { - let (max_emission, config) = Self::recalculate_inflation(now); + let config = Self::recalculate_inflation(now); ActiveInflationConfig::::put(config.clone()); - SafetyInflationTracker::::mutate(|tracker| { - tracker.cap.saturating_accrue(max_emission); - }); - Self::deposit_event(Event::::NewInflationConfiguration { config }); } } @@ -210,48 +187,45 @@ pub mod pallet { Ok(().into()) } - /// Used to force-set the inflation configuration. - /// The parameters aren't checked for validity, since essentially anything can be valid. + /// Used to force inflation recalculation. + /// This is done in the same way as it would be done in an appropriate block, but this call forces it. /// /// Must be called by `root` origin. /// /// Purpose of the call is testing & handling unforseen circumstances. #[pallet::call_index(1)] - #[pallet::weight(T::WeightInfo::force_set_inflation_config())] - pub fn force_set_inflation_config( - origin: OriginFor, - config: InflationConfiguration, - ) -> DispatchResult { + #[pallet::weight(T::WeightInfo::force_inflation_recalculation())] + pub fn force_inflation_recalculation(origin: OriginFor) -> DispatchResult { ensure_root(origin)?; + let config = Self::recalculate_inflation(frame_system::Pallet::::block_number()); + ActiveInflationConfig::::put(config.clone()); - Self::deposit_event(Event::::InflationConfigurationForceChanged { config }); + Self::deposit_event(Event::::ForcedInflationRecalculation { config }); Ok(().into()) } - /// Used to force inflation recalculation. - /// This is done in the same way as it would be done in an appropriate block, but this call forces it. + /// Used to force-set the inflation configuration. + /// The parameters aren't checked for validity, since essentially anything can be valid. /// /// Must be called by `root` origin. /// /// Purpose of the call is testing & handling unforseen circumstances. + /// + /// **NOTE:** and a TODO, remove this before deploying on mainnet. #[pallet::call_index(2)] - #[pallet::weight(T::WeightInfo::force_inflation_recalculation())] - pub fn force_inflation_recalculation(origin: OriginFor) -> DispatchResult { + #[pallet::weight(T::WeightInfo::force_set_inflation_config())] + pub fn force_set_inflation_config( + origin: OriginFor, + config: InflationConfiguration, + ) -> DispatchResult { ensure_root(origin)?; - let (max_emission, config) = - Self::recalculate_inflation(frame_system::Pallet::::block_number()); - ActiveInflationConfig::::put(config.clone()); - SafetyInflationTracker::::mutate(|tracker| { - tracker.cap.saturating_accrue(max_emission); - }); - - Self::deposit_event(Event::::ForcedInflationRecalculation { config }); + Self::deposit_event(Event::::InflationConfigurationForceChanged { config }); Ok(().into()) } @@ -285,13 +259,14 @@ pub mod pallet { /// Recalculates the inflation based on the total issuance & inflation parameters. /// - /// Returns the maximum total emission for the cycle, and the new inflation configuration. - pub(crate) fn recalculate_inflation(now: BlockNumber) -> (Balance, InflationConfiguration) { + /// Returns the new inflation configuration. + pub(crate) fn recalculate_inflation(now: BlockNumber) -> InflationConfiguration { let params = InflationParams::::get(); let total_issuance = T::Currency::total_issuance(); // 1. Calculate maximum emission over the period before the next recalculation. let max_emission = params.max_inflation_rate * total_issuance; + let issuance_safety_cap = total_issuance.saturating_add(max_emission); // 2. Calculate distribution of max emission between different purposes. let treasury_emission = params.treasury_part * max_emission; @@ -342,19 +317,38 @@ pub mod pallet { let recalculation_block = now.saturating_add(T::CycleConfiguration::blocks_per_cycle()); // 5. Return calculated values - ( - max_emission, - InflationConfiguration { - recalculation_block, - collator_reward_per_block, - treasury_reward_per_block, - dapp_reward_pool_per_era, - base_staker_reward_pool_per_era, - adjustable_staker_reward_pool_per_era, - bonus_reward_pool_per_period, - ideal_staking_rate: params.ideal_staking_rate, - }, - ) + InflationConfiguration { + recalculation_block, + issuance_safety_cap, + collator_reward_per_block, + treasury_reward_per_block, + dapp_reward_pool_per_era, + base_staker_reward_pool_per_era, + adjustable_staker_reward_pool_per_era, + bonus_reward_pool_per_period, + ideal_staking_rate: params.ideal_staking_rate, + } + } + + /// Check if payout cap limit would be reached after payout. + fn is_payout_cap_limit_exceeded(payout: Balance) -> bool { + let config = ActiveInflationConfig::::get(); + let total_issuance = T::Currency::total_issuance(); + + let new_issuance = total_issuance.saturating_add(payout); + + if new_issuance > config.issuance_safety_cap { + log::error!("Issuance cap has been exceeded. Please report this issue ASAP!"); + } + + // Allow for 1% safety cap overflow, to prevent bad UX for users in case of rounding errors. + // This will be removed in the future once we know everything is working as expected. + let relaxed_issuance_safety_cap = config + .issuance_safety_cap + .saturating_mul(101) + .saturating_div(100); + + new_issuance > relaxed_issuance_safety_cap } } @@ -382,13 +376,8 @@ pub mod pallet { } fn payout_reward(account: &T::AccountId, reward: Balance) -> Result<(), ()> { - let mut tracker = SafetyInflationTracker::::get(); - // This is a safety measure to prevent excessive minting. - // TODO: discuss this in review with the team. Is it strict enough? Should we use a different approach? - tracker.issued.saturating_accrue(reward); - ensure!(tracker.issued <= tracker.cap, ()); - SafetyInflationTracker::::put(tracker); + ensure!(!Self::is_payout_cap_limit_exceeded(reward), ()); // This can fail only if the amount is below existential deposit & the account doesn't exist, // or if the account has no provider references. @@ -408,6 +397,9 @@ pub struct InflationConfiguration { /// Block number at which the inflation must be recalculated, based on the total issuance at that block. #[codec(compact)] pub recalculation_block: BlockNumber, + /// Maximum amount of issuance we can have during this cycle. + #[codec(compact)] + pub issuance_safety_cap: Balance, /// Reward for collator who produced the block. Always deposited the collator in full. #[codec(compact)] pub collator_reward_per_block: Balance, @@ -510,18 +502,6 @@ impl Default for InflationParameters { } } -/// A safety-measure to ensure we never issue more inflation than we are supposed to. -#[derive(Encode, Decode, MaxEncodedLen, Copy, Clone, Default, Debug, PartialEq, Eq, TypeInfo)] -pub struct InflationTracker { - /// The amount of inflation 'approved' for issuance so far. - #[codec(compact)] - cap: Balance, - /// The amount of inflation issued so far. - /// Must never exceed the `cap`. - #[codec(compact)] - issued: Balance, -} - /// Defines functions used to payout the beneficiaries of block rewards pub trait PayoutPerBlock { /// Payout reward to the treasury. diff --git a/pallets/inflation/src/mock.rs b/pallets/inflation/src/mock.rs index c783469f1d..a2c0af4b6a 100644 --- a/pallets/inflation/src/mock.rs +++ b/pallets/inflation/src/mock.rs @@ -18,8 +18,7 @@ use crate::{ self as pallet_inflation, ActiveInflationConfig, CycleConfiguration, InflationConfiguration, - InflationParameters, InflationParams, InflationTracker, NegativeImbalanceOf, PayoutPerBlock, - SafetyInflationTracker, + InflationParameters, InflationParams, NegativeImbalanceOf, PayoutPerBlock, }; use frame_support::{ @@ -56,6 +55,7 @@ pub const INIT_PARAMS: InflationParameters = InflationParameters { /// Initial inflation config set by the mock. pub const INIT_CONFIG: InflationConfiguration = InflationConfiguration { recalculation_block: 100, + issuance_safety_cap: 1_000_000, collator_reward_per_block: 1000, treasury_reward_per_block: 1500, dapp_reward_pool_per_era: 3000, @@ -65,12 +65,6 @@ pub const INIT_CONFIG: InflationConfiguration = InflationConfiguration { ideal_staking_rate: Perquintill::from_percent(50), }; -/// Initial inflation tracker set by the mock. -pub const INIT_TRACKER: InflationTracker = InflationTracker { - cap: 1000000, - issued: 30000, -}; - type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -196,7 +190,6 @@ impl ExternalityBuilder { // Set initial pallet inflation values ActiveInflationConfig::::put(INIT_CONFIG); InflationParams::::put(INIT_PARAMS); - SafetyInflationTracker::::put(INIT_TRACKER); System::set_block_number(1); Inflation::on_initialize(1); diff --git a/pallets/inflation/src/tests.rs b/pallets/inflation/src/tests.rs index afc1450e1f..518aa369c4 100644 --- a/pallets/inflation/src/tests.rs +++ b/pallets/inflation/src/tests.rs @@ -147,7 +147,7 @@ fn inflation_recalculation_occurs_when_exepcted() { // Make sure `on_finalize` calls before the expected change are storage noops advance_to_block(init_config.recalculation_block - 3); assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 3)); - Inflation::on_initialize( + Inflation::on_initialize( init_config.recalculation_block - 2 ); assert_storage_noop!(Inflation::on_finalize(init_config.recalculation_block - 2)); @@ -157,7 +157,6 @@ fn inflation_recalculation_occurs_when_exepcted() { // One block before recalculation, on_finalize should calculate new inflation config let init_config = ActiveInflationConfig::::get(); - let init_tracker = SafetyInflationTracker::::get(); let init_total_issuance = Balances::total_issuance(); // Finally trigger inflation recalculation. @@ -176,9 +175,7 @@ fn inflation_recalculation_occurs_when_exepcted() { "Total issuance must not change when inflation is recalculated - nothing is minted until it's needed." ); - let new_tracker = SafetyInflationTracker::::get(); - assert_eq!(new_tracker.issued, init_tracker.issued); - assert_eq!(new_tracker.cap, init_tracker.cap + InflationParams::::get().max_inflation_rate * init_total_issuance); + assert_eq!(new_config.issuance_safety_cap, init_total_issuance + InflationParams::::get().max_inflation_rate * init_total_issuance); }) } @@ -187,7 +184,6 @@ fn on_initialize_reward_payout_works() { ExternalityBuilder::build().execute_with(|| { // Save initial state, before the payout let config = ActiveInflationConfig::::get(); - let init_tracker = SafetyInflationTracker::::get(); let init_issuance = Balances::total_issuance(); let init_collator_pot = Balances::free_balance(&COLLATOR_POT.into_account_truncating()); @@ -209,11 +205,6 @@ fn on_initialize_reward_payout_works() { Balances::free_balance(&TREASURY_POT.into_account_truncating()), init_treasury_pot + config.treasury_reward_per_block ); - - // Safety tracker has been properly updated - let post_tracker = SafetyInflationTracker::::get(); - assert_eq!(post_tracker.cap, init_tracker.cap); - assert_eq!(post_tracker.issued, init_tracker.issued + expected_reward); }) } @@ -253,14 +244,18 @@ fn inflation_recalucation_works() { let now = System::block_number(); // Calculate new config - let (max_emission, new_config) = Inflation::recalculate_inflation(now); + let new_config = Inflation::recalculate_inflation(now); + let max_emission = params.max_inflation_rate * total_issuance; // Verify basics are ok - assert_eq!(max_emission, params.max_inflation_rate * total_issuance); assert_eq!( new_config.recalculation_block, now + ::CycleConfiguration::blocks_per_cycle() ); + assert_eq!( + new_config.issuance_safety_cap, + total_issuance + max_emission, + ); // Verify collator rewards are as expected assert_eq!( @@ -382,13 +377,12 @@ fn bonus_reward_pool_is_ok() { } #[test] -fn payout_reward_is_ok() { +fn basic_payout_reward_is_ok() { ExternalityBuilder::build().execute_with(|| { - let init_safety_tracker = SafetyInflationTracker::::get(); - // Prepare reward payout params + let config = ActiveInflationConfig::::get(); let account = 1; - let reward = init_safety_tracker.cap - init_safety_tracker.issued; + let reward = config.issuance_safety_cap - Balances::total_issuance(); let init_balance = Balances::free_balance(&account); let init_issuance = Balances::total_issuance(); @@ -397,24 +391,38 @@ fn payout_reward_is_ok() { assert_eq!(Balances::free_balance(&account), init_balance + reward); assert_eq!(Balances::total_issuance(), init_issuance + reward); - - let post_safety_tracker = SafetyInflationTracker::::get(); - assert_eq!(post_safety_tracker.cap, init_safety_tracker.cap); - assert_eq!( - post_safety_tracker.issued, - init_safety_tracker.issued + reward - ); }) } #[test] -fn payout_reward_fails_when_cap_is_exceeded() { +fn payout_reward_with_exceeded_cap_but_not_exceeded_relaxed_cap_is_ok() { ExternalityBuilder::build().execute_with(|| { - let safety_tracker = SafetyInflationTracker::::get(); + // Prepare reward payout params + let config = ActiveInflationConfig::::get(); + let account = 1; + + let relaxed_cap = config.issuance_safety_cap * 101 / 100; + let reward = relaxed_cap - Balances::total_issuance(); + let init_balance = Balances::free_balance(&account); + let init_issuance = Balances::total_issuance(); + + // Payout reward and verify balances are as expected + assert_ok!(Inflation::payout_reward(&account, reward)); + + assert_eq!(Balances::free_balance(&account), init_balance + reward); + assert_eq!(Balances::total_issuance(), init_issuance + reward); + }) +} - // Prepare reward payout params. Reward must exceed the cap. +#[test] +fn payout_reward_fails_when_relaxed_cap_is_exceeded() { + ExternalityBuilder::build().execute_with(|| { + // Prepare reward payout params + let config = ActiveInflationConfig::::get(); let account = 1; - let reward = safety_tracker.cap - safety_tracker.issued + 1; + + let relaxed_cap = config.issuance_safety_cap * 101 / 100; + let reward = relaxed_cap - Balances::total_issuance() + 1; // Payout should be a failure, with storage noop. assert_noop!(Inflation::payout_reward(&account, reward), ()); @@ -454,14 +462,12 @@ fn test_genesis_build() { // Prep actions ActiveInflationConfig::::kill(); InflationParams::::kill(); - SafetyInflationTracker::::kill(); // Execute genesis build >::build(&genesis_config); // Verify state is as expected assert_eq!(InflationParams::::get(), genesis_config.params); - assert!(SafetyInflationTracker::::get().cap > 0); assert!(ActiveInflationConfig::::get().recalculation_block > 0); }) } From cd3e473021e38100876666a3f38258dfb89284d7 Mon Sep 17 00:00:00 2001 From: Dino Pacandi Date: Thu, 30 Nov 2023 15:31:25 +0100 Subject: [PATCH 21/21] toml format --- pallets/inflation/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml index f11bf132b5..6b1065d9bb 100644 --- a/pallets/inflation/Cargo.toml +++ b/pallets/inflation/Cargo.toml @@ -9,9 +9,9 @@ homepage.workspace = true repository.workspace = true [dependencies] +log = { workspace = true } parity-scale-codec = { workspace = true } serde = { workspace = true } -log = { workspace = true } astar-primitives = { workspace = true } frame-support = { workspace = true }