// 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) }) } }