pallet_parameters/lib.rs
1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18#![cfg_attr(not(feature = "std"), no_std)]
19#![deny(missing_docs)]
20// Need to enable this one since we document feature-gated stuff.
21#![allow(rustdoc::broken_intra_doc_links)]
22
23//! # Parameters
24//!
25//! Allows to update configuration parameters at runtime.
26//!
27//! ## Pallet API
28//!
29//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side
30//! to access said parameters. Parameters themselves are defined in the runtime config and will be
31//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value.
32//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`]
33//! macro or alternatives.
34//!
35//! Note that this is incurring one storage read per access. This should not be a problem in most
36//! cases but must be considered in weight-restrained code.
37//!
38//! ### Inbound
39//!
40//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value
41//! of a parameter. Each parameter can have their own admin origin as given by the
42//! [`Config::AdminOrigin`].
43//!
44//! ### Outbound
45//!
46//! The outbound side is runtime facing for the most part. More general, it provides a `Get`
47//! implementation and can be used in every spot where that is accepted. Two macros are in place:
48//! [`frame_support::dynamic_params::define_parameters` and
49//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a
50//! typed manner.
51//!
52//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
53//! including its configuration trait, dispatchables, storage items, events and errors.
54//!
55//! ## Overview
56//!
57//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to
58//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot
59//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple
60//! parameter change. This pallet allows for fine-grained control over who can update what.
61//! The only down-side is that it trades off performance with convenience and should therefore only
62//! be used in places where that is proven to be uncritical. Values that are rarely accessed but
63//! change often would be a perfect fit.
64//!
65//! ### Example Configuration
66//!
67//! Here is an example of how to define some parameters, including their default values:
68#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)]
69//! A permissioned origin can be define on a per-key basis like this:
70#![doc = docify::embed!("src/tests/mock.rs", custom_origin)]
71//! The pallet will also require a default value for benchmarking. Ideally this is the variant with
72//! the longest encoded length. Although in either case the PoV benchmarking will take the worst
73//! case over the whole enum.
74#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)]
75//! Now the aggregated parameter needs to be injected into the pallet config:
76#![doc = docify::embed!("src/tests/mock.rs", impl_config)]
77//! As last step, the parameters can now be used in other pallets ๐
78#![doc = docify::embed!("src/tests/mock.rs", usage)]
79//! ### Examples Usage
80//!
81//! Now to demonstrate how the values can be updated:
82#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)]
83//! ## Low Level / Implementation Details
84//!
85//! The pallet stores the parameters in a storage map and implements the matching `Get<Value>` for
86//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is
87//! emitted every time that a value was updated. It is even emitted when the value is changed to the
88//! same.
89//!
90//! The key and value types themselves are defined by macros and aggregated into a runtime wide
91//! enum. This enum is then injected into the pallet. This allows it to be used without any changes
92//! to the pallet that the parameter will be utilized by.
93//!
94//! ### Design Goals
95//!
96//! 1. Easy to update without runtime upgrade.
97//! 2. Exposes metadata and docs for user convenience.
98//! 3. Can be permissioned on a per-key base.
99//!
100//! ### Design
101//!
102//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this -
103//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which
104//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg`
105//! trait, that allows to pass data along to the origin check. It gets passed in the key. The
106//! implementor can then match on the key and the origin to decide whether the origin is
107//! permissioned to set the value.
108
109use frame_support::pallet_prelude::*;
110use frame_system::pallet_prelude::*;
111
112use frame_support::traits::{
113 dynamic_params::{AggregatedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey},
114 EnsureOriginWithArg,
115};
116
117mod benchmarking;
118#[cfg(test)]
119mod tests;
120mod weights;
121
122pub use pallet::*;
123pub use weights::WeightInfo;
124
125/// The key type of a parameter.
126type KeyOf<T> = <<T as Config>::RuntimeParameters as AggregatedKeyValue>::Key;
127
128/// The value type of a parameter.
129type ValueOf<T> = <<T as Config>::RuntimeParameters as AggregatedKeyValue>::Value;
130
131#[frame_support::pallet]
132pub mod pallet {
133 use super::*;
134
135 #[pallet::config(with_default)]
136 pub trait Config: frame_system::Config {
137 /// The overarching event type.
138 #[pallet::no_default_bounds]
139 #[allow(deprecated)]
140 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
141
142 /// The overarching KV type of the parameters.
143 ///
144 /// Usually created by [`frame_support::dynamic_params`] or equivalent.
145 #[pallet::no_default_bounds]
146 type RuntimeParameters: AggregatedKeyValue;
147
148 /// The origin which may update a parameter.
149 ///
150 /// The key of the parameter is passed in as second argument to allow for fine grained
151 /// control.
152 #[pallet::no_default_bounds]
153 type AdminOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, KeyOf<Self>>;
154
155 /// Weight information for extrinsics in this module.
156 type WeightInfo: WeightInfo;
157 }
158
159 #[pallet::event]
160 #[pallet::generate_deposit(pub(crate) fn deposit_event)]
161 pub enum Event<T: Config> {
162 /// A Parameter was set.
163 ///
164 /// Is also emitted when the value was not changed.
165 Updated {
166 /// The key that was updated.
167 key: <T::RuntimeParameters as AggregatedKeyValue>::Key,
168 /// The old value before this call.
169 old_value: Option<<T::RuntimeParameters as AggregatedKeyValue>::Value>,
170 /// The new value after this call.
171 new_value: Option<<T::RuntimeParameters as AggregatedKeyValue>::Value>,
172 },
173 }
174
175 /// Stored parameters.
176 #[pallet::storage]
177 pub type Parameters<T: Config> =
178 StorageMap<_, Blake2_128Concat, KeyOf<T>, ValueOf<T>, OptionQuery>;
179
180 #[pallet::pallet]
181 pub struct Pallet<T>(_);
182
183 #[pallet::call]
184 impl<T: Config> Pallet<T> {
185 /// Set the value of a parameter.
186 ///
187 /// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be
188 /// deleted by setting them to `None`.
189 #[pallet::call_index(0)]
190 #[pallet::weight(T::WeightInfo::set_parameter())]
191 pub fn set_parameter(
192 origin: OriginFor<T>,
193 key_value: T::RuntimeParameters,
194 ) -> DispatchResult {
195 let (key, new) = key_value.into_parts();
196 T::AdminOrigin::ensure_origin(origin, &key)?;
197
198 let mut old = None;
199 Parameters::<T>::mutate(&key, |v| {
200 old = v.clone();
201 *v = new.clone();
202 });
203
204 Self::deposit_event(Event::Updated { key, old_value: old, new_value: new });
205
206 Ok(())
207 }
208 }
209 /// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
210 pub mod config_preludes {
211 use super::*;
212 use frame_support::derive_impl;
213
214 /// A configuration for testing.
215 pub struct TestDefaultConfig;
216
217 #[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
218 impl frame_system::DefaultConfig for TestDefaultConfig {}
219
220 #[frame_support::register_default_impl(TestDefaultConfig)]
221 impl DefaultConfig for TestDefaultConfig {
222 #[inject_runtime_type]
223 type RuntimeEvent = ();
224 #[inject_runtime_type]
225 type RuntimeParameters = ();
226
227 type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg<
228 frame_system::EnsureRoot<Self::AccountId>,
229 >;
230
231 type WeightInfo = ();
232 }
233 }
234}
235
236impl<T: Config> RuntimeParameterStore for Pallet<T> {
237 type AggregatedKeyValue = T::RuntimeParameters;
238
239 fn get<KV, K>(key: K) -> Option<K::Value>
240 where
241 KV: AggregatedKeyValue,
242 K: Key + Into<<KV as AggregatedKeyValue>::Key>,
243 <KV as AggregatedKeyValue>::Key: IntoKey<
244 <<Self as RuntimeParameterStore>::AggregatedKeyValue as AggregatedKeyValue>::Key,
245 >,
246 <<Self as RuntimeParameterStore>::AggregatedKeyValue as AggregatedKeyValue>::Value:
247 TryIntoKey<<KV as AggregatedKeyValue>::Value>,
248 <KV as AggregatedKeyValue>::Value: TryInto<K::WrappedValue>,
249 {
250 let key: <KV as AggregatedKeyValue>::Key = key.into();
251 let val = Parameters::<T>::get(key.into_key());
252 val.and_then(|v| {
253 let val: <KV as AggregatedKeyValue>::Value = v.try_into_key().ok()?;
254 let val: K::WrappedValue = val.try_into().ok()?;
255 let val = val.into();
256 Some(val)
257 })
258 }
259}