From 50cc1c2f7efa0e61ac773b8e89171c9640315475 Mon Sep 17 00:00:00 2001
From: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Date: Thu, 7 Mar 2024 11:40:30 +0000
Subject: [PATCH] Add documentation around pallet coupling (#3542)

substrate.io deprecation companion:
https://github.com/substrate-developer-hub/substrate-docs/pull/2139
pba-content companion:
https://github.com/Polkadot-Blockchain-Academy/pba-content/pull/978

partially inspired by:
https://github.com/paritytech/polkadot-sdk/issues/3535

---------

Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com>
---
 Cargo.lock                                    |   1 +
 docs/sdk/Cargo.toml                           |   1 +
 docs/sdk/src/polkadot_sdk/frame_runtime.rs    | 144 +++++----
 .../reference_docs/frame_pallet_coupling.rs   | 296 ++++++++++++++++++
 docs/sdk/src/reference_docs/mod.rs            |   4 +
 substrate/frame/support/src/lib.rs            |   5 +-
 6 files changed, 374 insertions(+), 77 deletions(-)
 create mode 100644 docs/sdk/src/reference_docs/frame_pallet_coupling.rs

diff --git a/Cargo.lock b/Cargo.lock
index 4815e976b5d..e38f09db4d3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -13332,6 +13332,7 @@ dependencies = [
  "frame-support",
  "frame-system",
  "kitchensink-runtime",
+ "pallet-assets",
  "pallet-aura",
  "pallet-authorship",
  "pallet-balances",
diff --git a/docs/sdk/Cargo.toml b/docs/sdk/Cargo.toml
index 735b3d7df61..8b498d407c0 100644
--- a/docs/sdk/Cargo.toml
+++ b/docs/sdk/Cargo.toml
@@ -65,6 +65,7 @@ pallet-aura = { path = "../../substrate/frame/aura", default-features = false }
 # Pallets and FRAME internals
 pallet-timestamp = { path = "../../substrate/frame/timestamp" }
 pallet-balances = { path = "../../substrate/frame/balances" }
+pallet-assets = { path = "../../substrate/frame/assets" }
 pallet-transaction-payment = { path = "../../substrate/frame/transaction-payment" }
 pallet-utility = { path = "../../substrate/frame/utility" }
 pallet-multisig = { path = "../../substrate/frame/multisig" }
diff --git a/docs/sdk/src/polkadot_sdk/frame_runtime.rs b/docs/sdk/src/polkadot_sdk/frame_runtime.rs
index c9eba7d64bd..f178fc82bf4 100644
--- a/docs/sdk/src/polkadot_sdk/frame_runtime.rs
+++ b/docs/sdk/src/polkadot_sdk/frame_runtime.rs
@@ -87,93 +87,89 @@
 //! * writing a runtime in pure Rust, as done in [this template](https://github.com/JoshOrndorff/frameless-node-template).
 //! * writing a runtime in AssemblyScript,as explored in [this project](https://github.com/LimeChain/subsembly).
 
-#[cfg(test)]
-mod tests {
-	use frame::prelude::*;
+use frame::prelude::*;
 
-	/// A FRAME based pallet. This `mod` is the entry point for everything else. All
-	/// `#[pallet::xxx]` macros must be defined in this `mod`. Although, frame also provides an
-	/// experimental feature to break these parts into different `mod`s. See [`pallet_examples`] for
-	/// more.
-	#[docify::export]
-	#[frame::pallet(dev_mode)]
-	pub mod pallet {
-		use super::*;
+/// A FRAME based pallet. This `mod` is the entry point for everything else. All
+/// `#[pallet::xxx]` macros must be defined in this `mod`. Although, frame also provides an
+/// experimental feature to break these parts into different `mod`s. See [`pallet_examples`] for
+/// more.
+#[docify::export]
+#[frame::pallet(dev_mode)]
+pub mod pallet {
+	use super::*;
 
-		/// The configuration trait of a pallet. Mandatory. Allows a pallet to receive types at a
-		/// later point from the runtime that wishes to contain it. It allows the pallet to be
-		/// parameterized over both types and values.
-		#[pallet::config]
-		pub trait Config: frame_system::Config {
-			/// A type that is not known now, but the runtime that will contain this pallet will
-			/// know it later, therefore we define it here as an associated type.
-			type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent>
-				+ From<Event<Self>>;
+	/// The configuration trait of a pallet. Mandatory. Allows a pallet to receive types at a
+	/// later point from the runtime that wishes to contain it. It allows the pallet to be
+	/// parameterized over both types and values.
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// A type that is not known now, but the runtime that will contain this pallet will
+		/// know it later, therefore we define it here as an associated type.
+		type RuntimeEvent: IsType<<Self as frame_system::Config>::RuntimeEvent> + From<Event<Self>>;
 
-			/// A parameterize-able value that we receive later via the `Get<_>` trait.
-			type ValueParameter: Get<u32>;
+		/// A parameterize-able value that we receive later via the `Get<_>` trait.
+		type ValueParameter: Get<u32>;
 
-			/// Similar to [`Config::ValueParameter`], but using `const`. Both are functionally
-			/// equal, but offer different tradeoffs.
-			const ANOTHER_VALUE_PARAMETER: u32;
-		}
+		/// Similar to [`Config::ValueParameter`], but using `const`. Both are functionally
+		/// equal, but offer different tradeoffs.
+		const ANOTHER_VALUE_PARAMETER: u32;
+	}
 
-		/// A mandatory struct in each pallet. All functions callable by external users (aka.
-		/// transactions) must be attached to this type (see [`frame::pallet_macros::call`]). For
-		/// convenience, internal (private) functions can also be attached to this type.
-		#[pallet::pallet]
-		pub struct Pallet<T>(PhantomData<T>);
+	/// A mandatory struct in each pallet. All functions callable by external users (aka.
+	/// transactions) must be attached to this type (see [`frame::pallet_macros::call`]). For
+	/// convenience, internal (private) functions can also be attached to this type.
+	#[pallet::pallet]
+	pub struct Pallet<T>(PhantomData<T>);
 
-		/// The events tha this pallet can emit.
-		#[pallet::event]
-		pub enum Event<T: Config> {}
+	/// The events tha this pallet can emit.
+	#[pallet::event]
+	pub enum Event<T: Config> {}
 
-		/// A storage item that this pallet contains. This will be part of the state root trie/root
-		/// of the blockchain.
-		#[pallet::storage]
-		pub type Value<T> = StorageValue<Value = u32>;
+	/// A storage item that this pallet contains. This will be part of the state root trie/root
+	/// of the blockchain.
+	#[pallet::storage]
+	pub type Value<T> = StorageValue<Value = u32>;
 
-		/// All *dispatchable* call functions (aka. transactions) are attached to `Pallet` in a
-		/// `impl` block.
-		#[pallet::call]
-		impl<T: Config> Pallet<T> {
-			/// This will be callable by external users, and has two u32s as a parameter.
-			pub fn some_dispatchable(
-				_origin: OriginFor<T>,
-				_param: u32,
-				_other_para: u32,
-			) -> DispatchResult {
-				Ok(())
-			}
+	/// All *dispatchable* call functions (aka. transactions) are attached to `Pallet` in a
+	/// `impl` block.
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		/// This will be callable by external users, and has two u32s as a parameter.
+		pub fn some_dispatchable(
+			_origin: OriginFor<T>,
+			_param: u32,
+			_other_para: u32,
+		) -> DispatchResult {
+			Ok(())
 		}
 	}
+}
 
-	/// A simple runtime that contains the above pallet and `frame_system`, the mandatory pallet of
-	/// all runtimes. This runtime is for testing, but it shares a lot of similarities with a *real*
-	/// runtime.
-	#[docify::export]
-	pub mod runtime {
-		use super::pallet as pallet_example;
-		use frame::{prelude::*, testing_prelude::*};
-
-		// The major macro that amalgamates pallets into `enum Runtime`
-		construct_runtime!(
-			pub enum Runtime {
-				System: frame_system,
-				Example: pallet_example,
-			}
-		);
+/// A simple runtime that contains the above pallet and `frame_system`, the mandatory pallet of
+/// all runtimes. This runtime is for testing, but it shares a lot of similarities with a *real*
+/// runtime.
+#[docify::export]
+pub mod runtime {
+	use super::pallet as pallet_example;
+	use frame::{prelude::*, testing_prelude::*};
 
-		// These `impl` blocks specify the parameters of each pallet's `trait Config`.
-		#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
-		impl frame_system::Config for Runtime {
-			type Block = MockBlock<Self>;
+	// The major macro that amalgamates pallets into `enum Runtime`
+	construct_runtime!(
+		pub enum Runtime {
+			System: frame_system,
+			Example: pallet_example,
 		}
+	);
 
-		impl pallet_example::Config for Runtime {
-			type RuntimeEvent = RuntimeEvent;
-			type ValueParameter = ConstU32<42>;
-			const ANOTHER_VALUE_PARAMETER: u32 = 42;
-		}
+	// These `impl` blocks specify the parameters of each pallet's `trait Config`.
+	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+	impl frame_system::Config for Runtime {
+		type Block = MockBlock<Self>;
+	}
+
+	impl pallet_example::Config for Runtime {
+		type RuntimeEvent = RuntimeEvent;
+		type ValueParameter = ConstU32<42>;
+		const ANOTHER_VALUE_PARAMETER: u32 = 42;
 	}
 }
diff --git a/docs/sdk/src/reference_docs/frame_pallet_coupling.rs b/docs/sdk/src/reference_docs/frame_pallet_coupling.rs
new file mode 100644
index 00000000000..942717ecfb2
--- /dev/null
+++ b/docs/sdk/src/reference_docs/frame_pallet_coupling.rs
@@ -0,0 +1,296 @@
+//! # FRAME Pallet Coupling
+//!
+//! This reference document explains how FRAME pallets can be combined to interact together.
+//!
+//! It is suggested to re-read [`crate::polkadot_sdk::frame_runtime`], notably the information
+//! around [`frame::pallet_macros::config`]. Recall that:
+//!
+//! > Configuration trait of a pallet: It allows a pallet to receive types at a later
+//! > point from the runtime that wishes to contain it. It allows the pallet to be parameterized
+//! > over both types and values.
+//!
+//! ## Context, Background
+//!
+//! FRAME pallets, as per described in [`crate::polkadot_sdk::frame_runtime`] are:
+//!
+//! > A pallet is a unit of encapsulated logic. It has a clearly defined responsibility and can be
+//! linked to other pallets.
+//!
+//! That is to say:
+//!
+//! * *encapsulated*: Ideally, a FRAME pallet contains encapsulated logic which has clear
+//!   boundaries. It is generally a bad idea to build a single monolithic pallet that does multiple
+//!   things, such as handling currencies, identities and staking all at the same time.
+//! * *linked to other pallets*: But, adhering extensively to the above also hinders the ability to
+//!   write useful applications. Pallets often need to work with each other, communicate and use
+//!   each other's functionalities.
+//!
+//! The broad principle that allows pallets to be linked together is the same way through which a
+//! pallet uses its `Config` trait to receive types and values from the runtime that contains it.
+//!
+//! There are generally two ways to achieve this:
+//!
+//! 1. Tight coupling pallets
+//! 2. Loose coupling pallets
+//!
+//! To explain the difference between the two, consider two pallets, `A` and `B`. In both cases, `A`
+//! wants to use some functionality exposed by `B`.
+//!
+//! When tightly coupling pallets, `A` can only exist in a runtime if `B` is also present in the
+//! same runtime. That is, `A` is expressing that can only work if `B` is present.
+//!
+//! This translates to the following Rust code:
+//!
+//! ```
+//! trait Pallet_B_Config {}
+//! trait Pallet_A_Config: Pallet_B_Config {}
+//! ```
+//!
+//! Contrary, when pallets are loosely coupled, `A` expresses that some functionality, expressed via
+//! a trait `F`, needs to be fulfilled. This trait is then implemented by `B`, and the two pallets
+//! are linked together at the runtime level. This means that `A` only relies on the implementation
+//! of `F`, which may be `B`, or another implementation of `F`.
+//!
+//! This translates to the following Rust code:
+//!
+//! ```
+//! trait F {}
+//! trait Pallet_A_Config {
+//!    type F: F;
+//! }
+//! // Pallet_B will implement and fulfill `F`.
+//! ```
+//!
+//! ## Example
+//!
+//! Consider the following example, in which `pallet-foo` needs another pallet to provide the block
+//! author to it, and `pallet-author` which has access to this information.
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_foo)]
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author)]
+//!
+//! ### Tight Coupling Pallets
+//!
+//! To tightly couple `pallet-foo` and `pallet-author`, we use Rust's supertrait system. When a
+//! pallet makes its own `trait Config` be bounded by another pallet's `trait Config`, it is
+//! expressing two things:
+//!
+//! 1. that it can only exist in a runtime if the other pallet is also present.
+//! 2. that it can use the other pallet's functionality.
+//!
+//! `pallet-foo`'s `Config` would then look like:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)]
+//!
+//! And `pallet-foo` can use the method exposed by `pallet_author::Pallet` directly:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)]
+//!
+//!
+//! ### Loosely  Coupling Pallets
+//!
+//! If `pallet-foo` wants to *not* rely on `pallet-author` directly, it can leverage its
+//! `Config`'s associated types. First, we need a trait to express the functionality that
+//! `pallet-foo` wants to obtain:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)]
+//!
+//! > We sometimes refer to such traits that help two pallets interact as "glue traits".
+//!
+//! Next, `pallet-foo` states that it needs this trait to be provided to it, at the runtime level,
+//! via an associated type:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)]
+//!
+//! Then, `pallet-foo` can use this trait to obtain the block author, without knowing where it comes
+//! from:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)]
+//!
+//! Then, if `pallet-author` implements this glue-trait:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author_provider)]
+//!
+//! And upon the creation of the runtime, the two pallets are linked together as such:
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)]
+//!
+//! Crucially, when using loose coupling, we gain the flexibility of providing different
+//! implementations of `AuthorProvider`, such that different users of a `pallet-foo` can use
+//! different ones, without any code change being needed. For example, in the code snippets of this
+//! module, you can fund [`OtherAuthorProvider`] which is an alternative implementation of
+//! [`AuthorProvider`].
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)]
+//!
+//! A common pattern in polkadot-sdk is to provide an implementation of such glu traits for the unit
+//! type as a "default/test behavior".
+#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)]
+//!
+//! ## Frame System
+//!
+//! With the above information in context, we can conclude that **`frame_system` is a special pallet
+//! that is tightly coupled with every other pallet**. This is because it provides the fundamental
+//! system functionality that every pallet needs, such as some types like
+//! [`frame::prelude::frame_system::Config::AccountId`],
+//! [`frame::prelude::frame_system::Config::Hash`], and some functionality such as block number,
+//! etc.
+//!
+//! ## Recap
+//!
+//! To recap, consider the following rules of thumb:
+//!
+//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In
+//!   general, it is easier to argue about multiple pallet if they only communicate together via a
+//!   known trait, rather than having access to all of each others public items, such as storage and
+//!   dispatchables.
+//! * If a group of pallets are meant to work together, and but are not foreseen to be generalized,
+//!   or used by others, consider tightly coupling pallets, *if it simplifies the development*.
+//! * If a pallet needs a functionality provided by another pallet, but multiple implementations can
+//!   be foreseen, consider loosely coupling pallets.
+//!
+//! For example, all pallets in `polkadot-sdk` that needed to work with currencies could have been
+//! tightly coupled with [`pallet_balances`]. But, `polkadot-sdk` also provides [`pallet_assets`]
+//! (and more implementations by the community), therefore all pallets use traits to loosely couple
+//! with balances or assets pallet. More on this in [`crate::reference_docs::frame_currency`].
+//!
+//! ## Further References
+//!
+//! - <https://www.youtube.com/watch?v=0eNGZpNkJk4>
+//! - <https://substrate.stackexchange.com/questions/922/pallet-loose-couplingtight-coupling-and-missing-traits>
+//!
+//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider
+//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider
+
+#![allow(unused)]
+
+use frame::prelude::*;
+
+#[docify::export]
+#[frame::pallet]
+pub mod pallet_foo {
+	use super::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	impl<T: Config> Pallet<T> {
+		fn do_stuff_with_author() {
+			// needs block author here
+		}
+	}
+}
+
+#[docify::export]
+#[frame::pallet]
+pub mod pallet_author {
+	use super::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	impl<T: Config> Pallet<T> {
+		pub fn author() -> T::AccountId {
+			todo!("somehow has access to the block author and can return it here")
+		}
+	}
+}
+
+#[frame::pallet]
+pub mod pallet_foo_tight {
+	use super::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[docify::export(tight_config)]
+	/// This pallet can only live in a runtime that has both `frame_system` and `pallet_author`.
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_author::Config {}
+
+	#[docify::export(tight_usage)]
+	impl<T: Config> Pallet<T> {
+		// anywhere in `pallet-foo`, we can call into `pallet-author` directly, namely because
+		// `T: pallet_author::Config`
+		fn do_stuff_with_author() {
+			let _ = pallet_author::Pallet::<T>::author();
+		}
+	}
+}
+
+#[docify::export]
+/// Abstraction over "something that can provide the block author".
+pub trait AuthorProvider<AccountId> {
+	fn author() -> AccountId;
+}
+
+#[frame::pallet]
+pub mod pallet_foo_loose {
+	use super::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[docify::export(loose_config)]
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// This pallet relies on the existence of something that implements [`AuthorProvider`],
+		/// which may or may not be `pallet-author`.
+		type AuthorProvider: AuthorProvider<Self::AccountId>;
+	}
+
+	#[docify::export(loose_usage)]
+	impl<T: Config> Pallet<T> {
+		fn do_stuff_with_author() {
+			let _ = T::AuthorProvider::author();
+		}
+	}
+}
+
+#[docify::export(pallet_author_provider)]
+impl<T: pallet_author::Config> AuthorProvider<T::AccountId> for pallet_author::Pallet<T> {
+	fn author() -> T::AccountId {
+		pallet_author::Pallet::<T>::author()
+	}
+}
+
+pub struct OtherAuthorProvider;
+
+#[docify::export(other_author_provider)]
+impl<AccountId> AuthorProvider<AccountId> for OtherAuthorProvider {
+	fn author() -> AccountId {
+		todo!("somehow get the block author here")
+	}
+}
+
+#[docify::export(unit_author_provider)]
+impl<AccountId> AuthorProvider<AccountId> for () {
+	fn author() -> AccountId {
+		todo!("somehow get the block author here")
+	}
+}
+
+pub mod runtime {
+	use super::*;
+	use cumulus_pallet_aura_ext::pallet;
+	use frame::{runtime::prelude::*, testing_prelude::*};
+
+	construct_runtime!(
+		pub struct Runtime {
+			System: frame_system,
+			PalletFoo: pallet_foo_loose,
+			PalletAuthor: pallet_author,
+		}
+	);
+
+	#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+	impl frame_system::Config for Runtime {
+		type Block = MockBlock<Self>;
+	}
+
+	impl pallet_author::Config for Runtime {}
+
+	#[docify::export(runtime_author_provider)]
+	impl pallet_foo_loose::Config for Runtime {
+		type AuthorProvider = pallet_author::Pallet<Runtime>;
+		// which is also equivalent to
+		// type AuthorProvider = PalletAuthor;
+	}
+}
diff --git a/docs/sdk/src/reference_docs/mod.rs b/docs/sdk/src/reference_docs/mod.rs
index 3fd815d2a15..f4b52208e2f 100644
--- a/docs/sdk/src/reference_docs/mod.rs
+++ b/docs/sdk/src/reference_docs/mod.rs
@@ -103,3 +103,7 @@ pub mod light_nodes;
 /// Learn about the offchain workers, how they function, and how to use them, as provided by the
 /// [`frame`] APIs.
 pub mod frame_offchain_workers;
