Expand description
Learn about the details of what derives are needed for a type to be store-able in frame
storage.
§Frame storage derives
Note:
In all examples, a few lines of boilerplate have been hidden from each snippet for conciseness.
Let’s begin by starting to store a NewType in a storage item:
#[frame::pallet]
pub mod pallet {
pub struct NewType(u32);
#[pallet::storage]
pub type Something<T> = StorageValue<_, NewType>;
}This raises a number of compiler errors, like:
the trait `MaxEncodedLen` is not implemented for `NewType`, which is required by
`frame::prelude::StorageValue<_GeneratedPrefixForStorageSomething<T>, NewType>:
StorageInfoTrait`This implies the following set of traits that need to be derived for a type to be stored in
frame storage:
#[frame::pallet]
pub mod pallet {
#[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
pub struct NewType(u32);
#[pallet::storage]
pub type Something<T> = StorageValue<_, NewType>;
}Next, let’s look at how this will differ if we are to store a type that is derived from T in
storage, such as frame::prelude::BlockNumberFor:
#[frame::pallet]
pub mod pallet {
#[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
pub struct NewType<T: Config>(BlockNumberFor<T>);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, NewType<T>>;
}Surprisingly, this will also raise a number of errors, like:
the trait `TypeInfo` is not implemented for `T`, which is required
by`frame_support::pallet_prelude::StorageValue<pallet_2::_GeneratedPrefixForStorageSomething<T>,
pallet_2::NewType<T>>:StorageEntryMetadataBuilderWhy is that? The underlying reason is that the TypeInfo derive macro will only work for
NewType if all of NewType’s generics also implement TypeInfo. This is not the case for T
in the example above.
If you expand an instance of the derive, you will find something along the lines of:
impl<T> TypeInfo for NewType<T> where T: TypeInfo { ... }. This is the reason why the
TypeInfo trait is required for T.
To fix this, we need to add a #[scale_info(skip_type_params(T))]
attribute to NewType. This additional macro will instruct the derive to skip the bound on
T.
#[frame::pallet]
pub mod pallet {
#[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo)]
#[scale_info(skip_type_params(T))]
pub struct NewType<T: Config>(BlockNumberFor<T>);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, NewType<T>>;
}Next, let’s say we wish to store NewType as frame::prelude::ValueQuery, which means it
must also implement Default. This should be as simple as adding derive(Default) to it,
right?
#[frame::pallet]
pub mod pallet {
#[derive(codec::Encode, codec::Decode, codec::MaxEncodedLen, scale_info::TypeInfo, Default)]
#[scale_info(skip_type_params(T))]
pub struct NewType<T: Config>(BlockNumberFor<T>);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
}Under the hood, the expansion of the derive(Default) will suffer from the same restriction as
before: it will only work if T: Default, and T is not Default. Note that this is an
expected issue: T is merely a wrapper of many other types, such as BlockNumberFor<T>.
BlockNumberFor<T> should indeed implement Default, but T implementing Default is rather
meaningless.
To fix this, frame provides a set of macros that are analogous to normal rust derive macros, but
work nicely on top of structs that are generic over T: Config. These macros are:
frame::prelude::DefaultNoBoundframe::prelude::DebugNoBoundframe::prelude::PartialEqNoBoundframe::prelude::EqNoBoundframe::prelude::CloneNoBoundframe::prelude::PartialOrdNoBoundframe::prelude::OrdNoBound
The above traits are almost certainly needed for your tests - to print your type, assert equality or clone it.
We can fix the following example by using frame::prelude::DefaultNoBound.
#[frame::pallet]
pub mod pallet {
#[derive(
codec::Encode,
codec::Decode,
codec::MaxEncodedLen,
scale_info::TypeInfo,
DefaultNoBound
)]
#[scale_info(skip_type_params(T))]
pub struct NewType<T:Config>(BlockNumberFor<T>);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, NewType<T>, ValueQuery>;
}Finally, if a custom type that is provided through Config is to be stored in the storage, it
is subject to the same trait requirements. The following does not work:
#[frame::pallet]
pub mod pallet {
use frame::prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type CustomType;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, T::CustomType>;
}But adding the right trait bounds will fix it.
#[frame::pallet]
pub mod pallet {
use frame::prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {
type CustomType: codec::FullCodec
+ codec::MaxEncodedLen
+ scale_info::TypeInfo
+ Debug
+ Default;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type Something<T: Config> = StorageValue<_, T::CustomType>;
}