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:

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":[...]
        }
    ]
}
comments powered by Disqus