Events and errors
A pallet can emit events when it wants to send notification about changes or conditions in the runtime to external entities like users, chain explorers, or dApps.
In custom pallets, you can define:
- what type of events you want to be emitted
- what information is contained within those events
- when those events are emitted
Declaring an event
Events are created using the #[pallet::event]
macro.
For example:
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// Set a value.
ValueSet { value: u32, who: T::AccountId },
}
Then, the RuntimeEvent
type is needed to aggregate them for the runtime.
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
Exposing events to your runtime
Any events you define in your pallet must be exposed to the runtime in the /runtime/src/lib.rs
file.
To expose events to the runtime:
- Open the
/runtime/src/lib.rs
file in a text editor. -
Implement the
RuntimeEvent
type in the configuration trait for your pallet:impl template::Config for Runtime { type RuntimeEvent = RuntimeEvent; }
-
Add the
RuntimeEvent
type to theconstruct_runtime!
macro:construct_runtime!( pub enum Runtime where Block = Block, NodeBlock = opaque::Block, UncheckedExtrinsic = UncheckedExtrinsic { // --snip-- TemplateModule: template::{Pallet, Call, Storage, Event<T>}, //--add-this-------------------------------------> } );
In this example, the event is a generic type and requires the
<T>
parameter. The<T>
parameter isn't needed if your events don't use generic types.
Depositing an event
Substrate provides a default implementation of how to deposit an event using macros. Depositing an event has the following structure:
// 1. Use the `generate_deposit` attribute when declaring the Events enum.
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] // <------ here ----
#[pallet::metadata(...)]
pub enum Event<T: Config> {
// --snip--
}
// 2. Use `deposit_event` inside the dispatchable function
#[pallet::call]
impl<T: Config> Pallet<T> {
#[pallet::weight(1_000)]
pub(super) fn set_value(
origin: OriginFor<T>,
value: u64,
) -> DispatchResultWithPostInfo {
let sender = ensure_signed(origin)?;
// --snip--
Self::deposit_event(RawEvent::ValueSet(value, sender));
}
}
The default behavior of this function is to call deposit_event
from the FRAME system, which writes the event to storage.
This function places the event in the System pallet's runtime storage for that block. At the beginning of a new block, the System pallet automatically removes all events that were stored from the previous block.
Events deposited using the default implementation are directly supported by downstream libraries like the Polkadot-JS API.
However, you can implement your own deposit_event
function if you want to handle events differently.
Supported types
Events can emit any type which supports type encoding using SCALE codec.
In the case where you want to use Runtime generic types like AccountId
or Balances
, you need to
include a where
clause to define
those types as shown in the example above.
Listening to events
The Substrate RPC does not directly expose an endpoint for querying events. If you used the default implementation, you can see the list of events for the current block by querying the storage of the System pallet. Otherwise, the Polkadot-JS API supports a WebSocket subscription on runtime events.
Errors
Runtime code should explicitly and gracefully handle all error cases.
Functions in the runtime code must be non-throwing functions that never cause the compiler to panic.
A common idiom for writing non-throwing Rust code is to write functions that return Result
types.
The Result
enum type possesses an Err
variant that allows a function to indicate that it failed to execute successfully without needing to panic.
Function calls that can be dispatched to the runtime in the FRAME development evironment must return a DispatchResult
type that could be a DispatchError
variant if the function encountered an error.
Each FRAME pallet can define a custom DispatchError
by using the #[pallet::error]
macro.
For example:
#[pallet::error]
pub enum Error<T> {
/// Error names should be descriptive.
InvalidParameter,
/// Errors should have helpful documentation associated with them.
OutOfSpace,
}
The FRAME Support module also includes a helpful ensure!
macro that can be used to check pre-conditions and emit an error if they are not met.
frame_support::ensure!(param < T::MaxVal::get(), Error::<T>::InvalidParameter);