## Module your_first_pallet

Source
Expand description

Write your first simple pezpallet, learning the most most basic features of FRAME along the way.

## §Currency Pezpallet

By the end of this guide, you will have written a small FRAME pezpallet (see `crate::pezkuwi_sdk::frame_runtime`) that is capable of handling a simple crypto-currency. This pezpallet will:

1. Allow anyone to mint new tokens into accounts (which is obviously not a great idea for a real system).
2. Allow any user that owns tokens to transfer them to others.
3. Track the total issuance of all tokens at all times.

> This guide will build a currency pezpallet from scratch using only the lowest primitives of FRAME, and is mainly intended for education, not applicability. For example, almost all FRAME-based runtimes use various techniques to re-use a currency pezpallet instead of writing one. Further advanced FRAME related topics are discussed in `crate::reference_docs`.

### §Writing Your First Pezpallet

To get started, clone one of the templates mentioned in `crate::pezkuwi_sdk::templates`. We recommend using the `pezkuwi-sdk-minimal-template`. You might need to change small parts of this guide, namely the crate/package names, based on which template you use.

> Be aware that you can read the entire source code backing this tutorial by clicking on the `source` button at the top right of the page.

You should have studied the following modules as a prelude to this guide:

- `crate::reference_docs::blockchain_state_machines`
- `crate::reference_docs::trait_based_programming`
- `crate::pezkuwi_sdk::frame_runtime`

### §Topics Covered

The following FRAME topics are covered in this guide:

- `pezpallet::storage`
- `pezpallet::call`
- `pezpallet::event`
- `pezpallet::error`
- Basics of testing a pezpallet
- Constructing a runtime

#### §Shell Pezpallet

Consider the following as a “shell pezpallet”. We continue building the rest of this pezpallet based on this template.

`pezpallet::config` and `pezpallet::pezpallet` are both mandatory parts of any pezpallet. Refer to the documentation of each to get an overview of what they do.

ⓘ

    #[pezframe::pezpallet(dev_mode)]
    pub mod shell_pallet {
    	use pezframe::prelude::*;

    	#[pezpallet::config]
    	pub trait Config: pezframe_system::Config {}

    	#[pezpallet::pezpallet]
    	pub struct Pezpallet<T>(_);
    }

All of the code that follows in this guide should live inside of the `mod pezpallet`.

#### §Storage

First, we will need to create two onchain storage declarations.

One should be a mapping from account-ids to a balance type, and one value that is the total issuance.

> For the rest of this guide, we will opt for a balance type of `u128`. For the sake of simplicity, we are hardcoding this type. In a real pezpallet is best practice to define it as a generic bounded type in the `Config` trait, and then specify it in the implementation.

ⓘ

    pub type Balance = u128;

The definition of these two storage items, based on `pezpallet::storage` details, is as follows:

ⓘ

    #[pezpallet::storage]
    pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;

ⓘ

    #[pezpallet::storage]
    pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;

#### §Dispatchables

Next, we will define the dispatchable functions. As per `pezpallet::call`, these will be defined as normal `fn`s attached to `struct Pezpallet`.

ⓘ

    #[pezpallet::call]
    impl<T: Config> Pezpallet<T> {
    	/// An unsafe mint that can be called by anyone. Not a great idea.
    	pub fn mint_unsafe(
    		origin: T::RuntimeOrigin,
    		dest: T::AccountId,
    		amount: Balance,
    	) -> DispatchResult {
    		// ensure that this is a signed account, but we don't really check `_anyone`.
    		let _anyone = ensure_signed(origin)?;

    		// update the balances map. Notice how all `<T: Config>` remains as `<T>`.
    		Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
    		// update total issuance.
    		TotalIssuance::<T>::mutate(|t| *t = Some(t.unwrap_or(0) + amount));

    		Ok(())
    	}

    	/// Transfer `amount` from `origin` to `dest`.
    	pub fn transfer(
    		origin: T::RuntimeOrigin,
    		dest: T::AccountId,
    		amount: Balance,
    	) -> DispatchResult {
    		let sender = ensure_signed(origin)?;

    		// ensure sender has enough balance, and if so, calculate what is left after `amount`.
    		let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    		if sender_balance < amount {
    			return Err("InsufficientBalance".into());
    		}
    		let remainder = sender_balance - amount;

    		// update sender and dest balances.
    		Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
    		Balances::<T>::insert(&sender, remainder);

    		Ok(())
    	}
    }

