Pallet Events
The last thing we have included in our starting template is a simple event.
When a callable function completes successfully, there is often some metadata you would like to expose to the outside world about what exactly happened during the execution.
Events allow Pallets to express that something has happened, and allows off-chain systems like indexers or block explorers to track certain state transitions.
Event Macro
The #[pallet::event]
macro acts on an enum Event
.
#![allow(unused)] fn main() { #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } }
In this enum, you can introduce new variants as objects with arbitrary fields. Obviously you don't want to stick a ton of data in there, but you should feel comfortable to put the data which is relevant for tools like indexers and block explorers.
In this case, we set you up with a simple event stating a new kitty was created, and by whom. Of course there is no logic which is actually doing that yet, but that is what we will start to work on next.
Emitting an event is usually the last thing you will do in your extrinsic, noting when everything is done and with any final values you might have generated.
We will probably want to update our Created
event with details about the Kitty we created. We can do that in the future.
Macro Magic
You might ask, "What is this generate_deposit
stuff?
When we deposit an event, we actually have to pass our event to frame_system
, which manages events across all pallets.
The code for that function is:
#![allow(unused)] fn main() { impl<T: Config> Pallet<T> { pub(super) fn deposit_event(event: Event<T>) { let event = <<T as Config>::RuntimeEvent as From<Event<T>>>::from(event); let event = <<T as Config>::RuntimeEvent as Into< <T as frame_system::Config>::RuntimeEvent, >>::into(event); <frame_system::Pallet<T>>::deposit_event(event) } } }
Rather than asking the user to remember and write this every time, we are able to automatically generate it for the user.
Do you not like macro magic?
Delete the generate_deposit
line, and copy and paste this code block into your code!
It is literally the same. In this case, I think the macro magic is justified.
You are able to access this function like you could any other function implemented on Pallet
:
#![allow(unused)] fn main() { Self::deposit_event(Event::<T>::Created { owner }); }
As you see in our starting code.
Tests
Don't forget to update your tests.rs
file to include the test provided in this step.
It shows how you can:
- Set the blocknumber of your blockchain inside your tests.
- Call an extrinsic in your pallet from an
AccountId
of your choice. - Check the extrinsic call completed
Ok(())
. - Get the last event deposited into
System
. - Check that last event matches the event you would expect from your pallet.
From this point forward, every step where you write some code will include new tests or modify existing tests.
Make sure to keep updating your tests.rs
file throughout the tutorial.
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; pub use pallet::*; #[frame::pallet(dev_mode)] pub mod pallet { use super::*; #[pallet::pallet] pub struct Pallet<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; } /* 🚧 TODO 🚧: Learn about Pallet Events. */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> {} #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; Self::mint(who)?; Ok(()) } } } }
#![allow(unused)] fn main() { // Tests for the Kitties Pallet. // // Normally this file would be split into two parts: `mock.rs` and `tests.rs`. // The `mock.rs` file would contain all the setup code for our `TestRuntime`. // Then `tests.rs` would only have the tests for our pallet. // However, to minimize the project, these have been combined into this single file. // // Learn more about creating tests for Pallets: // https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html // This flag tells rust to only run this file when running `cargo test`. #![cfg(test)] use crate as pallet_kitties; use crate::*; use frame::deps::sp_io; use frame::runtime::prelude::*; use frame::testing_prelude::*; use frame::traits::fungible::*; type Balance = u64; type Block = frame_system::mocking::MockBlock<TestRuntime>; // In our "test runtime", we represent a user `AccountId` with a `u64`. // This is just a simplification so that we don't need to generate a bunch of proper cryptographic // public keys when writing tests. It is just easier to say "user 1 transfers to user 2". // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; // Our blockchain tests only need 3 Pallets: // 1. System: Which is included with every FRAME runtime. // 2. PalletBalances: Which is manages your blockchain's native currency. (i.e. DOT on Polkadot) // 3. PalletKitties: The pallet you are building in this tutorial! construct_runtime! { pub struct TestRuntime { System: frame_system, PalletBalances: pallet_balances, PalletKitties: pallet_kitties, } } // Normally `System` would have many more configurations, but you can see that we use some macro // magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] impl frame_system::Config for TestRuntime { type Block = Block; type AccountData = pallet_balances::AccountData<Balance>; } // Normally `pallet_balances` would have many more configurations, but you can see that we use some // macro magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] impl pallet_balances::Config for TestRuntime { type AccountStore = System; type Balance = Balance; } // This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` // It simulates the blockchain database backend for our tests. // If you forget to include this and try to access your Pallet storage, you will get an error like: // "`get_version_1` called outside of an Externalities-provided environment." pub fn new_test_ext() -> sp_io::TestExternalities { frame_system::GenesisConfig::<TestRuntime>::default() .build_storage() .unwrap() .into() } #[test] fn starting_template_is_sane() { new_test_ext().execute_with(|| { let event = Event::<TestRuntime>::Created { owner: ALICE }; let _runtime_event: RuntimeEvent = event.into(); let _call = Call::<TestRuntime>::create_kitty {}; let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); assert_ok!(result); }); } #[test] fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { // We often need to set `System` to block 1 so that we can see events. System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); }); } #[test] fn create_kitty_checks_signed() { new_test_ext().execute_with(|| { // The `create_kitty` extrinsic should work when being called by a user. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // The `create_kitty` extrinsic should fail when being called by an unsigned message. assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); }) } #[test] fn create_kitty_emits_event() { new_test_ext().execute_with(|| { // We need to set block number to 1 to view events. System::set_block_number(1); // Execute our call, and ensure it is successful. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); // Assert the last event by our blockchain is the `Created` event with the correct owner. System::assert_last_event(Event::<TestRuntime>::Created { owner: 1 }.into()); }) } }
diff --git a/src/lib.rs b/src/lib.rs
index 405a6e5..a52896e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -13,12 +13,12 @@ pub mod pallet {
#[pallet::pallet]
pub struct Pallet<T>(core::marker::PhantomData<T>);
- /* 🚧 TODO 🚧: Learn about Pallet `Config` and `frame_system`. */
#[pallet::config]
pub trait Config: frame_system::Config {
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
}
+ /* 🚧 TODO 🚧: Learn about Pallet Events. */
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
diff --git a/src/tests.rs b/src/tests.rs
index 08649c3..2526de8 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -105,3 +105,15 @@ fn create_kitty_checks_signed() {
assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin);
})
}
+
+#[test]
+fn create_kitty_emits_event() {
+ new_test_ext().execute_with(|| {
+ // We need to set block number to 1 to view events.
+ System::set_block_number(1);
+ // Execute our call, and ensure it is successful.
+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE)));
+ // Assert the last event by our blockchain is the `Created` event with the correct owner.
+ System::assert_last_event(Event::<TestRuntime>::Created { owner: 1 }.into());
+ })
+}