Developers Home»how to guides»Use a Struct in Storage

Use a Struct in Storage

Goal

Use a StorageValue struct and use it in on_initialize.

Use Cases

Keep track of different accounts and balances for testing a pallet.

Overview

Creating a struct of similarly grouped storage items is a neat way to keep track of them. They can be easier to reference than keeping individual StorageValue items separate this way. In addition, they can be used to ease testing and genesis configuration. This guide steps through the procedure of creating a struct in storage which:

  • Keeps track of an initial amount (issuance)
  • Keeps track of the account that receives that amount (minter)
  • Keeps track of an account that can burn some amount (burner)
  • Is (partially) used in on_initialize

Steps

1. Create a your struct

Call it MetaData and declare its different types:

#[derive(Clone, Encode, Decode, Eq, PartialEq, RuntimeDebug, Default)]
pub struct MetaData<AccountId, Balance> {
    issuance: Balance,
    minter: AccountId,
    burner: AccountId,
}

2. Declare the struct as a storage item

Use StorageValue to declare the struct as a new single item in storage:

#[pallet::storage]
#[pallet::getter(fn meta_data)]
    pub(super) type MetaDataStore<T: Config> = StorageValue<_, MetaData<T::AccountId, T::Balance>, ValueQuery>;

3. Configure GenesisConfig and GenesisBuild

GenesisConfig

Use #[pallet::genesis_config] to declare the admin account that you'll use in your to initialize values from your MetaData struct:

// Declare `admin` as type `T::AccountId`.
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
    pub admin: T::AccountId,
    }
// Give it a default value.
#[cfg(feature = "std")]
impl<T: Config> Default for GenesisConfig<T> {
    fn default() -> Self {
        Self {
            admin: Default::default(),
            }
        }
    }

GenesisBuild

Use #[pallet::genesis_build] to initialize the values of your struct, using admin to initialize the values of type T::AccountId:

#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig<T> {
    fn build(&self) {
        MetaDataStore::<T>::put(MetaData {
            issuance: Zero::zero(),
            minter: self.admin.clone(),
            burner: self.admin.clone(),
        });
    }
}

4. Use the struct in on_initialize()

Assign an amount to the issuance field of MetaData to be initialized when the chain is launched:

fn on_initialize(_n: T::BlockNumber) -> Weight {
    let mut meta = MetaDataStore::<T>::get();
    let value: T::Balance = 50u8.into();
    meta.issuance = meta.issuance.saturating_add(value);

    // Add the amount to the `minter` account in storage.
    Accounts::<T>::mutate(&meta.minter, |bal| {
        *bal = bal.saturating_add(value);
    });

}

Examples

Resources

Rust docs

Last edit: on

Was This Guide Helpful?
Help us improve