The logic of these functions is self-explanatory. Instead, we will focus on the FRAME-related details:

- Where do `T::AccountId` and `T::RuntimeOrigin` come from? These are both defined in [`pezframe::prelude::pezframe_system::Config`], therefore we can access them in `T`.
- What is `ensure_signed`, and what does it do with the aforementioned `T::RuntimeOrigin`? This is outside the scope of this guide, and you can learn more about it in the origin reference document (`crate::reference_docs::frame_origin`). For now, you should only know the signature of the function: it takes a generic `T::RuntimeOrigin` and returns a `Result<T::AccountId, _>`. So by the end of this function call, we know that this dispatchable was signed by `sender`.

ⓘ

    pub fn ensure_signed<OuterOrigin, AccountId>(o: OuterOrigin) -> Result<AccountId, BadOrigin>
    where
    	OuterOrigin: Into<Result<RawOrigin<AccountId>, OuterOrigin>>,
    {
    	match o.into() {
    		Ok(RawOrigin::Signed(t)) => Ok(t),
    		_ => Err(BadOrigin),
    	}
    }

- Where does `mutate`, `get` and `insert` and other storage APIs come from? All of them are explained in the corresponding `type`, for example, for `Balances::<T>::insert`, you can look into [`pezframe::prelude::StorageMap::insert`].

- The return type of all dispatchable functions is [`pezframe::prelude::DispatchResult`]:

ⓘ

    pub type DispatchResult = Result<(), pezsp_runtime::DispatchError>;

Which is more or less a normal Rust `Result`, with a custom [`pezframe::prelude::DispatchError`] as the `Err` variant. We won’t cover this error in detail here, but importantly you should know that there is an `impl From<&'static string> for DispatchError` provided (see here). Therefore, we can use basic string literals as our error type and `.into()` them into `DispatchError`.

- Why are all `get` and `mutate` functions returning an `Option`? This is the default behavior of FRAME storage APIs. You can learn more about how to override this by looking into `pezpallet::storage`, and [`pezframe::prelude::ValueQuery`]/[`pezframe::prelude::OptionQuery`]

#### §Improving Errors

How we handle error in the above snippets is fairly rudimentary. Let’s look at how this can be improved. First, we can use [`pezframe::prelude::ensure`] to express the error slightly better. This macro will call `.into()` under the hood.

ⓘ

    pub fn transfer_better(
    	origin: T::RuntimeOrigin,
    	dest: T::AccountId,
    	amount: Balance,
    ) -> DispatchResult {
    	let sender = ensure_signed(origin)?;

    	let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    	ensure!(sender_balance >= amount, "InsufficientBalance");
    	let remainder = sender_balance - amount;

    	// .. snip
    	Ok(())
    }

Moreover, you will learn in the Defensive Programming section that it is always recommended to use safe arithmetic operations in your runtime. By using `pezframe::traits::CheckedSub`, we can not only take a step in that direction, but also improve the error handing and make it slightly more ergonomic.

ⓘ

    pub fn transfer_better_checked(
    	origin: T::RuntimeOrigin,
    	dest: T::AccountId,
    	amount: Balance,
    ) -> DispatchResult {
    	let sender = ensure_signed(origin)?;

    	let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    	let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?;

    	// .. snip
    	Ok(())
    }

This is more or less all the logic that there is in this basic currency pezpallet!

#### §Your First (Test) Runtime

The typical testing code of a pezpallet lives in a module that imports some preludes useful for testing, similar to:

    pub mod pezpallet {
    	// snip -- actually pezpallet code.
    }

    #[cfg(test)]
    mod tests {
    	// bring in the testing prelude of frame
    	use pezframe::testing_prelude::*;
    	// bring in all pezpallet items
    	use super::pezpallet::*;

    	// snip -- rest of the testing code.
    }

Next, we create a “test runtime” in order to test our pezpallet. Recall from `crate::pezkuwi_sdk::frame_runtime` that a runtime is a collection of pallets, expressed through [`pezframe::runtime::prelude::construct_runtime`]. All runtimes also have to include [`pezframe::prelude::pezframe_system`]. So we expect to see a runtime with two pezpallet, `pezframe_system` and the one we just wrote.

