polkadot_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::polkadot_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::polkadot_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//! ### Tight Coupling Pallets
71//!
72//! To tightly couple `pallet-foo` and `pallet-author`, we use Rust's supertrait system. When a
73//! pallet makes its own `trait Config` be bounded by another pallet's `trait Config`, it is
74//! expressing two things:
75//!
76//! 1. That it can only exist in a runtime if the other pallet is also present.
77//! 2. That it can use the other pallet's functionality.
78//!
79//! `pallet-foo`'s `Config` would then look like:
80#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_config)]
81//! And `pallet-foo` can use the method exposed by `pallet_author::Pallet` directly:
82#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", tight_usage)]
83//! ### Loosely Coupling Pallets
84//!
85//! If `pallet-foo` wants to *not* rely on `pallet-author` directly, it can leverage its
86//! `Config`'s associated types. First, we need a trait to express the functionality that
87//! `pallet-foo` wants to obtain:
88#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", AuthorProvider)]
89//! > We sometimes refer to such traits that help two pallets interact as "glue traits".
90//!
91//! Next, `pallet-foo` states that it needs this trait to be provided to it, at the runtime level,
92//! via an associated type:
93#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_config)]
94//! Then, `pallet-foo` can use this trait to obtain the block author, without knowing where it comes
95//! from:
96#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", loose_usage)]
97//! Then, if `pallet-author` implements this glue-trait:
98#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", pallet_author_provider)]
99//! And upon the creation of the runtime, the two pallets are linked together as such:
100#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", runtime_author_provider)]
101//! Crucially, when using loose coupling, we gain the flexibility of providing different
102//! implementations of `AuthorProvider`, such that different users of a `pallet-foo` can use
103//! different ones, without any code change being needed. For example, in the code snippets of this
104//! module, you can find [`OtherAuthorProvider`], which is an alternative implementation of
105//! [`AuthorProvider`].
106#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", other_author_provider)]
107//! A common pattern in polkadot-sdk is to provide an implementation of such glu traits for the unit
108//! type as a "default/test behavior".
109#![doc = docify::embed!("./src/reference_docs/frame_pallet_coupling.rs", unit_author_provider)]
110//! ## Frame System
111//!
112//! With the above information in context, we can conclude that **`frame_system` is a special pallet
113//! that is tightly coupled with every other pallet**. This is because it provides the fundamental
114//! system functionality that every pallet needs, such as some types like
115//! [`frame::prelude::frame_system::Config::AccountId`],
116//! [`frame::prelude::frame_system::Config::Hash`], and some functionality such as block number,
117//! etc.
118//!
119//! ## Recap
120//!
121//! To recap, consider the following rules of thumb:
122//!
123//! * In all cases, try and break down big pallets apart with clear boundaries of responsibility. In
124//! general, it is easier to argue about multiple pallet if they only communicate together via a
125//! known trait, rather than having access to all of each others public items, such as storage and
126//! dispatchables.
127//! * If a group of pallets is meant to work together, but is not foreseen to be generalized, or
128//! used by others, consider tightly coupling pallets, *if it simplifies the development*.
129//! * If a pallet needs a functionality provided by another pallet, but multiple implementations can
130//! be foreseen, consider loosely coupling pallets.
131//!
132//! For example, all pallets in `polkadot-sdk` that needed to work with currencies could have been
133//! tightly coupled with [`pallet_balances`]. But, `polkadot-sdk` also provides [`pallet_assets`]
134//! (and more implementations by the community), therefore all pallets use traits to loosely couple
135//! with balances or assets pallet. More on this in [`crate::reference_docs::frame_tokens`].
136//!
137//! ## Further References
138//!
139//! - <https://www.youtube.com/watch?v=0eNGZpNkJk4>
140//! - <https://substrate.stackexchange.com/questions/922/pallet-loose-couplingtight-coupling-and-missing-traits>
141//!
142//! [`AuthorProvider`]: crate::reference_docs::frame_pallet_coupling::AuthorProvider
143//! [`OtherAuthorProvider`]: crate::reference_docs::frame_pallet_coupling::OtherAuthorProvider
144
145#![allow(unused)]
146
147use frame::prelude::*;
148
149#[docify::export]
150#[frame::pallet]
151pub mod pallet_foo {
152 use super::*;
153
154 #[pallet::config]
155 pub trait Config: frame_system::Config {}
156
157 #[pallet::pallet]
158 pub struct Pallet<T>(_);
159
160 impl<T: Config> Pallet<T> {
161 fn do_stuff_with_author() {
162 // needs block author here
163 }
164 }
165}
166
167#[docify::export]
168#[frame::pallet]
169pub mod pallet_author {
170 use super::*;
171
172 #[pallet::config]
173 pub trait Config: frame_system::Config {}
174
175 #[pallet::pallet]
176 pub struct Pallet<T>(_);
177
178 impl<T: Config> Pallet<T> {
179 pub fn author() -> T::AccountId {
180 todo!("somehow has access to the block author and can return it here")
181 }
182 }
183}
184
185#[frame::pallet]
186pub mod pallet_foo_tight {
187 use super::*;
188
189 #[pallet::pallet]
190 pub struct Pallet<T>(_);
191
192 #[docify::export(tight_config)]
193 /// This pallet can only live in a runtime that has both `frame_system` and `pallet_author`.
194 #[pallet::config]
195 pub trait Config: frame_system::Config + pallet_author::Config {}
196
197 #[docify::export(tight_usage)]
198 impl<T: Config> Pallet<T> {
199 // anywhere in `pallet-foo`, we can call into `pallet-author` directly, namely because
200 // `T: pallet_author::Config`
201 fn do_stuff_with_author() {
202 let _ = pallet_author::Pallet::<T>::author();
203 }
204 }
205}
206
207#[docify::export]
208/// Abstraction over "something that can provide the block author".
209pub trait AuthorProvider<AccountId> {
210 fn author() -> AccountId;
211}
212
213#[frame::pallet]
214pub mod pallet_foo_loose {
215 use super::*;
216
217 #[pallet::pallet]
218 pub struct Pallet<T>(_);
219
220 #[docify::export(loose_config)]
221 #[pallet::config]
222 pub trait Config: frame_system::Config {
223 /// This pallet relies on the existence of something that implements [`AuthorProvider`],
224 /// which may or may not be `pallet-author`.
225 type AuthorProvider: AuthorProvider<Self::AccountId>;
226 }
227
228 #[docify::export(loose_usage)]
229 impl<T: Config> Pallet<T> {
230 fn do_stuff_with_author() {
231 let _ = T::AuthorProvider::author();
232 }
233 }
234}
235
236#[docify::export(pallet_author_provider)]
237impl<T: pallet_author::Config> AuthorProvider<T::AccountId> for pallet_author::Pallet<T> {
238 fn author() -> T::AccountId {
239 pallet_author::Pallet::<T>::author()
240 }
241}
242
243pub struct OtherAuthorProvider;
244
245#[docify::export(other_author_provider)]
246impl<AccountId> AuthorProvider<AccountId> for OtherAuthorProvider {
247 fn author() -> AccountId {
248 todo!("somehow get the block author here")
249 }
250}
251
252#[docify::export(unit_author_provider)]
253impl<AccountId> AuthorProvider<AccountId> for () {
254 fn author() -> AccountId {
255 todo!("somehow get the block author here")
256 }
257}
258
259pub mod runtime {
260 use super::*;
261 use cumulus_pallet_aura_ext::pallet;
262 use frame::{runtime::prelude::*, testing_prelude::*};
263
264 construct_runtime!(
265 pub struct Runtime {
266 System: frame_system,
267 PalletFoo: pallet_foo_loose,
268 PalletAuthor: pallet_author,
269 }
270 );
271
272 #[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
273 impl frame_system::Config for Runtime {
274 type Block = MockBlock<Self>;
275 }
276
277 impl pallet_author::Config for Runtime {}
278
279 #[docify::export(runtime_author_provider)]
280 impl pallet_foo_loose::Config for Runtime {
281 type AuthorProvider = pallet_author::Pallet<Runtime>;
282 // which is also equivalent to
283 // type AuthorProvider = PalletAuthor;
284 }
285}