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