ⓘ

    mod runtime {
    	use super::*;
    	// we need to reference our `mod pezpallet` as an identifier to pass to
    	// `construct_runtime`.
    	// YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE
    	use crate::pezpallet as pezpallet_currency;

    	construct_runtime!(
    		pub enum Runtime {
    			// ---^^^^^^ This is where `enum Runtime` is defined.
    			System: pezframe_system,
    			Currency: pezpallet_currency,
    		}
    	);

    	#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
    	impl pezframe_system::Config for Runtime {
    		type Block = MockBlock<Runtime>;
    		// within pezpallet we just said `<T as pezframe_system::Config>::AccountId`, now we
    		// finally specified it.
    		type AccountId = u64;
    	}

    	// our simple pezpallet has nothing to be configured.
    	impl pezpallet_currency::Config for Runtime {}
    }

> [`pezframe::pezpallet_macros::derive_impl`] is a FRAME feature that enables developers to have defaults for associated types.

Recall that within our pezpallet, (almost) all blocks of code are generic over `<T: Config>`. And, because `trait Config: pezframe_system::Config`, we can get access to all items in `Config` (or `pezframe_system::Config`) using `T::NameOfItem`. This is all within the boundaries of how Rust traits and generics work. If unfamiliar with this pattern, read `crate::reference_docs::trait_based_programming` before going further.

Crucially, a typical FRAME runtime contains a `struct Runtime`. The main role of this `struct` is to implement the `trait Config` of all pallets. That is, anywhere within your pezpallet code where you see `<T: Config>` (read: “some type `T` that implements `Config`”), in the runtime, it can be replaced with `<Runtime>`, because `Runtime` implements `Config` of all pallets, as we see above.

Another way to think about this is that within a pezpallet, a lot of types are “unknown” and, we only know that they will be provided at some later point. For example, when you write `T::AccountId` (which is short for `<T as pezframe_system::Config>::AccountId`) in your pezpallet, you are in fact saying “Some type `AccountId` that will be known later”. That “later” is in fact when you specify these types when you implement all `Config` traits for `Runtime`.

As you see above, `pezframe_system::Config` is setting the `AccountId` to `u64`. Of course, a real runtime will not use this type, and instead reside to a proper type like a 32-byte standard public key. This is a HUGE benefit that FRAME developers can tap into: through the framework being so generic, different types can always be customized to simple things when needed.

> Imagine how hard it would have been if all tests had to use a real 32-byte account id, as opposed to just a u64 number 🙈.

#### §Your First Test

The above is all you need to execute the dispatchables of your pezpallet. The last thing you need to learn is that all of your pezpallet testing code should be wrapped in [`pezframe::testing_prelude::TestState`]. This is a type that provides access to an in-memory state to be used in our tests.

ⓘ

    #[test]
    fn first_test() {
    	TestState::new_empty().execute_with(|| {
    		// We expect Alice's account to have no funds.
    		assert_eq!(Balances::<Runtime>::get(&ALICE), None);
    		assert_eq!(TotalIssuance::<Runtime>::get(), None);

    		// mint some funds into Alice's account.
    		assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    			RuntimeOrigin::signed(ALICE),
    			ALICE,
    			100
    		));

    		// re-check the above
    		assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
    	})
    }

In the first test, we simply assert that there is no total issuance, and no balance associated with Alice’s account. Then, we mint some balance into Alice’s, and re-check.

As noted above, the `T::AccountId` is now `u64`. Moreover, `Runtime` is replacing `<T: Config>`. This is why for example you see `Balances::<Runtime>::get(..)`. Finally, notice that the dispatchables are simply functions that can be called on top of the `Pezpallet` struct.

Congratulations! You have written your first pezpallet and tested it! Next, we learn a few optional steps to improve our pezpallet.

### §Improving the Currency Pezpallet

#### §Better Test Setup

Idiomatic FRAME pallets often use Builder pattern to define their initial state.

> The Pezkuwi Blockchain Academy’s Rust entrance exam has a section on this that you can use to learn the Builder Pattern.

Let’s see how we can implement a better test setup using this pattern. First, we define a `struct StateBuilder`.

ⓘ

    pub(crate) struct StateBuilder {
    	balances: Vec<(<Runtime as pezframe_system::Config>::AccountId, Balance)>,
    }

This struct is meant to contain the same list of accounts and balances that we want to have at the beginning of each block. We hardcoded this to `let accounts = vec![(ALICE, 100), (2, 100)];` so far. Then, if desired, we attach a default value for this struct.