+
+/// Learn about the different ways through which multiple [`frame`] pallets can be combined to work
+/// together.
+pub mod frame_pallet_coupling;
diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs
index b5b1ac09c60..6ea7fa33e77 100644
--- a/substrate/frame/support/src/lib.rs
+++ b/substrate/frame/support/src/lib.rs
@@ -1531,9 +1531,8 @@ pub mod pallet_macros {
 	/// The attribute currently only supports enum definitions, and identifiers that are named
 	/// `FreezeReason`, `HoldReason`, `LockId` or `SlashReason`. Arbitrary identifiers for the
 	/// enum are not supported. The aggregate enum generated by
-	/// [`frame_support::construct_runtime`](frame_support::construct_runtime) will have the
-	/// name of `RuntimeFreezeReason`, `RuntimeHoldReason`, `RuntimeLockId` and
-	/// `RuntimeSlashReason` respectively.
+	/// [`frame_support::construct_runtime`] will have the name of `RuntimeFreezeReason`,
+	/// `RuntimeHoldReason`, `RuntimeLockId` and `RuntimeSlashReason` respectively.
 	///
 	/// NOTE: The aggregate enum generated by `construct_runtime` generates a conversion
 	/// function from the pallet enum to the aggregate enum, and automatically derives the
-- 
GitLab