Buy Kitty Extrinsic
Now that kitties can have a price, we want to enable them to be purchasable.
For that, we will create the buy_kitty
extrinsic.
Max Price
You will notice in our scaffolding, one of the parameters of our buy_kitty
extrinsic is max_price: BalanceOf<T>
.
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.
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 theSold
event.
#![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(()) } pub fn do_set_price( caller: T::AccountId, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>>, ) -> DispatchResult { let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?; ensure!(kitty.owner == caller, Error::<T>::NotOwner); kitty.price = new_price; Kitties::<T>::insert(kitty_id, kitty); Self::deposit_event(Event::<T>::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<T>`. - It returns `DispatchResult`. - The internal logic, for now, should be: - `Self::deposit_event` with `Event::<T>::Sold` and the appropriate params. - Return `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] }, PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>> }, /* 🚧 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<T>`. */ } #[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(()) } pub fn set_price( origin: OriginFor<T>, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>>, ) -> DispatchResult { let who = ensure_signed(origin)?; 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<T>`. - `kitty_id` which is `[u8; 32]`. - `max_price` which is `BalanceOf<T>`. - 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(())`. */ } } }
#![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(()) } pub fn do_set_price( caller: T::AccountId, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>>, ) -> DispatchResult { let mut kitty = Kitties::<T>::get(kitty_id).ok_or(Error::<T>::NoKitty)?; ensure!(kitty.owner == caller, Error::<T>::NotOwner); kitty.price = new_price; Kitties::<T>::insert(kitty_id, kitty); Self::deposit_event(Event::<T>::PriceSet { owner: caller, kitty_id, new_price }); Ok(()) } pub fn do_buy_kitty( buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf<T>, ) -> DispatchResult { Self::deposit_event(Event::<T>::Sold { buyer, kitty_id, price }); 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] }, PriceSet { owner: T::AccountId, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>> }, Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf<T> }, } #[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(()) } pub fn set_price( origin: OriginFor<T>, kitty_id: [u8; 32], new_price: Option<BalanceOf<T>>, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_set_price(who, kitty_id, new_price)?; Ok(()) } pub fn buy_kitty( origin: OriginFor<T>, kitty_id: [u8; 32], max_price: BalanceOf<T>, ) -> DispatchResult { let who = ensure_signed(origin)?; Self::do_buy_kitty(who, kitty_id, max_price)?; 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; } #[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::<TestRuntime>::iter_keys().collect::<Vec<_>>()[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::<TestRuntime>::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::<TestRuntime>::iter_values().collect::<Vec<_>>()[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::<TestRuntime>::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::<TestRuntime>::iter_keys().collect::<Vec<_>>()[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::<TestRuntime>::Sold { buyer: BOB, kitty_id, price: 1337 }.into(), ); }) } }
diff --git a/src/impls.rs b/src/impls.rs
index af4e460..04c92c5 100644
--- a/src/impls.rs
+++ b/src/impls.rs
@@ -72,4 +72,15 @@ impl<T: Config> Pallet<T> {
Self::deposit_event(Event::<T>::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<T>`.
+ - It returns `DispatchResult`.
+ - The internal logic, for now, should be:
+ - `Self::deposit_event` with `Event::<T>::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<BalanceOf<T>> },
+ /* 🚧 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<T>`.
+ */
}
#[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<T>`.
+ - `kitty_id` which is `[u8; 32]`.
+ - `max_price` which is `BalanceOf<T>`.
+ - 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/impls.rs b/src/impls.rs
index 04c92c5..03cf1f1 100644
--- a/src/impls.rs
+++ b/src/impls.rs
@@ -73,14 +73,12 @@ impl<T: Config> Pallet<T> {
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<T>`.
- - It returns `DispatchResult`.
- - The internal logic, for now, should be:
- - `Self::deposit_event` with `Event::<T>::Sold` and the appropriate params.
- - Return `Ok(())`.
- */
+ pub fn do_buy_kitty(
+ buyer: T::AccountId,
+ kitty_id: [u8; 32],
+ price: BalanceOf<T>,
+ ) -> DispatchResult {
+ Self::deposit_event(Event::<T>::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<BalanceOf<T>> },
- /* 🚧 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<T>`.
- */
+ Sold { buyer: T::AccountId, kitty_id: [u8; 32], price: BalanceOf<T> },
}
#[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<T>`.
- - `kitty_id` which is `[u8; 32]`.
- - `max_price` which is `BalanceOf<T>`.
- - 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<T>,
+ kitty_id: [u8; 32],
+ max_price: BalanceOf<T>,
+ ) -> 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 1733f2d..142f9d0 100644
--- a/src/tests.rs
+++ b/src/tests.rs
@@ -338,3 +338,20 @@ 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::<TestRuntime>::iter_keys().collect::<Vec<_>>()[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::<TestRuntime>::Sold { buyer: BOB, kitty_id, price: 1337 }.into(),
+ );
+ })
+}