Kitty Struct
In this step, we will create a new struct which is generic over <T: Config>
. 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.
#![allow(unused)] fn main() { 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:
#![allow(unused)] fn main() { 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.
#![allow(unused)] fn main() { pub struct Kitty<AccountId> { pub dna: [u8; 32], pub owner: AccountId, } }
If we want to use multiple generic types, we could just keep extending this pattern:
#![allow(unused)] fn main() { pub struct Kitty<AccountId, BlockNumber> { 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:
#![allow(unused)] fn main() { pub type MyAlias<T: Config> = Kitty<T::AccountId, T::BlockNumber>; }
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 <T: Config>
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:
#![allow(unused)] fn main() { pub struct Kitty<T: Config> { 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:
#![allow(unused)] fn main() { pub type MyAlias<T: Config> = Kitty<T>; }
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 <T: Config>
. 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:
#![allow(unused)] fn main() { const DEFAULT_KITTY: Kitty<TestRuntime> = 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.
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; 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>; } /* 🚧 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<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>; #[pallet::storage] pub(super) type Kitties<T: Config> = StorageMap<Key = [u8; 32], Value = ()>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let dna = [0u8; 32]; Self::mint(who, dna)?; 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::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<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; /* 🚧 TODO 🚧: - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };. */ #[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<Runtime>; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] pub type PalletBalances = pallet_balances::Pallet<Runtime>; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] pub type PalletKitties = pallet_kitties::Pallet<Runtime>; } // 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; } // 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, ()); 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); }) } }
#![allow(unused)] #![cfg_attr(not(feature = "std"), no_std)] fn main() { mod impls; mod tests; use frame::prelude::*; 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>; } pub struct Kitty<T: Config> { // Using 32 bytes to represent a kitty DNA pub dna: [u8; 32], pub owner: T::AccountId, } #[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 = ()>; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event<T: Config> { Created { owner: T::AccountId }, } #[pallet::error] pub enum Error<T> { TooManyKitties, DuplicateKitty, } #[pallet::call] impl<T: Config> Pallet<T> { pub fn create_kitty(origin: OriginFor<T>) -> DispatchResult { let who = ensure_signed(origin)?; let dna = [0u8; 32]; Self::mint(who, dna)?; 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::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<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; #[allow(unused)] const DEFAULT_KITTY: Kitty<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0 }; #[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<Runtime>; /// PalletBalances: Manages your blockchain's native currency. (i.e. DOT on Polkadot) #[runtime::pallet_index(1)] pub type PalletBalances = pallet_balances::Pallet<Runtime>; /// PalletKitties: The pallet you are building in this tutorial! #[runtime::pallet_index(2)] pub type PalletKitties = pallet_kitties::Pallet<Runtime>; } // 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; } // 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, ()); 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); }) } }
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<Event<Self>> + IsType<<Self as frame_system::Config>::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<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>;
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<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;
+/* 🚧 TODO 🚧:
+ - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };.
+*/
#[runtime]
mod runtime {
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<Event<Self>> + IsType<<Self as frame_system::Config>::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<T: Config> {
+ // Using 32 bytes to represent a kitty DNA
+ pub dna: [u8; 32],
+ pub owner: T::AccountId,
+ }
#[pallet::storage]
pub(super) type CountForKitties<T: Config> = StorageValue<Value = u32, QueryKind = ValueQuery>;
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<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;
-/* 🚧 TODO 🚧:
- - Create `const DEFAULT_KITTY` with a `dna` of [0u8; 32] and an `owner` of 0 };.
-*/
+#[allow(unused)]
+const DEFAULT_KITTY: Kitty<TestRuntime> = Kitty { dna: [0u8; 32], owner: 0 };
#[runtime]
mod runtime {