FRAME macros
Substrate uses customized Rust macros to generate code and aggregate the logic from the pallets you implement for a runtime. These runtime macros allow you to focus on your runtime logic rather than spending time on encoding and decoding on-chain variables or duplicating the code required for basic blockchain development.
This section provides an overview of the types of macros available in Rust and highlights how the specific FRAME macros are used in runtime development.
Macro basics
In computer programming, macros are lines of code that encapsulate a preset sequence of instructions to execute. As code that writes code, macros enable you to abstract repetitive operations and simplify the code you need to write. With macros, you can declare complex data structures implicitly.
In Rust, macros can be declarative macros or procedural macros.
Declarative macros enable you to declare a pattern and compare the result of an expression to the pattern, then execute any additional lines of code based on whether the pattern was matched. Declarative macros are widely-used in Rust programming.
Procedural macros are similar to functions. Unlike the pattern-matching done in declarative macros, procedural macros take code as input, perform some set of instructions on the input, and produce code as output. There are three types of procedural macros:
- Custom derive macros enable you to define and reuse the implementation of a trait for a given type.
The
derive
macro is particularly useful for defining the implementation for custom runtime types that must satisfy specific traits. - Attribute-like macros enable you to create new attributes to generate code.
- Function-like macros enable you to define macros that operate like function calls to generate code.
Because macros are expanded before the compiler interprets the lines of code they contain, they can define complex data structures and operations. FRAME macros take advantage of the different types of macros to provide shortcut abstractions to what is often complex blocks of code. However, the abstraction that macros provide can make the runtime code somewhat difficult to follow.
To learn more about the FRAME macros used in the Substrate runtime, you can install and use cargo-expand.
After you install cargo-expand, you can use the cargo expand
command to display the results of the code contained in the macros the runtime uses.
FRAME support and system macros
Substrate primitives and FRAME both rely on a collection of different macros. This section provides an overview of the macros provided in the FRAME support and FRAME system libraries. In most cases, these macros provide the framework that other pallets depend on and you should be familiar with how and where they are used in the runtime logic. After the overview, this section describes the specific macros that you are most likely to use as a runtime developer.
FRAME support macros
The frame_support
crate provides many of the most important declarative, derive, attribute-like, and function-like macros used in the runtime.
A few of the important macros that you should be familiar with from the frame_support
crate include the following:
construct_runtime
used to construct runtime from the list of pallets you have implemented.match_types
used to create a type that implements theContains
trait with syntax similar tomatches!
.parameter_types
used to create new implementations of theGet
trait.
For additional information about the macros in the frame_support
crate, see the Rust documentation for Macros, Derive macros, and Attribute macros.
FRAME system macros
The frame_system
crate uses macros to define primitives that provide access to core data types and shared utilities.
These primitives and associated macros form the foundation for many node operations both in the outer node and in the runtime and act as the base layer for other pallets to interact with the Substrate framework.
A few of the important primitives and macros that you should be familiar with from the frame_system
crate include the following:
-
map
used to initialize a key-value collection from array.RuntimeDebug
used to debug the runtime.
For more information about
sp_core
function-like macros, see Macros. -
bounded_btree_map
used to build a boundedbtree-map
from given literals.bounded_vec
used to build a boundedvec
from given literals.impl_opaque_keys
used to implementOpaqueKeys
for a described data structure.parameter_types
used to create new implementations of theGet
trait.
For more information about
sp_runtime
function-like macros, see Macros. For information aboutsp_runtime
derive macros, see Derive macros. -
decl_runtime_apis
used to declare specified traits as runtime APIs.impl_runtime_apis
used to tag specified trait implementations as runtime APIs.
For more information about
sp_api
function-like macros, see Macros. -
if_std
used to indicate code that should only be run when thestd
feature set is enabled.map
used to initialize a key-value collection from array.vec
used to create a vector containing the arguments.
For more information about
sp_std
function-like macros, see Macros. -
create_apis_vec
used to create a vector of API declarations.create_runtime_str
used to create aRuntimeString
constant.runtime_version
used as an attribute that accepts the version declaration of a runtime and generates a custom WebAssembly section with the equivalent contents.
You'll see these many of these crates listed as dependencies in the runtime and node Cargo.toml
file for the node template.
Macros for composing pallets
As discussed in Building custom pallets, most FRAME pallets are composed using a common set of sections.
Macros make building each of those sections more modular and extensible. This section describes the macros available and how to use them to build your custom runtime.
#[pallet]
The #[pallet]
macro is required to declare a pallet.
This attribute macro is an attribute of the pallet module (mod pallet
).
Within the pallet
module, the #[pallet]
macro serves as an entry point for additional #[pallet::*]
macros that describe the attributes used to identify the specific items the pallet requires.
For example, a pallet typically includes a set of types, functions, and trait implementations that are aggregated by the construct_runtime!
macro to build the runtime.
#[pallet]
pub mod pallet {
...
}
Development mode
You can specify dev_mode
as an argument on the #[pallet]
or #[frame_support::pallet]
attribute macro to enable development mode for a pallet.
For example, replace #[pallet]
with #[pallet(dev_mode)]
or #[frame_support::pallet]
with #[frame_support::pallet(dev_mode)]
to enable development mode for the pallet you're working on.
Development mode loosens some of the restrictions and requirements placed on production pallets to make it easier to iterate on your code during development and testing cycles. For example, if you enable development mode for a pallet:
- You don't need to specify a weight on every
#[pallet::call]
declaration. By default, development mode assigns a weight of zero (0
) to calls that don't have a weight explicitly specified. - You don't need to implement
MaxEncodedLen
on storage types. By default, development mode marks all storage items as unbounded.
Note that you can only add the dev_mode
argument to the #[pallet]
or #[frame_support::pallet]
attribute macro that encloses your pallet module.
You can't specify this argument for any of the #[pallet::*]
attribute macros.
You should never deploy pallets with development mode enabled in a production network. Before deploying a pallet in a production runtime, be sure to remove the dev_mode
argument from the #[pallet]
declaration, fix any compiler errors, and complete testing with the development mode disabled.
Using the pallet module
Inside the module, the macro parses items with the attribute #[pallet::*]
.
Some #[pallet::*]
attributes are mandatory and some are optional.
You can import system-level types from the frame_support
and frame_system
crates automatically by using the pallet_prelude
from those crates.
For example:
#[pallet]
pub mod pallet {
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;
...
}
The #[pallet]
macro is similar to a derive macro in that it expands the pallet types and trait implementations by reading the input.
In most cases, the macro doesn't modify any input.
However, there are a few specific scenarios where—unlike a derive macro—this macro modifies its input.
The macro will modify the input in the following circumstances:
-
If a generic is replaced with a type
For example, this can occur if the inner type of an item in
pub struct Pallet<..>(_)
is replaced in thepallet::storage
macro with a type that implements theStorageInstance
trait. -
If a function or data structure is changed
For example, this can occur if the
pallet::type_value
macro changes a function item into a struct and trait implementation. -
If docs are not provided by the user
For example, if no documentation is provided, the macro
pallet::pallet
modifies the input to add documentation above thestruct Pallet<T>(_);
item.
#[pallet::config]
The #[pallet::config]
macro is required to define the generic data types that the pallet uses.
This macro provides the constants that are part of the system-level Config
trait for the pallet.
The Config trait for this macro must be defined as a regular trait definition named Config
that includes the system-level frame_system::Config
trait.
The definition can include other top-level traits and a where clause.
For example:
#[pallet::config]
pub trait Config: frame_system::Config + $optionally_some_other_supertraits
$optional_where_clause
{
...
}
To bypass the frame_system::Config
requirement, you can use the attribute #[pallet::disable_frame_system_supertrait_check]
.
For example:
#[pallet::config]
#[pallet::disable_frame_system_supertrait_check]
pub trait Config: pallet_timestamp::Config {}
#[pallet::constant]
The #[pallet::constant]
macro provides the Config
trait—inside the #[pallet::config]
macro—with the types and attributes it needs for the runtime and generates associated metadata.
This macro adds information about the constants used in a pallet to the runtime metadata, including:
- the constant name
- the name of the associated types
- the constant value
- the value returned by
Get::get()
for the constant
For example, you can use #[pallet::constant]
to add type MyGetParam
to the metadata:
#[pallet::config]
pub trait Config: frame_system::Config {
#[pallet::constant] // puts attributes in metadata
type MyGetParam: Get<u32>;
}
#[pallet::extra_constants]
The #[pallet::extra_constants]
macro enables you to add constants to the metadata.
For example, you can declare a function that returns a generated value.
You can then use the #[pallet::extra_constants]
macro to add the information for the generated value to the metadata:
#[pallet::extra_constants]
impl<T: Config> Pallet<T> {
//Example function using extra_constants
fn example_extra_constants() -> u128 { 4u128 }
}
#[pallet::pallet]
The #[pallet::pallet]
macro is required to declare the pallet data structure placeholder to be used by construct_runtime!
macro.
This macro must be defined as a struct named Pallet with a generic type
For example:
#[pallet::pallet]
pub struct Pallet<T>(_);
This macro can generate the Store
trait to contain an associated type for each storage item if you provide the #[pallet::generate_store($vis trait Store)]
attribute macro.
For example:
#[pallet::pallet]
pub struct Pallet<T>(_);
For more information about working with storage and this macro, see the macro expansion added to the struct Pallet<T>
definition.
#[pallet::without_storage_info]
The #[pallet::without_storage_info]
macro enables you to define pallet storage items that don't have a fixed size.
By default, all pallet storage items are required to implement traits::StorageInfoTrait
, so that all key and value types have a fixed size based on the bound defined in the pallet_prelude::MaxEncodedLen
attribute.
This size limitation is required for parachain development to estimate the size of the Proof of Validity (PoV) blob.
The #[pallet::without_storage_info]
attribute macro allows you to override the default behavior if you require unbounded storage for an entire pallet.
To use it, add the #[pallet::without_storage_info]
attribute to the pallet struct like so:
#[pallet::pallet]
#[pallet::generate_store(pub(super) trait Store)]
#[pallet::without_storage_info]
pub struct Pallet<T>(_);
Note that you should only use the #[pallet::without_storage_info]
macro if you need to make all of the storage items in your pallet unbounded.
If you only need undefined storage for a specific storage item, you can use the #[pallet::unbounded]
attribute macro to override the fixed size constraint.
Because the #[pallet::without_storage_info]
macro applies to all storage items in your pallet, you should only use it in a test or development environment.
You should never use the #[pallet::without_storage_info]
attribute macro in a production environment.
For more information about working with storage and this macro, see the macro expansion added to the struct Pallet<T>
definition.
#[pallet::unbounded]
The #[pallet::unbounded]
attribute macro enables you to declare a specific storage item as unbounded.
By default, all pallet storage items are required to have a fixed size.
You can use this attribute macro to override the default requirement on a specific storage item.
If you are a parachain developer, you can use this macro for storage items that will never go into the Proof of Validity (PoV) blob.
#[pallet::hooks]
The #[pallet::hooks]
macro allows you to declare optional pallet hooks to implement pallet-specific logic at specific points in the block making process.
Within the #[pallet::hooks]
macro, you can implement the Hooks
trait to execute logic when a block is being initialized or finalized, before a runtime is upgraded, or after a runtime upgrade has been completed.
For example:
#[pallet::hooks]
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
// Hooks functions and logic goes here.
}
For more information about using hooks, see the Rust documentation for pallet::hooks and macro expansion.
#[pallet::call]
The #[pallet::call]
is required to implement the functions that can be dispatched to the runtime for a pallet. Each function must:
- define a weight with the
#[pallet::weight($expr)]
attribute - have its first argument as
origin: OriginFor<T>
- use compact encoding for arguments using #[pallet::compact]
- return
DispatchResultWithPostInfo
orDispatchResult
Extrinsic requests coming into the runtime can use calls to trigger specific logic.
Calls can also be used in on-chain governance, demonstrated by the democracy pallet where calls can be voted on.
The #[pallet::call]
aggregates all of the function call logic using the Call
enum.
The aggregation enables FRAME to batch functions of the same type into a single runtime call.
The runtime then generates the associated items from the implementation defined in the impl
code blocks.
For more information. see the Rust documentation for pallet::call.
#[pallet::error]
The #[pallet::error]
macro allows you to define the error types that can be returned from the function calls dispatched to the runtime.
The error information is included in the runtime metadata.
The macro must be defined as an enumeration named Error with a generic type
For example:
#[pallet::error]
pub enum Error<T> {
/// $some_optional_doc
$SomeFieldLessVariant,
/// $some_more_optional_doc
$SomeVariantWithOneField(FieldType),
...
}
Any field type you specify for an enumeration variant must implement the scale_info::TypeInfo
trait and its encoded size should be as small as possible.
Field types in enum variants must also implement the PalletError trait to compile.
For more information, see the Rust documentation for pallet::error.
#[pallet::event]
The #[pallet::event]
macro allows you to define event types for a pallet.
This macro is similar to the pallet::error
macro but it can hold more information.
The macro is defined as an enumeration named Event.
For example:
#[pallet::event]
#[pallet::generate_deposit($visibility fn deposit_event)] // Optional
pub enum Event<$some_generic> $optional_where_clause {
/// Some doc
$SomeName($SomeType, $YetanotherType, ...),
...
}
For more information, see the Rust documentation for pallet::event.
#[pallet::storage]
The #[pallet::storage]
macro enables you to define abstract storage inside runtime storage and to set metadata for that storage.
This attribute macro can be used multiple times.
The [pallet::storage]
macro can be defined using named or unnamed generics with a type alias of StorageValue, StorageMap or StorageDoubleMap.
#[pallet::storage]
#[pallet::getter(fn $getter_name)] // optional
$vis type $StorageName<$some_generic> $optional_where_clause
= $StorageType<$generic_name = $some_generics, $other_name = $some_other, ...>;
For more information, see the Rust documentation for pallet::storage and the following storage data structures:
#[pallet::type_value]
The #[pallet::type_value]
macro enables you to define a struct that implements a Get
trait for storage types. This attribute macro can be used multiple times in combination with the #[pallet::storage] macro to define default values in storage.
#[pallet::type_value]
fn MyDefault<T: Config>() -> T::Balance { 3.into() }
For more information about using this macro, see the Rust documentation for pallet::type_value.
#[pallet::genesis_build]
The #[pallet::genesis_build]
macro allows you to define how a genesis configuration is built.
The macro is defined as a Rust trait implementation with a generic type <T: Config> of trait GenesisBuild
For example:
#[pallet::genesis_build]
impl<T: Config> GenesisBuild<T> for GenesisConfig {
fn build(&self) {}
}
For more information, see the Rust documentation for pallet::genesis_build.
#[pallet::genesis_config]
The #[pallet::genesis_config]
macro allows you to define the genesis configuration of the pallet.
The macro can be defined as an enumeration or a struct, but must be public and implement trait the GenesisBuild with the #[pallet::genesis_build] macro.
For example:
#[pallet::genesis_config]
pub struct GenesisConfig<T: Config> {
_myfield: BalanceOf<T>,
}
For more information, see the Rust documentation for pallet::genesis_config.
#[pallet::inherent]
The #[pallet::inherent]
macro allows the pallet to provide data in an unsigned inherent transaction.
The macro is defined as a trait implementation with bound <T: Config,> of trait ProvideInherent for type Pallet
For example:
#[pallet::inherent]
impl<T: Config> ProvideInherent for Pallet<T> {
// ... regular trait implementation
}
For more information, see the Rust documentation for pallet::inherent.
#[pallet::origin]
The #[pallet::origin]
macro allows you to define an origin for the pallet.
The macro must be defined as a type alias, enumeration, or struct. The macro must be public.
For example:
#[pallet::origin]
pub struct Origin<T>(PhantomData<(T)>);
For more information, see the Rust documentation for pallet::origin.
#[pallet::validate_unsigned]
The #[pallet::validate_unsigned]
macro allows the pallet to validate unsigned transactions.
The macro is defined as a trait implementation with bound <T: Config> of trait ValidateUnsigned for type Pallet
For example:
#[pallet::validate_unsigned]
impl<T: Config> ValidateUnsigned for Pallet<T> {
// ... regular trait implementation
}
For more information, see the Rust documentation for pallet::validate_unsigned.
Runtime construction macros
As an introduction to the use of macros, Substrate runtime macros highlighted several of the macros that are provided in the frame_support
and frame_system
crates.
However, a few of those macros introduced in Substrate runtime macros warrant additional attention because of the role they play in constructing the runtime logic.
construct_runtime!
The construct_runtime!
macro generates all of the data structures that have been declared and implemented for the pallets to be included in the runtime.
For example, the construct_runtime!
macro extrapolates information from the pallet attribute macros to generate details such as:
- Runtime struct to represent the Substrate runtime.
- Call enumeration variants and metadata for each pallet included in the runtime that has callable functions.
- Event enumeration variants for each pallet included in the runtime that emits events.
- Genesis configuration struct for building storage for each pallet included in the runtime that defines storage items.
- Inherent data for pallets that support inherent transactions.
- Origin enumeration variants for pallets that identify the function caller using the default origin definition or pallet-specific custom origins.
- Validation for unsigned transactions for pallets that support unsigned transactions.
The construct_runtime!
macro also implements helper traits for the data structures and types exposed, generates an index of the pallets listed in the runtime, and maps events, errors, and callable functions from the runtime back to the pallet they originate from.
By default, the construct_runtime!
macro includes all pallet attributes for all pallets listed in the macro definition.
You can override the default behavior to exclude specific parts or to only include specific parts.
For example, if you don't want the runtime to expose the function calls defined in specific pallet, you can explicitly exclude them in the construct_runtime!
macro with an entry similar to the following:
MyCustomPallet: pallet_my_custom_pallet exclude_parts { Call }
In this example, MyCustomPallet
is the name that uniquely identifies the pallet that requires special handling and pallet_my_custom_pallet
represents the path to the pallet.
The exclude_parts
keyword specifies that you only want to exclude certain attributes for the specified pallet.
In this case, only callable functions for MyCustomPallet are excluded.
Similarly, you can override the default to explicitly include certain parts in the runtime with the use_parts
keyword.
For example, if you only want the runtime to expose a subset of attributes for a specific pallet, you can explicitly include them in the construct_runtime!
macro with an entry similar to the following:
MyCustomPallet: pallet_my_custom_pallet use_parts { Pallet, Call, Storage, RuntimeEvent, RuntimeOrigin, Config }
You should note the the order in which pallets are listed in the construct_runtime!
macro is significant.
By default, the pallet index starts at zero for the first pallet and is incremented for each pallet thereafter.
You can manually adjust the indexing in the construct_runtime!
macro by adding an index number for a pallet.
For example, you can generate all pallet attributes for the MyCustomPallet
and set the index to eight with syntax like this:
MyCustomPallet: pallet_my_custom_pallet use_parts = 8,
However, you should also note that the order used for defining pallets in the construct_runtime! macro affects the genesis storage configuration. If you have one pallet that depends on another pallet, be sure the pallet that is depended upon comes before—that is, is listed before or has a lower index value—than the pallet that depends on it.
For more information, see the Rust documentation for construct_runtime
parameter_types!
The parameter_types!
macro declares the parameter types that are to be assigned to the configuration trait for each pallet during runtime construction.
This macro converts each parameter specified into a struct type with a get()
function that returns the specified type.
Each parameter struct type also implements a frame_support::traits::Get<I>
trait to convert the type to its specified value.
For more information, see the Rust documentation for parameter_types.
implruntimeapis!
The impl_runtime_apis!
macro generates the runtime API for all of the traits that are implemented by the macro.
The traits implemented in this macro must first be declared in the decl_runtime_apis
macro.
The macro generates the RuntimeApi
and RuntimeApiImpl
structs to expose these traits as runtime APIs.
The traits exposed by the macro enable outer node components to communicate with the runtime through the RuntimeApi
type.
The macro declares the RuntimeApi
and RuntimeApiImpl
structs and implements various helper traits for the RuntimeApiImpl
struct.
If you define additional interfaces for the runtime to expose in the impl_runtime_apis!
macro, they are appended to the default RuntimeApiImpl
implementation.
The macro also generates the RUNTIME_API_VERSIONS
constant to expose version information about all of the implemented api
traits.
This constant is used to instantiate the apis
field of RuntimeVersion
.
For more information, see the Rust documentation for implruntimeapis.
app_crypto!
The app_crypto!
macro generates application-specific cryptographic key pairs using the specified signature algorithm.
The macro declares the following struct types:
-
Public
For the
Public
type, the macro implements thesp_application_crypto::AppKey
trait to define the public key type and thesp_application_crypto::RuntimeAppPublic
trait enable generating key pairs, signing transactions, and verifying signatures. -
Signature
For the
Signature
type, the macro implements thecore::hash::Hash
trait to specify the signature algorithm—for example, SR25519 or ED25519—used to hash the signature. -
Pair
For the
Pair
type, the macro implements thesp_application_crypto::Pair
andsp_application_crypto::AppKey
traits to generate public-private key pairs from a secret phrase or seed.
In addition to the traits for these structs, the macro implements helper traits.
For more information, see the Rust documentation for app_crypto.
Benchmarking macros
The FRAME benchmarking framework defines several macros for benchmarking pallets. The following macros are used for benchmarking:
add_benchmark
to add pallet benchmarks to aVec<BenchmarkBatch>
object using the pallet crate name and generated module struct.benchmarks
to construct the benchmark logic for testing the execution time for function calls.benchmarks_instance
to provide the same functionality as thebenchmarks
macro for instantiable modules.benchmarks_instance_pallet
to provide the same functionality as thebenchmarks
macro for instantiable pallets that are declared with the [frame_support::pallet
] macro.cb_add_benchmarks
to calladd_benchmark
as a callback for thedefine_benchmarks
macro.cb_list_benchmarks
to calllist_benchmark
as a callback for thedefine_benchmarks
macro.define_benchmarks
to define all of the benchmarked pallets for the runtime.impl_benchmark_test_suite
to create a test suite that runs the benchmarks defined in the benchmarking module.list_benchmark
to generate a list of benchmarks for the pallets configured in the runtime.whitelist
to add accounts to an allow list for testing purposes.