#
pezkuwi_sdk_docs/reference_docs/
frame_pallet_coupling.rs

    1//! # FRAME Pallet Coupling
    2//!
    3//! This reference document explains how FRAME pallets can be combined to interact together.
    4//!
    5//! It is suggested to re-read [`crate::pezkuwi_sdk::frame_runtime`], notably the information
    6//! around [`frame::pallet_macros::config`]. Recall that:
    7//!
    8//! > Configuration trait of a pallet: It allows a pallet to receive types at a later
    9//! > point from the runtime that wishes to contain it. It allows the pallet to be parameterized
    10//! > over both types and values.
    11//!
    12//! ## Context, Background
    13//!
    14//! FRAME pallets, as per described in [`crate::pezkuwi_sdk::frame_runtime`] are:
    15//!
    16//! > A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be
    17//! linked to other pallets.
    18//!
    19//! That is to say:
    20//!
    21//! * *encapsulated*: Ideally, a FRAME pallet contains encapsulated logic which has clear
    22//!   boundaries. It is generally a bad idea to build a single monolithic pallet that does multiple
    23//!   things, such as handling currencies, identities and staking all at the same time.
    24//! * *linked to other pallets*: But, adhering extensively to the above also hinders the ability to
    25//!   write useful applications. Pallets often need to work with each other, communicate and use
    26//!   each other's functionalities.
    27//!
    28//! The broad principle that allows pallets to be linked together is the same way through which a
    29//! pallet uses its `Config` trait to receive types and values from the runtime that contains it.
    30//!
    31//! There are generally two ways to achieve this:
    32//!
    33//! 1. Tight coupling pallets.
    34//! 2. Loose coupling pallets.
    35//!
    36//! To explain the difference between the two, consider two pallets, `A` and `B`. In both cases, `A`
    37//! wants to use some functionality exposed by `B`.
    38//!
    39//! When tightly coupling pallets, `A` can only exist in a runtime if `B` is also present in the
    40//! same runtime. That is, `A` is expressing that can only work if `B` is present.
    41//!
    42//! This translates to the following Rust code:
    43//!
    44//! ```
    45//! trait Pallet_B_Config {}
    46//! trait Pallet_A_Config: Pallet_B_Config {}
    47//! ```
    48//!
    49//! Contrary, when pallets are loosely coupled, `A` expresses that some functionality, expressed via
    50//! a trait `F`, needs to be fulfilled. This trait is then implemented by `B`, and the two pallets
    51//! are linked together at the runtime level. This means that `A` only relies on the implementation
    52//! of `F`, which may be `B`, or another implementation of `F`.
    53//!
    54//! This translates to the following Rust code:
    55//!
    56//! ```
    57//! trait F {}
    58//! trait Pallet_A_Config {
    59//!    type F: F;
    60//! }
    61//! // Pallet_B will implement and fulfill `F`.
    62//! ```
    63//!
    64//! ## Example
    65//!
    66//! Consider the following example, in which `pallet-foo` needs another pallet to provide the block
    67//! author to it, and `pallet-author` which has access to this information.
    68#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_foo)]
    69#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author)]
    70//!
    71//! ### Tight Coupling Pallets
    72//!
    73//! To tightly couple `pallet-foo` and `pallet-author`, we use Rust's supertrait system. When a
    74//! pallet makes its own `trait Config` be bounded by another pallet's `trait Config`, it is
    75//! expressing two things:
    76//!
    77//! 1. That it can only exist in a runtime if the other pallet is also present.
    78//! 2. That it can use the other pallet's functionality.
    79//!
    80//! `pallet-foo`'s `Config` would then look like:
    81#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)]
    82//!
    83//! And `pallet-foo` can use the method exposed by `pallet_author::Pallet` directly:
    84#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)]
    85//!
    86//!
    87//! ### Loosely  Coupling Pallets
    88//!
    89//! If `pallet-foo` wants to *not* rely on `pallet-author` directly, it can leverage its
    90//! `Config`'s associated types. First, we need a trait to express the functionality that
    91//! `pallet-foo` wants to obtain:
    92#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)]
    93//!
    94//! > We sometimes refer to such traits that help two pallets interact as "glue traits".
    95//!
    96//! Next, `pallet-foo` states that it needs this trait to be provided to it, at the runtime level,
    97//! via an associated type:
    98#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)]
    99//!
    100//! Then, `pallet-foo` can use this trait to obtain the block author, without knowing where it comes
    101//! from:
    102#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)]
    103//!
    104//! Then, if `pallet-author` implements this glue-trait:
    105#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author_provider)]
    106//!
    107//! And upon the creation of the runtime, the two pallets are linked together as such:
    108#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)]
    109//!
    110//! Crucially, when using loose coupling, we gain the flexibility of providing different
    111//! implementations of `AuthorProvider`, such that different users of a `pallet-foo` can use
    112//! different ones, without any code change being needed. For example, in the code snippets of this
    113//! module, you can find [`OtherAuthorProvider`], which is an alternative implementation of
    114//! [`AuthorProvider`].
    115#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)]
    116//!
    117//! A common pattern in pezkuwi-sdk is to provide an implementation of such glu traits for the unit
    118//! type as a "default/test behavior".
    119#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)]
    120//!
    121//! ## Frame System
    122//!
    123//! With the above information in context, we can conclude that **`frame_system` is a special pallet
    124//! that is tightly coupled with every other pallet**. This is because it provides the fundamental
    125//! system functionality that every pallet needs, such as some types like
    126//! [`frame::prelude::frame_system::Config::AccountId`],
    127//! [`frame::prelude::frame_system::Config::Hash`], and some functionality such as block number,
    128//! etc.
    129//!
    130//! ## Recap
    131//!
    132//! To recap, consider the following rules of thumb:
    133//!
    134//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In
    135//!   general, it is easier to argue about multiple pallet if they only communicate together via a
    136//!   known trait, rather than having access to all of each others public items, such as storage and
    137//!   dispatchables.
    138//! * If a group of pallets is meant to work together, but is not foreseen to be generalized, or
    139//!   used by others, consider tightly coupling pallets, *if it simplifies the development*.
    140//! * If a pallet needs a functionality provided by another pallet, but multiple implementations can
    141//!   be foreseen, consider loosely coupling pallets.
    142//!
    143//! For example, all pallets in `pezkuwi-sdk` that needed to work with currencies could have been
    144//! tightly coupled with [`pallet_balances`]. But, `pezkuwi-sdk` also provides [`pallet_assets`]
    145//! (and more implementations by the community), therefore all pallets use traits to loosely couple
    146//! with balances or assets pallet. More on this in [`crate::reference_docs::frame_tokens`].
    147//!
    148//! ## Further References
    149//!
    150//! - <https://www.youtube.com/watch?v=0eNGZpNkJk4>
    151//! - <https://exchange.pezkuwichain.app/questions/922/pallet-loose-couplingtight-coupling-and-missing-traits>
    152//!
    153//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider
    154//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider
    155
    156#![allow(unused)]
    157
    158use frame::prelude::*;
    159
    160#[docify::export]
    161#[frame::pallet]
    162pub mod pallet_foo {
    163	use super::*;
    164
    165	#[pallet::config]
    166	pub trait Config: frame_system::Config {}
    167
    168	#[pallet::pallet]
    169	pub struct Pallet<T>(_);
    170
    171	impl<T: Config> Pallet<T> {
    172		fn do_stuff_with_author() {
    173			// needs block author here
    174		}
    175	}
    176}
    177
    178#[docify::export]
    179#[frame::pallet]
    180pub mod pallet_author {
    181	use super::*;
    182
    183	#[pallet::config]
    184	pub trait Config: frame_system::Config {}
    185
    186	#[pallet::pallet]
    187	pub struct Pallet<T>(_);
    188
    189	impl<T: Config> Pallet<T> {
    190		pub fn author() -> T::AccountId {
    191			todo!("somehow has access to the block author and can return it here")
    192		}
    193	}
    194}
    195
    196#[frame::pallet]
    197pub mod pallet_foo_tight {
    198	use super::*;
    199
    200	#[pallet::pallet]
    201	pub struct Pallet<T>(_);
    202
    203	#[docify::export(tight_config)]
    204	/// This pallet can only live in a runtime that has both `frame_system` and `pallet_author`.
    205	#[pallet::config]
    206	pub trait Config: frame_system::Config + pallet_author::Config {}
    207
    208	#[docify::export(tight_usage)]
    209	impl<T: Config> Pallet<T> {
    210		// anywhere in `pallet-foo`, we can call into `pallet-author` directly, namely because
    211		// `T: pallet_author::Config`
    212		fn do_stuff_with_author() {
    213			let _ = pallet_author::Pallet::<T>::author();
    214		}
    215	}
    216}
    217
    218#[docify::export]
    219/// Abstraction over "something that can provide the block author".
    220pub trait AuthorProvider<AccountId> {
    221	fn author() -> AccountId;
    222}
    223
    224#[frame::pallet]
    225pub mod pallet_foo_loose {
    226	use super::*;
    227
    228	#[pallet::pallet]
    229	pub struct Pallet<T>(_);
    230
    231	#[docify::export(loose_config)]
    232	#[pallet::config]
    233	pub trait Config: frame_system::Config {
    234		/// This pallet relies on the existence of something that implements [`AuthorProvider`],
    235		/// which may or may not be `pallet-author`.
    236		type AuthorProvider: AuthorProvider<Self::AccountId>;
    237	}
    238
    239	#[docify::export(loose_usage)]
    240	impl<T: Config> Pallet<T> {
    241		fn do_stuff_with_author() {
    242			let _ = T::AuthorProvider::author();
    243		}
    244	}
    245}
    246
    247#[docify::export(pallet_author_provider)]
    248impl<T: pallet_author::Config> AuthorProvider<T::AccountId> for pallet_author::Pallet<T> {
    249	fn author() -> T::AccountId {
    250		pallet_author::Pallet::<T>::author()
    251	}
    252}
    253
    254pub struct OtherAuthorProvider;
    255
    256#[docify::export(other_author_provider)]
    257impl<AccountId> AuthorProvider<AccountId> for OtherAuthorProvider {
    258	fn author() -> AccountId {
    259		todo!("somehow get the block author here")
    260	}
    261}
    262
    263#[docify::export(unit_author_provider)]
    264impl<AccountId> AuthorProvider<AccountId> for () {
    265	fn author() -> AccountId {
    266		todo!("somehow get the block author here")
    267	}
    268}
    269
    270pub mod runtime {
    271	use super::*;
    272	use cumulus_pallet_aura_ext::pallet;
    273	use frame::{runtime::prelude::*, testing_prelude::*};
    274
    275	construct_runtime!(
    276		pub struct Runtime {
    277			System: frame_system,
    278			PalletFoo: pallet_foo_loose,
    279			PalletAuthor: pallet_author,
    280		}
    281	);
    282
    283	#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
    284	impl frame_system::Config for Runtime {
    285		type Block = MockBlock<Self>;
    286	}
    287
    288	impl pallet_author::Config for Runtime {}
    289
    290	#[docify::export(runtime_author_provider)]
    291	impl pallet_foo_loose::Config for Runtime {
    292		type AuthorProvider = pallet_author::Pallet<Runtime>;
    293		// which is also equivalent to
    294		// type AuthorProvider = PalletAuthor;
    295	}
    296}