#
pezkuwi_sdk_docs/reference_docs/
frame_pallet_coupling.rs

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