Native Balance Type
One of the most challenging parts of using the polkadot-sdk
is using generic types.
Hopefully, things like T::AccountId
have been easy enough to use, but using the Balance
type coming from NativeBalance
can be a little tricky.
Generic Types
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.
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.
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<T>
alias type like so:
#![allow(unused)] fn main() { // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. pub type BalanceOf<T> = <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::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.#![allow(unused)] fn main() { Balance }
- This
Balance
type is part of theInspect
trait.#![allow(unused)] fn main() { Inspect::Balance }
- The
Inspect
trait is generic overAccountId
, so we need to include that.#![allow(unused)] fn main() { Inspect<AccountId>::Balance }
- The
AccountId
type comes fromT
, throughframe_system::Config
, where the type is defined.#![allow(unused)] fn main() { Inspect<<T as frame_system::Config>::AccountId>::Balance }
- The
Inspect
is accessible through theNativeBalance
associated type.#![allow(unused)] fn main() { <NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance }
- The
NativeBalance
type also comes fromT
, but though our pallet's ownConfig
.#![allow(unused)] fn main() { <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance }
- Finally, we assign this to a new alias
BalanceOf
which is generic over<T>
.#![allow(unused)] fn main() { pub type BalanceOf<T> = <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::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<T>
alias?
So that we can change this:
#![allow(unused)] fn main() { fn set_price(id: [u8; 32], price: <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance) { // -- snip -- } }
To this:
#![allow(unused)] fn main() { fn set_price(id: [u8; 32], price: BalanceOf<T>) { // -- 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<T>
type in our code.
Interacting with Primitive Numbers
I will repeat again: Because the BalanceOf<T>
type is generic, we cannot know what underlying type it is. This means we CANNOT write the following:
#![allow(unused)] fn main() { // This code doesn't work fn add_one(input: BalanceOf<T>) -> BalanceOf<T> { 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<T>
does have traits that we can use to interact with it. The key one being AtLeast32BitUnsigned
.
This means our BalanceOf<T>
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:
#![allow(unused)] fn main() { // This code does work fn add_one(input: BalanceOf<T>) -> BalanceOf<T> { input + 1u32.into() } }
We can convert any u32
into the BalanceOf<T>
type because we know at a minimum BalanceOf<T>
is AtLeast32BitUnsigned
.
Interacting with Itself
Interacting between two BalanceOf<T>
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.
#![allow(unused)] fn main() { let total_balance: BalanceOf<T> = balance_1.checked_add(balance_2).ok_or(ArithmeticError::Overflow)?; }
Price Field
We are going to use BalanceOf<T>
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<BalanceOf<T>>
, 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
Now that you know how to create and use the BalanceOf<T>
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<BalanceOf<T>>
.
Update the mint
function to create a new Kitty
with the new price
field set as None
.
Update the do_transfer
function to reset the kitty.price
to None
when the kitty gets a new owner.
Finally, update your tests.rs
file so that DEFAULT_KITTY
has the field and value price: None
.
#![allow(unused)] fn main() { use super::*; use frame::prelude::*; use frame::primitives::BlakeTwo256; use frame::traits::Hash; // Learn about internal functions. impl<T: Config> Pallet<T> { // 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::<T>::parent_hash(), frame_system::Pallet::<T>::block_number(), frame_system::Pallet::<T>::extrinsic_index(), CountForKitties::<T>::get(), ); BlakeTwo256::hash_of(&unique_payload).into() } 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::<T>::contains_key(dna), Error::<T>::DuplicateKitty); let current_count: u32 = CountForKitties::<T>::get(); let new_count = current_count.checked_add(1).ok_or(Error::<T>::TooManyKitties)?; KittiesOwned::<T>::try_append(&owner, dna).map_err(|_| Error::<T>::TooManyOwned)?; Kitties::<T>::insert(dna, kitty); CountForKitties::<T>::set(new_count); Self::deposit_event(Event::<T>::Created { owner }); Ok(()) } pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { ensure!(from != to, Error::<T>::TransferToSelf); let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?; ensure!(kitty.owner == from, Error::<T>::NotOwner); kitty.owner = to.clone(); /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */ let mut to_owned = KittiesOwned::<T>::get(&to); to_owned.try_push(kitty_id).map_err(|_| Error::<T>::TooManyOwned)?; let mut from_owned = KittiesOwned::<T>::get(&from); if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { from_owned.swap_remove(ind); } else { return Err(Error::<T>::NoKitty.into()) } Kitties::<T>::insert(kitty_id, kitty); KittiesOwned::<T>::insert(&to, to_owned); KittiesOwned::<T>::insert(&from, from_owned); Self::deposit_event(Event::<T>::Transferred { from, to, kitty_id }); Ok(()) } } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { 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<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// The Fungible handler for the kitties pallet. type NativeBalance: Inspect<Self::AccountId> + Mutate<Self::AccountId>; } /* 🚧 TODO 🚧: - Create a new type alias called `BalanceOf<T>`. - 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<T: Config> { // 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<BalanceOf<T>>`. */ } #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>; #[pallet::storage] pub(super) type Kitties<T: Config> = StorageMap<Key = [u8; 32], Value = Kitty<T>>; /// Track the kitties owned by each account. #[pallet::storage] pub(super) type KittiesOwned<T: Config> = 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<T: Config> { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, TooManyOwned, TransferToSelf, NoKitty, NotOwner, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let dna = Self::gen_dna(); Self::mint(who, dna)?; Ok(()) } pub fn transfer( origin: OriginFor<T>, to: T::AccountId, kitty_id: [u8; 32], ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_transfer(who, to, kitty_id)?; Ok(()) } } } }
#![allow(unused)] fn main() { use super::*; use frame::prelude::*; use frame::primitives::BlakeTwo256; use frame::traits::Hash; // Learn about internal functions. impl<T: Config> Pallet<T> { // 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::<T>::parent_hash(), frame_system::Pallet::<T>::block_number(), frame_system::Pallet::<T>::extrinsic_index(), CountForKitties::<T>::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::<T>::contains_key(dna), Error::<T>::DuplicateKitty); let current_count: u32 = CountForKitties::<T>::get(); let new_count = current_count.checked_add(1).ok_or(Error::<T>::TooManyKitties)?; KittiesOwned::<T>::try_append(&owner, dna).map_err(|_| Error::<T>::TooManyOwned)?; Kitties::<T>::insert(dna, kitty); CountForKitties::<T>::set(new_count); Self::deposit_event(Event::<T>::Created { owner }); Ok(()) } pub fn do_transfer(from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32]) -> DispatchResult { ensure!(from != to, Error::<T>::TransferToSelf); let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?; ensure!(kitty.owner == from, Error::<T>::NotOwner); kitty.owner = to.clone(); kitty.price = None; let mut to_owned = KittiesOwned::<T>::get(&to); to_owned.try_push(kitty_id).map_err(|_| Error::<T>::TooManyOwned)?; let mut from_owned = KittiesOwned::<T>::get(&from); if let Some(ind) = from_owned.iter().position(|&id| id == kitty_id) { from_owned.swap_remove(ind); } else { return Err(Error::<T>::NoKitty.into()) } Kitties::<T>::insert(kitty_id, kitty); KittiesOwned::<T>::insert(&to, to_owned); KittiesOwned::<T>::insert(&from, from_owned); Self::deposit_event(Event::<T>::Transferred { from, to, kitty_id }); Ok(()) } } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { 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<T>(core::marker::PhantomData<T>); #[pallet::config] pub trait Config: frame_system::Config { type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>; /// The Fungible handler for the kitties pallet. type NativeBalance: Inspect<Self::AccountId> + Mutate<Self::AccountId>; } // Allows easy access our Pallet's `Balance` type. Comes from `Fungible` interface. pub type BalanceOf<T> = <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance; #[derive(Encode, Decode, TypeInfo, MaxEncodedLen)] #[scale_info(skip_type_params(T))] pub struct Kitty<T: Config> { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, pub price: Option<BalanceOf<T>>, } #[pallet::storage] pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>; #[pallet::storage] pub(super) type Kitties<T: Config> = StorageMap<Key = [u8; 32], Value = Kitty<T>>; /// Track the kitties owned by each account. #[pallet::storage] pub(super) type KittiesOwned<T: Config> = 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<T: Config> { Created { owner: T::AccountId }, Transferred { from: T::AccountId, to: T::AccountId, kitty_id: [u8; 32] }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, TooManyOwned, TransferToSelf, NoKitty, NotOwner, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let dna = Self::gen_dna(); Self::mint(who, dna)?; Ok(()) } pub fn transfer( origin: OriginFor<T>, to: T::AccountId, kitty_id: [u8; 32], ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_transfer(who, to, kitty_id)?; Ok(()) } } } }
#![allow(unused)] fn main() { // 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::sp_io; use frame::runtime::prelude::*; use frame::testing_prelude::*; use frame::traits::fungible::*; type Balance = u64; type Block = frame_system::mocking::MockBlock<TestRuntime>; // 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<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0, price: None }; // Our blockchain tests only need 3 Pallets: // 1. System: Which is included with every FRAME runtime. // 2. PalletBalances: Which is manages your blockchain's native currency. (i.e. DOT on Polkadot) // 3. PalletKitties: The pallet you are building in this tutorial! construct_runtime! { pub struct TestRuntime { System: frame_system, PalletBalances: pallet_balances, PalletKitties: pallet_kitties, } } // 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<Balance>; } // 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::<TestRuntime>::default() .build_storage() .unwrap() .into() } #[test] fn starting_template_is_sane() { new_test_ext().execute_with(|| { let event = Event::<TestRuntime>::Created { owner: ALICE }; let _runtime_event: RuntimeEvent = event.into(); let _call = Call::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::get(), 0); // You can `set` the value using an `u32`. CountForKitties::<TestRuntime>::set(1337u32); // You can `put` the value directly with a `u32`. CountForKitties::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::get(), 1); }) } #[test] fn mint_errors_when_overflow() { new_test_ext().execute_with(|| { // Set the count to the largest value possible. CountForKitties::<TestRuntime>::set(u32::MAX); // `create_kitty` should not succeed because of safe math. assert_noop!( PalletKitties::create_kitty(RuntimeOrigin::signed(1)), Error::<TestRuntime>::TooManyKitties ); }) } #[test] fn kitties_map_created_correctly() { new_test_ext().execute_with(|| { let zero_key = [0u8; 32]; assert!(!Kitties::<TestRuntime>::contains_key(zero_key)); Kitties::<TestRuntime>::insert(zero_key, DEFAULT_KITTY); assert!(Kitties::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::decode(&mut &bytes[..]).unwrap(); assert!(Kitty::<TestRuntime>::max_encoded_len() > 0); let _info = Kitty::<TestRuntime>::type_info(); }) } #[test] fn mint_stores_owner_in_kitty() { new_test_ext().execute_with(|| { assert_ok!(PalletKitties::mint(1337, [42u8; 32])); let kitty = Kitties::<TestRuntime>::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::<TestRuntime>::get(), 2); assert_eq!(Kitties::<TestRuntime>::iter().count(), 2); }) } #[test] fn kitties_owned_created_correctly() { new_test_ext().execute_with(|| { // Initially users have no kitties owned. assert_eq!(KittiesOwned::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::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::<TestRuntime>::iter_keys().collect::<Vec<_>>()[0]; assert_ok!(PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, kitty_id)); System::assert_last_event( Event::<TestRuntime>::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::<TestRuntime>::iter_values().collect::<Vec<_>>()[0]; let kitty_id = kitty.dna; assert_eq!(kitty.owner, ALICE); assert_eq!(KittiesOwned::<TestRuntime>::get(ALICE), vec![kitty_id]); assert_eq!(KittiesOwned::<TestRuntime>::get(BOB), vec![]); // Cannot transfer to yourself. assert_noop!( PalletKitties::transfer(RuntimeOrigin::signed(ALICE), ALICE, kitty_id), Error::<TestRuntime>::TransferToSelf ); // Cannot transfer a non-existent kitty. assert_noop!( PalletKitties::transfer(RuntimeOrigin::signed(ALICE), BOB, [0u8; 32]), Error::<TestRuntime>::NoKitty ); // Cannot transfer kitty you do not own. assert_noop!( PalletKitties::transfer(RuntimeOrigin::signed(BOB), ALICE, kitty_id), Error::<TestRuntime>::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::<TestRuntime>::get(ALICE), vec![]); assert_eq!(KittiesOwned::<TestRuntime>::get(BOB), vec![kitty_id]); let kitty = &Kitties::<TestRuntime>::iter_values().collect::<Vec<_>>()[0]; assert_eq!(kitty.owner, BOB); }); } #[test] fn native_balance_associated_type_works() { new_test_ext().execute_with(|| { assert_ok!(<<TestRuntime as Config>::NativeBalance as Mutate<_>>::mint_into(&ALICE, 1337)); assert_eq!( <<TestRuntime as Config>::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<TestRuntime> = 1337u64; } }
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<T: Config> Pallet<T> {
}
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::<T>::contains_key(dna), Error::<T>::DuplicateKitty);
@@ -40,6 +41,7 @@ impl<T: Config> Pallet<T> {
let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?;
ensure!(kitty.owner == from, Error::<T>::NotOwner);
kitty.owner = to.clone();
+ /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */
let mut to_owned = KittiesOwned::<T>::get(&to);
to_owned.try_push(kitty_id).map_err(|_| Error::<T>::TooManyOwned)?;
diff --git a/src/lib.rs b/src/lib.rs
index 59150bd..3a10644 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,12 +23,21 @@ pub mod pallet {
type NativeBalance: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
}
+ /* 🚧 TODO 🚧:
+ - Create a new type alias called `BalanceOf<T>`.
+ - 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<T: Config> {
// 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<BalanceOf<T>>`. */
}
#[pallet::storage]
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<T: Config> Pallet<T> {
}
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::<T>::contains_key(dna), Error::<T>::DuplicateKitty);
@@ -41,7 +40,7 @@ impl<T: Config> Pallet<T> {
let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?;
ensure!(kitty.owner == from, Error::<T>::NotOwner);
kitty.owner = to.clone();
- /* 🚧 TODO 🚧: Set the `kitty.price` to `None` for the new owner. */
+ kitty.price = None;
let mut to_owned = KittiesOwned::<T>::get(&to);
to_owned.try_push(kitty_id).map_err(|_| Error::<T>::TooManyOwned)?;
diff --git a/src/lib.rs b/src/lib.rs
index 3a10644..7d8350d 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -23,13 +23,9 @@ pub mod pallet {
type NativeBalance: Inspect<Self::AccountId> + Mutate<Self::AccountId>;
}
- /* 🚧 TODO 🚧:
- - Create a new type alias called `BalanceOf<T>`.
- - 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<T> =
+ <<T as Config>::NativeBalance as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
#[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<BalanceOf<T>>`. */
+ pub price: Option<BalanceOf<T>>,
}
#[pallet::storage]
diff --git a/src/tests.rs b/src/tests.rs
index 4ecdf9d..15c7df4 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -27,7 +27,7 @@ type Block = frame_system::mocking::MockBlock<TestRuntime>;
// 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<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0 };
+const DEFAULT_KITTY: Kitty<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0, price: None };
// Our blockchain tests only need 3 Pallets:
// 1. System: Which is included with every FRAME runtime.
@@ -304,3 +304,9 @@ 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<TestRuntime> = 1337u64;
+}