ⓘ

    impl Default for StateBuilder {
    	fn default() -> Self {
    		Self { balances: vec![(ALICE, 100), (BOB, 100)] }
    	}
    }

Like any other builder pattern, we attach functions to the type to mutate its internal properties.

ⓘ

    impl StateBuilder {
    	fn add_balance(
    		mut self,
    		who: <Runtime as pezframe_system::Config>::AccountId,
    		amount: Balance,
    	) -> Self {
    		self.balances.push((who, amount));
    		self
    	}
    }

Finally –the useful part– we write our own custom `build_and_execute` function on this type. This function will do multiple things:

1. It would consume `self` to produce our `TestState` based on the properties that we attached to `self`.
2. It would execute any test function that we pass in as closure.
3. A nifty trick, this allows our test setup to have some code that is executed both before and after each test. For example, in this test, we do some additional checking about the correctness of the `TotalIssuance`. We leave it up to you as an exercise to learn why the assertion should always hold, and how it is checked.

ⓘ

    impl StateBuilder {
    	pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
    		let mut ext = TestState::new_empty();
    		ext.execute_with(|| {
    			for (who, amount) in &self.balances {
    				Balances::<Runtime>::insert(who, amount);
    				TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
    			}
    		});

    		ext.execute_with(test);

    		// assertions that must always hold
    		ext.execute_with(|| {
    			assert_eq!(
    				Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
    				TotalIssuance::<Runtime>::get().unwrap_or_default()
    			);
    		})
    	}
    }

We can write tests that specifically check the initial state, and making sure our `StateBuilder` is working exactly as intended.

ⓘ

    #[test]
    fn state_builder_works() {
    	StateBuilder::default().build_and_execute(|| {
    		assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    		assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    		assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    	});
    }

ⓘ

    #[test]
    fn state_builder_add_balance() {
    	StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
    		assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
    	})
    }

#### §More Tests

Now that we have a more ergonomic test setup, let’s see how a well written test for transfer and mint would look like.

ⓘ

    #[test]
    fn transfer_works() {
    	StateBuilder::default().build_and_execute(|| {
    		// given the initial state, when:
    		assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));

    		// then:
    		assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
    		assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));

    		// when:
    		assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));

    		// then:
    		assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    		assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    	});
    }

ⓘ

    #[test]
    fn mint_works() {
    	StateBuilder::default().build_and_execute(|| {
    		// given the initial state, when:
    		assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    			RuntimeOrigin::signed(ALICE),
    			BOB,
    			100
    		));

    		// then:
    		assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));

    		// given:
    		assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    			RuntimeOrigin::signed(ALICE),
    			CHARLIE,
    			100
    		));

    		// then:
    		assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
    	});
    }

It is always a good idea to build a mental model where you write at least one test for each “success path” of a dispatchable, and one test for each “failure path”, such as:

ⓘ

    #[test]
    fn transfer_from_non_existent_fails() {
    	StateBuilder::default().build_and_execute(|| {
    		// given the initial state, when:
    		assert_err!(
    			Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
    			"NonExistentAccount"
    		);

    		// then nothing has changed.
    		assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    		assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    		assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
    		assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    	});
    }

We leave it up to you to write a test that triggers the `InsufficientBalance` error.

#### §Event and Error

Our pezpallet is mainly missing two parts that are common in most FRAME pallets: Events, and Errors. First, let’s understand what each is.

- Error: The static string-based error scheme we used so far is good for readability, but it has a few drawbacks. The biggest problem with strings are that they are not type safe, e.g. a match statement cannot be exhaustive. These string literals will bloat the final wasm blob, and are relatively heavy to transmit and encode/decode. Moreover, it is easy to mistype them by one character. FRAME errors are exactly a solution to maintain readability, whilst fixing the drawbacks mentioned. In short, we use an enum to represent different variants of our error. These variants are then mapped in an efficient way (using only `u8` indices) to [`pezsp_runtime::DispatchError::Module`]. Read more about this in `pezpallet::error`.

- Event: Events are akin to the return type of dispatchables. They are mostly data blobs emitted by the runtime to let outside world know what is happening inside the pezpallet. Since otherwise, the outside world does not have an easy access to the state changes. They should represent what happened at the end of a dispatch operation. Therefore, the convention is to use passive tense for event names (eg. `SomethingHappened`). This allows other sub-systems or external parties (eg. a light-node, a DApp) to listen to particular events happening, without needing to re-execute the whole state transition function.

