Configurable Pallet Constants

pallets/constant-config Try on playground View on GitHub

To declare constant values within a runtime, it is necessary to import the Get trait from frame_support

use frame_support::traits::Get;

Configurable constants are declared as associated types in the pallet's configuration trait using the Get<T> syntax for any type T.

pub trait Config: frame_system::Config {
	type Event: From<Event> + Into<<Self as frame_system::Config>::Event>;

	/// Maximum amount added per invocation
	type MaxAddend: Get<u32>;

	/// Frequency with which the stored value is deleted
	type ClearFrequency: Get<Self::BlockNumber>;
}

In order to make these constants and their values appear in the runtime metadata, it is necessary to declare them with the const syntax. Usually constants are declared at the top of this block, right after fn deposit_event.

#[pallet::config]
pub trait Config: frame_system::Config {
	type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>;

	/// Maximum amount added per invocation
	type MaxAddend: Get<u32>;

	/// Frequency with which the stored value is deleted
	type ClearFrequency: Get<Self::BlockNumber>;
}

This example manipulates a single value in storage declared as SingleValue.

#[pallet::storage]
#[pallet::getter(fn single_value)]
pub(super) type SingleValue<T: Config> = StorageValue<_, u32, ValueQuery>;

SingleValue is set to 0 every ClearFrequency number of blocks in the on_finalize function that runs at the end of blocks execution.

#[pallet::hooks]
impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
	fn on_finalize(n: T::BlockNumber) {
		if (n % T::ClearFrequency::get()).is_zero() {
			let c_val = SingleValue::<T>::get();
			SingleValue::<T>::put(0u32);
			Self::deposit_event(Event::Cleared(c_val));
		}
	}
}

Signed transactions may invoke the add_value runtime method to increase SingleValue as long as each call adds less than MaxAddend. There is no anti-sybil mechanism so a user could just split a larger request into multiple smaller requests to overcome the MaxAddend, but overflow is still handled appropriately.

#[pallet::weight(10_000)]
pub fn add_value(origin: OriginFor<T>, val_to_add: u32) -> DispatchResultWithPostInfo {
	let _ = ensure_signed(origin)?;
	ensure!(
		val_to_add <= T::MaxAddend::get(),
		"value must be <= maximum add amount constant"
	);

	// previous value got
	let c_val = SingleValue::<T>::get();

	// checks for overflow when new value added
	let result = match c_val.checked_add(val_to_add) {
		Some(r) => r,
		None => {
			return Err(DispatchErrorWithPostInfo {
				post_info: PostDispatchInfo::from(()),
				error: DispatchError::Other("Addition overflowed"),
			})
		}
	};
	SingleValue::<T>::put(result);
	Self::deposit_event(Event::Added(c_val, val_to_add, result));
	Ok(().into())
}

In more complex patterns, the constant value may be used as a static, base value that is scaled by a multiplier to incorporate stateful context for calculating some dynamic fee (i.e. floating transaction fees).

Supplying the Constant Value

When the pallet is included in a runtime, the runtime developer supplies the value of the constant using the parameter_types! macro. This pallet is included in the super-runtime where we see the following macro invocation and trait implementation.


#![allow(unused)]
fn main() {
parameter_types! {
	pub const MaxAddend: u32 = 1738;
	pub const ClearFrequency: u32 = 10;
}

#[pallet::config]
pub trait Config: frame_system::Config {
	type Event: From<Event> + IsType<<Self as frame_system::Config>::Event>;

	/// Maximum amount added per invocation
	type MaxAddend: Get<u32>;

	/// Frequency with which the stored value is deleted
	type ClearFrequency: Get<Self::BlockNumber>;
}
}