diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ce0f3c789..07be41f296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ Changelog for the runtimes governed by the Polkadot Fellowship. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## [Unreleased] + +### Added + +- Polkadot: Make inflation rate adjustable by governance ([polkadot-fellows/runtimes#732](https://github.com/polkadot-fellows/runtimes/pull/732)) +- [[#755](https://github.com/polkadot-fellows/runtimes/pull/755)] Added `pallet_revive` to Kusama AssetHub. + +### Changed + +### Fixed + ## [1.6.0] 19.06.2025 ### Added @@ -18,8 +29,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - [[#711](https://github.com/polkadot-fellows/runtimes/pull/711)] Pallet-broker: add extrinsic to remove an assignment ([SDK stable2503 #7080](https://github.com/paritytech/polkadot-sdk/pull/7080)). - [[#711](https://github.com/polkadot-fellows/runtimes/pull/711)] Pallet-broker: add extrinsic to reserve a system core without having to wait two sale boundaries ([SDK stable2503 #4273](https://github.com/paritytech/polkadot-sdk/pull/4273)). -- [[#755](https://github.com/polkadot-fellows/runtimes/pull/755)] Added `pallet_revive` to Kusama AssetHub. - ### Changed - Update to SDK version `stable2503-5` ([polkadot-fellows/runtimes/pull/711](https://github.com/polkadot-fellows/runtimes/pull/711)) diff --git a/Cargo.lock b/Cargo.lock index 45ee83623c..b3d32b35f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10631,6 +10631,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-referenda", diff --git a/relay/polkadot/Cargo.toml b/relay/polkadot/Cargo.toml index 0c8ab961eb..4a748a7975 100644 --- a/relay/polkadot/Cargo.toml +++ b/relay/polkadot/Cargo.toml @@ -60,6 +60,7 @@ pallet-mmr = { workspace = true } pallet-multisig = { workspace = true } pallet-nomination-pools = { workspace = true } pallet-nomination-pools-runtime-api = { workspace = true } +pallet-parameters = { workspace = true } pallet-offences = { workspace = true } pallet-preimage = { workspace = true } pallet-proxy = { workspace = true } @@ -168,6 +169,7 @@ std = [ "pallet-nomination-pools/std", "pallet-offences-benchmarking?/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-referenda/std", @@ -248,6 +250,7 @@ runtime-benchmarks = [ "pallet-nomination-pools/runtime-benchmarks", "pallet-offences-benchmarking/runtime-benchmarks", "pallet-offences/runtime-benchmarks", + "pallet-parameters/runtime-benchmarks", "pallet-preimage/runtime-benchmarks", "pallet-proxy/runtime-benchmarks", "pallet-referenda/runtime-benchmarks", @@ -305,6 +308,7 @@ try-runtime = [ "pallet-multisig/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-referenda/try-runtime", diff --git a/relay/polkadot/src/lib.rs b/relay/polkadot/src/lib.rs index 56d034e696..1f17f0d997 100644 --- a/relay/polkadot/src/lib.rs +++ b/relay/polkadot/src/lib.rs @@ -70,6 +70,7 @@ use frame_election_provider_support::{ }; use frame_support::{ construct_runtime, + dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_state, get_preset}, parameter_types, traits::{ @@ -623,6 +624,65 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type ElectionBounds = ElectionBounds; } +/// Parameters that can be adjusted at runtime. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + /// Parameters used to calculate inflation. + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod inflation { + /// The fixed amount minted per year (~120 M DOT). + /// Default set to 8% of 15_011_657_390_566_252_333, following execution of [Referendum 1139](https://polkadot.subsquare.io/referenda/1139), block hash: `0x39422610299a75ef69860417f4d0e1d94e77699f45005645ffc5e8e619950f9f`. + #[codec(index = 0)] + pub static YearlyEmission: u128 = 1_200_932_591_245_300_186; + + /// The percentage of inflation diverted to the treasury. + /// Default to 15%, as per [Referendum 1139](https://polkadot.subsquare.io/referenda/1139). + #[codec(index = 1)] + pub static PercentToTreasury: Perquintill = Perquintill::from_percent(15); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Inflation(dynamic_params::inflation::Parameters::YearlyEmission( + dynamic_params::inflation::YearlyEmission, + Some(1_200_932_591_245_300_186u128), // Value does not matter, just for benchmarks + )) + } +} + +/// Defines which origins can modify which dynamic parameters. +pub struct DynamicParametersOrigins; +impl frame_support::traits::EnsureOriginWithArg + for DynamicParametersOrigins +{ + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + _key: &RuntimeParametersKey, + ) -> Result { + frame_system::ensure_root(origin.clone()).map_err(|_| origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + // Provide the origin for the parameter returned by `Default`: + Ok(RuntimeOrigin::root()) + } +} + +impl pallet_parameters::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeParameters = RuntimeParameters; + type AdminOrigin = DynamicParametersOrigins; + type WeightInfo = weights::pallet_parameters::WeightInfo; +} + parameter_types! { pub const BagThresholds: &'static [u64] = &bag_thresholds::THRESHOLDS; } @@ -636,7 +696,7 @@ impl pallet_bags_list::Config for Runtime { type Score = sp_npos_elections::VoteWeight; } -/// Defines how much should the inflation be for an era given its duration. +/// Defines how much the inflation should be for an era given its duration. pub struct EraPayout; impl pallet_staking::EraPayout for EraPayout { fn era_payout( @@ -648,17 +708,11 @@ impl pallet_staking::EraPayout for EraPayout { // A normal-sized era will have 1 / 365.25 here: let relative_era_len = FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into()); - - // TI at the time of execution of [Referendum 1139](https://polkadot.subsquare.io/referenda/1139), block hash: `0x39422610299a75ef69860417f4d0e1d94e77699f45005645ffc5e8e619950f9f`. - let fixed_total_issuance: i128 = 15_011_657_390_566_252_333; - let fixed_inflation_rate = FixedU128::from_rational(8, 100); - let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance); - + let yearly_emission = dynamic_params::inflation::YearlyEmission::get(); let era_emission = relative_era_len.saturating_mul_int(yearly_emission); - // 15% to treasury, as per ref 1139. - let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission); + let percent_to_treasury = dynamic_params::inflation::PercentToTreasury::get(); + let to_treasury = percent_to_treasury.mul_floor(era_emission); let to_stakers = era_emission.saturating_sub(to_treasury); - (to_stakers.saturated_into(), to_treasury.saturated_into()) } } @@ -1589,6 +1643,9 @@ construct_runtime! { // Cunning utilities. Usable initially. Utility: pallet_utility = 26, + // The settable runtime parameters module. + Parameters: pallet_parameters = 27, + // Identity: pallet_identity = 28, (removed post 1.2.8) // Proxy module. Late addition. @@ -1771,6 +1828,7 @@ mod benches { [pallet_multisig, Multisig] [pallet_nomination_pools, NominationPoolsBench::] [pallet_offences, OffencesBench::] + [pallet_parameters, Parameters] [pallet_preimage, Preimage] [pallet_proxy, Proxy] [pallet_scheduler, Scheduler] @@ -2010,10 +2068,10 @@ impl Runtime { stake_able_issuance, era_duration, ); - // reverse-engineer the current inflation by looking at the total minted against the total - // issuance. + + // Yearly emission / total issuance. let inflation = Perquintill::from_rational( - (next_mint.0 + next_mint.1) * 36525 / 100, + dynamic_params::inflation::YearlyEmission::get(), stake_able_issuance, ); @@ -2934,7 +2992,6 @@ mod multiplier_tests { dispatch::DispatchInfo, traits::{OnFinalize, PalletInfoAccess}, }; - use pallet_staking::EraPayout; use polkadot_runtime_common::{MinimumMultiplier, TargetBlockFullness}; use separator::Separatable; use sp_runtime::traits::Convert; @@ -2966,113 +3023,6 @@ mod multiplier_tests { }) } - use approx::assert_relative_eq; - const MILLISECONDS_PER_DAY: u64 = 24 * 60 * 60 * 1000; - - #[test] - fn staking_inflation_correct_single_era() { - let (to_stakers, to_treasury) = super::EraPayout::era_payout( - 123, // ignored - 456, // ignored - MILLISECONDS_PER_DAY, - ); - - // Values are within 0.1% - assert_relative_eq!(to_stakers as f64, (279_477 * UNITS) as f64, max_relative = 0.001); - assert_relative_eq!(to_treasury as f64, (49_320 * UNITS) as f64, max_relative = 0.001); - // Total per day is ~328,797 DOT - assert_relative_eq!( - (to_stakers as f64 + to_treasury as f64), - (328_797 * UNITS) as f64, - max_relative = 0.001 - ); - } - - #[test] - fn staking_inflation_correct_longer_era() { - // Twice the era duration means twice the emission: - let (to_stakers, to_treasury) = super::EraPayout::era_payout( - 123, // ignored - 456, // ignored - 2 * MILLISECONDS_PER_DAY, - ); - - assert_relative_eq!( - to_stakers as f64, - (279_477 * UNITS) as f64 * 2.0, - max_relative = 0.001 - ); - assert_relative_eq!( - to_treasury as f64, - (49_320 * UNITS) as f64 * 2.0, - max_relative = 0.001 - ); - } - - #[test] - fn staking_inflation_correct_whole_year() { - let (to_stakers, to_treasury) = super::EraPayout::era_payout( - 123, // ignored - 456, // ignored - (36525 * MILLISECONDS_PER_DAY) / 100, // 1 year - ); - - // Our yearly emissions is about 120M DOT: - let yearly_emission = 120_093_259 * UNITS; - assert_relative_eq!( - to_stakers as f64 + to_treasury as f64, - yearly_emission as f64, - max_relative = 0.001 - ); - - assert_relative_eq!(to_stakers as f64, yearly_emission as f64 * 0.85, max_relative = 0.001); - assert_relative_eq!( - to_treasury as f64, - yearly_emission as f64 * 0.15, - max_relative = 0.001 - ); - } - - // 10 years into the future, our values do not overflow. - #[test] - fn staking_inflation_correct_not_overflow() { - let (to_stakers, to_treasury) = super::EraPayout::era_payout( - 123, // ignored - 456, // ignored - (36525 * MILLISECONDS_PER_DAY) / 10, // 10 years - ); - let initial_ti: i128 = 15_011_657_390_566_252_333; - let projected_total_issuance = (to_stakers as i128 + to_treasury as i128) + initial_ti; - - // In 2034, there will be about 2.7 billion DOT in existence. - assert_relative_eq!( - projected_total_issuance as f64, - (2_700_000_000 * UNITS) as f64, - max_relative = 0.001 - ); - } - - // Print percent per year, just as convenience. - #[test] - fn staking_inflation_correct_print_percent() { - let (to_stakers, to_treasury) = super::EraPayout::era_payout( - 123, // ignored - 456, // ignored - (36525 * MILLISECONDS_PER_DAY) / 100, // 1 year - ); - let yearly_emission = to_stakers + to_treasury; - let mut ti: i128 = 15_011_657_390_566_252_333; - - for y in 0..10 { - let new_ti = ti + yearly_emission as i128; - let inflation = 100.0 * (new_ti - ti) as f64 / ti as f64; - println!("Year {y} inflation: {inflation}%"); - ti = new_ti; - - assert!(inflation <= 8.0 && inflation > 2.0, "sanity check"); - } - } - #[test] fn fast_unstake_estimate() { use pallet_fast_unstake::WeightInfo; @@ -3173,6 +3123,168 @@ mod multiplier_tests { } } } + +#[cfg(test)] +mod inflation_tests { + use super::*; + use crate::{dynamic_params::inflation, RuntimeParameters::Inflation}; + use approx::assert_relative_eq; + use frame_support::{assert_noop, assert_ok}; + use pallet_staking::EraPayout; + use sp_runtime::DispatchError; + const MILLISECONDS_PER_DAY: u64 = 24 * 60 * 60 * 1000; + + #[test] + fn staking_inflation_correct_single_era() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + MILLISECONDS_PER_DAY, + ); + + // Values are within 0.1% + assert_relative_eq!(to_stakers as f64, (279_477 * UNITS) as f64, max_relative = 0.001); + assert_relative_eq!(to_treasury as f64, (49_320 * UNITS) as f64, max_relative = 0.001); + // Total per day is ~328,797 DOT + assert_relative_eq!( + (to_stakers as f64 + to_treasury as f64), + (328_797 * UNITS) as f64, + max_relative = 0.001 + ); + }); + } + + #[test] + fn staking_inflation_correct_longer_era() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + // Twice the era duration means twice the emission: + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + 2 * MILLISECONDS_PER_DAY, + ); + + assert_relative_eq!( + to_stakers as f64, + (279_477 * UNITS) as f64 * 2.0, + max_relative = 0.001 + ); + assert_relative_eq!( + to_treasury as f64, + (49_320 * UNITS) as f64 * 2.0, + max_relative = 0.001 + ); + }); + } + + #[test] + fn staking_inflation_correct_whole_year() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + MILLISECONDS_PER_DAY * (36525 / 100), // 1 year + ); + + // Our yearly emissions is about 120M DOT: + let yearly_emission = 120_093_259 * UNITS; + assert_relative_eq!( + to_stakers as f64 + to_treasury as f64, + yearly_emission as f64, + max_relative = 0.001 + ); + + assert_relative_eq!( + to_stakers as f64, + yearly_emission as f64 * 0.85, + max_relative = 0.001 + ); + assert_relative_eq!( + to_treasury as f64, + yearly_emission as f64 * 0.15, + max_relative = 0.001 + ); + }); + } + + // 10 years into the future, our values do not overflow. + #[test] + fn staking_inflation_correct_not_overflow() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + MILLISECONDS_PER_DAY * (36525 / 10), // 10 years + ); + let initial_ti: i128 = 15_011_657_390_566_252_333; + let projected_total_issuance = (to_stakers as i128 + to_treasury as i128) + initial_ti; + + // In 2034, there will be about 2.7 billion DOT in existence. + assert_relative_eq!( + projected_total_issuance as f64, + (2_700_000_000 * UNITS) as f64, + max_relative = 0.001 + ); + }); + } + + // Print percent per year, just as convenience. + #[test] + fn staking_inflation_correct_print_percent() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + let (to_stakers, to_treasury) = super::EraPayout::era_payout( + 123, // ignored + 456, // ignored + (36525 * MILLISECONDS_PER_DAY) / 100, // 1 year + ); + let yearly_emission = to_stakers + to_treasury; + let mut ti: i128 = 15_011_657_390_566_252_333; + + for y in 0..10 { + let new_ti = ti + yearly_emission as i128; + let inflation = 100.0 * (new_ti - ti) as f64 / ti as f64; + println!("Year {y} inflation: {inflation}%"); + ti = new_ti; + + assert!(inflation <= 8.0 && inflation > 2.0, "sanity check"); + } + }); + } + + #[test] + fn inflation_only_settable_by_root() { + let mut ext = sp_io::TestExternalities::new_empty(); + ext.execute_with(|| { + // Rejected since the origin is not root. + assert_noop!( + Parameters::set_parameter( + RuntimeOrigin::signed(AccountId::from([1u8; 32])), + Inflation(inflation::Parameters::YearlyEmission( + inflation::YearlyEmission, + Some(123u128) + )), + ), + DispatchError::BadOrigin + ); + + // Accepted since origin is root. + assert_ok!(Parameters::set_parameter( + RuntimeOrigin::root(), + Inflation(inflation::Parameters::YearlyEmission( + inflation::YearlyEmission, + Some(123u128) + )), + )); + }); + } +} + #[cfg(all(test, feature = "try-runtime"))] mod remote_tests { use super::*; @@ -3346,6 +3458,8 @@ mod remote_tests { log::info!(target: LOG_TARGET, "era-duration = {:?}", average_era_duration_millis); log::info!(target: LOG_TARGET, "maxStakingRewards = {:?}", pallet_staking::MaxStakedRewards::::get()); log::info!(target: LOG_TARGET, "💰 Inflation ==> staking = {:?} / leftover = {:?}", token.amount(staking), token.amount(leftover)); + log::info!(target: LOG_TARGET, "yearlyEmission = {:?}", dynamic_params::inflation::YearlyEmission::get()); + log::info!(target: LOG_TARGET, "percentToTreasury = {:?}", dynamic_params::inflation::PercentToTreasury::get()); log::info!(target: LOG_TARGET, "inflation_rate runtime API: {:?}", Runtime::impl_experimental_inflation_info()); }); } diff --git a/relay/polkadot/src/weights/mod.rs b/relay/polkadot/src/weights/mod.rs index f9b9c932bb..ed01de078b 100644 --- a/relay/polkadot/src/weights/mod.rs +++ b/relay/polkadot/src/weights/mod.rs @@ -31,6 +31,7 @@ pub mod pallet_indices; pub mod pallet_message_queue; pub mod pallet_multisig; pub mod pallet_nomination_pools; +pub mod pallet_parameters; pub mod pallet_preimage; pub mod pallet_proxy; pub mod pallet_referenda; diff --git a/relay/polkadot/src/weights/pallet_parameters.rs b/relay/polkadot/src/weights/pallet_parameters.rs new file mode 100644 index 0000000000..c9a3ae12a8 --- /dev/null +++ b/relay/polkadot/src/weights/pallet_parameters.rs @@ -0,0 +1,63 @@ +// Copyright (C) Parity Technologies and the various Polkadot contributors, see Contributions.md +// for a list of specific contributors. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Autogenerated weights for `pallet_parameters` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 47.1.0 +//! DATE: 2025-06-11, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `0f02087fe1ea`, CPU: `QEMU Virtual CPU version 2.5+` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/polkadot-runtime/polkadot_runtime.wasm +// --pallet=pallet_parameters +// --header=/_work/fellowship-001/runtimes/runtimes/.github/scripts/cmd/file_header.txt +// --output=./relay/polkadot/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_parameters`. +pub struct WeightInfo(PhantomData); +impl pallet_parameters::WeightInfo for WeightInfo { + /// Storage: `Parameters::Parameters` (r:1 w:1) + /// Proof: `Parameters::Parameters` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `4` + // Estimated: `3501` + // Minimum execution time: 12_560_000 picoseconds. + Weight::from_parts(13_731_000, 0) + .saturating_add(Weight::from_parts(0, 3501)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +}