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
//! # **⚠️ WARNING ⚠️**
24
//!  
25
//! <br>  
26
//! <b>THIS CRATE IS NOT AUDITED AND SHOULD NOT BE USED IN PRODUCTION.</b>  
27
//! <br>  
28
//!
29
//! # Parameters
30
//!
31
//! Allows to update configuration parameters at runtime.
32
//!
33
//! ## Pallet API
34
//!
35
//! This pallet exposes two APIs; one *inbound* side to update parameters, and one *outbound* side
36
//! to access said parameters. Parameters themselves are defined in the runtime config and will be
37
//! aggregated into an enum. Each parameter is addressed by a `key` and can have a default value.
38
//! This is not done by the pallet but through the [`frame_support::dynamic_params::dynamic_params`]
39
//! macro or alternatives.
40
//!
41
//! Note that this is incurring one storage read per access. This should not be a problem in most
42
//! cases but must be considered in weight-restrained code.
43
//!
44
//! ### Inbound
45
//!
46
//! The inbound side solely consists of the [`Pallet::set_parameter`] extrinsic to update the value
47
//! of a parameter. Each parameter can have their own admin origin as given by the
48
//! [`Config::AdminOrigin`].
49
//!
50
//! ### Outbound
51
//!
52
//! The outbound side is runtime facing for the most part. More general, it provides a `Get`
53
//! implementation and can be used in every spot where that is accepted. Two macros are in place:
54
//! [`frame_support::dynamic_params::define_parameters` and
55
//! [`frame_support::dynamic_params:dynamic_pallet_params`] to define and expose parameters in a
56
//! typed manner.
57
//!
58
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
59
//! including its configuration trait, dispatchables, storage items, events and errors.
60
//!
61
//! ## Overview
62
//!
63
//! This pallet is a good fit for updating parameters without a runtime upgrade. It is very handy to
64
//! not require a runtime upgrade for a simple parameter change since runtime upgrades require a lot
65
//! of diligence and always bear risks. It seems overkill to update the whole runtime for a simple
66
//! parameter change. This pallet allows for fine-grained control over who can update what.
67
//! The only down-side is that it trades off performance with convenience and should therefore only
68
//! be used in places where that is proven to be uncritical. Values that are rarely accessed but
69
//! change often would be a perfect fit.
70
//!
71
//! ### Example Configuration
72
//!
73
//! Here is an example of how to define some parameters, including their default values:
74
#![doc = docify::embed!("src/tests/mock.rs", dynamic_params)]
75
//!
76
//! A permissioned origin can be define on a per-key basis like this:
77
#![doc = docify::embed!("src/tests/mock.rs", custom_origin)]
78
//!
79
//! The pallet will also require a default value for benchmarking. Ideally this is the variant with
80
//! the longest encoded length. Although in either case the PoV benchmarking will take the worst
81
//! case over the whole enum.
82
#![doc = docify::embed!("src/tests/mock.rs", benchmarking_default)]
83
//!
84
//! Now the aggregated parameter needs to be injected into the pallet config:
85
#![doc = docify::embed!("src/tests/mock.rs", impl_config)]
86
//!
87
//! As last step, the parameters can now be used in other pallets 🙌
88
#![doc = docify::embed!("src/tests/mock.rs", usage)]
89
//!
90
//! ### Examples Usage
91
//!
92
//! Now to demonstrate how the values can be updated:
93
#![doc = docify::embed!("src/tests/unit.rs", set_parameters_example)]
94
//!
95
//! ## Low Level / Implementation Details
96
//!
97
//! The pallet stores the parameters in a storage map and implements the matching `Get<Value>` for
98
//! each `Key` type. The `Get` then accesses the `Parameters` map to retrieve the value. An event is
99
//! emitted every time that a value was updated. It is even emitted when the value is changed to the
100
//! same.
101
//!
102
//! The key and value types themselves are defined by macros and aggregated into a runtime wide
103
//! enum. This enum is then injected into the pallet. This allows it to be used without any changes
104
//! to the pallet that the parameter will be utilized by.
105
//!
106
//! ### Design Goals
107
//!
108
//! 1. Easy to update without runtime upgrade.
109
//! 2. Exposes metadata and docs for user convenience.
110
//! 3. Can be permissioned on a per-key base.
111
//!
112
//! ### Design
113
//!
114
//! 1. Everything is done at runtime without the need for `const` values. `Get` allows for this -
115
//! which is coincidentally an upside and a downside. 2. The types are defined through macros, which
116
//! allows to expose metadata and docs. 3. Access control is done through the `EnsureOriginWithArg`
117
//! trait, that allows to pass data along to the origin check. It gets passed in the key. The
118
//! implementor can then match on the key and the origin to decide whether the origin is
119
//! permissioned to set the value.
120

            
121
use frame_support::pallet_prelude::*;
122
use frame_system::pallet_prelude::*;
123

            
124
use frame_support::traits::{
125
	dynamic_params::{AggregatedKeyValue, IntoKey, Key, RuntimeParameterStore, TryIntoKey},
126
	EnsureOriginWithArg,
127
};
128

            
129
mod benchmarking;
130
#[cfg(test)]
131
mod tests;
132
mod weights;
133

            
134
pub use pallet::*;
135
pub use weights::WeightInfo;
136

            
137
/// The key type of a parameter.
138
type KeyOf<T> = <<T as Config>::RuntimeParameters as AggregatedKeyValue>::Key;
139

            
140
/// The value type of a parameter.
141
type ValueOf<T> = <<T as Config>::RuntimeParameters as AggregatedKeyValue>::Value;
142

            
143
1676
#[frame_support::pallet]
144
pub mod pallet {
145
	use super::*;
146

            
147
	#[pallet::config(with_default)]
148
	pub trait Config: frame_system::Config {
149
		/// The overarching event type.
150
		#[pallet::no_default_bounds]
151
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
152

            
153
		/// The overarching KV type of the parameters.
154
		///
155
		/// Usually created by [`frame_support::dynamic_params`] or equivalent.
156
		#[pallet::no_default_bounds]
157
		type RuntimeParameters: AggregatedKeyValue;
158

            
159
		/// The origin which may update a parameter.
160
		///
161
		/// The key of the parameter is passed in as second argument to allow for fine grained
162
		/// control.
163
		#[pallet::no_default_bounds]
164
		type AdminOrigin: EnsureOriginWithArg<Self::RuntimeOrigin, KeyOf<Self>>;
165

            
166
		/// Weight information for extrinsics in this module.
167
		type WeightInfo: WeightInfo;
168
	}
169

            
170
	#[pallet::event]
171
	#[pallet::generate_deposit(pub(crate) fn deposit_event)]
172
	pub enum Event<T: Config> {
173
		/// A Parameter was set.
174
		///
175
		/// Is also emitted when the value was not changed.
176
		Updated {
177
			/// The key that was updated.
178
			key: <T::RuntimeParameters as AggregatedKeyValue>::Key,
179
			/// The old value before this call.
180
			old_value: Option<<T::RuntimeParameters as AggregatedKeyValue>::Value>,
181
			/// The new value after this call.
182
			new_value: Option<<T::RuntimeParameters as AggregatedKeyValue>::Value>,
183
		},
184
	}
185

            
186
	/// Stored parameters.
187
1020
	#[pallet::storage]
188
	pub type Parameters<T: Config> =
189
		StorageMap<_, Blake2_128Concat, KeyOf<T>, ValueOf<T>, OptionQuery>;
190

            
191
554790
	#[pallet::pallet]
192
	pub struct Pallet<T>(_);
193

            
194
8100
	#[pallet::call]
195
	impl<T: Config> Pallet<T> {
196
		/// Set the value of a parameter.
197
		///
198
		/// The dispatch origin of this call must be `AdminOrigin` for the given `key`. Values be
199
		/// deleted by setting them to `None`.
200
		#[pallet::call_index(0)]
201
		#[pallet::weight(T::WeightInfo::set_parameter())]
202
		pub fn set_parameter(
203
			origin: OriginFor<T>,
204
			key_value: T::RuntimeParameters,
205
1587
		) -> DispatchResult {
206
1587
			let (key, new) = key_value.into_parts();
207
1587
			T::AdminOrigin::ensure_origin(origin, &key)?;
208

            
209
			let mut old = None;
210
			Parameters::<T>::mutate(&key, |v| {
211
				old = v.clone();
212
				*v = new.clone();
213
			});
214

            
215
			Self::deposit_event(Event::Updated { key, old_value: old, new_value: new });
216

            
217
			Ok(())
218
		}
219
	}
220
	/// Default implementations of [`DefaultConfig`], which can be used to implement [`Config`].
221
	pub mod config_preludes {
222
		use super::*;
223
		use frame_support::derive_impl;
224

            
225
		/// A configuration for testing.
226
		pub struct TestDefaultConfig;
227

            
228
		#[derive_impl(frame_system::config_preludes::TestDefaultConfig, no_aggregated_types)]
229
		impl frame_system::DefaultConfig for TestDefaultConfig {}
230

            
231
		#[frame_support::register_default_impl(TestDefaultConfig)]
232
		impl DefaultConfig for TestDefaultConfig {
233
			#[inject_runtime_type]
234
			type RuntimeEvent = ();
235
			#[inject_runtime_type]
236
			type RuntimeParameters = ();
237

            
238
			type AdminOrigin = frame_support::traits::AsEnsureOriginWithArg<
239
				frame_system::EnsureRoot<Self::AccountId>,
240
			>;
241

            
242
			type WeightInfo = ();
243
		}
244
	}
245
}
246

            
247
impl<T: Config> RuntimeParameterStore for Pallet<T> {
248
	type AggregatedKeyValue = T::RuntimeParameters;
249

            
250
	fn get<KV, K>(key: K) -> Option<K::Value>
251
	where
252
		KV: AggregatedKeyValue,
253
		K: Key + Into<<KV as AggregatedKeyValue>::Key>,
254
		<KV as AggregatedKeyValue>::Key: IntoKey<
255
			<<Self as RuntimeParameterStore>::AggregatedKeyValue as AggregatedKeyValue>::Key,
256
		>,
257
		<<Self as RuntimeParameterStore>::AggregatedKeyValue as AggregatedKeyValue>::Value:
258
			TryIntoKey<<KV as AggregatedKeyValue>::Value>,
259
		<KV as AggregatedKeyValue>::Value: TryInto<K::WrappedValue>,
260
	{
261
		let key: <KV as AggregatedKeyValue>::Key = key.into();
262
		let val = Parameters::<T>::get(key.into_key());
263
		val.and_then(|v| {
264
			let val: <KV as AggregatedKeyValue>::Value = v.try_into_key().ok()?;
265
			let val: K::WrappedValue = val.try_into().ok()?;
266
			let val = val.into();
267
			Some(val)
268
		})
269
	}
270
}