Crate pallet_parameters
source ·Expand description
§⚠️ 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:
#[dynamic_params(RuntimeParameters, pallet_parameters::Parameters::<Runtime>)]
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;
}
#[dynamic_pallet_params]
#[codec(index = 2)]
pub mod nis {
#[codec(index = 0)]
pub static Target: u64 = 0;
}
#[dynamic_pallet_params]
#[codec(index = 3)]
pub mod somE_weird_SPElLInG_s {
#[codec(index = 0)]
pub static V: u64 = 0;
}
}
A permissioned origin can be define on a per-key basis like this:
mod custom_origin {
use super::*;
pub struct ParamsManager;
impl EnsureOriginWithArg<RuntimeOrigin, RuntimeParametersKey> for ParamsManager {
type Success = ();
fn try_origin(
origin: RuntimeOrigin,
key: &RuntimeParametersKey,
) -> Result<Self::Success, RuntimeOrigin> {
// 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::SomEWeirdSPElLInGS(_) |
RuntimeParametersKey::Nis(_) |
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<RuntimeOrigin, ()> {
Ok(RuntimeOrigin::signed(123))
}
}
}
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.
#[cfg(feature = "runtime-benchmarks")]
impl Default for RuntimeParameters {
fn default() -> Self {
RuntimeParameters::Pallet1(dynamic_params::pallet1::Parameters::Key1(
dynamic_params::pallet1::Key1,
Some(123),
))
}
}
Now the aggregated parameter needs to be injected into the pallet config:
#[derive_impl(pallet_parameters::config_preludes::TestDefaultConfig)]
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.
}
As last step, the parameters can now be used in other pallets 🙌
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 = ();
}
§Examples Usage
Now to demonstrate how the values can be updated:
#[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(),
);
});
}
§Low Level / Implementation Details
The pallet stores the parameters in a storage map and implements the matching Get<Value>
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
- Easy to update without runtime upgrade.
- Exposes metadata and docs for user convenience.
- Can be permissioned on a per-key base.
§Design
- 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 theEnsureOriginWithArg
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.
Re-exports§
pub use pallet::*;
Modules§
- The
pallet
module in each FRAME pallet hosts the most important items needed to construct this pallet.
Traits§
- Weight functions needed for
pallet_parameters
.