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}