Skip to content

Pallet Decoupling

Georgi Zlatarev edited this page Jul 27, 2021 · 1 revision

Why decoupling

  • Verbosity
  • Readability
  • Debug
  • Maintainability
  • ...

Coupled Way

Example:

// Definition
pub trait Trait: frame_sysmte::Trait + module1::Trait + module2::Trait + ... {
   type Event: From<Event<Self>> + Into<<Self as system::Trait>::Event>;
   ...
}

// usage
{
   ...
   // read storages
   let module1_value = <module1::ModuleStorage1<T>>::get(key1);
   let module2_value = <module2::ModuleStorage2<T>>::get(key2);
   // write storages
   <module1::ModuleStorage1<T>>::mutate(&key1, |val| {
       // some change here
   });
   <module2::ModuleStorage2<T>>::mutate(&key2, |val| {
       // some change here
   });
   ...
}

And you have to import module1 and module2 to your current cargo.toml.

module1 = { path = ".." }
module2 = { path = ".." }

How to decouple

Senario:

There's a pallet named Exchange that wants to read/write pallet Token’s storage.

Token has a storage:

decl_storage! {
    trait Store for Module<T: Trait> as Token {
        pub AccountToken get(fn token): map hasher(blake2_128_concat) (T::AccountId, Symbol) => Token;
    }
}
  1. Define a trait(Better do define it in a crate only execept Token)
pub trait TokenTrait<AccountId> {
   fn decrease(who: &AccountId, symbol: Symbol, amount: u128);
   fn increase(who: &AccountId, symbol: Symbol, amount: u128);
   fn get_token(who: &AccountId, symbol: Symbol) -> Token;
   fn has_token(who: &AccountId, symbol: Symbol) -> bool;
}
  1. Impl this trait for module Token
// impl this trait for others module can access and modyfy token'data
impl<T: Trait> token_primitives::TokenTrait<T::AccountId> for Module<T> {
   fn decrease(who: &T::AccountId, symbol: Symbol, amount: u128) {
       <AccountToken<T>>::mutate((who, symbol), |token| {
           token.balance = token.balance.saturating_sub(amount);
       });
   }

   fn increase(who: &T::AccountId, symbol: Symbol, amount: u128) {
       <AccountToken<T>>::mutate((who, symbol), |token| {
           token.balance = token.balance.saturating_add(amount);
       });
   }

   fn get_token(who: &T::AccountId, symbol: Symbol) -> Token {
       <AccountToken<T>>::get((who, symbol))
   }

   fn has_token(who: &T::AccountId, symbol: Symbol) -> bool {
       <AccountToken<T>>::contains_key((who, symbol))
   }
}
  1. Register a trait handler for module Exchange.
use token_primitives::{Symbol, TokenTrait};

pub trait Trait: frame_system::Trait {
   type Event: From<Event<Self>> + Into<<Self as frame_system::Trait>::Event>;
  
   /// Token trait handler
   type TokenTrait: TokenTrait<Self::AccountId>;
}

// usage
ensure!(
    T::TokenTrait::has_token(&exchanger, exchange_symbol) && T::TokenTrait::has_token(&exchanger, target_symbol),
    Error::<T>::UserHasNoThisToken
);
ensure!(
    T::TokenTrait::get_token(&exchanger, exchange_symbol).balance >= amount,
    Error::<T>::ExchangeTooMuch
);
  1. Connect them in runtime
// configure token for runtime
impl token::Trait for Runtime {
   type Event = Event;
}

// configure token exchange for runtime
impl token_exchange::Trait for Runtime {
   type Event = Event;
   type TokenTrait = Token; // this is the key for decoupling modules
}

construct_runtime!(
   pub enum Runtime where
       Block = Block,
       NodeBlock = node_primitives::Block,
       UncheckedExtrinsic = UncheckedExtrinsic
   {
        …
       // construct token and exchange fo runtime
       Token: token::{Pallet, Call, Storage, Event<T>},
       TokenExchange: token_exchange::{Pallet, Call, Storage, Event<T>},
     }
);