Search code examples
substrate

How can I use a storage variable in one pallet as a configuration variable in another?


So you have 2 pallets. The goal is to have a configuration variable C in pallet_1:

  • In pallet_2 have some logic that constantly changes storage var A;
  • Then the runtime should automatically write the new state of var A to the constant C in pallet_1.

The configuration variable in pallet_1's trait should be something like MyConfig: Get<u32>

Something like this: https://github.com/paritytech/substrate/blob/master/frame/recovery/src/lib.rs#L200

So this says that we need to define a type which implements the Get<T> trait:

https://github.com/paritytech/substrate/blob/master/frame/support/src/traits.rs#L471

So this is just a function fn get() -> T

Lets then assume you have pallet_2, where there is some storage item you want to control this config:

decl_storage!{
    MyStorage: u32;
}

In your runtime/src/lib.rs, you define something like:

struct StorageToConfig;
impl Get<u32> for StorageToConfig {
     fn get() -> u32 {
         return pallet_2::MyStorage::get();
    }
}

How can you use this struct in your trait definition in pallet_1, and make sure the runtime automatically pushes (or pallet_1 pulls from the runtime) the MyStorage var in pallet_2?


Solution

  • Here is a working example of how you would attach the value of a configuration trait to another pallet's storage item.

    Pallet 1

    Here is pallet_1 which has the storage item we want to use.

    NOTE: This storage is marked pub so it is accessible outside the pallet.

    use frame_support::{decl_module, decl_storage};
    use frame_system::ensure_signed;
    
    pub trait Trait: frame_system::Trait {}
    
    decl_storage! {
        trait Store for Module<T: Trait> as TemplateModule {
            pub MyStorage: u32;
        }
    }
    
    decl_module! {
        pub struct Module<T: Trait> for enum Call where origin: T::Origin {
            #[weight = 0]
            pub fn set_storage(origin, value: u32) {
                let _ = ensure_signed(origin)?;
                MyStorage::put(value);
            }
        }
    }
    

    Pallet 2

    Here is pallet_2 which has a configuration trait that we want to populate with the storage item from pallet_1:

    use frame_support::{decl_module, dispatch, traits::Get};
    use frame_system::ensure_signed;
    
    pub trait Trait: frame_system::Trait {
        type MyConfig: Get<u32>;
    }
    
    decl_module! {
        pub struct Module<T: Trait> for enum Call where origin: T::Origin {
            #[weight = 0]
            pub fn do_something(origin) -> dispatch::DispatchResult {
                let _ = ensure_signed(origin)?;
    
                let _my_config =  T::MyConfig::get();
    
                Ok(())
            }
        }
    }
    

    Runtime Configuration

    These two pallets are very straightforward and work separately. But if we want to connect them, we need to configure our runtime:

    use frame_support::traits::Get;
    
    impl pallet_1::Trait for Runtime {}
    
    pub struct StorageToConfig;
    impl Get<u32> for StorageToConfig {
         fn get() -> u32 {
             return pallet_1::MyStorage::get();
        }
    }
    
    impl pallet_2::Trait for Runtime {
        type MyConfig = StorageToConfig;
    }
    
    // We also update the `construct_runtime!`, but that is omitted for this example.
    

    Here we have defined a struct StorageToConfig which implements the Get<u32> trait that is expected by pallet_2. This struct tells the runtime when MyConfig::get() is called, it should then call pallet_1::MyStorage::get() which reads into runtime storage and gets that value.

    So now, every call to T::MyConfig::get() in pallet_2 will be a storage read, and will get whatever value is set in pallet_1.

    Let me know if this helps!