#
pezkuwi_sdk_docs/guides/your_first_pallet/
mod.rs

    1//! # Currency Pezpallet
    2//!
    3//! By the end of this guide, you will have written a small FRAME pezpallet (see
    4//! [`crate::pezkuwi_sdk::frame_runtime`]) that is capable of handling a simple crypto-currency.
    5//! This pezpallet will:
    6//!
    7//! 1. Allow anyone to mint new tokens into accounts (which is obviously not a great idea for a real
    8//!    system).
    9//! 2. Allow any user that owns tokens to transfer them to others.
    10//! 3. Track the total issuance of all tokens at all times.
    11//!
    12//! > This guide will build a currency pezpallet from scratch using only the lowest primitives of
    13//! > FRAME, and is mainly intended for education, not *applicability*. For example, almost all
    14//! > FRAME-based runtimes use various techniques to re-use a currency pezpallet instead of writing
    15//! > one. Further advanced FRAME related topics are discussed in [`crate::reference_docs`].
    16//!
    17//! ## Writing Your First Pezpallet
    18//!
    19//! To get started, clone one of the templates mentioned in [`crate::pezkuwi_sdk::templates`]. We
    20//! recommend using the `pezkuwi-sdk-minimal-template`. You might need to change small parts of
    21//! this guide, namely the crate/package names, based on which template you use.
    22//!
    23//! > Be aware that you can read the entire source code backing this tutorial by clicking on the
    24//! > `source` button at the top right of the page.
    25//!
    26//! You should have studied the following modules as a prelude to this guide:
    27//!
    28//! - [`crate::reference_docs::blockchain_state_machines`]
    29//! - [`crate::reference_docs::trait_based_programming`]
    30//! - [`crate::pezkuwi_sdk::frame_runtime`]
    31//!
    32//! ## Topics Covered
    33//!
    34//! The following FRAME topics are covered in this guide:
    35//!
    36//! - [`pezpallet::storage`]
    37//! - [`pezpallet::call`]
    38//! - [`pezpallet::event`]
    39//! - [`pezpallet::error`]
    40//! - Basics of testing a pezpallet
    41//! - [Constructing a runtime](pezframe::runtime::prelude::construct_runtime)
    42//!
    43//! ### Shell Pezpallet
    44//!
    45//! Consider the following as a "shell pezpallet". We continue building the rest of this pezpallet
    46//! based on this template.
    47//!
    48//! [`pezpallet::config`] and [`pezpallet::pezpallet`] are both mandatory parts of any
    49//! pezpallet. Refer to the documentation of each to get an overview of what they do.
    50#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", shell_pallet)]
    51//!
    52//! All of the code that follows in this guide should live inside of the `mod pezpallet`.
    53//!
    54//! ### Storage
    55//!
    56//! First, we will need to create two onchain storage declarations.
    57//!
    58//! One should be a mapping from account-ids to a balance type, and one value that is the total
    59//! issuance.
    60//!
    61//! > For the rest of this guide, we will opt for a balance type of `u128`. For the sake of
    62//! > simplicity, we are hardcoding this type. In a real pezpallet is best practice to define it as
    63//! > a
    64//! > generic bounded type in the `Config` trait, and then specify it in the implementation.
    65#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", Balance)]
    66//!
    67//! The definition of these two storage items, based on [`pezpallet::storage`] details, is as
    68//! follows:
    69#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", TotalIssuance)]
    70#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", Balances)]
    71//!
    72//! ### Dispatchables
    73//!
    74//! Next, we will define the dispatchable functions. As per [`pezpallet::call`], these will be
    75//! defined as normal `fn`s attached to `struct Pezpallet`.
    76#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", impl_pallet)]
    77//!
    78//! The logic of these functions is self-explanatory. Instead, we will focus on the FRAME-related
    79//! details:
    80//!
    81//! - Where do `T::AccountId` and `T::RuntimeOrigin` come from? These are both defined in
    82//!  [`pezframe::prelude::pezframe_system::Config`], therefore we can access them in `T`.
    83//! - What is `ensure_signed`, and what does it do with the aforementioned `T::RuntimeOrigin`? This
    84//!   is outside the scope of this guide, and you can learn more about it in the origin reference
    85//!   document ([`crate::reference_docs::frame_origin`]). For now, you should only know the
    86//!   signature of the function: it takes a generic `T::RuntimeOrigin` and returns a
    87//!   `Result<T::AccountId, _>`. So by the end of this function call, we know that this dispatchable
    88//!   was signed by `sender`.
    89#![doc = docify::embed!("../../bizinikiwi/pezframe/system/src/lib.rs", ensure_signed)]
    90//!
    91//! - Where does `mutate`, `get` and `insert` and other storage APIs come from? All of them are
    92//! explained in the corresponding `type`, for example, for `Balances::<T>::insert`, you can look
    93//! into [`pezframe::prelude::StorageMap::insert`].
    94//!
    95//! - The return type of all dispatchable functions is [`pezframe::prelude::DispatchResult`]:
    96#![doc = docify::embed!("../../bizinikiwi/pezframe/support/src/dispatch.rs", DispatchResult)]
    97//!
    98//! Which is more or less a normal Rust `Result`, with a custom [`pezframe::prelude::DispatchError`] as
    99//! the `Err` variant. We won't cover this error in detail here, but importantly you should know
    100//! that there is an `impl From<&'static string> for DispatchError` provided (see
    101//! [here](`pezframe::prelude::DispatchError#impl-From<%26str>-for-DispatchError`)). Therefore,
    102//! we can use basic string literals as our error type and `.into()` them into `DispatchError`.
    103//!
    104//! - Why are all `get` and `mutate` functions returning an `Option`? This is the default behavior
    105//!   of FRAME storage APIs. You can learn more about how to override this by looking into
    106//!   [`pezpallet::storage`], and [`pezframe::prelude::ValueQuery`]/[`pezframe::prelude::OptionQuery`]
    107//!
    108//! ### Improving Errors
    109//!
    110//! How we handle error in the above snippets is fairly rudimentary. Let's look at how this can be
    111//! improved. First, we can use [`pezframe::prelude::ensure`] to express the error slightly better.
    112//! This macro will call `.into()` under the hood.
    113#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", transfer_better)]
    114//!
    115//! Moreover, you will learn in the [Defensive Programming
    116//! section](crate::reference_docs::defensive_programming) that it is always recommended to use
    117//! safe arithmetic operations in your runtime. By using [`pezframe::traits::CheckedSub`], we can not
    118//! only take a step in that direction, but also improve the error handing and make it slightly more
    119//! ergonomic.
    120#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", transfer_better_checked)]
    121//!
    122//! This is more or less all the logic that there is in this basic currency pezpallet!
    123//!
    124//! ### Your First (Test) Runtime
    125//!
    126//! The typical testing code of a pezpallet lives in a module that imports some preludes useful for
    127//! testing, similar to:
    128//!
    129//! ```
    130//! pub mod pezpallet {
    131//! 	// snip -- actually pezpallet code.
    132//! }
    133//!
    134//! #[cfg(test)]
    135//! mod tests {
    136//! 	// bring in the testing prelude of frame
    137//! 	use pezframe::testing_prelude::*;
    138//! 	// bring in all pezpallet items
    139//! 	use super::pezpallet::*;
    140//!
    141//! 	// snip -- rest of the testing code.
    142//! }
    143//! ```
    144//!
    145//! Next, we create a "test runtime" in order to test our pezpallet. Recall from
    146//! [`crate::pezkuwi_sdk::frame_runtime`] that a runtime is a collection of pallets, expressed
    147//! through [`pezframe::runtime::prelude::construct_runtime`]. All runtimes also have to include
    148//! [`pezframe::prelude::pezframe_system`]. So we expect to see a runtime with two pezpallet,
    149//! `pezframe_system` and the one we just wrote.
    150#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", runtime)]
    151//!
    152//! > [`pezframe::pezpallet_macros::derive_impl`] is a FRAME feature that enables developers to have
    153//! > defaults for associated types.
    154//!
    155//! Recall that within our pezpallet, (almost) all blocks of code are generic over `<T: Config>`.
    156//! And, because `trait Config: pezframe_system::Config`, we can get access to all items in `Config`
    157//! (or `pezframe_system::Config`) using `T::NameOfItem`. This is all within the boundaries of how
    158//! Rust traits and generics work. If unfamiliar with this pattern, read
    159//! [`crate::reference_docs::trait_based_programming`] before going further.
    160//!
    161//! Crucially, a typical FRAME runtime contains a `struct Runtime`. The main role of this `struct`
    162//! is to implement the `trait Config` of all pallets. That is, anywhere within your pezpallet code
    163//! where you see `<T: Config>` (read: *"some type `T` that implements `Config`"*), in the runtime,
    164//! it can be replaced with `<Runtime>`, because `Runtime` implements `Config` of all pallets, as we
    165//! see above.
    166//!
    167//! Another way to think about this is that within a pezpallet, a lot of types are "unknown" and, we
    168//! only know that they will be provided at some later point. For example, when you write
    169//! `T::AccountId` (which is short for `<T as pezframe_system::Config>::AccountId`) in your
    170//! pezpallet, you are in fact saying "*Some type `AccountId` that will be known later*". That
    171//! "later" is in fact when you specify these types when you implement all `Config` traits for
    172//! `Runtime`.
    173//!
    174//! As you see above, `pezframe_system::Config` is setting the `AccountId` to `u64`. Of course, a
    175//! real runtime will not use this type, and instead reside to a proper type like a 32-byte standard
    176//! public key. This is a HUGE benefit that FRAME developers can tap into: through the framework
    177//! being so generic, different types can always be customized to simple things when needed.
    178//!
    179//! > Imagine how hard it would have been if all tests had to use a real 32-byte account id, as
    180//! > opposed to just a u64 number 🙈.
    181//!
    182//! ### Your First Test
    183//!
    184//! The above is all you need to execute the dispatchables of your pezpallet. The last thing you
    185//! need to learn is that all of your pezpallet testing code should be wrapped in
    186//! [`pezframe::testing_prelude::TestState`]. This is a type that provides access to an in-memory state
    187//! to be used in our tests.
    188#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", first_test)]
    189//!
    190//! In the first test, we simply assert that there is no total issuance, and no balance associated
    191//! with Alice's account. Then, we mint some balance into Alice's, and re-check.
    192//!
    193//! As noted above, the `T::AccountId` is now `u64`. Moreover, `Runtime` is replacing `<T: Config>`.
    194//! This is why for example you see `Balances::<Runtime>::get(..)`. Finally, notice that the
    195//! dispatchables are simply functions that can be called on top of the `Pezpallet` struct.
    196//!
    197//! Congratulations! You have written your first pezpallet and tested it! Next, we learn a few
    198//! optional steps to improve our pezpallet.
    199//!
    200//! ## Improving the Currency Pezpallet
    201//!
    202//! ### Better Test Setup
    203//!
    204//! Idiomatic FRAME pallets often use Builder pattern to define their initial state.
    205//!
    206//! > The Pezkuwi Blockchain Academy's Rust entrance exam has a
    207//! > [section](https://github.com/pezkuwichain/kurdistan_blockchain-akademy/blob/main/src/m_builder.rs)
    208//! > on this that you can use to learn the Builder Pattern.
    209//!
    210//! Let's see how we can implement a better test setup using this pattern. First, we define a
    211//! `struct StateBuilder`.
    212#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", StateBuilder)]
    213//!
    214//! This struct is meant to contain the same list of accounts and balances that we want to have at
    215//! the beginning of each block. We hardcoded this to `let accounts = vec![(ALICE, 100), (2, 100)];`
    216//! so far. Then, if desired, we attach a default value for this struct.
    217#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", default_state_builder)]
    218//!
    219//! Like any other builder pattern, we attach functions to the type to mutate its internal
    220//! properties.
    221#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", impl_state_builder_add)]
    222//!
    223//!  Finally --the useful part-- we write our own custom `build_and_execute` function on
    224//! this type. This function will do multiple things:
    225//!
    226//! 1. It would consume `self` to produce our `TestState` based on the properties that we attached
    227//!    to `self`.
    228//! 2. It would execute any test function that we pass in as closure.
    229//! 3. A nifty trick, this allows our test setup to have some code that is executed both before and
    230//!    after each test. For example, in this test, we do some additional checking about the
    231//!    correctness of the `TotalIssuance`. We leave it up to you as an exercise to learn why the
    232//!    assertion should always hold, and how it is checked.
    233#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", impl_state_builder_build)]
    234//!
    235//! We can write tests that specifically check the initial state, and making sure our `StateBuilder`
    236//! is working exactly as intended.
    237#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", state_builder_works)]
    238#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", state_builder_add_balance)]
    239//!
    240//! ### More Tests
    241//!
    242//! Now that we have a more ergonomic test setup, let's see how a well written test for transfer and
    243//! mint would look like.
    244#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", transfer_works)]
    245#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", mint_works)]
    246//!
    247//! It is always a good idea to build a mental model where you write *at least* one test for each
    248//! "success path" of a dispatchable, and one test for each "failure path", such as:
    249#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", transfer_from_non_existent_fails)]
    250//!
    251//! We leave it up to you to write a test that triggers the `InsufficientBalance` error.
    252//!
    253//! ### Event and Error
    254//!
    255//! Our pezpallet is mainly missing two parts that are common in most FRAME pallets: Events, and
    256//! Errors. First, let's understand what each is.
    257//!
    258//! - **Error**: The static string-based error scheme we used so far is good for readability, but it
    259//!   has a few drawbacks. The biggest problem with strings are that they are not type safe, e.g. a
    260//!   match statement cannot be exhaustive. These string literals will bloat the final wasm blob,
    261//!   and are relatively heavy to transmit and encode/decode. Moreover, it is easy to mistype them
    262//!   by one character. FRAME errors are exactly a solution to maintain readability, whilst fixing
    263//!   the drawbacks mentioned. In short, we use an enum to represent different variants of our
    264//!   error. These variants are then mapped in an efficient way (using only `u8` indices) to
    265//!   [`pezsp_runtime::DispatchError::Module`]. Read more about this in [`pezpallet::error`].
    266//!
    267//! - **Event**: Events are akin to the return type of dispatchables. They are mostly data blobs
    268//!   emitted by the runtime to let outside world know what is happening inside the pezpallet. Since
    269//!   otherwise, the outside world does not have an easy access to the state changes. They should
    270//!   represent what happened at the end of a dispatch operation. Therefore, the convention is to
    271//!   use passive tense for event names (eg. `SomethingHappened`). This allows other sub-systems or
    272//!   external parties (eg. a light-node, a DApp) to listen to particular events happening, without
    273//!   needing to re-execute the whole state transition function.
    274//!
    275//! With the explanation out of the way, let's see how these components can be added. Both follow a
    276//! fairly familiar syntax: normal Rust enums, with extra [`pezpallet::event`] and
    277//! [`pezpallet::error`] attributes attached.
    278#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", Event)]
    279#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", Error)]
    280//!
    281//! One slightly custom part of this is the [`pezpallet::generate_deposit`] part. Without going into
    282//! too much detail, in order for a pezpallet to emit events to the rest of the system, it needs to
    283//! do two things:
    284//!
    285//! 1. Declare a type in its `Config` that refers to the overarching event type of the runtime. In
    286//! short, by doing this, the pezpallet is expressing an important bound: `type RuntimeEvent:
    287//! From<Event<Self>>`. Read: a `RuntimeEvent` exists, and it can be created from the local `enum
    288//! Event` of this pezpallet. This enables the pezpallet to convert its `Event` into `RuntimeEvent`,
    289//! and store it where needed.
    290//!
    291//! 2. But, doing this conversion and storing is too much to expect each pezpallet to define. FRAME
    292//! provides a default way of storing events, and this is what [`pezpallet::generate_deposit`] is
    293//! doing.
    294#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", config_v2)]
    295//!
    296//! > These `Runtime*` types are better explained in
    297//! > [`crate::reference_docs::frame_runtime_types`].
    298//!
    299//! Then, we can rewrite the `transfer` dispatchable as such:
    300#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", transfer_v2)]
    301//!
    302//! Then, notice how now we would need to provide this `type RuntimeEvent` in our test runtime
    303//! setup.
    304#![doc = docify::embed!("./packages/guides/first-pezpallet/src/lib.rs", runtime_v2)]
    305//!
    306//! In this snippet, the actual `RuntimeEvent` type (right hand side of `type RuntimeEvent =
    307//! RuntimeEvent`) is generated by
    308//! [`construct_runtime`](pezframe::runtime::prelude::construct_runtime). An interesting way to inspect
    309//! this type is to see its definition in rust-docs:
    310//! [`crate::guides::your_first_pallet::pezpallet_v2::tests::runtime_v2::RuntimeEvent`].
    311//!
    312//!
    313//! ## What Next?
    314//!
    315//! The following topics where used in this guide, but not covered in depth. It is suggested to
    316//! study them subsequently:
    317//!
    318//! - [`crate::reference_docs::defensive_programming`].
    319//! - [`crate::reference_docs::frame_origin`].
    320//! - [`crate::reference_docs::frame_runtime_types`].
    321//! - The pezpallet we wrote in this guide was using `dev_mode`, learn more in
    322//!   [`pezpallet::config`].
    323//! - Learn more about the individual pezpallet items/macros, such as event and errors and call, in
    324//!   [`pezframe::pezpallet_macros`].
    325//!
    326//! [`pezpallet::storage`]: pezframe_support::pezpallet_macros::storage
    327//! [`pezpallet::call`]: pezframe_support::pezpallet_macros::call
    328//! [`pezpallet::event`]: pezframe_support::pezpallet_macros::event
    329//! [`pezpallet::error`]: pezframe_support::pezpallet_macros::error
    330//! [`pezpallet::pezpallet`]: pezframe_support::pezpallet
    331//! [`pezpallet::config`]: pezframe_support::pezpallet_macros::config
    332//! [`pezpallet::generate_deposit`]: pezframe_support::pezpallet_macros::generate_deposit
    333//! [`frame`]: crate::pezkuwi_sdk::frame_runtime
    334
    335#[docify::export]
    336#[pezframe::pezpallet(dev_mode)]
    337pub mod shell_pallet {
    338	use pezframe::prelude::*;
    339
    340	#[pezpallet::config]
    341	pub trait Config: pezframe_system::Config {}
    342
    343	#[pezpallet::pezpallet]
    344	pub struct Pezpallet<T>(_);
    345}
    346
    347#[pezframe::pezpallet(dev_mode)]
    348pub mod pezpallet {
    349	use pezframe::prelude::*;
    350
    351	#[docify::export]
    352	pub type Balance = u128;
    353
    354	#[pezpallet::config]
    355	pub trait Config: pezframe_system::Config {}
    356
    357	#[pezpallet::pezpallet]
    358	pub struct Pezpallet<T>(_);
    359
    360	#[docify::export]
    361	/// Single storage item, of type `Balance`.
    362	#[pezpallet::storage]
    363	pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
    364
    365	#[docify::export]
    366	/// A mapping from `T::AccountId` to `Balance`
    367	#[pezpallet::storage]
    368	pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
    369
    370	#[docify::export(impl_pallet)]
    371	#[pezpallet::call]
    372	impl<T: Config> Pezpallet<T> {
    373		/// An unsafe mint that can be called by anyone. Not a great idea.
    374		pub fn mint_unsafe(
    375			origin: T::RuntimeOrigin,
    376			dest: T::AccountId,
    377			amount: Balance,
    378		) -> DispatchResult {
    379			// ensure that this is a signed account, but we don't really check `_anyone`.
    380			let _anyone = ensure_signed(origin)?;
    381
    382			// update the balances map. Notice how all `<T: Config>` remains as `<T>`.
    383			Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
    384			// update total issuance.
    385			TotalIssuance::<T>::mutate(|t| *t = Some(t.unwrap_or(0) + amount));
    386
    387			Ok(())
    388		}
    389
    390		/// Transfer `amount` from `origin` to `dest`.
    391		pub fn transfer(
    392			origin: T::RuntimeOrigin,
    393			dest: T::AccountId,
    394			amount: Balance,
    395		) -> DispatchResult {
    396			let sender = ensure_signed(origin)?;
    397
    398			// ensure sender has enough balance, and if so, calculate what is left after `amount`.
    399			let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    400			if sender_balance < amount {
    401				return Err("InsufficientBalance".into());
    402			}
    403			let remainder = sender_balance - amount;
    404
    405			// update sender and dest balances.
    406			Balances::<T>::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount));
    407			Balances::<T>::insert(&sender, remainder);
    408
    409			Ok(())
    410		}
    411	}
    412
    413	#[allow(unused)]
    414	impl<T: Config> Pezpallet<T> {
    415		#[docify::export]
    416		pub fn transfer_better(
    417			origin: T::RuntimeOrigin,
    418			dest: T::AccountId,
    419			amount: Balance,
    420		) -> DispatchResult {
    421			let sender = ensure_signed(origin)?;
    422
    423			let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    424			ensure!(sender_balance >= amount, "InsufficientBalance");
    425			let remainder = sender_balance - amount;
    426
    427			// .. snip
    428			Ok(())
    429		}
    430
    431		#[docify::export]
    432		/// Transfer `amount` from `origin` to `dest`.
    433		pub fn transfer_better_checked(
    434			origin: T::RuntimeOrigin,
    435			dest: T::AccountId,
    436			amount: Balance,
    437		) -> DispatchResult {
    438			let sender = ensure_signed(origin)?;
    439
    440			let sender_balance = Balances::<T>::get(&sender).ok_or("NonExistentAccount")?;
    441			let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?;
    442
    443			// .. snip
    444			Ok(())
    445		}
    446	}
    447
    448	#[cfg(any(test, doc))]
    449	pub(crate) mod tests {
    450		use crate::guides::your_first_pallet::pezpallet::*;
    451
    452		#[docify::export(testing_prelude)]
    453		use pezframe::testing_prelude::*;
    454
    455		pub(crate) const ALICE: u64 = 1;
    456		pub(crate) const BOB: u64 = 2;
    457		pub(crate) const CHARLIE: u64 = 3;
    458
    459		#[docify::export]
    460		// This runtime is only used for testing, so it should be somewhere like `#[cfg(test)] mod
    461		// tests { .. }`
    462		mod runtime {
    463			use super::*;
    464			// we need to reference our `mod pezpallet` as an identifier to pass to
    465			// `construct_runtime`.
    466			// YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE
    467			use crate::guides::your_first_pallet::pezpallet as pezpallet_currency;
    468
    469			construct_runtime!(
    470				pub enum Runtime {
    471					// ---^^^^^^ This is where `enum Runtime` is defined.
    472					System: pezframe_system,
    473					Currency: pezpallet_currency,
    474				}
    475			);
    476
    477			#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
    478			impl pezframe_system::Config for Runtime {
    479				type Block = MockBlock<Runtime>;
    480				// within pezpallet we just said `<T as pezframe_system::Config>::AccountId`, now we
    481				// finally specified it.
    482				type AccountId = u64;
    483			}
    484
    485			// our simple pezpallet has nothing to be configured.
    486			impl pezpallet_currency::Config for Runtime {}
    487		}
    488
    489		pub(crate) use runtime::*;
    490
    491		#[allow(unused)]
    492		#[docify::export]
    493		fn new_test_state_basic() -> TestState {
    494			let mut state = TestState::new_empty();
    495			let accounts = vec![(ALICE, 100), (BOB, 100)];
    496			state.execute_with(|| {
    497				for (who, amount) in &accounts {
    498					Balances::<Runtime>::insert(who, amount);
    499					TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
    500				}
    501			});
    502
    503			state
    504		}
    505
    506		#[docify::export]
    507		pub(crate) struct StateBuilder {
    508			balances: Vec<(<Runtime as pezframe_system::Config>::AccountId, Balance)>,
    509		}
    510
    511		#[docify::export(default_state_builder)]
    512		impl Default for StateBuilder {
    513			fn default() -> Self {
    514				Self { balances: vec![(ALICE, 100), (BOB, 100)] }
    515			}
    516		}
    517
    518		#[docify::export(impl_state_builder_add)]
    519		impl StateBuilder {
    520			fn add_balance(
    521				mut self,
    522				who: <Runtime as pezframe_system::Config>::AccountId,
    523				amount: Balance,
    524			) -> Self {
    525				self.balances.push((who, amount));
    526				self
    527			}
    528		}
    529
    530		#[docify::export(impl_state_builder_build)]
    531		impl StateBuilder {
    532			pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) {
    533				let mut ext = TestState::new_empty();
    534				ext.execute_with(|| {
    535					for (who, amount) in &self.balances {
    536						Balances::<Runtime>::insert(who, amount);
    537						TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
    538					}
    539				});
    540
    541				ext.execute_with(test);
    542
    543				// assertions that must always hold
    544				ext.execute_with(|| {
    545					assert_eq!(
    546						Balances::<Runtime>::iter().map(|(_, x)| x).sum::<u128>(),
    547						TotalIssuance::<Runtime>::get().unwrap_or_default()
    548					);
    549				})
    550			}
    551		}
    552
    553		#[docify::export]
    554		#[test]
    555		fn first_test() {
    556			TestState::new_empty().execute_with(|| {
    557				// We expect Alice's account to have no funds.
    558				assert_eq!(Balances::<Runtime>::get(&ALICE), None);
    559				assert_eq!(TotalIssuance::<Runtime>::get(), None);
    560
    561				// mint some funds into Alice's account.
    562				assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    563					RuntimeOrigin::signed(ALICE),
    564					ALICE,
    565					100
    566				));
    567
    568				// re-check the above
    569				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    570				assert_eq!(TotalIssuance::<Runtime>::get(), Some(100));
    571			})
    572		}
    573
    574		#[docify::export]
    575		#[test]
    576		fn state_builder_works() {
    577			StateBuilder::default().build_and_execute(|| {
    578				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    579				assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    580				assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
    581				assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    582			});
    583		}
    584
    585		#[docify::export]
    586		#[test]
    587		fn state_builder_add_balance() {
    588			StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| {
    589				assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(42));
    590				assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
    591			})
    592		}
    593
    594		#[test]
    595		#[should_panic]
    596		fn state_builder_duplicate_genesis_fails() {
    597			StateBuilder::default()
    598				.add_balance(CHARLIE, 42)
    599				.add_balance(CHARLIE, 43)
    600				.build_and_execute(|| {
    601					assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
    602					assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
    603				})
    604		}
    605
    606		#[docify::export]
    607		#[test]
    608		fn mint_works() {
    609			StateBuilder::default().build_and_execute(|| {
    610				// given the initial state, when:
    611				assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    612					RuntimeOrigin::signed(ALICE),
    613					BOB,
    614					100
    615				));
    616
    617				// then:
    618				assert_eq!(Balances::<Runtime>::get(&BOB), Some(200));
    619				assert_eq!(TotalIssuance::<Runtime>::get(), Some(300));
    620
    621				// given:
    622				assert_ok!(Pezpallet::<Runtime>::mint_unsafe(
    623					RuntimeOrigin::signed(ALICE),
    624					CHARLIE,
    625					100
    626				));
    627
    628				// then:
    629				assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
    630				assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
    631			});
    632		}
    633
    634		#[docify::export]
    635		#[test]
    636		fn transfer_works() {
    637			StateBuilder::default().build_and_execute(|| {
    638				// given the initial state, when:
    639				assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
    640
    641				// then:
    642				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
    643				assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
    644				assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    645
    646				// when:
    647				assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(BOB), ALICE, 50));
    648
    649				// then:
    650				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    651				assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    652				assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    653			});
    654		}
    655
    656		#[docify::export]
    657		#[test]
    658		fn transfer_from_non_existent_fails() {
    659			StateBuilder::default().build_and_execute(|| {
    660				// given the initial state, when:
    661				assert_err!(
    662					Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10),
    663					"NonExistentAccount"
    664				);
    665
    666				// then nothing has changed.
    667				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(100));
    668				assert_eq!(Balances::<Runtime>::get(&BOB), Some(100));
    669				assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
    670				assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    671			});
    672		}
    673	}
    674}
    675
    676#[pezframe::pezpallet(dev_mode)]
    677pub mod pezpallet_v2 {
    678	use super::pezpallet::Balance;
    679	use pezframe::prelude::*;
    680
    681	#[docify::export(config_v2)]
    682	#[pezpallet::config]
    683	pub trait Config: pezframe_system::Config {
    684		/// The overarching event type of the runtime.
    685		#[allow(deprecated)]
    686		type RuntimeEvent: From<Event<Self>>
    687			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>
    688			+ TryInto<Event<Self>>;
    689	}
    690
    691	#[pezpallet::pezpallet]
    692	pub struct Pezpallet<T>(_);
    693
    694	#[pezpallet::storage]
    695	pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
    696
    697	#[pezpallet::storage]
    698	pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
    699
    700	#[docify::export]
    701	#[pezpallet::error]
    702	pub enum Error<T> {
    703		/// Account does not exist.
    704		NonExistentAccount,
    705		/// Account does not have enough balance.
    706		InsufficientBalance,
    707	}
    708
    709	#[docify::export]
    710	#[pezpallet::event]
    711	#[pezpallet::generate_deposit(pub(super) fn deposit_event)]
    712	pub enum Event<T: Config> {
    713		/// A transfer succeeded.
    714		Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
    715	}
    716
    717	#[pezpallet::call]
    718	impl<T: Config> Pezpallet<T> {
    719		#[docify::export(transfer_v2)]
    720		pub fn transfer(
    721			origin: T::RuntimeOrigin,
    722			dest: T::AccountId,
    723			amount: Balance,
    724		) -> DispatchResult {
    725			let sender = ensure_signed(origin)?;
    726
    727			// ensure sender has enough balance, and if so, calculate what is left after `amount`.
    728			let sender_balance =
    729				Balances::<T>::get(&sender).ok_or(Error::<T>::NonExistentAccount)?;
    730			let remainder =
    731				sender_balance.checked_sub(amount).ok_or(Error::<T>::InsufficientBalance)?;
    732
    733			Balances::<T>::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount));
    734			Balances::<T>::insert(&sender, remainder);
    735
    736			Self::deposit_event(Event::<T>::Transferred { from: sender, to: dest, amount });
    737
    738			Ok(())
    739		}
    740	}
    741
    742	#[cfg(any(test, doc))]
    743	pub mod tests {
    744		use super::{super::pezpallet::tests::StateBuilder, *};
    745		use pezframe::testing_prelude::*;
    746		const ALICE: u64 = 1;
    747		const BOB: u64 = 2;
    748
    749		#[docify::export]
    750		pub mod runtime_v2 {
    751			use super::*;
    752			use crate::guides::your_first_pallet::pezpallet_v2 as pezpallet_currency;
    753
    754			construct_runtime!(
    755				pub enum Runtime {
    756					System: pezframe_system,
    757					Currency: pezpallet_currency,
    758				}
    759			);
    760
    761			#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)]
    762			impl pezframe_system::Config for Runtime {
    763				type Block = MockBlock<Runtime>;
    764				type AccountId = u64;
    765			}
    766
    767			impl pezpallet_currency::Config for Runtime {
    768				type RuntimeEvent = RuntimeEvent;
    769			}
    770		}
    771
    772		pub(crate) use runtime_v2::*;
    773
    774		#[docify::export(transfer_works_v2)]
    775		#[test]
    776		fn transfer_works() {
    777			StateBuilder::default().build_and_execute(|| {
    778				// skip the genesis block, as events are not deposited there and we need them for
    779				// the final assertion.
    780				System::set_block_number(ALICE);
    781
    782				// given the initial state, when:
    783				assert_ok!(Pezpallet::<Runtime>::transfer(RuntimeOrigin::signed(ALICE), BOB, 50));
    784
    785				// then:
    786				assert_eq!(Balances::<Runtime>::get(&ALICE), Some(50));
    787				assert_eq!(Balances::<Runtime>::get(&BOB), Some(150));
    788				assert_eq!(TotalIssuance::<Runtime>::get(), Some(200));
    789
    790				// now we can also check that an event has been deposited:
    791				assert_eq!(
    792					System::read_events_for_pallet::<Event<Runtime>>(),
    793					vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
    794				);
    795			});
    796		}
    797	}
    798}