Substrate Module Struct
July 30, 2019
Blockchain
Substrate
The Module
struct is the backbone of each Substrate runtime module. It wraps a set of useful functions and implementations which is either written by runtime developer or generated by the macros with Substrate.
This page will explore the various components of the Module
struct to help gain a better understanding of how it all works together.
Components of the Module Struct
Let's recall how we declare a raw Module
struct within decl_module!
macro:
decl_module! {
pub struct Module<T: Trait> for enum Call where origin: T::Origin {
// --snip--
}
}
The final generated definition of the Module
struct in standard Rust looks like:
pub struct Module<T: Trait>();
If you are interested in the fully expanded code of your module, try using cargo-expand by following its instructions.
On top of this struct, we implement a wealth of functions and traits for our module like:
- Callable Functions
- Runtime Storage
- OnInitialize / OnFinalize
- OffchainWorker
- etc…
In addition, the Module
struct is used to implement all of the various module internal functions like deposit_event
.
Module Struct Implementations
Callable Functions
Runtime developers write callable functions that maintain the logic to manipulate the blockchain state. Here is an example that shows one callable function named do_something
:
impl<T: Trait> Module<T> {
pub fn do_something(origin: T::Origin, something: u32) -> Result {
// --snip--
}
}
To make sure the functions can be called via an extrinsic, the Module
struct also implements the Callable
trait by connecting to the module's Call
enum. All the dispatchable functions will be exposed through this Call
enum, which you can learn more about here.
impl<T: Trait> Callable for Module<T> {
type Call = Call<T>;
}
Runtime Storage
The Store
trait lists all of the runtime storage items exposed by the module. Each storage item is given a struct onto which all of its storage APIs are defined. You can learn more about individual storage item here.
impl<T: Trait> Store for Module<T> {
type Something = Something<T>;
}
OnInitialize / OnFinalize
Module
struct implements the OnInitialize
and OnFinalize
traits that contain the functions that should be run at the beginning or end of block execution. You can learn more about those functions here.
Module Internal Functions
The Module
struct is also used to implement all the various module internal functions we define. This is why, when writing internal module functions, you write code like:
impl<T: Trait> Module<T> {
// --snip--
}
We then gain access to these functions throughout our module with Self::function_name()
.
For example, the default definition of deposit_event
expands to look like:
impl<T: Trait> Module<T> {
fn deposit_event(event: Event<T>) {
<system::Module<T>>::deposit_event(<T as Trait>::from(event).into());
}
}
and can be accessed with:
Self::deposit_event(...)
All Modules Tuple
The Module
struct is finally imported into your overall blockchain's runtime using the construct_runtime!
macro. This macro includes your module along with all the other modules into a tuple called AllModules
:
type AllModules = (
timestamp::Module<Runtime>,
balances::Module<Runtime>,
template::Module<Runtime>,
// --snip--
);
This tuple is used by the runtime's Executive module which handles orchestration of executing these modules.
Module Metadata
Just like the Module
struct, the metadata for your module contains all the various components of your module such as its storage items, callable functions, and events. When represented as JSON, the metadata for a module will look like:
{
"modules":[
{
"name":"template",
"prefix":"TemplateModule",
"storage":[...],
"calls":[...],
"events":[...]
}
]
}