diff --git a/src/0/source/changes.diff b/src/0/source/changes.diff index 3caeddca..ac08edd8 100644 --- a/src/0/source/changes.diff +++ b/src/0/source/changes.diff @@ -1,6 +1,6 @@ diff --git a/.gitignore b/.gitignore new file mode 100644 -index 0000000..c537f0b +index 00000000..c537f0b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ diff --git a/src/1/source/changes.diff b/src/1/source/changes.diff index a86e756b..52920272 100644 --- a/src/1/source/changes.diff +++ b/src/1/source/changes.diff @@ -1,6 +1,6 @@ diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 -index 0000000..31cbe7d +index 00000000..31cbe7df --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3624 @@ @@ -3630,7 +3630,7 @@ index 0000000..31cbe7d +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 -index 0000000..293d4f2 +index 00000000..293d4f2a --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,26 @@ @@ -3662,7 +3662,7 @@ index 0000000..293d4f2 +try-runtime = [] diff --git a/rustfmt.toml b/rustfmt.toml new file mode 100644 -index 0000000..cf9aa25 +index 00000000..cf9aa251 --- /dev/null +++ b/rustfmt.toml @@ -0,0 +1,24 @@ @@ -3692,7 +3692,7 @@ index 0000000..cf9aa25 +wrap_comments = true diff --git a/src/impls.rs b/src/impls.rs new file mode 100644 -index 0000000..03abf99 +index 00000000..03abf99e --- /dev/null +++ b/src/impls.rs @@ -0,0 +1,9 @@ @@ -3707,7 +3707,7 @@ index 0000000..03abf99 +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 -index 0000000..ae8a09b +index 00000000..ae8a09bc --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,38 @@ @@ -3751,10 +3751,10 @@ index 0000000..ae8a09b +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 -index 0000000..6d271d5 +index 00000000..7e6f34da --- /dev/null +++ b/src/tests.rs -@@ -0,0 +1,117 @@ +@@ -0,0 +1,115 @@ +// Tests for the Kitties Pallet. +// +// Normally this file would be split into two parts: `mock.rs` and `tests.rs`. @@ -3806,15 +3806,15 @@ index 0000000..6d271d5 + + /// System: Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] -+ pub type System = frame_system::Pallet; ++ pub type System = frame_system::Pallet; + + /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) + #[runtime::pallet_index(1)] -+ pub type PalletBalances = pallet_balances::Pallet; ++ pub type PalletBalances = pallet_balances::Pallet; + + /// PalletKitties: The pallet you are building in this tutorial! + #[runtime::pallet_index(2)] -+ pub type PalletKitties = pallet_kitties::Pallet; ++ pub type PalletKitties = pallet_kitties::Pallet; +} + +// Normally `System` would have many more configurations, but you can see that we use some macro @@ -3865,8 +3865,6 @@ index 0000000..6d271d5 +fn system_and_balances_work() { + // This test will just sanity check that we can access `System` and `PalletBalances`. + new_test_ext().execute_with(|| { -+ // We often need to set `System` to block 1 so that we can see events. -+ System::set_block_number(1); + // We often need to add some balance to a user to test features which needs tokens. + assert_ok!(PalletBalances::mint_into(&ALICE, 100)); + assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/1/source/src/tests.rs b/src/1/source/src/tests.rs index 6d271d5e..7e6f34da 100644 --- a/src/1/source/src/tests.rs +++ b/src/1/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/10/README.md b/src/10/README.md index ddb222a4..df81bc46 100644 --- a/src/10/README.md +++ b/src/10/README.md @@ -1,38 +1,6 @@ -
-
+
{{#include ./source/README.md}}
-
- -
- - -
- -
- -No files edited in this step. - -
- -
- - -
- -
-
- -```diff -{{#include ./source/changes.diff}} -``` - -
- -
- -
-
diff --git a/src/10/source/README.md b/src/10/source/README.md index f40e746a..4bbf4a56 100644 --- a/src/10/source/README.md +++ b/src/10/source/README.md @@ -1,64 +1,5 @@ -# Blockchain Storage +# Storage Basics -Blockchains use a Merkle Trie structure to store data. The Merkle Trie provides two important properties for blockchains: +Now that we have covered the basics of Pallets and gone through all of the template code, we can start writing some code ourselves. -1. Allows the whole database to be represented by a single fingerprint, which can easily be compared to other nodes. -2. Allows the creation of lightweight proofs, proving that specific data exists in the database. - -This comes at the cost of additional complexity reading and writing data to the blockchain. - -Let's learn about Merkle Tries in more detail. - -## Hash Functions - -Hash functions are an important tool throughout blockchain development. - -A hash function takes an arbitrary sized input and returns a fixed-size string of bytes. - -This output, usually called a hash, is unique to each unique input. Even a small change to the input creates a dramatic change to the output. - -Hash functions have several key properties: - -- Deterministic: The same input always produces the same output. -- Pre-image Resistant: It is difficult to derive the original input from its hash value. -- Collision Resistant: It’s hard to find two different inputs that produce the same hash output. - -These properties make hash functions key for ensuring data integrity and uniqueness in blockchain technology. - -## Hash Fingerprint - -Due to the properties of a Hash, it is often referred to as a fingerprint. - -For context, a 32-byte hash has 2^256 different possible outputs. This is nearly as many atoms as there are in the whole universe! - -This uniqueness property helps blockchain nodes come to consensus with one another. - -Rather than needing to compare all the data in their blockchain database with one another, they can simply share the hash of that database, and know in a single small comparison if all data in that database is the same. - -Remember, if there were any small differences between their databases, even just one bit in a multi-terabyte database being different, the resulting hash would dramatically change, and they would know their databases are not the same. - -## Merkle Trie - -A merkle trie is a data structure which is constructed using a hash function. - -Rather than hashing the whole database into a single hash, we create a tree of hashes. - -For example, we take pairs of data, combine them, then hash them to generate a new output. Then we take pairs of hashes, combine them, then hash them to generate another new output. - -We can repeat this process until we are left with a single hash called the "root hash". This process literally creates a tree of hashes. - -Just like before, we can use a single hash to represent the integrity of all data underneath it, but now we can efficiently represent specific pieces of data in the database using the path down the trie to that data. - -It is called a merkle "trie" because the trie data structure is used to reduce the amount of redundant data stored in the tree. - -### Complexity - -The reason we go into this much detail about merkle tries is that they increase the complexity in reading and writing to the blockchain database. - -Whereas reading and writing to a database could be considered `O(1)`, a merklized database has read and write complexity of `O(log N)`, where `N` is the total number of items stored in the database. - -This additional complexity means that designing storage for a blockchain is an extremely important and sensitive operation. - -The primary advantage of using a merkle trie is that proving specific data exists inside the database is much more efficient! Whereas you would normally need to share the whole database to prove that some data exists, with a merklized database, you only need to share `O(log N)` amount of data. This is very important to support light clients. - -In this next section, and throughout the tutorial, we will start to explore some of those decisions. +In this section you will learn the basics of creating and using storage in your Pallet, including creating and using storage values and storage maps. diff --git a/src/10/source/changes.diff b/src/10/source/changes.diff index e69de29b..3aca560e 100644 --- a/src/10/source/changes.diff +++ b/src/10/source/changes.diff @@ -0,0 +1,48 @@ +diff --git a/src/lib.rs b/src/lib.rs +index a52896ed..ae8a09bc 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -18,7 +18,6 @@ pub mod pallet { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + +- /* 🚧 TODO 🚧: Learn about Pallet Events. */ + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { +diff --git a/src/tests.rs b/src/tests.rs +index 434ce723..59889339 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -29,7 +29,6 @@ type Block = frame_system::mocking::MockBlock; + const ALICE: u64 = 1; + const BOB: u64 = 2; + +-/* 🚧 TODO 🚧: Learn about constructing a runtime. */ + #[runtime] + mod runtime { + #[runtime::derive( +@@ -61,7 +60,6 @@ mod runtime { + pub type PalletKitties = pallet_kitties::Pallet; + } + +-/* 🚧 TODO 🚧: Learn about configuring a pallet. */ + // Normally `System` would have many more configurations, but you can see that we use some macro + // magic to automatically configure most of the pallet for a "default test configuration". + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +@@ -84,7 +82,6 @@ impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + } + +-/* 🚧 TODO 🚧: Learn about test externalities. */ + // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` + // It simulates the blockchain database backend for our tests. + // If you forget to include this and try to access your Pallet storage, you will get an error like: +@@ -127,7 +124,6 @@ fn create_kitty_checks_signed() { + }) + } + +-/* 🚧 TODO 🚧: Learn about writing tests. */ + #[test] + fn create_kitty_emits_event() { + new_test_ext().execute_with(|| { diff --git a/src/10/source/src/tests.rs b/src/10/source/src/tests.rs index 8baa63c6..59889339 100644 --- a/src/10/source/src/tests.rs +++ b/src/10/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/11/README.md b/src/11/README.md index 54677c78..ddb222a4 100644 --- a/src/11/README.md +++ b/src/11/README.md @@ -2,58 +2,19 @@
-{{#include ./template/README.md}} +{{#include ./source/README.md}}
-
- - +
-
- -
- -
-
- -```rust -{{#include ./template/src/lib.rs}} -``` - -
- - - -
- -
- -
- - -
-
- -```rust -{{#include ./solution/src/lib.rs}} -``` - -
- -
- -```rust -{{#include ./solution/src/tests.rs}} -``` - -
- +
+No files edited in this step.
@@ -61,20 +22,12 @@
- - -
-
- -```diff -{{#include ./template/template.diff}} -``` - +
-
+
```diff -{{#include ./solution/solution.diff}} +{{#include ./source/changes.diff}} ```
diff --git a/src/11/solution/solution.diff b/src/11/solution/solution.diff deleted file mode 100644 index 9527cc68..00000000 --- a/src/11/solution/solution.diff +++ /dev/null @@ -1,40 +0,0 @@ -diff --git a/src/lib.rs b/src/lib.rs -index 7d88fff..90242f6 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,11 +18,8 @@ pub mod pallet { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - -- /* 🚧 TODO 🚧: -- - Create a new `StorageValue` named `CountForKitties`. -- - `CountForKitties` should be generic over ``. -- - Set `Value` to `u32` to store that type. -- */ -+ #[pallet::storage] -+ pub(super) type CountForKitties = StorageValue; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] -diff --git a/src/tests.rs b/src/tests.rs -index 8baa63c..6f68640 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -137,3 +137,17 @@ fn create_kitty_emits_event() { - System::assert_last_event(Event::::Created { owner: 1 }.into()); - }) - } -+ -+#[test] -+fn count_for_kitties_created_correctly() { -+ new_test_ext().execute_with(|| { -+ // Querying storage before anything is set will return `None`. -+ assert_eq!(CountForKitties::::get(), None); -+ // You can `set` the value using an `Option`. -+ CountForKitties::::set(Some(1337u32)); -+ // You can `put` the value directly with a `u32`. -+ CountForKitties::::put(1337u32); -+ // Check that the value is now in storage. -+ assert_eq!(CountForKitties::::get(), Some(1337u32)); -+ }) -+} diff --git a/src/11/solution/src/tests.rs b/src/11/solution/src/tests.rs deleted file mode 100644 index 6f686408..00000000 --- a/src/11/solution/src/tests.rs +++ /dev/null @@ -1,153 +0,0 @@ -// Tests for the Kitties Pallet. -// -// Normally this file would be split into two parts: `mock.rs` and `tests.rs`. -// The `mock.rs` file would contain all the setup code for our `TestRuntime`. -// Then `tests.rs` would only have the tests for our pallet. -// However, to minimize the project, these have been combined into this single file. -// -// Learn more about creating tests for Pallets: -// https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html - -// This flag tells rust to only run this file when running `cargo test`. -#![cfg(test)] - -use crate as pallet_kitties; -use crate::*; -use frame::deps::frame_support::runtime; -use frame::deps::sp_io; -use frame::runtime::prelude::*; -use frame::testing_prelude::*; -use frame::traits::fungible::*; - -type Balance = u64; -type Block = frame_system::mocking::MockBlock; - -// In our "test runtime", we represent a user `AccountId` with a `u64`. -// This is just a simplification so that we don't need to generate a bunch of proper cryptographic -// public keys when writing tests. It is just easier to say "user 1 transfers to user 2". -// We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. -const ALICE: u64 = 1; -const BOB: u64 = 2; - -#[runtime] -mod runtime { - #[runtime::derive( - RuntimeCall, - RuntimeEvent, - RuntimeError, - RuntimeOrigin, - RuntimeTask, - RuntimeHoldReason, - RuntimeFreezeReason - )] - #[runtime::runtime] - /// The "test runtime" that represents the state transition function for our blockchain. - /// - /// The runtime is composed of individual modules called "pallets", which you find see below. - /// Each pallet has its own logic and storage, all of which can be combined together. - pub struct TestRuntime; - - /// System: Mandatory system pallet that should always be included in a FRAME runtime. - #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; - - /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) - #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; - - /// PalletKitties: The pallet you are building in this tutorial! - #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; -} - -// Normally `System` would have many more configurations, but you can see that we use some macro -// magic to automatically configure most of the pallet for a "default test configuration". -#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] -impl frame_system::Config for TestRuntime { - type Block = Block; - type AccountData = pallet_balances::AccountData; -} - -// Normally `pallet_balances` would have many more configurations, but you can see that we use some -// macro magic to automatically configure most of the pallet for a "default test configuration". -#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] -impl pallet_balances::Config for TestRuntime { - type AccountStore = System; - type Balance = Balance; -} - -// This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you -// will also need to update this configuration to represent that. -impl pallet_kitties::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; -} - -// We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` -// It simulates the blockchain database backend for our tests. -// If you forget to include this and try to access your Pallet storage, you will get an error like: -// "`get_version_1` called outside of an Externalities-provided environment." -pub fn new_test_ext() -> sp_io::TestExternalities { - frame_system::GenesisConfig::::default() - .build_storage() - .unwrap() - .into() -} - -#[test] -fn starting_template_is_sane() { - new_test_ext().execute_with(|| { - let event = Event::::Created { owner: ALICE }; - let _runtime_event: RuntimeEvent = event.into(); - let _call = Call::::create_kitty {}; - let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); - assert_ok!(result); - }); -} - -#[test] -fn system_and_balances_work() { - // This test will just sanity check that we can access `System` and `PalletBalances`. - new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); - // We often need to add some balance to a user to test features which needs tokens. - assert_ok!(PalletBalances::mint_into(&ALICE, 100)); - assert_ok!(PalletBalances::mint_into(&BOB, 100)); - }); -} - -#[test] -fn create_kitty_checks_signed() { - new_test_ext().execute_with(|| { - // The `create_kitty` extrinsic should work when being called by a user. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // The `create_kitty` extrinsic should fail when being called by an unsigned message. - assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); - }) -} - -#[test] -fn create_kitty_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - // Execute our call, and ensure it is successful. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Assert the last event by our blockchain is the `Created` event with the correct owner. - System::assert_last_event(Event::::Created { owner: 1 }.into()); - }) -} - -#[test] -fn count_for_kitties_created_correctly() { - new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `None`. - assert_eq!(CountForKitties::::get(), None); - // You can `set` the value using an `Option`. - CountForKitties::::set(Some(1337u32)); - // You can `put` the value directly with a `u32`. - CountForKitties::::put(1337u32); - // Check that the value is now in storage. - assert_eq!(CountForKitties::::get(), Some(1337u32)); - }) -} diff --git a/src/11/solution/.gitignore b/src/11/source/.gitignore similarity index 100% rename from src/11/solution/.gitignore rename to src/11/source/.gitignore diff --git a/src/11/solution/Cargo.lock b/src/11/source/Cargo.lock similarity index 100% rename from src/11/solution/Cargo.lock rename to src/11/source/Cargo.lock diff --git a/src/11/solution/Cargo.toml b/src/11/source/Cargo.toml similarity index 100% rename from src/11/solution/Cargo.toml rename to src/11/source/Cargo.toml diff --git a/src/11/source/README.md b/src/11/source/README.md new file mode 100644 index 00000000..f40e746a --- /dev/null +++ b/src/11/source/README.md @@ -0,0 +1,64 @@ +# Blockchain Storage + +Blockchains use a Merkle Trie structure to store data. The Merkle Trie provides two important properties for blockchains: + +1. Allows the whole database to be represented by a single fingerprint, which can easily be compared to other nodes. +2. Allows the creation of lightweight proofs, proving that specific data exists in the database. + +This comes at the cost of additional complexity reading and writing data to the blockchain. + +Let's learn about Merkle Tries in more detail. + +## Hash Functions + +Hash functions are an important tool throughout blockchain development. + +A hash function takes an arbitrary sized input and returns a fixed-size string of bytes. + +This output, usually called a hash, is unique to each unique input. Even a small change to the input creates a dramatic change to the output. + +Hash functions have several key properties: + +- Deterministic: The same input always produces the same output. +- Pre-image Resistant: It is difficult to derive the original input from its hash value. +- Collision Resistant: It’s hard to find two different inputs that produce the same hash output. + +These properties make hash functions key for ensuring data integrity and uniqueness in blockchain technology. + +## Hash Fingerprint + +Due to the properties of a Hash, it is often referred to as a fingerprint. + +For context, a 32-byte hash has 2^256 different possible outputs. This is nearly as many atoms as there are in the whole universe! + +This uniqueness property helps blockchain nodes come to consensus with one another. + +Rather than needing to compare all the data in their blockchain database with one another, they can simply share the hash of that database, and know in a single small comparison if all data in that database is the same. + +Remember, if there were any small differences between their databases, even just one bit in a multi-terabyte database being different, the resulting hash would dramatically change, and they would know their databases are not the same. + +## Merkle Trie + +A merkle trie is a data structure which is constructed using a hash function. + +Rather than hashing the whole database into a single hash, we create a tree of hashes. + +For example, we take pairs of data, combine them, then hash them to generate a new output. Then we take pairs of hashes, combine them, then hash them to generate another new output. + +We can repeat this process until we are left with a single hash called the "root hash". This process literally creates a tree of hashes. + +Just like before, we can use a single hash to represent the integrity of all data underneath it, but now we can efficiently represent specific pieces of data in the database using the path down the trie to that data. + +It is called a merkle "trie" because the trie data structure is used to reduce the amount of redundant data stored in the tree. + +### Complexity + +The reason we go into this much detail about merkle tries is that they increase the complexity in reading and writing to the blockchain database. + +Whereas reading and writing to a database could be considered `O(1)`, a merklized database has read and write complexity of `O(log N)`, where `N` is the total number of items stored in the database. + +This additional complexity means that designing storage for a blockchain is an extremely important and sensitive operation. + +The primary advantage of using a merkle trie is that proving specific data exists inside the database is much more efficient! Whereas you would normally need to share the whole database to prove that some data exists, with a merklized database, you only need to share `O(log N)` amount of data. This is very important to support light clients. + +In this next section, and throughout the tutorial, we will start to explore some of those decisions. diff --git a/src/18/source/changes.diff b/src/11/source/changes.diff similarity index 100% rename from src/18/source/changes.diff rename to src/11/source/changes.diff diff --git a/src/11/solution/rustfmt.toml b/src/11/source/rustfmt.toml similarity index 100% rename from src/11/solution/rustfmt.toml rename to src/11/source/rustfmt.toml diff --git a/src/11/solution/src/impls.rs b/src/11/source/src/impls.rs similarity index 100% rename from src/11/solution/src/impls.rs rename to src/11/source/src/impls.rs diff --git a/src/11/solution/src/lib.rs b/src/11/source/src/lib.rs similarity index 89% rename from src/11/solution/src/lib.rs rename to src/11/source/src/lib.rs index 90242f63..ae8a09bc 100644 --- a/src/11/solution/src/lib.rs +++ b/src/11/source/src/lib.rs @@ -18,9 +18,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::storage] - pub(super) type CountForKitties = StorageValue; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/src/11/template/src/tests.rs b/src/11/source/src/tests.rs similarity index 94% rename from src/11/template/src/tests.rs rename to src/11/source/src/tests.rs index 8baa63c6..59889339 100644 --- a/src/11/template/src/tests.rs +++ b/src/11/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/11/template/README.md b/src/11/template/README.md deleted file mode 100644 index 22b856b5..00000000 --- a/src/11/template/README.md +++ /dev/null @@ -1,28 +0,0 @@ -# Storage Values - -The most basic storage type for a blockchain is a single `StorageValue`. - -A `StorageValue` is used to place a single object into the blockchain storage. - -A single object can be as simple as a single type like a `u32`, or more complex structures, or even vectors. - -What is most important to understand is that a `StorageValue` places a single entry into the merkle trie. So when you read data, you read all of it. When you write data, you write all of it. This is in contrast to a `StorageMap`, which you will learn about next. - -## Construction - -We constructed a simple `StorageValue` for you in the code, but let's break it down: - -```rust -#[pallet::storage] -pub(super) type CountForKitties = StorageValue; -``` - -As you can see, our storage is a type alias for a new instance of `StorageValue`. - -Our storage value has a parameter `Value` where we can define the type we want to place in storage. In this case, it is a simple `u32`. - -You will also notice `CountForKitties` is generic over ``. All of our storage must be generic over `` even if we are not using it directly. Macros use this generic parameter to fill in behind the scene details to make the `StorageValue` work. Think about all the code behind the scenes which actually sticks this storage into a merkle trie in the database of our blockchain. - -Visibility of the type is up to you and your needs, but you need to remember that blockchains are public databases. So `pub` in this case is only about Rust, and allowing other modules to access this storage and its APIs directly. - -You cannot make storage on a blockchain "private", and even if you make this storage without `pub`, there are low level ways to manipulate the storage in the database. diff --git a/src/11/template/src/impls.rs b/src/11/template/src/impls.rs deleted file mode 100644 index 03abf99e..00000000 --- a/src/11/template/src/impls.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::*; -use frame::prelude::*; - -impl Pallet { - pub fn mint(owner: T::AccountId) -> DispatchResult { - Self::deposit_event(Event::::Created { owner }); - Ok(()) - } -} diff --git a/src/11/template/src/lib.rs b/src/11/template/src/lib.rs deleted file mode 100644 index 7d88fffb..00000000 --- a/src/11/template/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] - -mod impls; -mod tests; - -use frame::prelude::*; -pub use pallet::*; - -#[frame::pallet(dev_mode)] -pub mod pallet { - use super::*; - - #[pallet::pallet] - pub struct Pallet(core::marker::PhantomData); - - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - - /* 🚧 TODO 🚧: - - Create a new `StorageValue` named `CountForKitties`. - - `CountForKitties` should be generic over ``. - - Set `Value` to `u32` to store that type. - */ - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Created { owner: T::AccountId }, - } - - #[pallet::error] - pub enum Error {} - - #[pallet::call] - impl Pallet { - pub fn create_kitty(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::mint(who)?; - Ok(()) - } - } -} diff --git a/src/11/template/template.diff b/src/11/template/template.diff deleted file mode 100644 index 204909ac..00000000 --- a/src/11/template/template.diff +++ /dev/null @@ -1,17 +0,0 @@ -diff --git a/src/lib.rs b/src/lib.rs -index ae8a09b..7d88fff 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,6 +18,12 @@ pub mod pallet { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - -+ /* 🚧 TODO 🚧: -+ - Create a new `StorageValue` named `CountForKitties`. -+ - `CountForKitties` should be generic over ``. -+ - Set `Value` to `u32` to store that type. -+ */ -+ - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { diff --git a/src/12/README.md b/src/12/README.md index a634c256..54677c78 100644 --- a/src/12/README.md +++ b/src/12/README.md @@ -17,12 +17,12 @@
- +
-
+
```rust -{{#include ./template/src/impls.rs}} +{{#include ./template/src/lib.rs}} ```
@@ -34,13 +34,13 @@
- +
-
+
```rust -{{#include ./solution/src/impls.rs}} +{{#include ./solution/src/lib.rs}} ```
diff --git a/src/12/solution/solution.diff b/src/12/solution/solution.diff index e552bb59..c0329ff5 100644 --- a/src/12/solution/solution.diff +++ b/src/12/solution/solution.diff @@ -1,40 +1,40 @@ -diff --git a/src/impls.rs b/src/impls.rs -index b396f98..9739330 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -3,12 +3,9 @@ use frame::prelude::*; - - impl Pallet { - pub fn mint(owner: T::AccountId) -> DispatchResult { -- /* 🚧 TODO 🚧: -- - `get` the `current_count` of kitties. -- - `unwrap_or` set the count to `0`. -- - Create `new_count` by adding one to the `current_count`. -- - `set` the `new_count` of kitties. -- */ -+ let current_count: u32 = CountForKitties::::get().unwrap_or(0); -+ let new_count = current_count + 1; -+ CountForKitties::::set(Some(new_count)); - Self::deposit_event(Event::::Created { owner }); - Ok(()) +diff --git a/src/lib.rs b/src/lib.rs +index 7d88fffb..90242f63 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -18,11 +18,8 @@ pub mod pallet { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + +- /* 🚧 TODO 🚧: +- - Create a new `StorageValue` named `CountForKitties`. +- - `CountForKitties` should be generic over ``. +- - Set `Value` to `u32` to store that type. +- */ ++ #[pallet::storage] ++ pub(super) type CountForKitties = StorageValue; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/tests.rs b/src/tests.rs -index 6f68640..a396679 100644 +index 59889339..9ceddfbb 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -151,3 +151,15 @@ fn count_for_kitties_created_correctly() { - assert_eq!(CountForKitties::::get(), Some(1337u32)); +@@ -135,3 +135,17 @@ fn create_kitty_emits_event() { + System::assert_last_event(Event::::Created { owner: 1 }.into()); }) } + +#[test] -+fn mint_increments_count_for_kitty() { ++fn count_for_kitties_created_correctly() { + new_test_ext().execute_with(|| { + // Querying storage before anything is set will return `None`. + assert_eq!(CountForKitties::::get(), None); -+ // Call `create_kitty` which will call `mint`. -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ // Now the storage should be `Some(1)` -+ assert_eq!(CountForKitties::::get(), Some(1)); ++ // You can `set` the value using an `Option`. ++ CountForKitties::::set(Some(1337u32)); ++ // You can `put` the value directly with a `u32`. ++ CountForKitties::::put(1337u32); ++ // Check that the value is now in storage. ++ assert_eq!(CountForKitties::::get(), Some(1337u32)); + }) +} diff --git a/src/12/solution/src/impls.rs b/src/12/solution/src/impls.rs index 97393307..03abf99e 100644 --- a/src/12/solution/src/impls.rs +++ b/src/12/solution/src/impls.rs @@ -3,9 +3,6 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get().unwrap_or(0); - let new_count = current_count + 1; - CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/12/solution/src/tests.rs b/src/12/solution/src/tests.rs index a3966795..9ceddfbb 100644 --- a/src/12/solution/src/tests.rs +++ b/src/12/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -151,15 +149,3 @@ fn count_for_kitties_created_correctly() { assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } - -#[test] -fn mint_increments_count_for_kitty() { - new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `None`. - assert_eq!(CountForKitties::::get(), None); - // Call `create_kitty` which will call `mint`. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now the storage should be `Some(1)` - assert_eq!(CountForKitties::::get(), Some(1)); - }) -} diff --git a/src/12/template/README.md b/src/12/template/README.md index 4745e19d..22b856b5 100644 --- a/src/12/template/README.md +++ b/src/12/template/README.md @@ -1,51 +1,28 @@ -# Kitty Counter +# Storage Values -Let's now learn how to use our new `StorageValue`. +The most basic storage type for a blockchain is a single `StorageValue`. -## Basic APIs +A `StorageValue` is used to place a single object into the blockchain storage. -This tutorial will only go over just the basic APIs needed to build our Pallet. +A single object can be as simple as a single type like a `u32`, or more complex structures, or even vectors. -Check out the [`StorageValue` documentation](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageValue.html) if you want to see the full APIs. +What is most important to understand is that a `StorageValue` places a single entry into the merkle trie. So when you read data, you read all of it. When you write data, you write all of it. This is in contrast to a `StorageMap`, which you will learn about next. -### Reading Storage +## Construction -To read the current value of a `StorageValue`, you can simply call the `get` API: +We constructed a simple `StorageValue` for you in the code, but let's break it down: ```rust -let maybe_count: Option = CountForKitties::::get(); +#[pallet::storage] +pub(super) type CountForKitties = StorageValue; ``` -A few things to note here. +As you can see, our storage is a type alias for a new instance of `StorageValue`. -The most obvious one is that `get` returns an `Option`, rather than the type itself. +Our storage value has a parameter `Value` where we can define the type we want to place in storage. In this case, it is a simple `u32`. -In fact, all storage in a blockchain is an `Option`: either there is some data in the database or there isn't. +You will also notice `CountForKitties` is generic over ``. All of our storage must be generic over `` even if we are not using it directly. Macros use this generic parameter to fill in behind the scene details to make the `StorageValue` work. Think about all the code behind the scenes which actually sticks this storage into a merkle trie in the database of our blockchain. -In this context, when there is no value in storage for the `CountForKitties`, we probably mean that the `CountForKitties` is zero. +Visibility of the type is up to you and your needs, but you need to remember that blockchains are public databases. So `pub` in this case is only about Rust, and allowing other modules to access this storage and its APIs directly. -So we can write the following to handle this ergonomically: - -```rust -let current_count: u32 = CountForKitties::::get().unwrap_or(0); -``` - -Now, whenever `CountForKitties` returns `Some(count)`, we will simply unwrap that count and directly access the `u32`. If it returns `None`, we will simply return `0u32` instead. - -The other thing to note is the generic `` that we need to include. You better get used to this, we will be using `` everywhere! But remember, in our definition of `CountForKitties`, it was a type generic over ``, and thus we need to include `` to access any of the APIs. - -### Writing Storage - -To set the current value of a `StorageValue`, you can simply call the `set` API: - -```rust -CountForKitties::::set(Some(1u32)); -``` - -This storage API cannot fail, so there is no error handling needed. You just set the value directly in storage. Note that `set` will also happily replace any existing value there, so you will need to use other APIs like `exists` or `get` to check if a value is already in storage. - -If you `set` the storage to `None`, it is the same as deleting the storage item. - -## Your Turn - -Now that you know the basics of reading and writing to storage, add the logic needed to increment the `CountForKitties` storage whenever we call `mint`. +You cannot make storage on a blockchain "private", and even if you make this storage without `pub`, there are low level ways to manipulate the storage in the database. diff --git a/src/12/template/src/impls.rs b/src/12/template/src/impls.rs index b396f986..03abf99e 100644 --- a/src/12/template/src/impls.rs +++ b/src/12/template/src/impls.rs @@ -3,12 +3,6 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - /* 🚧 TODO 🚧: - - `get` the `current_count` of kitties. - - `unwrap_or` set the count to `0`. - - Create `new_count` by adding one to the `current_count`. - - `set` the `new_count` of kitties. - */ Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/12/template/src/lib.rs b/src/12/template/src/lib.rs index 90242f63..7d88fffb 100644 --- a/src/12/template/src/lib.rs +++ b/src/12/template/src/lib.rs @@ -18,8 +18,11 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[pallet::storage] - pub(super) type CountForKitties = StorageValue; + /* 🚧 TODO 🚧: + - Create a new `StorageValue` named `CountForKitties`. + - `CountForKitties` should be generic over ``. + - Set `Value` to `u32` to store that type. + */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/12/template/src/tests.rs b/src/12/template/src/tests.rs index 6f686408..59889339 100644 --- a/src/12/template/src/tests.rs +++ b/src/12/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -137,17 +135,3 @@ fn create_kitty_emits_event() { System::assert_last_event(Event::::Created { owner: 1 }.into()); }) } - -#[test] -fn count_for_kitties_created_correctly() { - new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `None`. - assert_eq!(CountForKitties::::get(), None); - // You can `set` the value using an `Option`. - CountForKitties::::set(Some(1337u32)); - // You can `put` the value directly with a `u32`. - CountForKitties::::put(1337u32); - // Check that the value is now in storage. - assert_eq!(CountForKitties::::get(), Some(1337u32)); - }) -} diff --git a/src/12/template/template.diff b/src/12/template/template.diff index dfabb73a..2e05d69b 100644 --- a/src/12/template/template.diff +++ b/src/12/template/template.diff @@ -1,17 +1,17 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 03abf99..b396f98 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -3,6 +3,12 @@ use frame::prelude::*; - - impl Pallet { - pub fn mint(owner: T::AccountId) -> DispatchResult { -+ /* 🚧 TODO 🚧: -+ - `get` the `current_count` of kitties. -+ - `unwrap_or` set the count to `0`. -+ - Create `new_count` by adding one to the `current_count`. -+ - `set` the `new_count` of kitties. -+ */ - Self::deposit_event(Event::::Created { owner }); - Ok(()) +diff --git a/src/lib.rs b/src/lib.rs +index ae8a09bc..7d88fffb 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -18,6 +18,12 @@ pub mod pallet { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + ++ /* 🚧 TODO 🚧: ++ - Create a new `StorageValue` named `CountForKitties`. ++ - `CountForKitties` should be generic over ``. ++ - Set `Value` to `u32` to store that type. ++ */ ++ + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { diff --git a/src/13/README.md b/src/13/README.md index eb9560d4..a634c256 100644 --- a/src/13/README.md +++ b/src/13/README.md @@ -18,7 +18,6 @@
-
@@ -28,14 +27,6 @@
-
- -```rust -{{#include ./template/src/lib.rs}} -``` - -
-
@@ -44,7 +35,6 @@
-
@@ -55,14 +45,6 @@
-
- -```rust -{{#include ./solution/src/lib.rs}} -``` - -
-
```rust diff --git a/src/13/solution/solution.diff b/src/13/solution/solution.diff index b607d50d..104e349c 100644 --- a/src/13/solution/solution.diff +++ b/src/13/solution/solution.diff @@ -1,50 +1,40 @@ diff --git a/src/impls.rs b/src/impls.rs -index c550bc8..7277e36 100644 +index b396f986..97393307 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -4,8 +4,7 @@ use frame::prelude::*; +@@ -3,12 +3,9 @@ use frame::prelude::*; + impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get().unwrap_or(0); -- /* 🚧 TODO 🚧: Update this logic to use safe math. */ -- let new_count = current_count + 1; -+ let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - CountForKitties::::set(Some(new_count)); - Self::deposit_event(Event::::Created { owner }); - Ok(()) -diff --git a/src/lib.rs b/src/lib.rs -index 8edcc9b..2c11f46 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -29,9 +29,7 @@ pub mod pallet { - - #[pallet::error] - pub enum Error { - /* 🚧 TODO 🚧: -- - Introduce a new error `TooManyKitties`. +- - `get` the `current_count` of kitties. +- - `unwrap_or` set the count to `0`. +- - Create `new_count` by adding one to the `current_count`. +- - `set` the `new_count` of kitties. - */ -+ TooManyKitties, ++ let current_count: u32 = CountForKitties::::get().unwrap_or(0); ++ let new_count = current_count + 1; ++ CountForKitties::::set(Some(new_count)); + Self::deposit_event(Event::::Created { owner }); + Ok(()) } - - #[pallet::call] diff --git a/src/tests.rs b/src/tests.rs -index a396679..8701a36 100644 +index 9ceddfbb..2b579ffc 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -163,3 +163,16 @@ fn mint_increments_count_for_kitty() { - assert_eq!(CountForKitties::::get(), Some(1)); +@@ -149,3 +149,15 @@ fn count_for_kitties_created_correctly() { + assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } + +#[test] -+fn mint_errors_when_overflow() { ++fn mint_increments_count_for_kitty() { + new_test_ext().execute_with(|| { -+ // Set the count to the largest value possible. -+ CountForKitties::::set(Some(u32::MAX)); -+ // `create_kitty` should not succeed because of safe math. -+ assert_noop!( -+ PalletKitties::create_kitty(RuntimeOrigin::signed(1)), -+ Error::::TooManyKitties -+ ); ++ // Querying storage before anything is set will return `None`. ++ assert_eq!(CountForKitties::::get(), None); ++ // Call `create_kitty` which will call `mint`. ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ // Now the storage should be `Some(1)` ++ assert_eq!(CountForKitties::::get(), Some(1)); + }) +} diff --git a/src/13/solution/src/impls.rs b/src/13/solution/src/impls.rs index 7277e367..97393307 100644 --- a/src/13/solution/src/impls.rs +++ b/src/13/solution/src/impls.rs @@ -4,7 +4,7 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { let current_count: u32 = CountForKitties::::get().unwrap_or(0); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + let new_count = current_count + 1; CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/13/solution/src/lib.rs b/src/13/solution/src/lib.rs index 2c11f460..90242f63 100644 --- a/src/13/solution/src/lib.rs +++ b/src/13/solution/src/lib.rs @@ -28,9 +28,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { - TooManyKitties, - } + pub enum Error {} #[pallet::call] impl Pallet { diff --git a/src/13/solution/src/tests.rs b/src/13/solution/src/tests.rs index 8701a366..2b579ffc 100644 --- a/src/13/solution/src/tests.rs +++ b/src/13/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -163,16 +161,3 @@ fn mint_increments_count_for_kitty() { assert_eq!(CountForKitties::::get(), Some(1)); }) } - -#[test] -fn mint_errors_when_overflow() { - new_test_ext().execute_with(|| { - // Set the count to the largest value possible. - CountForKitties::::set(Some(u32::MAX)); - // `create_kitty` should not succeed because of safe math. - assert_noop!( - PalletKitties::create_kitty(RuntimeOrigin::signed(1)), - Error::::TooManyKitties - ); - }) -} diff --git a/src/13/template/README.md b/src/13/template/README.md index 316589e6..4745e19d 100644 --- a/src/13/template/README.md +++ b/src/13/template/README.md @@ -1,141 +1,51 @@ -# Safety First +# Kitty Counter -If you look into the history of "hacks" and "bugs" that happen in the blockchain world, a lot of it is associated with some kind of "unsafe" code. +Let's now learn how to use our new `StorageValue`. -We need to keep our blockchain logic safe, and Rust is designed to handle it well. +## Basic APIs -## Errors +This tutorial will only go over just the basic APIs needed to build our Pallet. -When talking about handling safe math, we will start to introduce and use errors. +Check out the [`StorageValue` documentation](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageValue.html) if you want to see the full APIs. -### Do Not Panic! +### Reading Storage -If there is only one thing you remember after this whole tutorial, it should be this fact: - -**You cannot panic inside the runtime.** - -As a runtime developer, you are building logic in the low level parts of your blockchain. - -A smart contract system must be able to handle malicious developers, but this comes at a performance cost. - -When you program directly in the runtime, you get the highest performance possible, but you are also expected to be a competent developer and a good actor. - -In short, if you introduce a panic in your code, you make your blockchain vulnerable to DDoS attacks. - -But there is no reason you would ever need to panic because Rust has a great error handling system that we take advantage of in FRAME. - -### Pallet Errors - -All of our callable functions use the `DispatchResult` type. This means that we can always propagate up any errors that our Pallet runs into, and handle them properly, versus needing to panic. - -The [`DispatchResult`](https://docs.rs/frame-support/38.0.0/frame_support/dispatch/type.DispatchResult.html) type expects either `Ok(())` or `Err(DispatchError)`. - -The [`DispatchError`](https://docs.rs/frame-support/38.0.0/frame_support/pallet_prelude/enum.DispatchError.html) type has a few variants that you can easily construct / use. - -For example, if you want to be a little lazy, you can simply return a `&'static str`: - -```rust -fn always_error() -> DispatchResult { - return Err("this function always errors".into()) -} -``` - -But the better option is to return a custom Pallet Error: - -```rust -fn custom_error() -> DispatchResult { - return Err(Error::::CustomPalletError.into()) -} -``` - -Notice in both of these cases we had to call `into()` to convert our input type into the `DispatchError` type. - -To create `CustomPalletError` or whatever error you want, you simply add a new variants to the `enum Error` type. +To read the current value of a `StorageValue`, you can simply call the `get` API: ```rust -#[pallet::error] -pub enum Error { - /// This is a description for the error. - /// - /// This description can be shown to the user in UIs, so make it descriptive. - CustomPalletError, -} +let maybe_count: Option = CountForKitties::::get(); ``` -We will show you the common ergonomic ways to use Pallet Errors going forward. +A few things to note here. -## Math +The most obvious one is that `get` returns an `Option`, rather than the type itself. -### Unsafe Math +In fact, all storage in a blockchain is an `Option`: either there is some data in the database or there isn't. -The basic math operators in Rust are **unsafe**. +In this context, when there is no value in storage for the `CountForKitties`, we probably mean that the `CountForKitties` is zero. -Imagine our `CountForKitties` was already at the limit of `u32::MAX`. What would happen if we tried to call `mint` one more time? - -We would get an overflow! - -In tests `u32::MAX + 1` will actually trigger a panic, but in a `release` build, this overflow will happen silently... - -And this would be really bad. Now our count would be back to 0, and if we had any logic which depended on this count being accurate, that logic would be broken. - -In blockchain systems, these can literally be billion dollar bugs, so let's look at how we can do math safely. - -### Checked Math - -The first choice for doing safe math is to use `checked_*` APIs, for example [`checked_add`](https://docs.rs/num/latest/num/trait.CheckedAdd.html). - -The checked math APIs will check if there are any underflows or overflows, and return `None` in those cases. Otherwise, if the math operation is calculated without error, it returns `Some(result)`. - -Here is a verbose way you could handle checked math in a Pallet: - -```rust -let final_result: u32 = match value_a.checked_add(value_b) { - Some(result) => result, - None => return Err(Error::::CustomPalletError.into()), -}; -``` - -You can see how we can directly assign the `u32` value to `final_result`, otherwise it will return an error. - -We can also do this as a one-liner, which is more ergonomic and preferred: +So we can write the following to handle this ergonomically: ```rust -let final_result: u32 = value_a.checked_add(value_b).ok_or(Error::::CustomPalletError)?; +let current_count: u32 = CountForKitties::::get().unwrap_or(0); ``` -This is exactly how you should be writing all the safe math inside your Pallet. - -Note that we didn't need to call `.into()` in this case, because `?` already does this! - -### Saturating Math +Now, whenever `CountForKitties` returns `Some(count)`, we will simply unwrap that count and directly access the `u32`. If it returns `None`, we will simply return `0u32` instead. -The other option for safe math is to use `saturating_*` APIs, for example [`saturating_add`](https://docs.rs/num/latest/num/traits/trait.SaturatingAdd.html). +The other thing to note is the generic `` that we need to include. You better get used to this, we will be using `` everywhere! But remember, in our definition of `CountForKitties`, it was a type generic over ``, and thus we need to include `` to access any of the APIs. -This option is useful because it is safe and does NOT return an `Option`. +### Writing Storage -Instead, it performs the math operations and keeps the value at the numerical limits, rather than overflowing. For example: +To set the current value of a `StorageValue`, you can simply call the `set` API: ```rust -let value_a: u32 = 1; -let value_b: u32 = u32::MAX; -let result: u32 = value_a.saturating_add(value_b); -assert!(result == u32::MAX); +CountForKitties::::set(Some(1u32)); ``` -This generally is NOT the preferred API to use because usually you want to handle situations where an overflow would occur. Overflows and underflows usually indicate something "bad" is happening. +This storage API cannot fail, so there is no error handling needed. You just set the value directly in storage. Note that `set` will also happily replace any existing value there, so you will need to use other APIs like `exists` or `get` to check if a value is already in storage. -However, there are times where you need to do math inside of functions where you cannot return a Result, and for that, saturating math might make sense. - -There are also times where you might want to perform the operation no matter that an underflow / overflow would occur. For example, imagine you made a function `slash` which slashes the balance of a malicious user. Your slash function may have some input parameter `amount` which says how much we should slash from the user. - -In a situation like this, it would make sense to use `saturating_sub` because we definitely want to slash as much as we can, even if we intended to slash more. The alternative would be returning an error, and not slashing anything! - -Anyway, every bone in your body should generally prefer to use the `checked_*` APIs, and handle all errors explicitly, but this is yet another tool in your pocket when it makes sense to use it. +If you `set` the storage to `None`, it is the same as deleting the storage item. ## Your Turn -We covered a lot in this section, but the concepts here are super important. - -Feel free to read this section again right now, and again at the end of the tutorial. - -Now that you know how to ergonomically do safe math, update your Pallet to handle the `mint` logic safely and return a custom Pallet Error if an overflow would occur. +Now that you know the basics of reading and writing to storage, add the logic needed to increment the `CountForKitties` storage whenever we call `mint`. diff --git a/src/13/template/src/impls.rs b/src/13/template/src/impls.rs index c550bc8d..b396f986 100644 --- a/src/13/template/src/impls.rs +++ b/src/13/template/src/impls.rs @@ -3,10 +3,12 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get().unwrap_or(0); - /* 🚧 TODO 🚧: Update this logic to use safe math. */ - let new_count = current_count + 1; - CountForKitties::::set(Some(new_count)); + /* 🚧 TODO 🚧: + - `get` the `current_count` of kitties. + - `unwrap_or` set the count to `0`. + - Create `new_count` by adding one to the `current_count`. + - `set` the `new_count` of kitties. + */ Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/13/template/src/lib.rs b/src/13/template/src/lib.rs index 8edcc9b7..90242f63 100644 --- a/src/13/template/src/lib.rs +++ b/src/13/template/src/lib.rs @@ -28,11 +28,7 @@ pub mod pallet { } #[pallet::error] - pub enum Error { - /* 🚧 TODO 🚧: - - Introduce a new error `TooManyKitties`. - */ - } + pub enum Error {} #[pallet::call] impl Pallet { diff --git a/src/13/template/src/tests.rs b/src/13/template/src/tests.rs index a3966795..9ceddfbb 100644 --- a/src/13/template/src/tests.rs +++ b/src/13/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -151,15 +149,3 @@ fn count_for_kitties_created_correctly() { assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } - -#[test] -fn mint_increments_count_for_kitty() { - new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `None`. - assert_eq!(CountForKitties::::get(), None); - // Call `create_kitty` which will call `mint`. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now the storage should be `Some(1)` - assert_eq!(CountForKitties::::get(), Some(1)); - }) -} diff --git a/src/13/template/template.diff b/src/13/template/template.diff index 250ddefd..f5375a21 100644 --- a/src/13/template/template.diff +++ b/src/13/template/template.diff @@ -1,29 +1,17 @@ diff --git a/src/impls.rs b/src/impls.rs -index 9739330..c550bc8 100644 +index 03abf99e..b396f986 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -4,6 +4,7 @@ use frame::prelude::*; +@@ -3,6 +3,12 @@ use frame::prelude::*; + impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get().unwrap_or(0); -+ /* 🚧 TODO 🚧: Update this logic to use safe math. */ - let new_count = current_count + 1; - CountForKitties::::set(Some(new_count)); - Self::deposit_event(Event::::Created { owner }); -diff --git a/src/lib.rs b/src/lib.rs -index 90242f6..8edcc9b 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -28,7 +28,11 @@ pub mod pallet { - } - - #[pallet::error] -- pub enum Error {} -+ pub enum Error { + /* 🚧 TODO 🚧: -+ - Introduce a new error `TooManyKitties`. ++ - `get` the `current_count` of kitties. ++ - `unwrap_or` set the count to `0`. ++ - Create `new_count` by adding one to the `current_count`. ++ - `set` the `new_count` of kitties. + */ -+ } - - #[pallet::call] - impl Pallet { + Self::deposit_event(Event::::Created { owner }); + Ok(()) + } diff --git a/src/14/solution/solution.diff b/src/14/solution/solution.diff index ee4d127f..bc83a6af 100644 --- a/src/14/solution/solution.diff +++ b/src/14/solution/solution.diff @@ -1,80 +1,50 @@ diff --git a/src/impls.rs b/src/impls.rs -index b9b5548..d283fd6 100644 +index c550bc8d..7277e367 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -3,11 +3,9 @@ use frame::prelude::*; - +@@ -4,8 +4,7 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { -- /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ -- let current_count: u32 = CountForKitties::::get().unwrap_or(0); -+ let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -- /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ -- CountForKitties::::set(Some(new_count)); -+ CountForKitties::::set(new_count); + let current_count: u32 = CountForKitties::::get().unwrap_or(0); +- /* 🚧 TODO 🚧: Update this logic to use safe math. */ +- let new_count = current_count + 1; ++ let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) - } diff --git a/src/lib.rs b/src/lib.rs -index eeb9724..57baa0d 100644 +index 8edcc9b7..2c11f460 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -19,8 +19,7 @@ pub mod pallet { - } +@@ -29,9 +29,7 @@ pub mod pallet { - #[pallet::storage] -- /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ -- pub(super) type CountForKitties = StorageValue; -+ pub(super) type CountForKitties = StorageValue; + #[pallet::error] + pub enum Error { +- /* 🚧 TODO 🚧: +- - Introduce a new error `TooManyKitties`. +- */ ++ TooManyKitties, + } - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] + #[pallet::call] diff --git a/src/tests.rs b/src/tests.rs -index 8701a36..4ec3538 100644 +index 2b579ffc..58df4d40 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -141,26 +141,24 @@ fn create_kitty_emits_event() { - #[test] - fn count_for_kitties_created_correctly() { - new_test_ext().execute_with(|| { -- // Querying storage before anything is set will return `None`. -- assert_eq!(CountForKitties::::get(), None); -- // You can `set` the value using an `Option`. -- CountForKitties::::set(Some(1337u32)); -+ // Querying storage before anything is set will return `0`. -+ assert_eq!(CountForKitties::::get(), 0); -+ // You can `set` the value using an `u32`. -+ CountForKitties::::set(1337u32); - // You can `put` the value directly with a `u32`. - CountForKitties::::put(1337u32); -- // Check that the value is now in storage. -- assert_eq!(CountForKitties::::get(), Some(1337u32)); +@@ -161,3 +161,16 @@ fn mint_increments_count_for_kitty() { + assert_eq!(CountForKitties::::get(), Some(1)); }) } - - #[test] - fn mint_increments_count_for_kitty() { - new_test_ext().execute_with(|| { -- // Querying storage before anything is set will return `None`. -- assert_eq!(CountForKitties::::get(), None); -+ // Querying storage before anything is set will return `0`. -+ assert_eq!(CountForKitties::::get(), 0); - // Call `create_kitty` which will call `mint`. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -- // Now the storage should be `Some(1)` -- assert_eq!(CountForKitties::::get(), Some(1)); -+ // Now the storage should be `1` -+ assert_eq!(CountForKitties::::get(), 1); - }) - } - -@@ -168,7 +166,7 @@ fn mint_increments_count_for_kitty() { - fn mint_errors_when_overflow() { - new_test_ext().execute_with(|| { - // Set the count to the largest value possible. -- CountForKitties::::set(Some(u32::MAX)); -+ CountForKitties::::set(u32::MAX); - // `create_kitty` should not succeed because of safe math. - assert_noop!( - PalletKitties::create_kitty(RuntimeOrigin::signed(1)), ++ ++#[test] ++fn mint_errors_when_overflow() { ++ new_test_ext().execute_with(|| { ++ // Set the count to the largest value possible. ++ CountForKitties::::set(Some(u32::MAX)); ++ // `create_kitty` should not succeed because of safe math. ++ assert_noop!( ++ PalletKitties::create_kitty(RuntimeOrigin::signed(1)), ++ Error::::TooManyKitties ++ ); ++ }) ++} diff --git a/src/14/solution/src/impls.rs b/src/14/solution/src/impls.rs index d283fd6b..7277e367 100644 --- a/src/14/solution/src/impls.rs +++ b/src/14/solution/src/impls.rs @@ -3,9 +3,9 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get(); + let current_count: u32 = CountForKitties::::get().unwrap_or(0); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - CountForKitties::::set(new_count); + CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/14/solution/src/lib.rs b/src/14/solution/src/lib.rs index 57baa0d1..2c11f460 100644 --- a/src/14/solution/src/lib.rs +++ b/src/14/solution/src/lib.rs @@ -19,7 +19,7 @@ pub mod pallet { } #[pallet::storage] - pub(super) type CountForKitties = StorageValue; + pub(super) type CountForKitties = StorageValue; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/14/solution/src/tests.rs b/src/14/solution/src/tests.rs index 4ec35382..58df4d40 100644 --- a/src/14/solution/src/tests.rs +++ b/src/14/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -141,24 +139,26 @@ fn create_kitty_emits_event() { #[test] fn count_for_kitties_created_correctly() { new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `0`. - assert_eq!(CountForKitties::::get(), 0); - // You can `set` the value using an `u32`. - CountForKitties::::set(1337u32); + // Querying storage before anything is set will return `None`. + assert_eq!(CountForKitties::::get(), None); + // You can `set` the value using an `Option`. + CountForKitties::::set(Some(1337u32)); // You can `put` the value directly with a `u32`. CountForKitties::::put(1337u32); + // Check that the value is now in storage. + assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } #[test] fn mint_increments_count_for_kitty() { new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `0`. - assert_eq!(CountForKitties::::get(), 0); + // Querying storage before anything is set will return `None`. + assert_eq!(CountForKitties::::get(), None); // Call `create_kitty` which will call `mint`. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now the storage should be `1` - assert_eq!(CountForKitties::::get(), 1); + // Now the storage should be `Some(1)` + assert_eq!(CountForKitties::::get(), Some(1)); }) } @@ -166,7 +166,7 @@ fn mint_increments_count_for_kitty() { fn mint_errors_when_overflow() { new_test_ext().execute_with(|| { // Set the count to the largest value possible. - CountForKitties::::set(u32::MAX); + CountForKitties::::set(Some(u32::MAX)); // `create_kitty` should not succeed because of safe math. assert_noop!( PalletKitties::create_kitty(RuntimeOrigin::signed(1)), diff --git a/src/14/template/README.md b/src/14/template/README.md index a7d593ee..316589e6 100644 --- a/src/14/template/README.md +++ b/src/14/template/README.md @@ -1,89 +1,141 @@ -# Value Query +# Safety First -When we originally introduced the `StorageValue`, we only exposed to you one field which you could manipulate, which is the `Value` type, which defines the type that will be stored. +If you look into the history of "hacks" and "bugs" that happen in the blockchain world, a lot of it is associated with some kind of "unsafe" code. -But there are actually more ways you can configure the `StorageValue` to increase developer ergonomics. +We need to keep our blockchain logic safe, and Rust is designed to handle it well. -## Storage Value Definition +## Errors -Let's look at the definition of a [`StorageValue`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageValue.html): +When talking about handling safe math, we will start to introduce and use errors. + +### Do Not Panic! + +If there is only one thing you remember after this whole tutorial, it should be this fact: + +**You cannot panic inside the runtime.** + +As a runtime developer, you are building logic in the low level parts of your blockchain. + +A smart contract system must be able to handle malicious developers, but this comes at a performance cost. + +When you program directly in the runtime, you get the highest performance possible, but you are also expected to be a competent developer and a good actor. + +In short, if you introduce a panic in your code, you make your blockchain vulnerable to DDoS attacks. + +But there is no reason you would ever need to panic because Rust has a great error handling system that we take advantage of in FRAME. + +### Pallet Errors + +All of our callable functions use the `DispatchResult` type. This means that we can always propagate up any errors that our Pallet runs into, and handle them properly, versus needing to panic. + +The [`DispatchResult`](https://docs.rs/frame-support/38.0.0/frame_support/dispatch/type.DispatchResult.html) type expects either `Ok(())` or `Err(DispatchError)`. + +The [`DispatchError`](https://docs.rs/frame-support/38.0.0/frame_support/pallet_prelude/enum.DispatchError.html) type has a few variants that you can easily construct / use. + +For example, if you want to be a little lazy, you can simply return a `&'static str`: + +```rust +fn always_error() -> DispatchResult { + return Err("this function always errors".into()) +} +``` + +But the better option is to return a custom Pallet Error: + +```rust +fn custom_error() -> DispatchResult { + return Err(Error::::CustomPalletError.into()) +} +``` + +Notice in both of these cases we had to call `into()` to convert our input type into the `DispatchError` type. + +To create `CustomPalletError` or whatever error you want, you simply add a new variants to the `enum Error` type. ```rust -pub struct StorageValue< - Prefix, - Value, - QueryKind = OptionQuery, - OnEmpty = GetDefault ->(_); +#[pallet::error] +pub enum Error { + /// This is a description for the error. + /// + /// This description can be shown to the user in UIs, so make it descriptive. + CustomPalletError, +} ``` -You can see the `StorageValue` expects 4 different generic parameters, two of which have default implementations, which means you don't need to define them unless you want to change the configuration. +We will show you the common ergonomic ways to use Pallet Errors going forward. -### Prefix +## Math -We have been able to hide the `Prefix` type from you so far because the `#[pallet::storage]` macro implements this for us auto-magically. +### Unsafe Math -We won't go into detail about what exactly `Prefix` does, but the high level idea is that it contains metadata needed to be able to correctly, uniquely, and consistently read and write data into the merkle trie. +The basic math operators in Rust are **unsafe**. -Those details are pretty low level, and something we can automatically implement for you with the macros. You should never need to do anything with `Prefix`, else you are probably doing something wrong. +Imagine our `CountForKitties` was already at the limit of `u32::MAX`. What would happen if we tried to call `mint` one more time? -### Value +We would get an overflow! -You already know how to use and implement `Value` for `StorageValue`, so we won't go into too much detail here. +In tests `u32::MAX + 1` will actually trigger a panic, but in a `release` build, this overflow will happen silently... -I will note that not literally every object can be placed in storage. It must satisfy some traits, which we will learn later in the tutorial. +And this would be really bad. Now our count would be back to 0, and if we had any logic which depended on this count being accurate, that logic would be broken. -### Query Kind +In blockchain systems, these can literally be billion dollar bugs, so let's look at how we can do math safely. -You can see the `QueryKind` parameter defaults to `OptionQuery`. +### Checked Math -This is why when we query storage, we get an `Option` returned to us. +The first choice for doing safe math is to use `checked_*` APIs, for example [`checked_add`](https://docs.rs/num/latest/num/trait.CheckedAdd.html). -Now as we stated before, Runtime storage is ALWAYS an `Option`. It is always possible that some storage does not exist / is not set when we first try to query it. But as we showed in our Pallet so far, there is logic we can write to "hide" that fact from the user, like `unwrap_or(0)`. +The checked math APIs will check if there are any underflows or overflows, and return `None` in those cases. Otherwise, if the math operation is calculated without error, it returns `Some(result)`. -We can do this even more ergonomically by changing the `QueryKind` to `ValueQuery`. +Here is a verbose way you could handle checked math in a Pallet: ```rust -#[pallet::storage] -pub(super) type CountForKitties = StorageValue; +let final_result: u32 = match value_a.checked_add(value_b) { + Some(result) => result, + None => return Err(Error::::CustomPalletError.into()), +}; ``` -In this case, all of our APIs change. +You can see how we can directly assign the `u32` value to `final_result`, otherwise it will return an error. + +We can also do this as a one-liner, which is more ergonomic and preferred: -Now when you call `get`, you will directly return a value, and when you call `set`, you just need to pass the value, not an option. +```rust +let final_result: u32 = value_a.checked_add(value_b).ok_or(Error::::CustomPalletError)?; +``` -Now you might ask: "What value do you get when you call `get` and there is no value stored?". +This is exactly how you should be writing all the safe math inside your Pallet. -That is controlled by the `OnEmpty` type. +Note that we didn't need to call `.into()` in this case, because `?` already does this! -### On Empty +### Saturating Math -As we noted above, the `OnEmpty` type defines the behavior of the `QueryKind` type. +The other option for safe math is to use `saturating_*` APIs, for example [`saturating_add`](https://docs.rs/num/latest/num/traits/trait.SaturatingAdd.html). -When you call `get`, and the storage is empty, the `OnEmpty` configuration kicks in. +This option is useful because it is safe and does NOT return an `Option`. -The default configuration for `OnEmpty` is `GetDefault`. This of course requires that the `Value` type must implement `Default`. But if it does, then you will get the following behavior: +Instead, it performs the math operations and keeps the value at the numerical limits, rather than overflowing. For example: ```rust -assert!(CountForKitties::::get() == u32::default()); +let value_a: u32 = 1; +let value_b: u32 = u32::MAX; +let result: u32 = value_a.saturating_add(value_b); +assert!(result == u32::MAX); ``` -For numbers, this value is normally zero, so simply setting `QueryKind = ValueQuery` gives you exactly the same behavior as what we programmed in our Pallet so far. +This generally is NOT the preferred API to use because usually you want to handle situations where an overflow would occur. Overflows and underflows usually indicate something "bad" is happening. -If your type does not implement `Default`, you can't use `ValueQuery`. A common example of this is the `T::AccountId` type, which purposefully has no default value, and thus is not compatible out of the box with `ValueQuery`. +However, there are times where you need to do math inside of functions where you cannot return a Result, and for that, saturating math might make sense. -You CAN modify `OnEmpty` to return a custom value, rather than `Default`, but we won't cover that here. Feel free to explore this idea on your own. +There are also times where you might want to perform the operation no matter that an underflow / overflow would occur. For example, imagine you made a function `slash` which slashes the balance of a malicious user. Your slash function may have some input parameter `amount` which says how much we should slash from the user. -## Your Turn - -Update your `CountForKitties` to use `QueryKind = ValueQuery`. +In a situation like this, it would make sense to use `saturating_sub` because we definitely want to slash as much as we can, even if we intended to slash more. The alternative would be returning an error, and not slashing anything! -This will affect the APIs for `get` and `set`, so also update your code to reflect those changes. +Anyway, every bone in your body should generally prefer to use the `checked_*` APIs, and handle all errors explicitly, but this is yet another tool in your pocket when it makes sense to use it. -### Tests +## Your Turn -In this step we made a breaking change to our Pallet. +We covered a lot in this section, but the concepts here are super important. -There are no new tests for this step, but you will need to go back through your existing tests and update your types. +Feel free to read this section again right now, and again at the end of the tutorial. -Since this tutorial does not really focus on writing tests for your pallet, feel free to just copy the `tests.rs` file included in this step's solution and update your project with it. +Now that you know how to ergonomically do safe math, update your Pallet to handle the `mint` logic safely and return a custom Pallet Error if an overflow would occur. diff --git a/src/14/template/src/impls.rs b/src/14/template/src/impls.rs index b9b5548a..c550bc8d 100644 --- a/src/14/template/src/impls.rs +++ b/src/14/template/src/impls.rs @@ -3,10 +3,9 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ let current_count: u32 = CountForKitties::::get().unwrap_or(0); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ + /* 🚧 TODO 🚧: Update this logic to use safe math. */ + let new_count = current_count + 1; CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/14/template/src/lib.rs b/src/14/template/src/lib.rs index eeb9724b..8edcc9b7 100644 --- a/src/14/template/src/lib.rs +++ b/src/14/template/src/lib.rs @@ -19,7 +19,6 @@ pub mod pallet { } #[pallet::storage] - /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ pub(super) type CountForKitties = StorageValue; #[pallet::event] @@ -30,7 +29,9 @@ pub mod pallet { #[pallet::error] pub enum Error { - TooManyKitties, + /* 🚧 TODO 🚧: + - Introduce a new error `TooManyKitties`. + */ } #[pallet::call] diff --git a/src/14/template/src/tests.rs b/src/14/template/src/tests.rs index 8701a366..2b579ffc 100644 --- a/src/14/template/src/tests.rs +++ b/src/14/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -163,16 +161,3 @@ fn mint_increments_count_for_kitty() { assert_eq!(CountForKitties::::get(), Some(1)); }) } - -#[test] -fn mint_errors_when_overflow() { - new_test_ext().execute_with(|| { - // Set the count to the largest value possible. - CountForKitties::::set(Some(u32::MAX)); - // `create_kitty` should not succeed because of safe math. - assert_noop!( - PalletKitties::create_kitty(RuntimeOrigin::signed(1)), - Error::::TooManyKitties - ); - }) -} diff --git a/src/14/template/template.diff b/src/14/template/template.diff index 0da88e53..900ee9c7 100644 --- a/src/14/template/template.diff +++ b/src/14/template/template.diff @@ -1,27 +1,29 @@ diff --git a/src/impls.rs b/src/impls.rs -index 7277e36..b9b5548 100644 +index 97393307..c550bc8d 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -3,8 +3,10 @@ use frame::prelude::*; - +@@ -4,6 +4,7 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { -+ /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ let current_count: u32 = CountForKitties::::get().unwrap_or(0); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -+ /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ ++ /* 🚧 TODO 🚧: Update this logic to use safe math. */ + let new_count = current_count + 1; CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); - Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 2c11f46..eeb9724 100644 +index 90242f63..8edcc9b7 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -19,6 +19,7 @@ pub mod pallet { +@@ -28,7 +28,11 @@ pub mod pallet { } - #[pallet::storage] -+ /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ - pub(super) type CountForKitties = StorageValue; + #[pallet::error] +- pub enum Error {} ++ pub enum Error { ++ /* 🚧 TODO 🚧: ++ - Introduce a new error `TooManyKitties`. ++ */ ++ } - #[pallet::event] + #[pallet::call] + impl Pallet { diff --git a/src/15/README.md b/src/15/README.md index 54677c78..eb9560d4 100644 --- a/src/15/README.md +++ b/src/15/README.md @@ -17,9 +17,18 @@
- + +
-
+
+ +```rust +{{#include ./template/src/impls.rs}} +``` + +
+ +
```rust {{#include ./template/src/lib.rs}} @@ -34,10 +43,19 @@
- + +
-
+
+ +```rust +{{#include ./solution/src/impls.rs}} +``` + +
+ +
```rust {{#include ./solution/src/lib.rs}} diff --git a/src/15/solution/solution.diff b/src/15/solution/solution.diff index 3582f383..91037523 100644 --- a/src/15/solution/solution.diff +++ b/src/15/solution/solution.diff @@ -1,37 +1,80 @@ +diff --git a/src/impls.rs b/src/impls.rs +index b9b5548a..d283fd6b 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -3,11 +3,9 @@ use frame::prelude::*; + + impl Pallet { + pub fn mint(owner: T::AccountId) -> DispatchResult { +- /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ +- let current_count: u32 = CountForKitties::::get().unwrap_or(0); ++ let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; +- /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ +- CountForKitties::::set(Some(new_count)); ++ CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); + Ok(()) + } diff --git a/src/lib.rs b/src/lib.rs -index bb39563..42fb376 100644 +index eeb9724b..57baa0d1 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -21,12 +21,8 @@ pub mod pallet { - #[pallet::storage] - pub(super) type CountForKitties = StorageValue; +@@ -19,8 +19,7 @@ pub mod pallet { + } -- /* 🚧 TODO 🚧: -- - Create a new `StorageMap` named `Kitties`. -- - `Kitties` should be generic over ``. -- - Set `Key` to `[u8; 32]` to use the kitty id as the key. -- - Set `Value` to `()` as a placeholder for now. -- */ -+ #[pallet::storage] -+ pub(super) type Kitties = StorageMap; + #[pallet::storage] +- /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ +- pub(super) type CountForKitties = StorageValue; ++ pub(super) type CountForKitties = StorageValue; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/tests.rs b/src/tests.rs -index 4ec3538..6fc2683 100644 +index 58df4d40..cdbc4e25 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -174,3 +174,13 @@ fn mint_errors_when_overflow() { - ); +@@ -139,26 +139,24 @@ fn create_kitty_emits_event() { + #[test] + fn count_for_kitties_created_correctly() { + new_test_ext().execute_with(|| { +- // Querying storage before anything is set will return `None`. +- assert_eq!(CountForKitties::::get(), None); +- // You can `set` the value using an `Option`. +- CountForKitties::::set(Some(1337u32)); ++ // Querying storage before anything is set will return `0`. ++ assert_eq!(CountForKitties::::get(), 0); ++ // You can `set` the value using an `u32`. ++ CountForKitties::::set(1337u32); + // You can `put` the value directly with a `u32`. + CountForKitties::::put(1337u32); +- // Check that the value is now in storage. +- assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } -+ -+#[test] -+fn kitties_map_created_correctly() { -+ new_test_ext().execute_with(|| { -+ let zero_key = [0u8; 32]; -+ assert!(!Kitties::::contains_key(zero_key)); -+ Kitties::::insert(zero_key, ()); -+ assert!(Kitties::::contains_key(zero_key)); -+ }) -+} + + #[test] + fn mint_increments_count_for_kitty() { + new_test_ext().execute_with(|| { +- // Querying storage before anything is set will return `None`. +- assert_eq!(CountForKitties::::get(), None); ++ // Querying storage before anything is set will return `0`. ++ assert_eq!(CountForKitties::::get(), 0); + // Call `create_kitty` which will call `mint`. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); +- // Now the storage should be `Some(1)` +- assert_eq!(CountForKitties::::get(), Some(1)); ++ // Now the storage should be `1` ++ assert_eq!(CountForKitties::::get(), 1); + }) + } + +@@ -166,7 +164,7 @@ fn mint_increments_count_for_kitty() { + fn mint_errors_when_overflow() { + new_test_ext().execute_with(|| { + // Set the count to the largest value possible. +- CountForKitties::::set(Some(u32::MAX)); ++ CountForKitties::::set(u32::MAX); + // `create_kitty` should not succeed because of safe math. + assert_noop!( + PalletKitties::create_kitty(RuntimeOrigin::signed(1)), diff --git a/src/15/solution/src/lib.rs b/src/15/solution/src/lib.rs index 42fb376b..57baa0d1 100644 --- a/src/15/solution/src/lib.rs +++ b/src/15/solution/src/lib.rs @@ -21,9 +21,6 @@ pub mod pallet { #[pallet::storage] pub(super) type CountForKitties = StorageValue; - #[pallet::storage] - pub(super) type Kitties = StorageMap; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/src/15/solution/src/tests.rs b/src/15/solution/src/tests.rs index 6fc26837..cdbc4e25 100644 --- a/src/15/solution/src/tests.rs +++ b/src/15/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -174,13 +172,3 @@ fn mint_errors_when_overflow() { ); }) } - -#[test] -fn kitties_map_created_correctly() { - new_test_ext().execute_with(|| { - let zero_key = [0u8; 32]; - assert!(!Kitties::::contains_key(zero_key)); - Kitties::::insert(zero_key, ()); - assert!(Kitties::::contains_key(zero_key)); - }) -} diff --git a/src/15/template/README.md b/src/15/template/README.md index 169722f0..a7d593ee 100644 --- a/src/15/template/README.md +++ b/src/15/template/README.md @@ -1,78 +1,89 @@ -# Storage Maps +# Value Query -Now that you have learned everything you need to know about `StorageValue`s, it is time to move on to `StorageMap`s. +When we originally introduced the `StorageValue`, we only exposed to you one field which you could manipulate, which is the `Value` type, which defines the type that will be stored. -`StorageMap`s are key / value storage items designed for Pallet development. +But there are actually more ways you can configure the `StorageValue` to increase developer ergonomics. -## Syntax +## Storage Value Definition -Declaring a new [`StorageMap`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html) is very similar to a `StorageValue`: +Let's look at the definition of a [`StorageValue`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageValue.html): ```rust -#[pallet::storage] -pub(super) type Kitties = StorageMap; +pub struct StorageValue< + Prefix, + Value, + QueryKind = OptionQuery, + OnEmpty = GetDefault +>(_); ``` -Nearly everything is the same, except you need to define a `Key` and `Value`. +You can see the `StorageValue` expects 4 different generic parameters, two of which have default implementations, which means you don't need to define them unless you want to change the configuration. -Each `Key` can store a separate `Value`, which makes maps super useful. +### Prefix -In this case `[u8; 32]` represents some unique identifier for each Kitty we will store, and `()` is simply a placeholder type for now. +We have been able to hide the `Prefix` type from you so far because the `#[pallet::storage]` macro implements this for us auto-magically. -Note that each storage item needs its own `#[pallet::storage]` attribute. +We won't go into detail about what exactly `Prefix` does, but the high level idea is that it contains metadata needed to be able to correctly, uniquely, and consistently read and write data into the merkle trie. -## Conceptual +Those details are pretty low level, and something we can automatically implement for you with the macros. You should never need to do anything with `Prefix`, else you are probably doing something wrong. -The key difference between a `StorageValue` and a `StorageMap` is: +### Value -- A `StorageValue` stores a single value into a single key in the Merkle Trie. -- A `StorageMap` stores multiple values under different storage keys, all into different places in the Merkle Trie. +You already know how to use and implement `Value` for `StorageValue`, so we won't go into too much detail here. -Let's clarify further. +I will note that not literally every object can be placed in storage. It must satisfy some traits, which we will learn later in the tutorial. -Rust has a type `BTreeMap`, which is also a key/value map. +### Query Kind -So what would be the difference between: +You can see the `QueryKind` parameter defaults to `OptionQuery`. -```rust -#[pallet::storage] -pub(super) type MyValueMap = StorageValue>; -``` +This is why when we query storage, we get an `Option` returned to us. + +Now as we stated before, Runtime storage is ALWAYS an `Option`. It is always possible that some storage does not exist / is not set when we first try to query it. But as we showed in our Pallet so far, there is logic we can write to "hide" that fact from the user, like `unwrap_or(0)`. -and +We can do this even more ergonomically by changing the `QueryKind` to `ValueQuery`. ```rust #[pallet::storage] -pub(super) type MyMap = StorageMap; +pub(super) type CountForKitties = StorageValue; ``` -They both can store the same data, but the `StorageValue` puts all of the data into a single object and stores that all into a single key in the Merkle Trie. +In this case, all of our APIs change. -This means if we want to read just a single key / value pair, we must read ALL data in the whole map, and parse out just the single value we want. +Now when you call `get`, you will directly return a value, and when you call `set`, you just need to pass the value, not an option. -In a `StorageMap`, each value is stored in its own spot in the Merkle Trie, so you are able to read just one key / value on its own. This can be way more efficient for reading just a single item. +Now you might ask: "What value do you get when you call `get` and there is no value stored?". -However, trying to read multiple items from a `StorageMap` is extremely expensive. +That is controlled by the `OnEmpty` type. -So there is no perfect kind of storage, just tradeoffs. +### On Empty -## Use Cases +As we noted above, the `OnEmpty` type defines the behavior of the `QueryKind` type. -`StorageMap`s are really great when we need to store some unique information about a bunch of different things. +When you call `get`, and the storage is empty, the `OnEmpty` configuration kicks in. -The most common example would be trying to store the token balance of all users in your blockchain. In this case, each user has their own `T::AccountId`, and that maps to some balance amount. +The default configuration for `OnEmpty` is `GetDefault`. This of course requires that the `Value` type must implement `Default`. But if it does, then you will get the following behavior: -In our pallet, we use the `StorageMap` to store unique information about each `Kitty` in our pallet. +```rust +assert!(CountForKitties::::get() == u32::default()); +``` -These use cases make sense because all the logic in our pallet typically touches only one key at a time. +For numbers, this value is normally zero, so simply setting `QueryKind = ValueQuery` gives you exactly the same behavior as what we programmed in our Pallet so far. -- when you mint a kitty, we create one key / value. -- when you transfer a kitty, we mutate one key / value. -- when you put your kitty for sale, you mutate one key / value. -- etc... +If your type does not implement `Default`, you can't use `ValueQuery`. A common example of this is the `T::AccountId` type, which purposefully has no default value, and thus is not compatible out of the box with `ValueQuery`. -And with the `StorageMap`, we can store a nearly infinite number of different kitties, or at least as many as there are unique keys. +You CAN modify `OnEmpty` to return a custom value, rather than `Default`, but we won't cover that here. Feel free to explore this idea on your own. ## Your Turn -Add the `Kitties` storage map to your project as shown in the template. +Update your `CountForKitties` to use `QueryKind = ValueQuery`. + +This will affect the APIs for `get` and `set`, so also update your code to reflect those changes. + +### Tests + +In this step we made a breaking change to our Pallet. + +There are no new tests for this step, but you will need to go back through your existing tests and update your types. + +Since this tutorial does not really focus on writing tests for your pallet, feel free to just copy the `tests.rs` file included in this step's solution and update your project with it. diff --git a/src/15/template/src/impls.rs b/src/15/template/src/impls.rs index d283fd6b..b9b5548a 100644 --- a/src/15/template/src/impls.rs +++ b/src/15/template/src/impls.rs @@ -3,9 +3,11 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get(); + /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ + let current_count: u32 = CountForKitties::::get().unwrap_or(0); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - CountForKitties::::set(new_count); + /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ + CountForKitties::::set(Some(new_count)); Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/15/template/src/lib.rs b/src/15/template/src/lib.rs index bb395631..eeb9724b 100644 --- a/src/15/template/src/lib.rs +++ b/src/15/template/src/lib.rs @@ -19,14 +19,8 @@ pub mod pallet { } #[pallet::storage] - pub(super) type CountForKitties = StorageValue; - - /* 🚧 TODO 🚧: - - Create a new `StorageMap` named `Kitties`. - - `Kitties` should be generic over ``. - - Set `Key` to `[u8; 32]` to use the kitty id as the key. - - Set `Value` to `()` as a placeholder for now. - */ + /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ + pub(super) type CountForKitties = StorageValue; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/15/template/src/tests.rs b/src/15/template/src/tests.rs index 4ec35382..58df4d40 100644 --- a/src/15/template/src/tests.rs +++ b/src/15/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -141,24 +139,26 @@ fn create_kitty_emits_event() { #[test] fn count_for_kitties_created_correctly() { new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `0`. - assert_eq!(CountForKitties::::get(), 0); - // You can `set` the value using an `u32`. - CountForKitties::::set(1337u32); + // Querying storage before anything is set will return `None`. + assert_eq!(CountForKitties::::get(), None); + // You can `set` the value using an `Option`. + CountForKitties::::set(Some(1337u32)); // You can `put` the value directly with a `u32`. CountForKitties::::put(1337u32); + // Check that the value is now in storage. + assert_eq!(CountForKitties::::get(), Some(1337u32)); }) } #[test] fn mint_increments_count_for_kitty() { new_test_ext().execute_with(|| { - // Querying storage before anything is set will return `0`. - assert_eq!(CountForKitties::::get(), 0); + // Querying storage before anything is set will return `None`. + assert_eq!(CountForKitties::::get(), None); // Call `create_kitty` which will call `mint`. assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now the storage should be `1` - assert_eq!(CountForKitties::::get(), 1); + // Now the storage should be `Some(1)` + assert_eq!(CountForKitties::::get(), Some(1)); }) } @@ -166,7 +166,7 @@ fn mint_increments_count_for_kitty() { fn mint_errors_when_overflow() { new_test_ext().execute_with(|| { // Set the count to the largest value possible. - CountForKitties::::set(u32::MAX); + CountForKitties::::set(Some(u32::MAX)); // `create_kitty` should not succeed because of safe math. assert_noop!( PalletKitties::create_kitty(RuntimeOrigin::signed(1)), diff --git a/src/15/template/template.diff b/src/15/template/template.diff index 32ce5962..2640dc75 100644 --- a/src/15/template/template.diff +++ b/src/15/template/template.diff @@ -1,18 +1,27 @@ +diff --git a/src/impls.rs b/src/impls.rs +index 7277e367..b9b5548a 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -3,8 +3,10 @@ use frame::prelude::*; + + impl Pallet { + pub fn mint(owner: T::AccountId) -> DispatchResult { ++ /* 🚧 TODO 🚧: Remove the `unwrap_or` which is not needed when using `ValueQuery`. */ + let current_count: u32 = CountForKitties::::get().unwrap_or(0); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; ++ /* 🚧 TODO 🚧: Remove the `Option` wrapper when setting the `new_count`. */ + CountForKitties::::set(Some(new_count)); + Self::deposit_event(Event::::Created { owner }); + Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 57baa0d..bb39563 100644 +index 2c11f460..eeb9724b 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -21,6 +21,13 @@ pub mod pallet { +@@ -19,6 +19,7 @@ pub mod pallet { + } + #[pallet::storage] - pub(super) type CountForKitties = StorageValue; ++ /* 🚧 TODO 🚧: Update this storage to use a `QueryKind` of `ValueQuery`. */ + pub(super) type CountForKitties = StorageValue; -+ /* 🚧 TODO 🚧: -+ - Create a new `StorageMap` named `Kitties`. -+ - `Kitties` should be generic over ``. -+ - Set `Key` to `[u8; 32]` to use the kitty id as the key. -+ - Set `Value` to `()` as a placeholder for now. -+ */ -+ #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { diff --git a/src/16/README.md b/src/16/README.md index eb9560d4..54677c78 100644 --- a/src/16/README.md +++ b/src/16/README.md @@ -17,18 +17,9 @@
- - +
-
- -```rust -{{#include ./template/src/impls.rs}} -``` - -
- -
+
```rust {{#include ./template/src/lib.rs}} @@ -43,19 +34,10 @@
- - +
-
- -```rust -{{#include ./solution/src/impls.rs}} -``` - -
- -
+
```rust {{#include ./solution/src/lib.rs}} diff --git a/src/16/solution/solution.diff b/src/16/solution/solution.diff index b613fba1..27ccd869 100644 --- a/src/16/solution/solution.diff +++ b/src/16/solution/solution.diff @@ -1,52 +1,37 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 4443702..363f5e4 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -2,11 +2,10 @@ use super::*; - use frame::prelude::*; - - impl Pallet { -- /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ -- pub fn mint(owner: T::AccountId) -> DispatchResult { -+ pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -- /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ -+ Kitties::::insert(dna, ()); - CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); - Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 717c900..15fb504 100644 +index bb395631..42fb376b 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -39,11 +39,8 @@ pub mod pallet { - impl Pallet { - pub fn create_kitty(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; -- /* 🚧 TODO 🚧: -- - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. -- - Pass `dna` to the `mint` function as the second parameter. -- */ -- Self::mint(who)?; -+ let dna = [0u8; 32]; -+ Self::mint(who, dna)?; - Ok(()) - } - } +@@ -21,12 +21,8 @@ pub mod pallet { + #[pallet::storage] + pub(super) type CountForKitties = StorageValue; + +- /* 🚧 TODO 🚧: +- - Create a new `StorageMap` named `Kitties`. +- - `Kitties` should be generic over ``. +- - Set `Key` to `[u8; 32]` to use the kitty id as the key. +- - Set `Value` to `()` as a placeholder for now. +- */ ++ #[pallet::storage] ++ pub(super) type Kitties = StorageMap; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/tests.rs b/src/tests.rs -index 6fc2683..916f434 100644 +index cdbc4e25..58334971 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -184,3 +184,11 @@ fn kitties_map_created_correctly() { - assert!(Kitties::::contains_key(zero_key)); +@@ -172,3 +172,13 @@ fn mint_errors_when_overflow() { + ); }) } + +#[test] -+fn create_kitty_adds_to_map() { ++fn kitties_map_created_correctly() { + new_test_ext().execute_with(|| { -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ assert_eq!(Kitties::::iter().count(), 1); ++ let zero_key = [0u8; 32]; ++ assert!(!Kitties::::contains_key(zero_key)); ++ Kitties::::insert(zero_key, ()); ++ assert!(Kitties::::contains_key(zero_key)); + }) +} diff --git a/src/16/solution/src/impls.rs b/src/16/solution/src/impls.rs index 363f5e49..d283fd6b 100644 --- a/src/16/solution/src/impls.rs +++ b/src/16/solution/src/impls.rs @@ -2,10 +2,9 @@ use super::*; use frame::prelude::*; impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { + pub fn mint(owner: T::AccountId) -> DispatchResult { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, ()); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/16/solution/src/lib.rs b/src/16/solution/src/lib.rs index 15fb5043..42fb376b 100644 --- a/src/16/solution/src/lib.rs +++ b/src/16/solution/src/lib.rs @@ -39,8 +39,7 @@ pub mod pallet { impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let dna = [0u8; 32]; - Self::mint(who, dna)?; + Self::mint(who)?; Ok(()) } } diff --git a/src/16/solution/src/tests.rs b/src/16/solution/src/tests.rs index 916f4341..58334971 100644 --- a/src/16/solution/src/tests.rs +++ b/src/16/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -184,11 +182,3 @@ fn kitties_map_created_correctly() { assert!(Kitties::::contains_key(zero_key)); }) } - -#[test] -fn create_kitty_adds_to_map() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_eq!(Kitties::::iter().count(), 1); - }) -} diff --git a/src/16/template/README.md b/src/16/template/README.md index 96643436..169722f0 100644 --- a/src/16/template/README.md +++ b/src/16/template/README.md @@ -1,47 +1,78 @@ -# Kitties Map +# Storage Maps -Now let's learn to interact with our `Kitties` storage map, and update the map when we `mint` new kitties. +Now that you have learned everything you need to know about `StorageValue`s, it is time to move on to `StorageMap`s. -## Basic APIs +`StorageMap`s are key / value storage items designed for Pallet development. -This tutorial will only go over just the basic APIs needed to build our Pallet. +## Syntax -Check out the [`StorageMap` documentation](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html) if you want to see the full APIs. - -### Reading Storage - -To read the current value of a key in a `StorageMap`, you can simply call the [`get(key)`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.get) API: +Declaring a new [`StorageMap`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html) is very similar to a `StorageValue`: ```rust -let my_key: [u8; 32] = [0u8; 32]; -let maybe_value: Option<()> = Kitties::::get(my_key); +#[pallet::storage] +pub(super) type Kitties = StorageMap; ``` -Just as the `StorageValue`, you can see this query returns an `Option`, indicating whether there is actually a value under the key. +Nearly everything is the same, except you need to define a `Key` and `Value`. + +Each `Key` can store a separate `Value`, which makes maps super useful. + +In this case `[u8; 32]` represents some unique identifier for each Kitty we will store, and `()` is simply a placeholder type for now. + +Note that each storage item needs its own `#[pallet::storage]` attribute. + +## Conceptual -Just as before, the most ergonomic way to read a kitty, or throw an error when there is no kitty is to write the following: +The key difference between a `StorageValue` and a `StorageMap` is: + +- A `StorageValue` stores a single value into a single key in the Merkle Trie. +- A `StorageMap` stores multiple values under different storage keys, all into different places in the Merkle Trie. + +Let's clarify further. + +Rust has a type `BTreeMap`, which is also a key/value map. + +So what would be the difference between: ```rust -let kitty: () = Kitties::::get(my_key).ok_or(Error::::NoKitty)?; +#[pallet::storage] +pub(super) type MyValueMap = StorageValue>; ``` -### Writing Storage - -To add a new value to the `StorageMap`, you can simply call the `insert` API: +and ```rust -let my_key: [u8; 32] = [0u8; 32]; -Kitties::::insert(my_key, ()); +#[pallet::storage] +pub(super) type MyMap = StorageMap; ``` -The same behaviors apply to `StorageMap` as a `StorageValue`. +They both can store the same data, but the `StorageValue` puts all of the data into a single object and stores that all into a single key in the Merkle Trie. -The [`insert`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.insert) API cannot fail. If a value already exists in the map, under the key, we will simply overwrite that value. If you want to check if a value already exists in the map under a key, the most efficient way is to call [`contains_key(key)`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.contains_key). +This means if we want to read just a single key / value pair, we must read ALL data in the whole map, and parse out just the single value we want. -## Your Turn +In a `StorageMap`, each value is stored in its own spot in the Merkle Trie, so you are able to read just one key / value on its own. This can be way more efficient for reading just a single item. + +However, trying to read multiple items from a `StorageMap` is extremely expensive. + +So there is no perfect kind of storage, just tradeoffs. + +## Use Cases + +`StorageMap`s are really great when we need to store some unique information about a bunch of different things. -`StorageMap`s are easy! +The most common example would be trying to store the token balance of all users in your blockchain. In this case, each user has their own `T::AccountId`, and that maps to some balance amount. -Update the logic in your pallet to insert a new kitty into your `Kitties` map when we call `mint`. +In our pallet, we use the `StorageMap` to store unique information about each `Kitty` in our pallet. + +These use cases make sense because all the logic in our pallet typically touches only one key at a time. + +- when you mint a kitty, we create one key / value. +- when you transfer a kitty, we mutate one key / value. +- when you put your kitty for sale, you mutate one key / value. +- etc... + +And with the `StorageMap`, we can store a nearly infinite number of different kitties, or at least as many as there are unique keys. + +## Your Turn -For this, you will need to add a second parameter to the `mint` function to pass the unique identifier for the kitty. +Add the `Kitties` storage map to your project as shown in the template. diff --git a/src/16/template/src/impls.rs b/src/16/template/src/impls.rs index 44437026..d283fd6b 100644 --- a/src/16/template/src/impls.rs +++ b/src/16/template/src/impls.rs @@ -2,11 +2,9 @@ use super::*; use frame::prelude::*; impl Pallet { - /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ pub fn mint(owner: T::AccountId) -> DispatchResult { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/16/template/src/lib.rs b/src/16/template/src/lib.rs index 717c9009..bb395631 100644 --- a/src/16/template/src/lib.rs +++ b/src/16/template/src/lib.rs @@ -21,8 +21,12 @@ pub mod pallet { #[pallet::storage] pub(super) type CountForKitties = StorageValue; - #[pallet::storage] - pub(super) type Kitties = StorageMap; + /* 🚧 TODO 🚧: + - Create a new `StorageMap` named `Kitties`. + - `Kitties` should be generic over ``. + - Set `Key` to `[u8; 32]` to use the kitty id as the key. + - Set `Value` to `()` as a placeholder for now. + */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -39,10 +43,6 @@ pub mod pallet { impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - /* 🚧 TODO 🚧: - - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. - - Pass `dna` to the `mint` function as the second parameter. - */ Self::mint(who)?; Ok(()) } diff --git a/src/16/template/src/tests.rs b/src/16/template/src/tests.rs index 6fc26837..cdbc4e25 100644 --- a/src/16/template/src/tests.rs +++ b/src/16/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -174,13 +172,3 @@ fn mint_errors_when_overflow() { ); }) } - -#[test] -fn kitties_map_created_correctly() { - new_test_ext().execute_with(|| { - let zero_key = [0u8; 32]; - assert!(!Kitties::::contains_key(zero_key)); - Kitties::::insert(zero_key, ()); - assert!(Kitties::::contains_key(zero_key)); - }) -} diff --git a/src/16/template/template.diff b/src/16/template/template.diff index 47d45378..4922d1b9 100644 --- a/src/16/template/template.diff +++ b/src/16/template/template.diff @@ -1,31 +1,18 @@ -diff --git a/src/impls.rs b/src/impls.rs -index d283fd6..4443702 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -2,9 +2,11 @@ use super::*; - use frame::prelude::*; - - impl Pallet { -+ /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ - pub fn mint(owner: T::AccountId) -> DispatchResult { - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -+ /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ - CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); - Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 42fb376..717c900 100644 +index 57baa0d1..bb395631 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -39,6 +39,10 @@ pub mod pallet { - impl Pallet { - pub fn create_kitty(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; -+ /* 🚧 TODO 🚧: -+ - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. -+ - Pass `dna` to the `mint` function as the second parameter. -+ */ - Self::mint(who)?; - Ok(()) - } +@@ -21,6 +21,13 @@ pub mod pallet { + #[pallet::storage] + pub(super) type CountForKitties = StorageValue; + ++ /* 🚧 TODO 🚧: ++ - Create a new `StorageMap` named `Kitties`. ++ - `Kitties` should be generic over ``. ++ - Set `Key` to `[u8; 32]` to use the kitty id as the key. ++ - Set `Value` to `()` as a placeholder for now. ++ */ ++ + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { diff --git a/src/17/solution/solution.diff b/src/17/solution/solution.diff index c876cd57..f70ff1a6 100644 --- a/src/17/solution/solution.diff +++ b/src/17/solution/solution.diff @@ -1,47 +1,52 @@ diff --git a/src/impls.rs b/src/impls.rs -index 292909e..c0e7cb2 100644 +index 44437026..363f5e49 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -3,10 +3,9 @@ use frame::prelude::*; +@@ -2,11 +2,10 @@ use super::*; + use frame::prelude::*; impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -- /* 🚧 TODO 🚧: -- - `ensure!` that `Kitties` map does not `contains_key` for `dna`. -- - If it does, return `Error::::DuplicateKitty`. -- */ -+ // Check if the kitty does not already exist in our storage map -+ ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); -+ +- /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ +- pub fn mint(owner: T::AccountId) -> DispatchResult { ++ pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, ()); +- /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ ++ Kitties::::insert(dna, ()); + CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); + Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 6fb4b6d..75a1b65 100644 +index 717c9009..15fb5043 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -33,7 +33,7 @@ pub mod pallet { - #[pallet::error] - pub enum Error { - TooManyKitties, -- /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ -+ DuplicateKitty, +@@ -39,11 +39,8 @@ pub mod pallet { + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; +- /* 🚧 TODO 🚧: +- - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. +- - Pass `dna` to the `mint` function as the second parameter. +- */ +- Self::mint(who)?; ++ let dna = [0u8; 32]; ++ Self::mint(who, dna)?; + Ok(()) + } } - - #[pallet::call] diff --git a/src/tests.rs b/src/tests.rs -index 916f434..ea6bb5b 100644 +index 58334971..8fca31e4 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -192,3 +192,11 @@ fn create_kitty_adds_to_map() { - assert_eq!(Kitties::::iter().count(), 1); +@@ -182,3 +182,11 @@ fn kitties_map_created_correctly() { + assert!(Kitties::::contains_key(zero_key)); }) } + +#[test] -+fn cannot_mint_duplicate_kitty() { ++fn create_kitty_adds_to_map() { + new_test_ext().execute_with(|| { -+ assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); -+ assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ assert_eq!(Kitties::::iter().count(), 1); + }) +} diff --git a/src/17/solution/src/impls.rs b/src/17/solution/src/impls.rs index c0e7cb27..363f5e49 100644 --- a/src/17/solution/src/impls.rs +++ b/src/17/solution/src/impls.rs @@ -3,9 +3,6 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); - let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; Kitties::::insert(dna, ()); diff --git a/src/17/solution/src/lib.rs b/src/17/solution/src/lib.rs index 75a1b65c..15fb5043 100644 --- a/src/17/solution/src/lib.rs +++ b/src/17/solution/src/lib.rs @@ -33,7 +33,6 @@ pub mod pallet { #[pallet::error] pub enum Error { TooManyKitties, - DuplicateKitty, } #[pallet::call] diff --git a/src/17/solution/src/tests.rs b/src/17/solution/src/tests.rs index ea6bb5ba..8fca31e4 100644 --- a/src/17/solution/src/tests.rs +++ b/src/17/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -192,11 +190,3 @@ fn create_kitty_adds_to_map() { assert_eq!(Kitties::::iter().count(), 1); }) } - -#[test] -fn cannot_mint_duplicate_kitty() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); - assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); - }) -} diff --git a/src/17/template/README.md b/src/17/template/README.md index 4e71c024..96643436 100644 --- a/src/17/template/README.md +++ b/src/17/template/README.md @@ -1,44 +1,47 @@ -# Duplicate Kitty Check +# Kitties Map -To make sure your state transition function behaves as expected, you must check everything that could go wrong, and return an error in those cases. +Now let's learn to interact with our `Kitties` storage map, and update the map when we `mint` new kitties. -## Contains Key +## Basic APIs -As we mentioned in the previous section, the `insert` API will simply overwrite any existing data which is already there. We want to prevent this, else we risk overwriting data about a kitty that already exists and is already owned by someone. +This tutorial will only go over just the basic APIs needed to build our Pallet. -To prevent this, we can call the `contains_key(key)` API, which will return `true` if there is already a value in storage, or `false` if there isn't. +Check out the [`StorageMap` documentation](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html) if you want to see the full APIs. -You might ask: Why do we call `contains_key` rather than call `get` and checking the `Option`? +### Reading Storage -Well, there are two reasons: +To read the current value of a key in a `StorageMap`, you can simply call the [`get(key)`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.get) API: -1. Just like `StorageValue`, you can set a `StorageMap` with `QueryKind = ValueQuery`, thus we would no longer return an `Option` for you to check, and we would "hide" the fact that the map is empty at that key. So this API is the only way to check if the value ACTUALLY exists or not. -2. The `contains_key` API is strictly more efficient than the `get` API. The `get` API returns the value, which means we need to go into the database, read the bits, serialize it to a Rust type, pass that back to the Pallet, and assign it to the variable. - - On the other hand, `contains_key` can simply check if the value exists in the database, and only return back the boolean result. No need to serialize all the bytes, store the variable, or any of that. - -This same trick applies to `StorageValue` too. Rather than calling `get`, you can call `exists`, which provides the same api as `contains_key`. +```rust +let my_key: [u8; 32] = [0u8; 32]; +let maybe_value: Option<()> = Kitties::::get(my_key); +``` -## Ensure +Just as the `StorageValue`, you can see this query returns an `Option`, indicating whether there is actually a value under the key. -To do simple duplication checks and return an error, we can write the following: +Just as before, the most ergonomic way to read a kitty, or throw an error when there is no kitty is to write the following: ```rust -if (PalletKitties::::contains_key(my_key)) { - return Err(Error::::DuplicateKitty.into()); -} +let kitty: () = Kitties::::get(my_key).ok_or(Error::::NoKitty)?; ``` -But that is pretty verbose. We have a macro which can do this for you in a single line: +### Writing Storage + +To add a new value to the `StorageMap`, you can simply call the `insert` API: ```rust -ensure!(!Kitties::::contains_key(my_key), Error::::DuplicateKitty); +let my_key: [u8; 32] = [0u8; 32]; +Kitties::::insert(my_key, ()); ``` -`ensure!` is a macro, which basically expands into the same verbose code shown above, except checking the opposite condition. That is to say, we `ensure!` that `Kitties` does NOT `contains_key`, else we return the error specified. +The same behaviors apply to `StorageMap` as a `StorageValue`. -There really is no difference here, so use whatever makes you comfortable. +The [`insert`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.insert) API cannot fail. If a value already exists in the map, under the key, we will simply overwrite that value. If you want to check if a value already exists in the map under a key, the most efficient way is to call [`contains_key(key)`](https://docs.rs/frame-support/38.0.0/frame_support/storage/types/struct.StorageMap.html#method.contains_key). ## Your Turn -Now that you know how to efficiently check for an existing kitty, put that check into the `mint` function to ensure that we do not mint a duplicate kitty in the `Kitties` storage map. +`StorageMap`s are easy! + +Update the logic in your pallet to insert a new kitty into your `Kitties` map when we call `mint`. + +For this, you will need to add a second parameter to the `mint` function to pass the unique identifier for the kitty. diff --git a/src/17/template/src/impls.rs b/src/17/template/src/impls.rs index 292909e1..44437026 100644 --- a/src/17/template/src/impls.rs +++ b/src/17/template/src/impls.rs @@ -2,14 +2,11 @@ use super::*; use frame::prelude::*; impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - /* 🚧 TODO 🚧: - - `ensure!` that `Kitties` map does not `contains_key` for `dna`. - - If it does, return `Error::::DuplicateKitty`. - */ + /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ + pub fn mint(owner: T::AccountId) -> DispatchResult { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, ()); + /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/17/template/src/lib.rs b/src/17/template/src/lib.rs index 6fb4b6d7..717c9009 100644 --- a/src/17/template/src/lib.rs +++ b/src/17/template/src/lib.rs @@ -33,15 +33,17 @@ pub mod pallet { #[pallet::error] pub enum Error { TooManyKitties, - /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ } #[pallet::call] impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let dna = [0u8; 32]; - Self::mint(who, dna)?; + /* 🚧 TODO 🚧: + - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. + - Pass `dna` to the `mint` function as the second parameter. + */ + Self::mint(who)?; Ok(()) } } diff --git a/src/17/template/src/tests.rs b/src/17/template/src/tests.rs index 916f4341..58334971 100644 --- a/src/17/template/src/tests.rs +++ b/src/17/template/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -184,11 +182,3 @@ fn kitties_map_created_correctly() { assert!(Kitties::::contains_key(zero_key)); }) } - -#[test] -fn create_kitty_adds_to_map() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_eq!(Kitties::::iter().count(), 1); - }) -} diff --git a/src/17/template/template.diff b/src/17/template/template.diff index ec4c467e..0c25a8c2 100644 --- a/src/17/template/template.diff +++ b/src/17/template/template.diff @@ -1,27 +1,31 @@ diff --git a/src/impls.rs b/src/impls.rs -index 363f5e4..292909e 100644 +index d283fd6b..44437026 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -3,6 +3,10 @@ use frame::prelude::*; +@@ -2,9 +2,11 @@ use super::*; + use frame::prelude::*; impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -+ /* 🚧 TODO 🚧: -+ - `ensure!` that `Kitties` map does not `contains_key` for `dna`. -+ - If it does, return `Error::::DuplicateKitty`. -+ */ ++ /* 🚧 TODO 🚧: Update this function signature to include `dna` which is type `[u8; 32]`. */ + pub fn mint(owner: T::AccountId) -> DispatchResult { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, ()); ++ /* 🚧 TODO 🚧: In the `Kitties` map, under the key `dna`, insert `()`. */ + CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); + Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 15fb504..6fb4b6d 100644 +index 42fb376b..717c9009 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -33,6 +33,7 @@ pub mod pallet { - #[pallet::error] - pub enum Error { - TooManyKitties, -+ /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ - } - - #[pallet::call] +@@ -39,6 +39,10 @@ pub mod pallet { + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; ++ /* 🚧 TODO 🚧: ++ - Create a `dna` variable for this kitty, which we will set as `[0u8; 32]` for now. ++ - Pass `dna` to the `mint` function as the second parameter. ++ */ + Self::mint(who)?; + Ok(()) + } diff --git a/src/18/README.md b/src/18/README.md index df81bc46..eb9560d4 100644 --- a/src/18/README.md +++ b/src/18/README.md @@ -1,6 +1,103 @@ -
+
+
-{{#include ./source/README.md}} +{{#include ./template/README.md}}
+ +
+ +
+ + + +
+ +
+ +
+ + +
+
+ +```rust +{{#include ./template/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./template/src/lib.rs}} +``` + +
+ + + +
+ +
+ +
+ + + +
+
+ +```rust +{{#include ./solution/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/lib.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/tests.rs}} +``` + +
+ + + +
+ +
+ + +
+ + +
+
+ +```diff +{{#include ./template/template.diff}} +``` + +
+
+ +```diff +{{#include ./solution/solution.diff}} +``` + +
+ +
+ +
+
diff --git a/src/11/template/.gitignore b/src/18/solution/.gitignore similarity index 100% rename from src/11/template/.gitignore rename to src/18/solution/.gitignore diff --git a/src/11/template/Cargo.lock b/src/18/solution/Cargo.lock similarity index 100% rename from src/11/template/Cargo.lock rename to src/18/solution/Cargo.lock diff --git a/src/11/template/Cargo.toml b/src/18/solution/Cargo.toml similarity index 100% rename from src/11/template/Cargo.toml rename to src/18/solution/Cargo.toml diff --git a/src/11/solution/README.md b/src/18/solution/README.md similarity index 100% rename from src/11/solution/README.md rename to src/18/solution/README.md diff --git a/src/11/template/rustfmt.toml b/src/18/solution/rustfmt.toml similarity index 100% rename from src/11/template/rustfmt.toml rename to src/18/solution/rustfmt.toml diff --git a/src/18/solution/solution.diff b/src/18/solution/solution.diff new file mode 100644 index 00000000..7a8b90aa --- /dev/null +++ b/src/18/solution/solution.diff @@ -0,0 +1,47 @@ +diff --git a/src/impls.rs b/src/impls.rs +index 292909e1..c0e7cb27 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -3,10 +3,9 @@ use frame::prelude::*; + + impl Pallet { + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { +- /* 🚧 TODO 🚧: +- - `ensure!` that `Kitties` map does not `contains_key` for `dna`. +- - If it does, return `Error::::DuplicateKitty`. +- */ ++ // Check if the kitty does not already exist in our storage map ++ ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); ++ + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + Kitties::::insert(dna, ()); +diff --git a/src/lib.rs b/src/lib.rs +index 6fb4b6d7..75a1b65c 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -33,7 +33,7 @@ pub mod pallet { + #[pallet::error] + pub enum Error { + TooManyKitties, +- /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ ++ DuplicateKitty, + } + + #[pallet::call] +diff --git a/src/tests.rs b/src/tests.rs +index 8fca31e4..d5f99d2c 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -190,3 +190,11 @@ fn create_kitty_adds_to_map() { + assert_eq!(Kitties::::iter().count(), 1); + }) + } ++ ++#[test] ++fn cannot_mint_duplicate_kitty() { ++ new_test_ext().execute_with(|| { ++ assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); ++ assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); ++ }) ++} diff --git a/src/18/source/src/impls.rs b/src/18/solution/src/impls.rs similarity index 100% rename from src/18/source/src/impls.rs rename to src/18/solution/src/impls.rs diff --git a/src/18/source/src/lib.rs b/src/18/solution/src/lib.rs similarity index 100% rename from src/18/source/src/lib.rs rename to src/18/solution/src/lib.rs diff --git a/src/18/source/src/tests.rs b/src/18/solution/src/tests.rs similarity index 96% rename from src/18/source/src/tests.rs rename to src/18/solution/src/tests.rs index ea6bb5ba..d5f99d2c 100644 --- a/src/18/source/src/tests.rs +++ b/src/18/solution/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/18/source/.gitignore b/src/18/template/.gitignore similarity index 100% rename from src/18/source/.gitignore rename to src/18/template/.gitignore diff --git a/src/18/source/Cargo.lock b/src/18/template/Cargo.lock similarity index 100% rename from src/18/source/Cargo.lock rename to src/18/template/Cargo.lock diff --git a/src/18/source/Cargo.toml b/src/18/template/Cargo.toml similarity index 100% rename from src/18/source/Cargo.toml rename to src/18/template/Cargo.toml diff --git a/src/18/template/README.md b/src/18/template/README.md new file mode 100644 index 00000000..4e71c024 --- /dev/null +++ b/src/18/template/README.md @@ -0,0 +1,44 @@ +# Duplicate Kitty Check + +To make sure your state transition function behaves as expected, you must check everything that could go wrong, and return an error in those cases. + +## Contains Key + +As we mentioned in the previous section, the `insert` API will simply overwrite any existing data which is already there. We want to prevent this, else we risk overwriting data about a kitty that already exists and is already owned by someone. + +To prevent this, we can call the `contains_key(key)` API, which will return `true` if there is already a value in storage, or `false` if there isn't. + +You might ask: Why do we call `contains_key` rather than call `get` and checking the `Option`? + +Well, there are two reasons: + +1. Just like `StorageValue`, you can set a `StorageMap` with `QueryKind = ValueQuery`, thus we would no longer return an `Option` for you to check, and we would "hide" the fact that the map is empty at that key. So this API is the only way to check if the value ACTUALLY exists or not. +2. The `contains_key` API is strictly more efficient than the `get` API. The `get` API returns the value, which means we need to go into the database, read the bits, serialize it to a Rust type, pass that back to the Pallet, and assign it to the variable. + + On the other hand, `contains_key` can simply check if the value exists in the database, and only return back the boolean result. No need to serialize all the bytes, store the variable, or any of that. + +This same trick applies to `StorageValue` too. Rather than calling `get`, you can call `exists`, which provides the same api as `contains_key`. + +## Ensure + +To do simple duplication checks and return an error, we can write the following: + +```rust +if (PalletKitties::::contains_key(my_key)) { + return Err(Error::::DuplicateKitty.into()); +} +``` + +But that is pretty verbose. We have a macro which can do this for you in a single line: + +```rust +ensure!(!Kitties::::contains_key(my_key), Error::::DuplicateKitty); +``` + +`ensure!` is a macro, which basically expands into the same verbose code shown above, except checking the opposite condition. That is to say, we `ensure!` that `Kitties` does NOT `contains_key`, else we return the error specified. + +There really is no difference here, so use whatever makes you comfortable. + +## Your Turn + +Now that you know how to efficiently check for an existing kitty, put that check into the `mint` function to ensure that we do not mint a duplicate kitty in the `Kitties` storage map. diff --git a/src/18/source/rustfmt.toml b/src/18/template/rustfmt.toml similarity index 100% rename from src/18/source/rustfmt.toml rename to src/18/template/rustfmt.toml diff --git a/src/19/template/src/impls.rs b/src/18/template/src/impls.rs similarity index 73% rename from src/19/template/src/impls.rs rename to src/18/template/src/impls.rs index c0e7cb27..292909e1 100644 --- a/src/19/template/src/impls.rs +++ b/src/18/template/src/impls.rs @@ -3,9 +3,10 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); - + /* 🚧 TODO 🚧: + - `ensure!` that `Kitties` map does not `contains_key` for `dna`. + - If it does, return `Error::::DuplicateKitty`. + */ let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; Kitties::::insert(dna, ()); diff --git a/src/19/template/src/lib.rs b/src/18/template/src/lib.rs similarity index 80% rename from src/19/template/src/lib.rs rename to src/18/template/src/lib.rs index c8ea8705..6fb4b6d7 100644 --- a/src/19/template/src/lib.rs +++ b/src/18/template/src/lib.rs @@ -18,14 +18,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - /* 🚧 TODO 🚧: - - Create a new `struct` called `Kitty`. - - Make `Kitty` generic over `T` where `T: Config`. - - Add two fields to `Kitty`: - - `dna` which is type `[u8; 32]`. - - `owner` which is type `T::AccountId`. - */ - #[pallet::storage] pub(super) type CountForKitties = StorageValue; @@ -41,7 +33,7 @@ pub mod pallet { #[pallet::error] pub enum Error { TooManyKitties, - DuplicateKitty, + /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ } #[pallet::call] diff --git a/src/19/template/src/tests.rs b/src/18/template/src/tests.rs similarity index 91% rename from src/19/template/src/tests.rs rename to src/18/template/src/tests.rs index de02e5c6..8fca31e4 100644 --- a/src/19/template/src/tests.rs +++ b/src/18/template/src/tests.rs @@ -28,9 +28,6 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; -/* 🚧 TODO 🚧: - - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. -*/ #[runtime] mod runtime { @@ -52,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -111,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -195,11 +190,3 @@ fn create_kitty_adds_to_map() { assert_eq!(Kitties::::iter().count(), 1); }) } - -#[test] -fn cannot_mint_duplicate_kitty() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); - assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); - }) -} diff --git a/src/18/template/template.diff b/src/18/template/template.diff new file mode 100644 index 00000000..a2f2387e --- /dev/null +++ b/src/18/template/template.diff @@ -0,0 +1,27 @@ +diff --git a/src/impls.rs b/src/impls.rs +index 363f5e49..292909e1 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -3,6 +3,10 @@ use frame::prelude::*; + + impl Pallet { + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { ++ /* 🚧 TODO 🚧: ++ - `ensure!` that `Kitties` map does not `contains_key` for `dna`. ++ - If it does, return `Error::::DuplicateKitty`. ++ */ + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + Kitties::::insert(dna, ()); +diff --git a/src/lib.rs b/src/lib.rs +index 15fb5043..6fb4b6d7 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -33,6 +33,7 @@ pub mod pallet { + #[pallet::error] + pub enum Error { + TooManyKitties, ++ /* 🚧 TODO 🚧: Create a new error `DuplicateKitty`. */ + } + + #[pallet::call] diff --git a/src/19/README.md b/src/19/README.md index b3c92274..df81bc46 100644 --- a/src/19/README.md +++ b/src/19/README.md @@ -1,94 +1,6 @@ -
-
+
-{{#include ./template/README.md}} +{{#include ./source/README.md}}
- -
- -
- - - -
- -
- -
- - -
-
- -```rust -{{#include ./template/src/lib.rs}} -``` - -
- -
- -```rust -{{#include ./template/src/tests.rs}} -``` - -
- - - -
- -
- -
- - -
-
- -```rust -{{#include ./solution/src/lib.rs}} -``` - -
- -
- -```rust -{{#include ./solution/src/tests.rs}} -``` - -
- - - -
- -
- - -
- - -
-
- -```diff -{{#include ./template/template.diff}} -``` - -
-
- -```diff -{{#include ./solution/solution.diff}} -``` - -
- -
- -
-
diff --git a/src/19/solution/solution.diff b/src/19/solution/solution.diff deleted file mode 100644 index fd0658ba..00000000 --- a/src/19/solution/solution.diff +++ /dev/null @@ -1,39 +0,0 @@ -diff --git a/src/lib.rs b/src/lib.rs -index c8ea870..30d12b4 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,13 +18,11 @@ pub mod pallet { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - -- /* 🚧 TODO 🚧: -- - Create a new `struct` called `Kitty`. -- - Make `Kitty` generic over `T` where `T: Config`. -- - Add two fields to `Kitty`: -- - `dna` which is type `[u8; 32]`. -- - `owner` which is type `T::AccountId`. -- */ -+ pub struct Kitty { -+ // Using 32 bytes to represent a kitty DNA -+ pub dna: [u8; 32], -+ pub owner: T::AccountId, -+ } - - #[pallet::storage] - pub(super) type CountForKitties = StorageValue; -diff --git a/src/tests.rs b/src/tests.rs -index de02e5c..5778ff8 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -28,9 +28,8 @@ type Block = frame_system::mocking::MockBlock; - // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. - const ALICE: u64 = 1; - const BOB: u64 = 2; --/* 🚧 TODO 🚧: -- - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. --*/ -+#[allow(unused)] -+const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; - - #[runtime] - mod runtime { diff --git a/src/19/solution/.gitignore b/src/19/source/.gitignore similarity index 100% rename from src/19/solution/.gitignore rename to src/19/source/.gitignore diff --git a/src/19/solution/Cargo.lock b/src/19/source/Cargo.lock similarity index 100% rename from src/19/solution/Cargo.lock rename to src/19/source/Cargo.lock diff --git a/src/19/solution/Cargo.toml b/src/19/source/Cargo.toml similarity index 100% rename from src/19/solution/Cargo.toml rename to src/19/source/Cargo.toml diff --git a/src/18/source/README.md b/src/19/source/README.md similarity index 100% rename from src/18/source/README.md rename to src/19/source/README.md diff --git a/src/24/source/changes.diff b/src/19/source/changes.diff similarity index 100% rename from src/24/source/changes.diff rename to src/19/source/changes.diff diff --git a/src/19/solution/rustfmt.toml b/src/19/source/rustfmt.toml similarity index 100% rename from src/19/solution/rustfmt.toml rename to src/19/source/rustfmt.toml diff --git a/src/19/solution/src/impls.rs b/src/19/source/src/impls.rs similarity index 100% rename from src/19/solution/src/impls.rs rename to src/19/source/src/impls.rs diff --git a/src/19/solution/src/lib.rs b/src/19/source/src/lib.rs similarity index 89% rename from src/19/solution/src/lib.rs rename to src/19/source/src/lib.rs index 30d12b4c..75a1b65c 100644 --- a/src/19/solution/src/lib.rs +++ b/src/19/source/src/lib.rs @@ -18,12 +18,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - pub struct Kitty { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], - pub owner: T::AccountId, - } - #[pallet::storage] pub(super) type CountForKitties = StorageValue; diff --git a/src/19/solution/src/tests.rs b/src/19/source/src/tests.rs similarity index 95% rename from src/19/solution/src/tests.rs rename to src/19/source/src/tests.rs index 5778ff80..d5f99d2c 100644 --- a/src/19/solution/src/tests.rs +++ b/src/19/source/src/tests.rs @@ -28,8 +28,6 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; -#[allow(unused)] -const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] mod runtime { @@ -51,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/19/template/README.md b/src/19/template/README.md deleted file mode 100644 index dea845d6..00000000 --- a/src/19/template/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# Kitty Struct - -In this step, we will create a new struct which is generic over ``. This step will just be using basic Rust, so no macro-magic or Pallet specific stuff is happening here. - -## Creating a Struct - -Creating a new struct in Rust is pretty straight forward. - -```rust -pub struct Kitty { - pub dna: [u8; 32], - pub owner: u32, -} -``` - -One important detail for creating a struct to be used in a Pallet is that the struct should be marked `pub`. The compiler will complain if you try to use an object in your pallet without marking it `pub`, because it will be used across other modules in your blockchain. - -Creating a new instance of this struct is easy: - -```rust -let kitty = Kitty { - dna: [0u8; 32], - owner: 0u32, -} -``` - -## Creating a Generic Struct - -We will often want to access our Pallet specific types and store them. To do this, we need to make a struct which is generic over those types. - -The example we will use here is wanting to store the account of the owner of the kitty. - -There are actually two ways we can do this, and we will show you both. - -### Generic Over Each Type - -The first, and most verbose option in our situation is to make the struct generic over each type we want to use. - -```rust -pub struct Kitty { - pub dna: [u8; 32], - pub owner: AccountId, -} -``` - -If we want to use multiple generic types, we could just keep extending this pattern: - -```rust -pub struct Kitty { - pub dna: [u8; 32], - pub owner: AccountId, - pub created: BlockNumber, -} -``` - -When you reference this type, you will need to mention the generics that it is using: - -```rust -pub type MyAlias = Kitty; -``` - -The problem with this approach is that as you use more and more types, everything will just become excessively verbose. - -### Generic Over `T` - -The more common option is to make a struct generic over `` instead of the individual types. Through `T` you will then be able to access ALL the types from our blockchain. This is exactly why we created `T` to begin with! - -Let's look how that might look like: - -```rust -pub struct Kitty { - pub dna: [u8; 32], - pub owner: T::AccountId, -} -``` - -In this context, you can see you can access any of the runtime types by doing `T::Type`. - -It also becomes a lot easier to reference this type, no matter how many different runtime types you are using: - -```rust -pub type MyAlias = Kitty; -``` - -I want to be clear, in the final compiled binary, both options for creating a generic struct are exactly the same. There are some nuanced advantages to both options, but these details are really at a much lower level than we will go over in this tutorial. - -## Your Turn - -Now that you know how to create a generic struct, create a new `Kitty` struct which is generic over ``. It should have fields for the `dna` of the kitty and the `owner` of the kitty. - -In our next step, we will learn how we can actually use this struct in runtime storage. - -### Tests - -We can't really "test" a struct, but we will use instances of the `Kitty` struct in other tests. - -So for this step, we simply introduce a `const DEFAULT_KITTY` to access a basic copy of the `Kitty` struct: - -```rust -const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; -``` - -Remember if we update the `Kitty` struct in the future with more fields, we will also need to update this constant. diff --git a/src/19/template/template.diff b/src/19/template/template.diff deleted file mode 100644 index aad996d7..00000000 --- a/src/19/template/template.diff +++ /dev/null @@ -1,33 +0,0 @@ -diff --git a/src/lib.rs b/src/lib.rs -index 75a1b65..c8ea870 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,6 +18,14 @@ pub mod pallet { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } - -+ /* 🚧 TODO 🚧: -+ - Create a new `struct` called `Kitty`. -+ - Make `Kitty` generic over `T` where `T: Config`. -+ - Add two fields to `Kitty`: -+ - `dna` which is type `[u8; 32]`. -+ - `owner` which is type `T::AccountId`. -+ */ -+ - #[pallet::storage] - pub(super) type CountForKitties = StorageValue; - -diff --git a/src/tests.rs b/src/tests.rs -index ea6bb5b..de02e5c 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -28,6 +28,9 @@ type Block = frame_system::mocking::MockBlock; - // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. - const ALICE: u64 = 1; - const BOB: u64 = 2; -+/* 🚧 TODO 🚧: -+ - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. -+*/ - - #[runtime] - mod runtime { diff --git a/src/2/source/changes.diff b/src/2/source/changes.diff index 2ca69121..b185aed5 100644 --- a/src/2/source/changes.diff +++ b/src/2/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/lib.rs b/src/lib.rs -index ae8a09b..df435c8 100644 +index ae8a09bc..df435c8a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ diff --git a/src/2/source/src/tests.rs b/src/2/source/src/tests.rs index 6d271d5e..7e6f34da 100644 --- a/src/2/source/src/tests.rs +++ b/src/2/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/20/README.md b/src/20/README.md index eb9560d4..b3c92274 100644 --- a/src/20/README.md +++ b/src/20/README.md @@ -17,21 +17,21 @@
- - + +
-
+
```rust -{{#include ./template/src/impls.rs}} +{{#include ./template/src/lib.rs}} ```
-
+
```rust -{{#include ./template/src/lib.rs}} +{{#include ./template/src/tests.rs}} ```
@@ -43,19 +43,10 @@
- - +
-
- -```rust -{{#include ./solution/src/impls.rs}} -``` - -
- -
+
```rust {{#include ./solution/src/lib.rs}} diff --git a/src/20/solution/solution.diff b/src/20/solution/solution.diff index 0914c74f..5c8dde59 100644 --- a/src/20/solution/solution.diff +++ b/src/20/solution/solution.diff @@ -1,97 +1,39 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 741a6ff..5d3c104 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -3,17 +3,13 @@ use frame::prelude::*; - - impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -- /* 🚧 TODO 🚧: -- - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. -- */ -- -+ let kitty = Kitty { dna, owner: owner.clone() }; - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); - - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -- /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ -- Kitties::::insert(dna, ()); -+ Kitties::::insert(dna, kitty); - CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); - Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 68f57e1..aa6ca3b 100644 +index c8ea8705..30d12b4c 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -18,10 +18,8 @@ pub mod pallet { +@@ -18,13 +18,11 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - /* 🚧 TODO 🚧: -- - Add the derive macros needed for putting a struct in storage. -- - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. +- - Create a new `struct` called `Kitty`. +- - Make `Kitty` generic over `T` where `T: Config`. +- - Add two fields to `Kitty`: +- - `dna` which is type `[u8; 32]`. +- - `owner` which is type `T::AccountId`. - */ -+ #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] -+ #[scale_info(skip_type_params(T))] - pub struct Kitty { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], -@@ -32,8 +30,7 @@ pub mod pallet { - pub(super) type CountForKitties = StorageValue; ++ pub struct Kitty { ++ // Using 32 bytes to represent a kitty DNA ++ pub dna: [u8; 32], ++ pub owner: T::AccountId, ++ } #[pallet::storage] -- /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ -- pub(super) type Kitties = StorageMap; -+ pub(super) type Kitties = StorageMap>; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub(super) type CountForKitties = StorageValue; diff --git a/src/tests.rs b/src/tests.rs -index 5778ff8..138a084 100644 +index 44dd98f9..f3652059 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -28,7 +28,6 @@ type Block = frame_system::mocking::MockBlock; +@@ -28,9 +28,8 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; --#[allow(unused)] - const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; +-/* 🚧 TODO 🚧: +- - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. +-*/ ++#[allow(unused)] ++const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] -@@ -182,7 +181,7 @@ fn kitties_map_created_correctly() { - new_test_ext().execute_with(|| { - let zero_key = [0u8; 32]; - assert!(!Kitties::::contains_key(zero_key)); -- Kitties::::insert(zero_key, ()); -+ Kitties::::insert(zero_key, DEFAULT_KITTY); - assert!(Kitties::::contains_key(zero_key)); - }) - } -@@ -202,3 +201,24 @@ fn cannot_mint_duplicate_kitty() { - assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); - }) - } -+ -+#[test] -+fn kitty_struct_has_expected_traits() { -+ new_test_ext().execute_with(|| { -+ let kitty = DEFAULT_KITTY; -+ let bytes = kitty.encode(); -+ let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); -+ assert!(Kitty::::max_encoded_len() > 0); -+ let _info = Kitty::::type_info(); -+ }) -+} -+ -+#[test] -+fn mint_stores_owner_in_kitty() { -+ new_test_ext().execute_with(|| { -+ assert_ok!(PalletKitties::mint(1337, [42u8; 32])); -+ let kitty = Kitties::::get([42u8; 32]).unwrap(); -+ assert_eq!(kitty.owner, 1337); -+ assert_eq!(kitty.dna, [42u8; 32]); -+ }) -+} + mod runtime { diff --git a/src/20/solution/src/impls.rs b/src/20/solution/src/impls.rs index 5d3c1046..c0e7cb27 100644 --- a/src/20/solution/src/impls.rs +++ b/src/20/solution/src/impls.rs @@ -3,13 +3,12 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, kitty); + Kitties::::insert(dna, ()); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/20/solution/src/lib.rs b/src/20/solution/src/lib.rs index aa6ca3b9..30d12b4c 100644 --- a/src/20/solution/src/lib.rs +++ b/src/20/solution/src/lib.rs @@ -18,8 +18,6 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(T))] pub struct Kitty { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], @@ -30,7 +28,7 @@ pub mod pallet { pub(super) type CountForKitties = StorageValue; #[pallet::storage] - pub(super) type Kitties = StorageMap>; + pub(super) type Kitties = StorageMap; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/20/solution/src/tests.rs b/src/20/solution/src/tests.rs index 138a0842..f3652059 100644 --- a/src/20/solution/src/tests.rs +++ b/src/20/solution/src/tests.rs @@ -28,6 +28,7 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; +#[allow(unused)] const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] @@ -50,15 +51,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -181,7 +180,7 @@ fn kitties_map_created_correctly() { new_test_ext().execute_with(|| { let zero_key = [0u8; 32]; assert!(!Kitties::::contains_key(zero_key)); - Kitties::::insert(zero_key, DEFAULT_KITTY); + Kitties::::insert(zero_key, ()); assert!(Kitties::::contains_key(zero_key)); }) } @@ -201,24 +200,3 @@ fn cannot_mint_duplicate_kitty() { assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); }) } - -#[test] -fn kitty_struct_has_expected_traits() { - new_test_ext().execute_with(|| { - let kitty = DEFAULT_KITTY; - let bytes = kitty.encode(); - let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); - assert!(Kitty::::max_encoded_len() > 0); - let _info = Kitty::::type_info(); - }) -} - -#[test] -fn mint_stores_owner_in_kitty() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::mint(1337, [42u8; 32])); - let kitty = Kitties::::get([42u8; 32]).unwrap(); - assert_eq!(kitty.owner, 1337); - assert_eq!(kitty.dna, [42u8; 32]); - }) -} diff --git a/src/20/template/README.md b/src/20/template/README.md index 387d3040..dea845d6 100644 --- a/src/20/template/README.md +++ b/src/20/template/README.md @@ -1,130 +1,103 @@ -# Storing a Struct +# Kitty Struct -We have successfully created a generic struct for our pallet. Now we need to actually use it in our runtime. +In this step, we will create a new struct which is generic over ``. This step will just be using basic Rust, so no macro-magic or Pallet specific stuff is happening here. -## Derive Macros +## Creating a Struct -One of the powerful tools you get from vanilla Rust is the `#[derive(...)]` macros. - -In the spirit of Rust macros, derive macros help reduce boiler plate code which can be automatically generated for you. In this case, the derive macros generate trait implementations for the objects they are applied on. - -The most simple example might be `Default`. - -The verbose way of implementing `Default` would be: +Creating a new struct in Rust is pretty straight forward. ```rust -pub struct MyObject { - field_0: u32, - field_1: u64, -} - -impl Default for MyObject { - fn default() -> Self { - Self { - field_0: Default::default(), - field_1: Default::default(), - } - } +pub struct Kitty { + pub dna: [u8; 32], + pub owner: u32, } ``` -You can see here that we can simply say the default for `MyObject` is taking the default of each field in `MyObject` and constructing the struct. +One important detail for creating a struct to be used in a Pallet is that the struct should be marked `pub`. The compiler will complain if you try to use an object in your pallet without marking it `pub`, because it will be used across other modules in your blockchain. -We can do the exact same thing with `#[derive(Default)]`: +Creating a new instance of this struct is easy: ```rust -#[derive(Default)] -pub struct MyObject { - field_0: u32, - field_1: u64, +let kitty = Kitty { + dna: [0u8; 32], + owner: 0u32, } ``` -As long as all the fields inside `MyObject` implement `Default`, then the derive macro will handle all the magic. - -> Remember that `T::AccountId` explicitly chooses not to implement `Default`, so you cannot implement `Default` on the `Kitty` struct. - -## Traits Required for Storage +## Creating a Generic Struct -For an object to be placed inside runtime storage, we require it to have a number of traits implemented: +We will often want to access our Pallet specific types and store them. To do this, we need to make a struct which is generic over those types. -- `Encode`: The object must be encodable to bytes using `parity_scale_codec`. -- `Decode`: The object must be decodable from bytes using `parity_scale_codec`. -- `MaxEncodedLen`: When the object is encoded, it must have an upper limit to its size. -- `TypeInfo`: The object must be able to generate metadata describing the object. +The example we will use here is wanting to store the account of the owner of the kitty. -All of these things are pretty specific to the requirements of using the Polkadot-SDK for building a blockchain. +There are actually two ways we can do this, and we will show you both. -### Parity SCALE Codec +### Generic Over Each Type -Parity SCALE Codec is a custom encoding and decoding library used in the `polkadot-sdk`. +The first, and most verbose option in our situation is to make the struct generic over each type we want to use. -The first question we are always asked when talking about SCALE, is why don't we use `` instead? - -Well, SCALE is: - -- Simple to define. -- Not Rust-specific (but happens to work great in Rust). - - Easy to derive codec logic: `#[derive(Encode, Decode)]` - - Viable and useful for APIs like: `MaxEncodedLen` and `TypeInfo` - - It does not use Rust `std`, and thus can compile to Wasm `no_std`. -- Consensus critical / bijective; one value will always encode to one blob and that blob will only decode to that value. -- Supports a copy-free decode for basic types on LE architectures (like Wasm). -- It is about as thin and lightweight as can be. - -What you need to know about SCALE is that it defines how every object in the `polkadot-sdk` is represented in bytes. - -### Max Encoded Length - -Now that we have the tools to define the way objects should be encoded, we are able to create a trait which tracks the maximum encoded length of an object: `MaxEncodedLen`. - -We then use that information to predict in the worst case scenario how much data will be used when we store it. +```rust +pub struct Kitty { + pub dna: [u8; 32], + pub owner: AccountId, +} +``` -- For a `u8`, the `max_encoded_len()` is always the same: 1 byte. -- For a `u64`, the `max_encoded_len()` is always the same: 8 bytes. -- For a basic `enum`, it is also just 1 byte, since an enum can represent up to 256 variants. -- For a `struct`, the `max_encoded_len()` will be the sum of the `max_encoded_len()` of all items in the `struct`. +If we want to use multiple generic types, we could just keep extending this pattern: -We need to be able to predict the size of items in our storage because it affects nearly all the constraints of our blockchain: storage size, memory usage, network bandwidth, and even execution time for encoding and decoding. +```rust +pub struct Kitty { + pub dna: [u8; 32], + pub owner: AccountId, + pub created: BlockNumber, +} +``` -### Type Info +When you reference this type, you will need to mention the generics that it is using: -The last required trait for any storage item is `TypeInfo`. +```rust +pub type MyAlias = Kitty; +``` -This trait is key for off-chain interactions with your blockchain. It is used to generate metadata for all the objects and types in your blockchain. +The problem with this approach is that as you use more and more types, everything will just become excessively verbose. -Metadata exposes all the details of your blockchain to the outside world, allowing us to dynamically construct APIs to interact with the blockchain. This is super relevant since the `polkadot-sdk` is a framework for modular and upgradable blockchains. +### Generic Over `T` -We won't really use this in this tutorial, but it is super relevant to learn about once you start getting ready to actually use your blockchain. +The more common option is to make a struct generic over `` instead of the individual types. Through `T` you will then be able to access ALL the types from our blockchain. This is exactly why we created `T` to begin with! -#### Skip Type Params +Let's look how that might look like: -One nasty thing about the `TypeInfo` derive macro, is that it isn't very "smart". +```rust +pub struct Kitty { + pub dna: [u8; 32], + pub owner: T::AccountId, +} +``` -As I mentioned, the whole point of `TypeInfo` is to generate relevant metadata about the types used in your blockchain. However, part of our `Kitty` type is the generic parameter `T`, and it really does not make any sense to generate `TypeInfo` for `T`. +In this context, you can see you can access any of the runtime types by doing `T::Type`. -To make `TypeInfo` work while we have `T`, we need to include the additional line: +It also becomes a lot easier to reference this type, no matter how many different runtime types you are using: ```rust -#[scale_info(skip_type_params(T))] +pub type MyAlias = Kitty; ``` -This tells the `TypeInfo` derive macro to simply "skip" the `T` type parameter when generating its code. The best thing for you to do is try compiling your code without this additional line, look at the errors that are generated, then see them disappear with the `skip_type_params`. - -Then in the future, if you run into this error again, you will know what to do. +I want to be clear, in the final compiled binary, both options for creating a generic struct are exactly the same. There are some nuanced advantages to both options, but these details are really at a much lower level than we will go over in this tutorial. ## Your Turn -Now that you know all about the various traits required for runtime development, derive them on the `Kitty` struct. +Now that you know how to create a generic struct, create a new `Kitty` struct which is generic over ``. It should have fields for the `dna` of the kitty and the `owner` of the kitty. -Don't forget to include the `skip_type_params(T)`. +In our next step, we will learn how we can actually use this struct in runtime storage. -After that, update your `Kitties` map to use `Value = Kitty`. +### Tests -Finally, update the logic in `mint` to create and insert this object into storage. +We can't really "test" a struct, but we will use instances of the `Kitty` struct in other tests. -## Learn More +So for this step, we simply introduce a `const DEFAULT_KITTY` to access a basic copy of the `Kitty` struct: -To get a primer on Parity SCALE Codec, check out this video from the Polkadot Blockchain Academy: +```rust +const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; +``` - +Remember if we update the `Kitty` struct in the future with more fields, we will also need to update this constant. diff --git a/src/20/template/src/impls.rs b/src/20/template/src/impls.rs index 741a6ffd..c0e7cb27 100644 --- a/src/20/template/src/impls.rs +++ b/src/20/template/src/impls.rs @@ -3,16 +3,11 @@ use frame::prelude::*; impl Pallet { pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - /* 🚧 TODO 🚧: - - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. - */ - // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ Kitties::::insert(dna, ()); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); diff --git a/src/20/template/src/lib.rs b/src/20/template/src/lib.rs index 68f57e11..c8ea8705 100644 --- a/src/20/template/src/lib.rs +++ b/src/20/template/src/lib.rs @@ -19,20 +19,17 @@ pub mod pallet { } /* 🚧 TODO 🚧: - - Add the derive macros needed for putting a struct in storage. - - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. + - Create a new `struct` called `Kitty`. + - Make `Kitty` generic over `T` where `T: Config`. + - Add two fields to `Kitty`: + - `dna` which is type `[u8; 32]`. + - `owner` which is type `T::AccountId`. */ - pub struct Kitty { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], - pub owner: T::AccountId, - } #[pallet::storage] pub(super) type CountForKitties = StorageValue; #[pallet::storage] - /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ pub(super) type Kitties = StorageMap; #[pallet::event] diff --git a/src/20/template/src/tests.rs b/src/20/template/src/tests.rs index 5778ff80..44dd98f9 100644 --- a/src/20/template/src/tests.rs +++ b/src/20/template/src/tests.rs @@ -28,8 +28,9 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; -#[allow(unused)] -const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; +/* 🚧 TODO 🚧: + - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. +*/ #[runtime] mod runtime { @@ -51,15 +52,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +111,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/20/template/template.diff b/src/20/template/template.diff index a376b387..3118a801 100644 --- a/src/20/template/template.diff +++ b/src/20/template/template.diff @@ -1,44 +1,33 @@ -diff --git a/src/impls.rs b/src/impls.rs -index c0e7cb2..741a6ff 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -3,11 +3,16 @@ use frame::prelude::*; - - impl Pallet { - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -+ /* 🚧 TODO 🚧: -+ - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. -+ */ -+ - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); - - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -+ /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ - Kitties::::insert(dna, ()); - CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); diff --git a/src/lib.rs b/src/lib.rs -index 30d12b4..68f57e1 100644 +index 75a1b65c..c8ea8705 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -18,6 +18,10 @@ pub mod pallet { +@@ -18,6 +18,14 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + /* 🚧 TODO 🚧: -+ - Add the derive macros needed for putting a struct in storage. -+ - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. ++ - Create a new `struct` called `Kitty`. ++ - Make `Kitty` generic over `T` where `T: Config`. ++ - Add two fields to `Kitty`: ++ - `dna` which is type `[u8; 32]`. ++ - `owner` which is type `T::AccountId`. + */ - pub struct Kitty { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], -@@ -28,6 +32,7 @@ pub mod pallet { ++ + #[pallet::storage] pub(super) type CountForKitties = StorageValue; - #[pallet::storage] -+ /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ - pub(super) type Kitties = StorageMap; +diff --git a/src/tests.rs b/src/tests.rs +index d5f99d2c..44dd98f9 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -28,6 +28,9 @@ type Block = frame_system::mocking::MockBlock; + // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. + const ALICE: u64 = 1; + const BOB: u64 = 2; ++/* 🚧 TODO 🚧: ++ - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. ++*/ - #[pallet::event] + #[runtime] + mod runtime { diff --git a/src/21/solution/solution.diff b/src/21/solution/solution.diff index e0da2bea..78dfcbfc 100644 --- a/src/21/solution/solution.diff +++ b/src/21/solution/solution.diff @@ -1,72 +1,97 @@ diff --git a/src/impls.rs b/src/impls.rs -index fac2ead..ff69cae 100644 +index 741a6ffd..5d3c1046 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -1,18 +1,22 @@ - use super::*; - use frame::prelude::*; --/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ --/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ -+use frame::primitives::BlakeTwo256; -+use frame::traits::Hash; +@@ -3,17 +3,13 @@ use frame::prelude::*; impl Pallet { -- /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. -- - Create a `unique_payload` which contains data from `frame_system::Pallet::`: -- - `parent_hash` -- - `block_number` -- - `extrinsic_index` -- - `CountForKitties::::get()` -- - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. -- - Return the hash as a `[u8; 32]`. -- */ -+ // Generates and returns DNA -+ pub fn gen_dna() -> [u8; 32] { -+ // Create randomness payload. Multiple kitties can be generated in the same block, -+ // retaining uniqueness. -+ let unique_payload = ( -+ frame_system::Pallet::::parent_hash(), -+ frame_system::Pallet::::block_number(), -+ frame_system::Pallet::::extrinsic_index(), -+ CountForKitties::::get(), -+ ); -+ -+ BlakeTwo256::hash_of(&unique_payload).into() -+ } - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone() }; +- /* 🚧 TODO 🚧: +- - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. +- */ +- ++ let kitty = Kitty { dna, owner: owner.clone() }; + // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); + + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; +- /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ +- Kitties::::insert(dna, ()); ++ Kitties::::insert(dna, kitty); + CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); + Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 34507f3..8795b0e 100644 +index 68f57e11..aa6ca3b9 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -48,8 +48,7 @@ pub mod pallet { - impl Pallet { - pub fn create_kitty(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; -- /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ -- let dna = [0u8; 32]; -+ let dna = Self::gen_dna(); - Self::mint(who, dna)?; - Ok(()) - } +@@ -18,10 +18,8 @@ pub mod pallet { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + +- /* 🚧 TODO 🚧: +- - Add the derive macros needed for putting a struct in storage. +- - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. +- */ ++ #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] ++ #[scale_info(skip_type_params(T))] + pub struct Kitty { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], +@@ -32,8 +30,7 @@ pub mod pallet { + pub(super) type CountForKitties = StorageValue; + + #[pallet::storage] +- /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ +- pub(super) type Kitties = StorageMap; ++ pub(super) type Kitties = StorageMap>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/tests.rs b/src/tests.rs -index 138a084..7803005 100644 +index f3652059..3d5e39d9 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -222,3 +222,15 @@ fn mint_stores_owner_in_kitty() { - assert_eq!(kitty.dna, [42u8; 32]); +@@ -28,7 +28,6 @@ type Block = frame_system::mocking::MockBlock; + // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. + const ALICE: u64 = 1; + const BOB: u64 = 2; +-#[allow(unused)] + const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; + + #[runtime] +@@ -180,7 +179,7 @@ fn kitties_map_created_correctly() { + new_test_ext().execute_with(|| { + let zero_key = [0u8; 32]; + assert!(!Kitties::::contains_key(zero_key)); +- Kitties::::insert(zero_key, ()); ++ Kitties::::insert(zero_key, DEFAULT_KITTY); + assert!(Kitties::::contains_key(zero_key)); + }) + } +@@ -200,3 +199,24 @@ fn cannot_mint_duplicate_kitty() { + assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); }) } + +#[test] -+fn create_kitty_makes_unique_kitties() { ++fn kitty_struct_has_expected_traits() { ++ new_test_ext().execute_with(|| { ++ let kitty = DEFAULT_KITTY; ++ let bytes = kitty.encode(); ++ let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); ++ assert!(Kitty::::max_encoded_len() > 0); ++ let _info = Kitty::::type_info(); ++ }) ++} ++ ++#[test] ++fn mint_stores_owner_in_kitty() { + new_test_ext().execute_with(|| { -+ // Two calls to `create_kitty` should work. -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); -+ // And should result in two kitties in our system. -+ assert_eq!(CountForKitties::::get(), 2); -+ assert_eq!(Kitties::::iter().count(), 2); ++ assert_ok!(PalletKitties::mint(1337, [42u8; 32])); ++ let kitty = Kitties::::get([42u8; 32]).unwrap(); ++ assert_eq!(kitty.owner, 1337); ++ assert_eq!(kitty.dna, [42u8; 32]); + }) +} diff --git a/src/21/solution/src/impls.rs b/src/21/solution/src/impls.rs index ff69caec..5d3c1046 100644 --- a/src/21/solution/src/impls.rs +++ b/src/21/solution/src/impls.rs @@ -1,23 +1,7 @@ use super::*; use frame::prelude::*; -use frame::primitives::BlakeTwo256; -use frame::traits::Hash; impl Pallet { - // Generates and returns DNA - pub fn gen_dna() -> [u8; 32] { - // Create randomness payload. Multiple kitties can be generated in the same block, - // retaining uniqueness. - let unique_payload = ( - frame_system::Pallet::::parent_hash(), - frame_system::Pallet::::block_number(), - frame_system::Pallet::::extrinsic_index(), - CountForKitties::::get(), - ); - - BlakeTwo256::hash_of(&unique_payload).into() - } - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map diff --git a/src/21/solution/src/lib.rs b/src/21/solution/src/lib.rs index 8795b0e1..aa6ca3b9 100644 --- a/src/21/solution/src/lib.rs +++ b/src/21/solution/src/lib.rs @@ -48,7 +48,7 @@ pub mod pallet { impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let dna = Self::gen_dna(); + let dna = [0u8; 32]; Self::mint(who, dna)?; Ok(()) } diff --git a/src/21/solution/src/tests.rs b/src/21/solution/src/tests.rs index 78030053..3d5e39d9 100644 --- a/src/21/solution/src/tests.rs +++ b/src/21/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -222,15 +220,3 @@ fn mint_stores_owner_in_kitty() { assert_eq!(kitty.dna, [42u8; 32]); }) } - -#[test] -fn create_kitty_makes_unique_kitties() { - new_test_ext().execute_with(|| { - // Two calls to `create_kitty` should work. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); - // And should result in two kitties in our system. - assert_eq!(CountForKitties::::get(), 2); - assert_eq!(Kitties::::iter().count(), 2); - }) -} diff --git a/src/21/template/README.md b/src/21/template/README.md index 37f9a647..387d3040 100644 --- a/src/21/template/README.md +++ b/src/21/template/README.md @@ -1,51 +1,130 @@ -# Generate Unique DNA +# Storing a Struct -In this step, we will show how to generate uniqueness using information from the blockchain. +We have successfully created a generic struct for our pallet. Now we need to actually use it in our runtime. -## Randomness +## Derive Macros -Ideally, we would give every kitty we mint a randomly generated DNA. +One of the powerful tools you get from vanilla Rust is the `#[derive(...)]` macros. -However generating randomness on a blockchain is extremely difficult because we must come to consensus over all logic and data used on the blockchain. Any kind of randomness function must generate exactly the same randomness for all nodes. And if that is the case, it is still possible to influence randomness as a block producer by choosing NOT to build a block with a randomness you do not like. +In the spirit of Rust macros, derive macros help reduce boiler plate code which can be automatically generated for you. In this case, the derive macros generate trait implementations for the objects they are applied on. -Polkadot does provide access to a verifiable random function (VRF), but exactly the properties of this VRF and how to use it is beyond the scope of this tutorial. Not to mention we are also iteratively improving the VRF provided by Polkadot. +The most simple example might be `Default`. -## Uniqueness +The verbose way of implementing `Default` would be: -So rather than using true randomness, we will instead try to generate uniqueness. +```rust +pub struct MyObject { + field_0: u32, + field_1: u64, +} + +impl Default for MyObject { + fn default() -> Self { + Self { + field_0: Default::default(), + field_1: Default::default(), + } + } +} +``` + +You can see here that we can simply say the default for `MyObject` is taking the default of each field in `MyObject` and constructing the struct. + +We can do the exact same thing with `#[derive(Default)]`: + +```rust +#[derive(Default)] +pub struct MyObject { + field_0: u32, + field_1: u64, +} +``` + +As long as all the fields inside `MyObject` implement `Default`, then the derive macro will handle all the magic. + +> Remember that `T::AccountId` explicitly chooses not to implement `Default`, so you cannot implement `Default` on the `Kitty` struct. + +## Traits Required for Storage + +For an object to be placed inside runtime storage, we require it to have a number of traits implemented: + +- `Encode`: The object must be encodable to bytes using `parity_scale_codec`. +- `Decode`: The object must be decodable from bytes using `parity_scale_codec`. +- `MaxEncodedLen`: When the object is encoded, it must have an upper limit to its size. +- `TypeInfo`: The object must be able to generate metadata describing the object. + +All of these things are pretty specific to the requirements of using the Polkadot-SDK for building a blockchain. + +### Parity SCALE Codec + +Parity SCALE Codec is a custom encoding and decoding library used in the `polkadot-sdk`. + +The first question we are always asked when talking about SCALE, is why don't we use `` instead? -There are different levels of uniqueness we can achieve using data from our blockchain. +Well, SCALE is: -- `frame_system::Pallet::::parent_hash()`: The hash of the previous block. This will ensure uniqueness for every fork of the blockchain. -- `frame_system::Pallet::::block_number()`: The number of the current block. This will obviously be unique for each block. -- `frame_system::Pallet::::extrinsic_index()`: The number of the extrinsic in the block that is being executed. This will be unique for each extrinsic in a block. -- `CountForKitties::::get()`: The number of kitties in our blockchain. +- Simple to define. +- Not Rust-specific (but happens to work great in Rust). + - Easy to derive codec logic: `#[derive(Encode, Decode)]` + - Viable and useful for APIs like: `MaxEncodedLen` and `TypeInfo` + - It does not use Rust `std`, and thus can compile to Wasm `no_std`. +- Consensus critical / bijective; one value will always encode to one blob and that blob will only decode to that value. +- Supports a copy-free decode for basic types on LE architectures (like Wasm). +- It is about as thin and lightweight as can be. -If we combine all of these things together, we can ensure that every kitty we mint will be unique, no matter: +What you need to know about SCALE is that it defines how every object in the `polkadot-sdk` is represented in bytes. -- Which block it comes in. -- How many extrinsics are in a block. -- Or even if a single extrinsic mints multiple kitties. +### Max Encoded Length -## Hash +Now that we have the tools to define the way objects should be encoded, we are able to create a trait which tracks the maximum encoded length of an object: `MaxEncodedLen`. -Obviously our uniqueness inputs are not super useful as is. But we can convert these inputs into a unique set of bytes with fixed length using a Hash function like [`frame::primitives::BlakeTwo256`](https://docs.rs/polkadot-sdk-frame/0.7.0/polkadot_sdk_frame/primitives/struct.BlakeTwo256.html). +We then use that information to predict in the worst case scenario how much data will be used when we store it. + +- For a `u8`, the `max_encoded_len()` is always the same: 1 byte. +- For a `u64`, the `max_encoded_len()` is always the same: 8 bytes. +- For a basic `enum`, it is also just 1 byte, since an enum can represent up to 256 variants. +- For a `struct`, the `max_encoded_len()` will be the sum of the `max_encoded_len()` of all items in the `struct`. + +We need to be able to predict the size of items in our storage because it affects nearly all the constraints of our blockchain: storage size, memory usage, network bandwidth, and even execution time for encoding and decoding. + +### Type Info + +The last required trait for any storage item is `TypeInfo`. + +This trait is key for off-chain interactions with your blockchain. It is used to generate metadata for all the objects and types in your blockchain. + +Metadata exposes all the details of your blockchain to the outside world, allowing us to dynamically construct APIs to interact with the blockchain. This is super relevant since the `polkadot-sdk` is a framework for modular and upgradable blockchains. + +We won't really use this in this tutorial, but it is super relevant to learn about once you start getting ready to actually use your blockchain. + +#### Skip Type Params + +One nasty thing about the `TypeInfo` derive macro, is that it isn't very "smart". + +As I mentioned, the whole point of `TypeInfo` is to generate relevant metadata about the types used in your blockchain. However, part of our `Kitty` type is the generic parameter `T`, and it really does not make any sense to generate `TypeInfo` for `T`. + +To make `TypeInfo` work while we have `T`, we need to include the additional line: ```rust -// Collect our unique inputs into a single object. -let unique_payload = (item1, item2, item3); -// To use the `hash_of` API, we need to bring the `Hash` trait into scope. -use frame::traits::Hash; -// Hash that object to get a unique identifier. -let hash: [u8; 32] = BlakeTwo256::hash_of(&unique_payload).into(); +#[scale_info(skip_type_params(T))] ``` -The `hash_of` API comes from the `Hash` trait and takes any `encode`-able object, and returns a `H256`, which is a 256-bit hash. As you can see in the code above, it is easy to convert that to a `[u8; 32]` by just calling `.into()`, since these two types are equivalent. +This tells the `TypeInfo` derive macro to simply "skip" the `T` type parameter when generating its code. The best thing for you to do is try compiling your code without this additional line, look at the errors that are generated, then see them disappear with the `skip_type_params`. -Another nice thing about using a hash is you get some sense of pseudo-randomness between the input and output. This means that two kitties which are minted right after one another could have totally different DNA, which could be useful if you want to associate unique attributes to the different parts of their DNA. πŸ€” +Then in the future, if you run into this error again, you will know what to do. ## Your Turn -Now that you know how to acquire uniqueness from your blockchain, and how to hash those items, create a new function called `fn gen_dna() -> [u8; 32];` which does these steps to create unique DNA for each kitty that is minted. +Now that you know all about the various traits required for runtime development, derive them on the `Kitty` struct. + +Don't forget to include the `skip_type_params(T)`. + +After that, update your `Kitties` map to use `Value = Kitty`. + +Finally, update the logic in `mint` to create and insert this object into storage. + +## Learn More + +To get a primer on Parity SCALE Codec, check out this video from the Polkadot Blockchain Academy: -Update your `create_kitty` extrinsic to generate and use this unique DNA. + diff --git a/src/21/template/src/impls.rs b/src/21/template/src/impls.rs index fac2eada..741a6ffd 100644 --- a/src/21/template/src/impls.rs +++ b/src/21/template/src/impls.rs @@ -1,27 +1,19 @@ use super::*; use frame::prelude::*; -/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ -/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ impl Pallet { - /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. - - Create a `unique_payload` which contains data from `frame_system::Pallet::`: - - `parent_hash` - - `block_number` - - `extrinsic_index` - - `CountForKitties::::get()` - - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. - - Return the hash as a `[u8; 32]`. - */ - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone() }; + /* 🚧 TODO 🚧: + - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. + */ + // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - Kitties::::insert(dna, kitty); + /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ + Kitties::::insert(dna, ()); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/21/template/src/lib.rs b/src/21/template/src/lib.rs index 34507f3b..68f57e11 100644 --- a/src/21/template/src/lib.rs +++ b/src/21/template/src/lib.rs @@ -18,8 +18,10 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(T))] + /* 🚧 TODO 🚧: + - Add the derive macros needed for putting a struct in storage. + - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. + */ pub struct Kitty { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], @@ -30,7 +32,8 @@ pub mod pallet { pub(super) type CountForKitties = StorageValue; #[pallet::storage] - pub(super) type Kitties = StorageMap>; + /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ + pub(super) type Kitties = StorageMap; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -48,7 +51,6 @@ pub mod pallet { impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ let dna = [0u8; 32]; Self::mint(who, dna)?; Ok(()) diff --git a/src/21/template/src/tests.rs b/src/21/template/src/tests.rs index 138a0842..f3652059 100644 --- a/src/21/template/src/tests.rs +++ b/src/21/template/src/tests.rs @@ -28,6 +28,7 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; +#[allow(unused)] const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] @@ -50,15 +51,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -181,7 +180,7 @@ fn kitties_map_created_correctly() { new_test_ext().execute_with(|| { let zero_key = [0u8; 32]; assert!(!Kitties::::contains_key(zero_key)); - Kitties::::insert(zero_key, DEFAULT_KITTY); + Kitties::::insert(zero_key, ()); assert!(Kitties::::contains_key(zero_key)); }) } @@ -201,24 +200,3 @@ fn cannot_mint_duplicate_kitty() { assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); }) } - -#[test] -fn kitty_struct_has_expected_traits() { - new_test_ext().execute_with(|| { - let kitty = DEFAULT_KITTY; - let bytes = kitty.encode(); - let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); - assert!(Kitty::::max_encoded_len() > 0); - let _info = Kitty::::type_info(); - }) -} - -#[test] -fn mint_stores_owner_in_kitty() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::mint(1337, [42u8; 32])); - let kitty = Kitties::::get([42u8; 32]).unwrap(); - assert_eq!(kitty.owner, 1337); - assert_eq!(kitty.dna, [42u8; 32]); - }) -} diff --git a/src/21/template/template.diff b/src/21/template/template.diff index 1ab6b4e8..4e7ed5d5 100644 --- a/src/21/template/template.diff +++ b/src/21/template/template.diff @@ -1,36 +1,44 @@ diff --git a/src/impls.rs b/src/impls.rs -index 5d3c104..fac2ead 100644 +index c0e7cb27..741a6ffd 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -1,7 +1,19 @@ - use super::*; - use frame::prelude::*; -+/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ -+/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ +@@ -3,11 +3,16 @@ use frame::prelude::*; impl Pallet { -+ /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. -+ - Create a `unique_payload` which contains data from `frame_system::Pallet::`: -+ - `parent_hash` -+ - `block_number` -+ - `extrinsic_index` -+ - `CountForKitties::::get()` -+ - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. -+ - Return the hash as a `[u8; 32]`. -+ */ -+ pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone() }; ++ /* 🚧 TODO 🚧: ++ - Create a new variable `kitty` which is a `Kitty` struct with `dna` and `owner`. ++ */ ++ // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); + + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; ++ /* 🚧 TODO 🚧: Insert `kitty`into the map instead of `()`. */ + Kitties::::insert(dna, ()); + CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); diff --git a/src/lib.rs b/src/lib.rs -index aa6ca3b..34507f3 100644 +index 30d12b4c..68f57e11 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -48,6 +48,7 @@ pub mod pallet { - impl Pallet { - pub fn create_kitty(origin: OriginFor) -> DispatchResult { - let who = ensure_signed(origin)?; -+ /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ - let dna = [0u8; 32]; - Self::mint(who, dna)?; - Ok(()) +@@ -18,6 +18,10 @@ pub mod pallet { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + } + ++ /* 🚧 TODO 🚧: ++ - Add the derive macros needed for putting a struct in storage. ++ - Add `#[scale_info(skip_type_params(T))]` to ignore the generic `T`. ++ */ + pub struct Kitty { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], +@@ -28,6 +32,7 @@ pub mod pallet { + pub(super) type CountForKitties = StorageValue; + + #[pallet::storage] ++ /* 🚧 TODO 🚧: Update the `Value` to be type `Kitty` instead of (). */ + pub(super) type Kitties = StorageMap; + + #[pallet::event] diff --git a/src/22/solution/solution.diff b/src/22/solution/solution.diff index 82f6e633..69bd9ab9 100644 --- a/src/22/solution/solution.diff +++ b/src/22/solution/solution.diff @@ -1,58 +1,72 @@ diff --git a/src/impls.rs b/src/impls.rs -index 05c43d7..e464223 100644 +index fac2eada..ff69caec 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -26,10 +26,10 @@ impl Pallet { - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; +@@ -1,18 +1,22 @@ + use super::*; + use frame::prelude::*; +-/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ +-/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ ++use frame::primitives::BlakeTwo256; ++use frame::traits::Hash; -- /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ -- -+ KittiesOwned::::append(&owner, dna); - Kitties::::insert(dna, kitty); - CountForKitties::::set(new_count); + impl Pallet { +- /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. +- - Create a `unique_payload` which contains data from `frame_system::Pallet::`: +- - `parent_hash` +- - `block_number` +- - `extrinsic_index` +- - `CountForKitties::::get()` +- - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. +- - Return the hash as a `[u8; 32]`. +- */ ++ // Generates and returns DNA ++ pub fn gen_dna() -> [u8; 32] { ++ // Create randomness payload. Multiple kitties can be generated in the same block, ++ // retaining uniqueness. ++ let unique_payload = ( ++ frame_system::Pallet::::parent_hash(), ++ frame_system::Pallet::::block_number(), ++ frame_system::Pallet::::extrinsic_index(), ++ CountForKitties::::get(), ++ ); + - Self::deposit_event(Event::::Created { owner }); - Ok(()) - } ++ BlakeTwo256::hash_of(&unique_payload).into() ++ } + + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { + let kitty = Kitty { dna, owner: owner.clone() }; diff --git a/src/lib.rs b/src/lib.rs -index a92f8e5..9c10069 100644 +index 34507f3b..8795b0e1 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -32,11 +32,10 @@ pub mod pallet { - #[pallet::storage] - pub(super) type Kitties = StorageMap>; - -- /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. -- - The `Key` of this map is `T::AccountId`. -- - The `Value` of this map is `Vec<[u8; 32]>`. -- - The `QueryKind` should be set to `ValueQuery`. -- */ -+ /// Track the kitties owned by each account. -+ #[pallet::storage] -+ pub(super) type KittiesOwned = -+ StorageMap, QueryKind = ValueQuery>; - - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] +@@ -48,8 +48,7 @@ pub mod pallet { + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; +- /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ +- let dna = [0u8; 32]; ++ let dna = Self::gen_dna(); + Self::mint(who, dna)?; + Ok(()) + } diff --git a/src/tests.rs b/src/tests.rs -index 7803005..f110a8b 100644 +index 3d5e39d9..bd09cce2 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -234,3 +234,16 @@ fn create_kitty_makes_unique_kitties() { - assert_eq!(Kitties::::iter().count(), 2); +@@ -220,3 +220,15 @@ fn mint_stores_owner_in_kitty() { + assert_eq!(kitty.dna, [42u8; 32]); }) } + +#[test] -+fn kitties_owned_created_correctly() { ++fn create_kitty_makes_unique_kitties() { + new_test_ext().execute_with(|| { -+ // Initially users have no kitties owned. -+ assert_eq!(KittiesOwned::::get(1).len(), 0); -+ // Let's create two kitties. -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ // Two calls to `create_kitty` should work. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ // Now they should have two kitties owned. -+ assert_eq!(KittiesOwned::::get(1).len(), 2); -+ }); ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); ++ // And should result in two kitties in our system. ++ assert_eq!(CountForKitties::::get(), 2); ++ assert_eq!(Kitties::::iter().count(), 2); ++ }) +} diff --git a/src/22/solution/src/impls.rs b/src/22/solution/src/impls.rs index e464223c..ff69caec 100644 --- a/src/22/solution/src/impls.rs +++ b/src/22/solution/src/impls.rs @@ -25,11 +25,8 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - - KittiesOwned::::append(&owner, dna); Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/22/solution/src/lib.rs b/src/22/solution/src/lib.rs index 9c10069f..8795b0e1 100644 --- a/src/22/solution/src/lib.rs +++ b/src/22/solution/src/lib.rs @@ -32,11 +32,6 @@ pub mod pallet { #[pallet::storage] pub(super) type Kitties = StorageMap>; - /// Track the kitties owned by each account. - #[pallet::storage] - pub(super) type KittiesOwned = - StorageMap, QueryKind = ValueQuery>; - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/src/22/solution/src/tests.rs b/src/22/solution/src/tests.rs index f110a8b4..bd09cce2 100644 --- a/src/22/solution/src/tests.rs +++ b/src/22/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -234,16 +232,3 @@ fn create_kitty_makes_unique_kitties() { assert_eq!(Kitties::::iter().count(), 2); }) } - -#[test] -fn kitties_owned_created_correctly() { - new_test_ext().execute_with(|| { - // Initially users have no kitties owned. - assert_eq!(KittiesOwned::::get(1).len(), 0); - // Let's create two kitties. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now they should have two kitties owned. - assert_eq!(KittiesOwned::::get(1).len(), 2); - }); -} diff --git a/src/22/template/README.md b/src/22/template/README.md index 728b6af1..37f9a647 100644 --- a/src/22/template/README.md +++ b/src/22/template/README.md @@ -1,96 +1,51 @@ -# Track Owned Kitties +# Generate Unique DNA -Now that we can generate unique kitties, we need to consider all the ways we need to store and track those kitties. +In this step, we will show how to generate uniqueness using information from the blockchain. -## Redundant Storage +## Randomness -As a rule, you only want to store data in your blockchain which is necessary for consensus. Blockchains are extremely slow, low powered, and expensive. Blockchains are extremely good at one thing: achieving agreement among a decentralized and untrusted set of individuals. +Ideally, we would give every kitty we mint a randomly generated DNA. -When building a blockchain, you need to think about the various constraints: +However generating randomness on a blockchain is extremely difficult because we must come to consensus over all logic and data used on the blockchain. Any kind of randomness function must generate exactly the same randomness for all nodes. And if that is the case, it is still possible to influence randomness as a block producer by choosing NOT to build a block with a randomness you do not like. -- Execution Constraints -- Memory Constraints -- Storage Constraints -- Bandwidth Constrains -- etc... +Polkadot does provide access to a verifiable random function (VRF), but exactly the properties of this VRF and how to use it is beyond the scope of this tutorial. Not to mention we are also iteratively improving the VRF provided by Polkadot. -In the case of adding redundant storage, all of these constraints come into play! So let's talk about that a bit. +## Uniqueness -### Iteration +So rather than using true randomness, we will instead try to generate uniqueness. -It is common when writing code that you might want to perform iteration over items stored in your blockchain. +There are different levels of uniqueness we can achieve using data from our blockchain. -In general iteration should be avoided where possible, but if unavoidable it is critical that iteration be bounded in size. +- `frame_system::Pallet::::parent_hash()`: The hash of the previous block. This will ensure uniqueness for every fork of the blockchain. +- `frame_system::Pallet::::block_number()`: The number of the current block. This will obviously be unique for each block. +- `frame_system::Pallet::::extrinsic_index()`: The number of the extrinsic in the block that is being executed. This will be unique for each extrinsic in a block. +- `CountForKitties::::get()`: The number of kitties in our blockchain. -We literally cannot allow code on our blockchain which would do unbounded iteration, else that would stall our blockchain, which needs to produce a new block on a regular time interval. +If we combine all of these things together, we can ensure that every kitty we mint will be unique, no matter: -#### Maps +- Which block it comes in. +- How many extrinsics are in a block. +- Or even if a single extrinsic mints multiple kitties. -When you store and iterate over a map, you need to make two considerations: +## Hash -1. That the map may not have a bounded upper limit. -2. That each access to the map is very expensive to the blockchain (each is a unique read to the merkle trie). - -If you want to do iteration, probably you do NOT want to use a map for exactly these reasons. - -Maps are great instead for when you need to access or manipulate a single item at a time. - -#### Vec - -When you store and iterate over a vector, the only real consideration you need to have is how large that vector is. - -Accessing large files from the database is going to be slower than accessing small files. - -Once you access the vector, iterating over it and manipulating it is relatively cheap compared to any kind of storage map (but not zero, complexity about vector access still applies). - -If you want to do iteration, you definitely would prefer to use a vector. - -#### Middle Ground - -But sometimes you need to iterate over data, and store a lot of data. This is where we can do a middle ground. - -While it is not great for your Storage Constraints to store redundant data, it can be much better for all your other constraints. - -Let's say we want to answer the question: Give me all the kitties owned by Shawn. - -If all the kitties are stored just in the map, then we would need to iterate over all of them to find which ones are owned by Shawn. - -However, if we ALSO store a vector of kitties owned by Shawn in another storage, yes we would have redundant information in our database, but we will also be able to answer this question much more efficiently. - -A key part of designing your storage is making it efficient for the tasks your code will need to execute. Similarly, you will need to design your code to be efficient for the storage constraints you have. - -Honestly, its a lose / lose situation most times, but it is part of what we need to do when designing blockchain systems. - -## Storage Optimizations - -Storing vectors is a pretty normal part of Pallet development, and there are ways we can optimize adding items to a vector. - -Let's look at a naive way to add a new item to the vector: - -```rust -let mut owned_kitties: Vec<[u8; 32]> = KittiesOwned::::get(owner); -owned_kitties.append(new_kitty); -KittiesOwned::::insert(owner, owned_kitties); -``` - -The first call we need to make is `get` which returns to us all the data in the vector, and all that data is stored in a merkle trie in a database that is really expensive to read from. - -Then we add the item to the vector, and then write the whole new item back into storage. - -But this is way more inefficient than we need! We don't actually need to know what is inside the vector to add a new item to it, we can just say "add this item". - -So we can convert the whole logic to: +Obviously our uniqueness inputs are not super useful as is. But we can convert these inputs into a unique set of bytes with fixed length using a Hash function like [`frame::primitives::BlakeTwo256`](https://docs.rs/polkadot-sdk-frame/0.7.0/polkadot_sdk_frame/primitives/struct.BlakeTwo256.html). ```rust -KittiesOwned::::append(owner, new_kitty); +// Collect our unique inputs into a single object. +let unique_payload = (item1, item2, item3); +// To use the `hash_of` API, we need to bring the `Hash` trait into scope. +use frame::traits::Hash; +// Hash that object to get a unique identifier. +let hash: [u8; 32] = BlakeTwo256::hash_of(&unique_payload).into(); ``` -In this case, we use our own storage abstractions to avoid needing to read the whole vector in our runtime logic to simply add a new item to it. +The `hash_of` API comes from the `Hash` trait and takes any `encode`-able object, and returns a `H256`, which is a 256-bit hash. As you can see in the code above, it is easy to convert that to a `[u8; 32]` by just calling `.into()`, since these two types are equivalent. -Fun fact, this optimization actually lead to a 95% performance increase for the `polkadot-sdk` back before Polkadot launched and we were benchmarking it! +Another nice thing about using a hash is you get some sense of pseudo-randomness between the input and output. This means that two kitties which are minted right after one another could have totally different DNA, which could be useful if you want to associate unique attributes to the different parts of their DNA. πŸ€” ## Your Turn -Now that you understand the tradeoffs associated with creating redundant storage, let's make a new `StorageMap` called `KittiesOwned` which can help us more easily find what kitties an account is the owner of. +Now that you know how to acquire uniqueness from your blockchain, and how to hash those items, create a new function called `fn gen_dna() -> [u8; 32];` which does these steps to create unique DNA for each kitty that is minted. -Then let's update the `mint` function to `append` the kitty's DNA to the `KittiesOwned` vector for the `owner`. +Update your `create_kitty` extrinsic to generate and use this unique DNA. diff --git a/src/22/template/src/impls.rs b/src/22/template/src/impls.rs index 05c43d7a..fac2eada 100644 --- a/src/22/template/src/impls.rs +++ b/src/22/template/src/impls.rs @@ -1,22 +1,18 @@ use super::*; use frame::prelude::*; -use frame::primitives::BlakeTwo256; -use frame::traits::Hash; +/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ +/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ impl Pallet { - // Generates and returns DNA - pub fn gen_dna() -> [u8; 32] { - // Create randomness payload. Multiple kitties can be generated in the same block, - // retaining uniqueness. - let unique_payload = ( - frame_system::Pallet::::parent_hash(), - frame_system::Pallet::::block_number(), - frame_system::Pallet::::extrinsic_index(), - CountForKitties::::get(), - ); - - BlakeTwo256::hash_of(&unique_payload).into() - } + /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. + - Create a `unique_payload` which contains data from `frame_system::Pallet::`: + - `parent_hash` + - `block_number` + - `extrinsic_index` + - `CountForKitties::::get()` + - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. + - Return the hash as a `[u8; 32]`. + */ pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { let kitty = Kitty { dna, owner: owner.clone() }; @@ -25,9 +21,6 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - - /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ - Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); diff --git a/src/22/template/src/lib.rs b/src/22/template/src/lib.rs index a92f8e52..34507f3b 100644 --- a/src/22/template/src/lib.rs +++ b/src/22/template/src/lib.rs @@ -32,12 +32,6 @@ pub mod pallet { #[pallet::storage] pub(super) type Kitties = StorageMap>; - /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. - - The `Key` of this map is `T::AccountId`. - - The `Value` of this map is `Vec<[u8; 32]>`. - - The `QueryKind` should be set to `ValueQuery`. - */ - #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { @@ -54,7 +48,8 @@ pub mod pallet { impl Pallet { pub fn create_kitty(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; - let dna = Self::gen_dna(); + /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ + let dna = [0u8; 32]; Self::mint(who, dna)?; Ok(()) } diff --git a/src/22/template/src/tests.rs b/src/22/template/src/tests.rs index 78030053..3d5e39d9 100644 --- a/src/22/template/src/tests.rs +++ b/src/22/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -222,15 +220,3 @@ fn mint_stores_owner_in_kitty() { assert_eq!(kitty.dna, [42u8; 32]); }) } - -#[test] -fn create_kitty_makes_unique_kitties() { - new_test_ext().execute_with(|| { - // Two calls to `create_kitty` should work. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); - // And should result in two kitties in our system. - assert_eq!(CountForKitties::::get(), 2); - assert_eq!(Kitties::::iter().count(), 2); - }) -} diff --git a/src/22/template/template.diff b/src/22/template/template.diff index 58a1017f..832c5f50 100644 --- a/src/22/template/template.diff +++ b/src/22/template/template.diff @@ -1,31 +1,36 @@ diff --git a/src/impls.rs b/src/impls.rs -index ff69cae..05c43d7 100644 +index 5d3c1046..fac2eada 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -25,6 +25,9 @@ impl Pallet { +@@ -1,7 +1,19 @@ + use super::*; + use frame::prelude::*; ++/* 🚧 TODO 🚧: Import `frame::primitives::BlakeTwo256`. */ ++/* 🚧 TODO 🚧: Import `frame::traits::Hash`. */ - let current_count: u32 = CountForKitties::::get(); - let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -+ -+ /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ + impl Pallet { ++ /* 🚧 TODO 🚧: Create a function `gen_dna` which returns a `[u8; 32]`. ++ - Create a `unique_payload` which contains data from `frame_system::Pallet::`: ++ - `parent_hash` ++ - `block_number` ++ - `extrinsic_index` ++ - `CountForKitties::::get()` ++ - Use `BlakeTwo256` to calculate the `hash_of` the unique payload. ++ - Return the hash as a `[u8; 32]`. ++ */ + - Kitties::::insert(dna, kitty); - CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { + let kitty = Kitty { dna, owner: owner.clone() }; + // Check if the kitty does not already exist in our storage map diff --git a/src/lib.rs b/src/lib.rs -index 8795b0e..a92f8e5 100644 +index aa6ca3b9..34507f3b 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -32,6 +32,12 @@ pub mod pallet { - #[pallet::storage] - pub(super) type Kitties = StorageMap>; - -+ /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. -+ - The `Key` of this map is `T::AccountId`. -+ - The `Value` of this map is `Vec<[u8; 32]>`. -+ - The `QueryKind` should be set to `ValueQuery`. -+ */ -+ - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { +@@ -48,6 +48,7 @@ pub mod pallet { + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; ++ /* 🚧 TODO 🚧: Use the `Self::gen_dna()` function to generate a unique Kitty. */ + let dna = [0u8; 32]; + Self::mint(who, dna)?; + Ok(()) diff --git a/src/23/solution/solution.diff b/src/23/solution/solution.diff index 161ffccc..a58ce51d 100644 --- a/src/23/solution/solution.diff +++ b/src/23/solution/solution.diff @@ -1,69 +1,58 @@ diff --git a/src/impls.rs b/src/impls.rs -index f8f58b4..793dec3 100644 +index 05c43d7a..e464223c 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -3,6 +3,7 @@ use frame::prelude::*; - use frame::primitives::BlakeTwo256; - use frame::traits::Hash; - -+// Learn about internal functions. - impl Pallet { - // Generates and returns DNA - pub fn gen_dna() -> [u8; 32] { -@@ -26,10 +27,7 @@ impl Pallet { +@@ -26,10 +26,10 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; -- /* 🚧 TODO 🚧: -- - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. -- */ -- KittiesOwned::::append(&owner, dna); -+ KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; +- /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ +- ++ KittiesOwned::::append(&owner, dna); Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); - ++ + Self::deposit_event(Event::::Created { owner }); + Ok(()) + } diff --git a/src/lib.rs b/src/lib.rs -index 40f2592..e032974 100644 +index a92f8e52..9c10069f 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -36,8 +36,7 @@ pub mod pallet { +@@ -32,11 +32,10 @@ pub mod pallet { #[pallet::storage] - pub(super) type KittiesOwned = StorageMap< - Key = T::AccountId, -- /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ -- Value = Vec<[u8; 32]>, -+ Value = BoundedVec<[u8; 32], ConstU32<100>>, - QueryKind = ValueQuery, - >; + pub(super) type Kitties = StorageMap>; -@@ -51,7 +50,7 @@ pub mod pallet { - pub enum Error { - TooManyKitties, - DuplicateKitty, -- /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ -+ TooManyOwned, - } +- /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. +- - The `Key` of this map is `T::AccountId`. +- - The `Value` of this map is `Vec<[u8; 32]>`. +- - The `QueryKind` should be set to `ValueQuery`. +- */ ++ /// Track the kitties owned by each account. ++ #[pallet::storage] ++ pub(super) type KittiesOwned = ++ StorageMap, QueryKind = ValueQuery>; - #[pallet::call] + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] diff --git a/src/tests.rs b/src/tests.rs -index f110a8b..939f943 100644 +index bd09cce2..546344b8 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -247,3 +247,17 @@ fn kitties_owned_created_correctly() { - assert_eq!(KittiesOwned::::get(1).len(), 2); - }); +@@ -232,3 +232,16 @@ fn create_kitty_makes_unique_kitties() { + assert_eq!(Kitties::::iter().count(), 2); + }) } + +#[test] -+fn cannot_own_too_many_kitties() { ++fn kitties_owned_created_correctly() { + new_test_ext().execute_with(|| { -+ // If your max owned is different than 100, you will need to update this. -+ for _ in 0..100 { -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ } -+ assert_noop!( -+ PalletKitties::create_kitty(RuntimeOrigin::signed(1)), -+ Error::::TooManyOwned -+ ); ++ // Initially users have no kitties owned. ++ assert_eq!(KittiesOwned::::get(1).len(), 0); ++ // Let's create two kitties. ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ // Now they should have two kitties owned. ++ assert_eq!(KittiesOwned::::get(1).len(), 2); + }); +} diff --git a/src/23/solution/src/impls.rs b/src/23/solution/src/impls.rs index 793dec31..e464223c 100644 --- a/src/23/solution/src/impls.rs +++ b/src/23/solution/src/impls.rs @@ -3,7 +3,6 @@ use frame::prelude::*; use frame::primitives::BlakeTwo256; use frame::traits::Hash; -// Learn about internal functions. impl Pallet { // Generates and returns DNA pub fn gen_dna() -> [u8; 32] { @@ -27,7 +26,7 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; + KittiesOwned::::append(&owner, dna); Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); diff --git a/src/23/solution/src/lib.rs b/src/23/solution/src/lib.rs index e0329740..9c10069f 100644 --- a/src/23/solution/src/lib.rs +++ b/src/23/solution/src/lib.rs @@ -34,11 +34,8 @@ pub mod pallet { /// Track the kitties owned by each account. #[pallet::storage] - pub(super) type KittiesOwned = StorageMap< - Key = T::AccountId, - Value = BoundedVec<[u8; 32], ConstU32<100>>, - QueryKind = ValueQuery, - >; + pub(super) type KittiesOwned = + StorageMap, QueryKind = ValueQuery>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -50,7 +47,6 @@ pub mod pallet { pub enum Error { TooManyKitties, DuplicateKitty, - TooManyOwned, } #[pallet::call] diff --git a/src/23/solution/src/tests.rs b/src/23/solution/src/tests.rs index 939f9433..546344b8 100644 --- a/src/23/solution/src/tests.rs +++ b/src/23/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -247,17 +245,3 @@ fn kitties_owned_created_correctly() { assert_eq!(KittiesOwned::::get(1).len(), 2); }); } - -#[test] -fn cannot_own_too_many_kitties() { - new_test_ext().execute_with(|| { - // If your max owned is different than 100, you will need to update this. - for _ in 0..100 { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - } - assert_noop!( - PalletKitties::create_kitty(RuntimeOrigin::signed(1)), - Error::::TooManyOwned - ); - }); -} diff --git a/src/23/template/README.md b/src/23/template/README.md index 8f147a4e..728b6af1 100644 --- a/src/23/template/README.md +++ b/src/23/template/README.md @@ -1,69 +1,96 @@ -# Bounded Vectors +# Track Owned Kitties -We placed a vector in storage in the last step. This is okay for initial development, but this is NOT okay for a production Pallet. Instead we need to use objects which have a `MaxEncodedLen`, and for that, we have the `BoundedVec` type. +Now that we can generate unique kitties, we need to consider all the ways we need to store and track those kitties. -## Max Encoded Length +## Redundant Storage -We mentioned earlier that we require all blockchain storage to have a maximum upper limit to the encoded length of that object. For that, we use the `MaxEncodedLen` trait. +As a rule, you only want to store data in your blockchain which is necessary for consensus. Blockchains are extremely slow, low powered, and expensive. Blockchains are extremely good at one thing: achieving agreement among a decentralized and untrusted set of individuals. -But what is the `max_encoded_len()` of a `Vec`? +When building a blockchain, you need to think about the various constraints: -One answer might be: approximately `T * 2^32`; but this is not a reasonable answer. :) +- Execution Constraints +- Memory Constraints +- Storage Constraints +- Bandwidth Constrains +- etc... -In fact, we do not implement `MaxEncodedLen` on `Vec` because the answer is so unreasonable. +In the case of adding redundant storage, all of these constraints come into play! So let's talk about that a bit. -So we need to create a new structure which can act like a `Vec`, but also have reasonable bounds as to how many items are inside of it. +### Iteration -Hence the `BoundedVec` was born. +It is common when writing code that you might want to perform iteration over items stored in your blockchain. -## Construction +In general iteration should be avoided where possible, but if unavoidable it is critical that iteration be bounded in size. -The `BoundedVec` type is a zero-overhead abstraction over the `Vec` type allowing us to control the maximum number of item in the vector. +We literally cannot allow code on our blockchain which would do unbounded iteration, else that would stall our blockchain, which needs to produce a new block on a regular time interval. -To create a new `BoundedVec` with a maximum of 100 `u8`s, you can do the following: +#### Maps -```rust -let my_bounded_vec = BoundedVec::>::new(); -``` +When you store and iterate over a map, you need to make two considerations: + +1. That the map may not have a bounded upper limit. +2. That each access to the map is very expensive to the blockchain (each is a unique read to the merkle trie). + +If you want to do iteration, probably you do NOT want to use a map for exactly these reasons. + +Maps are great instead for when you need to access or manipulate a single item at a time. + +#### Vec + +When you store and iterate over a vector, the only real consideration you need to have is how large that vector is. + +Accessing large files from the database is going to be slower than accessing small files. + +Once you access the vector, iterating over it and manipulating it is relatively cheap compared to any kind of storage map (but not zero, complexity about vector access still applies). -The syntax here is very similar to creating a `Vec`, however we include a second generic parameter which tells us the bound. The easiest way to set this bound is using the `ConstU32` type. +If you want to do iteration, you definitely would prefer to use a vector. -There are other ways to define the bound, and even make it configurable, but that is beyond the scope of this tutorial. Add it to the list of things to follow up on after you have completed this tutorial. +#### Middle Ground -## Basic APIs +But sometimes you need to iterate over data, and store a lot of data. This is where we can do a middle ground. -The `BoundedVec` type has almost all the same APIs as a `Vec`. You can find the full list of APIs in the [`BoundedVec` documentation](https://docs.rs/frame-support/38.0.0/frame_support/struct.BoundedVec.html). +While it is not great for your Storage Constraints to store redundant data, it can be much better for all your other constraints. -The main difference is the fact that a `BoundedVec` cannot always accept a new item. +Let's say we want to answer the question: Give me all the kitties owned by Shawn. -So rather than having `push`, `append`, `extend`, `insert`, and so on, you have `try_push`, `try_append`, `try_extend`, `try_insert`, etc... +If all the kitties are stored just in the map, then we would need to iterate over all of them to find which ones are owned by Shawn. -These functions have the same parameters as their `Vec` equivalent, but can return a `Result` rather than being infallible. +However, if we ALSO store a vector of kitties owned by Shawn in another storage, yes we would have redundant information in our database, but we will also be able to answer this question much more efficiently. -So converting the logic of a `Vec` to a `BoundedVec` can be as easy as: +A key part of designing your storage is making it efficient for the tasks your code will need to execute. Similarly, you will need to design your code to be efficient for the storage constraints you have. + +Honestly, its a lose / lose situation most times, but it is part of what we need to do when designing blockchain systems. + +## Storage Optimizations + +Storing vectors is a pretty normal part of Pallet development, and there are ways we can optimize adding items to a vector. + +Let's look at a naive way to add a new item to the vector: ```rust -// Append to a normal vec. -vec.append(item); -// Try append to a bounded vec, handling the error. -bounded_vec.try_append(item).map_err(|_| Error::::TooManyOwned)?; +let mut owned_kitties: Vec<[u8; 32]> = KittiesOwned::::get(owner); +owned_kitties.append(new_kitty); +KittiesOwned::::insert(owner, owned_kitties); ``` -## Storage Optimizations +The first call we need to make is `get` which returns to us all the data in the vector, and all that data is stored in a merkle trie in a database that is really expensive to read from. + +Then we add the item to the vector, and then write the whole new item back into storage. -Just like for `Vec`, our `BoundedVec` also has an optimized `try_append` API for trying to append a new item to the `BoundedVec` without having to read the whole vector in the runtime. +But this is way more inefficient than we need! We don't actually need to know what is inside the vector to add a new item to it, we can just say "add this item". -The change to use this API also looks pretty much the same as above: +So we can convert the whole logic to: ```rust -// Append to a normal vec. -KittiesOwned::::append(item); -// Try append to a bounded vec, handling the error. -KittiesOwned::::try_append(item).map_err(|_| Error::::TooManyOwned)?; +KittiesOwned::::append(owner, new_kitty); ``` +In this case, we use our own storage abstractions to avoid needing to read the whole vector in our runtime logic to simply add a new item to it. + +Fun fact, this optimization actually lead to a 95% performance increase for the `polkadot-sdk` back before Polkadot launched and we were benchmarking it! + ## Your Turn -Update the `KittiesOwned` storage map to use `Value = BoundedVec` with up to 100 items. +Now that you understand the tradeoffs associated with creating redundant storage, let's make a new `StorageMap` called `KittiesOwned` which can help us more easily find what kitties an account is the owner of. -You will need to update the logic for the `mint` function to handle the case where we cannot mint a new kitty for an `owner`. For that, we will need to introduce a new error `TooManyOwned`. +Then let's update the `mint` function to `append` the kitty's DNA to the `KittiesOwned` vector for the `owner`. diff --git a/src/23/template/src/impls.rs b/src/23/template/src/impls.rs index f8f58b46..05c43d7a 100644 --- a/src/23/template/src/impls.rs +++ b/src/23/template/src/impls.rs @@ -26,13 +26,10 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - /* 🚧 TODO 🚧: - - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. - */ - KittiesOwned::::append(&owner, dna); + /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ + Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); - Self::deposit_event(Event::::Created { owner }); Ok(()) } diff --git a/src/23/template/src/lib.rs b/src/23/template/src/lib.rs index 40f25922..a92f8e52 100644 --- a/src/23/template/src/lib.rs +++ b/src/23/template/src/lib.rs @@ -32,14 +32,11 @@ pub mod pallet { #[pallet::storage] pub(super) type Kitties = StorageMap>; - /// Track the kitties owned by each account. - #[pallet::storage] - pub(super) type KittiesOwned = StorageMap< - Key = T::AccountId, - /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ - Value = Vec<[u8; 32]>, - QueryKind = ValueQuery, - >; + /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. + - The `Key` of this map is `T::AccountId`. + - The `Value` of this map is `Vec<[u8; 32]>`. + - The `QueryKind` should be set to `ValueQuery`. + */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -51,7 +48,6 @@ pub mod pallet { pub enum Error { TooManyKitties, DuplicateKitty, - /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ } #[pallet::call] diff --git a/src/23/template/src/tests.rs b/src/23/template/src/tests.rs index f110a8b4..bd09cce2 100644 --- a/src/23/template/src/tests.rs +++ b/src/23/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -234,16 +232,3 @@ fn create_kitty_makes_unique_kitties() { assert_eq!(Kitties::::iter().count(), 2); }) } - -#[test] -fn kitties_owned_created_correctly() { - new_test_ext().execute_with(|| { - // Initially users have no kitties owned. - assert_eq!(KittiesOwned::::get(1).len(), 0); - // Let's create two kitties. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Now they should have two kitties owned. - assert_eq!(KittiesOwned::::get(1).len(), 2); - }); -} diff --git a/src/23/template/template.diff b/src/23/template/template.diff index 29d17d5c..67b1742a 100644 --- a/src/23/template/template.diff +++ b/src/23/template/template.diff @@ -1,41 +1,31 @@ diff --git a/src/impls.rs b/src/impls.rs -index e464223..f8f58b4 100644 +index ff69caec..05c43d7a 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -26,6 +26,9 @@ impl Pallet { +@@ -25,6 +25,9 @@ impl Pallet { + let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - -+ /* 🚧 TODO 🚧: -+ - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. -+ */ - KittiesOwned::::append(&owner, dna); ++ ++ /* 🚧 TODO 🚧: `append` the `dna` to the `KittiesOwned` storage for the `owner`. */ ++ Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); + Self::deposit_event(Event::::Created { owner }); diff --git a/src/lib.rs b/src/lib.rs -index 9c10069..40f2592 100644 +index 8795b0e1..a92f8e52 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -34,8 +34,12 @@ pub mod pallet { - - /// Track the kitties owned by each account. +@@ -32,6 +32,12 @@ pub mod pallet { #[pallet::storage] -- pub(super) type KittiesOwned = -- StorageMap, QueryKind = ValueQuery>; -+ pub(super) type KittiesOwned = StorageMap< -+ Key = T::AccountId, -+ /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ -+ Value = Vec<[u8; 32]>, -+ QueryKind = ValueQuery, -+ >; + pub(super) type Kitties = StorageMap>; ++ /* 🚧 TODO 🚧: Create a new `StorageMap` called `KittiesOwned`. ++ - The `Key` of this map is `T::AccountId`. ++ - The `Value` of this map is `Vec<[u8; 32]>`. ++ - The `QueryKind` should be set to `ValueQuery`. ++ */ ++ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] -@@ -47,6 +51,7 @@ pub mod pallet { - pub enum Error { - TooManyKitties, - DuplicateKitty, -+ /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ - } - - #[pallet::call] + pub enum Event { diff --git a/src/24/README.md b/src/24/README.md index df81bc46..eb9560d4 100644 --- a/src/24/README.md +++ b/src/24/README.md @@ -1,6 +1,103 @@ -
+
+
-{{#include ./source/README.md}} +{{#include ./template/README.md}}
+ +
+ +
+ + + +
+ +
+ +
+ + +
+
+ +```rust +{{#include ./template/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./template/src/lib.rs}} +``` + +
+ + + +
+ +
+ +
+ + + +
+
+ +```rust +{{#include ./solution/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/lib.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/tests.rs}} +``` + +
+ + + +
+ +
+ + +
+ + +
+
+ +```diff +{{#include ./template/template.diff}} +``` + +
+
+ +```diff +{{#include ./solution/solution.diff}} +``` + +
+ +
+ +
+
diff --git a/src/19/template/.gitignore b/src/24/solution/.gitignore similarity index 100% rename from src/19/template/.gitignore rename to src/24/solution/.gitignore diff --git a/src/19/template/Cargo.lock b/src/24/solution/Cargo.lock similarity index 100% rename from src/19/template/Cargo.lock rename to src/24/solution/Cargo.lock diff --git a/src/19/template/Cargo.toml b/src/24/solution/Cargo.toml similarity index 100% rename from src/19/template/Cargo.toml rename to src/24/solution/Cargo.toml diff --git a/src/19/solution/README.md b/src/24/solution/README.md similarity index 100% rename from src/19/solution/README.md rename to src/24/solution/README.md diff --git a/src/19/template/rustfmt.toml b/src/24/solution/rustfmt.toml similarity index 100% rename from src/19/template/rustfmt.toml rename to src/24/solution/rustfmt.toml diff --git a/src/24/solution/solution.diff b/src/24/solution/solution.diff new file mode 100644 index 00000000..1a7c43fc --- /dev/null +++ b/src/24/solution/solution.diff @@ -0,0 +1,69 @@ +diff --git a/src/impls.rs b/src/impls.rs +index f8f58b46..793dec31 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -3,6 +3,7 @@ use frame::prelude::*; + use frame::primitives::BlakeTwo256; + use frame::traits::Hash; + ++// Learn about internal functions. + impl Pallet { + // Generates and returns DNA + pub fn gen_dna() -> [u8; 32] { +@@ -26,10 +27,7 @@ impl Pallet { + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + +- /* 🚧 TODO 🚧: +- - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. +- */ +- KittiesOwned::::append(&owner, dna); ++ KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; + Kitties::::insert(dna, kitty); + CountForKitties::::set(new_count); + +diff --git a/src/lib.rs b/src/lib.rs +index 40f25922..e0329740 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -36,8 +36,7 @@ pub mod pallet { + #[pallet::storage] + pub(super) type KittiesOwned = StorageMap< + Key = T::AccountId, +- /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ +- Value = Vec<[u8; 32]>, ++ Value = BoundedVec<[u8; 32], ConstU32<100>>, + QueryKind = ValueQuery, + >; + +@@ -51,7 +50,7 @@ pub mod pallet { + pub enum Error { + TooManyKitties, + DuplicateKitty, +- /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ ++ TooManyOwned, + } + + #[pallet::call] +diff --git a/src/tests.rs b/src/tests.rs +index 546344b8..6a7f5b74 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -245,3 +245,17 @@ fn kitties_owned_created_correctly() { + assert_eq!(KittiesOwned::::get(1).len(), 2); + }); + } ++ ++#[test] ++fn cannot_own_too_many_kitties() { ++ new_test_ext().execute_with(|| { ++ // If your max owned is different than 100, you will need to update this. ++ for _ in 0..100 { ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ } ++ assert_noop!( ++ PalletKitties::create_kitty(RuntimeOrigin::signed(1)), ++ Error::::TooManyOwned ++ ); ++ }); ++} diff --git a/src/24/source/src/impls.rs b/src/24/solution/src/impls.rs similarity index 100% rename from src/24/source/src/impls.rs rename to src/24/solution/src/impls.rs diff --git a/src/24/source/src/lib.rs b/src/24/solution/src/lib.rs similarity index 100% rename from src/24/source/src/lib.rs rename to src/24/solution/src/lib.rs diff --git a/src/25/template/src/tests.rs b/src/24/solution/src/tests.rs similarity index 97% rename from src/25/template/src/tests.rs rename to src/24/solution/src/tests.rs index 939f9433..6a7f5b74 100644 --- a/src/25/template/src/tests.rs +++ b/src/24/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/24/source/.gitignore b/src/24/template/.gitignore similarity index 100% rename from src/24/source/.gitignore rename to src/24/template/.gitignore diff --git a/src/24/source/Cargo.lock b/src/24/template/Cargo.lock similarity index 100% rename from src/24/source/Cargo.lock rename to src/24/template/Cargo.lock diff --git a/src/24/source/Cargo.toml b/src/24/template/Cargo.toml similarity index 100% rename from src/24/source/Cargo.toml rename to src/24/template/Cargo.toml diff --git a/src/24/template/README.md b/src/24/template/README.md new file mode 100644 index 00000000..8f147a4e --- /dev/null +++ b/src/24/template/README.md @@ -0,0 +1,69 @@ +# Bounded Vectors + +We placed a vector in storage in the last step. This is okay for initial development, but this is NOT okay for a production Pallet. Instead we need to use objects which have a `MaxEncodedLen`, and for that, we have the `BoundedVec` type. + +## Max Encoded Length + +We mentioned earlier that we require all blockchain storage to have a maximum upper limit to the encoded length of that object. For that, we use the `MaxEncodedLen` trait. + +But what is the `max_encoded_len()` of a `Vec`? + +One answer might be: approximately `T * 2^32`; but this is not a reasonable answer. :) + +In fact, we do not implement `MaxEncodedLen` on `Vec` because the answer is so unreasonable. + +So we need to create a new structure which can act like a `Vec`, but also have reasonable bounds as to how many items are inside of it. + +Hence the `BoundedVec` was born. + +## Construction + +The `BoundedVec` type is a zero-overhead abstraction over the `Vec` type allowing us to control the maximum number of item in the vector. + +To create a new `BoundedVec` with a maximum of 100 `u8`s, you can do the following: + +```rust +let my_bounded_vec = BoundedVec::>::new(); +``` + +The syntax here is very similar to creating a `Vec`, however we include a second generic parameter which tells us the bound. The easiest way to set this bound is using the `ConstU32` type. + +There are other ways to define the bound, and even make it configurable, but that is beyond the scope of this tutorial. Add it to the list of things to follow up on after you have completed this tutorial. + +## Basic APIs + +The `BoundedVec` type has almost all the same APIs as a `Vec`. You can find the full list of APIs in the [`BoundedVec` documentation](https://docs.rs/frame-support/38.0.0/frame_support/struct.BoundedVec.html). + +The main difference is the fact that a `BoundedVec` cannot always accept a new item. + +So rather than having `push`, `append`, `extend`, `insert`, and so on, you have `try_push`, `try_append`, `try_extend`, `try_insert`, etc... + +These functions have the same parameters as their `Vec` equivalent, but can return a `Result` rather than being infallible. + +So converting the logic of a `Vec` to a `BoundedVec` can be as easy as: + +```rust +// Append to a normal vec. +vec.append(item); +// Try append to a bounded vec, handling the error. +bounded_vec.try_append(item).map_err(|_| Error::::TooManyOwned)?; +``` + +## Storage Optimizations + +Just like for `Vec`, our `BoundedVec` also has an optimized `try_append` API for trying to append a new item to the `BoundedVec` without having to read the whole vector in the runtime. + +The change to use this API also looks pretty much the same as above: + +```rust +// Append to a normal vec. +KittiesOwned::::append(item); +// Try append to a bounded vec, handling the error. +KittiesOwned::::try_append(item).map_err(|_| Error::::TooManyOwned)?; +``` + +## Your Turn + +Update the `KittiesOwned` storage map to use `Value = BoundedVec` with up to 100 items. + +You will need to update the logic for the `mint` function to handle the case where we cannot mint a new kitty for an `owner`. For that, we will need to introduce a new error `TooManyOwned`. diff --git a/src/24/source/rustfmt.toml b/src/24/template/rustfmt.toml similarity index 100% rename from src/24/source/rustfmt.toml rename to src/24/template/rustfmt.toml diff --git a/src/25/template/src/impls.rs b/src/24/template/src/impls.rs similarity index 69% rename from src/25/template/src/impls.rs rename to src/24/template/src/impls.rs index 3dd15659..f8f58b46 100644 --- a/src/25/template/src/impls.rs +++ b/src/24/template/src/impls.rs @@ -3,7 +3,6 @@ use frame::prelude::*; use frame::primitives::BlakeTwo256; use frame::traits::Hash; -// Learn about internal functions. impl Pallet { // Generates and returns DNA pub fn gen_dna() -> [u8; 32] { @@ -27,22 +26,14 @@ impl Pallet { let current_count: u32 = CountForKitties::::get(); let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; - KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; + /* 🚧 TODO 🚧: + - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. + */ + KittiesOwned::::append(&owner, dna); Kitties::::insert(dna, kitty); CountForKitties::::set(new_count); Self::deposit_event(Event::::Created { owner }); Ok(()) } - - /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: - - It has inputs: - - `from` which is `T::AccountId`. - - `to` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - It returns a `DispatchResult` - - The inner logic for now is: - - Call `Self::deposit_event` and emit `Event:::Transferred` with params. - - Return `Ok(())`. - */ } diff --git a/src/25/template/src/lib.rs b/src/24/template/src/lib.rs similarity index 68% rename from src/25/template/src/lib.rs rename to src/24/template/src/lib.rs index ac84fa65..40f25922 100644 --- a/src/25/template/src/lib.rs +++ b/src/24/template/src/lib.rs @@ -36,7 +36,8 @@ pub mod pallet { #[pallet::storage] pub(super) type KittiesOwned = StorageMap< Key = T::AccountId, - Value = BoundedVec<[u8; 32], ConstU32<100>>, + /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ + Value = Vec<[u8; 32]>, QueryKind = ValueQuery, >; @@ -44,19 +45,13 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Created { owner: T::AccountId }, - /* 🚧 TODO 🚧: Create a new event called `Transferred`: - - Parameters are: - - `from` which is `T::AccountId`. - - `to` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - */ } #[pallet::error] pub enum Error { TooManyKitties, DuplicateKitty, - TooManyOwned, + /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ } #[pallet::call] @@ -67,17 +62,5 @@ pub mod pallet { Self::mint(who, dna)?; Ok(()) } - - /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. - - Input parameters are: - - `origin` which is `OriginFor`. - - `to` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - Returns a `DispatchResult`. - - The inner logic should be: - - Get the caller `who` from `ensure_signed`. - - Call `Self::do_transfer`, and propagate the result. - - End with Ok(()). - */ } } diff --git a/src/25/solution/src/tests.rs b/src/24/template/src/tests.rs similarity index 87% rename from src/25/solution/src/tests.rs rename to src/24/template/src/tests.rs index 0a90b3b5..546344b8 100644 --- a/src/25/solution/src/tests.rs +++ b/src/24/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -247,33 +245,3 @@ fn kitties_owned_created_correctly() { assert_eq!(KittiesOwned::::get(1).len(), 2); }); } - -#[test] -fn cannot_own_too_many_kitties() { - new_test_ext().execute_with(|| { - // If your max owned is different than 100, you will need to update this. - for _ in 0..100 { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - } - assert_noop!( - PalletKitties::create_kitty(RuntimeOrigin::signed(1)), - Error::::TooManyOwned - ); - }); -} - -#[test] -fn transfer_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - // Create a kitty to transfer - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Get the kitty id. - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); - System::assert_last_event( - Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), - ); - }); -} diff --git a/src/24/template/template.diff b/src/24/template/template.diff new file mode 100644 index 00000000..d50fa957 --- /dev/null +++ b/src/24/template/template.diff @@ -0,0 +1,41 @@ +diff --git a/src/impls.rs b/src/impls.rs +index e464223c..f8f58b46 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -26,6 +26,9 @@ impl Pallet { + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + ++ /* 🚧 TODO 🚧: ++ - Update `append` to `try_append` and `map_err` to `Error::::TooManyOwned`. ++ */ + KittiesOwned::::append(&owner, dna); + Kitties::::insert(dna, kitty); + CountForKitties::::set(new_count); +diff --git a/src/lib.rs b/src/lib.rs +index 9c10069f..40f25922 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -34,8 +34,12 @@ pub mod pallet { + + /// Track the kitties owned by each account. + #[pallet::storage] +- pub(super) type KittiesOwned = +- StorageMap, QueryKind = ValueQuery>; ++ pub(super) type KittiesOwned = StorageMap< ++ Key = T::AccountId, ++ /* 🚧 TODO 🚧: Turn this into a `BoundedVec` with a limit of `ConstU32<100>`. */ ++ Value = Vec<[u8; 32]>, ++ QueryKind = ValueQuery, ++ >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] +@@ -47,6 +51,7 @@ pub mod pallet { + pub enum Error { + TooManyKitties, + DuplicateKitty, ++ /* 🚧 TODO 🚧: Add a new `Error` named `TooManyOwned` */ + } + + #[pallet::call] diff --git a/src/25/README.md b/src/25/README.md index eb9560d4..df81bc46 100644 --- a/src/25/README.md +++ b/src/25/README.md @@ -1,103 +1,6 @@ -
-
+
-{{#include ./template/README.md}} +{{#include ./source/README.md}}
- -
- -
- - - -
- -
- -
- - -
-
- -```rust -{{#include ./template/src/impls.rs}} -``` - -
- -
- -```rust -{{#include ./template/src/lib.rs}} -``` - -
- - - -
- -
- -
- - - -
-
- -```rust -{{#include ./solution/src/impls.rs}} -``` - -
- -
- -```rust -{{#include ./solution/src/lib.rs}} -``` - -
- -
- -```rust -{{#include ./solution/src/tests.rs}} -``` - -
- - - -
- -
- - -
- - -
-
- -```diff -{{#include ./template/template.diff}} -``` - -
-
- -```diff -{{#include ./solution/solution.diff}} -``` - -
- -
- -
-
diff --git a/src/25/solution/solution.diff b/src/25/solution/solution.diff deleted file mode 100644 index 44e229f2..00000000 --- a/src/25/solution/solution.diff +++ /dev/null @@ -1,91 +0,0 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 3dd1565..9d435f5 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -35,14 +35,8 @@ impl Pallet { - Ok(()) - } - -- /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: -- - It has inputs: -- - `from` which is `T::AccountId`. -- - `to` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - It returns a `DispatchResult` -- - The inner logic for now is: -- - Call `Self::deposit_event` and emit `Event:::Transferred` with params. -- - Return `Ok(())`. -- */ -+ pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { -+ Self::deposit_event(Event::::Transferred { from, to, kitty_id }); -+ Ok(()) -+ } - } -diff --git a/src/lib.rs b/src/lib.rs -index ac84fa6..971af78 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -44,12 +44,7 @@ pub mod pallet { - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Created { owner: T::AccountId }, -- /* 🚧 TODO 🚧: Create a new event called `Transferred`: -- - Parameters are: -- - `from` which is `T::AccountId`. -- - `to` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- */ -+ Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - } - - #[pallet::error] -@@ -68,16 +63,14 @@ pub mod pallet { - Ok(()) - } - -- /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. -- - Input parameters are: -- - `origin` which is `OriginFor`. -- - `to` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - Returns a `DispatchResult`. -- - The inner logic should be: -- - Get the caller `who` from `ensure_signed`. -- - Call `Self::do_transfer`, and propagate the result. -- - End with Ok(()). -- */ -+ pub fn transfer( -+ origin: OriginFor, -+ to: T::AccountId, -+ kitty_id: [u8; 32], -+ ) -> DispatchResult { -+ let who = ensure_signed(origin)?; -+ Self::do_transfer(who, to, kitty_id)?; -+ Ok(()) -+ } - } - } -diff --git a/src/tests.rs b/src/tests.rs -index 939f943..0a90b3b 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -261,3 +261,19 @@ fn cannot_own_too_many_kitties() { - ); - }); - } -+ -+#[test] -+fn transfer_emits_event() { -+ new_test_ext().execute_with(|| { -+ // We need to set block number to 1 to view events. -+ System::set_block_number(1); -+ // Create a kitty to transfer -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ // Get the kitty id. -+ let kitty_id = Kitties::::iter_keys().collect::>()[0]; -+ assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); -+ System::assert_last_event( -+ Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), -+ ); -+ }); -+} diff --git a/src/25/solution/.gitignore b/src/25/source/.gitignore similarity index 100% rename from src/25/solution/.gitignore rename to src/25/source/.gitignore diff --git a/src/25/solution/Cargo.lock b/src/25/source/Cargo.lock similarity index 100% rename from src/25/solution/Cargo.lock rename to src/25/source/Cargo.lock diff --git a/src/25/solution/Cargo.toml b/src/25/source/Cargo.toml similarity index 100% rename from src/25/solution/Cargo.toml rename to src/25/source/Cargo.toml diff --git a/src/24/source/README.md b/src/25/source/README.md similarity index 100% rename from src/24/source/README.md rename to src/25/source/README.md diff --git a/src/33/source/changes.diff b/src/25/source/changes.diff similarity index 100% rename from src/33/source/changes.diff rename to src/25/source/changes.diff diff --git a/src/25/solution/rustfmt.toml b/src/25/source/rustfmt.toml similarity index 100% rename from src/25/solution/rustfmt.toml rename to src/25/source/rustfmt.toml diff --git a/src/25/solution/src/impls.rs b/src/25/source/src/impls.rs similarity index 87% rename from src/25/solution/src/impls.rs rename to src/25/source/src/impls.rs index 9d435f5b..793dec31 100644 --- a/src/25/solution/src/impls.rs +++ b/src/25/source/src/impls.rs @@ -34,9 +34,4 @@ impl Pallet { Self::deposit_event(Event::::Created { owner }); Ok(()) } - - pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); - Ok(()) - } } diff --git a/src/25/solution/src/lib.rs b/src/25/source/src/lib.rs similarity index 84% rename from src/25/solution/src/lib.rs rename to src/25/source/src/lib.rs index 971af78d..e0329740 100644 --- a/src/25/solution/src/lib.rs +++ b/src/25/source/src/lib.rs @@ -44,7 +44,6 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, } #[pallet::error] @@ -62,15 +61,5 @@ pub mod pallet { Self::mint(who, dna)?; Ok(()) } - - pub fn transfer( - origin: OriginFor, - to: T::AccountId, - kitty_id: [u8; 32], - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_transfer(who, to, kitty_id)?; - Ok(()) - } } } diff --git a/src/24/source/src/tests.rs b/src/25/source/src/tests.rs similarity index 97% rename from src/24/source/src/tests.rs rename to src/25/source/src/tests.rs index 939f9433..6a7f5b74 100644 --- a/src/24/source/src/tests.rs +++ b/src/25/source/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/25/template/README.md b/src/25/template/README.md deleted file mode 100644 index 96ec63e7..00000000 --- a/src/25/template/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# Transfer Extrinsic - -We will build the `transfer` extrinsic over two steps. In this step, we will just step up the skeleton of the extrinsic, the internal function, and the event that will be emitted at the end of the extrinsic. - -## Clean Code - -It's been a while since we started programming using the initial template provided by this tutorial. - -In that template, we already scaffolded for you the `create_kitty` extrinsic, the `mint` internal function, and the `Created` event. - -In this step, we will be doing the same thing, but for a new extrinsic `transfer`. - -To keep our code clean, we put a minimal amount of logic inside the `transfer` extrinsic, and instead push most of our logic into a `do_transfer` internal function. - -Remember that things which rely on the `#[pallet::*]` macros must be in the same file. - -But if you look closely, all of the "internal" functions like `gen_dna`, `mint`, and soon `do_transfer`, do not depend on the `#[pallet::*]` macros at all! - -So you could actually move all of this logic into its own file, and that would lead to much cleaner code. The same thing can be said for the definition of the `Kitty` struct and any other custom types you might use in your Pallet. - -However, for the purposes of this tutorial, keeping everything in one file makes things a bit easier to teach. - -## Your Turn - -There is nothing in this step that you are not already familiar with. - -Follow the `TODO`s in the template to: - -- Create a `transfer` extrinsic. -- Create a `do_transfer` internal function. -- Create a `Transferred` event. diff --git a/src/25/template/template.diff b/src/25/template/template.diff deleted file mode 100644 index eabed4f1..00000000 --- a/src/25/template/template.diff +++ /dev/null @@ -1,55 +0,0 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 793dec3..3dd1565 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -34,4 +34,15 @@ impl Pallet { - Self::deposit_event(Event::::Created { owner }); - Ok(()) - } -+ -+ /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: -+ - It has inputs: -+ - `from` which is `T::AccountId`. -+ - `to` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - It returns a `DispatchResult` -+ - The inner logic for now is: -+ - Call `Self::deposit_event` and emit `Event:::Transferred` with params. -+ - Return `Ok(())`. -+ */ - } -diff --git a/src/lib.rs b/src/lib.rs -index e032974..ac84fa6 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -44,6 +44,12 @@ pub mod pallet { - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { - Created { owner: T::AccountId }, -+ /* 🚧 TODO 🚧: Create a new event called `Transferred`: -+ - Parameters are: -+ - `from` which is `T::AccountId`. -+ - `to` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ */ - } - - #[pallet::error] -@@ -61,5 +67,17 @@ pub mod pallet { - Self::mint(who, dna)?; - Ok(()) - } -+ -+ /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. -+ - Input parameters are: -+ - `origin` which is `OriginFor`. -+ - `to` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - Returns a `DispatchResult`. -+ - The inner logic should be: -+ - Get the caller `who` from `ensure_signed`. -+ - Call `Self::do_transfer`, and propagate the result. -+ - End with Ok(()). -+ */ - } - } diff --git a/src/26/solution/solution.diff b/src/26/solution/solution.diff index c17ff180..542955f9 100644 --- a/src/26/solution/solution.diff +++ b/src/26/solution/solution.diff @@ -1,112 +1,91 @@ diff --git a/src/impls.rs b/src/impls.rs -index d44508b..5574211 100644 +index 3dd15659..9d435f5b 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -36,28 +36,23 @@ impl Pallet { +@@ -35,14 +35,8 @@ impl Pallet { + Ok(()) } - pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { -- /* 🚧 TODO 🚧: Sanity check the transfer is allowed: -- - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. -- - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. -- - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. -- */ -- -- /* 🚧 TODO 🚧: Update the owner of the kitty: -- - Update `kitty.owner` to `to`. -- - Update the `KittiesOwned` of `from` and `to: -- - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. -- - `try_push` the `kitty_id` to the `to_owned` vector. -- - If the vector is full, `map_err` and return `Error::::TooManyOwned`. -- - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. -- - Write logic to `swap_remove` the item from the `from_owned` vector. -- - If you cannot find the kitty in the vector, return `Error::::NoKitty`. -- */ -- -- /* 🚧 TODO 🚧: Update the final storage. -- - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. -- - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. -- - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. -- */ -+ ensure!(from != to, Error::::TransferToSelf); -+ let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; -+ ensure!(kitty.owner == from, Error::::NotOwner); -+ kitty.owner = to.clone(); -+ -+ let mut to_owned = KittiesOwned::::get(&to); -+ to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; -+ let mut from_owned = KittiesOwned::::get(&from); -+ if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { -+ from_owned.swap_remove(ind); -+ } else { -+ return Err(Error::::NoKitty.into()) -+ } -+ -+ Kitties::::insert(kitty_id, kitty); -+ KittiesOwned::::insert(&to, to_owned); -+ KittiesOwned::::insert(&from, from_owned); - - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); - Ok(()) +- /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: +- - It has inputs: +- - `from` which is `T::AccountId`. +- - `to` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - It returns a `DispatchResult` +- - The inner logic for now is: +- - Call `Self::deposit_event` and emit `Event:::Transferred` with params. +- - Return `Ok(())`. +- */ ++ pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { ++ Self::deposit_event(Event::::Transferred { from, to, kitty_id }); ++ Ok(()) ++ } + } diff --git a/src/lib.rs b/src/lib.rs -index 7256cb5..d44d77d 100644 +index ac84fa65..971af78d 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -52,11 +52,9 @@ pub mod pallet { - TooManyKitties, - DuplicateKitty, - TooManyOwned, -- /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: -- - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. -- - `NoKitty`: for when a transfer involves a kitty that does not exist. -- - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. +@@ -44,12 +44,7 @@ pub mod pallet { + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Created { owner: T::AccountId }, +- /* 🚧 TODO 🚧: Create a new event called `Transferred`: +- - Parameters are: +- - `from` which is `T::AccountId`. +- - `to` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. - */ -+ TransferToSelf, -+ NoKitty, -+ NotOwner, ++ Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, } - #[pallet::call] + #[pallet::error] +@@ -68,16 +63,14 @@ pub mod pallet { + Ok(()) + } + +- /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. +- - Input parameters are: +- - `origin` which is `OriginFor`. +- - `to` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - Returns a `DispatchResult`. +- - The inner logic should be: +- - Get the caller `who` from `ensure_signed`. +- - Call `Self::do_transfer`, and propagate the result. +- - End with Ok(()). +- */ ++ pub fn transfer( ++ origin: OriginFor, ++ to: T::AccountId, ++ kitty_id: [u8; 32], ++ ) -> DispatchResult { ++ let who = ensure_signed(origin)?; ++ Self::do_transfer(who, to, kitty_id)?; ++ Ok(()) ++ } + } + } diff --git a/src/tests.rs b/src/tests.rs -index 0a90b3b..6e16cc0 100644 +index 6a7f5b74..3dc077cc 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -277,3 +277,38 @@ fn transfer_emits_event() { +@@ -259,3 +259,19 @@ fn cannot_own_too_many_kitties() { ); }); } + +#[test] -+fn transfer_logic_works() { ++fn transfer_emits_event() { + new_test_ext().execute_with(|| { ++ // We need to set block number to 1 to view events. ++ System::set_block_number(1); ++ // Create a kitty to transfer + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ // Starting state looks good. -+ let kitty = &Kitties::::iter_values().collect::>()[0]; -+ let kitty_id = kitty.dna; -+ assert_eq!(kitty.owner, ALICE); -+ assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); -+ assert_eq!(KittiesOwned::::get(BOB), vec![]); -+ // Cannot transfer to yourself. -+ assert_noop!( -+ PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), -+ Error::::TransferToSelf -+ ); -+ // Cannot transfer a non-existent kitty. -+ assert_noop!( -+ PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), -+ Error::::NoKitty -+ ); -+ // Cannot transfer kitty you do not own. -+ assert_noop!( -+ PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), -+ Error::::NotOwner -+ ); -+ // Transfer should work when parameters are right. ++ // Get the kitty id. ++ let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); -+ // Storage is updated correctly. -+ assert_eq!(KittiesOwned::::get(ALICE), vec![]); -+ assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); -+ let kitty = &Kitties::::iter_values().collect::>()[0]; -+ assert_eq!(kitty.owner, BOB); ++ System::assert_last_event( ++ Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), ++ ); + }); +} diff --git a/src/26/solution/src/impls.rs b/src/26/solution/src/impls.rs index 55742115..9d435f5b 100644 --- a/src/26/solution/src/impls.rs +++ b/src/26/solution/src/impls.rs @@ -36,24 +36,6 @@ impl Pallet { } pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { - ensure!(from != to, Error::::TransferToSelf); - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == from, Error::::NotOwner); - kitty.owner = to.clone(); - - let mut to_owned = KittiesOwned::::get(&to); - to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; - let mut from_owned = KittiesOwned::::get(&from); - if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { - from_owned.swap_remove(ind); - } else { - return Err(Error::::NoKitty.into()) - } - - Kitties::::insert(kitty_id, kitty); - KittiesOwned::::insert(&to, to_owned); - KittiesOwned::::insert(&from, from_owned); - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); Ok(()) } diff --git a/src/26/solution/src/lib.rs b/src/26/solution/src/lib.rs index d44d77dd..971af78d 100644 --- a/src/26/solution/src/lib.rs +++ b/src/26/solution/src/lib.rs @@ -52,9 +52,6 @@ pub mod pallet { TooManyKitties, DuplicateKitty, TooManyOwned, - TransferToSelf, - NoKitty, - NotOwner, } #[pallet::call] diff --git a/src/26/solution/src/tests.rs b/src/26/solution/src/tests.rs index 6e16cc05..3dc077cc 100644 --- a/src/26/solution/src/tests.rs +++ b/src/26/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -277,38 +275,3 @@ fn transfer_emits_event() { ); }); } - -#[test] -fn transfer_logic_works() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Starting state looks good. - let kitty = &Kitties::::iter_values().collect::>()[0]; - let kitty_id = kitty.dna; - assert_eq!(kitty.owner, ALICE); - assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); - assert_eq!(KittiesOwned::::get(BOB), vec![]); - // Cannot transfer to yourself. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), - Error::::TransferToSelf - ); - // Cannot transfer a non-existent kitty. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), - Error::::NoKitty - ); - // Cannot transfer kitty you do not own. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), - Error::::NotOwner - ); - // Transfer should work when parameters are right. - assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); - // Storage is updated correctly. - assert_eq!(KittiesOwned::::get(ALICE), vec![]); - assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); - let kitty = &Kitties::::iter_values().collect::>()[0]; - assert_eq!(kitty.owner, BOB); - }); -} diff --git a/src/26/template/README.md b/src/26/template/README.md index 09214e5f..96ec63e7 100644 --- a/src/26/template/README.md +++ b/src/26/template/README.md @@ -1,65 +1,31 @@ -# Transfer Logic +# Transfer Extrinsic -Now that we scaffolded the `transfer` extrinsic, we can actually populate the appropriate logic to actually do a transfer. +We will build the `transfer` extrinsic over two steps. In this step, we will just step up the skeleton of the extrinsic, the internal function, and the event that will be emitted at the end of the extrinsic. -## Sanity Checks +## Clean Code -Before we should write any logic which actually transfers a kitty, we should sanity check that all the conditions are met for us to be able to actually execute the transfer. +It's been a while since we started programming using the initial template provided by this tutorial. -### Don't Transfer to Yourself +In that template, we already scaffolded for you the `create_kitty` extrinsic, the `mint` internal function, and the `Created` event. -The `do_transfer` logic has a `from` and `to` account. If they are the same, well we wouldn't really be doing a transfer. +In this step, we will be doing the same thing, but for a new extrinsic `transfer`. -As the developer, you can treat this as a `noop`, and return early, or as an error and return an error. In our pallet, we are choosing to emit an error. +To keep our code clean, we put a minimal amount of logic inside the `transfer` extrinsic, and instead push most of our logic into a `do_transfer` internal function. -This should be the first check you do because it takes no additional logic to make this check. You already have access to `from` and `to`, so checking equality is about the lightest amount of logic you could do. +Remember that things which rely on the `#[pallet::*]` macros must be in the same file. -Subsequent sanity checks will require us to read storage, and this is much more expensive to execute. If we can error early and without doing that storage read, that is way better for the blockchain. +But if you look closely, all of the "internal" functions like `gen_dna`, `mint`, and soon `do_transfer`, do not depend on the `#[pallet::*]` macros at all! -So remember, error early, error fast. +So you could actually move all of this logic into its own file, and that would lead to much cleaner code. The same thing can be said for the definition of the `Kitty` struct and any other custom types you might use in your Pallet. -### Does the Kitty Exist? +However, for the purposes of this tutorial, keeping everything in one file makes things a bit easier to teach. -The input to the `transfer` extrinsic allows the sender to submit any `[u8; 32]` identifier to transfer, but we shouldn't assume that kitty actually exists in storage. - -If the sender is trying to send a kitty which doesn't exist, we should emit an error. - -That should be easy to write with something like: - -```rust -let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; -``` - -### Correct Owner? - -This is an important one. - -It could be super simple to really mess up your blockchain if you do not check that the right owner is the one who is actually initiating the transfer. - -Now that we have the `kitty` object, we want to make sure the `from` account matches the `kitty.owner` account, else someone is trying to transfer the kitty who is not allowed to! - -## Updating the Owner - -At this point, we have done all of the sanity checks, and we can actually update the owner of the kitty. Based on our storage, we need to do this in three places: - -- Update the `Kitty` object so that `kitty.owner` is set to `to`. -- Add the `kitty_id` from the vector of `KittiesOwned` for `to`. -- Remove the `kitty_id` from the vector of `KittiesOwned` for `from`. - -This is really an exercise in writing Rust logic and using vector APIs. Unfortunately, we don't have any low level storage optimizations for such tasks, so you really just have to read both vectors and mutate them both. - -There are still optimizations you can make by following the principle of fail early and fail fast. For example, it is probably better to try and add a new item to the `to_owned` bounded vector first to check that their vector isn't full. If it is full, we wouldn't be able to do the transfer anyway, so we could fail early and fast. - -On the other hand, removing an item from a bounded vector can never fail, and since we already checked that `kitty.owner == from`, removing the `kitty_id` from `from_owned` should be infallible, unless there is really a big issue in the pallet logic. So the chances of an error here is much lower. - -## Update Storage - -Now that we have mutated the appropriate storage values, all that is left is to write those updated values back into storage. - -No magic tricks here, just call the `insert` API. +## Your Turn -The most important thing is to not forget to update everything! +There is nothing in this step that you are not already familiar with. -## Your Turn +Follow the `TODO`s in the template to: -Follow the `TODO`s included in the template to flesh out the logic required to complete the `transfer` extrinsic. +- Create a `transfer` extrinsic. +- Create a `do_transfer` internal function. +- Create a `Transferred` event. diff --git a/src/26/template/src/impls.rs b/src/26/template/src/impls.rs index d44508bd..3dd15659 100644 --- a/src/26/template/src/impls.rs +++ b/src/26/template/src/impls.rs @@ -35,31 +35,14 @@ impl Pallet { Ok(()) } - pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { - /* 🚧 TODO 🚧: Sanity check the transfer is allowed: - - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. - - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. - - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. - */ - - /* 🚧 TODO 🚧: Update the owner of the kitty: - - Update `kitty.owner` to `to`. - - Update the `KittiesOwned` of `from` and `to: - - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. - - `try_push` the `kitty_id` to the `to_owned` vector. - - If the vector is full, `map_err` and return `Error::::TooManyOwned`. - - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. - - Write logic to `swap_remove` the item from the `from_owned` vector. - - If you cannot find the kitty in the vector, return `Error::::NoKitty`. - */ - - /* 🚧 TODO 🚧: Update the final storage. - - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. - - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. - - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. - */ - - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); - Ok(()) - } + /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: + - It has inputs: + - `from` which is `T::AccountId`. + - `to` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - It returns a `DispatchResult` + - The inner logic for now is: + - Call `Self::deposit_event` and emit `Event:::Transferred` with params. + - Return `Ok(())`. + */ } diff --git a/src/26/template/src/lib.rs b/src/26/template/src/lib.rs index 7256cb5b..ac84fa65 100644 --- a/src/26/template/src/lib.rs +++ b/src/26/template/src/lib.rs @@ -44,7 +44,12 @@ pub mod pallet { #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, + /* 🚧 TODO 🚧: Create a new event called `Transferred`: + - Parameters are: + - `from` which is `T::AccountId`. + - `to` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + */ } #[pallet::error] @@ -52,11 +57,6 @@ pub mod pallet { TooManyKitties, DuplicateKitty, TooManyOwned, - /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: - - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. - - `NoKitty`: for when a transfer involves a kitty that does not exist. - - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. - */ } #[pallet::call] @@ -68,14 +68,16 @@ pub mod pallet { Ok(()) } - pub fn transfer( - origin: OriginFor, - to: T::AccountId, - kitty_id: [u8; 32], - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_transfer(who, to, kitty_id)?; - Ok(()) - } + /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. + - Input parameters are: + - `origin` which is `OriginFor`. + - `to` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - Returns a `DispatchResult`. + - The inner logic should be: + - Get the caller `who` from `ensure_signed`. + - Call `Self::do_transfer`, and propagate the result. + - End with Ok(()). + */ } } diff --git a/src/26/template/src/tests.rs b/src/26/template/src/tests.rs index 0a90b3b5..6a7f5b74 100644 --- a/src/26/template/src/tests.rs +++ b/src/26/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -109,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -261,19 +259,3 @@ fn cannot_own_too_many_kitties() { ); }); } - -#[test] -fn transfer_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - // Create a kitty to transfer - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Get the kitty id. - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); - System::assert_last_event( - Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), - ); - }); -} diff --git a/src/26/template/template.diff b/src/26/template/template.diff index ca43360d..dffab227 100644 --- a/src/26/template/template.diff +++ b/src/26/template/template.diff @@ -1,50 +1,55 @@ diff --git a/src/impls.rs b/src/impls.rs -index 9d435f5..d44508b 100644 +index 793dec31..3dd15659 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -36,6 +36,29 @@ impl Pallet { - } - - pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { -+ /* 🚧 TODO 🚧: Sanity check the transfer is allowed: -+ - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. -+ - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. -+ - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. -+ */ -+ -+ /* 🚧 TODO 🚧: Update the owner of the kitty: -+ - Update `kitty.owner` to `to`. -+ - Update the `KittiesOwned` of `from` and `to: -+ - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. -+ - `try_push` the `kitty_id` to the `to_owned` vector. -+ - If the vector is full, `map_err` and return `Error::::TooManyOwned`. -+ - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. -+ - Write logic to `swap_remove` the item from the `from_owned` vector. -+ - If you cannot find the kitty in the vector, return `Error::::NoKitty`. -+ */ -+ -+ /* 🚧 TODO 🚧: Update the final storage. -+ - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. -+ - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. -+ - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. -+ */ -+ - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); +@@ -34,4 +34,15 @@ impl Pallet { + Self::deposit_event(Event::::Created { owner }); Ok(()) } ++ ++ /* 🚧 TODO 🚧: Create an internal function called `do_transfer`: ++ - It has inputs: ++ - `from` which is `T::AccountId`. ++ - `to` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - It returns a `DispatchResult` ++ - The inner logic for now is: ++ - Call `Self::deposit_event` and emit `Event:::Transferred` with params. ++ - Return `Ok(())`. ++ */ + } diff --git a/src/lib.rs b/src/lib.rs -index 971af78..7256cb5 100644 +index e0329740..ac84fa65 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -52,6 +52,11 @@ pub mod pallet { - TooManyKitties, - DuplicateKitty, - TooManyOwned, -+ /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: -+ - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. -+ - `NoKitty`: for when a transfer involves a kitty that does not exist. -+ - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. +@@ -44,6 +44,12 @@ pub mod pallet { + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Created { owner: T::AccountId }, ++ /* 🚧 TODO 🚧: Create a new event called `Transferred`: ++ - Parameters are: ++ - `from` which is `T::AccountId`. ++ - `to` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. + */ } - #[pallet::call] + #[pallet::error] +@@ -61,5 +67,17 @@ pub mod pallet { + Self::mint(who, dna)?; + Ok(()) + } ++ ++ /* 🚧 TODO 🚧: Make a new extrinsic called `transfer`. ++ - Input parameters are: ++ - `origin` which is `OriginFor`. ++ - `to` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - Returns a `DispatchResult`. ++ - The inner logic should be: ++ - Get the caller `who` from `ensure_signed`. ++ - Call `Self::do_transfer`, and propagate the result. ++ - End with Ok(()). ++ */ + } + } diff --git a/src/27/README.md b/src/27/README.md index b3c92274..eb9560d4 100644 --- a/src/27/README.md +++ b/src/27/README.md @@ -17,21 +17,21 @@
- - + +
-
+
```rust -{{#include ./template/src/lib.rs}} +{{#include ./template/src/impls.rs}} ```
-
+
```rust -{{#include ./template/src/tests.rs}} +{{#include ./template/src/lib.rs}} ```
@@ -43,10 +43,19 @@
- + +
-
+
+ +```rust +{{#include ./solution/src/impls.rs}} +``` + +
+ +
```rust {{#include ./solution/src/lib.rs}} diff --git a/src/27/solution/solution.diff b/src/27/solution/solution.diff index 6954b362..dec6df9c 100644 --- a/src/27/solution/solution.diff +++ b/src/27/solution/solution.diff @@ -1,58 +1,112 @@ +diff --git a/src/impls.rs b/src/impls.rs +index d44508bd..55742115 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -36,28 +36,23 @@ impl Pallet { + } + + pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { +- /* 🚧 TODO 🚧: Sanity check the transfer is allowed: +- - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. +- - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. +- - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. +- */ +- +- /* 🚧 TODO 🚧: Update the owner of the kitty: +- - Update `kitty.owner` to `to`. +- - Update the `KittiesOwned` of `from` and `to: +- - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. +- - `try_push` the `kitty_id` to the `to_owned` vector. +- - If the vector is full, `map_err` and return `Error::::TooManyOwned`. +- - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. +- - Write logic to `swap_remove` the item from the `from_owned` vector. +- - If you cannot find the kitty in the vector, return `Error::::NoKitty`. +- */ +- +- /* 🚧 TODO 🚧: Update the final storage. +- - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. +- - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. +- - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. +- */ ++ ensure!(from != to, Error::::TransferToSelf); ++ let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ++ ensure!(kitty.owner == from, Error::::NotOwner); ++ kitty.owner = to.clone(); ++ ++ let mut to_owned = KittiesOwned::::get(&to); ++ to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; ++ let mut from_owned = KittiesOwned::::get(&from); ++ if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { ++ from_owned.swap_remove(ind); ++ } else { ++ return Err(Error::::NoKitty.into()) ++ } ++ ++ Kitties::::insert(kitty_id, kitty); ++ KittiesOwned::::insert(&to, to_owned); ++ KittiesOwned::::insert(&from, from_owned); + + Self::deposit_event(Event::::Transferred { from, to, kitty_id }); + Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 5a4e765..59150bd 100644 +index 7256cb5b..d44d77dd 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -4,8 +4,8 @@ mod impls; - mod tests; - - use frame::prelude::*; --/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ --/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ -+use frame::traits::fungible::Inspect; -+use frame::traits::fungible::Mutate; - pub use pallet::*; - - #[frame::pallet(dev_mode)] -@@ -19,12 +19,8 @@ pub mod pallet { - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - -- /* 🚧 TODO 🚧: -- - Create a new associated type named `NativeBalance`. -- - Require that `NativeBalance` implements the following traits: -- - `Inspect` which is generic over `Self::AccountId`. -- - `Mutate` which is also generic over `Self::AccountId`. +@@ -52,11 +52,9 @@ pub mod pallet { + TooManyKitties, + DuplicateKitty, + TooManyOwned, +- /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: +- - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. +- - `NoKitty`: for when a transfer involves a kitty that does not exist. +- - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. - */ -+ /// The Fungible handler for the kitties pallet. -+ type NativeBalance: Inspect + Mutate; ++ TransferToSelf, ++ NoKitty, ++ NotOwner, } - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + #[pallet::call] diff --git a/src/tests.rs b/src/tests.rs -index c136aef..dcb9619 100644 +index 3dc077cc..4a168591 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -81,7 +81,7 @@ impl pallet_balances::Config for TestRuntime { - // will also need to update this configuration to represent that. - impl pallet_kitties::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; -- /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ -+ type NativeBalance = PalletBalances; - } - - // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` -@@ -313,3 +313,14 @@ fn transfer_logic_works() { - assert_eq!(kitty.owner, BOB); +@@ -275,3 +275,38 @@ fn transfer_emits_event() { + ); }); } + +#[test] -+fn native_balance_associated_type_works() { ++fn transfer_logic_works() { + new_test_ext().execute_with(|| { -+ assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); -+ assert_eq!( -+ <::NativeBalance as Inspect<_>>::total_balance(&ALICE), -+ 1337 ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ // Starting state looks good. ++ let kitty = &Kitties::::iter_values().collect::>()[0]; ++ let kitty_id = kitty.dna; ++ assert_eq!(kitty.owner, ALICE); ++ assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); ++ assert_eq!(KittiesOwned::::get(BOB), vec![]); ++ // Cannot transfer to yourself. ++ assert_noop!( ++ PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), ++ Error::::TransferToSelf ++ ); ++ // Cannot transfer a non-existent kitty. ++ assert_noop!( ++ PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), ++ Error::::NoKitty ++ ); ++ // Cannot transfer kitty you do not own. ++ assert_noop!( ++ PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), ++ Error::::NotOwner + ); ++ // Transfer should work when parameters are right. ++ assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); ++ // Storage is updated correctly. ++ assert_eq!(KittiesOwned::::get(ALICE), vec![]); ++ assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); ++ let kitty = &Kitties::::iter_values().collect::>()[0]; ++ assert_eq!(kitty.owner, BOB); + }); +} diff --git a/src/27/solution/src/lib.rs b/src/27/solution/src/lib.rs index 59150bd6..d44d77dd 100644 --- a/src/27/solution/src/lib.rs +++ b/src/27/solution/src/lib.rs @@ -4,8 +4,6 @@ mod impls; mod tests; use frame::prelude::*; -use frame::traits::fungible::Inspect; -use frame::traits::fungible::Mutate; pub use pallet::*; #[frame::pallet(dev_mode)] @@ -18,9 +16,6 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// The Fungible handler for the kitties pallet. - type NativeBalance: Inspect + Mutate; } #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/src/27/solution/src/tests.rs b/src/27/solution/src/tests.rs index dcb9619c..4a168591 100644 --- a/src/27/solution/src/tests.rs +++ b/src/27/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -81,7 +81,6 @@ impl pallet_balances::Config for TestRuntime { // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; - type NativeBalance = PalletBalances; } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` @@ -110,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -313,14 +310,3 @@ fn transfer_logic_works() { assert_eq!(kitty.owner, BOB); }); } - -#[test] -fn native_balance_associated_type_works() { - new_test_ext().execute_with(|| { - assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); - assert_eq!( - <::NativeBalance as Inspect<_>>::total_balance(&ALICE), - 1337 - ); - }); -} diff --git a/src/27/template/README.md b/src/27/template/README.md index 82a11584..09214e5f 100644 --- a/src/27/template/README.md +++ b/src/27/template/README.md @@ -1,118 +1,65 @@ -# Native Balances +# Transfer Logic -In our next steps, we will introduce a marketplace for buying and selling kitties. +Now that we scaffolded the `transfer` extrinsic, we can actually populate the appropriate logic to actually do a transfer. -For that, we will need to access a user's blockchain balance in addition to the logic in our pallet. +## Sanity Checks -## The Balances Pallet +Before we should write any logic which actually transfers a kitty, we should sanity check that all the conditions are met for us to be able to actually execute the transfer. -Every blockchain has a cryptocurrency associated with it. Bitcoin has BTC. Ethereum as ETH. +### Don't Transfer to Yourself -For Polkadot, that native token is the DOT token. +The `do_transfer` logic has a `from` and `to` account. If they are the same, well we wouldn't really be doing a transfer. -Polkadot is built using FRAME and Pallets just like you have been building so far. Included in the `polkadot-sdk` is [`pallet_balances`](https://docs.rs/pallet-balances/39.0.0/pallet_balances/index.html). +As the developer, you can treat this as a `noop`, and return early, or as an error and return an error. In our pallet, we are choosing to emit an error. -This is a Pallet designed specifically to manage the native balance for users. +This should be the first check you do because it takes no additional logic to make this check. You already have access to `from` and `to`, so checking equality is about the lightest amount of logic you could do. -It has the ability to: +Subsequent sanity checks will require us to read storage, and this is much more expensive to execute. If we can error early and without doing that storage read, that is way better for the blockchain. -- Mint new tokens. -- Transfer tokens between users. -- Apply freezes and holds for users. -- Slash tokens from accounts. -- and much more... +So remember, error early, error fast. -Basically everything you could expect to want or need when working with the native balance of a blockchain. +### Does the Kitty Exist? -## Pallet Coupling +The input to the `transfer` extrinsic allows the sender to submit any `[u8; 32]` identifier to transfer, but we shouldn't assume that kitty actually exists in storage. -The `polkadot-sdk` is designed to be a flexible and modular blockchain development SDK. +If the sender is trying to send a kitty which doesn't exist, we should emit an error. -Part of that flexibility comes through the use of Rust traits to allow two pallets to interact with one another. We call this pallet coupling, and there are two forms of it we will briefly explain next. - -### Tight Coupling - -We have already been using tight coupling throughout this tutorial to give our custom Kitties pallet access to the `frame_system` pallet: +That should be easy to write with something like: ```rust -#[pallet::config] -pub trait Config: frame_system::Config { - // Through supertraits, we are tightly coupled to `frame_system`. -} +let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ``` -You can see our Pallet's `Config` is tightly coupled to the `frame_system::Config`. This is why we have been able to use the types coming from `frame_system` (like `T::AccountId`) and why we have been able to use functions directly from `frame_system` (like `frame_system::Pallet::::block_number()`). - -In fact, every Pallet built with FRAME is required to be tightly coupled to `frame_system`. But if we wanted, we could tightly couple to other pallets too! +### Correct Owner? -```rust -#[pallet::config] -pub trait Config: frame_system::Config + pallet_balances:: Config { - // Here you can see we can also tightly couple to `pallet_balances`. -} -``` - -The upside to tight coupling is gaining direct access to the pallet's Rust module, and all the functions, types, storage, and everything else that is included in that pallet. - -With tight coupling, we are able to access the `pallet_balances` APIs like: - -```rust -let total_issuance = pallet_balances::Pallet::::total_issuance(); -let alice_balance = pallet_balances::Pallet::::total_balance(alice); -pallet_balances::Pallet::::mint_into(alice, amount)?; -pallet_balances::Pallet::::transfer(alice, bob, amount, Preserve)?; -``` +This is an important one. -The downside however, is that you make your pallet very rigid, forcing everyone who wants to use your pallet to use a specific version of `pallet_balances` which you import into your crate. +It could be super simple to really mess up your blockchain if you do not check that the right owner is the one who is actually initiating the transfer. -### Loose Coupling +Now that we have the `kitty` object, we want to make sure the `from` account matches the `kitty.owner` account, else someone is trying to transfer the kitty who is not allowed to! -Loose coupling is the more flexible approach to accessing another pallet, and will be our way of integrating the Balances Pallet in our project. +## Updating the Owner -Loose coupling involves using the interface of a `trait` to access the APIs of another Pallet. +At this point, we have done all of the sanity checks, and we can actually update the owner of the kitty. Based on our storage, we need to do this in three places: -In the case of accessing the Balances Pallet, it looks exactly like this: +- Update the `Kitty` object so that `kitty.owner` is set to `to`. +- Add the `kitty_id` from the vector of `KittiesOwned` for `to`. +- Remove the `kitty_id` from the vector of `KittiesOwned` for `from`. -```rust -#[pallet::config] -pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /// Access the balances pallet through the associated type `NativeBalance`. - /// The `NativeBalance` type must implement `Inspect` and `Mutate`. - /// Both of these traits are generic over the `AccountId` type. - type NativeBalance: Inspect + Mutate; -} -``` +This is really an exercise in writing Rust logic and using vector APIs. Unfortunately, we don't have any low level storage optimizations for such tasks, so you really just have to read both vectors and mutate them both. -You can see we introduce a new associated type called `NativeBalance`. We then require that this type must implement two traits: +There are still optimizations you can make by following the principle of fail early and fail fast. For example, it is probably better to try and add a new item to the `to_owned` bounded vector first to check that their vector isn't full. If it is full, we wouldn't be able to do the transfer anyway, so we could fail early and fast. -- [`fungible::Inspect`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Inspect.html): A trait allowing us to read data about a fungible token. -- [`fungible::Mutate`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Mutate.html): A trait allowing us to write data about a fungible token. +On the other hand, removing an item from a bounded vector can never fail, and since we already checked that `kitty.owner == from`, removing the `kitty_id` from `from_owned` should be infallible, unless there is really a big issue in the pallet logic. So the chances of an error here is much lower. -So with this, we are able to access our native balance using APIs like: +## Update Storage -```rust -// Example APIs coming from `Inspect`. -let total_issuance = T::NativeBalance::total_issuance(); -let alice_balance = T::NativeBalance::total_balance(alice); -// Example APIs coming from `Mutate`. -T::NativeBalance::mint_into(alice, amount)?; -T::NativeBalance::transfer(alice, bob, amount, Preserve)?; -``` +Now that we have mutated the appropriate storage values, all that is left is to write those updated values back into storage. -The key difference here is that we do NOT assume that these APIs must come from specifically `pallet_balances`. If you wanted to use another pallet in the `polkadot-sdk` ecosystem which provides these same functions, you can use it! Our pallet is NOT tightly coupled to which pallet provides access to the `NativeBalance`, it only requires that there is something implementing the `Inspect` and `Mutate` traits. +No magic tricks here, just call the `insert` API. -The power of loose coupling may not be immediately obvious, but as you get deeper into developing in the Polkadot ecosystem, you will start to realize how powerful this approach can be. +The most important thing is to not forget to update everything! ## Your Turn -Import the `Inspect` and `Mutate` traits from `frame::traits::fungible`. - -Introduce the `NativeBalance` associated type to your `trait Config` using these traits. - -## Learn More - -To continue learning about Pallet Coupling, check out the following video from the Polkadot Blockchain Academy: - - +Follow the `TODO`s included in the template to flesh out the logic required to complete the `transfer` extrinsic. diff --git a/src/27/template/src/impls.rs b/src/27/template/src/impls.rs index 55742115..d44508bd 100644 --- a/src/27/template/src/impls.rs +++ b/src/27/template/src/impls.rs @@ -36,23 +36,28 @@ impl Pallet { } pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { - ensure!(from != to, Error::::TransferToSelf); - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == from, Error::::NotOwner); - kitty.owner = to.clone(); - - let mut to_owned = KittiesOwned::::get(&to); - to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; - let mut from_owned = KittiesOwned::::get(&from); - if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { - from_owned.swap_remove(ind); - } else { - return Err(Error::::NoKitty.into()) - } - - Kitties::::insert(kitty_id, kitty); - KittiesOwned::::insert(&to, to_owned); - KittiesOwned::::insert(&from, from_owned); + /* 🚧 TODO 🚧: Sanity check the transfer is allowed: + - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. + - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. + - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. + */ + + /* 🚧 TODO 🚧: Update the owner of the kitty: + - Update `kitty.owner` to `to`. + - Update the `KittiesOwned` of `from` and `to: + - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. + - `try_push` the `kitty_id` to the `to_owned` vector. + - If the vector is full, `map_err` and return `Error::::TooManyOwned`. + - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. + - Write logic to `swap_remove` the item from the `from_owned` vector. + - If you cannot find the kitty in the vector, return `Error::::NoKitty`. + */ + + /* 🚧 TODO 🚧: Update the final storage. + - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. + - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. + - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. + */ Self::deposit_event(Event::::Transferred { from, to, kitty_id }); Ok(()) diff --git a/src/27/template/src/lib.rs b/src/27/template/src/lib.rs index 5a4e7650..7256cb5b 100644 --- a/src/27/template/src/lib.rs +++ b/src/27/template/src/lib.rs @@ -4,8 +4,6 @@ mod impls; mod tests; use frame::prelude::*; -/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ -/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ pub use pallet::*; #[frame::pallet(dev_mode)] @@ -18,13 +16,6 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - - /* 🚧 TODO 🚧: - - Create a new associated type named `NativeBalance`. - - Require that `NativeBalance` implements the following traits: - - `Inspect` which is generic over `Self::AccountId`. - - `Mutate` which is also generic over `Self::AccountId`. - */ } #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] @@ -61,9 +52,11 @@ pub mod pallet { TooManyKitties, DuplicateKitty, TooManyOwned, - TransferToSelf, - NoKitty, - NotOwner, + /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: + - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. + - `NoKitty`: for when a transfer involves a kitty that does not exist. + - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. + */ } #[pallet::call] diff --git a/src/27/template/src/tests.rs b/src/27/template/src/tests.rs index c136aefa..3dc077cc 100644 --- a/src/27/template/src/tests.rs +++ b/src/27/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -81,7 +81,6 @@ impl pallet_balances::Config for TestRuntime { // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; - /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` @@ -110,8 +109,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -278,38 +275,3 @@ fn transfer_emits_event() { ); }); } - -#[test] -fn transfer_logic_works() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Starting state looks good. - let kitty = &Kitties::::iter_values().collect::>()[0]; - let kitty_id = kitty.dna; - assert_eq!(kitty.owner, ALICE); - assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); - assert_eq!(KittiesOwned::::get(BOB), vec![]); - // Cannot transfer to yourself. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), - Error::::TransferToSelf - ); - // Cannot transfer a non-existent kitty. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), - Error::::NoKitty - ); - // Cannot transfer kitty you do not own. - assert_noop!( - PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), - Error::::NotOwner - ); - // Transfer should work when parameters are right. - assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); - // Storage is updated correctly. - assert_eq!(KittiesOwned::::get(ALICE), vec![]); - assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); - let kitty = &Kitties::::iter_values().collect::>()[0]; - assert_eq!(kitty.owner, BOB); - }); -} diff --git a/src/27/template/template.diff b/src/27/template/template.diff index 8da0eeb7..8f018c0e 100644 --- a/src/27/template/template.diff +++ b/src/27/template/template.diff @@ -1,39 +1,50 @@ +diff --git a/src/impls.rs b/src/impls.rs +index 9d435f5b..d44508bd 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -36,6 +36,29 @@ impl Pallet { + } + + pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { ++ /* 🚧 TODO 🚧: Sanity check the transfer is allowed: ++ - First `ensure!` that `from` and `to` are not equal, else return `Error::::TransferToSelf`. ++ - Get the `kitty` from `Kitties` using `kitty_id`, else return `Error::::NoKitty`. ++ - Check the `kitty.owner` is equal to `from`, else return `NotOwner`. ++ */ ++ ++ /* 🚧 TODO 🚧: Update the owner of the kitty: ++ - Update `kitty.owner` to `to`. ++ - Update the `KittiesOwned` of `from` and `to: ++ - Create a mutable `to_owned` by querying `KittiesOwned` for `to`. ++ - `try_push` the `kitty_id` to the `to_owned` vector. ++ - If the vector is full, `map_err` and return `Error::::TooManyOwned`. ++ - Create a mutable `from_owned` by querying `KittiesOwned` for `from`. ++ - Write logic to `swap_remove` the item from the `from_owned` vector. ++ - If you cannot find the kitty in the vector, return `Error::::NoKitty`. ++ */ ++ ++ /* 🚧 TODO 🚧: Update the final storage. ++ - Insert into `Kitties` under `kitty_id` the modified `kitty` struct. ++ - Insert into `KittiesOwned` under `to` the modified `to_owned` vector. ++ - Insert into `KittiesOwned` under `from` the modified `from_owned` vector. ++ */ ++ + Self::deposit_event(Event::::Transferred { from, to, kitty_id }); + Ok(()) + } diff --git a/src/lib.rs b/src/lib.rs -index d44d77d..5a4e765 100644 +index 971af78d..7256cb5b 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -4,6 +4,8 @@ mod impls; - mod tests; - - use frame::prelude::*; -+/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ -+/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ - pub use pallet::*; - - #[frame::pallet(dev_mode)] -@@ -16,6 +18,13 @@ pub mod pallet { - #[pallet::config] - pub trait Config: frame_system::Config { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; -+ -+ /* 🚧 TODO 🚧: -+ - Create a new associated type named `NativeBalance`. -+ - Require that `NativeBalance` implements the following traits: -+ - `Inspect` which is generic over `Self::AccountId`. -+ - `Mutate` which is also generic over `Self::AccountId`. +@@ -52,6 +52,11 @@ pub mod pallet { + TooManyKitties, + DuplicateKitty, + TooManyOwned, ++ /* 🚧 TODO 🚧: Add new `Error` variants needed for `do_transfer`: ++ - `TransferToSelf`: for when the `from` and `to` of the transfer is the same. ++ - `NoKitty`: for when a transfer involves a kitty that does not exist. ++ - `NotOwner`: for when a transfer is initiated by someone who is not the current owner. + */ } - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] -diff --git a/src/tests.rs b/src/tests.rs -index 6e16cc0..c136aef 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -81,6 +81,7 @@ impl pallet_balances::Config for TestRuntime { - // will also need to update this configuration to represent that. - impl pallet_kitties::Config for TestRuntime { - type RuntimeEvent = RuntimeEvent; -+ /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ - } - - // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` + #[pallet::call] diff --git a/src/28/README.md b/src/28/README.md index eb9560d4..b3c92274 100644 --- a/src/28/README.md +++ b/src/28/README.md @@ -17,21 +17,21 @@
- - + +
-
+
```rust -{{#include ./template/src/impls.rs}} +{{#include ./template/src/lib.rs}} ```
-
+
```rust -{{#include ./template/src/lib.rs}} +{{#include ./template/src/tests.rs}} ```
@@ -43,19 +43,10 @@
- - +
-
- -```rust -{{#include ./solution/src/impls.rs}} -``` - -
- -
+
```rust {{#include ./solution/src/lib.rs}} diff --git a/src/28/solution/solution.diff b/src/28/solution/solution.diff index 9b790650..d4d7c80c 100644 --- a/src/28/solution/solution.diff +++ b/src/28/solution/solution.diff @@ -1,76 +1,58 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 2c01fdc..16bdb73 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -20,8 +20,7 @@ impl Pallet { - } - - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -- /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ -- let kitty = Kitty { dna, owner: owner.clone() }; -+ let kitty = Kitty { dna, owner: owner.clone(), price: None }; - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); - -@@ -41,7 +40,7 @@ impl Pallet { - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == from, Error::::NotOwner); - kitty.owner = to.clone(); -- /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ -+ kitty.price = None; - - let mut to_owned = KittiesOwned::::get(&to); - to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/lib.rs b/src/lib.rs -index 3a10644..7d8350d 100644 +index 5a4e7650..59150bd6 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -23,13 +23,9 @@ pub mod pallet { - type NativeBalance: Inspect + Mutate; - } +@@ -4,8 +4,8 @@ mod impls; + mod tests; -- /* 🚧 TODO 🚧: -- - Create a new type alias called `BalanceOf`. -- - Extract the `Balance` type from the `NativeBalance` associated type: -- - The `Balance` type comes from the `Inspect` trait. -- - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. -- - Inspect comes from `NativeBalance`, which comes from `T as Config`. -- */ -+ // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. -+ pub type BalanceOf = -+ <::NativeBalance as Inspect<::AccountId>>::Balance; + use frame::prelude::*; +-/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ +-/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ ++use frame::traits::fungible::Inspect; ++use frame::traits::fungible::Mutate; + pub use pallet::*; - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(T))] -@@ -37,7 +33,7 @@ pub mod pallet { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], - pub owner: T::AccountId, -- /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ -+ pub price: Option>, + #[frame::pallet(dev_mode)] +@@ -19,12 +19,8 @@ pub mod pallet { + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + +- /* 🚧 TODO 🚧: +- - Create a new associated type named `NativeBalance`. +- - Require that `NativeBalance` implements the following traits: +- - `Inspect` which is generic over `Self::AccountId`. +- - `Mutate` which is also generic over `Self::AccountId`. +- */ ++ /// The Fungible handler for the kitties pallet. ++ type NativeBalance: Inspect + Mutate; } - #[pallet::storage] + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] diff --git a/src/tests.rs b/src/tests.rs -index dcb9619..5326761 100644 +index 8a76f591..df5dc0dd 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -28,7 +28,7 @@ type Block = frame_system::mocking::MockBlock; - // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. - const ALICE: u64 = 1; - const BOB: u64 = 2; --const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; -+const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; +@@ -81,7 +81,7 @@ impl pallet_balances::Config for TestRuntime { + // will also need to update this configuration to represent that. + impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; +- /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ ++ type NativeBalance = PalletBalances; + } - #[runtime] - mod runtime { -@@ -324,3 +324,9 @@ fn native_balance_associated_type_works() { - ); + // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` +@@ -311,3 +311,14 @@ fn transfer_logic_works() { + assert_eq!(kitty.owner, BOB); }); } + +#[test] -+fn balance_of_type_works() { -+ // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. -+ let _example_balance: BalanceOf = 1337u64; ++fn native_balance_associated_type_works() { ++ new_test_ext().execute_with(|| { ++ assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); ++ assert_eq!( ++ <::NativeBalance as Inspect<_>>::total_balance(&ALICE), ++ 1337 ++ ); ++ }); +} diff --git a/src/28/solution/src/impls.rs b/src/28/solution/src/impls.rs index 16bdb731..55742115 100644 --- a/src/28/solution/src/impls.rs +++ b/src/28/solution/src/impls.rs @@ -20,7 +20,7 @@ impl Pallet { } pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone(), price: None }; + let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); @@ -40,7 +40,6 @@ impl Pallet { let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ensure!(kitty.owner == from, Error::::NotOwner); kitty.owner = to.clone(); - kitty.price = None; let mut to_owned = KittiesOwned::::get(&to); to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/28/solution/src/lib.rs b/src/28/solution/src/lib.rs index 7d8350d6..59150bd6 100644 --- a/src/28/solution/src/lib.rs +++ b/src/28/solution/src/lib.rs @@ -23,17 +23,12 @@ pub mod pallet { type NativeBalance: Inspect + Mutate; } - // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. - pub type BalanceOf = - <::NativeBalance as Inspect<::AccountId>>::Balance; - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Kitty { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, - pub price: Option>, } #[pallet::storage] diff --git a/src/28/solution/src/tests.rs b/src/28/solution/src/tests.rs index 53267614..df5dc0dd 100644 --- a/src/28/solution/src/tests.rs +++ b/src/28/solution/src/tests.rs @@ -28,7 +28,7 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; -const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; +const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] mod runtime { @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -324,9 +322,3 @@ fn native_balance_associated_type_works() { ); }); } - -#[test] -fn balance_of_type_works() { - // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. - let _example_balance: BalanceOf = 1337u64; -} diff --git a/src/28/template/README.md b/src/28/template/README.md index a680fdc2..82a11584 100644 --- a/src/28/template/README.md +++ b/src/28/template/README.md @@ -1,154 +1,118 @@ -# Native Balance Type +# Native Balances -One of the most challenging parts of using the `polkadot-sdk` is using generic types. +In our next steps, we will introduce a marketplace for buying and selling kitties. -Hopefully, things like `T::AccountId` have been easy enough to use, but using the `Balance` type coming from `NativeBalance` can be a little tricky. +For that, we will need to access a user's blockchain balance in addition to the logic in our pallet. -## Generic Types +## The Balances Pallet -The ability to use generic types in Rust is extremely powerful, but it can be hard to easily understand for those new to the `polkadot-sdk`. Not to mention that `polkadot-sdk` uses a lot of generic types. +Every blockchain has a cryptocurrency associated with it. Bitcoin has BTC. Ethereum as ETH. -The key thing to remember is that all of these generic types eventually become a concrete type. So while we are not sure while we write our pallet if `T::AccountId` is `[u8; 20]`, `[u8; 32]`, or maybe even a `String`, we know that eventually it must be one of these primitive types. +For Polkadot, that native token is the DOT token. -In this situation, the same can be said for the `Balance` type coming from `NativeBalance`. Depending on the configuration of your blockchain, and the required traits that `Balance` needs to satisfy, it is perfectly valid for your `Balance` type to be `u32`, `u64`, or `u128`. +Polkadot is built using FRAME and Pallets just like you have been building so far. Included in the `polkadot-sdk` is [`pallet_balances`](https://docs.rs/pallet-balances/39.0.0/pallet_balances/index.html). -But it can only concretely be one of those, and the weird thing is that we don't know which one it is as we program our Kitties pallet! +This is a Pallet designed specifically to manage the native balance for users. -Let's look at how we would interact with this generic type, and solve many of the issues you might encounter when trying to use it. +It has the ability to: -## Balance Type +- Mint new tokens. +- Transfer tokens between users. +- Apply freezes and holds for users. +- Slash tokens from accounts. +- and much more... -The `Balance` type is ultimately configured inside `pallet_balances`, and remember, we don't have direct access to that pallet because we used loose coupling. +Basically everything you could expect to want or need when working with the native balance of a blockchain. -The way we can access the `Balance` type is through the `Inspect` trait of the `NativeBalance` associated type. Accessing it is kind of funny, which is why we commonly introduce a `BalanceOf` alias type like so: +## Pallet Coupling -```rust -// Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. -pub type BalanceOf = - <::NativeBalance as Inspect<::AccountId>>::Balance; +The `polkadot-sdk` is designed to be a flexible and modular blockchain development SDK. -``` +Part of that flexibility comes through the use of Rust traits to allow two pallets to interact with one another. We call this pallet coupling, and there are two forms of it we will briefly explain next. -This is kind of a crazy line of code, so let's break it down: - -- At the very end, there is a `Balance` type. This is what we want to access and alias. - ```rust - Balance - ``` -- This `Balance` type is part of the `Inspect` trait. - ```rust - Inspect::Balance - ``` -- The `Inspect` trait is generic over `AccountId`, so we need to include that. - ```rust - Inspect::Balance - ``` -- The `AccountId` type comes from `T`, through `frame_system::Config`, where the type is defined. - ```rust - Inspect<::AccountId>::Balance - ``` -- The `Inspect` is accessible through the `NativeBalance` associated type. - ```rust - ::AccountId>>::Balance - ``` -- The `NativeBalance` type also comes from `T`, but though our pallet's own `Config`. - ```rust - <::NativeBalance as Inspect<::AccountId>>::Balance - ``` -- Finally, we assign this to a new alias `BalanceOf` which is generic over ``. - ```rust - pub type BalanceOf = <::NativeBalance as Inspect<::AccountId>>::Balance - ``` - -Phew! Did you get all that? If not, don't worry too much. You can review these Rust concepts after you complete the tutorial, but there is nothing here which is specific to the `polkadot-sdk`. - -Why do we need this `BalanceOf` alias? - -So that we can change this: +### Tight Coupling + +We have already been using tight coupling throughout this tutorial to give our custom Kitties pallet access to the `frame_system` pallet: ```rust -fn set_price(id: [u8; 32], price: <::NativeBalance as Inspect<::AccountId>>::Balance) { - // -- snip -- +#[pallet::config] +pub trait Config: frame_system::Config { + // Through supertraits, we are tightly coupled to `frame_system`. } ``` -To this: +You can see our Pallet's `Config` is tightly coupled to the `frame_system::Config`. This is why we have been able to use the types coming from `frame_system` (like `T::AccountId`) and why we have been able to use functions directly from `frame_system` (like `frame_system::Pallet::::block_number()`). + +In fact, every Pallet built with FRAME is required to be tightly coupled to `frame_system`. But if we wanted, we could tightly couple to other pallets too! ```rust -fn set_price(id: [u8; 32], price: BalanceOf) { - // -- snip -- +#[pallet::config] +pub trait Config: frame_system::Config + pallet_balances:: Config { + // Here you can see we can also tightly couple to `pallet_balances`. } ``` -The second is way better right? This type alias handles extracting the `Balance` type out of the `NativeBalance` associated type every time, and all we need to do is pass the generic parameter `T`. +The upside to tight coupling is gaining direct access to the pallet's Rust module, and all the functions, types, storage, and everything else that is included in that pallet. -## Basic API - -Let's learn how we can use the `BalanceOf` type in our code. - -### Interacting with Primitive Numbers - -I will repeat again: Because the `BalanceOf` type is generic, we cannot know what underlying type it is. This means we CANNOT write the following: +With tight coupling, we are able to access the `pallet_balances` APIs like: ```rust -// This code doesn't work -fn add_one(input: BalanceOf) -> BalanceOf { - input + 1u128 -} +let total_issuance = pallet_balances::Pallet::::total_issuance(); +let alice_balance = pallet_balances::Pallet::::total_balance(alice); +pallet_balances::Pallet::::mint_into(alice, amount)?; +pallet_balances::Pallet::::transfer(alice, bob, amount, Preserve)?; ``` -Even if we don't include `u128`, we cannot write the line above. This is because that line assumes that `input` must be some specific number type, and in that code, it is simply generic. +The downside however, is that you make your pallet very rigid, forcing everyone who wants to use your pallet to use a specific version of `pallet_balances` which you import into your crate. -However, `BalanceOf` [does have traits](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/trait.Balance.html) that we can use to interact with it. The key one being [`AtLeast32BitUnsigned`](https://docs.rs/polkadot-sdk-frame/0.7.0/polkadot_sdk_frame/arithmetic/trait.AtLeast32BitUnsigned.html). +### Loose Coupling -This means our `BalanceOf` must be an unsigned integer, and must be at least `u32`. So it could be `u32`, `u64`, `u128`, or even bigger if you import other crates with those larger unsigned types. +Loose coupling is the more flexible approach to accessing another pallet, and will be our way of integrating the Balances Pallet in our project. -This also means we **would** be able to write the following: +Loose coupling involves using the interface of a `trait` to access the APIs of another Pallet. + +In the case of accessing the Balances Pallet, it looks exactly like this: ```rust -// This code does work -fn add_one(input: BalanceOf) -> BalanceOf { - input + 1u32.into() +#[pallet::config] +pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// Access the balances pallet through the associated type `NativeBalance`. + /// The `NativeBalance` type must implement `Inspect` and `Mutate`. + /// Both of these traits are generic over the `AccountId` type. + type NativeBalance: Inspect + Mutate; } ``` -We can convert any `u32` into the `BalanceOf` type because we know at a minimum `BalanceOf` is `AtLeast32BitUnsigned`. - -### Interacting with Itself +You can see we introduce a new associated type called `NativeBalance`. We then require that this type must implement two traits: -Interacting between two `BalanceOf` types will act just like two normal numbers of the same type. +- [`fungible::Inspect`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Inspect.html): A trait allowing us to read data about a fungible token. +- [`fungible::Mutate`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Mutate.html): A trait allowing us to write data about a fungible token. -You can add them, subtract them, multiply them, divide them, and even better, do safe math operations on all of them. +So with this, we are able to access our native balance using APIs like: ```rust -let total_balance: BalanceOf = balance_1.checked_add(balance_2).ok_or(ArithmeticError::Overflow)?; +// Example APIs coming from `Inspect`. +let total_issuance = T::NativeBalance::total_issuance(); +let alice_balance = T::NativeBalance::total_balance(alice); +// Example APIs coming from `Mutate`. +T::NativeBalance::mint_into(alice, amount)?; +T::NativeBalance::transfer(alice, bob, amount, Preserve)?; ``` -## Price Field - -We are going to use `BalanceOf` in the `Kitty` struct to keep track if it is for sale, and the price the owner wants. - -For this we can use an `Option>`, where `None` denotes that a kitty is not for sale, and `Some(price)` denotes the kitty is for sale at some `price`. - -### Resetting the Price Field - -In this step, we are introducing a new `price` field to the `Kitty` struct, and we must consider how that might affect existing logic in our Pallet. - -The price is something that should only be set by the owner of the kitty, and describes what the current owner would want to sell the kitty for. - -However, when a kitty is transferred to a new owner, that new owner may not agree with the existing price or want to sell the kitty at all! +The key difference here is that we do NOT assume that these APIs must come from specifically `pallet_balances`. If you wanted to use another pallet in the `polkadot-sdk` ecosystem which provides these same functions, you can use it! Our pallet is NOT tightly coupled to which pallet provides access to the `NativeBalance`, it only requires that there is something implementing the `Inspect` and `Mutate` traits. -Thus, whenever we transfer the kitty, we will want to reset the `price` to `None` to make sure it is not immediately for sale. -The new owner will be able to set the new price if they want to sell their new kitty. +The power of loose coupling may not be immediately obvious, but as you get deeper into developing in the Polkadot ecosystem, you will start to realize how powerful this approach can be. ## Your Turn -Now that you know how to create and use the `BalanceOf` type, add the type alias to your Pallet as shown in the template. +Import the `Inspect` and `Mutate` traits from `frame::traits::fungible`. -Then add a new field to the `Kitty` struct called `price`, which is an `Option>`. +Introduce the `NativeBalance` associated type to your `trait Config` using these traits. -Update the `mint` function to create a new `Kitty` with the new `price` field set as `None`. +## Learn More -Update the `do_transfer` function to reset the `kitty.price` to `None` when the kitty gets a new owner. +To continue learning about Pallet Coupling, check out the following video from the Polkadot Blockchain Academy: -Finally, update your `tests.rs` file so that `DEFAULT_KITTY` has the field and value `price: None`. + diff --git a/src/28/template/src/impls.rs b/src/28/template/src/impls.rs index 2c01fdca..55742115 100644 --- a/src/28/template/src/impls.rs +++ b/src/28/template/src/impls.rs @@ -20,7 +20,6 @@ impl Pallet { } pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); @@ -41,7 +40,6 @@ impl Pallet { let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ensure!(kitty.owner == from, Error::::NotOwner); kitty.owner = to.clone(); - /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ let mut to_owned = KittiesOwned::::get(&to); to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/28/template/src/lib.rs b/src/28/template/src/lib.rs index 3a10644d..5a4e7650 100644 --- a/src/28/template/src/lib.rs +++ b/src/28/template/src/lib.rs @@ -4,8 +4,8 @@ mod impls; mod tests; use frame::prelude::*; -use frame::traits::fungible::Inspect; -use frame::traits::fungible::Mutate; +/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ +/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ pub use pallet::*; #[frame::pallet(dev_mode)] @@ -19,25 +19,20 @@ pub mod pallet { pub trait Config: frame_system::Config { type RuntimeEvent: From> + IsType<::RuntimeEvent>; - /// The Fungible handler for the kitties pallet. - type NativeBalance: Inspect + Mutate; + /* 🚧 TODO 🚧: + - Create a new associated type named `NativeBalance`. + - Require that `NativeBalance` implements the following traits: + - `Inspect` which is generic over `Self::AccountId`. + - `Mutate` which is also generic over `Self::AccountId`. + */ } - /* 🚧 TODO 🚧: - - Create a new type alias called `BalanceOf`. - - Extract the `Balance` type from the `NativeBalance` associated type: - - The `Balance` type comes from the `Inspect` trait. - - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. - - Inspect comes from `NativeBalance`, which comes from `T as Config`. - */ - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Kitty { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, - /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ } #[pallet::storage] diff --git a/src/28/template/src/tests.rs b/src/28/template/src/tests.rs index dcb9619c..8a76f591 100644 --- a/src/28/template/src/tests.rs +++ b/src/28/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -81,7 +81,7 @@ impl pallet_balances::Config for TestRuntime { // will also need to update this configuration to represent that. impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; - type NativeBalance = PalletBalances; + /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ } // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -313,14 +311,3 @@ fn transfer_logic_works() { assert_eq!(kitty.owner, BOB); }); } - -#[test] -fn native_balance_associated_type_works() { - new_test_ext().execute_with(|| { - assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); - assert_eq!( - <::NativeBalance as Inspect<_>>::total_balance(&ALICE), - 1337 - ); - }); -} diff --git a/src/28/template/template.diff b/src/28/template/template.diff index 7692a827..9c073776 100644 --- a/src/28/template/template.diff +++ b/src/28/template/template.diff @@ -1,46 +1,39 @@ -diff --git a/src/impls.rs b/src/impls.rs -index 5574211..2c01fdc 100644 ---- a/src/impls.rs -+++ b/src/impls.rs -@@ -20,6 +20,7 @@ impl Pallet { - } - - pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { -+ /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ - let kitty = Kitty { dna, owner: owner.clone() }; - // Check if the kitty does not already exist in our storage map - ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); -@@ -40,6 +41,7 @@ impl Pallet { - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == from, Error::::NotOwner); - kitty.owner = to.clone(); -+ /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ - - let mut to_owned = KittiesOwned::::get(&to); - to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/lib.rs b/src/lib.rs -index 59150bd..3a10644 100644 +index d44d77dd..5a4e7650 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -23,12 +23,21 @@ pub mod pallet { - type NativeBalance: Inspect + Mutate; - } +@@ -4,6 +4,8 @@ mod impls; + mod tests; -+ /* 🚧 TODO 🚧: -+ - Create a new type alias called `BalanceOf`. -+ - Extract the `Balance` type from the `NativeBalance` associated type: -+ - The `Balance` type comes from the `Inspect` trait. -+ - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. -+ - Inspect comes from `NativeBalance`, which comes from `T as Config`. -+ */ + use frame::prelude::*; ++/* 🚧 TODO 🚧: Import `frame::traits::fungible::Inspect`. */ ++/* 🚧 TODO 🚧: Import `frame::traits::fungible::Mutate`. */ + pub use pallet::*; + + #[frame::pallet(dev_mode)] +@@ -16,6 +18,13 @@ pub mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + - #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] - #[scale_info(skip_type_params(T))] - pub struct Kitty { - // Using 32 bytes to represent a kitty DNA - pub dna: [u8; 32], - pub owner: T::AccountId, -+ /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ ++ /* 🚧 TODO 🚧: ++ - Create a new associated type named `NativeBalance`. ++ - Require that `NativeBalance` implements the following traits: ++ - `Inspect` which is generic over `Self::AccountId`. ++ - `Mutate` which is also generic over `Self::AccountId`. ++ */ } - #[pallet::storage] + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] +diff --git a/src/tests.rs b/src/tests.rs +index 4a168591..8a76f591 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -81,6 +81,7 @@ impl pallet_balances::Config for TestRuntime { + // will also need to update this configuration to represent that. + impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; ++ /* 🚧 TODO 🚧: Assign associated type `NativeBalance` to `PalletBalances`. */ + } + + // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` diff --git a/src/29/solution/solution.diff b/src/29/solution/solution.diff index ba45aac3..f350e632 100644 --- a/src/29/solution/solution.diff +++ b/src/29/solution/solution.diff @@ -1,93 +1,76 @@ diff --git a/src/impls.rs b/src/impls.rs -index f3f8281..48af847 100644 +index 2c01fdca..16bdb731 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -59,14 +59,12 @@ impl Pallet { - Ok(()) +@@ -20,8 +20,7 @@ impl Pallet { } -- /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: -- - Inputs to the function are: -- - `caller` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - `new_price` which is `Option`. -- - Returns a `DispatchResult`. -- - The internal logic, for now, should be: -- - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. -- - Return `Ok(())`. -- */ -+ pub fn do_set_price( -+ caller: T::AccountId, -+ kitty_id: [u8; 32], -+ new_price: Option>, -+ ) -> DispatchResult { -+ Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); -+ Ok(()) -+ } - } + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { +- /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ +- let kitty = Kitty { dna, owner: owner.clone() }; ++ let kitty = Kitty { dna, owner: owner.clone(), price: None }; + // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); + +@@ -41,7 +40,7 @@ impl Pallet { + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == from, Error::::NotOwner); + kitty.owner = to.clone(); +- /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ ++ kitty.price = None; + + let mut to_owned = KittiesOwned::::get(&to); + to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/lib.rs b/src/lib.rs -index 45f5fdf..57e0eeb 100644 +index 3a10644d..7d8350d6 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -55,11 +55,7 @@ pub mod pallet { - pub enum Event { - Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, -- /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: -- - `owner` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - `new_price` which is `Option>`. -- */ -+ PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, +@@ -23,13 +23,9 @@ pub mod pallet { + type NativeBalance: Inspect + Mutate; } - #[pallet::error] -@@ -91,16 +87,14 @@ pub mod pallet { - Ok(()) - } +- /* 🚧 TODO 🚧: +- - Create a new type alias called `BalanceOf`. +- - Extract the `Balance` type from the `NativeBalance` associated type: +- - The `Balance` type comes from the `Inspect` trait. +- - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. +- - Inspect comes from `NativeBalance`, which comes from `T as Config`. +- */ ++ // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. ++ pub type BalanceOf = ++ <::NativeBalance as Inspect<::AccountId>>::Balance; -- /* 🚧 TODO 🚧: Make an callable function called `set_price`: -- - Inputs to the function are: -- - `origin` which is `OriginFor`. -- - `kitty_id` which is `[u8; 32]`. -- - `new_price` which is `Option`. -- - Returns a `DispatchResult` -- - The internal logic, for now, should be: -- - Extract the caller `who` with `ensure_signed`. -- - Call `Self::do_set_price` with the appropriate parameters, propagating the result. -- - Return `Ok(())`. -- */ -+ pub fn set_price( -+ origin: OriginFor, -+ kitty_id: [u8; 32], -+ new_price: Option>, -+ ) -> DispatchResult { -+ let who = ensure_signed(origin)?; -+ Self::do_set_price(who, kitty_id, new_price)?; -+ Ok(()) -+ } + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] +@@ -37,7 +33,7 @@ pub mod pallet { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], + pub owner: T::AccountId, +- /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ ++ pub price: Option>, } - } + + #[pallet::storage] diff --git a/src/tests.rs b/src/tests.rs -index 5326761..549f0f8 100644 +index df5dc0dd..ff0b8c24 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -330,3 +330,18 @@ fn balance_of_type_works() { - // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. - let _example_balance: BalanceOf = 1337u64; +@@ -28,7 +28,7 @@ type Block = frame_system::mocking::MockBlock; + // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. + const ALICE: u64 = 1; + const BOB: u64 = 2; +-const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; ++const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; + + #[runtime] + mod runtime { +@@ -322,3 +322,9 @@ fn native_balance_associated_type_works() { + ); + }); } + +#[test] -+fn set_price_emits_event() { -+ new_test_ext().execute_with(|| { -+ // We need to set block number to 1 to view events. -+ System::set_block_number(1); -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ let kitty_id = Kitties::::iter_keys().collect::>()[0]; -+ assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); -+ // Assert the last event is `PriceSet` event with the correct information. -+ System::assert_last_event( -+ Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), -+ ); -+ }) ++fn balance_of_type_works() { ++ // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. ++ let _example_balance: BalanceOf = 1337u64; +} diff --git a/src/29/solution/src/impls.rs b/src/29/solution/src/impls.rs index 48af8471..16bdb731 100644 --- a/src/29/solution/src/impls.rs +++ b/src/29/solution/src/impls.rs @@ -58,13 +58,4 @@ impl Pallet { Self::deposit_event(Event::::Transferred { from, to, kitty_id }); Ok(()) } - - pub fn do_set_price( - caller: T::AccountId, - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); - Ok(()) - } } diff --git a/src/29/solution/src/lib.rs b/src/29/solution/src/lib.rs index 57e0eeb7..7d8350d6 100644 --- a/src/29/solution/src/lib.rs +++ b/src/29/solution/src/lib.rs @@ -55,7 +55,6 @@ pub mod pallet { pub enum Event { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, } #[pallet::error] @@ -86,15 +85,5 @@ pub mod pallet { Self::do_transfer(who, to, kitty_id)?; Ok(()) } - - pub fn set_price( - origin: OriginFor, - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_set_price(who, kitty_id, new_price)?; - Ok(()) - } } } diff --git a/src/29/solution/src/tests.rs b/src/29/solution/src/tests.rs index 549f0f8b..ff0b8c24 100644 --- a/src/29/solution/src/tests.rs +++ b/src/29/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -330,18 +328,3 @@ fn balance_of_type_works() { // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. let _example_balance: BalanceOf = 1337u64; } - -#[test] -fn set_price_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - // Assert the last event is `PriceSet` event with the correct information. - System::assert_last_event( - Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), - ); - }) -} diff --git a/src/29/template/README.md b/src/29/template/README.md index 5e0c171a..a680fdc2 100644 --- a/src/29/template/README.md +++ b/src/29/template/README.md @@ -1,23 +1,154 @@ -# Set Price Extrinsic +# Native Balance Type -Now that we have our Pallet set up to handle balances, let's actually use them. +One of the most challenging parts of using the `polkadot-sdk` is using generic types. -In this step, we will create an extrinsic which allows the owner of a kitty to set a price for the kitty. +Hopefully, things like `T::AccountId` have been easy enough to use, but using the `Balance` type coming from `NativeBalance` can be a little tricky. -## Set Price +## Generic Types -The set price extrinsic is pretty straight forward. +The ability to use generic types in Rust is extremely powerful, but it can be hard to easily understand for those new to the `polkadot-sdk`. Not to mention that `polkadot-sdk` uses a lot of generic types. -As we noted in the last step, we allow the `price` field of a kitty to be `Option>`, where `None` denotes a kitty which is not for sale, and `Some(price)` denotes a kitty which is for sale at some `price`. +The key thing to remember is that all of these generic types eventually become a concrete type. So while we are not sure while we write our pallet if `T::AccountId` is `[u8; 20]`, `[u8; 32]`, or maybe even a `String`, we know that eventually it must be one of these primitive types. -With this in mind, our `set_price` extrinsic will also accept an `Option>` so that a user can put the kitty on or off the market. +In this situation, the same can be said for the `Balance` type coming from `NativeBalance`. Depending on the configuration of your blockchain, and the required traits that `Balance` needs to satisfy, it is perfectly valid for your `Balance` type to be `u32`, `u64`, or `u128`. + +But it can only concretely be one of those, and the weird thing is that we don't know which one it is as we program our Kitties pallet! + +Let's look at how we would interact with this generic type, and solve many of the issues you might encounter when trying to use it. + +## Balance Type + +The `Balance` type is ultimately configured inside `pallet_balances`, and remember, we don't have direct access to that pallet because we used loose coupling. + +The way we can access the `Balance` type is through the `Inspect` trait of the `NativeBalance` associated type. Accessing it is kind of funny, which is why we commonly introduce a `BalanceOf` alias type like so: + +```rust +// Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. +pub type BalanceOf = + <::NativeBalance as Inspect<::AccountId>>::Balance; + +``` + +This is kind of a crazy line of code, so let's break it down: + +- At the very end, there is a `Balance` type. This is what we want to access and alias. + ```rust + Balance + ``` +- This `Balance` type is part of the `Inspect` trait. + ```rust + Inspect::Balance + ``` +- The `Inspect` trait is generic over `AccountId`, so we need to include that. + ```rust + Inspect::Balance + ``` +- The `AccountId` type comes from `T`, through `frame_system::Config`, where the type is defined. + ```rust + Inspect<::AccountId>::Balance + ``` +- The `Inspect` is accessible through the `NativeBalance` associated type. + ```rust + ::AccountId>>::Balance + ``` +- The `NativeBalance` type also comes from `T`, but though our pallet's own `Config`. + ```rust + <::NativeBalance as Inspect<::AccountId>>::Balance + ``` +- Finally, we assign this to a new alias `BalanceOf` which is generic over ``. + ```rust + pub type BalanceOf = <::NativeBalance as Inspect<::AccountId>>::Balance + ``` + +Phew! Did you get all that? If not, don't worry too much. You can review these Rust concepts after you complete the tutorial, but there is nothing here which is specific to the `polkadot-sdk`. + +Why do we need this `BalanceOf` alias? + +So that we can change this: + +```rust +fn set_price(id: [u8; 32], price: <::NativeBalance as Inspect<::AccountId>>::Balance) { + // -- snip -- +} +``` + +To this: + +```rust +fn set_price(id: [u8; 32], price: BalanceOf) { + // -- snip -- +} +``` + +The second is way better right? This type alias handles extracting the `Balance` type out of the `NativeBalance` associated type every time, and all we need to do is pass the generic parameter `T`. + +## Basic API + +Let's learn how we can use the `BalanceOf` type in our code. + +### Interacting with Primitive Numbers + +I will repeat again: Because the `BalanceOf` type is generic, we cannot know what underlying type it is. This means we CANNOT write the following: + +```rust +// This code doesn't work +fn add_one(input: BalanceOf) -> BalanceOf { + input + 1u128 +} +``` + +Even if we don't include `u128`, we cannot write the line above. This is because that line assumes that `input` must be some specific number type, and in that code, it is simply generic. + +However, `BalanceOf` [does have traits](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/trait.Balance.html) that we can use to interact with it. The key one being [`AtLeast32BitUnsigned`](https://docs.rs/polkadot-sdk-frame/0.7.0/polkadot_sdk_frame/arithmetic/trait.AtLeast32BitUnsigned.html). + +This means our `BalanceOf` must be an unsigned integer, and must be at least `u32`. So it could be `u32`, `u64`, `u128`, or even bigger if you import other crates with those larger unsigned types. + +This also means we **would** be able to write the following: + +```rust +// This code does work +fn add_one(input: BalanceOf) -> BalanceOf { + input + 1u32.into() +} +``` + +We can convert any `u32` into the `BalanceOf` type because we know at a minimum `BalanceOf` is `AtLeast32BitUnsigned`. + +### Interacting with Itself + +Interacting between two `BalanceOf` types will act just like two normal numbers of the same type. + +You can add them, subtract them, multiply them, divide them, and even better, do safe math operations on all of them. + +```rust +let total_balance: BalanceOf = balance_1.checked_add(balance_2).ok_or(ArithmeticError::Overflow)?; +``` + +## Price Field + +We are going to use `BalanceOf` in the `Kitty` struct to keep track if it is for sale, and the price the owner wants. + +For this we can use an `Option>`, where `None` denotes that a kitty is not for sale, and `Some(price)` denotes the kitty is for sale at some `price`. + +### Resetting the Price Field + +In this step, we are introducing a new `price` field to the `Kitty` struct, and we must consider how that might affect existing logic in our Pallet. + +The price is something that should only be set by the owner of the kitty, and describes what the current owner would want to sell the kitty for. + +However, when a kitty is transferred to a new owner, that new owner may not agree with the existing price or want to sell the kitty at all! + +Thus, whenever we transfer the kitty, we will want to reset the `price` to `None` to make sure it is not immediately for sale. +The new owner will be able to set the new price if they want to sell their new kitty. ## Your Turn -In this step, you will scaffold the extrinsic and internal functions for `set_price`. +Now that you know how to create and use the `BalanceOf` type, add the type alias to your Pallet as shown in the template. + +Then add a new field to the `Kitty` struct called `price`, which is an `Option>`. + +Update the `mint` function to create a new `Kitty` with the new `price` field set as `None`. -- Create a new event `PriceSet` with the fields noted in the template. -- Create a new extrinsic `set_price` with the params noted in the template. -- Create a new internal function `do_set_price` which simply deposits the `PriceSet` event. +Update the `do_transfer` function to reset the `kitty.price` to `None` when the kitty gets a new owner. -We will actually populate the logic for `do_set_price` in the next step. +Finally, update your `tests.rs` file so that `DEFAULT_KITTY` has the field and value `price: None`. diff --git a/src/29/template/src/impls.rs b/src/29/template/src/impls.rs index f3f82812..2c01fdca 100644 --- a/src/29/template/src/impls.rs +++ b/src/29/template/src/impls.rs @@ -20,7 +20,8 @@ impl Pallet { } pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { - let kitty = Kitty { dna, owner: owner.clone(), price: None }; + /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ + let kitty = Kitty { dna, owner: owner.clone() }; // Check if the kitty does not already exist in our storage map ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); @@ -40,7 +41,7 @@ impl Pallet { let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ensure!(kitty.owner == from, Error::::NotOwner); kitty.owner = to.clone(); - kitty.price = None; + /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ let mut to_owned = KittiesOwned::::get(&to); to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; @@ -58,15 +59,4 @@ impl Pallet { Self::deposit_event(Event::::Transferred { from, to, kitty_id }); Ok(()) } - - /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: - - Inputs to the function are: - - `caller` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - `new_price` which is `Option`. - - Returns a `DispatchResult`. - - The internal logic, for now, should be: - - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. - - Return `Ok(())`. - */ } diff --git a/src/29/template/src/lib.rs b/src/29/template/src/lib.rs index 45f5fdfb..3a10644d 100644 --- a/src/29/template/src/lib.rs +++ b/src/29/template/src/lib.rs @@ -23,9 +23,13 @@ pub mod pallet { type NativeBalance: Inspect + Mutate; } - // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. - pub type BalanceOf = - <::NativeBalance as Inspect<::AccountId>>::Balance; + /* 🚧 TODO 🚧: + - Create a new type alias called `BalanceOf`. + - Extract the `Balance` type from the `NativeBalance` associated type: + - The `Balance` type comes from the `Inspect` trait. + - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. + - Inspect comes from `NativeBalance`, which comes from `T as Config`. + */ #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] @@ -33,7 +37,7 @@ pub mod pallet { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, - pub price: Option>, + /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ } #[pallet::storage] @@ -55,11 +59,6 @@ pub mod pallet { pub enum Event { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: - - `owner` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - `new_price` which is `Option>`. - */ } #[pallet::error] @@ -90,17 +89,5 @@ pub mod pallet { Self::do_transfer(who, to, kitty_id)?; Ok(()) } - - /* 🚧 TODO 🚧: Make an callable function called `set_price`: - - Inputs to the function are: - - `origin` which is `OriginFor`. - - `kitty_id` which is `[u8; 32]`. - - `new_price` which is `Option`. - - Returns a `DispatchResult` - - The internal logic, for now, should be: - - Extract the caller `who` with `ensure_signed`. - - Call `Self::do_set_price` with the appropriate parameters, propagating the result. - - Return `Ok(())`. - */ } } diff --git a/src/29/template/src/tests.rs b/src/29/template/src/tests.rs index 53267614..df5dc0dd 100644 --- a/src/29/template/src/tests.rs +++ b/src/29/template/src/tests.rs @@ -28,7 +28,7 @@ type Block = frame_system::mocking::MockBlock; // We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. const ALICE: u64 = 1; const BOB: u64 = 2; -const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; +const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0 }; #[runtime] mod runtime { @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -324,9 +322,3 @@ fn native_balance_associated_type_works() { ); }); } - -#[test] -fn balance_of_type_works() { - // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. - let _example_balance: BalanceOf = 1337u64; -} diff --git a/src/29/template/template.diff b/src/29/template/template.diff index f85d39ee..3f48dcb6 100644 --- a/src/29/template/template.diff +++ b/src/29/template/template.diff @@ -1,54 +1,46 @@ diff --git a/src/impls.rs b/src/impls.rs -index 16bdb73..f3f8281 100644 +index 55742115..2c01fdca 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -58,4 +58,15 @@ impl Pallet { - Self::deposit_event(Event::::Transferred { from, to, kitty_id }); - Ok(()) +@@ -20,6 +20,7 @@ impl Pallet { } -+ -+ /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: -+ - Inputs to the function are: -+ - `caller` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `new_price` which is `Option`. -+ - Returns a `DispatchResult`. -+ - The internal logic, for now, should be: -+ - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. -+ - Return `Ok(())`. -+ */ - } + + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { ++ /* 🚧 TODO 🚧: Add the `price` field set to `None` when initializing the `Kitty` struct. */ + let kitty = Kitty { dna, owner: owner.clone() }; + // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); +@@ -40,6 +41,7 @@ impl Pallet { + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == from, Error::::NotOwner); + kitty.owner = to.clone(); ++ /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ + + let mut to_owned = KittiesOwned::::get(&to); + to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; diff --git a/src/lib.rs b/src/lib.rs -index 7d8350d..45f5fdf 100644 +index 59150bd6..3a10644d 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -55,6 +55,11 @@ pub mod pallet { - pub enum Event { - Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, -+ /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: -+ - `owner` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `new_price` which is `Option>`. -+ */ +@@ -23,12 +23,21 @@ pub mod pallet { + type NativeBalance: Inspect + Mutate; } - #[pallet::error] -@@ -85,5 +90,17 @@ pub mod pallet { - Self::do_transfer(who, to, kitty_id)?; - Ok(()) - } ++ /* 🚧 TODO 🚧: ++ - Create a new type alias called `BalanceOf`. ++ - Extract the `Balance` type from the `NativeBalance` associated type: ++ - The `Balance` type comes from the `Inspect` trait. ++ - `Inspect` requires a generic parameter `AccountId` from `T as frame_system::Config`. ++ - Inspect comes from `NativeBalance`, which comes from `T as Config`. ++ */ + -+ /* 🚧 TODO 🚧: Make an callable function called `set_price`: -+ - Inputs to the function are: -+ - `origin` which is `OriginFor`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `new_price` which is `Option`. -+ - Returns a `DispatchResult` -+ - The internal logic, for now, should be: -+ - Extract the caller `who` with `ensure_signed`. -+ - Call `Self::do_set_price` with the appropriate parameters, propagating the result. -+ - Return `Ok(())`. -+ */ + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct Kitty { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], + pub owner: T::AccountId, ++ /* 🚧 TODO 🚧: Add a new field `price`, which is an `Option>`. */ } - } + + #[pallet::storage] diff --git a/src/3/source/changes.diff b/src/3/source/changes.diff index ba20de71..47a25c99 100644 --- a/src/3/source/changes.diff +++ b/src/3/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/Cargo.lock b/Cargo.lock -index 31cbe7d..6bb5e40 100644 +index 31cbe7df..6bb5e408 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ @@ -11,7 +11,7 @@ index 31cbe7d..6bb5e40 100644 [[package]] name = "Inflector" diff --git a/src/lib.rs b/src/lib.rs -index df435c8..dae4e99 100644 +index df435c8a..dae4e999 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,10 +3,10 @@ diff --git a/src/3/source/src/tests.rs b/src/3/source/src/tests.rs index 6d271d5e..7e6f34da 100644 --- a/src/3/source/src/tests.rs +++ b/src/3/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/30/README.md b/src/30/README.md index a634c256..eb9560d4 100644 --- a/src/30/README.md +++ b/src/30/README.md @@ -18,6 +18,7 @@
+
@@ -27,6 +28,14 @@
+
+ +```rust +{{#include ./template/src/lib.rs}} +``` + +
+
@@ -35,6 +44,7 @@
+
@@ -45,6 +55,14 @@
+
+ +```rust +{{#include ./solution/src/lib.rs}} +``` + +
+
```rust diff --git a/src/30/solution/solution.diff b/src/30/solution/solution.diff index 43760167..3f2bdcf8 100644 --- a/src/30/solution/solution.diff +++ b/src/30/solution/solution.diff @@ -1,43 +1,93 @@ diff --git a/src/impls.rs b/src/impls.rs -index aa65cdd..af4e460 100644 +index f3f82812..48af8471 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -64,13 +64,10 @@ impl Pallet { - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { -- /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: -- - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. -- - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. -- - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. -- - Set the `kitty.price` to `new_price`. -- - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. +@@ -59,14 +59,12 @@ impl Pallet { + Ok(()) + } + +- /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: +- - Inputs to the function are: +- - `caller` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - `new_price` which is `Option`. +- - Returns a `DispatchResult`. +- - The internal logic, for now, should be: +- - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. +- - Return `Ok(())`. +- */ ++ pub fn do_set_price( ++ caller: T::AccountId, ++ kitty_id: [u8; 32], ++ new_price: Option>, ++ ) -> DispatchResult { ++ Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); ++ Ok(()) ++ } + } +diff --git a/src/lib.rs b/src/lib.rs +index 45f5fdfb..57e0eeb7 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -55,11 +55,7 @@ pub mod pallet { + pub enum Event { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, +- /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: +- - `owner` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - `new_price` which is `Option>`. - */ -+ let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; -+ ensure!(kitty.owner == caller, Error::::NotOwner); -+ kitty.price = new_price; -+ Kitties::::insert(kitty_id, kitty); ++ PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, + } - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); - Ok(()) + #[pallet::error] +@@ -91,16 +87,14 @@ pub mod pallet { + Ok(()) + } + +- /* 🚧 TODO 🚧: Make an callable function called `set_price`: +- - Inputs to the function are: +- - `origin` which is `OriginFor`. +- - `kitty_id` which is `[u8; 32]`. +- - `new_price` which is `Option`. +- - Returns a `DispatchResult` +- - The internal logic, for now, should be: +- - Extract the caller `who` with `ensure_signed`. +- - Call `Self::do_set_price` with the appropriate parameters, propagating the result. +- - Return `Ok(())`. +- */ ++ pub fn set_price( ++ origin: OriginFor, ++ kitty_id: [u8; 32], ++ new_price: Option>, ++ ) -> DispatchResult { ++ let who = ensure_signed(origin)?; ++ Self::do_set_price(who, kitty_id, new_price)?; ++ Ok(()) ++ } + } + } diff --git a/src/tests.rs b/src/tests.rs -index 549f0f8..392e5bf 100644 +index ff0b8c24..e47a4736 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -345,3 +345,16 @@ fn set_price_emits_event() { - ); - }) +@@ -328,3 +328,18 @@ fn balance_of_type_works() { + // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. + let _example_balance: BalanceOf = 1337u64; } + +#[test] -+fn set_price_logic_works() { ++fn set_price_emits_event() { + new_test_ext().execute_with(|| { ++ // We need to set block number to 1 to view events. ++ System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ let kitty = &Kitties::::iter_values().collect::>()[0]; -+ assert_eq!(kitty.price, None); -+ let kitty_id = kitty.dna; ++ let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); -+ let kitty = Kitties::::get(kitty_id).unwrap(); -+ assert_eq!(kitty.price, Some(1337)); ++ // Assert the last event is `PriceSet` event with the correct information. ++ System::assert_last_event( ++ Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), ++ ); + }) +} diff --git a/src/30/solution/src/impls.rs b/src/30/solution/src/impls.rs index af4e4602..48af8471 100644 --- a/src/30/solution/src/impls.rs +++ b/src/30/solution/src/impls.rs @@ -64,11 +64,6 @@ impl Pallet { kitty_id: [u8; 32], new_price: Option>, ) -> DispatchResult { - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == caller, Error::::NotOwner); - kitty.price = new_price; - Kitties::::insert(kitty_id, kitty); - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); Ok(()) } diff --git a/src/30/solution/src/tests.rs b/src/30/solution/src/tests.rs index 392e5bf5..e47a4736 100644 --- a/src/30/solution/src/tests.rs +++ b/src/30/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -345,16 +343,3 @@ fn set_price_emits_event() { ); }) } - -#[test] -fn set_price_logic_works() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty = &Kitties::::iter_values().collect::>()[0]; - assert_eq!(kitty.price, None); - let kitty_id = kitty.dna; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - let kitty = Kitties::::get(kitty_id).unwrap(); - assert_eq!(kitty.price, Some(1337)); - }) -} diff --git a/src/30/template/README.md b/src/30/template/README.md index 68d1d6d2..5e0c171a 100644 --- a/src/30/template/README.md +++ b/src/30/template/README.md @@ -1,11 +1,23 @@ -# Set Price Logic +# Set Price Extrinsic -The set price logic is very straight forward. +Now that we have our Pallet set up to handle balances, let's actually use them. -The only thing we really need to check is that the `kitty.owner` matches the `caller` of the function. +In this step, we will create an extrinsic which allows the owner of a kitty to set a price for the kitty. -Besides that, we simply need to update the `kitty.price` field, and write that back to the runtime storage. +## Set Price + +The set price extrinsic is pretty straight forward. + +As we noted in the last step, we allow the `price` field of a kitty to be `Option>`, where `None` denotes a kitty which is not for sale, and `Some(price)` denotes a kitty which is for sale at some `price`. + +With this in mind, our `set_price` extrinsic will also accept an `Option>` so that a user can put the kitty on or off the market. ## Your Turn -Follow the `TODO`s listed in the template, and complete the logic for the `do_set_price` function. +In this step, you will scaffold the extrinsic and internal functions for `set_price`. + +- Create a new event `PriceSet` with the fields noted in the template. +- Create a new extrinsic `set_price` with the params noted in the template. +- Create a new internal function `do_set_price` which simply deposits the `PriceSet` event. + +We will actually populate the logic for `do_set_price` in the next step. diff --git a/src/30/template/src/impls.rs b/src/30/template/src/impls.rs index aa65cdd0..f3f82812 100644 --- a/src/30/template/src/impls.rs +++ b/src/30/template/src/impls.rs @@ -59,20 +59,14 @@ impl Pallet { Ok(()) } - pub fn do_set_price( - caller: T::AccountId, - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { - /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: - - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. - - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. - - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. - - Set the `kitty.price` to `new_price`. - - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. - */ - - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); - Ok(()) - } + /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: + - Inputs to the function are: + - `caller` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - `new_price` which is `Option`. + - Returns a `DispatchResult`. + - The internal logic, for now, should be: + - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. + - Return `Ok(())`. + */ } diff --git a/src/30/template/src/lib.rs b/src/30/template/src/lib.rs index 57e0eeb7..45f5fdfb 100644 --- a/src/30/template/src/lib.rs +++ b/src/30/template/src/lib.rs @@ -55,7 +55,11 @@ pub mod pallet { pub enum Event { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, + /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: + - `owner` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - `new_price` which is `Option>`. + */ } #[pallet::error] @@ -87,14 +91,16 @@ pub mod pallet { Ok(()) } - pub fn set_price( - origin: OriginFor, - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_set_price(who, kitty_id, new_price)?; - Ok(()) - } + /* 🚧 TODO 🚧: Make an callable function called `set_price`: + - Inputs to the function are: + - `origin` which is `OriginFor`. + - `kitty_id` which is `[u8; 32]`. + - `new_price` which is `Option`. + - Returns a `DispatchResult` + - The internal logic, for now, should be: + - Extract the caller `who` with `ensure_signed`. + - Call `Self::do_set_price` with the appropriate parameters, propagating the result. + - Return `Ok(())`. + */ } } diff --git a/src/30/template/src/tests.rs b/src/30/template/src/tests.rs index 549f0f8b..ff0b8c24 100644 --- a/src/30/template/src/tests.rs +++ b/src/30/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -330,18 +328,3 @@ fn balance_of_type_works() { // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. let _example_balance: BalanceOf = 1337u64; } - -#[test] -fn set_price_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - // Assert the last event is `PriceSet` event with the correct information. - System::assert_last_event( - Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), - ); - }) -} diff --git a/src/30/template/template.diff b/src/30/template/template.diff index 2a72afe7..2745bfa0 100644 --- a/src/30/template/template.diff +++ b/src/30/template/template.diff @@ -1,19 +1,54 @@ diff --git a/src/impls.rs b/src/impls.rs -index 48af847..aa65cdd 100644 +index 16bdb731..f3f82812 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -64,6 +64,14 @@ impl Pallet { - kitty_id: [u8; 32], - new_price: Option>, - ) -> DispatchResult { -+ /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: -+ - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. -+ - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. -+ - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. -+ - Set the `kitty.price` to `new_price`. -+ - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. +@@ -58,4 +58,15 @@ impl Pallet { + Self::deposit_event(Event::::Transferred { from, to, kitty_id }); + Ok(()) + } ++ ++ /* 🚧 TODO 🚧: Make an internal function called `do_set_price`: ++ - Inputs to the function are: ++ - `caller` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `new_price` which is `Option`. ++ - Returns a `DispatchResult`. ++ - The internal logic, for now, should be: ++ - `Self::deposit_event` for `Event::::PriceSet` with the appropriate params. ++ - Return `Ok(())`. ++ */ + } +diff --git a/src/lib.rs b/src/lib.rs +index 7d8350d6..45f5fdfb 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -55,6 +55,11 @@ pub mod pallet { + pub enum Event { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, ++ /* 🚧 TODO 🚧: Create a new `Event` called `PriceSet` with fields: ++ - `owner` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `new_price` which is `Option>`. + */ + } + + #[pallet::error] +@@ -85,5 +90,17 @@ pub mod pallet { + Self::do_transfer(who, to, kitty_id)?; + Ok(()) + } + - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); - Ok(()) ++ /* 🚧 TODO 🚧: Make an callable function called `set_price`: ++ - Inputs to the function are: ++ - `origin` which is `OriginFor`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `new_price` which is `Option`. ++ - Returns a `DispatchResult` ++ - The internal logic, for now, should be: ++ - Extract the caller `who` with `ensure_signed`. ++ - Call `Self::do_set_price` with the appropriate parameters, propagating the result. ++ - Return `Ok(())`. ++ */ } + } diff --git a/src/31/README.md b/src/31/README.md index eb9560d4..a634c256 100644 --- a/src/31/README.md +++ b/src/31/README.md @@ -18,7 +18,6 @@
-
@@ -28,14 +27,6 @@
-
- -```rust -{{#include ./template/src/lib.rs}} -``` - -
-
@@ -44,7 +35,6 @@
-
@@ -55,14 +45,6 @@
-
- -```rust -{{#include ./solution/src/lib.rs}} -``` - -
-
```rust diff --git a/src/31/solution/solution.diff b/src/31/solution/solution.diff index 8dc15d61..5b4b3fdc 100644 --- a/src/31/solution/solution.diff +++ b/src/31/solution/solution.diff @@ -1,95 +1,43 @@ diff --git a/src/impls.rs b/src/impls.rs -index 04c92c5..03cf1f1 100644 +index aa65cdd0..af4e4602 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -73,14 +73,12 @@ impl Pallet { - Ok(()) - } - -- /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: -- - Inputs to the function are: -- - `buyer` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - `price` which is `BalanceOf`. -- - It returns `DispatchResult`. -- - The internal logic, for now, should be: -- - `Self::deposit_event` with `Event::::Sold` and the appropriate params. -- - Return `Ok(())`. -- */ -+ pub fn do_buy_kitty( -+ buyer: T::AccountId, -+ kitty_id: [u8; 32], -+ price: BalanceOf, -+ ) -> DispatchResult { -+ Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); -+ Ok(()) -+ } - } -diff --git a/src/lib.rs b/src/lib.rs -index 8c3eb2e..4a8d2b1 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -56,11 +56,7 @@ pub mod pallet { - Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, -- /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: -- - `buyer` which is `T::AccountId`. -- - `kitty_id` which is `[u8; 32]`. -- - `price` which is `BalanceOf`. +@@ -64,13 +64,10 @@ impl Pallet { + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { +- /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: +- - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. +- - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. +- - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. +- - Set the `kitty.price` to `new_price`. +- - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. - */ -+ Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, - } - - #[pallet::error] -@@ -102,16 +98,14 @@ pub mod pallet { - Ok(()) - } ++ let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ++ ensure!(kitty.owner == caller, Error::::NotOwner); ++ kitty.price = new_price; ++ Kitties::::insert(kitty_id, kitty); -- /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: -- - Inputs to the function are: -- - `origin` which is `OriginFor`. -- - `kitty_id` which is `[u8; 32]`. -- - `max_price` which is `BalanceOf`. -- - It returns `DispatchResult`. -- - The internal logic should be: -- - Extract `who` using `ensure_signed` on `origin`. -- - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. -- - Return `Ok(())`. -- */ -+ pub fn buy_kitty( -+ origin: OriginFor, -+ kitty_id: [u8; 32], -+ max_price: BalanceOf, -+ ) -> DispatchResult { -+ let who = ensure_signed(origin)?; -+ Self::do_buy_kitty(who, kitty_id, max_price)?; -+ Ok(()) -+ } - } - } + Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); + Ok(()) diff --git a/src/tests.rs b/src/tests.rs -index 392e5bf..3aa8456 100644 +index e47a4736..c7f1af6a 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -358,3 +358,20 @@ fn set_price_logic_works() { - assert_eq!(kitty.price, Some(1337)); +@@ -343,3 +343,16 @@ fn set_price_emits_event() { + ); }) } + +#[test] -+fn do_buy_kitty_emits_event() { ++fn set_price_logic_works() { + new_test_ext().execute_with(|| { -+ // We need to set block number to 1 to view events. -+ System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ let kitty_id = Kitties::::iter_keys().collect::>()[0]; ++ let kitty = &Kitties::::iter_values().collect::>()[0]; ++ assert_eq!(kitty.price, None); ++ let kitty_id = kitty.dna; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); -+ assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); -+ assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); -+ // Assert the last event by our blockchain is the `Created` event with the correct owner. -+ System::assert_last_event( -+ Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), -+ ); ++ let kitty = Kitties::::get(kitty_id).unwrap(); ++ assert_eq!(kitty.price, Some(1337)); + }) +} diff --git a/src/31/solution/src/impls.rs b/src/31/solution/src/impls.rs index 03cf1f18..af4e4602 100644 --- a/src/31/solution/src/impls.rs +++ b/src/31/solution/src/impls.rs @@ -72,13 +72,4 @@ impl Pallet { Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); Ok(()) } - - pub fn do_buy_kitty( - buyer: T::AccountId, - kitty_id: [u8; 32], - price: BalanceOf, - ) -> DispatchResult { - Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); - Ok(()) - } } diff --git a/src/31/solution/src/lib.rs b/src/31/solution/src/lib.rs index 4a8d2b1b..57e0eeb7 100644 --- a/src/31/solution/src/lib.rs +++ b/src/31/solution/src/lib.rs @@ -56,7 +56,6 @@ pub mod pallet { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, - Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, } #[pallet::error] @@ -97,15 +96,5 @@ pub mod pallet { Self::do_set_price(who, kitty_id, new_price)?; Ok(()) } - - pub fn buy_kitty( - origin: OriginFor, - kitty_id: [u8; 32], - max_price: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_buy_kitty(who, kitty_id, max_price)?; - Ok(()) - } } } diff --git a/src/31/solution/src/tests.rs b/src/31/solution/src/tests.rs index 3aa8456a..c7f1af6a 100644 --- a/src/31/solution/src/tests.rs +++ b/src/31/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -358,20 +356,3 @@ fn set_price_logic_works() { assert_eq!(kitty.price, Some(1337)); }) } - -#[test] -fn do_buy_kitty_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); - assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); - // Assert the last event by our blockchain is the `Created` event with the correct owner. - System::assert_last_event( - Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), - ); - }) -} diff --git a/src/31/template/README.md b/src/31/template/README.md index 909c412a..68d1d6d2 100644 --- a/src/31/template/README.md +++ b/src/31/template/README.md @@ -1,39 +1,11 @@ -# Buy Kitty Extrinsic +# Set Price Logic -Now that kitties can have a price, we want to enable them to be purchasable. +The set price logic is very straight forward. -For that, we will create the `buy_kitty` extrinsic. +The only thing we really need to check is that the `kitty.owner` matches the `caller` of the function. -## Max Price - -You will notice in our scaffolding, one of the parameters of our `buy_kitty` extrinsic is `max_price: BalanceOf`. - -This allows the buyer to set the maximum price they are willing to spend to buy a specific kitty. - -But this is a little strange right? The kitty already has a price set on it. Why would we need the buyer to also send us a price? - -Well, without this `max_price` parameter, you open users of your pallet to a nasty attack! - -Remember that transactions on a blockchain are bundled into a block and executed one by one. - -Also remember that the order of transactions in the block is up for interpretation by the block producer! - -Imagine there is a kitty, and its price is set to 10 DOT. Then someone goes and submits `buy_kitty` without there being a `max_price` parameter. - -What can happen is that the kitty owner could see that transaction in the transaction pool, before the transaction is included in the block, then submit their own high priority transaction (usually by increasing the fees they pay), which raises the price of the kitty. - -Do you see the problem now? - -Basically without the `max_price` parameter, your pallet will become a honeypot for attackers to trick users into sending them their whole balance. - -The `max_price` parameter ensures the buyer and seller have reached an agreement on price, and that any changes to the agreement would not be executed. +Besides that, we simply need to update the `kitty.price` field, and write that back to the runtime storage. ## Your Turn -Hopefully you can see some of the ways that programming for a blockchain is different than what you might expect. Remember that blockchain systems need to be resilient to malicious individuals. Because it is a public and open system, we cannot make any assumptions about the behaviors of our users. We can only influence those behaviors through the rules of our state transition function. - -In this step, we again will just scaffold what we need for the `buy_kitty` extrinsic. - -- Create a new event `Sold` with the fields noted in the template. -- Create a new extrinsic `buy_kitty` with the params noted in the template. -- Create a new internal function `do_buy_kitty` which simply deposits the `Sold` event. +Follow the `TODO`s listed in the template, and complete the logic for the `do_set_price` function. diff --git a/src/31/template/src/impls.rs b/src/31/template/src/impls.rs index 04c92c57..aa65cdd0 100644 --- a/src/31/template/src/impls.rs +++ b/src/31/template/src/impls.rs @@ -64,23 +64,15 @@ impl Pallet { kitty_id: [u8; 32], new_price: Option>, ) -> DispatchResult { - let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - ensure!(kitty.owner == caller, Error::::NotOwner); - kitty.price = new_price; - Kitties::::insert(kitty_id, kitty); + /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: + - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. + - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. + - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. + - Set the `kitty.price` to `new_price`. + - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. + */ Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); Ok(()) } - - /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: - - Inputs to the function are: - - `buyer` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - `price` which is `BalanceOf`. - - It returns `DispatchResult`. - - The internal logic, for now, should be: - - `Self::deposit_event` with `Event::::Sold` and the appropriate params. - - Return `Ok(())`. - */ } diff --git a/src/31/template/src/lib.rs b/src/31/template/src/lib.rs index 8c3eb2e6..57e0eeb7 100644 --- a/src/31/template/src/lib.rs +++ b/src/31/template/src/lib.rs @@ -56,11 +56,6 @@ pub mod pallet { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, - /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: - - `buyer` which is `T::AccountId`. - - `kitty_id` which is `[u8; 32]`. - - `price` which is `BalanceOf`. - */ } #[pallet::error] @@ -101,17 +96,5 @@ pub mod pallet { Self::do_set_price(who, kitty_id, new_price)?; Ok(()) } - - /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: - - Inputs to the function are: - - `origin` which is `OriginFor`. - - `kitty_id` which is `[u8; 32]`. - - `max_price` which is `BalanceOf`. - - It returns `DispatchResult`. - - The internal logic should be: - - Extract `who` using `ensure_signed` on `origin`. - - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. - - Return `Ok(())`. - */ } } diff --git a/src/31/template/src/tests.rs b/src/31/template/src/tests.rs index 392e5bf5..e47a4736 100644 --- a/src/31/template/src/tests.rs +++ b/src/31/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -345,16 +343,3 @@ fn set_price_emits_event() { ); }) } - -#[test] -fn set_price_logic_works() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty = &Kitties::::iter_values().collect::>()[0]; - assert_eq!(kitty.price, None); - let kitty_id = kitty.dna; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - let kitty = Kitties::::get(kitty_id).unwrap(); - assert_eq!(kitty.price, Some(1337)); - }) -} diff --git a/src/31/template/template.diff b/src/31/template/template.diff index d0dab543..a6992a8d 100644 --- a/src/31/template/template.diff +++ b/src/31/template/template.diff @@ -1,54 +1,19 @@ diff --git a/src/impls.rs b/src/impls.rs -index af4e460..04c92c5 100644 +index 48af8471..aa65cdd0 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -72,4 +72,15 @@ impl Pallet { - Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); - Ok(()) - } -+ -+ /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: -+ - Inputs to the function are: -+ - `buyer` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `price` which is `BalanceOf`. -+ - It returns `DispatchResult`. -+ - The internal logic, for now, should be: -+ - `Self::deposit_event` with `Event::::Sold` and the appropriate params. -+ - Return `Ok(())`. -+ */ - } -diff --git a/src/lib.rs b/src/lib.rs -index 57e0eeb..8c3eb2e 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -56,6 +56,11 @@ pub mod pallet { - Created { owner: T::AccountId }, - Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, - PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, -+ /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: -+ - `buyer` which is `T::AccountId`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `price` which is `BalanceOf`. +@@ -64,6 +64,14 @@ impl Pallet { + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { ++ /* 🚧 TODO 🚧: Create the logic for setting the Kitty price: ++ - Create a mutable `kitty` by calling `get` on `Kitties` with `kitty_id`. ++ - Return an error if the kitty doesn't exist by returning `Error::::NoKitty`. ++ - `ensure!` that the `kitty.owner` is equal to the `caller` else return `Error::::NotOwner`. ++ - Set the `kitty.price` to `new_price`. ++ - Insert the modified `kitty` back into the `Kitties` map under `kitty_id`. + */ - } - - #[pallet::error] -@@ -96,5 +101,17 @@ pub mod pallet { - Self::do_set_price(who, kitty_id, new_price)?; - Ok(()) - } + -+ /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: -+ - Inputs to the function are: -+ - `origin` which is `OriginFor`. -+ - `kitty_id` which is `[u8; 32]`. -+ - `max_price` which is `BalanceOf`. -+ - It returns `DispatchResult`. -+ - The internal logic should be: -+ - Extract `who` using `ensure_signed` on `origin`. -+ - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. -+ - Return `Ok(())`. -+ */ + Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); + Ok(()) } - } diff --git a/src/32/solution/solution.diff b/src/32/solution/solution.diff index b06281ef..c300c612 100644 --- a/src/32/solution/solution.diff +++ b/src/32/solution/solution.diff @@ -1,117 +1,95 @@ diff --git a/src/impls.rs b/src/impls.rs -index c7a34b2..463894e 100644 +index 04c92c57..03cf1f18 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -1,7 +1,7 @@ - use super::*; - use frame::prelude::*; - use frame::primitives::BlakeTwo256; --/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ -+use frame::traits::tokens::Preservation; - use frame::traits::Hash; - - // Learn about internal functions. -@@ -79,22 +79,14 @@ impl Pallet { - kitty_id: [u8; 32], - price: BalanceOf, - ) -> DispatchResult { -- /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: -- - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. -- - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. -- - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. -- */ -+ let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; -+ let real_price = kitty.price.ok_or(Error::::NotForSale)?; -+ ensure!(price >= real_price, Error::::MaxPriceTooLow); - -- /* 🚧 TODO 🚧: Execute the transfers: -- - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. -- - The amount transferred should be the `real_price`. -- - Use `Preservation::Preserve` to ensure the buyer account stays alive. -- - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. -- - Remember to propagate up all results from these functions with `?`. -- */ -+ T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; -+ Self::do_transfer(kitty.owner, buyer.clone(), kitty_id)?; - -- /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ -- Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); -+ Self::deposit_event(Event::::Sold { buyer, kitty_id, price: real_price }); +@@ -73,14 +73,12 @@ impl Pallet { Ok(()) } + +- /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: +- - Inputs to the function are: +- - `buyer` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - `price` which is `BalanceOf`. +- - It returns `DispatchResult`. +- - The internal logic, for now, should be: +- - `Self::deposit_event` with `Event::::Sold` and the appropriate params. +- - Return `Ok(())`. +- */ ++ pub fn do_buy_kitty( ++ buyer: T::AccountId, ++ kitty_id: [u8; 32], ++ price: BalanceOf, ++ ) -> DispatchResult { ++ Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); ++ Ok(()) ++ } } diff --git a/src/lib.rs b/src/lib.rs -index f465b49..8ca2bdd 100644 +index 8c3eb2e6..4a8d2b1b 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -67,10 +67,8 @@ pub mod pallet { - TransferToSelf, - NoKitty, - NotOwner, -- /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: -- - `NotForSale`: for when the Kitty has a price set to `None`. -- - `MaxPriceTooLow`: for when the price offered by the buyer is too low. +@@ -56,11 +56,7 @@ pub mod pallet { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, + PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, +- /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: +- - `buyer` which is `T::AccountId`. +- - `kitty_id` which is `[u8; 32]`. +- - `price` which is `BalanceOf`. - */ -+ NotForSale, -+ MaxPriceTooLow, ++ Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, } - #[pallet::call] + #[pallet::error] +@@ -102,16 +98,14 @@ pub mod pallet { + Ok(()) + } + +- /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: +- - Inputs to the function are: +- - `origin` which is `OriginFor`. +- - `kitty_id` which is `[u8; 32]`. +- - `max_price` which is `BalanceOf`. +- - It returns `DispatchResult`. +- - The internal logic should be: +- - Extract `who` using `ensure_signed` on `origin`. +- - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. +- - Return `Ok(())`. +- */ ++ pub fn buy_kitty( ++ origin: OriginFor, ++ kitty_id: [u8; 32], ++ max_price: BalanceOf, ++ ) -> DispatchResult { ++ let who = ensure_signed(origin)?; ++ Self::do_buy_kitty(who, kitty_id, max_price)?; ++ Ok(()) ++ } + } + } diff --git a/src/tests.rs b/src/tests.rs -index 3aa8456..ec921a1 100644 +index c7f1af6a..91905422 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -375,3 +375,53 @@ fn do_buy_kitty_emits_event() { - ); +@@ -356,3 +356,20 @@ fn set_price_logic_works() { + assert_eq!(kitty.price, Some(1337)); }) } + +#[test] -+fn do_buy_kitty_logic_works() { ++fn do_buy_kitty_emits_event() { + new_test_ext().execute_with(|| { ++ // We need to set block number to 1 to view events. ++ System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ let kitty = &Kitties::::iter_values().collect::>()[0]; -+ let kitty_id = kitty.dna; -+ assert_eq!(kitty.owner, ALICE); -+ assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); -+ // Cannot buy kitty which does not exist. -+ assert_noop!( -+ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), [0u8; 32], 1337), -+ Error::::NoKitty -+ ); -+ // Cannot buy kitty which is not for sale. -+ assert_noop!( -+ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), -+ Error::::NotForSale -+ ); ++ let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); -+ // Cannot buy kitty for a lower price. -+ assert_noop!( -+ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1336), -+ Error::::MaxPriceTooLow -+ ); -+ // Cannot buy kitty if you don't have the funds. -+ assert_noop!( -+ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), -+ frame::arithmetic::ArithmeticError::Underflow -+ ); -+ // Cannot buy kitty if it would kill your account (i.e. set your balance to 0). -+ assert_ok!(PalletBalances::mint_into(&BOB, 1337)); -+ assert!( -+ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337).is_err(), -+ // TODO: assert_noop on DispatchError::Token(TokenError::NotExpendable) -+ ); -+ // When everything is right, it works. + assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); + assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); -+ // State is updated correctly. -+ assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); -+ let kitty = Kitties::::get(kitty_id).unwrap(); -+ assert_eq!(kitty.owner, BOB); -+ // Price is reset to `None`. -+ assert_eq!(kitty.price, None); -+ // BOB transferred funds to ALICE. -+ assert_eq!(PalletBalances::balance(&ALICE), 1337); -+ assert_eq!(PalletBalances::balance(&BOB), 100_000); ++ // Assert the last event by our blockchain is the `Created` event with the correct owner. ++ System::assert_last_event( ++ Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), ++ ); + }) +} diff --git a/src/32/solution/src/impls.rs b/src/32/solution/src/impls.rs index 463894ed..03cf1f18 100644 --- a/src/32/solution/src/impls.rs +++ b/src/32/solution/src/impls.rs @@ -1,7 +1,6 @@ use super::*; use frame::prelude::*; use frame::primitives::BlakeTwo256; -use frame::traits::tokens::Preservation; use frame::traits::Hash; // Learn about internal functions. @@ -79,14 +78,7 @@ impl Pallet { kitty_id: [u8; 32], price: BalanceOf, ) -> DispatchResult { - let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; - let real_price = kitty.price.ok_or(Error::::NotForSale)?; - ensure!(price >= real_price, Error::::MaxPriceTooLow); - - T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; - Self::do_transfer(kitty.owner, buyer.clone(), kitty_id)?; - - Self::deposit_event(Event::::Sold { buyer, kitty_id, price: real_price }); + Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); Ok(()) } } diff --git a/src/32/solution/src/lib.rs b/src/32/solution/src/lib.rs index 8ca2bdd1..4a8d2b1b 100644 --- a/src/32/solution/src/lib.rs +++ b/src/32/solution/src/lib.rs @@ -67,8 +67,6 @@ pub mod pallet { TransferToSelf, NoKitty, NotOwner, - NotForSale, - MaxPriceTooLow, } #[pallet::call] diff --git a/src/32/solution/src/tests.rs b/src/32/solution/src/tests.rs index ec921a1e..91905422 100644 --- a/src/32/solution/src/tests.rs +++ b/src/32/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -375,53 +373,3 @@ fn do_buy_kitty_emits_event() { ); }) } - -#[test] -fn do_buy_kitty_logic_works() { - new_test_ext().execute_with(|| { - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty = &Kitties::::iter_values().collect::>()[0]; - let kitty_id = kitty.dna; - assert_eq!(kitty.owner, ALICE); - assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); - // Cannot buy kitty which does not exist. - assert_noop!( - PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), [0u8; 32], 1337), - Error::::NoKitty - ); - // Cannot buy kitty which is not for sale. - assert_noop!( - PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), - Error::::NotForSale - ); - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - // Cannot buy kitty for a lower price. - assert_noop!( - PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1336), - Error::::MaxPriceTooLow - ); - // Cannot buy kitty if you don't have the funds. - assert_noop!( - PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), - frame::arithmetic::ArithmeticError::Underflow - ); - // Cannot buy kitty if it would kill your account (i.e. set your balance to 0). - assert_ok!(PalletBalances::mint_into(&BOB, 1337)); - assert!( - PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337).is_err(), - // TODO: assert_noop on DispatchError::Token(TokenError::NotExpendable) - ); - // When everything is right, it works. - assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); - assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); - // State is updated correctly. - assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); - let kitty = Kitties::::get(kitty_id).unwrap(); - assert_eq!(kitty.owner, BOB); - // Price is reset to `None`. - assert_eq!(kitty.price, None); - // BOB transferred funds to ALICE. - assert_eq!(PalletBalances::balance(&ALICE), 1337); - assert_eq!(PalletBalances::balance(&BOB), 100_000); - }) -} diff --git a/src/32/template/README.md b/src/32/template/README.md index 5ce0efd5..909c412a 100644 --- a/src/32/template/README.md +++ b/src/32/template/README.md @@ -1,118 +1,39 @@ -# Buy Kitty Logic +# Buy Kitty Extrinsic -Now that we have scaffolded the `buy_kitty` extrinsic, its time for us to program its logic. +Now that kitties can have a price, we want to enable them to be purchasable. -## Sanity Checks +For that, we will create the `buy_kitty` extrinsic. -As always, before we execute any logic, we should check that everything is okay to actually proceed with the kitty purchase. +## Max Price -The first check should obviously be if the kitty the user wants to purchase actually exists. +You will notice in our scaffolding, one of the parameters of our `buy_kitty` extrinsic is `max_price: BalanceOf`. -Then we should check if the kitty is actually for sale, which is indicated by `Some(real_price)`, and extract that `real_price`. +This allows the buyer to set the maximum price they are willing to spend to buy a specific kitty. -Finally, we should check that there is an agreement between the buyer and the seller on the price. +But this is a little strange right? The kitty already has a price set on it. Why would we need the buyer to also send us a price? -The buyer will submit `max_price`, and we want to ensure that this `max_price` is greater than or equal to the `real_price`. +Well, without this `max_price` parameter, you open users of your pallet to a nasty attack! -## Transfer Logic +Remember that transactions on a blockchain are bundled into a block and executed one by one. -To execute a purchase, we need to transfer two things: +Also remember that the order of transactions in the block is up for interpretation by the block producer! -- The token balance from the buyer to the seller. -- The kitty from the seller to the buyer. +Imagine there is a kitty, and its price is set to 10 DOT. Then someone goes and submits `buy_kitty` without there being a `max_price` parameter. -### Transfer the Native Balance +What can happen is that the kitty owner could see that transaction in the transaction pool, before the transaction is included in the block, then submit their own high priority transaction (usually by increasing the fees they pay), which raises the price of the kitty. -To transfer the `NativeBalance`, you can use the [`transfer`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Mutate.html#method.transfer) API which is included in the `fungible::Mutate` trait. +Do you see the problem now? -```rust -fn transfer( - source: &AccountId, - dest: &AccountId, - amount: Self::Balance, - preservation: Preservation -) -> Result -``` +Basically without the `max_price` parameter, your pallet will become a honeypot for attackers to trick users into sending them their whole balance. -> NOTE: To access this function, you will need import the trait to bring it in scope. Otherwise you will get an error that the function does not exist. So don't forget to include: -> -> ```rust -> use frame::traits::fungible::Mutate; -> ``` - -The first 3 parameters here are easy enough to understand: `source`, `dest`, and `amount`. - -However we also have a 4th parameter which is `preservation: Preservation`. - -```rust -/// The mode by which we describe whether an operation should keep an account alive. -pub enum Preservation { - /// We don't care if the account gets killed by this operation. - Expendable, - /// The account may not be killed, but we don't care if the balance gets dusted. - Protect, - /// The account may not be killed and our provider reference must remain (in the context of - /// tokens, this means that the account may not be dusted). - Preserve, -} -``` - -To understand this, you will need to dive deep into dust accounts, existential deposit, and account deletion. - -That is all beyond the scope of this tutorial, but the high level idea is that we require accounts in the `polkadot-sdk` to maintain a minimum balance. Whenever we transfer funds from the user, beyond checking they have enough to transfer, we also check whether that minimum amount is still left over in their account, and adjust the result of our transfer based on our `Preservation` requirements. - -In this context, we don't want someone to kill their account to buy a kitty, so we want to use `Preservation::Preserve` for our `transfer`. - -> NOTE: Don't forget the TODO that imports this enum so you can use it: -> -> ```rust -> use frame::traits::tokens::Preservation; -> ``` - -So the final syntax should look like: - -```rust -T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; -``` - -### Transfer the Kitty - -To transfer the kitty, we can simply reuse our `Self::do_transfer` function that we wrote in the past. - -Hopefully you can start to see the payoff of how we have organized our code. - -As you work on more complex projects, breaking down behaviors into simple to understand and reusable pieces of internal logic will not only make your code more clean, but also easier to audit and update. - -It is unlikely you will have foresight on exactly how to break down your project when you first start writing it, but it is definitely something you should keep in mind as you find yourself writing potentially duplicate code. - -### Propagate Up Errors - -Both transfer functions need to succeed for the sale to complete successfully. - -If either one of them would fail, the whole purchase should fail. - -Thankfully, both of our transfer functions return a result, and to handle things correctly here, we just need to propagate up those errors. For that, we simply include `?` at the end of the function. - -If at any point our extrinsic or the logic inside the extrinsic returns an error, the whole extrinsic will fail and all changes to storage will be undone. This is exactly the same behavior you would expect from a smart contract, and keeps our state transition function functioning smoothly. - -## Real Price - -As a final change in your pallet logic, we should update the `Event::::Sold` to emit the `real_price` instead of the buyers `max_price`. - -This is perhaps the first really good use case for emitting an Event. - -In all of our previous events, we basically just deposit the data that we already get from the extrinsic. - -However in this case, our `buy_kitty` extrinsic actually uses internal logic to manipulate what actually happens. It could be that the user submits a `max_price` higher than the `real_price`, and thus the extrinsic and a success message would not actually tell us what happened exactly. - -In this case, we can actually report by to the user and any UI they are using the executed sale price of the kitty. - -So this is really where Pallet Events shine, and how they should be used. This is something you will develop a sense for over time. +The `max_price` parameter ensures the buyer and seller have reached an agreement on price, and that any changes to the agreement would not be executed. ## Your Turn -You now have all the tools and information needed to build your `do_buy_kitty` logic. +Hopefully you can see some of the ways that programming for a blockchain is different than what you might expect. Remember that blockchain systems need to be resilient to malicious individuals. Because it is a public and open system, we cannot make any assumptions about the behaviors of our users. We can only influence those behaviors through the rules of our state transition function. + +In this step, we again will just scaffold what we need for the `buy_kitty` extrinsic. -- Add the sanity checks needed to make sure `do_buy_kitty` should execute at all. -- Transfer both the `NativeBalance` and the `Kitty`, being sure to check for success. -- Finally, update the `Event::::Sold` to deposit the `real_price` that was transferred. +- Create a new event `Sold` with the fields noted in the template. +- Create a new extrinsic `buy_kitty` with the params noted in the template. +- Create a new internal function `do_buy_kitty` which simply deposits the `Sold` event. diff --git a/src/32/template/src/impls.rs b/src/32/template/src/impls.rs index c7a34b2c..04c92c57 100644 --- a/src/32/template/src/impls.rs +++ b/src/32/template/src/impls.rs @@ -1,7 +1,6 @@ use super::*; use frame::prelude::*; use frame::primitives::BlakeTwo256; -/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ use frame::traits::Hash; // Learn about internal functions. @@ -74,27 +73,14 @@ impl Pallet { Ok(()) } - pub fn do_buy_kitty( - buyer: T::AccountId, - kitty_id: [u8; 32], - price: BalanceOf, - ) -> DispatchResult { - /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: - - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. - - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. - - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. - */ - - /* 🚧 TODO 🚧: Execute the transfers: - - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. - - The amount transferred should be the `real_price`. - - Use `Preservation::Preserve` to ensure the buyer account stays alive. - - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. - - Remember to propagate up all results from these functions with `?`. - */ - - /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ - Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); - Ok(()) - } + /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: + - Inputs to the function are: + - `buyer` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - `price` which is `BalanceOf`. + - It returns `DispatchResult`. + - The internal logic, for now, should be: + - `Self::deposit_event` with `Event::::Sold` and the appropriate params. + - Return `Ok(())`. + */ } diff --git a/src/32/template/src/lib.rs b/src/32/template/src/lib.rs index f465b493..8c3eb2e6 100644 --- a/src/32/template/src/lib.rs +++ b/src/32/template/src/lib.rs @@ -56,7 +56,11 @@ pub mod pallet { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, - Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, + /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: + - `buyer` which is `T::AccountId`. + - `kitty_id` which is `[u8; 32]`. + - `price` which is `BalanceOf`. + */ } #[pallet::error] @@ -67,10 +71,6 @@ pub mod pallet { TransferToSelf, NoKitty, NotOwner, - /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: - - `NotForSale`: for when the Kitty has a price set to `None`. - - `MaxPriceTooLow`: for when the price offered by the buyer is too low. - */ } #[pallet::call] @@ -102,14 +102,16 @@ pub mod pallet { Ok(()) } - pub fn buy_kitty( - origin: OriginFor, - kitty_id: [u8; 32], - max_price: BalanceOf, - ) -> DispatchResult { - let who = ensure_signed(origin)?; - Self::do_buy_kitty(who, kitty_id, max_price)?; - Ok(()) - } + /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: + - Inputs to the function are: + - `origin` which is `OriginFor`. + - `kitty_id` which is `[u8; 32]`. + - `max_price` which is `BalanceOf`. + - It returns `DispatchResult`. + - The internal logic should be: + - Extract `who` using `ensure_signed` on `origin`. + - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. + - Return `Ok(())`. + */ } } diff --git a/src/32/template/src/tests.rs b/src/32/template/src/tests.rs index 3aa8456a..c7f1af6a 100644 --- a/src/32/template/src/tests.rs +++ b/src/32/template/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -358,20 +356,3 @@ fn set_price_logic_works() { assert_eq!(kitty.price, Some(1337)); }) } - -#[test] -fn do_buy_kitty_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - let kitty_id = Kitties::::iter_keys().collect::>()[0]; - assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); - assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); - assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); - // Assert the last event by our blockchain is the `Created` event with the correct owner. - System::assert_last_event( - Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), - ); - }) -} diff --git a/src/32/template/template.diff b/src/32/template/template.diff index 44e4e778..73f35d2e 100644 --- a/src/32/template/template.diff +++ b/src/32/template/template.diff @@ -1,49 +1,54 @@ diff --git a/src/impls.rs b/src/impls.rs -index 03cf1f1..c7a34b2 100644 +index af4e4602..04c92c57 100644 --- a/src/impls.rs +++ b/src/impls.rs -@@ -1,6 +1,7 @@ - use super::*; - use frame::prelude::*; - use frame::primitives::BlakeTwo256; -+/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ - use frame::traits::Hash; - - // Learn about internal functions. -@@ -78,6 +79,21 @@ impl Pallet { - kitty_id: [u8; 32], - price: BalanceOf, - ) -> DispatchResult { -+ /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: -+ - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. -+ - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. -+ - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. -+ */ -+ -+ /* 🚧 TODO 🚧: Execute the transfers: -+ - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. -+ - The amount transferred should be the `real_price`. -+ - Use `Preservation::Preserve` to ensure the buyer account stays alive. -+ - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. -+ - Remember to propagate up all results from these functions with `?`. -+ */ -+ -+ /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ - Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); +@@ -72,4 +72,15 @@ impl Pallet { + Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); Ok(()) } ++ ++ /* 🚧 TODO 🚧: Create a new internal function `do_buy_kitty`: ++ - Inputs to the function are: ++ - `buyer` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `price` which is `BalanceOf`. ++ - It returns `DispatchResult`. ++ - The internal logic, for now, should be: ++ - `Self::deposit_event` with `Event::::Sold` and the appropriate params. ++ - Return `Ok(())`. ++ */ + } diff --git a/src/lib.rs b/src/lib.rs -index 4a8d2b1..f465b49 100644 +index 57e0eeb7..8c3eb2e6 100644 --- a/src/lib.rs +++ b/src/lib.rs -@@ -67,6 +67,10 @@ pub mod pallet { - TransferToSelf, - NoKitty, - NotOwner, -+ /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: -+ - `NotForSale`: for when the Kitty has a price set to `None`. -+ - `MaxPriceTooLow`: for when the price offered by the buyer is too low. +@@ -56,6 +56,11 @@ pub mod pallet { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, + PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, ++ /* 🚧 TODO 🚧: Create a new `Event` called `Sold` with the following parameters: ++ - `buyer` which is `T::AccountId`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `price` which is `BalanceOf`. + */ } - #[pallet::call] + #[pallet::error] +@@ -96,5 +101,17 @@ pub mod pallet { + Self::do_set_price(who, kitty_id, new_price)?; + Ok(()) + } ++ ++ /* 🚧 TODO 🚧: Create a new callable function `buy_kitty`: ++ - Inputs to the function are: ++ - `origin` which is `OriginFor`. ++ - `kitty_id` which is `[u8; 32]`. ++ - `max_price` which is `BalanceOf`. ++ - It returns `DispatchResult`. ++ - The internal logic should be: ++ - Extract `who` using `ensure_signed` on `origin`. ++ - Call `Self::do_buy_kitty` using appropriate params, and propagating the result. ++ - Return `Ok(())`. ++ */ + } + } diff --git a/src/33/README.md b/src/33/README.md index df81bc46..eb9560d4 100644 --- a/src/33/README.md +++ b/src/33/README.md @@ -1,6 +1,103 @@ -
+
+
-{{#include ./source/README.md}} +{{#include ./template/README.md}}
+ +
+ +
+ + + +
+ +
+ +
+ + +
+
+ +```rust +{{#include ./template/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./template/src/lib.rs}} +``` + +
+ + + +
+ +
+ +
+ + + +
+
+ +```rust +{{#include ./solution/src/impls.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/lib.rs}} +``` + +
+ +
+ +```rust +{{#include ./solution/src/tests.rs}} +``` + +
+ + + +
+ +
+ + +
+ + +
+
+ +```diff +{{#include ./template/template.diff}} +``` + +
+
+ +```diff +{{#include ./solution/solution.diff}} +``` + +
+ +
+ +
+
diff --git a/src/25/template/.gitignore b/src/33/solution/.gitignore similarity index 100% rename from src/25/template/.gitignore rename to src/33/solution/.gitignore diff --git a/src/25/template/Cargo.lock b/src/33/solution/Cargo.lock similarity index 100% rename from src/25/template/Cargo.lock rename to src/33/solution/Cargo.lock diff --git a/src/25/template/Cargo.toml b/src/33/solution/Cargo.toml similarity index 100% rename from src/25/template/Cargo.toml rename to src/33/solution/Cargo.toml diff --git a/src/25/solution/README.md b/src/33/solution/README.md similarity index 100% rename from src/25/solution/README.md rename to src/33/solution/README.md diff --git a/src/25/template/rustfmt.toml b/src/33/solution/rustfmt.toml similarity index 100% rename from src/25/template/rustfmt.toml rename to src/33/solution/rustfmt.toml diff --git a/src/33/solution/solution.diff b/src/33/solution/solution.diff new file mode 100644 index 00000000..573420e8 --- /dev/null +++ b/src/33/solution/solution.diff @@ -0,0 +1,117 @@ +diff --git a/src/impls.rs b/src/impls.rs +index c7a34b2c..463894ed 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -1,7 +1,7 @@ + use super::*; + use frame::prelude::*; + use frame::primitives::BlakeTwo256; +-/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ ++use frame::traits::tokens::Preservation; + use frame::traits::Hash; + + // Learn about internal functions. +@@ -79,22 +79,14 @@ impl Pallet { + kitty_id: [u8; 32], + price: BalanceOf, + ) -> DispatchResult { +- /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: +- - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. +- - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. +- - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. +- */ ++ let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; ++ let real_price = kitty.price.ok_or(Error::::NotForSale)?; ++ ensure!(price >= real_price, Error::::MaxPriceTooLow); + +- /* 🚧 TODO 🚧: Execute the transfers: +- - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. +- - The amount transferred should be the `real_price`. +- - Use `Preservation::Preserve` to ensure the buyer account stays alive. +- - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. +- - Remember to propagate up all results from these functions with `?`. +- */ ++ T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; ++ Self::do_transfer(kitty.owner, buyer.clone(), kitty_id)?; + +- /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ +- Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); ++ Self::deposit_event(Event::::Sold { buyer, kitty_id, price: real_price }); + Ok(()) + } + } +diff --git a/src/lib.rs b/src/lib.rs +index f465b493..8ca2bdd1 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -67,10 +67,8 @@ pub mod pallet { + TransferToSelf, + NoKitty, + NotOwner, +- /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: +- - `NotForSale`: for when the Kitty has a price set to `None`. +- - `MaxPriceTooLow`: for when the price offered by the buyer is too low. +- */ ++ NotForSale, ++ MaxPriceTooLow, + } + + #[pallet::call] +diff --git a/src/tests.rs b/src/tests.rs +index 91905422..5f8a1953 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -373,3 +373,53 @@ fn do_buy_kitty_emits_event() { + ); + }) + } ++ ++#[test] ++fn do_buy_kitty_logic_works() { ++ new_test_ext().execute_with(|| { ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ let kitty = &Kitties::::iter_values().collect::>()[0]; ++ let kitty_id = kitty.dna; ++ assert_eq!(kitty.owner, ALICE); ++ assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); ++ // Cannot buy kitty which does not exist. ++ assert_noop!( ++ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), [0u8; 32], 1337), ++ Error::::NoKitty ++ ); ++ // Cannot buy kitty which is not for sale. ++ assert_noop!( ++ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), ++ Error::::NotForSale ++ ); ++ assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); ++ // Cannot buy kitty for a lower price. ++ assert_noop!( ++ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1336), ++ Error::::MaxPriceTooLow ++ ); ++ // Cannot buy kitty if you don't have the funds. ++ assert_noop!( ++ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), ++ frame::arithmetic::ArithmeticError::Underflow ++ ); ++ // Cannot buy kitty if it would kill your account (i.e. set your balance to 0). ++ assert_ok!(PalletBalances::mint_into(&BOB, 1337)); ++ assert!( ++ PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337).is_err(), ++ // TODO: assert_noop on DispatchError::Token(TokenError::NotExpendable) ++ ); ++ // When everything is right, it works. ++ assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); ++ assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); ++ // State is updated correctly. ++ assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); ++ let kitty = Kitties::::get(kitty_id).unwrap(); ++ assert_eq!(kitty.owner, BOB); ++ // Price is reset to `None`. ++ assert_eq!(kitty.price, None); ++ // BOB transferred funds to ALICE. ++ assert_eq!(PalletBalances::balance(&ALICE), 1337); ++ assert_eq!(PalletBalances::balance(&BOB), 100_000); ++ }) ++} diff --git a/src/33/source/src/impls.rs b/src/33/solution/src/impls.rs similarity index 100% rename from src/33/source/src/impls.rs rename to src/33/solution/src/impls.rs diff --git a/src/33/source/src/lib.rs b/src/33/solution/src/lib.rs similarity index 100% rename from src/33/source/src/lib.rs rename to src/33/solution/src/lib.rs diff --git a/src/33/source/src/tests.rs b/src/33/solution/src/tests.rs similarity index 98% rename from src/33/source/src/tests.rs rename to src/33/solution/src/tests.rs index ec921a1e..5f8a1953 100644 --- a/src/33/source/src/tests.rs +++ b/src/33/solution/src/tests.rs @@ -50,15 +50,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -110,8 +110,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/33/source/.gitignore b/src/33/template/.gitignore similarity index 100% rename from src/33/source/.gitignore rename to src/33/template/.gitignore diff --git a/src/33/source/Cargo.lock b/src/33/template/Cargo.lock similarity index 100% rename from src/33/source/Cargo.lock rename to src/33/template/Cargo.lock diff --git a/src/33/source/Cargo.toml b/src/33/template/Cargo.toml similarity index 100% rename from src/33/source/Cargo.toml rename to src/33/template/Cargo.toml diff --git a/src/33/template/README.md b/src/33/template/README.md new file mode 100644 index 00000000..5ce0efd5 --- /dev/null +++ b/src/33/template/README.md @@ -0,0 +1,118 @@ +# Buy Kitty Logic + +Now that we have scaffolded the `buy_kitty` extrinsic, its time for us to program its logic. + +## Sanity Checks + +As always, before we execute any logic, we should check that everything is okay to actually proceed with the kitty purchase. + +The first check should obviously be if the kitty the user wants to purchase actually exists. + +Then we should check if the kitty is actually for sale, which is indicated by `Some(real_price)`, and extract that `real_price`. + +Finally, we should check that there is an agreement between the buyer and the seller on the price. + +The buyer will submit `max_price`, and we want to ensure that this `max_price` is greater than or equal to the `real_price`. + +## Transfer Logic + +To execute a purchase, we need to transfer two things: + +- The token balance from the buyer to the seller. +- The kitty from the seller to the buyer. + +### Transfer the Native Balance + +To transfer the `NativeBalance`, you can use the [`transfer`](https://docs.rs/frame-support/38.0.0/frame_support/traits/tokens/fungible/trait.Mutate.html#method.transfer) API which is included in the `fungible::Mutate` trait. + +```rust +fn transfer( + source: &AccountId, + dest: &AccountId, + amount: Self::Balance, + preservation: Preservation +) -> Result +``` + +> NOTE: To access this function, you will need import the trait to bring it in scope. Otherwise you will get an error that the function does not exist. So don't forget to include: +> +> ```rust +> use frame::traits::fungible::Mutate; +> ``` + +The first 3 parameters here are easy enough to understand: `source`, `dest`, and `amount`. + +However we also have a 4th parameter which is `preservation: Preservation`. + +```rust +/// The mode by which we describe whether an operation should keep an account alive. +pub enum Preservation { + /// We don't care if the account gets killed by this operation. + Expendable, + /// The account may not be killed, but we don't care if the balance gets dusted. + Protect, + /// The account may not be killed and our provider reference must remain (in the context of + /// tokens, this means that the account may not be dusted). + Preserve, +} +``` + +To understand this, you will need to dive deep into dust accounts, existential deposit, and account deletion. + +That is all beyond the scope of this tutorial, but the high level idea is that we require accounts in the `polkadot-sdk` to maintain a minimum balance. Whenever we transfer funds from the user, beyond checking they have enough to transfer, we also check whether that minimum amount is still left over in their account, and adjust the result of our transfer based on our `Preservation` requirements. + +In this context, we don't want someone to kill their account to buy a kitty, so we want to use `Preservation::Preserve` for our `transfer`. + +> NOTE: Don't forget the TODO that imports this enum so you can use it: +> +> ```rust +> use frame::traits::tokens::Preservation; +> ``` + +So the final syntax should look like: + +```rust +T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; +``` + +### Transfer the Kitty + +To transfer the kitty, we can simply reuse our `Self::do_transfer` function that we wrote in the past. + +Hopefully you can start to see the payoff of how we have organized our code. + +As you work on more complex projects, breaking down behaviors into simple to understand and reusable pieces of internal logic will not only make your code more clean, but also easier to audit and update. + +It is unlikely you will have foresight on exactly how to break down your project when you first start writing it, but it is definitely something you should keep in mind as you find yourself writing potentially duplicate code. + +### Propagate Up Errors + +Both transfer functions need to succeed for the sale to complete successfully. + +If either one of them would fail, the whole purchase should fail. + +Thankfully, both of our transfer functions return a result, and to handle things correctly here, we just need to propagate up those errors. For that, we simply include `?` at the end of the function. + +If at any point our extrinsic or the logic inside the extrinsic returns an error, the whole extrinsic will fail and all changes to storage will be undone. This is exactly the same behavior you would expect from a smart contract, and keeps our state transition function functioning smoothly. + +## Real Price + +As a final change in your pallet logic, we should update the `Event::::Sold` to emit the `real_price` instead of the buyers `max_price`. + +This is perhaps the first really good use case for emitting an Event. + +In all of our previous events, we basically just deposit the data that we already get from the extrinsic. + +However in this case, our `buy_kitty` extrinsic actually uses internal logic to manipulate what actually happens. It could be that the user submits a `max_price` higher than the `real_price`, and thus the extrinsic and a success message would not actually tell us what happened exactly. + +In this case, we can actually report by to the user and any UI they are using the executed sale price of the kitty. + +So this is really where Pallet Events shine, and how they should be used. This is something you will develop a sense for over time. + +## Your Turn + +You now have all the tools and information needed to build your `do_buy_kitty` logic. + +- Add the sanity checks needed to make sure `do_buy_kitty` should execute at all. +- Transfer both the `NativeBalance` and the `Kitty`, being sure to check for success. +- Finally, update the `Event::::Sold` to deposit the `real_price` that was transferred. diff --git a/src/33/source/rustfmt.toml b/src/33/template/rustfmt.toml similarity index 100% rename from src/33/source/rustfmt.toml rename to src/33/template/rustfmt.toml diff --git a/src/33/template/src/impls.rs b/src/33/template/src/impls.rs new file mode 100644 index 00000000..c7a34b2c --- /dev/null +++ b/src/33/template/src/impls.rs @@ -0,0 +1,100 @@ +use super::*; +use frame::prelude::*; +use frame::primitives::BlakeTwo256; +/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ +use frame::traits::Hash; + +// Learn about internal functions. +impl Pallet { + // Generates and returns DNA + pub fn gen_dna() -> [u8; 32] { + // Create randomness payload. Multiple kitties can be generated in the same block, + // retaining uniqueness. + let unique_payload = ( + frame_system::Pallet::::parent_hash(), + frame_system::Pallet::::block_number(), + frame_system::Pallet::::extrinsic_index(), + CountForKitties::::get(), + ); + + BlakeTwo256::hash_of(&unique_payload).into() + } + + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { + let kitty = Kitty { dna, owner: owner.clone(), price: None }; + // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); + + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + + KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; + Kitties::::insert(dna, kitty); + CountForKitties::::set(new_count); + + Self::deposit_event(Event::::Created { owner }); + Ok(()) + } + + pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { + ensure!(from != to, Error::::TransferToSelf); + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == from, Error::::NotOwner); + kitty.owner = to.clone(); + kitty.price = None; + + let mut to_owned = KittiesOwned::::get(&to); + to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; + let mut from_owned = KittiesOwned::::get(&from); + if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { + from_owned.swap_remove(ind); + } else { + return Err(Error::::NoKitty.into()) + } + + Kitties::::insert(kitty_id, kitty); + KittiesOwned::::insert(&to, to_owned); + KittiesOwned::::insert(&from, from_owned); + + Self::deposit_event(Event::::Transferred { from, to, kitty_id }); + Ok(()) + } + + pub fn do_set_price( + caller: T::AccountId, + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == caller, Error::::NotOwner); + kitty.price = new_price; + Kitties::::insert(kitty_id, kitty); + + Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); + Ok(()) + } + + pub fn do_buy_kitty( + buyer: T::AccountId, + kitty_id: [u8; 32], + price: BalanceOf, + ) -> DispatchResult { + /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: + - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. + - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. + - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. + */ + + /* 🚧 TODO 🚧: Execute the transfers: + - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. + - The amount transferred should be the `real_price`. + - Use `Preservation::Preserve` to ensure the buyer account stays alive. + - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. + - Remember to propagate up all results from these functions with `?`. + */ + + /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ + Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); + Ok(()) + } +} diff --git a/src/33/template/src/lib.rs b/src/33/template/src/lib.rs new file mode 100644 index 00000000..f465b493 --- /dev/null +++ b/src/33/template/src/lib.rs @@ -0,0 +1,115 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod impls; +mod tests; + +use frame::prelude::*; +use frame::traits::fungible::Inspect; +use frame::traits::fungible::Mutate; +pub use pallet::*; + +#[frame::pallet(dev_mode)] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Fungible handler for the kitties pallet. + type NativeBalance: Inspect + Mutate; + } + + // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. + pub type BalanceOf = + <::NativeBalance as Inspect<::AccountId>>::Balance; + + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct Kitty { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], + pub owner: T::AccountId, + pub price: Option>, + } + + #[pallet::storage] + pub(super) type CountForKitties = StorageValue; + + #[pallet::storage] + pub(super) type Kitties = StorageMap>; + + /// Track the kitties owned by each account. + #[pallet::storage] + pub(super) type KittiesOwned = StorageMap< + Key = T::AccountId, + Value = BoundedVec<[u8; 32], ConstU32<100>>, + QueryKind = ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, + PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, + Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + TooManyKitties, + DuplicateKitty, + TooManyOwned, + TransferToSelf, + NoKitty, + NotOwner, + /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: + - `NotForSale`: for when the Kitty has a price set to `None`. + - `MaxPriceTooLow`: for when the price offered by the buyer is too low. + */ + } + + #[pallet::call] + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let dna = Self::gen_dna(); + Self::mint(who, dna)?; + Ok(()) + } + + pub fn transfer( + origin: OriginFor, + to: T::AccountId, + kitty_id: [u8; 32], + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_transfer(who, to, kitty_id)?; + Ok(()) + } + + pub fn set_price( + origin: OriginFor, + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_price(who, kitty_id, new_price)?; + Ok(()) + } + + pub fn buy_kitty( + origin: OriginFor, + kitty_id: [u8; 32], + max_price: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_buy_kitty(who, kitty_id, max_price)?; + Ok(()) + } + } +} diff --git a/src/33/template/src/tests.rs b/src/33/template/src/tests.rs new file mode 100644 index 00000000..91905422 --- /dev/null +++ b/src/33/template/src/tests.rs @@ -0,0 +1,375 @@ +// Tests for the Kitties Pallet. +// +// Normally this file would be split into two parts: `mock.rs` and `tests.rs`. +// The `mock.rs` file would contain all the setup code for our `TestRuntime`. +// Then `tests.rs` would only have the tests for our pallet. +// However, to minimize the project, these have been combined into this single file. +// +// Learn more about creating tests for Pallets: +// https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html + +// This flag tells rust to only run this file when running `cargo test`. +#![cfg(test)] + +use crate as pallet_kitties; +use crate::*; +use frame::deps::frame_support::runtime; +use frame::deps::sp_io; +use frame::runtime::prelude::*; +use frame::testing_prelude::*; +use frame::traits::fungible::*; + +type Balance = u64; +type Block = frame_system::mocking::MockBlock; + +// In our "test runtime", we represent a user `AccountId` with a `u64`. +// This is just a simplification so that we don't need to generate a bunch of proper cryptographic +// public keys when writing tests. It is just easier to say "user 1 transfers to user 2". +// We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. +const ALICE: u64 = 1; +const BOB: u64 = 2; +const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; + +#[runtime] +mod runtime { + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + #[runtime::runtime] + /// The "test runtime" that represents the state transition function for our blockchain. + /// + /// The runtime is composed of individual modules called "pallets", which you find see below. + /// Each pallet has its own logic and storage, all of which can be combined together. + pub struct TestRuntime; + + /// System: Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) + #[runtime::pallet_index(1)] + pub type PalletBalances = pallet_balances::Pallet; + + /// PalletKitties: The pallet you are building in this tutorial! + #[runtime::pallet_index(2)] + pub type PalletKitties = pallet_kitties::Pallet; +} + +// Normally `System` would have many more configurations, but you can see that we use some macro +// magic to automatically configure most of the pallet for a "default test configuration". +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +// Normally `pallet_balances` would have many more configurations, but you can see that we use some +// macro magic to automatically configure most of the pallet for a "default test configuration". +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = Balance; +} + +// This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you +// will also need to update this configuration to represent that. +impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type NativeBalance = PalletBalances; +} + +// We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` +// It simulates the blockchain database backend for our tests. +// If you forget to include this and try to access your Pallet storage, you will get an error like: +// "`get_version_1` called outside of an Externalities-provided environment." +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} + +#[test] +fn starting_template_is_sane() { + new_test_ext().execute_with(|| { + let event = Event::::Created { owner: ALICE }; + let _runtime_event: RuntimeEvent = event.into(); + let _call = Call::::create_kitty {}; + let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); + assert_ok!(result); + }); +} + +#[test] +fn system_and_balances_work() { + // This test will just sanity check that we can access `System` and `PalletBalances`. + new_test_ext().execute_with(|| { + // We often need to add some balance to a user to test features which needs tokens. + assert_ok!(PalletBalances::mint_into(&ALICE, 100)); + assert_ok!(PalletBalances::mint_into(&BOB, 100)); + }); +} + +#[test] +fn create_kitty_checks_signed() { + new_test_ext().execute_with(|| { + // The `create_kitty` extrinsic should work when being called by a user. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // The `create_kitty` extrinsic should fail when being called by an unsigned message. + assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); + }) +} + +#[test] +fn create_kitty_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + // Execute our call, and ensure it is successful. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Assert the last event by our blockchain is the `Created` event with the correct owner. + System::assert_last_event(Event::::Created { owner: 1 }.into()); + }) +} + +#[test] +fn count_for_kitties_created_correctly() { + new_test_ext().execute_with(|| { + // Querying storage before anything is set will return `0`. + assert_eq!(CountForKitties::::get(), 0); + // You can `set` the value using an `u32`. + CountForKitties::::set(1337u32); + // You can `put` the value directly with a `u32`. + CountForKitties::::put(1337u32); + }) +} + +#[test] +fn mint_increments_count_for_kitty() { + new_test_ext().execute_with(|| { + // Querying storage before anything is set will return `0`. + assert_eq!(CountForKitties::::get(), 0); + // Call `create_kitty` which will call `mint`. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Now the storage should be `1` + assert_eq!(CountForKitties::::get(), 1); + }) +} + +#[test] +fn mint_errors_when_overflow() { + new_test_ext().execute_with(|| { + // Set the count to the largest value possible. + CountForKitties::::set(u32::MAX); + // `create_kitty` should not succeed because of safe math. + assert_noop!( + PalletKitties::create_kitty(RuntimeOrigin::signed(1)), + Error::::TooManyKitties + ); + }) +} + +#[test] +fn kitties_map_created_correctly() { + new_test_ext().execute_with(|| { + let zero_key = [0u8; 32]; + assert!(!Kitties::::contains_key(zero_key)); + Kitties::::insert(zero_key, DEFAULT_KITTY); + assert!(Kitties::::contains_key(zero_key)); + }) +} + +#[test] +fn create_kitty_adds_to_map() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_eq!(Kitties::::iter().count(), 1); + }) +} + +#[test] +fn cannot_mint_duplicate_kitty() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); + assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); + }) +} + +#[test] +fn kitty_struct_has_expected_traits() { + new_test_ext().execute_with(|| { + let kitty = DEFAULT_KITTY; + let bytes = kitty.encode(); + let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); + assert!(Kitty::::max_encoded_len() > 0); + let _info = Kitty::::type_info(); + }) +} + +#[test] +fn mint_stores_owner_in_kitty() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::mint(1337, [42u8; 32])); + let kitty = Kitties::::get([42u8; 32]).unwrap(); + assert_eq!(kitty.owner, 1337); + assert_eq!(kitty.dna, [42u8; 32]); + }) +} + +#[test] +fn create_kitty_makes_unique_kitties() { + new_test_ext().execute_with(|| { + // Two calls to `create_kitty` should work. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); + // And should result in two kitties in our system. + assert_eq!(CountForKitties::::get(), 2); + assert_eq!(Kitties::::iter().count(), 2); + }) +} + +#[test] +fn kitties_owned_created_correctly() { + new_test_ext().execute_with(|| { + // Initially users have no kitties owned. + assert_eq!(KittiesOwned::::get(1).len(), 0); + // Let's create two kitties. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Now they should have two kitties owned. + assert_eq!(KittiesOwned::::get(1).len(), 2); + }); +} + +#[test] +fn cannot_own_too_many_kitties() { + new_test_ext().execute_with(|| { + // If your max owned is different than 100, you will need to update this. + for _ in 0..100 { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + } + assert_noop!( + PalletKitties::create_kitty(RuntimeOrigin::signed(1)), + Error::::TooManyOwned + ); + }); +} + +#[test] +fn transfer_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + // Create a kitty to transfer + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Get the kitty id. + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); + System::assert_last_event( + Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), + ); + }); +} + +#[test] +fn transfer_logic_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Starting state looks good. + let kitty = &Kitties::::iter_values().collect::>()[0]; + let kitty_id = kitty.dna; + assert_eq!(kitty.owner, ALICE); + assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); + assert_eq!(KittiesOwned::::get(BOB), vec![]); + // Cannot transfer to yourself. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), + Error::::TransferToSelf + ); + // Cannot transfer a non-existent kitty. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), + Error::::NoKitty + ); + // Cannot transfer kitty you do not own. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), + Error::::NotOwner + ); + // Transfer should work when parameters are right. + assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); + // Storage is updated correctly. + assert_eq!(KittiesOwned::::get(ALICE), vec![]); + assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); + let kitty = &Kitties::::iter_values().collect::>()[0]; + assert_eq!(kitty.owner, BOB); + }); +} + +#[test] +fn native_balance_associated_type_works() { + new_test_ext().execute_with(|| { + assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); + assert_eq!( + <::NativeBalance as Inspect<_>>::total_balance(&ALICE), + 1337 + ); + }); +} + +#[test] +fn balance_of_type_works() { + // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. + let _example_balance: BalanceOf = 1337u64; +} + +#[test] +fn set_price_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + // Assert the last event is `PriceSet` event with the correct information. + System::assert_last_event( + Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), + ); + }) +} + +#[test] +fn set_price_logic_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty = &Kitties::::iter_values().collect::>()[0]; + assert_eq!(kitty.price, None); + let kitty_id = kitty.dna; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + let kitty = Kitties::::get(kitty_id).unwrap(); + assert_eq!(kitty.price, Some(1337)); + }) +} + +#[test] +fn do_buy_kitty_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); + assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); + // Assert the last event by our blockchain is the `Created` event with the correct owner. + System::assert_last_event( + Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), + ); + }) +} diff --git a/src/33/template/template.diff b/src/33/template/template.diff new file mode 100644 index 00000000..f5546ccd --- /dev/null +++ b/src/33/template/template.diff @@ -0,0 +1,49 @@ +diff --git a/src/impls.rs b/src/impls.rs +index 03cf1f18..c7a34b2c 100644 +--- a/src/impls.rs ++++ b/src/impls.rs +@@ -1,6 +1,7 @@ + use super::*; + use frame::prelude::*; + use frame::primitives::BlakeTwo256; ++/* 🚧 TODO 🚧: Import `frame::traits::tokens::Preservation`. */ + use frame::traits::Hash; + + // Learn about internal functions. +@@ -78,6 +79,21 @@ impl Pallet { + kitty_id: [u8; 32], + price: BalanceOf, + ) -> DispatchResult { ++ /* 🚧 TODO 🚧: Sanity check that the purchase is allowed: ++ - Get `kitty` from `Kitties` using `kitty_id`, `ok_or` return `Error::::NoKitty`. ++ - Get the `real_price` from `kitty.price`, `ok_or` return `Error::::NotForSale`. ++ - `ensure!` that `price` is greater or equal to `real_price`, else `Error::::MaxPriceTooLow`. ++ */ ++ ++ /* 🚧 TODO 🚧: Execute the transfers: ++ - Use `T::NativeBalance` to `transfer` from the `buyer` to the `kitty.owner`. ++ - The amount transferred should be the `real_price`. ++ - Use `Preservation::Preserve` to ensure the buyer account stays alive. ++ - Use `Self::do_transfer` to transfer from the `kitty.owner` to the `buyer` with `kitty_id`. ++ - Remember to propagate up all results from these functions with `?`. ++ */ ++ ++ /* 🚧 TODO 🚧: Update the event to use the `real_price` in the `Event`. */ + Self::deposit_event(Event::::Sold { buyer, kitty_id, price }); + Ok(()) + } +diff --git a/src/lib.rs b/src/lib.rs +index 4a8d2b1b..f465b493 100644 +--- a/src/lib.rs ++++ b/src/lib.rs +@@ -67,6 +67,10 @@ pub mod pallet { + TransferToSelf, + NoKitty, + NotOwner, ++ /* 🚧 TODO 🚧: Add `Errors` needed for `do_buy_kitty`: ++ - `NotForSale`: for when the Kitty has a price set to `None`. ++ - `MaxPriceTooLow`: for when the price offered by the buyer is too low. ++ */ + } + + #[pallet::call] diff --git a/src/34/README.md b/src/34/README.md new file mode 100644 index 00000000..df81bc46 --- /dev/null +++ b/src/34/README.md @@ -0,0 +1,6 @@ + +
+ +{{#include ./source/README.md}} + +
diff --git a/src/34/source/.gitignore b/src/34/source/.gitignore new file mode 100644 index 00000000..c537f0b6 --- /dev/null +++ b/src/34/source/.gitignore @@ -0,0 +1,5 @@ +target/ +.vscode +chain_spec.json +book +steps diff --git a/src/34/source/Cargo.lock b/src/34/source/Cargo.lock new file mode 100644 index 00000000..6bb5e408 --- /dev/null +++ b/src/34/source/Cargo.lock @@ -0,0 +1,3624 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "Inflector" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3" +dependencies = [ + "lazy_static", + "regex", +] + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array", +] + +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "getrandom", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" + +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + +[[package]] +name = "aquamarine" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21cc1548309245035eb18aa7f0967da6bc65587005170c56e6ef2788a4cf3f4e" +dependencies = [ + "include_dir", + "itertools 0.10.5", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "ark-bls12-377" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb00293ba84f51ce3bd026bd0de55899c4e68f0a39a5728cebae3a73ffdc0a4f" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-std", +] + +[[package]] +name = "ark-bls12-381" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" +dependencies = [ + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-std", +] + +[[package]] +name = "ark-ec" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "defd9a439d56ac24968cca0571f598a61bc8c55f71d50a89cda591cb750670ba" +dependencies = [ + "ark-ff", + "ark-poly", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", + "itertools 0.10.5", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm", + "ark-ff-macros", + "ark-serialize", + "ark-std", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint", + "num-traits", + "paste", + "rustc_version", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-poly" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d320bfc44ee185d899ccbadfa8bc31aab923ce1558716e1997a1e74057fe86bf" +dependencies = [ + "ark-ff", + "ark-serialize", + "ark-std", + "derivative", + "hashbrown 0.13.2", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-serialize-derive", + "ark-std", + "digest 0.10.7", + "num-bigint", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae3281bc6d0fd7e549af32b52511e1302185bd688fd3359fa36423346ff682ea" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand", +] + +[[package]] +name = "array-bytes" +version = "6.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5dde061bd34119e902bbb2d9b90c5692635cf59fb91d582c2b68043f1b8293" + +[[package]] +name = "arrayref" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" + +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + +[[package]] +name = "async-trait" +version = "0.1.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitcoin-internals" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9425c3bf7089c983facbae04de54513cce73b41c7f9ff8c845b54e7bc64ebbfb" + +[[package]] +name = "bitcoin_hashes" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1930a4dabfebb8d7d9992db18ebe3ae2876f0a305fab206fd168df931ede293b" +dependencies = [ + "bitcoin-internals", + "hex-conservative", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bounded-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d32385ecb91a31bddaf908e8dcf4a15aef1bcd3913cc03ebfad02ff6d568abc1" +dependencies = [ + "log", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "bytemuck" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a12916984aab3fa6e39d655a33e09c0071eb36d6ab3aea5c2d78551f1df6d952" + +[[package]] +name = "cc" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f" + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "common-path" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101" + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const-random" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87e00182fe74b066627d63b85fd550ac2998d4b0bd86bfed477a0ae4c7c71359" +dependencies = [ + "const-random-macro", +] + +[[package]] +name = "const-random-macro" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9d839f2a20b0aee515dc581a6172f2321f96cab76c1a38a4c584a194955390e" +dependencies = [ + "getrandom", + "once_cell", + "tiny-keccak", +] + +[[package]] +name = "constant_time_eq" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" + +[[package]] +name = "constcat" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd7e35aee659887cbfb97aaf227ac12cad1a9d7c71e55ff3376839ed4e282d08" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "rand_core", + "typenum", +] + +[[package]] +name = "crypto-mac" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b584a330336237c1eecd3e94266efb216c56ed91225d634cb2991c5f3fd1aeab" +dependencies = [ + "generic-array", + "subtle", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-syn-parse" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d65d7ce8132b7c0e54497a4d9a55a1c2a0912a0d786cf894472ba818fba45762" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "docify" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a2f138ad521dc4a2ced1a4576148a6a610b4c5923933b062a263130a6802ce" +dependencies = [ + "docify_macros", +] + +[[package]] +name = "docify_macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a081e51fb188742f5a7a1164ad752121abcb22874b21e2c3b0dd040c515fdad" +dependencies = [ + "common-path", + "derive-syn-parse", + "once_cell", + "proc-macro2", + "quote", + "regex", + "syn 2.0.71", + "termcolor", + "toml", + "walkdir", +] + +[[package]] +name = "dyn-clonable" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e9232f0e607a262ceb9bd5141a3dfb3e4db6994b31989bbfd845878cba59fd4" +dependencies = [ + "dyn-clonable-impl", + "dyn-clone", +] + +[[package]] +name = "dyn-clonable-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a3daa8e81a3963a60642bcc1f90a670680bd4a77535faa384e9d1c79d620871" +dependencies = [ + "curve25519-dalek", + "ed25519", + "serde", + "sha2 0.10.8", + "subtle", + "zeroize", +] + +[[package]] +name = "ed25519-zebra" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d9ce6874da5d4415896cd45ffbc4d1cfc0c4f9c079427bd870742c30f2f65a9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "hashbrown 0.14.5", + "hex", + "rand_core", + "sha2 0.10.8", + "zeroize", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff", + "generic-array", + "group", + "pkcs8", + "rand_core", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "environmental" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48c92028aaa870e83d51c64e5d4e0b6981b360c522198c23959f219a4e1b15b" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "expander" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2c470c71d91ecbd179935b24170459e926382eaaa86b590b78814e180d8a8e2" +dependencies = [ + "blake2", + "file-guard", + "fs-err", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "ff" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +dependencies = [ + "rand_core", + "subtle", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "file-guard" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21ef72acf95ec3d7dbf61275be556299490a245f017cf084bd23b4f68cf9407c" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "finality-grandpa" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36530797b9bf31cd4ff126dcfee8170f86b00cfdcea3269d73133cc0415945c3" +dependencies = [ + "either", + "futures", + "futures-timer", + "log", + "num-traits", + "parity-scale-codec", + "parking_lot", + "scale-info", +] + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "frame-benchmarking" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01bdd47c2d541b38bd892da647d1e972c9d85b4ecd7094ad64f7600175da54d" +dependencies = [ + "frame-support", + "frame-support-procedural", + "frame-system", + "linregress", + "log", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-io", + "sp-runtime", + "sp-runtime-interface", + "sp-storage", + "static_assertions", +] + +[[package]] +name = "frame-executive" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c365bf3879de25bbee28e9584096955a02fbe8d7e7624e10675800317f1cee5b" +dependencies = [ + "aquamarine", + "frame-support", + "frame-system", + "frame-try-runtime", + "log", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-tracing", +] + +[[package]] +name = "frame-metadata" +version = "16.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cf1549fba25a6fcac22785b61698317d958e96cac72a59102ea45b9ae64692" +dependencies = [ + "cfg-if", + "parity-scale-codec", + "scale-info", + "serde", +] + +[[package]] +name = "frame-support" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e44af69fa61bc5005ffe0339e198957e77f0f255704a9bee720da18a733e3dc" +dependencies = [ + "aquamarine", + "array-bytes", + "bitflags 1.3.2", + "docify", + "environmental", + "frame-metadata", + "frame-support-procedural", + "impl-trait-for-tuples", + "k256", + "log", + "macro_magic", + "parity-scale-codec", + "paste", + "scale-info", + "serde", + "serde_json", + "smallvec", + "sp-api", + "sp-arithmetic", + "sp-core", + "sp-crypto-hashing-proc-macro", + "sp-debug-derive", + "sp-genesis-builder", + "sp-inherents", + "sp-io", + "sp-metadata-ir", + "sp-runtime", + "sp-staking", + "sp-state-machine", + "sp-std", + "sp-tracing", + "sp-weights", + "static_assertions", + "tt-call", +] + +[[package]] +name = "frame-support-procedural" +version = "30.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e8f9b6bc1517a6fcbf0b2377e5c8c6d39f5bb7862b191a59a9992081d63972d" +dependencies = [ + "Inflector", + "cfg-expr", + "derive-syn-parse", + "expander", + "frame-support-procedural-tools", + "itertools 0.11.0", + "macro_magic", + "proc-macro-warning", + "proc-macro2", + "quote", + "sp-crypto-hashing", + "syn 2.0.71", +] + +[[package]] +name = "frame-support-procedural-tools" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bead15a320be1764cdd50458c4cfacb23e0cee65f64f500f8e34136a94c7eeca" +dependencies = [ + "frame-support-procedural-tools-derive", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "frame-support-procedural-tools-derive" +version = "12.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed971c6435503a099bdac99fe4c5bea08981709e5b5a0a8535a1856f48561191" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "frame-system" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c7fa02f8c305496d2ae52edaecdb9d165f11afa965e05686d7d7dd1ce93611" +dependencies = [ + "cfg-if", + "docify", + "frame-support", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", + "sp-version", + "sp-weights", +] + +[[package]] +name = "frame-system-benchmarking" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9693b2a736beb076e673520e1e8dee4fc128b8d35b020ef3e8a4b1b5ad63d9f2" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "frame-system-rpc-runtime-api" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "475c4f8604ba7e4f05cd2c881ba71105093e638b9591ec71a8db14a64b3b4ec3" +dependencies = [ + "docify", + "parity-scale-codec", + "sp-api", +] + +[[package]] +name = "frame-try-runtime" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c811a5a1f5429c7fb5ebbf6cf9502d8f9b673fd395c12cf46c44a30a7daf0e" +dependencies = [ + "frame-support", + "parity-scale-codec", + "sp-api", + "sp-runtime", +] + +[[package]] +name = "fs-err" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88a41f105fe1d5b6b34b2055e3dc59bb79b46b48b2040b9e6c7b4b5de097aa41" +dependencies = [ + "autocfg", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", + "num_cpus", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-timer" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom_or_panic" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ea1015b5a70616b688dc230cfe50c8af89d972cb132d5a622814d29773b10b9" +dependencies = [ + "rand", + "rand_core", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + +[[package]] +name = "hash-db" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e7d7786361d7425ae2fe4f9e407eb0efaa0840f5212d109cc018c40c35c6ab4" + +[[package]] +name = "hash256-std-hasher" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c171d55b98633f4ed3860808f004099b36c1cc29c42cfc53aa8591b21efcf2" +dependencies = [ + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43a3c133739dddd0d2990f9a4bdf8eb4b21ef50e4851ca85ab661199821d510e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-conservative" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" + +[[package]] +name = "hmac" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "126888268dcc288495a26bf004b38c5fdbb31682f992c84ceb046a1f0fe38840" +dependencies = [ + "crypto-mac", + "digest 0.9.0", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "hmac-drbg" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17ea0a1394df5b6574da6e0c1ade9e78868c9fb0a4e5ef4428e32da4676b85b1" +dependencies = [ + "digest 0.9.0", + "generic-array", + "hmac 0.8.1", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-serde" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc88fc67028ae3db0c853baa36269d398d5f45b6982f95549ff5def78c935cd" +dependencies = [ + "serde", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "include_dir" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "923d117408f1e49d914f1a379a309cffe4f18c05cf4e3d12e613a15fc81bd0dd" +dependencies = [ + "include_dir_macros", +] + +[[package]] +name = "include_dir_macros" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cab85a7ed0bd5f0e76d93846e0147172bed2e2d3f859bcc33a8d9699cad1a75" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "integer-sqrt" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "276ec31bcb4a9ee45f58bec6f9ec700ae4cf4f4f8f2fa7e06cb406bd5ffdd770" +dependencies = [ + "num-traits", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "k256" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956ff9b67e26e1a6a866cb758f12c6f8746208489e3e4a4b5580802f2f0a587b" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "once_cell", + "serdect", + "sha2 0.10.8", +] + +[[package]] +name = "keccak" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "libsecp256k1" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95b09eff1b35ed3b33b877ced3a691fc7a481919c7e29c53c906226fcf55e2a1" +dependencies = [ + "arrayref", + "base64", + "digest 0.9.0", + "hmac-drbg", + "libsecp256k1-core", + "libsecp256k1-gen-ecmult", + "libsecp256k1-gen-genmult", + "rand", + "serde", + "sha2 0.9.9", + "typenum", +] + +[[package]] +name = "libsecp256k1-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5be9b9bb642d8522a44d533eab56c16c738301965504753b03ad1de3425d5451" +dependencies = [ + "crunchy", + "digest 0.9.0", + "subtle", +] + +[[package]] +name = "libsecp256k1-gen-ecmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3038c808c55c87e8a172643a7d87187fc6c4174468159cb3090659d55bcb4809" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "libsecp256k1-gen-genmult" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3db8d6ba2cec9eacc40e6e8ccc98931840301f1006e95647ceb2dd5c3aa06f7c" +dependencies = [ + "libsecp256k1-core", +] + +[[package]] +name = "linregress" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4de04dcecc58d366391f9920245b85ffa684558a5ef6e7736e754347c3aea9c2" +dependencies = [ + "nalgebra", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "macro_magic" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc33f9f0351468d26fbc53d9ce00a096c8522ecb42f19b50f34f2c422f76d21d" +dependencies = [ + "macro_magic_core", + "macro_magic_macros", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "macro_magic_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1687dc887e42f352865a393acae7cf79d98fab6351cde1f58e9e057da89bf150" +dependencies = [ + "const-random", + "derive-syn-parse", + "macro_magic_core_macros", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "macro_magic_core_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b02abfe41815b5bd98dbd4260173db2c116dda171dc0fe7838cb206333b83308" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "macro_magic_macros" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ea28ee64b88876bf45277ed9a5817c1817df061a74f2b988971a12570e5869" +dependencies = [ + "macro_magic_core", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memory-db" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808b50db46293432a45e63bc15ea51e0ab4c0a1647b8eb114e31a3e698dd6fbe" +dependencies = [ + "hash-db", +] + +[[package]] +name = "merlin" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58c38e2799fc0978b65dfff8023ec7843e2330bb462f19198840b34b6582397d" +dependencies = [ + "byteorder", + "keccak", + "rand_core", + "zeroize", +] + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "nalgebra" +version = "0.32.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5c17de023a86f59ed79891b2e5d5a94c705dbe904a5b5c9c952ea6221b03e4" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-format" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" +dependencies = [ + "arrayvec", + "itoa", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pallet-balances" +version = "39.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c6945b078919acb14d126490e4b0973a688568b30142476ca69c6df2bed27ad" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-support", + "frame-system", + "log", + "parity-scale-codec", + "scale-info", + "sp-runtime", +] + +[[package]] +name = "pallet-kitties" +version = "0.1.0" +dependencies = [ + "frame-system", + "pallet-balances", + "parity-scale-codec", + "polkadot-sdk-frame", + "scale-info", +] + +[[package]] +name = "parity-bip39" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e69bf016dc406eff7d53a7d3f7cf1c2e72c82b9088aac1118591e36dd2cd3e9" +dependencies = [ + "bitcoin_hashes", + "rand", + "rand_core", + "serde", + "unicode-normalization", +] + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "parity-wasm" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1ad0aff30c1da14b1254fcb2af73e1fa9a28670e584a626f53a369d0e157304" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pbkdf2" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" +dependencies = [ + "digest 0.10.7", + "password-hash", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "polkadot-sdk-frame" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbdeb15ce08142082461afe1a62c15f7ce10a731d91b203ad6a8dc8d2e4a6a54" +dependencies = [ + "docify", + "frame-benchmarking", + "frame-executive", + "frame-support", + "frame-system", + "frame-system-benchmarking", + "frame-system-rpc-runtime-api", + "frame-try-runtime", + "log", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-arithmetic", + "sp-block-builder", + "sp-consensus-aura", + "sp-consensus-grandpa", + "sp-core", + "sp-inherents", + "sp-io", + "sp-offchain", + "sp-runtime", + "sp-session", + "sp-storage", + "sp-transaction-pool", + "sp-version", +] + +[[package]] +name = "polkavm-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d9428a5cfcc85c5d7b9fc4b6a18c4b802d0173d768182a51cc7751640f08b92" + +[[package]] +name = "polkavm-derive" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae8c4bea6f3e11cd89bb18bcdddac10bd9a24015399bd1c485ad68a985a19606" +dependencies = [ + "polkavm-derive-impl-macro", +] + +[[package]] +name = "polkavm-derive-impl" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c4fdfc49717fb9a196e74a5d28e0bc764eb394a2c803eb11133a31ac996c60c" +dependencies = [ + "polkavm-common", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "polkavm-derive-impl-macro" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ba81f7b5faac81e528eb6158a6f3c9e0bb1008e0ffa19653bc8dea925ecb429" +dependencies = [ + "polkavm-derive-impl", + "syn 2.0.71", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "prettyplease" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +dependencies = [ + "proc-macro2", + "syn 2.0.71", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "impl-serde", + "scale-info", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit 0.21.1", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-warning" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "834da187cfe638ae8abb0203f0b33e5ccdb02a28e7199f2f47b3e2754f50edca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "ref-cast" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf0a6f84d5f1d581da8b41b47ec8600871962f2a528115b542b362d4b744931" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc303e793d3734489387d205e9b186fac9c6cfacedd98cbb2e8a5943595f3e6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "regex" +version = "1.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", + "subtle", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scale-info" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eca070c12893629e2cc820a9761bedf6ce1dcddc9852984d1dc734b8bd9bd024" +dependencies = [ + "bitvec", + "cfg-if", + "derive_more", + "parity-scale-codec", + "scale-info-derive", + "serde", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d35494501194174bda522a32605929eefc9ecf7e0a326c26db1fdd85881eb62" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "schnellru" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9a8ef13a93c54d20580de1e5c413e624e53121d42fc7e2c11d10ef7f8b02367" +dependencies = [ + "ahash", + "cfg-if", + "hashbrown 0.13.2", +] + +[[package]] +name = "schnorrkel" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de18f6d8ba0aad7045f5feae07ec29899c1112584a38509a84ad7b04451eaa0" +dependencies = [ + "aead", + "arrayref", + "arrayvec", + "curve25519-dalek", + "getrandom_or_panic", + "merlin", + "rand_core", + "serde_bytes", + "sha2 0.10.8", + "subtle", + "zeroize", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.28.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" +dependencies = [ + "secp256k1-sys", +] + +[[package]] +name = "secp256k1-sys" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" +dependencies = [ + "cc", +] + +[[package]] +name = "secrecy" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9bd1c54ea06cfd2f6b63219704de0b9b4f72dcc2b8fdef820be6cd799780e91e" +dependencies = [ + "zeroize", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_bytes" +version = "0.11.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core", +] + +[[package]] +name = "simba" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "061507c94fc6ab4ba1c9a0305018408e312e17c041eb63bef8aa726fa33aceae" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + +[[package]] +name = "simple-mermaid" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "620a1d43d70e142b1d46a929af51d44f383db9c7a2ec122de2cd992ccfcf3c18" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "sp-api" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbce492e0482134128b7729ea36f5ef1a9f9b4de2d48ff8dde7b5e464e28ce75" +dependencies = [ + "docify", + "hash-db", + "log", + "parity-scale-codec", + "scale-info", + "sp-api-proc-macro", + "sp-core", + "sp-externalities", + "sp-metadata-ir", + "sp-runtime", + "sp-runtime-interface", + "sp-state-machine", + "sp-trie", + "sp-version", + "thiserror", +] + +[[package]] +name = "sp-api-proc-macro" +version = "20.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9aadf9e97e694f0e343978aa632938c5de309cbcc8afed4136cb71596737278" +dependencies = [ + "Inflector", + "blake2", + "expander", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "sp-application-crypto" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8133012faa5f75b2f0b1619d9f720c1424ac477152c143e5f7dbde2fe1a958" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-io", +] + +[[package]] +name = "sp-arithmetic" +version = "26.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46d0d0a4c591c421d3231ddd5e27d828618c24456d51445d21a1f79fcee97c23" +dependencies = [ + "docify", + "integer-sqrt", + "num-traits", + "parity-scale-codec", + "scale-info", + "serde", + "sp-std", + "static_assertions", +] + +[[package]] +name = "sp-block-builder" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74738809461e3d4bd707b5b94e0e0c064a623a74a6a8fe5c98514417a02858dd" +dependencies = [ + "sp-api", + "sp-inherents", + "sp-runtime", +] + +[[package]] +name = "sp-consensus-aura" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a8faaa05bbcb9c41f0cc535c4c1315abf6df472b53eae018678d1b4d811ac47" +dependencies = [ + "async-trait", + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-application-crypto", + "sp-consensus-slots", + "sp-inherents", + "sp-runtime", + "sp-timestamp", +] + +[[package]] +name = "sp-consensus-grandpa" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "587b791efe6c5f18e09dbbaf1ece0ee7b5fe51602c233e7151a3676b0de0260b" +dependencies = [ + "finality-grandpa", + "log", + "parity-scale-codec", + "scale-info", + "serde", + "sp-api", + "sp-application-crypto", + "sp-core", + "sp-keystore", + "sp-runtime", +] + +[[package]] +name = "sp-consensus-slots" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5211d11b84d8c8d2674fed81503ddad385782b50c7f60f5e3551d7f2dc8098f" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde", + "sp-timestamp", +] + +[[package]] +name = "sp-core" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c961a5e33fb2962fa775c044ceba43df9c6f917e2c35d63bfe23738468fa76a7" +dependencies = [ + "array-bytes", + "bitflags 1.3.2", + "blake2", + "bounded-collections", + "bs58", + "dyn-clonable", + "ed25519-zebra", + "futures", + "hash-db", + "hash256-std-hasher", + "impl-serde", + "itertools 0.11.0", + "k256", + "libsecp256k1", + "log", + "merlin", + "parity-bip39", + "parity-scale-codec", + "parking_lot", + "paste", + "primitive-types", + "rand", + "scale-info", + "schnorrkel", + "secp256k1", + "secrecy", + "serde", + "sp-crypto-hashing", + "sp-debug-derive", + "sp-externalities", + "sp-runtime-interface", + "sp-std", + "sp-storage", + "ss58-registry", + "substrate-bip39", + "thiserror", + "tracing", + "w3f-bls", + "zeroize", +] + +[[package]] +name = "sp-crypto-hashing" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9927a7f81334ed5b8a98a4a978c81324d12bd9713ec76b5c68fd410174c5eb" +dependencies = [ + "blake2b_simd", + "byteorder", + "digest 0.10.7", + "sha2 0.10.8", + "sha3", + "twox-hash", +] + +[[package]] +name = "sp-crypto-hashing-proc-macro" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b85d0f1f1e44bd8617eb2a48203ee854981229e3e79e6f468c7175d5fd37489b" +dependencies = [ + "quote", + "sp-crypto-hashing", + "syn 2.0.71", +] + +[[package]] +name = "sp-debug-derive" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d09fa0a5f7299fb81ee25ae3853d26200f7a348148aed6de76be905c007dbe" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "sp-externalities" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a904407d61cb94228c71b55a9d3708e9d6558991f9e83bd42bd91df37a159d30" +dependencies = [ + "environmental", + "parity-scale-codec", + "sp-storage", +] + +[[package]] +name = "sp-genesis-builder" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a646ed222fd86d5680faa4a8967980eb32f644cae6c8523e1c689a6deda3e8" +dependencies = [ + "parity-scale-codec", + "scale-info", + "serde_json", + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-inherents" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afffbddc380d99a90c459ba1554bbbc01d62e892de9f1485af6940b89c4c0d57" +dependencies = [ + "async-trait", + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sp-io" +version = "38.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ef7eb561bb4839cc8424ce58c5ea236cbcca83f26fcc0426d8decfe8aa97d4" +dependencies = [ + "bytes", + "docify", + "ed25519-dalek", + "libsecp256k1", + "log", + "parity-scale-codec", + "polkavm-derive", + "rustversion", + "secp256k1", + "sp-core", + "sp-crypto-hashing", + "sp-externalities", + "sp-keystore", + "sp-runtime-interface", + "sp-state-machine", + "sp-tracing", + "sp-trie", + "tracing", + "tracing-core", +] + +[[package]] +name = "sp-keystore" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0248b4d784cb4a01472276928977121fa39d977a5bb24793b6b15e64b046df42" +dependencies = [ + "parity-scale-codec", + "parking_lot", + "sp-core", + "sp-externalities", +] + +[[package]] +name = "sp-metadata-ir" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a616fa51350b35326682a472ee8e6ba742fdacb18babac38ecd46b3e05ead869" +dependencies = [ + "frame-metadata", + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "sp-offchain" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d9de237d72ecffd07f90826eef18360208b16d8de939d54e61591fac0fcbf99" +dependencies = [ + "sp-api", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sp-panic-handler" +version = "13.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8f5a17a0a11de029a8b811cb6e8b32ce7e02183cc04a3e965c383246798c416" +dependencies = [ + "backtrace", + "lazy_static", + "regex", +] + +[[package]] +name = "sp-runtime" +version = "39.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f23be7c79a85581029676a73265c107c5469157e3444c8c640fdbaa8bfed0" +dependencies = [ + "docify", + "either", + "hash256-std-hasher", + "impl-trait-for-tuples", + "log", + "num-traits", + "parity-scale-codec", + "paste", + "rand", + "scale-info", + "serde", + "simple-mermaid", + "sp-application-crypto", + "sp-arithmetic", + "sp-core", + "sp-io", + "sp-std", + "sp-weights", + "tracing", +] + +[[package]] +name = "sp-runtime-interface" +version = "28.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985eb981f40c689c6a0012c937b68ed58dabb4341d06f2dfe4dfd5ed72fa4017" +dependencies = [ + "bytes", + "impl-trait-for-tuples", + "parity-scale-codec", + "polkavm-derive", + "primitive-types", + "sp-externalities", + "sp-runtime-interface-proc-macro", + "sp-std", + "sp-storage", + "sp-tracing", + "sp-wasm-interface", + "static_assertions", +] + +[[package]] +name = "sp-runtime-interface-proc-macro" +version = "18.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0195f32c628fee3ce1dfbbf2e7e52a30ea85f3589da9fe62a8b816d70fc06294" +dependencies = [ + "Inflector", + "expander", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "sp-session" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00a3a307fedc423fb8cd2a7726a3bbb99014f1b4b52f26153993e2aae3338fe6" +dependencies = [ + "parity-scale-codec", + "scale-info", + "sp-api", + "sp-core", + "sp-keystore", + "sp-runtime", + "sp-staking", +] + +[[package]] +name = "sp-staking" +version = "36.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a73eedb4b85f4cd420d31764827546aa22f82ce1646d0fd258993d051de7a90" +dependencies = [ + "impl-trait-for-tuples", + "parity-scale-codec", + "scale-info", + "serde", + "sp-core", + "sp-runtime", +] + +[[package]] +name = "sp-state-machine" +version = "0.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "930104d6ae882626e8880d9b1578da9300655d337a3ffb45e130c608b6c89660" +dependencies = [ + "hash-db", + "log", + "parity-scale-codec", + "parking_lot", + "rand", + "smallvec", + "sp-core", + "sp-externalities", + "sp-panic-handler", + "sp-trie", + "thiserror", + "tracing", + "trie-db", +] + +[[package]] +name = "sp-std" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f8ee986414b0a9ad741776762f4083cd3a5128449b982a3919c4df36874834" + +[[package]] +name = "sp-storage" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c82989b3a4979a7e1ad848aad9f5d0b4388f1f454cc131766526601ab9e8f8" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "ref-cast", + "serde", + "sp-debug-derive", +] + +[[package]] +name = "sp-timestamp" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72a1cb4df653d62ccc0dbce1db45d1c9443ec60247ee9576962d24da4c9c6f07" +dependencies = [ + "async-trait", + "parity-scale-codec", + "sp-inherents", + "sp-runtime", + "thiserror", +] + +[[package]] +name = "sp-tracing" +version = "17.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf641a1d17268c8fcfdb8e0fa51a79c2d4222f4cfda5f3944dbdbc384dced8d5" +dependencies = [ + "parity-scale-codec", + "tracing", + "tracing-core", + "tracing-subscriber", +] + +[[package]] +name = "sp-transaction-pool" +version = "34.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4bf251059485a7dd38fe4afeda8792983511cc47f342ff4695e2dcae6b5247" +dependencies = [ + "sp-api", + "sp-runtime", +] + +[[package]] +name = "sp-trie" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6282aef9f4b6ecd95a67a45bcdb67a71f4a4155c09a53c10add4ffe823db18cd" +dependencies = [ + "ahash", + "hash-db", + "lazy_static", + "memory-db", + "nohash-hasher", + "parity-scale-codec", + "parking_lot", + "rand", + "scale-info", + "schnellru", + "sp-core", + "sp-externalities", + "thiserror", + "tracing", + "trie-db", + "trie-root", +] + +[[package]] +name = "sp-version" +version = "37.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d521a405707b5be561367cd3d442ff67588993de24062ce3adefcf8437ee9fe1" +dependencies = [ + "impl-serde", + "parity-scale-codec", + "parity-wasm", + "scale-info", + "serde", + "sp-crypto-hashing-proc-macro", + "sp-runtime", + "sp-std", + "sp-version-proc-macro", + "thiserror", +] + +[[package]] +name = "sp-version-proc-macro" +version = "14.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aee8f6730641a65fcf0c8f9b1e448af4b3bb083d08058b47528188bccc7b7a7" +dependencies = [ + "parity-scale-codec", + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "sp-wasm-interface" +version = "21.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b04b919e150b4736d85089d49327eab65507deb1485eec929af69daa2278eb3" +dependencies = [ + "impl-trait-for-tuples", + "log", + "parity-scale-codec", +] + +[[package]] +name = "sp-weights" +version = "31.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93cdaf72a1dad537bbb130ba4d47307ebe5170405280ed1aa31fa712718a400e" +dependencies = [ + "bounded-collections", + "parity-scale-codec", + "scale-info", + "serde", + "smallvec", + "sp-arithmetic", + "sp-debug-derive", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "ss58-registry" +version = "1.47.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4743ce898933fbff7bbf414f497c459a782d496269644b3d650a398ae6a487ba" +dependencies = [ + "Inflector", + "num-format", + "proc-macro2", + "quote", + "serde", + "serde_json", + "unicode-xid", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "substrate-bip39" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca58ffd742f693dc13d69bdbb2e642ae239e0053f6aab3b104252892f856700a" +dependencies = [ + "hmac 0.12.1", + "pbkdf2", + "schnorrkel", + "sha2 0.10.8", + "zeroize", +] + +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.22.16", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow 0.6.14", +] + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "time", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "trie-db" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c992b4f40c234a074d48a757efeabb1a6be88af84c0c23f7ca158950cb0ae7f" +dependencies = [ + "hash-db", + "log", + "rustc-hex", + "smallvec", +] + +[[package]] +name = "trie-root" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4ed310ef5ab98f5fa467900ed906cb9232dd5376597e00fd4cba2a449d06c0b" +dependencies = [ + "hash-db", +] + +[[package]] +name = "tt-call" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f195fd851901624eee5a58c4bb2b4f06399148fcd0ed336e6f1cb60a9881df" + +[[package]] +name = "twox-hash" +version = "1.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" +dependencies = [ + "cfg-if", + "digest 0.10.7", + "rand", + "static_assertions", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "w3f-bls" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c5da5fa2c6afa2c9158eaa7cd9aee249765eb32b5fb0c63ad8b9e79336a47ec" +dependencies = [ + "ark-bls12-377", + "ark-bls12-381", + "ark-ec", + "ark-ff", + "ark-serialize", + "ark-serialize-derive", + "arrayref", + "constcat", + "digest 0.10.7", + "rand", + "rand_chacha", + "rand_core", + "sha2 0.10.8", + "sha3", + "thiserror", + "zeroize", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wide" +version = "0.7.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901e8597c777fa042e9e245bd56c0dc4418c5db3f845b6ff94fbac732c6a0692" +dependencies = [ + "bytemuck", + "safe_arch", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374ec40a2d767a3c1b4972d9475ecd557356637be906f2cb3f7fe17a6eb5e22f" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] diff --git a/src/34/source/Cargo.toml b/src/34/source/Cargo.toml new file mode 100644 index 00000000..293d4f2a --- /dev/null +++ b/src/34/source/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "pallet-kitties" +version = "0.1.0" +description = "An NFT Marketplace for Kitties" +authors = ["Shawn Tabrizi "] +homepage = "https://www.shawntabrizi.com/substrate-collectables-workshop/" +repository = "https://github.com/shawntabrizi/substrate-collectables-workshop" +edition = "2021" +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.6.12", default-features = false, features = ["derive"] } +scale-info = { version = "2.11.1", default-features = false, features = ["derive"] } +frame = { version = "0.7.0", package = "polkadot-sdk-frame", default-features = false, features = ["experimental", "runtime"] } + +[dev-dependencies] +frame-system = { version = "38.0.0" } +pallet-balances = { version = "39.0.0" } + +[features] +default = [ "std" ] +std = [ "codec/std", "frame/std", "scale-info/std" ] +try-runtime = [] diff --git a/src/33/source/README.md b/src/34/source/README.md similarity index 100% rename from src/33/source/README.md rename to src/34/source/README.md diff --git a/src/34/source/changes.diff b/src/34/source/changes.diff new file mode 100644 index 00000000..e69de29b diff --git a/src/34/source/rustfmt.toml b/src/34/source/rustfmt.toml new file mode 100644 index 00000000..cf9aa251 --- /dev/null +++ b/src/34/source/rustfmt.toml @@ -0,0 +1,24 @@ +# Basic +edition = "2021" +hard_tabs = true +max_width = 100 +use_small_heuristics = "Max" +# Imports +imports_granularity = "Item" +reorder_imports = true +# Consistency +newline_style = "Unix" +# Misc +chain_width = 80 +spaces_around_ranges = false +binop_separator = "Back" +reorder_impl_items = false +match_arm_leading_pipes = "Preserve" +match_arm_blocks = false +match_block_trailing_comma = true +trailing_comma = "Vertical" +trailing_semicolon = false +use_field_init_shorthand = true +# Format comments +comment_width = 100 +wrap_comments = true diff --git a/src/34/source/src/impls.rs b/src/34/source/src/impls.rs new file mode 100644 index 00000000..463894ed --- /dev/null +++ b/src/34/source/src/impls.rs @@ -0,0 +1,92 @@ +use super::*; +use frame::prelude::*; +use frame::primitives::BlakeTwo256; +use frame::traits::tokens::Preservation; +use frame::traits::Hash; + +// Learn about internal functions. +impl Pallet { + // Generates and returns DNA + pub fn gen_dna() -> [u8; 32] { + // Create randomness payload. Multiple kitties can be generated in the same block, + // retaining uniqueness. + let unique_payload = ( + frame_system::Pallet::::parent_hash(), + frame_system::Pallet::::block_number(), + frame_system::Pallet::::extrinsic_index(), + CountForKitties::::get(), + ); + + BlakeTwo256::hash_of(&unique_payload).into() + } + + pub fn mint(owner: T::AccountId, dna: [u8; 32]) -> DispatchResult { + let kitty = Kitty { dna, owner: owner.clone(), price: None }; + // Check if the kitty does not already exist in our storage map + ensure!(!Kitties::::contains_key(dna), Error::::DuplicateKitty); + + let current_count: u32 = CountForKitties::::get(); + let new_count = current_count.checked_add(1).ok_or(Error::::TooManyKitties)?; + + KittiesOwned::::try_append(&owner, dna).map_err(|_| Error::::TooManyOwned)?; + Kitties::::insert(dna, kitty); + CountForKitties::::set(new_count); + + Self::deposit_event(Event::::Created { owner }); + Ok(()) + } + + pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { + ensure!(from != to, Error::::TransferToSelf); + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == from, Error::::NotOwner); + kitty.owner = to.clone(); + kitty.price = None; + + let mut to_owned = KittiesOwned::::get(&to); + to_owned.try_push(kitty_id).map_err(|_| Error::::TooManyOwned)?; + let mut from_owned = KittiesOwned::::get(&from); + if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { + from_owned.swap_remove(ind); + } else { + return Err(Error::::NoKitty.into()) + } + + Kitties::::insert(kitty_id, kitty); + KittiesOwned::::insert(&to, to_owned); + KittiesOwned::::insert(&from, from_owned); + + Self::deposit_event(Event::::Transferred { from, to, kitty_id }); + Ok(()) + } + + pub fn do_set_price( + caller: T::AccountId, + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { + let mut kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + ensure!(kitty.owner == caller, Error::::NotOwner); + kitty.price = new_price; + Kitties::::insert(kitty_id, kitty); + + Self::deposit_event(Event::::PriceSet { owner: caller, kitty_id, new_price }); + Ok(()) + } + + pub fn do_buy_kitty( + buyer: T::AccountId, + kitty_id: [u8; 32], + price: BalanceOf, + ) -> DispatchResult { + let kitty = Kitties::::get(kitty_id).ok_or(Error::::NoKitty)?; + let real_price = kitty.price.ok_or(Error::::NotForSale)?; + ensure!(price >= real_price, Error::::MaxPriceTooLow); + + T::NativeBalance::transfer(&buyer, &kitty.owner, real_price, Preservation::Preserve)?; + Self::do_transfer(kitty.owner, buyer.clone(), kitty_id)?; + + Self::deposit_event(Event::::Sold { buyer, kitty_id, price: real_price }); + Ok(()) + } +} diff --git a/src/34/source/src/lib.rs b/src/34/source/src/lib.rs new file mode 100644 index 00000000..8ca2bdd1 --- /dev/null +++ b/src/34/source/src/lib.rs @@ -0,0 +1,113 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +mod impls; +mod tests; + +use frame::prelude::*; +use frame::traits::fungible::Inspect; +use frame::traits::fungible::Mutate; +pub use pallet::*; + +#[frame::pallet(dev_mode)] +pub mod pallet { + use super::*; + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The Fungible handler for the kitties pallet. + type NativeBalance: Inspect + Mutate; + } + + // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. + pub type BalanceOf = + <::NativeBalance as Inspect<::AccountId>>::Balance; + + #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] + #[scale_info(skip_type_params(T))] + pub struct Kitty { + // Using 32 bytes to represent a kitty DNA + pub dna: [u8; 32], + pub owner: T::AccountId, + pub price: Option>, + } + + #[pallet::storage] + pub(super) type CountForKitties = StorageValue; + + #[pallet::storage] + pub(super) type Kitties = StorageMap>; + + /// Track the kitties owned by each account. + #[pallet::storage] + pub(super) type KittiesOwned = StorageMap< + Key = T::AccountId, + Value = BoundedVec<[u8; 32], ConstU32<100>>, + QueryKind = ValueQuery, + >; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + Created { owner: T::AccountId }, + Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, + PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option> }, + Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf }, + } + + #[pallet::error] + pub enum Error { + TooManyKitties, + DuplicateKitty, + TooManyOwned, + TransferToSelf, + NoKitty, + NotOwner, + NotForSale, + MaxPriceTooLow, + } + + #[pallet::call] + impl Pallet { + pub fn create_kitty(origin: OriginFor) -> DispatchResult { + let who = ensure_signed(origin)?; + let dna = Self::gen_dna(); + Self::mint(who, dna)?; + Ok(()) + } + + pub fn transfer( + origin: OriginFor, + to: T::AccountId, + kitty_id: [u8; 32], + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_transfer(who, to, kitty_id)?; + Ok(()) + } + + pub fn set_price( + origin: OriginFor, + kitty_id: [u8; 32], + new_price: Option>, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_set_price(who, kitty_id, new_price)?; + Ok(()) + } + + pub fn buy_kitty( + origin: OriginFor, + kitty_id: [u8; 32], + max_price: BalanceOf, + ) -> DispatchResult { + let who = ensure_signed(origin)?; + Self::do_buy_kitty(who, kitty_id, max_price)?; + Ok(()) + } + } +} diff --git a/src/34/source/src/tests.rs b/src/34/source/src/tests.rs new file mode 100644 index 00000000..5f8a1953 --- /dev/null +++ b/src/34/source/src/tests.rs @@ -0,0 +1,425 @@ +// Tests for the Kitties Pallet. +// +// Normally this file would be split into two parts: `mock.rs` and `tests.rs`. +// The `mock.rs` file would contain all the setup code for our `TestRuntime`. +// Then `tests.rs` would only have the tests for our pallet. +// However, to minimize the project, these have been combined into this single file. +// +// Learn more about creating tests for Pallets: +// https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/guides/your_first_pallet/index.html + +// This flag tells rust to only run this file when running `cargo test`. +#![cfg(test)] + +use crate as pallet_kitties; +use crate::*; +use frame::deps::frame_support::runtime; +use frame::deps::sp_io; +use frame::runtime::prelude::*; +use frame::testing_prelude::*; +use frame::traits::fungible::*; + +type Balance = u64; +type Block = frame_system::mocking::MockBlock; + +// In our "test runtime", we represent a user `AccountId` with a `u64`. +// This is just a simplification so that we don't need to generate a bunch of proper cryptographic +// public keys when writing tests. It is just easier to say "user 1 transfers to user 2". +// We create the constants `ALICE` and `BOB` to make it clear when we are representing users below. +const ALICE: u64 = 1; +const BOB: u64 = 2; +const DEFAULT_KITTY: Kitty = Kitty { dna: [0u8; 32], owner: 0, price: None }; + +#[runtime] +mod runtime { + #[runtime::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeTask, + RuntimeHoldReason, + RuntimeFreezeReason + )] + #[runtime::runtime] + /// The "test runtime" that represents the state transition function for our blockchain. + /// + /// The runtime is composed of individual modules called "pallets", which you find see below. + /// Each pallet has its own logic and storage, all of which can be combined together. + pub struct TestRuntime; + + /// System: Mandatory system pallet that should always be included in a FRAME runtime. + #[runtime::pallet_index(0)] + pub type System = frame_system::Pallet; + + /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) + #[runtime::pallet_index(1)] + pub type PalletBalances = pallet_balances::Pallet; + + /// PalletKitties: The pallet you are building in this tutorial! + #[runtime::pallet_index(2)] + pub type PalletKitties = pallet_kitties::Pallet; +} + +// Normally `System` would have many more configurations, but you can see that we use some macro +// magic to automatically configure most of the pallet for a "default test configuration". +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} + +// Normally `pallet_balances` would have many more configurations, but you can see that we use some +// macro magic to automatically configure most of the pallet for a "default test configuration". +#[derive_impl(pallet_balances::config_preludes::TestDefaultConfig)] +impl pallet_balances::Config for TestRuntime { + type AccountStore = System; + type Balance = Balance; +} + +// This is the configuration of our Pallet! If you make changes to the pallet's `trait Config`, you +// will also need to update this configuration to represent that. +impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + type NativeBalance = PalletBalances; +} + +// We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` +// It simulates the blockchain database backend for our tests. +// If you forget to include this and try to access your Pallet storage, you will get an error like: +// "`get_version_1` called outside of an Externalities-provided environment." +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} + +#[test] +fn starting_template_is_sane() { + new_test_ext().execute_with(|| { + let event = Event::::Created { owner: ALICE }; + let _runtime_event: RuntimeEvent = event.into(); + let _call = Call::::create_kitty {}; + let result = PalletKitties::create_kitty(RuntimeOrigin::signed(BOB)); + assert_ok!(result); + }); +} + +#[test] +fn system_and_balances_work() { + // This test will just sanity check that we can access `System` and `PalletBalances`. + new_test_ext().execute_with(|| { + // We often need to add some balance to a user to test features which needs tokens. + assert_ok!(PalletBalances::mint_into(&ALICE, 100)); + assert_ok!(PalletBalances::mint_into(&BOB, 100)); + }); +} + +#[test] +fn create_kitty_checks_signed() { + new_test_ext().execute_with(|| { + // The `create_kitty` extrinsic should work when being called by a user. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // The `create_kitty` extrinsic should fail when being called by an unsigned message. + assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); + }) +} + +#[test] +fn create_kitty_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + // Execute our call, and ensure it is successful. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Assert the last event by our blockchain is the `Created` event with the correct owner. + System::assert_last_event(Event::::Created { owner: 1 }.into()); + }) +} + +#[test] +fn count_for_kitties_created_correctly() { + new_test_ext().execute_with(|| { + // Querying storage before anything is set will return `0`. + assert_eq!(CountForKitties::::get(), 0); + // You can `set` the value using an `u32`. + CountForKitties::::set(1337u32); + // You can `put` the value directly with a `u32`. + CountForKitties::::put(1337u32); + }) +} + +#[test] +fn mint_increments_count_for_kitty() { + new_test_ext().execute_with(|| { + // Querying storage before anything is set will return `0`. + assert_eq!(CountForKitties::::get(), 0); + // Call `create_kitty` which will call `mint`. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Now the storage should be `1` + assert_eq!(CountForKitties::::get(), 1); + }) +} + +#[test] +fn mint_errors_when_overflow() { + new_test_ext().execute_with(|| { + // Set the count to the largest value possible. + CountForKitties::::set(u32::MAX); + // `create_kitty` should not succeed because of safe math. + assert_noop!( + PalletKitties::create_kitty(RuntimeOrigin::signed(1)), + Error::::TooManyKitties + ); + }) +} + +#[test] +fn kitties_map_created_correctly() { + new_test_ext().execute_with(|| { + let zero_key = [0u8; 32]; + assert!(!Kitties::::contains_key(zero_key)); + Kitties::::insert(zero_key, DEFAULT_KITTY); + assert!(Kitties::::contains_key(zero_key)); + }) +} + +#[test] +fn create_kitty_adds_to_map() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_eq!(Kitties::::iter().count(), 1); + }) +} + +#[test] +fn cannot_mint_duplicate_kitty() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::mint(ALICE, [0u8; 32])); + assert_noop!(PalletKitties::mint(BOB, [0u8; 32]), Error::::DuplicateKitty); + }) +} + +#[test] +fn kitty_struct_has_expected_traits() { + new_test_ext().execute_with(|| { + let kitty = DEFAULT_KITTY; + let bytes = kitty.encode(); + let _decoded_kitty = Kitty::::decode(&mut &bytes[..]).unwrap(); + assert!(Kitty::::max_encoded_len() > 0); + let _info = Kitty::::type_info(); + }) +} + +#[test] +fn mint_stores_owner_in_kitty() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::mint(1337, [42u8; 32])); + let kitty = Kitties::::get([42u8; 32]).unwrap(); + assert_eq!(kitty.owner, 1337); + assert_eq!(kitty.dna, [42u8; 32]); + }) +} + +#[test] +fn create_kitty_makes_unique_kitties() { + new_test_ext().execute_with(|| { + // Two calls to `create_kitty` should work. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(BOB))); + // And should result in two kitties in our system. + assert_eq!(CountForKitties::::get(), 2); + assert_eq!(Kitties::::iter().count(), 2); + }) +} + +#[test] +fn kitties_owned_created_correctly() { + new_test_ext().execute_with(|| { + // Initially users have no kitties owned. + assert_eq!(KittiesOwned::::get(1).len(), 0); + // Let's create two kitties. + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Now they should have two kitties owned. + assert_eq!(KittiesOwned::::get(1).len(), 2); + }); +} + +#[test] +fn cannot_own_too_many_kitties() { + new_test_ext().execute_with(|| { + // If your max owned is different than 100, you will need to update this. + for _ in 0..100 { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + } + assert_noop!( + PalletKitties::create_kitty(RuntimeOrigin::signed(1)), + Error::::TooManyOwned + ); + }); +} + +#[test] +fn transfer_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + // Create a kitty to transfer + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Get the kitty id. + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); + System::assert_last_event( + Event::::Transferred { from: ALICE, to: BOB, kitty_id }.into(), + ); + }); +} + +#[test] +fn transfer_logic_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + // Starting state looks good. + let kitty = &Kitties::::iter_values().collect::>()[0]; + let kitty_id = kitty.dna; + assert_eq!(kitty.owner, ALICE); + assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); + assert_eq!(KittiesOwned::::get(BOB), vec![]); + // Cannot transfer to yourself. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), + Error::::TransferToSelf + ); + // Cannot transfer a non-existent kitty. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), + Error::::NoKitty + ); + // Cannot transfer kitty you do not own. + assert_noop!( + PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), + Error::::NotOwner + ); + // Transfer should work when parameters are right. + assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); + // Storage is updated correctly. + assert_eq!(KittiesOwned::::get(ALICE), vec![]); + assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); + let kitty = &Kitties::::iter_values().collect::>()[0]; + assert_eq!(kitty.owner, BOB); + }); +} + +#[test] +fn native_balance_associated_type_works() { + new_test_ext().execute_with(|| { + assert_ok!(<::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); + assert_eq!( + <::NativeBalance as Inspect<_>>::total_balance(&ALICE), + 1337 + ); + }); +} + +#[test] +fn balance_of_type_works() { + // Inside our tests, the `BalanceOf` type has a concrete type of `u64`. + let _example_balance: BalanceOf = 1337u64; +} + +#[test] +fn set_price_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + // Assert the last event is `PriceSet` event with the correct information. + System::assert_last_event( + Event::::PriceSet { owner: ALICE, kitty_id, new_price: Some(1337) }.into(), + ); + }) +} + +#[test] +fn set_price_logic_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty = &Kitties::::iter_values().collect::>()[0]; + assert_eq!(kitty.price, None); + let kitty_id = kitty.dna; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + let kitty = Kitties::::get(kitty_id).unwrap(); + assert_eq!(kitty.price, Some(1337)); + }) +} + +#[test] +fn do_buy_kitty_emits_event() { + new_test_ext().execute_with(|| { + // We need to set block number to 1 to view events. + System::set_block_number(1); + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty_id = Kitties::::iter_keys().collect::>()[0]; + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); + assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); + // Assert the last event by our blockchain is the `Created` event with the correct owner. + System::assert_last_event( + Event::::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), + ); + }) +} + +#[test] +fn do_buy_kitty_logic_works() { + new_test_ext().execute_with(|| { + assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); + let kitty = &Kitties::::iter_values().collect::>()[0]; + let kitty_id = kitty.dna; + assert_eq!(kitty.owner, ALICE); + assert_eq!(KittiesOwned::::get(ALICE), vec![kitty_id]); + // Cannot buy kitty which does not exist. + assert_noop!( + PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), [0u8; 32], 1337), + Error::::NoKitty + ); + // Cannot buy kitty which is not for sale. + assert_noop!( + PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), + Error::::NotForSale + ); + assert_ok!(PalletKitties::set_price(RuntimeOrigin::signed(ALICE), kitty_id, Some(1337))); + // Cannot buy kitty for a lower price. + assert_noop!( + PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1336), + Error::::MaxPriceTooLow + ); + // Cannot buy kitty if you don't have the funds. + assert_noop!( + PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337), + frame::arithmetic::ArithmeticError::Underflow + ); + // Cannot buy kitty if it would kill your account (i.e. set your balance to 0). + assert_ok!(PalletBalances::mint_into(&BOB, 1337)); + assert!( + PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337).is_err(), + // TODO: assert_noop on DispatchError::Token(TokenError::NotExpendable) + ); + // When everything is right, it works. + assert_ok!(PalletBalances::mint_into(&BOB, 100_000)); + assert_ok!(PalletKitties::buy_kitty(RuntimeOrigin::signed(BOB), kitty_id, 1337)); + // State is updated correctly. + assert_eq!(KittiesOwned::::get(BOB), vec![kitty_id]); + let kitty = Kitties::::get(kitty_id).unwrap(); + assert_eq!(kitty.owner, BOB); + // Price is reset to `None`. + assert_eq!(kitty.price, None); + // BOB transferred funds to ALICE. + assert_eq!(PalletBalances::balance(&ALICE), 1337); + assert_eq!(PalletBalances::balance(&BOB), 100_000); + }) +} diff --git a/src/4/source/changes.diff b/src/4/source/changes.diff index 737a777f..a0fe3d57 100644 --- a/src/4/source/changes.diff +++ b/src/4/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/lib.rs b/src/lib.rs -index dae4e99..c77966d 100644 +index dae4e999..c77966df 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,11 +6,11 @@ mod tests; diff --git a/src/4/source/src/tests.rs b/src/4/source/src/tests.rs index 6d271d5e..7e6f34da 100644 --- a/src/4/source/src/tests.rs +++ b/src/4/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/5/source/changes.diff b/src/5/source/changes.diff index ecab1d1f..c48dca8c 100644 --- a/src/5/source/changes.diff +++ b/src/5/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/impls.rs b/src/impls.rs -index 03abf99..7454df9 100644 +index 03abf99e..7454df9a 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,6 +1,7 @@ @@ -11,7 +11,7 @@ index 03abf99..7454df9 100644 pub fn mint(owner: T::AccountId) -> DispatchResult { Self::deposit_event(Event::::Created { owner }); diff --git a/src/lib.rs b/src/lib.rs -index c77966d..52544f5 100644 +index c77966df..52544f5b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,7 +10,6 @@ pub use pallet::*; diff --git a/src/5/source/src/tests.rs b/src/5/source/src/tests.rs index 6d271d5e..7e6f34da 100644 --- a/src/5/source/src/tests.rs +++ b/src/5/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/6/source/changes.diff b/src/6/source/changes.diff index 08bbac2a..bd455a27 100644 --- a/src/6/source/changes.diff +++ b/src/6/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/impls.rs b/src/impls.rs -index 7454df9..ecb2e7d 100644 +index 7454df9a..ecb2e7df 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -1,8 +1,8 @@ @@ -13,7 +13,7 @@ index 7454df9..ecb2e7d 100644 Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 52544f5..76a7966 100644 +index 52544f5b..76a79660 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,10 +27,10 @@ pub mod pallet { @@ -29,10 +29,10 @@ index 52544f5..76a7966 100644 Self::mint(who)?; Ok(()) diff --git a/src/tests.rs b/src/tests.rs -index 6d271d5..209464e 100644 +index 7e6f34da..d87be46f 100644 --- a/src/tests.rs +++ b/src/tests.rs -@@ -115,3 +115,13 @@ fn system_and_balances_work() { +@@ -113,3 +113,13 @@ fn system_and_balances_work() { assert_ok!(PalletBalances::mint_into(&BOB, 100)); }); } diff --git a/src/6/source/src/tests.rs b/src/6/source/src/tests.rs index 209464e9..d87be46f 100644 --- a/src/6/source/src/tests.rs +++ b/src/6/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/7/source/changes.diff b/src/7/source/changes.diff index d0d7ca55..ffa1d466 100644 --- a/src/7/source/changes.diff +++ b/src/7/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/impls.rs b/src/impls.rs -index ecb2e7d..03abf99 100644 +index ecb2e7df..03abf99e 100644 --- a/src/impls.rs +++ b/src/impls.rs @@ -2,7 +2,6 @@ use super::*; @@ -11,7 +11,7 @@ index ecb2e7d..03abf99 100644 Self::deposit_event(Event::::Created { owner }); Ok(()) diff --git a/src/lib.rs b/src/lib.rs -index 76a7966..405a6e5 100644 +index 76a79660..405a6e52 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,6 +13,7 @@ pub mod pallet { diff --git a/src/7/source/src/tests.rs b/src/7/source/src/tests.rs index 209464e9..d87be46f 100644 --- a/src/7/source/src/tests.rs +++ b/src/7/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); diff --git a/src/8/README.md b/src/8/README.md index dc47e86b..4c64326e 100644 --- a/src/8/README.md +++ b/src/8/README.md @@ -16,7 +16,6 @@
-
@@ -26,14 +25,6 @@
-
- -```rust -{{#include ./source/src/tests.rs}} -``` - -
-
diff --git a/src/8/source/README.md b/src/8/source/README.md index 90dab91d..45eb2ab0 100644 --- a/src/8/source/README.md +++ b/src/8/source/README.md @@ -1,7 +1,5 @@ # Pallet Events -The last thing we have included in our starting template is a simple event. - When a callable function completes successfully, there is often some metadata you would like to expose to the outside world about what exactly happened during the execution. Events allow Pallets to express that something has happened, and allows off-chain systems like indexers or block explorers to track certain state transitions. @@ -61,18 +59,3 @@ Self::deposit_event(Event::::Created { owner }); ``` As you see in our starting code. - -## Tests - -Don't forget to update your `tests.rs` file to include the test provided in this step. - -It shows how you can: - -- Set the blocknumber of your blockchain inside your tests. -- Call an extrinsic in your pallet from an `AccountId` of your choice. -- Check the extrinsic call completed `Ok(())`. -- Get the last event deposited into `System`. -- Check that last event matches the event you would expect from your pallet. - -From this point forward, every step where you write some code will include new tests or modify existing tests. -Make sure to keep updating your `tests.rs` file throughout the tutorial. diff --git a/src/8/source/changes.diff b/src/8/source/changes.diff index 62284ce9..0f80b0fe 100644 --- a/src/8/source/changes.diff +++ b/src/8/source/changes.diff @@ -1,5 +1,5 @@ diff --git a/src/lib.rs b/src/lib.rs -index 405a6e5..a52896e 100644 +index 405a6e52..a52896ed 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -13,12 +13,12 @@ pub mod pallet { @@ -16,23 +16,3 @@ index 405a6e5..a52896e 100644 #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { -diff --git a/src/tests.rs b/src/tests.rs -index 209464e..8baa63c 100644 ---- a/src/tests.rs -+++ b/src/tests.rs -@@ -125,3 +125,15 @@ fn create_kitty_checks_signed() { - assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); - }) - } -+ -+#[test] -+fn create_kitty_emits_event() { -+ new_test_ext().execute_with(|| { -+ // We need to set block number to 1 to view events. -+ System::set_block_number(1); -+ // Execute our call, and ensure it is successful. -+ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); -+ // Assert the last event by our blockchain is the `Created` event with the correct owner. -+ System::assert_last_event(Event::::Created { owner: 1 }.into()); -+ }) -+} diff --git a/src/8/source/src/tests.rs b/src/8/source/src/tests.rs index 8baa63c6..d87be46f 100644 --- a/src/8/source/src/tests.rs +++ b/src/8/source/src/tests.rs @@ -49,15 +49,15 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } // Normally `System` would have many more configurations, but you can see that we use some macro @@ -108,8 +108,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -125,15 +123,3 @@ fn create_kitty_checks_signed() { assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); }) } - -#[test] -fn create_kitty_emits_event() { - new_test_ext().execute_with(|| { - // We need to set block number to 1 to view events. - System::set_block_number(1); - // Execute our call, and ensure it is successful. - assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); - // Assert the last event by our blockchain is the `Created` event with the correct owner. - System::assert_last_event(Event::::Created { owner: 1 }.into()); - }) -} diff --git a/src/9/README.md b/src/9/README.md index df81bc46..c9f5257f 100644 --- a/src/9/README.md +++ b/src/9/README.md @@ -1,6 +1,49 @@ -
+
+
{{#include ./source/README.md}}
+
+ +
+ + +
+ +
+ +
+ +
+
+ +```rust +{{#include ./source/src/tests.rs}} +``` + +
+ + + +
+ +
+ + +
+ +
+
+ +```diff +{{#include ./source/changes.diff}} +``` + +
+ +
+ +
+
diff --git a/src/9/source/README.md b/src/9/source/README.md index 4bbf4a56..7e5d9169 100644 --- a/src/9/source/README.md +++ b/src/9/source/README.md @@ -1,5 +1,264 @@ -# Storage Basics +# Runtime and Tests -Now that we have covered the basics of Pallets and gone through all of the template code, we can start writing some code ourselves. +The last thing we will cover in this section is a small dive into constructing a blockchain runtime and writing tests. -In this section you will learn the basics of creating and using storage in your Pallet, including creating and using storage values and storage maps. +Our pallet could be dependant on many "external" factors: + +- The current block number. +- Hooks when new blocks are built. +- Other pallets, for example to manage the blockchain balance. +- Specific configuration of your blockchain. +- etc... + +All of these things are managed outside of our pallet, thus to write tests for a pallet, we must create a "test blockchain" where we would include and use the pallet. + +A whole tutorial could be written just about configuring a runtime and writing tests, but that would be too much for this tutorial. Instead, we will just go over the basics, and hopefully in the near future, we can make time to write a dedicated tutorial for this and add a link to that here. + +The content on this page is not super relevant for the tutorial, so if you choose to skip this section, or come back to it later, you won't miss much. But definitely you will want to come back to it later, as you can't become a good Polkadot SDK developer without writing tests! + +## Constructing a Runtime + +As we briefly covered earlier, the runtime is the state transition function for our blockchain. + +In the context of writing unit tests for our pallet, we need not actually run a full, decentralized blockchain network, we just need to construct a runtime which places our custom pallet into our state transition function, and allows us to access it. + +### `#[runtime]` Macro + +The `#[runtime]` macro does all the work to build that state transition function that we can run tests on top of. You will see that much like our pallet macros, the runtime macros have an entry point: + +```rust +#[runtime] +mod runtime { + // -- snip -- +} +``` + +You can see inside this entrypoint, we have various sub-macros: + +- `#[runtime::runtime]` +- `#[runtime::derive]` +- `#[runtime::pallet_index(n)]` + +While the `runtime` module does not look super big, you should know it generates a LOT of code. Much of it is totally hidden from you, since it is all dynamically generated boilerplate code to interface our runtime to the rest of our blockchain. + +Let's look a little closer into these different sub-macros. + +#### `#[runtime::runtime]` + +Our whole blockchain runtime is represented by a single struct with the `#[runtime:runtime]` attribute: + +```rust +#[runtime::runtime] +pub struct Runtime; +``` + +You can name this struct whatever you want. As you see in our tests, we name it `TestRuntime` for additional clarity. When you build a full Polkadot SDK project, you will probably have multiple runtimes, some for unit tests, some for test networks, and some for production. Because the Polkadot SDK is designed to be modular and configurable, it is super easy to do this, and construct many versions of your blockchain runtime + +You can think of this runtime as just a placeholder for all of our runtime configuration and traits. The `TestRuntime` does not actually hold any data. It is a + +More specifically, if you remember the `Config` trait that we must implement, `TestRuntime` will be the struct that implements all those traits and satisfies `Config`. We will see this below. + +#### `#[runtime::derive]` + +The runtime macros generate a lot of objects which give access to our state transition function and the pallets integrated in them. + +You have already learned that pallets have: + +- Callable Functions +- Events +- Errors +- etc... + +The runtime macros generate "aggregated" runtime enums which represents all of those things across all pallets. + +For example, imagine our blockchain has two pallets, each with one event. That would mean in our codebase, we would have two enums which look something like: + +```rust +// Found in our Pallet 1 crate. +enum Pallet1Event { + Hello, +} + +// Found in our Pallet 2 crate. +enum Pallet2Event { + World, +} +``` + +Our `#[runtime::derive(RuntimeEvent)]` would aggregate these together, and allow you to access all possible events from a single object: + +```rust +// Constructed by our +enum RuntimeEvent { + Pallet1(Pallet1Event), + Pallet2(Pallet2Event) +} +``` + +> NOTE: If you want to dive deeper into this, be sure to check out the [`rust-state-machine`](https://github.com/shawntabrizi/rust-state-machine) tutorial. + +So at a high level, the runtime derive macros generate all of these aggregated types, which become available and can be used in our runtime and blockchain. + +#### `#[runtime::pallet_index(n)]` + +As we discussed earlier, FRAME's opinion on how to build a blockchain runtime is by allowing users to split up their state transition function into individual modules which we call pallets. + +With the pallet index macro, you can literally see how we can compose a new runtime using a collection of pallets. + +Our test runtime only needs three pallets to allow us to write good unit tests: + +1. `frame_system`: This is required for any FRAME based runtime, so always included. +2. `pallet_balances`: This is a pallet which manages a blockchain's native currency, which will be used by our custom pallet. +3. `pallet_kitties`: This is the custom pallet we are building in this tutorial, and that we will test the functionality of. + +You can see adding a pallet to your runtime is pretty simple: + +```rust +#[runtime::pallet_index(2)] +pub type PalletKitties = pallet_kitties::Pallet; +``` + +Each pallet needs a unique index, and right now we only support 256 pallets maximum. + +You can see that we extract the `Pallet` struct from each of the pallet crates. Since the `Pallet` struct is generic over `T: Config`, and because the `TestRuntime` struct will implement all the required traits, we can use it. + +We can assign this to a type with any name we choose. To make things simple and explicit, we chose the name `PalletKitties`, which we can use to reference this specific pallet in all of our tests. + +### Configuring your Runtime + +Right below the `mod runtime` block, you will see us implement all the `Config` traits for our different pallets on the `TestRuntime` object. + +```rust +impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; +} +``` + +This trait is named "Config", and you can really think about this as the configuration for each pallet in your runtime. + +You can see `pallet_kitties` only exposes one configuration, which is basically asking us to pass back to it the `RuntimeEvent` generated by the `#[runtime::derive]` macro. If we didn't configure this right, our pallet would not be able to emit events (or even compile). + +The `frame_system` and `pallet_balances` pallets also have a ton of configurations, but most of those are hidden and automatically configured thanks to the `config_preludes::TestDefaultConfig`: + +```rust +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for TestRuntime { + type Block = Block; + type AccountData = pallet_balances::AccountData; +} +``` + +Configuring pallets is very specific to each pallet you add to your blockchain, and requires you to read the documentation of that pallet. These default configurations are suitable for more unit tests, but depending on your needs, you might want to change some of the configuration choices. + +For the purposes of this tutorial, these `TestDefaultConfig` options are exactly what we need. + +## Writing Tests + +Now that we have constructed a test runtime with all the pallets we want to include, we can actually start writing unit tests. + +### Test Externalities + +Unit tests for the Polkadot SDK are just normal rust tests, but calling into our test runtime. + +However, in a regular blockchain, you would have a database, and your pallet and pallet storage would call into this database and actually store the changes caused by your state transition function. + +In our test environment, we must introduce a storage abstraction that will maintain state during a test and reset at the end. + +For this, we create a new test externalities: + +```rust +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} +``` + +To use this text externalities, you need to execute your tests within a closure: + +```rust +#[test] +fn my_pallet_test() { + new_test_ext().execute_with(|| { + // Your pallet test here. + }); +} +``` + +If you write a pallet test which uses some storage, and forget to wrap it inside the test externalities, you will get an error: + +```rust +#[test] +fn forgot_new_test_ext() { + System::set_block_number(1); +} +``` + +```text +---- tests::forgot_new_test_ext stdout ---- +thread 'tests::forgot_new_test_ext' panicked at /Users/shawntabrizi/.cargo/registry/src/index.crates.io-6f17d22bba15001f/sp-io-38.0.0/src/lib.rs:205:5: +`set_version_1` called outside of an Externalities-provided environment. +note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace +``` + +This error just tells you to add the `new_test_ext` wrapper. + +### Calling Pallets + +In order to setup and execute our various tests, we need to call into the pallets in our runtime. + +Looking back at our runtime definition, we created a bunch of types representing our pallets, and we can use those to access our pallet's functions. + +You already saw an example of us accessing the `frame_system::Pallet` with `System::set_block_number(1)`. + +Here, we are calling a function implemented on the `Pallet` in the crate `frame_system`: + +```rust +// In the `frame_system` crate... +impl Pallet { + fn set_block_number(n: T::BlockNumber) { + // -- snip -- + } +} +``` + +If, for example, you wanted to call the `mint` function in the pallet you are working on and ensure the mint succeeded, you would simply write: + +```rust +assert_oK!(PalletKitties::mint(some_account)); +``` + +This is not any kind of Polkadot SDK specific magic, this is just regular Rust. + +### Checking Events + +One of the ways you can check that your test goes right is by looking at the events emitted at the end of your call. + +For this, you can use `System::assert_last_event(...)`, which checks in storage what the last event emitted by any pallet was. + +You can see an example of this added to our `tests.rs` file in this step. + +One really important thing to remember is that you need to set the block number to a value greater than zero for events to work! +This is because on the genesis block, we don't want to emit events, because there will be so many of them, it would bloat and lag our blockchain on that zeroth block. + +If you write a test, and you expect some event, but don't see it, just double check that you have set the block number. + +## Your Turn! + +Don't forget to update your `tests.rs` file to include the test provided in this step. + +It shows how you can: + +- Set the blocknumber of your blockchain inside your tests. +- Call an extrinsic in your pallet from an `AccountId` of your choice. +- Check the extrinsic call completed `Ok(())`. +- Get the last event deposited into `System`. +- Check that last event matches the event you would expect from your pallet. + +There is so much more that can be taught about tests, but it really makes sense to cover these things AFTER you have learned all the basics about building a pallet. +There is a lot of content here already, and truthfully, it is not super important for completing this tutorial. +However, writing tests is a critically important for actually creating production ready systems. + +From this point forward, every step where you write some code will include new tests or modify existing tests. +Make sure to keep updating your `tests.rs` file throughout the tutorial. diff --git a/src/9/source/changes.diff b/src/9/source/changes.diff index 1462ccfe..52a6bc88 100644 --- a/src/9/source/changes.diff +++ b/src/9/source/changes.diff @@ -1,12 +1,45 @@ -diff --git a/src/lib.rs b/src/lib.rs -index a52896e..ae8a09b 100644 ---- a/src/lib.rs -+++ b/src/lib.rs -@@ -18,7 +18,6 @@ pub mod pallet { - type RuntimeEvent: From> + IsType<::RuntimeEvent>; - } +diff --git a/src/tests.rs b/src/tests.rs +index d87be46f..434ce723 100644 +--- a/src/tests.rs ++++ b/src/tests.rs +@@ -29,6 +29,7 @@ type Block = frame_system::mocking::MockBlock; + const ALICE: u64 = 1; + const BOB: u64 = 2; -- /* 🚧 TODO 🚧: Learn about Pallet Events. */ - #[pallet::event] - #[pallet::generate_deposit(pub(super) fn deposit_event)] - pub enum Event { ++/* 🚧 TODO 🚧: Learn about constructing a runtime. */ + #[runtime] + mod runtime { + #[runtime::derive( +@@ -60,6 +61,7 @@ mod runtime { + pub type PalletKitties = pallet_kitties::Pallet; + } + ++/* 🚧 TODO 🚧: Learn about configuring a pallet. */ + // Normally `System` would have many more configurations, but you can see that we use some macro + // magic to automatically configure most of the pallet for a "default test configuration". + #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +@@ -82,6 +84,7 @@ impl pallet_kitties::Config for TestRuntime { + type RuntimeEvent = RuntimeEvent; + } + ++/* 🚧 TODO 🚧: Learn about test externalities. */ + // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` + // It simulates the blockchain database backend for our tests. + // If you forget to include this and try to access your Pallet storage, you will get an error like: +@@ -123,3 +126,16 @@ fn create_kitty_checks_signed() { + assert_noop!(PalletKitties::create_kitty(RuntimeOrigin::none()), DispatchError::BadOrigin); + }) + } ++ ++/* 🚧 TODO 🚧: Learn about writing tests. */ ++#[test] ++fn create_kitty_emits_event() { ++ new_test_ext().execute_with(|| { ++ // We need to set block number to 1 to view events. ++ System::set_block_number(1); ++ // Execute our call, and ensure it is successful. ++ assert_ok!(PalletKitties::create_kitty(RuntimeOrigin::signed(ALICE))); ++ // Assert the last event by our blockchain is the `Created` event with the correct owner. ++ System::assert_last_event(Event::::Created { owner: 1 }.into()); ++ }) ++} diff --git a/src/9/source/src/lib.rs b/src/9/source/src/lib.rs index ae8a09bc..a52896ed 100644 --- a/src/9/source/src/lib.rs +++ b/src/9/source/src/lib.rs @@ -18,6 +18,7 @@ pub mod pallet { type RuntimeEvent: From> + IsType<::RuntimeEvent>; } + /* 🚧 TODO 🚧: Learn about Pallet Events. */ #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { diff --git a/src/9/source/src/tests.rs b/src/9/source/src/tests.rs index 8baa63c6..434ce723 100644 --- a/src/9/source/src/tests.rs +++ b/src/9/source/src/tests.rs @@ -29,6 +29,7 @@ type Block = frame_system::mocking::MockBlock; const ALICE: u64 = 1; const BOB: u64 = 2; +/* 🚧 TODO 🚧: Learn about constructing a runtime. */ #[runtime] mod runtime { #[runtime::derive( @@ -49,17 +50,18 @@ mod runtime { /// System: Mandatory system pallet that should always be included in a FRAME runtime. #[runtime::pallet_index(0)] - pub type System = frame_system::Pallet; + pub type System = frame_system::Pallet; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] - pub type PalletBalances = pallet_balances::Pallet; + pub type PalletBalances = pallet_balances::Pallet; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] - pub type PalletKitties = pallet_kitties::Pallet; + pub type PalletKitties = pallet_kitties::Pallet; } +/* 🚧 TODO 🚧: Learn about configuring a pallet. */ // Normally `System` would have many more configurations, but you can see that we use some macro // magic to automatically configure most of the pallet for a "default test configuration". #[derive_impl(frame_system::config_preludes::TestDefaultConfig)] @@ -82,6 +84,7 @@ impl pallet_kitties::Config for TestRuntime { type RuntimeEvent = RuntimeEvent; } +/* 🚧 TODO 🚧: Learn about test externalities. */ // We need to run most of our tests using this function: `new_test_ext().execute_with(|| { ... });` // It simulates the blockchain database backend for our tests. // If you forget to include this and try to access your Pallet storage, you will get an error like: @@ -108,8 +111,6 @@ fn starting_template_is_sane() { fn system_and_balances_work() { // This test will just sanity check that we can access `System` and `PalletBalances`. new_test_ext().execute_with(|| { - // We often need to set `System` to block 1 so that we can see events. - System::set_block_number(1); // We often need to add some balance to a user to test features which needs tokens. assert_ok!(PalletBalances::mint_into(&ALICE, 100)); assert_ok!(PalletBalances::mint_into(&BOB, 100)); @@ -126,6 +127,7 @@ fn create_kitty_checks_signed() { }) } +/* 🚧 TODO 🚧: Learn about writing tests. */ #[test] fn create_kitty_emits_event() { new_test_ext().execute_with(|| { diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 116548ce..2353deaf 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -7,28 +7,29 @@ - [6. Origin](6/README.md) - [7. Pallet Config](7/README.md) - [8. Pallet Events](8/README.md) -- [9. Storage Basics](9/README.md) - - [10. Blockchain Storage](10/README.md) - - [11. Storage Values](11/README.md) - - [12. Kitty Counter](12/README.md) - - [13. Safety First](13/README.md) - - [14. Value Query](14/README.md) - - [15. Storage Maps](15/README.md) - - [16. Kitties Map](16/README.md) - - [17. Duplicate Kitty Check](17/README.md) -- [18. Storing Objects](18/README.md) - - [19. Kitty Struct](19/README.md) - - [20. Storing a Struct](20/README.md) - - [21. Generate Unique DNA](21/README.md) - - [22. Track Owned Kitties](22/README.md) - - [23. Bounded Vectors](23/README.md) -- [24. Kitty Marketplace](24/README.md) - - [25. Transfer Extrinsic](25/README.md) - - [26. Transfer Logic](26/README.md) - - [27. Native Balances](27/README.md) - - [28. Native Balance Type](28/README.md) - - [29. Set Price Extrinsic](29/README.md) - - [30. Set Price Logic](30/README.md) - - [31. Buy Kitty Extrinsic](31/README.md) - - [32. Buy Kitty Logic](32/README.md) -- [33. Next Steps](33/README.md) + - [9. Runtime and Tests](9/README.md) +- [10. Storage Basics](10/README.md) + - [11. Blockchain Storage](11/README.md) + - [12. Storage Values](12/README.md) + - [13. Kitty Counter](13/README.md) + - [14. Safety First](14/README.md) + - [15. Value Query](15/README.md) + - [16. Storage Maps](16/README.md) + - [17. Kitties Map](17/README.md) + - [18. Duplicate Kitty Check](18/README.md) +- [19. Storing Objects](19/README.md) + - [20. Kitty Struct](20/README.md) + - [21. Storing a Struct](21/README.md) + - [22. Generate Unique DNA](22/README.md) + - [23. Track Owned Kitties](23/README.md) + - [24. Bounded Vectors](24/README.md) +- [25. Kitty Marketplace](25/README.md) + - [26. Transfer Extrinsic](26/README.md) + - [27. Transfer Logic](27/README.md) + - [28. Native Balances](28/README.md) + - [29. Native Balance Type](29/README.md) + - [30. Set Price Extrinsic](30/README.md) + - [31. Set Price Logic](31/README.md) + - [32. Buy Kitty Extrinsic](32/README.md) + - [33. Buy Kitty Logic](33/README.md) +- [34. Next Steps](34/README.md)