With the explanation out of the way, let’s see how these components can be added. Both follow a fairly familiar syntax: normal Rust enums, with extra `pezpallet::event` and `pezpallet::error` attributes attached.

ⓘ

    #[pezpallet::event]
    #[pezpallet::generate_deposit(pub(super) fn deposit_event)]
    pub enum Event<T: Config> {
    	/// A transfer succeeded.
    	Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
    }

ⓘ

    #[pezpallet::error]
    pub enum Error<T> {
    	/// Account does not exist.
    	NonExistentAccount,
    	/// Account does not have enough balance.
    	InsufficientBalance,
    }

One slightly custom part of this is the `pezpallet::generate_deposit` part. Without going into too much detail, in order for a pezpallet to emit events to the rest of the system, it needs to do two things:

1. Declare a type in its `Config` that refers to the overarching event type of the runtime. In short, by doing this, the pezpallet is expressing an important bound: `type RuntimeEvent: From<Event<Self>>`. Read: a `RuntimeEvent` exists, and it can be created from the local `enum Event` of this pezpallet. This enables the pezpallet to convert its `Event` into `RuntimeEvent`, and store it where needed.

2. But, doing this conversion and storing is too much to expect each pezpallet to define. FRAME provides a default way of storing events, and this is what `pezpallet::generate_deposit` is doing.

ⓘ

    #[pezpallet::config]
    pub trait Config: pezframe_system::Config {
    	/// The overarching event type of the runtime.
    	#[allow(deprecated)]
    	type RuntimeEvent: From<Event<Self>>
    		+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>
    		+ TryInto<Event<Self>>;
    }

> These `Runtime*` types are better explained in `crate::reference_docs::frame_runtime_types`.

Then, we can rewrite the `transfer` dispatchable as such:

ⓘ

    pub fn transfer(
    	origin: T::RuntimeOrigin,
    	dest: T::AccountId,
    	amount: Balance,
    ) -> DispatchResult {
    	let sender = ensure_signed(origin)?;

    	// ensure sender has enough balance, and if so, calculate what is left after `amount`.
    	let sender_balance =
    		Balances::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
    	let remainder =
    		sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;

    	Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
    	Balances::<T>::insert(&sender, remainder);

    	Self::deposit_event(Event::<T>::Transferred { from: sender, to: dest, amount });

    	Ok(())
    }

Then, notice how now we would need to provide this `type RuntimeEvent` in our test runtime setup.

ⓘ

    pub mod runtime_v2 {
    	use super::*;
    	use crate::pezpallet_v2 as pezpallet_currency;

    	construct_runtime!(
    		pub enum Runtime {
    			System: pezframe_system,
    			Currency: pezpallet_currency,
    		}
    	);

    	#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
    	impl pezframe_system::Config for Runtime {
    		type Block = MockBlock<Runtime>;
    		type AccountId = u64;
    	}

    	impl pezpallet_currency::Config for Runtime {
    		type RuntimeEvent = RuntimeEvent;
    	}
    }

In this snippet, the actual `RuntimeEvent` type (right hand side of `type RuntimeEvent = RuntimeEvent`) is generated by `construct_runtime`. An interesting way to inspect this type is to see its definition in rust-docs: `crate::guides::your_first_pallet::pezpallet_v2::tests::runtime_v2::RuntimeEvent`.

### §What Next?

The following topics where used in this guide, but not covered in depth. It is suggested to study them subsequently:

- `crate::reference_docs::defensive_programming`.
- `crate::reference_docs::frame_origin`.
- `crate::reference_docs::frame_runtime_types`.
- The pezpallet we wrote in this guide was using `dev_mode`, learn more in `pezpallet::config`.
- Learn more about the individual pezpallet items/macros, such as event and errors and call, in [`pezframe::pezpallet_macros`].

## Modules§

pezpallet
  The `pezpallet` module in each FRAME pezpallet hosts the most important items needed to construct this pezpallet.
pezpallet_v2
  The `pezpallet` module in each FRAME pezpallet hosts the most important items needed to construct this pezpallet.
shell_pallet
  The `pezpallet` module in each FRAME pezpallet hosts the most important items needed to construct this pezpallet.