1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788
//! # Currency Pallet
//!
//! By the end of this guide, you will have written a small FRAME pallet (see
//! [`crate::polkadot_sdk::frame_runtime`]) that is capable of handling a simple crypto-currency.
//! This pallet 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 pallet 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 pallet instead of writing
//! > one. Further advanced FRAME related topics are discussed in [`crate::reference_docs`].
//!
//! ## Writing Your First Pallet
//!
//! To get started, clone one of the templates mentioned in [`crate::polkadot_sdk::templates`]. We
//! recommend using the `polkadot-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::polkadot_sdk::frame_runtime`]
//!
//! ## Topics Covered
//!
//! The following FRAME topics are covered in this guide:
//!
//! - [`pallet::storage`]
//! - [`pallet::call`]
//! - [`pallet::event`]
//! - [`pallet::error`]
//! - Basics of testing a pallet
//! - [Constructing a runtime](frame::runtime::prelude::construct_runtime)
//!
//! ### Shell Pallet
//!
//! Consider the following as a "shell pallet". We continue building the rest of this pallet based
//! on this template.
//!
//! [`pallet::config`] and [`pallet::pallet`] are both mandatory parts of any
//! pallet. Refer to the documentation of each to get an overview of what they do.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", shell_pallet)]
//!
//! All of the code that follows in this guide should live inside of the `mod pallet`.
//!
//! ### 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 pallet is best practice to define it as a
//! > generic bounded type in the `Config` trait, and then specify it in the implementation.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Balance)]
//!
//! The definition of these two storage items, based on [`pallet::storage`] details, is as follows:
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", TotalIssuance)]
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Balances)]
//!
//! ### Dispatchables
//!
//! Next, we will define the dispatchable functions. As per [`pallet::call`], these will be defined
//! as normal `fn`s attached to `struct Pallet`.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_pallet)]
//!
//! 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
//! [`frame::prelude::frame_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`.
#![doc = docify::embed!("../../substrate/frame/system/src/lib.rs", ensure_signed)]
//!
//! - 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 [`frame::prelude::StorageMap::insert`].
//!
//! - The return type of all dispatchable functions is [`frame::prelude::DispatchResult`]:
#![doc = docify::embed!("../../substrate/frame/support/src/dispatch.rs", DispatchResult)]
//!
//! Which is more or less a normal Rust `Result`, with a custom [`frame::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](`frame::prelude::DispatchError#impl-From<%26str>-for-DispatchError`)). 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
//! [`pallet::storage`], and [`frame::prelude::ValueQuery`]/[`frame::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 [`frame::prelude::ensure`] to express the error slightly better.
//! This macro will call `.into()` under the hood.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_better)]
//!
//! Moreover, you will learn in the [Defensive Programming
//! section](crate::reference_docs::defensive_programming) that it is always recommended to use
//! safe arithmetic operations in your runtime. By using [`frame::traits::CheckedSub`], we can not
//! only take a step in that direction, but also improve the error handing and make it slightly more
//! ergonomic.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_better_checked)]
//!
//! This is more or less all the logic that there is in this basic currency pallet!
//!
//! ### Your First (Test) Runtime
//!
//! The typical testing code of a pallet lives in a module that imports some preludes useful for
//! testing, similar to:
//!
//! ```
//! pub mod pallet {
//! // snip -- actually pallet code.
//! }
//!
//! #[cfg(test)]
//! mod tests {
//! // bring in the testing prelude of frame
//! use frame::testing_prelude::*;
//! // bring in all pallet items
//! use super::pallet::*;
//!
//! // snip -- rest of the testing code.
//! }
//! ```
//!
//! Next, we create a "test runtime" in order to test our pallet. Recall from
//! [`crate::polkadot_sdk::frame_runtime`] that a runtime is a collection of pallets, expressed
//! through [`frame::runtime::prelude::construct_runtime`]. All runtimes also have to include
//! [`frame::prelude::frame_system`]. So we expect to see a runtime with two pallet, `frame_system`
//! and the one we just wrote.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", runtime)]
//!
//! > [`frame::pallet_macros::derive_impl`] is a FRAME feature that enables developers to have
//! > defaults for associated types.
//!
//! Recall that within our pallet, (almost) all blocks of code are generic over `<T: Config>`. And,
//! because `trait Config: frame_system::Config`, we can get access to all items in `Config` (or
//! `frame_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 pallet 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 pallet, 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 frame_system::Config>::AccountId`) in your pallet,
//! 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, `frame_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 pallet. The last thing you need
//! to learn is that all of your pallet testing code should be wrapped in
//! [`frame::testing_prelude::TestState`]. This is a type that provides access to an in-memory state
//! to be used in our tests.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", first_test)]
//!
//! 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 `Pallet` struct.
//!
//! Congratulations! You have written your first pallet and tested it! Next, we learn a few optional
//! steps to improve our pallet.
//!
//! ## Improving the Currency Pallet
//!
//! ### Better Test Setup
//!
//! Idiomatic FRAME pallets often use Builder pattern to define their initial state.
//!
//! > The Polkadot Blockchain Academy's Rust entrance exam has a
//! > [section](https://github.com/Polkadot-Blockchain-Academy/pba-qualifier-exam/blob/main/src/m_builder.rs)
//! > 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`.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", StateBuilder)]
//!
//! 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.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", default_state_builder)]
//!
//! Like any other builder pattern, we attach functions to the type to mutate its internal
//! properties.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_state_builder_add)]
//!
//! 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.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", impl_state_builder_build)]
//!
//! We can write tests that specifically check the initial state, and making sure our `StateBuilder`
//! is working exactly as intended.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", state_builder_works)]
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", state_builder_add_balance)]
//!
//! ### 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.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_works)]
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", mint_works)]
//!
//! 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:
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_from_non_existent_fails)]
//!
//! We leave it up to you to write a test that triggers the `InsufficientBalance` error.
//!
//! ### Event and Error
//!
//! Our pallet 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
//! [`sp_runtime::DispatchError::Module`]. Read more about this in [`pallet::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 pallet. 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 [`pallet::event`] and [`pallet::error`]
//! attributes attached.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Event)]
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", Error)]
//!
//! One slightly custom part of this is the [`pallet::generate_deposit`] part. Without going into
//! too much detail, in order for a pallet 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 pallet 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 pallet. This enables the pallet to convert its `Event` into `RuntimeEvent`, and
//! store it where needed.
//!
//! 2. But, doing this conversion and storing is too much to expect each pallet to define. FRAME
//! provides a default way of storing events, and this is what [`pallet::generate_deposit`] is
//! doing.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", config_v2)]
//!
//! > These `Runtime*` types are better explained in
//! > [`crate::reference_docs::frame_runtime_types`].
//!
//! Then, we can rewrite the `transfer` dispatchable as such:
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", transfer_v2)]
//!
//! Then, notice how now we would need to provide this `type RuntimeEvent` in our test runtime
//! setup.
#![doc = docify::embed!("./packages/guides/first-pallet/src/lib.rs", runtime_v2)]
//!
//! In this snippet, the actual `RuntimeEvent` type (right hand side of `type RuntimeEvent =
//! RuntimeEvent`) is generated by
//! [`construct_runtime`](frame::runtime::prelude::construct_runtime). An interesting way to inspect
//! this type is to see its definition in rust-docs:
//! [`crate::guides::your_first_pallet::pallet_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 pallet we wrote in this guide was using `dev_mode`, learn more in [`pallet::config`].
//! - Learn more about the individual pallet items/macros, such as event and errors and call, in
//! [`frame::pallet_macros`].
//!
//! [`pallet::storage`]: frame_support::pallet_macros::storage
//! [`pallet::call`]: frame_support::pallet_macros::call
//! [`pallet::event`]: frame_support::pallet_macros::event
//! [`pallet::error`]: frame_support::pallet_macros::error
//! [`pallet::pallet`]: frame_support::pallet
//! [`pallet::config`]: frame_support::pallet_macros::config
//! [`pallet::generate_deposit`]: frame_support::pallet_macros::generate_deposit
#[docify::export]
#[frame::pallet(dev_mode)]
pub mod shell_pallet {
use frame::prelude::*;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
}
#[frame::pallet(dev_mode)]
pub mod pallet {
use frame::prelude::*;
#[docify::export]
pub type Balance = u128;
#[pallet::config]
pub trait Config: frame_system::Config {}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[docify::export]
/// Single storage item, of type `Balance`.
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
/// A mapping from `T::AccountId` to `Balance`
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[docify::export(impl_pallet)]
#[pallet::call]
impl<T: Config> Pallet<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(())
}
}
#[allow(unused)]
impl<T: Config> Pallet<T> {
#[docify::export]
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(())
}
#[docify::export]
/// Transfer `amount` from `origin` to `dest`.
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(())
}
}
#[cfg(any(test, doc))]
pub(crate) mod tests {
use crate::guides::your_first_pallet::pallet::*;
#[docify::export(testing_prelude)]
use frame::testing_prelude::*;
pub(crate) const ALICE: u64 = 1;
pub(crate) const BOB: u64 = 2;
pub(crate) const CHARLIE: u64 = 3;
#[docify::export]
// This runtime is only used for testing, so it should be somewhere like `#[cfg(test)] mod
// tests { .. }`
mod runtime {
use super::*;
// we need to reference our `mod pallet` as an identifier to pass to
// `construct_runtime`.
// YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE
use crate::guides::your_first_pallet::pallet as pallet_currency;
construct_runtime!(
pub enum Runtime {
// ---^^^^^^ This is where `enum Runtime` is defined.
System: frame_system,
Currency: pallet_currency,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Runtime>;
// within pallet we just said `<T as frame_system::Config>::AccountId`, now we
// finally specified it.
type AccountId = u64;
}
// our simple pallet has nothing to be configured.
impl pallet_currency::Config for Runtime {}
}
pub(crate) use runtime::*;
#[allow(unused)]
#[docify::export]
fn new_test_state_basic() -> TestState {
let mut state = TestState::new_empty();
let accounts = vec![(ALICE, 100), (BOB, 100)];
state.execute_with(|| {
for (who, amount) in &accounts {
Balances::<Runtime>::insert(who, amount);
TotalIssuance::<Runtime>::mutate(|b| *b = Some(b.unwrap_or(0) + amount));
}
});
state
}
#[docify::export]
pub(crate) struct StateBuilder {
balances: Vec<(<Runtime as frame_system::Config>::AccountId, Balance)>,
}
#[docify::export(default_state_builder)]
impl Default for StateBuilder {
fn default() -> Self {
Self { balances: vec![(ALICE, 100), (BOB, 100)] }
}
}
#[docify::export(impl_state_builder_add)]
impl StateBuilder {
fn add_balance(
mut self,
who: <Runtime as frame_system::Config>::AccountId,
amount: Balance,
) -> Self {
self.balances.push((who, amount));
self
}
}
#[docify::export(impl_state_builder_build)]
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()
);
})
}
}
#[docify::export]
#[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!(Pallet::<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));
})
}
#[docify::export]
#[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));
});
}
#[docify::export]
#[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));
})
}
#[test]
#[should_panic]
fn state_builder_duplicate_genesis_fails() {
StateBuilder::default()
.add_balance(CHARLIE, 42)
.add_balance(CHARLIE, 43)
.build_and_execute(|| {
assert_eq!(Balances::<Runtime>::get(&CHARLIE), None);
assert_eq!(TotalIssuance::<Runtime>::get(), Some(242));
})
}
#[docify::export]
#[test]
fn mint_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<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!(Pallet::<Runtime>::mint_unsafe(
RuntimeOrigin::signed(ALICE),
CHARLIE,
100
));
// then:
assert_eq!(Balances::<Runtime>::get(&CHARLIE), Some(100));
assert_eq!(TotalIssuance::<Runtime>::get(), Some(400));
});
}
#[docify::export]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_ok!(Pallet::<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!(Pallet::<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));
});
}
#[docify::export]
#[test]
fn transfer_from_non_existent_fails() {
StateBuilder::default().build_and_execute(|| {
// given the initial state, when:
assert_err!(
Pallet::<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));
});
}
}
}
#[frame::pallet(dev_mode)]
pub mod pallet_v2 {
use super::pallet::Balance;
use frame::prelude::*;
#[docify::export(config_v2)]
#[pallet::config]
pub trait Config: frame_system::Config {
/// The overarching event type of the runtime.
type RuntimeEvent: From<Event<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeEvent>
+ TryInto<Event<Self>>;
}
#[pallet::pallet]
pub struct Pallet<T>(_);
#[pallet::storage]
pub type Balances<T: Config> = StorageMap<_, _, T::AccountId, Balance>;
#[pallet::storage]
pub type TotalIssuance<T: Config> = StorageValue<_, Balance>;
#[docify::export]
#[pallet::error]
pub enum Error<T> {
/// Account does not exist.
NonExistentAccount,
/// Account does not have enough balance.
InsufficientBalance,
}
#[docify::export]
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// A transfer succeeded.
Transferred { from: T::AccountId, to: T::AccountId, amount: Balance },
}
#[pallet::call]
impl<T: Config> Pallet<T> {
#[docify::export(transfer_v2)]
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(())
}
}
#[cfg(any(test, doc))]
pub mod tests {
use super::{super::pallet::tests::StateBuilder, *};
use frame::testing_prelude::*;
const ALICE: u64 = 1;
const BOB: u64 = 2;
#[docify::export]
pub mod runtime_v2 {
use super::*;
use crate::guides::your_first_pallet::pallet_v2 as pallet_currency;
construct_runtime!(
pub enum Runtime {
System: frame_system,
Currency: pallet_currency,
}
);
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Runtime {
type Block = MockBlock<Runtime>;
type AccountId = u64;
}
impl pallet_currency::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
}
}
pub(crate) use runtime_v2::*;
#[docify::export(transfer_works_v2)]
#[test]
fn transfer_works() {
StateBuilder::default().build_and_execute(|| {
// skip the genesis block, as events are not deposited there and we need them for
// the final assertion.
System::set_block_number(ALICE);
// given the initial state, when:
assert_ok!(Pallet::<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));
// now we can also check that an event has been deposited:
assert_eq!(
System::read_events_for_pallet::<Event<Runtime>>(),
vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }]
);
});
}
}
}