diff --git a/Cargo.lock b/Cargo.lock index 27a1efc38098..a84eb103548f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7020,6 +7020,7 @@ dependencies = [ "pallet-nomination-pools-runtime-api", "pallet-offences", "pallet-offences-benchmarking", + "pallet-parameters", "pallet-preimage", "pallet-proxy", "pallet-ranked-collective", @@ -10429,6 +10430,26 @@ dependencies = [ "sp-runtime", ] +[[package]] +name = "pallet-parameters" +version = "0.0.1" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-example-basic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std 14.0.0", +] + [[package]] name = "pallet-preimage" version = "28.0.0" diff --git a/Cargo.toml b/Cargo.toml index 8cbfc7a23fed..0a70bb03756b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -366,6 +366,7 @@ members = [ "substrate/frame/offences/benchmarking", "substrate/frame/paged-list", "substrate/frame/paged-list/fuzzer", + "substrate/frame/parameters", "substrate/frame/preimage", "substrate/frame/proxy", "substrate/frame/ranked-collective", diff --git a/prdoc/pr_2061.prdoc b/prdoc/pr_2061.prdoc new file mode 100644 index 000000000000..07df11ca0543 --- /dev/null +++ b/prdoc/pr_2061.prdoc @@ -0,0 +1,14 @@ +# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0 +# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json + +title: Add Parameters Pallet + +doc: + - audience: Runtime Dev + description: | + Adds `pallet-parameters` that allows to have parameters for pallet configs that dynamically change at runtime. Allows to be permissioned on a per-key basis and is compatible with ORML macros. + +crates: + - name: "pallet-parameters" + - name: "frame-support" + - name: "frame-support-procedural" diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml index 4bb5fed2b09a..76e32f02f0a3 100644 --- a/substrate/bin/node/runtime/Cargo.toml +++ b/substrate/bin/node/runtime/Cargo.toml @@ -142,6 +142,7 @@ pallet-vesting = { path = "../../../frame/vesting", default-features = false } pallet-whitelist = { path = "../../../frame/whitelist", default-features = false } pallet-tx-pause = { path = "../../../frame/tx-pause", default-features = false } pallet-safe-mode = { path = "../../../frame/safe-mode", default-features = false } +pallet-parameters = { path = "../../../frame/parameters", default-features = false } [build-dependencies] substrate-wasm-builder = { path = "../../../utils/wasm-builder", optional = true } @@ -209,6 +210,7 @@ std = [ "pallet-nomination-pools/std", "pallet-offences-benchmarking?/std", "pallet-offences/std", + "pallet-parameters/std", "pallet-preimage/std", "pallet-proxy/std", "pallet-ranked-collective/std", @@ -310,6 +312,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-ranked-collective/runtime-benchmarks", @@ -386,6 +389,7 @@ try-runtime = [ "pallet-nis/try-runtime", "pallet-nomination-pools/try-runtime", "pallet-offences/try-runtime", + "pallet-parameters/try-runtime", "pallet-preimage/try-runtime", "pallet-proxy/try-runtime", "pallet-ranked-collective/try-runtime", diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 515272cd99ab..02d5bb55429d 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -30,6 +30,7 @@ use frame_election_provider_support::{ use frame_support::{ construct_runtime, derive_impl, dispatch::DispatchClass, + dynamic_params::{dynamic_pallet_params, dynamic_params}, genesis_builder_helper::{build_config, create_default_config}, instances::{Instance1, Instance2}, ord_parameter_types, @@ -44,9 +45,9 @@ use frame_support::{ GetSalary, PayFromAccount, }, AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency, - EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter, - KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced, - WithdrawReasons, + EitherOfDiverse, EnsureOriginWithArg, EqualPrivilegeOnly, Imbalance, InsideBoth, + InstanceFilter, KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, + OnUnbalanced, WithdrawReasons, }, weights::{ constants::{ @@ -457,9 +458,6 @@ impl pallet_glutton::Config for Runtime { } parameter_types! { - pub const PreimageBaseDeposit: Balance = 1 * DOLLARS; - // One cent: $10,000 / MB - pub const PreimageByteDeposit: Balance = 1 * CENTS; pub const PreimageHoldReason: RuntimeHoldReason = RuntimeHoldReason::Preimage(pallet_preimage::HoldReason::Preimage); } @@ -472,7 +470,11 @@ impl pallet_preimage::Config for Runtime { AccountId, Balances, PreimageHoldReason, - LinearStoragePrice, + LinearStoragePrice< + dynamic_params::storage::BaseDeposit, + dynamic_params::storage::ByteDeposit, + Balance, + >, >; } @@ -1326,9 +1328,6 @@ impl pallet_tips::Config for Runtime { } parameter_types! { - pub const DepositPerItem: Balance = deposit(1, 0); - pub const DepositPerByte: Balance = deposit(0, 1); - pub const DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); pub Schedule: pallet_contracts::Schedule = Default::default(); pub CodeHashLockupDepositPercent: Perbill = Perbill::from_percent(30); } @@ -1346,9 +1345,9 @@ impl pallet_contracts::Config for Runtime { /// change because that would break already deployed contracts. The `Call` structure itself /// is not allowed to change the indices of existing pallets, too. type CallFilter = Nothing; - type DepositPerItem = DepositPerItem; - type DepositPerByte = DepositPerByte; - type DefaultDepositLimit = DefaultDepositLimit; + type DepositPerItem = dynamic_params::contracts::DepositPerItem; + type DepositPerByte = dynamic_params::contracts::DepositPerByte; + type DefaultDepositLimit = dynamic_params::contracts::DefaultDepositLimit; type CallStack = [pallet_contracts::Frame; 5]; type WeightPrice = pallet_transaction_payment::Pallet; type WeightInfo = pallet_contracts::weights::SubstrateWeight; @@ -2088,6 +2087,81 @@ impl pallet_mixnet::Config for Runtime { type MinMixnodes = ConstU32<7>; // Low to allow small testing networks } +/// Dynamic parameters that can be changed at runtime through the +/// `pallet_parameters::set_parameter`. +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 0)] + pub mod storage { + /// Configures the base deposit of storing some data. + #[codec(index = 0)] + pub static BaseDeposit: Balance = 1 * DOLLARS; + + /// Configures the per-byte deposit of storing some data. + #[codec(index = 1)] + pub static ByteDeposit: Balance = 1 * CENTS; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod contracts { + #[codec(index = 0)] + pub static DepositPerItem: Balance = deposit(1, 0); + + #[codec(index = 1)] + pub static DepositPerByte: Balance = deposit(0, 1); + + #[codec(index = 2)] + pub static DefaultDepositLimit: Balance = deposit(1024, 1024 * 1024); + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Storage(dynamic_params::storage::Parameters::BaseDeposit( + dynamic_params::storage::BaseDeposit, + Some(1 * DOLLARS), + )) + } +} + +pub struct DynamicParametersManagerOrigin; +impl EnsureOriginWithArg for DynamicParametersManagerOrigin { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + match key { + RuntimeParametersKey::Storage(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + RuntimeParametersKey::Contract(_) => { + frame_system::ensure_root(origin.clone()).map_err(|_| origin)?; + return Ok(()) + }, + } + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::root()) + } +} + +impl pallet_parameters::Config for Runtime { + type RuntimeParameters = RuntimeParameters; + type RuntimeEvent = RuntimeEvent; + type AdminOrigin = DynamicParametersManagerOrigin; + type WeightInfo = (); +} + construct_runtime!( pub enum Runtime { System: frame_system, @@ -2169,6 +2243,7 @@ construct_runtime!( Broker: pallet_broker, TasksExample: pallet_example_tasks, Mixnet: pallet_mixnet, + Parameters: pallet_parameters, SkipFeelessPayment: pallet_skip_feeless_payment, } ); @@ -2288,6 +2363,7 @@ mod benches { [pallet_elections_phragmen, Elections] [pallet_fast_unstake, FastUnstake] [pallet_nis, Nis] + [pallet_parameters, Parameters] [pallet_grandpa, Grandpa] [pallet_identity, Identity] [pallet_im_online, ImOnline] diff --git a/substrate/frame/examples/kitchensink/Cargo.toml b/substrate/frame/examples/kitchensink/Cargo.toml index 4255ebb66b65..f866024b8e90 100644 --- a/substrate/frame/examples/kitchensink/Cargo.toml +++ b/substrate/frame/examples/kitchensink/Cargo.toml @@ -20,7 +20,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features = log = { version = "0.4.17", default-features = false } scale-info = { version = "2.10.0", default-features = false, features = ["derive"] } -frame-support = { path = "../../support", default-features = false } +frame-support = { path = "../../support", default-features = false, features = ["experimental"] } frame-system = { path = "../../system", default-features = false } sp-io = { path = "../../../primitives/io", default-features = false } diff --git a/substrate/frame/parameters/Cargo.toml b/substrate/frame/parameters/Cargo.toml new file mode 100644 index 000000000000..c95e23b1a028 --- /dev/null +++ b/substrate/frame/parameters/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "pallet-parameters" +description = "Pallet to store and configure parameters." +repository.workspace = true +license = "Apache-2.0" +version = "0.0.1" +authors = ["Acala Developers", "Parity Technologies "] +edition.workspace = true + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["max-encoded-len"] } +scale-info = { version = "2.1.2", default-features = false, features = ["derive"] } +paste = { version = "1.0.14", default-features = false } +serde = { version = "1.0.188", features = ["derive"], optional = true } +docify = "0.2.5" + +frame-support = { path = "../support", default-features = false, features = ["experimental"] } +frame-system = { path = "../system", default-features = false } +sp-core = { path = "../../primitives/core", default-features = false } +sp-runtime = { path = "../../primitives/runtime", default-features = false } +sp-std = { path = "../../primitives/std", default-features = false } +frame-benchmarking = { path = "../benchmarking", default-features = false, optional = true } + +[dev-dependencies] +sp-core = { path = "../../primitives/core", features = ["std"] } +sp-io = { path = "../../primitives/io", features = ["std"] } +pallet-example-basic = { path = "../examples/basic", features = ["std"] } +pallet-balances = { path = "../balances", features = ["std"] } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-benchmarking?/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "serde", + "sp-core/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-example-basic/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", + "pallet-balances/try-runtime", + "pallet-example-basic/try-runtime", + "sp-runtime/try-runtime", +] diff --git a/substrate/frame/parameters/src/benchmarking.rs b/substrate/frame/parameters/src/benchmarking.rs new file mode 100644 index 000000000000..1f22e026cbca --- /dev/null +++ b/substrate/frame/parameters/src/benchmarking.rs @@ -0,0 +1,51 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +//! Parameters pallet benchmarking. + +#![cfg(feature = "runtime-benchmarks")] + +use super::*; +#[cfg(test)] +use crate::Pallet as Parameters; + +use frame_benchmarking::v2::*; + +#[benchmarks(where T::RuntimeParameters: Default)] +mod benchmarks { + use super::*; + + #[benchmark] + fn set_parameter() -> Result<(), BenchmarkError> { + let kv = T::RuntimeParameters::default(); + let k = kv.clone().into_parts().0; + + let origin = + T::AdminOrigin::try_successful_origin(&k).map_err(|_| BenchmarkError::Weightless)?; + + #[extrinsic_call] + _(origin as T::RuntimeOrigin, kv); + + Ok(()) + } + + impl_benchmark_test_suite! { + Parameters, + crate::tests::mock::new_test_ext(), + crate::tests::mock::Runtime, + } +} diff --git a/substrate/frame/parameters/src/lib.rs b/substrate/frame/parameters/src/lib.rs new file mode 100644 index 000000000000..91cf10ba93f7 --- /dev/null +++ b/substrate/frame/parameters/src/lib.rs @@ -0,0 +1,270 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +#![cfg_attr(not(feature = "std"), no_std)] +#![deny(missing_docs)] +// Need to enable this one since we document feature-gated stuff. +#![allow(rustdoc::broken_intra_doc_links)] + +//! # **⚠️ WARNING ⚠️** +//! +//!
+//! THIS CRATE IS NOT AUDITED AND SHOULD NOT BE USED IN PRODUCTION. +//!
+//! +//! # Parameters +//! +//! Allows to update configuration parameters at runtime. +//! +//! ## Pallet API +//! +//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side +//! to access said parameters. Parameters themselves are defined in the runtime config and will be +//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value. +//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`] +//! macro or alternatives. +//! +//! Note that this is incurring one storage read per access. This should not be a problem in most +//! cases but must be considered in weight-restrained code. +//! +//! ### Inbound +//! +//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value +//! of a parameter. Each parameter can have their own admin origin as given by the +//! [`Config::AdminOrigin`]. +//! +//! ### Outbound +//! +//! The outbound side is runtime facing for the most part. More general, it provides a `Get` +//! implementation and can be used in every spot where that is accepted. Two macros are in place: +//! [`frame_support::dynamic_params::define_parameters` and +//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a +//! typed manner. +//! +//! See the [`pallet`] module for more information about the interfaces this pallet exposes, +//! including its configuration trait, dispatchables, storage items, events and errors. +//! +//! ## Overview +//! +//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to +//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot +//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple +//! parameter change. This pallet allows for fine-grained control over who can update what. +//! The only down-side is that it trades off performance with convenience and should therefore only +//! be used in places where that is proven to be uncritical. Values that are rarely accessed but +//! change often would be a perfect fit. +//! +//! ### Example Configuration +//! +//! Here is an example of how to define some parameters, including their default values: +#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)] +//! +//! A permissioned origin can be define on a per-key basis like this: +#![doc = docify::embed!("src/tests/mock.rs", custom_origin)] +//! +//! The pallet will also require a default value for benchmarking. Ideally this is the variant with +//! the longest encoded length. Although in either case the PoV benchmarking will take the worst +//! case over the whole enum. +#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)] +//! +//! Now the aggregated parameter needs to be injected into the pallet config: +#![doc = docify::embed!("src/tests/mock.rs", impl_config)] +//! +//! As last step, the parameters can now be used in other pallets 🙌 +#![doc = docify::embed!("src/tests/mock.rs", usage)] +//! +//! ### Examples Usage +//! +//! Now to demonstrate how the values can be updated: +#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)] +//! +//! ## Low Level / Implementation Details +//! +//! The pallet stores the parameters in a storage map and implements the matching `Get` for +//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is +//! emitted every time that a value was updated. It is even emitted when the value is changed to the +//! same. +//! +//! The key and value types themselves are defined by macros and aggregated into a runtime wide +//! enum. This enum is then injected into the pallet. This allows it to be used without any changes +//! to the pallet that the parameter will be utilized by. +//! +//! ### Design Goals +//! +//! 1. Easy to update without runtime upgrade. +//! 2. Exposes metadata and docs for user convenience. +//! 3. Can be permissioned on a per-key base. +//! +//! ### Design +//! +//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this - +//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which +//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg` +//! trait, that allows to pass data along to the origin check. It gets passed in the key. The +//! implementor can then match on the key and the origin to decide whether the origin is +//! permissioned to set the value. + +use frame_support::pallet_prelude::*; +use frame_system::pallet_prelude::*; + +use frame_support::traits::{ + dynamic_params::{AggregratedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey}, + EnsureOriginWithArg, +}; + +mod benchmarking; +#[cfg(test)] +mod tests; +mod weights; + +pub use pallet::*; +pub use weights::WeightInfo; + +/// The key type of a parameter. +type KeyOf = <::RuntimeParameters as AggregratedKeyValue>::Key; + +/// The value type of a parameter. +type ValueOf = <::RuntimeParameters as AggregratedKeyValue>::Value; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + /// The overarching event type. + #[pallet::no_default_bounds] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching KV type of the parameters. + /// + /// Usually created by [`frame_support::dynamic_params`] or equivalent. + #[pallet::no_default_bounds] + type RuntimeParameters: AggregratedKeyValue; + + /// The origin which may update a parameter. + /// + /// The key of the parameter is passed in as second argument to allow for fine grained + /// control. + #[pallet::no_default_bounds] + type AdminOrigin: EnsureOriginWithArg>; + + /// Weight information for extrinsics in this module. + type WeightInfo: WeightInfo; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(crate) fn deposit_event)] + pub enum Event { + /// A Parameter was set. + /// + /// Is also emitted when the value was not changed. + Updated { + /// The key that was updated. + key: ::Key, + /// The old value before this call. + old_value: Option<::Value>, + /// The new value after this call. + new_value: Option<::Value>, + }, + } + + /// Stored parameters. + #[pallet::storage] + pub type Parameters = + StorageMap<_, Blake2_128Concat, KeyOf, ValueOf, OptionQuery>; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::call] + impl Pallet { + /// Set the value of a parameter. + /// + /// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be + /// deleted by setting them to `None`. + #[pallet::call_index(0)] + #[pallet::weight(T::WeightInfo::set_parameter())] + pub fn set_parameter( + origin: OriginFor, + key_value: T::RuntimeParameters, + ) -> DispatchResult { + let (key, new) = key_value.into_parts(); + T::AdminOrigin::ensure_origin(origin, &key)?; + + let mut old = None; + Parameters::::mutate(&key, |v| { + old = v.clone(); + *v = new.clone(); + }); + + Self::deposit_event(Event::Updated { key, old_value: old, new_value: new }); + + Ok(()) + } + } + /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`]. + pub mod config_preludes { + use super::*; + use frame_support::derive_impl; + + /// A configuration for testing. + pub struct TestDefaultConfig; + + #[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig, no_aggregated_types)] + impl frame_system::DefaultConfig for TestDefaultConfig {} + + #[frame_support::register_default_impl(TestDefaultConfig)] + impl DefaultConfig for TestDefaultConfig { + #[inject_runtime_type] + type RuntimeEvent = (); + #[inject_runtime_type] + type RuntimeParameters = (); + + type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg< + frame_system::EnsureRoot, + >; + + type WeightInfo = (); + } + } +} + +impl RuntimeParameterStore for Pallet { + type AggregratedKeyValue = T::RuntimeParameters; + + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto, + { + let key: ::Key = key.into(); + let val = Parameters::::get(key.into_key()); + val.and_then(|v| { + let val: ::Value = v.try_into_key().ok()?; + let val: K::WrappedValue = val.try_into().ok()?; + let val = val.into(); + Some(val) + }) + } +} diff --git a/substrate/frame/parameters/src/tests/mock.rs b/substrate/frame/parameters/src/tests/mock.rs new file mode 100644 index 000000000000..98612dc6a6d9 --- /dev/null +++ b/substrate/frame/parameters/src/tests/mock.rs @@ -0,0 +1,152 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Mock runtime that configures the `pallet_example_basic` to use dynamic params for testing. + +use frame_support::{ + construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::EnsureOriginWithArg, +}; + +use crate as pallet_parameters; +use crate::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[docify::export] +#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[docify::export(benchmarking_default)] +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParameters { + fn default() -> Self { + RuntimeParameters::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[docify::export] +mod custom_origin { + use super::*; + pub struct ParamsManager; + + impl EnsureOriginWithArg for ParamsManager { + type Success = (); + + fn try_origin( + origin: RuntimeOrigin, + key: &RuntimeParametersKey, + ) -> Result { + // Account 123 is allowed to set parameters in benchmarking only: + #[cfg(feature = "runtime-benchmarks")] + if ensure_signed(origin.clone()).map_or(false, |acc| acc == 123) { + return Ok(()); + } + + match key { + RuntimeParametersKey::Pallet1(_) => ensure_root(origin.clone()), + RuntimeParametersKey::Pallet2(_) => ensure_signed(origin.clone()).map(|_| ()), + } + .map_err(|_| origin) + } + + #[cfg(feature = "runtime-benchmarks")] + fn try_successful_origin(_key: &RuntimeParametersKey) -> Result { + Ok(RuntimeOrigin::signed(123)) + } + } +} + +#[docify::export(impl_config)] +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = custom_origin::ParamsManager; + // RuntimeParameters is injected by the `derive_impl` macro. + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +#[docify::export(usage)] +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} diff --git a/substrate/frame/parameters/src/tests/mod.rs b/substrate/frame/parameters/src/tests/mod.rs new file mode 100644 index 000000000000..0a2d16906aad --- /dev/null +++ b/substrate/frame/parameters/src/tests/mod.rs @@ -0,0 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +pub(crate) mod mock; +mod test_renamed; +mod unit; diff --git a/substrate/frame/parameters/src/tests/test_renamed.rs b/substrate/frame/parameters/src/tests/test_renamed.rs new file mode 100644 index 000000000000..b2e0c1fd9661 --- /dev/null +++ b/substrate/frame/parameters/src/tests/test_renamed.rs @@ -0,0 +1,168 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +#![cfg(any(test, feature = "runtime-benchmarks"))] + +//! Tests that the runtime params can be renamed. + +use frame_support::{ + assert_noop, assert_ok, construct_runtime, derive_impl, + dynamic_params::{dynamic_pallet_params, dynamic_params}, + traits::AsEnsureOriginWithArg, +}; +use frame_system::EnsureRoot; + +use crate as pallet_parameters; +use crate::*; +use dynamic_params::*; +use RuntimeParametersRenamed::*; + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)] +impl frame_system::Config for Runtime { + type Block = frame_system::mocking::MockBlock; + type AccountData = pallet_balances::AccountData<::Balance>; +} + +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig as pallet_balances::DefaultConfig)] +impl pallet_balances::Config for Runtime { + type ReserveIdentifier = [u8; 8]; + type AccountStore = System; +} + +#[dynamic_params(RuntimeParametersRenamed, pallet_parameters::Parameters::)] +pub mod dynamic_params { + use super::*; + + #[dynamic_pallet_params] + #[codec(index = 3)] + pub mod pallet1 { + #[codec(index = 0)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 1; + #[codec(index = 2)] + pub static Key3: u128 = 2; + } + + #[dynamic_pallet_params] + #[codec(index = 1)] + pub mod pallet2 { + #[codec(index = 2)] + pub static Key1: u64 = 0; + #[codec(index = 1)] + pub static Key2: u32 = 2; + #[codec(index = 0)] + pub static Key3: u128 = 4; + } +} + +#[cfg(feature = "runtime-benchmarks")] +impl Default for RuntimeParametersRenamed { + fn default() -> Self { + RuntimeParametersRenamed::Pallet1(dynamic_params::pallet1::Parameters::Key1( + dynamic_params::pallet1::Key1, + Some(123), + )) + } +} + +#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig as pallet_parameters::DefaultConfig)] +impl Config for Runtime { + type AdminOrigin = AsEnsureOriginWithArg>; + type RuntimeParameters = RuntimeParametersRenamed; + // RuntimeEvent is injected by the `derive_impl` macro. + // WeightInfo is injected by the `derive_impl` macro. +} + +impl pallet_example_basic::Config for Runtime { + // Use the dynamic key in the pallet config: + type MagicNumber = dynamic_params::pallet1::Key1; + + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); +} + +construct_runtime!( + pub enum Runtime { + System: frame_system, + PalletParameters: crate, + Example: pallet_example_basic, + Balances: pallet_balances, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut ext = sp_io::TestExternalities::new(Default::default()); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +pub(crate) fn assert_last_event(generic_event: RuntimeEvent) { + let events = frame_system::Pallet::::events(); + // compare to the last event record + let frame_system::EventRecord { event, .. } = &events.last().expect("Event expected"); + assert_eq!(event, &generic_event); +} + +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + RuntimeOrigin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersRenamedKey::Pallet1(pallet1::ParametersKey::Key3( + pallet1::Key3, + )), + old_value: None, + new_value: Some(RuntimeParametersRenamedValue::Pallet1( + pallet1::ParametersValue::Key3(123), + )), + } + .into(), + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + RuntimeOrigin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} diff --git a/substrate/frame/parameters/src/tests/unit.rs b/substrate/frame/parameters/src/tests/unit.rs new file mode 100644 index 000000000000..d3f11ba96403 --- /dev/null +++ b/substrate/frame/parameters/src/tests/unit.rs @@ -0,0 +1,311 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +//! Unit tests for the parameters pallet. + +#![cfg(test)] + +use crate::tests::mock::{ + assert_last_event, dynamic_params::*, new_test_ext, PalletParameters, Runtime, + RuntimeOrigin as Origin, RuntimeParameters, RuntimeParameters::*, RuntimeParametersKey, + RuntimeParametersValue, +}; +use codec::Encode; +use frame_support::{assert_noop, assert_ok, traits::dynamic_params::AggregratedKeyValue}; +use sp_core::Get; +use sp_runtime::DispatchError; + +#[docify::export] +#[test] +fn set_parameters_example() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2, "Default works"); + + // This gets rejected since the origin is not root. + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + ), + DispatchError::BadOrigin + ); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3( + 123, + ))), + } + .into(), + ); + }); +} + +#[test] +fn set_parameters_same_is_noop() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + }); +} + +#[test] +fn set_parameters_twice_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + )); + + assert_eq!(pallet1::Key3::get(), 432, "Update works"); + }); +} + +#[test] +fn set_parameters_removing_restores_default_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_eq!(pallet1::Key3::get(), 123, "Update works"); + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + + // Removing the value restores the default. + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, None)), + )); + + assert_eq!(pallet1::Key3::get(), 2, "Default restored"); + assert!( + !crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key removed" + ); + }); +} + +#[test] +fn set_parameters_to_default_emits_events_works() { + new_test_ext().execute_with(|| { + assert_eq!(pallet1::Key3::get(), 2); + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(pallet1::Key3::get(), 2); + + assert!( + crate::Parameters::::contains_key(RuntimeParametersKey::Pallet1( + pallet1::ParametersKey::Key3(pallet1::Key3) + )), + "Key inserted" + ); + assert_last_event( + crate::Event::Updated { + key: RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)), + old_value: None, + new_value: Some(RuntimeParametersValue::Pallet1(pallet1::ParametersValue::Key3(2))), + } + .into(), + ); + + // It will also emit a second event: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(2))), + )); + assert_eq!(frame_system::Pallet::::events().len(), 2); + }); +} + +#[test] +fn set_parameters_wrong_origin_errors() { + new_test_ext().execute_with(|| { + // Pallet1 is root origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::signed(1), + Pallet1(pallet1::Parameters::Key3(pallet1::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + + // Pallet2 is signed origin only: + assert_ok!(PalletParameters::set_parameter( + Origin::signed(1), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(123))), + )); + + assert_noop!( + PalletParameters::set_parameter( + Origin::root(), + Pallet2(pallet2::Parameters::Key3(pallet2::Key3, Some(432))), + ), + DispatchError::BadOrigin + ); + }); +} + +#[test] +fn get_through_external_pallet_works() { + new_test_ext().execute_with(|| { + assert_eq!(::MagicNumber::get(), 0); + + assert_ok!(PalletParameters::set_parameter( + Origin::root(), + Pallet1(pallet1::Parameters::Key1(pallet1::Key1, Some(123))), + )); + + assert_eq!(::MagicNumber::get(), 123); + }); +} + +#[test] +fn test_define_parameters_key_convert() { + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let key1_2: pallet1::Key1 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key1, key1_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key1(key1)); + + let key2 = pallet1::Key2; + let parameter_key: pallet1::ParametersKey = key2.clone().into(); + let key2_2: pallet1::Key2 = parameter_key.clone().try_into().unwrap(); + + assert_eq!(key2, key2_2); + assert_eq!(parameter_key, pallet1::ParametersKey::Key2(key2)); +} + +#[test] +fn test_define_parameters_value_convert() { + let value1 = pallet1::Key1Value(1); + let parameter_value: pallet1::ParametersValue = value1.clone().into(); + let value1_2: pallet1::Key1Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value1, value1_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key1(1)); + + let value2 = pallet1::Key2Value(2); + let parameter_value: pallet1::ParametersValue = value2.clone().into(); + let value2_2: pallet1::Key2Value = parameter_value.clone().try_into().unwrap(); + + assert_eq!(value2, value2_2); + assert_eq!(parameter_value, pallet1::ParametersValue::Key2(2)); +} + +#[test] +fn test_define_parameters_aggregrated_key_value() { + let kv1 = pallet1::Parameters::Key1(pallet1::Key1, None); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, pallet1::ParametersKey::Key1(pallet1::Key1)); + assert_eq!(value1, None); + + let kv2 = pallet1::Parameters::Key2(pallet1::Key2, Some(2)); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, pallet1::ParametersKey::Key2(pallet1::Key2)); + assert_eq!(value2, Some(pallet1::ParametersValue::Key2(2))); +} + +#[test] +fn test_define_aggregrated_parameters_key_convert() { + use codec::Encode; + + let key1 = pallet1::Key1; + let parameter_key: pallet1::ParametersKey = key1.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(key1))); + assert_eq!(runtime_key.encode(), vec![3, 0]); + + let key2 = pallet2::Key2; + let parameter_key: pallet2::ParametersKey = key2.clone().into(); + let runtime_key: RuntimeParametersKey = parameter_key.clone().into(); + + assert_eq!(runtime_key, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(key2))); + assert_eq!(runtime_key.encode(), vec![1, 1]); +} + +#[test] +fn test_define_aggregrated_parameters_aggregrated_key_value() { + let kv1 = RuntimeParameters::Pallet1(pallet1::Parameters::Key1(pallet1::Key1, None)); + let (key1, value1) = kv1.clone().into_parts(); + + assert_eq!(key1, RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1))); + assert_eq!(value1, None); + + let kv2 = RuntimeParameters::Pallet2(pallet2::Parameters::Key2(pallet2::Key2, Some(2))); + let (key2, value2) = kv2.clone().into_parts(); + + assert_eq!(key2, RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2))); + assert_eq!(value2, Some(RuntimeParametersValue::Pallet2(pallet2::ParametersValue::Key2(2)))); +} + +#[test] +fn codec_index_works() { + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key1(pallet1::Key1)).encode(); + assert_eq!(enc, vec![3, 0]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key2(pallet1::Key2)).encode(); + assert_eq!(enc, vec![3, 1]); + let enc = RuntimeParametersKey::Pallet1(pallet1::ParametersKey::Key3(pallet1::Key3)).encode(); + assert_eq!(enc, vec![3, 2]); + + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key1(pallet2::Key1)).encode(); + assert_eq!(enc, vec![1, 2]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key2(pallet2::Key2)).encode(); + assert_eq!(enc, vec![1, 1]); + let enc = RuntimeParametersKey::Pallet2(pallet2::ParametersKey::Key3(pallet2::Key3)).encode(); + assert_eq!(enc, vec![1, 0]); +} diff --git a/substrate/frame/parameters/src/weights.rs b/substrate/frame/parameters/src/weights.rs new file mode 100644 index 000000000000..6746960b1b71 --- /dev/null +++ b/substrate/frame/parameters/src/weights.rs @@ -0,0 +1,76 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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 32.0.0 +//! DATE: 2024-02-05, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `runner-bn-ce5rx-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024` + +// Executed Command: +// target/production/substrate-node +// benchmark +// pallet +// --steps=50 +// --repeat=20 +// --extrinsic=* +// --wasm-execution=compiled +// --heap-pages=4096 +// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json +// --pallet=pallet_parameters +// --chain=dev +// --header=./substrate/HEADER-APACHE2 +// --output=./substrate/frame/parameters/src/weights.rs +// --template=./substrate/.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use core::marker::PhantomData; + +/// Weight functions needed for `pallet_parameters`. +pub trait WeightInfo { + fn set_parameter() -> Weight; +} + +/// Weights for `pallet_parameters` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { + fn set_parameter() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 0_000 picoseconds. + Weight::from_parts(0, 0) + } +} diff --git a/substrate/frame/support/procedural/Cargo.toml b/substrate/frame/support/procedural/Cargo.toml index d77f6595db33..5b7fbb3fcaac 100644 --- a/substrate/frame/support/procedural/Cargo.toml +++ b/substrate/frame/support/procedural/Cargo.toml @@ -24,7 +24,7 @@ cfg-expr = "0.15.5" itertools = "0.10.3" proc-macro2 = "1.0.56" quote = "1.0.28" -syn = { version = "2.0.48", features = ["full"] } +syn = { version = "2.0.48", features = ["full", "visit-mut"] } frame-support-procedural-tools = { path = "tools" } macro_magic = { version = "0.5.0", features = ["proc_support"] } proc-macro-warning = { version = "1.0.0", default-features = false } diff --git a/substrate/frame/support/procedural/src/dynamic_params.rs b/substrate/frame/support/procedural/src/dynamic_params.rs new file mode 100644 index 000000000000..b718ccbc9558 --- /dev/null +++ b/substrate/frame/support/procedural/src/dynamic_params.rs @@ -0,0 +1,563 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +//! Code for the `#[dynamic_params]`, `#[dynamic_pallet_params]` and +//! `#[dynamic_aggregated_params_internal]` macros. + +use frame_support_procedural_tools::generate_access_from_frame_or_crate; +use inflector::Inflector; +use proc_macro2::{Span, TokenStream}; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse2, spanned::Spanned, visit_mut, visit_mut::VisitMut, Result, Token}; + +/// Parse and expand a `#[dynamic_params(..)]` module. +pub fn dynamic_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicParamModAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_pallet_params(..)]` attribute. +pub fn dynamic_pallet_params(attr: TokenStream, item: TokenStream) -> Result { + DynamicPalletParamAttr::parse(attr, item).map(ToTokens::into_token_stream) +} + +/// Parse and expand `#[dynamic_aggregated_params_internal]` attribute. +pub fn dynamic_aggregated_params_internal( + _attr: TokenStream, + item: TokenStream, +) -> Result { + parse2::(item).map(ToTokens::into_token_stream) +} + +/// A top `#[dynamic_params(..)]` attribute together with a mod. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttr { + params_mod: syn::ItemMod, + meta: DynamicParamModAttrMeta, +} + +/// The inner meta of a `#[dynamic_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamModAttrMeta { + name: syn::Ident, + _comma: Option, + #[parse_if(_comma.is_some())] + params_pallet: Option, +} + +impl DynamicParamModAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + let params_mod = parse2(item)?; + let meta = parse2(attr)?; + Ok(Self { params_mod, meta }) + } + + pub fn inner_mods(&self) -> Vec { + self.params_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Mod(m) => Some(m), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicParamModAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (mut params_mod, name) = (self.params_mod.clone(), &self.meta.name); + let dynam_params_ident = ¶ms_mod.ident; + + let mut quoted_enum = quote! {}; + for m in self.inner_mods() { + let aggregate_name = + syn::Ident::new(&m.ident.to_string().to_class_case(), m.ident.span()); + let mod_name = &m.ident; + + let mut attrs = m.attrs.clone(); + attrs.retain(|attr| !attr.path().is_ident("dynamic_pallet_params")); + if let Err(err) = ensure_codec_index(&attrs, m.span()) { + tokens.extend(err.into_compile_error()); + return + } + + quoted_enum.extend(quote! { + #(#attrs)* + #aggregate_name(#dynam_params_ident::#mod_name::Parameters), + }); + } + + // Inject the outer args into the inner `#[dynamic_pallet_params(..)]` attribute. + if let Some(params_pallet) = &self.meta.params_pallet { + MacroInjectArgs { runtime_params: name.clone(), params_pallet: params_pallet.clone() } + .visit_item_mod_mut(&mut params_mod); + } + + tokens.extend(quote! { + #params_mod + + #[#scrate::dynamic_params::dynamic_aggregated_params_internal] + pub enum #name { + #quoted_enum + } + }); + } +} + +/// Ensure there is a `#[codec(index = ..)]` attribute. +fn ensure_codec_index(attrs: &Vec, span: Span) -> Result<()> { + let mut found = false; + + for attr in attrs.iter() { + if attr.path().is_ident("codec") { + let meta: syn::ExprAssign = attr.parse_args()?; + if meta.left.to_token_stream().to_string() == "index" { + found = true; + break + } + } + } + + if !found { + Err(syn::Error::new(span, "Missing explicit `#[codec(index = ..)]` attribute")) + } else { + Ok(()) + } +} + +/// Used to inject arguments into the inner `#[dynamic_pallet_params(..)]` attribute. +/// +/// This allows the outer `#[dynamic_params(..)]` attribute to specify some arguments that dont need +/// to be repeated every time. +struct MacroInjectArgs { + runtime_params: syn::Ident, + params_pallet: syn::Type, +} +impl VisitMut for MacroInjectArgs { + fn visit_item_mod_mut(&mut self, item: &mut syn::ItemMod) { + // Check if the mod has a `#[dynamic_pallet_params(..)]` attribute. + let attr = item.attrs.iter_mut().find(|attr| attr.path().is_ident("dynamic_pallet_params")); + + if let Some(attr) = attr { + match &attr.meta { + syn::Meta::Path(path) => + assert_eq!(path.to_token_stream().to_string(), "dynamic_pallet_params"), + _ => (), + } + + let runtime_params = &self.runtime_params; + let params_pallet = &self.params_pallet; + + attr.meta = syn::parse2::(quote! { + dynamic_pallet_params(#runtime_params, #params_pallet) + }) + .unwrap() + .into(); + } + + visit_mut::visit_item_mod_mut(self, item); + } +} +/// The helper attribute of a `#[dynamic_pallet_params(runtime_params, params_pallet)]` +/// attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttr { + inner_mod: syn::ItemMod, + meta: DynamicPalletParamAttrMeta, +} + +/// The inner meta of a `#[dynamic_pallet_params(..)]` attribute. +#[derive(derive_syn_parse::Parse)] +pub struct DynamicPalletParamAttrMeta { + runtime_params: syn::Ident, + _comma: Token![,], + parameter_pallet: syn::Type, +} + +impl DynamicPalletParamAttr { + pub fn parse(attr: TokenStream, item: TokenStream) -> Result { + Ok(Self { inner_mod: parse2(item)?, meta: parse2(attr)? }) + } + + pub fn statics(&self) -> Vec { + self.inner_mod.content.as_ref().map_or(Vec::new(), |(_, items)| { + items + .iter() + .filter_map(|i| match i { + syn::Item::Static(s) => Some(s), + _ => None, + }) + .cloned() + .collect() + }) + } +} + +impl ToTokens for DynamicPalletParamAttr { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let (params_mod, parameter_pallet, runtime_params) = + (&self.inner_mod, &self.meta.parameter_pallet, &self.meta.runtime_params); + + let aggregate_name = + syn::Ident::new(¶ms_mod.ident.to_string().to_class_case(), params_mod.ident.span()); + let (mod_name, vis) = (¶ms_mod.ident, ¶ms_mod.vis); + let statics = self.statics(); + + let (mut key_names, mut key_values, mut defaults, mut attrs, mut value_types): ( + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + Vec<_>, + ) = Default::default(); + + for s in statics.iter() { + if let Err(err) = ensure_codec_index(&s.attrs, s.span()) { + tokens.extend(err.into_compile_error()); + return + } + + key_names.push(&s.ident); + key_values.push(format_ident!("{}Value", &s.ident)); + defaults.push(&s.expr); + attrs.push(&s.attrs); + value_types.push(&s.ty); + } + + let key_ident = syn::Ident::new("ParametersKey", params_mod.ident.span()); + let value_ident = syn::Ident::new("ParametersValue", params_mod.ident.span()); + let runtime_key_ident = format_ident!("{}Key", runtime_params); + let runtime_value_ident = format_ident!("{}Value", runtime_params); + + tokens.extend(quote! { + pub mod #mod_name { + use super::*; + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum Parameters { + #( + #(#attrs)* + #key_names(#key_names, Option<#value_types>), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #key_ident { + #( + #(#attrs)* + #key_names(#key_names), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #value_ident { + #( + #(#attrs)* + #key_names(#value_types), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for Parameters { + type Key = #key_ident; + type Value = #value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + Parameters::#key_names(key, value) => { + (#key_ident::#key_names(key), value.map(#value_ident::#key_names)) + }, + )* + } + } + } + + #( + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::__private::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis struct #key_names; + + impl #scrate::__private::Get<#value_types> for #key_names { + fn get() -> #value_types { + match + <#parameter_pallet as + #scrate::storage::StorageMap<#runtime_key_ident, #runtime_value_ident> + >::get(#runtime_key_ident::#aggregate_name(#key_ident::#key_names(#key_names))) + { + Some(#runtime_value_ident::#aggregate_name( + #value_ident::#key_names(inner))) => inner, + Some(_) => { + #scrate::defensive!("Unexpected value type at key - returning default"); + #defaults + }, + None => #defaults, + } + } + } + + impl #scrate::traits::dynamic_params::Key for #key_names { + type Value = #value_types; + type WrappedValue = #key_values; + } + + impl From<#key_names> for #key_ident { + fn from(key: #key_names) -> Self { + #key_ident::#key_names(key) + } + } + + impl TryFrom<#key_ident> for #key_names { + type Error = (); + + fn try_from(key: #key_ident) -> Result { + match key { + #key_ident::#key_names(key) => Ok(key), + _ => Err(()), + } + } + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::sp_runtime::RuntimeDebug, + )] + #vis struct #key_values(pub #value_types); + + impl From<#key_values> for #value_ident { + fn from(value: #key_values) -> Self { + #value_ident::#key_names(value.0) + } + } + + impl From<(#key_names, #value_types)> for Parameters { + fn from((key, value): (#key_names, #value_types)) -> Self { + Parameters::#key_names(key, Some(value)) + } + } + + impl From<#key_names> for Parameters { + fn from(key: #key_names) -> Self { + Parameters::#key_names(key, None) + } + } + + impl TryFrom<#value_ident> for #key_values { + type Error = (); + + fn try_from(value: #value_ident) -> Result { + match value { + #value_ident::#key_names(value) => Ok(#key_values(value)), + _ => Err(()), + } + } + } + + impl From<#key_values> for #value_types { + fn from(value: #key_values) -> Self { + value.0 + } + } + )* + } + }); + } +} + +#[derive(derive_syn_parse::Parse)] +pub struct DynamicParamAggregatedEnum { + aggregated_enum: syn::ItemEnum, +} + +impl ToTokens for DynamicParamAggregatedEnum { + fn to_tokens(&self, tokens: &mut TokenStream) { + let scrate = match crate_access() { + Ok(path) => path, + Err(err) => return tokens.extend(err), + }; + let params_enum = &self.aggregated_enum; + let (name, vis) = (¶ms_enum.ident, ¶ms_enum.vis); + + let (mut indices, mut param_names, mut param_types): (Vec<_>, Vec<_>, Vec<_>) = + Default::default(); + let mut attributes = Vec::new(); + for (i, variant) in params_enum.variants.iter().enumerate() { + indices.push(i); + param_names.push(&variant.ident); + attributes.push(&variant.attrs); + + param_types.push(match &variant.fields { + syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => &fields.unnamed[0].ty, + _ => { + *tokens = quote! { compile_error!("Only unnamed enum variants with one inner item are supported") }; + return + }, + }); + } + + let params_key_ident = format_ident!("{}Key", params_enum.ident); + let params_value_ident = format_ident!("{}Value", params_enum.ident); + + tokens.extend(quote! { + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #name { + #( + //#[codec(index = #indices)] + #(#attributes)* + #param_names(#param_types), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_key_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key), + )* + } + + #[doc(hidden)] + #[derive( + Clone, + PartialEq, + Eq, + #scrate::__private::codec::Encode, + #scrate::__private::codec::Decode, + #scrate::__private::codec::MaxEncodedLen, + #scrate::sp_runtime::RuntimeDebug, + #scrate::__private::scale_info::TypeInfo + )] + #vis enum #params_value_ident { + #( + #(#attributes)* + #param_names(<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value), + )* + } + + impl #scrate::traits::dynamic_params::AggregratedKeyValue for #name { + type Key = #params_key_ident; + type Value = #params_value_ident; + + fn into_parts(self) -> (Self::Key, Option) { + match self { + #( + #name::#param_names(parameter) => { + let (key, value) = parameter.into_parts(); + (#params_key_ident::#param_names(key), value.map(#params_value_ident::#param_names)) + }, + )* + } + } + } + + #( + impl ::core::convert::From<<#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key> for #params_key_ident { + fn from(key: <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Key) -> Self { + #params_key_ident::#param_names(key) + } + } + + impl ::core::convert::TryFrom<#params_value_ident> for <#param_types as #scrate::traits::dynamic_params::AggregratedKeyValue>::Value { + type Error = (); + + fn try_from(value: #params_value_ident) -> Result { + match value { + #params_value_ident::#param_names(value) => Ok(value), + _ => Err(()), + } + } + } + )* + }); + } +} + +/// Get access to the current crate and convert the error to a compile error. +fn crate_access() -> core::result::Result { + generate_access_from_frame_or_crate("frame-support").map_err(|e| e.to_compile_error()) +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index 34e68966b823..20b8d74310f3 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -18,12 +18,14 @@ //! Proc macro of Support code for the runtime. #![recursion_limit = "512"] +#![deny(rustdoc::broken_intra_doc_links)] mod benchmark; mod construct_runtime; mod crate_version; mod derive_impl; mod dummy_part_checker; +mod dynamic_params; mod key_prefix; mod match_and_insert; mod no_bound; @@ -890,12 +892,13 @@ pub fn inject_runtime_type(_: TokenStream, tokens: TokenStream) -> TokenStream { item.ident != "RuntimeOrigin" && item.ident != "RuntimeHoldReason" && item.ident != "RuntimeFreezeReason" && + item.ident != "RuntimeParameters" && item.ident != "PalletInfo" { return syn::Error::new_spanned( item, "`#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, \ - `RuntimeTask`, `RuntimeOrigin` or `PalletInfo`", + `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo`", ) .to_compile_error() .into() @@ -1685,3 +1688,52 @@ pub fn import_section(attr: TokenStream, tokens: TokenStream) -> TokenStream { } .into() } + +/// Mark a module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Arguments +/// +/// The macro accepts two positional arguments, of which the second is optional. +/// +/// ## Aggregated Enum Name +/// +/// This sets the name that the aggregated Key-Value enum will be named after. Common names would be +/// `RuntimeParameters`, akin to `RuntimeCall`, `RuntimeOrigin` etc. There is no default value for +/// this argument. +/// +/// ## Parameter Storage Backend +/// +/// The second argument provides access to the storage of the parameters. It can either be set on +/// on this attribute, or on the inner ones. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Define a module inside a [`macro@dynamic_params`] module that contains dynamic parameters. +/// +/// See the `pallet_parameters` for a full example. +/// +/// # Argument +/// +/// This attribute takes one optional argument. The argument can either be put here or on the +/// surrounding `#[dynamic_params]` attribute. If set on both, the inner one takes precedence. +#[proc_macro_attribute] +pub fn dynamic_pallet_params(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_pallet_params(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} + +/// Used internally by [`dynamic_params`]. +#[doc(hidden)] +#[proc_macro_attribute] +pub fn dynamic_aggregated_params_internal(attrs: TokenStream, input: TokenStream) -> TokenStream { + dynamic_params::dynamic_aggregated_params_internal(attrs.into(), input.into()) + .unwrap_or_else(|r| r.into_compile_error()) + .into() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index 0f6cf05959f5..8935acf4e2bb 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -44,7 +44,7 @@ pub mod __private { pub use paste; pub use scale_info; pub use serde; - pub use sp_core::{OpaqueMetadata, Void}; + pub use sp_core::{Get, OpaqueMetadata, Void}; pub use sp_crypto_hashing_proc_macro; pub use sp_inherents; #[cfg(feature = "std")] @@ -175,6 +175,14 @@ pub use frame_support_procedural::storage_alias; pub use frame_support_procedural::derive_impl; +/// Experimental macros for defining dynamic params that can be used in pallet configs. +#[cfg(feature = "experimental")] +pub mod dynamic_params { + pub use frame_support_procedural::{ + dynamic_aggregated_params_internal, dynamic_pallet_params, dynamic_params, + }; +} + /// Create new implementations of the [`Get`](crate::traits::Get) trait. /// /// The so-called parameter type can be created in four different ways: diff --git a/substrate/frame/support/src/traits.rs b/substrate/frame/support/src/traits.rs index 2a42fca76b3c..3d0429f71b11 100644 --- a/substrate/frame/support/src/traits.rs +++ b/substrate/frame/support/src/traits.rs @@ -124,6 +124,8 @@ pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify}; mod tx_pause; pub use tx_pause::{TransactionPause, TransactionPauseError}; +pub mod dynamic_params; + pub mod tasks; pub use tasks::Task; diff --git a/substrate/frame/support/src/traits/dynamic_params.rs b/substrate/frame/support/src/traits/dynamic_params.rs new file mode 100644 index 000000000000..8881df04141c --- /dev/null +++ b/substrate/frame/support/src/traits/dynamic_params.rs @@ -0,0 +1,153 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// 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. + +//! Types and traits for dynamic parameters. +//! +//! Can be used by 3rd party macros to define dynamic parameters that are compatible with the the +//! `parameters` pallet. + +use codec::MaxEncodedLen; +use frame_support::Parameter; + +/// A dynamic parameter store across an aggregated KV type. +pub trait RuntimeParameterStore { + type AggregratedKeyValue: AggregratedKeyValue; + + /// Get the value of a parametrized key. + /// + /// Should return `None` if no explicit value was set instead of a default. + fn get(key: K) -> Option + where + KV: AggregratedKeyValue, + K: Key + Into<::Key>, + ::Key: IntoKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Key, + >, + <::AggregratedKeyValue as AggregratedKeyValue>::Value: + TryIntoKey<::Value>, + ::Value: TryInto; +} + +/// A dynamic parameter store across a concrete KV type. +pub trait ParameterStore { + /// Get the value of a parametrized key. + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto; +} + +/// Key of a dynamic parameter. +pub trait Key { + /// The value that the key is parametrized with. + type Value; + + /// An opaque representation of `Self::Value`. + type WrappedValue: Into; +} + +/// The aggregated key-value type of a dynamic parameter store. +pub trait AggregratedKeyValue: Parameter { + /// The aggregated key type. + type Key: Parameter + MaxEncodedLen; + + /// The aggregated value type. + type Value: Parameter + MaxEncodedLen; + + /// Split the aggregated key-value type into its parts. + fn into_parts(self) -> (Self::Key, Option); +} + +impl AggregratedKeyValue for () { + type Key = (); + type Value = (); + + fn into_parts(self) -> (Self::Key, Option) { + ((), None) + } +} + +/// Allows to create a `ParameterStore` from a `RuntimeParameterStore`. +/// +/// This concretization is useful when configuring pallets, since a pallet will require a parameter +/// store for its own KV type and not the aggregated runtime-wide KV type. +pub struct ParameterStoreAdapter(sp_std::marker::PhantomData<(PS, KV)>); + +impl ParameterStore for ParameterStoreAdapter +where + PS: RuntimeParameterStore, + KV: AggregratedKeyValue, + ::Key: + IntoKey<<::AggregratedKeyValue as AggregratedKeyValue>::Key>, + ::Value: TryFromKey< + <::AggregratedKeyValue as AggregratedKeyValue>::Value, + >, +{ + fn get(key: K) -> Option + where + K: Key + Into<::Key>, + ::Value: TryInto, + { + PS::get::(key) + } +} + +// workaround for rust bug https://github.com/rust-lang/rust/issues/51445 +mod workaround { + pub trait FromKey: Sized { + #[must_use] + fn from_key(value: T) -> Self; + } + + pub trait IntoKey: Sized { + #[must_use] + fn into_key(self) -> T; + } + + impl IntoKey for T + where + U: FromKey, + { + fn into_key(self) -> U { + U::from_key(self) + } + } + + pub trait TryIntoKey: Sized { + type Error; + + fn try_into_key(self) -> Result; + } + + pub trait TryFromKey: Sized { + type Error; + + fn try_from_key(value: T) -> Result; + } + + impl TryIntoKey for T + where + U: TryFromKey, + { + type Error = U::Error; + + fn try_into_key(self) -> Result { + U::try_from_key(self) + } + } +} +pub use workaround::*; diff --git a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr index cda20288984a..c7159b34afb3 100644 --- a/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr +++ b/substrate/frame/support/test/tests/derive_impl_ui/inject_runtime_type_invalid.stderr @@ -1,4 +1,4 @@ -error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin` or `PalletInfo` +error: `#[inject_runtime_type]` can only be attached to `RuntimeCall`, `RuntimeEvent`, `RuntimeTask`, `RuntimeOrigin`, `RuntimeParameters` or `PalletInfo` --> tests/derive_impl_ui/inject_runtime_type_invalid.rs:32:5 | 32 | type RuntimeInfo = ();