// This file is part of Bizinikiwi. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. //! Pallets used in the `your_first_pallet` guide. #![cfg_attr(not(feature = "std"), no_std)] #[docify::export] #[frame::pallet(dev_mode)] pub mod shell_pallet { use frame::prelude::*; #[pallet::config] pub trait Config: pezframe_system::Config {} #[pallet::pallet] pub struct Pallet(_); } #[frame::pallet(dev_mode)] pub mod pallet { use frame::prelude::*; #[docify::export] pub type Balance = u128; #[pallet::config] pub trait Config: pezframe_system::Config {} #[pallet::pallet] pub struct Pallet(_); #[docify::export] /// Single storage item, of type `Balance`. #[pallet::storage] pub type TotalIssuance = StorageValue<_, Balance>; #[docify::export] /// A mapping from `T::AccountId` to `Balance` #[pallet::storage] pub type Balances = StorageMap<_, _, T::AccountId, Balance>; #[docify::export(impl_pallet)] #[pallet::call] impl Pallet { /// An unsafe mint that can be called by anyone. Not a great idea. pub fn mint_unsafe( origin: T::RuntimeOrigin, dest: T::AccountId, amount: Balance, ) -> DispatchResult { // ensure that this is a signed account, but we don't really check `_anyone`. let _anyone = ensure_signed(origin)?; // update the balances map. Notice how all `` remains as ``. Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); // update total issuance. TotalIssuance::::mutate(|t| *t = Some(t.unwrap_or(0) + amount)); Ok(()) } /// Transfer `amount` from `origin` to `dest`. pub fn transfer( origin: T::RuntimeOrigin, dest: T::AccountId, amount: Balance, ) -> DispatchResult { let sender = ensure_signed(origin)?; // ensure sender has enough balance, and if so, calculate what is left after `amount`. let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; if sender_balance < amount { return Err("InsufficientBalance".into()); } let remainder = sender_balance - amount; // update sender and dest balances. Balances::::mutate(dest, |b| *b = Some(b.unwrap_or(0) + amount)); Balances::::insert(&sender, remainder); Ok(()) } } #[allow(unused)] impl Pallet { #[docify::export] pub fn transfer_better( origin: T::RuntimeOrigin, dest: T::AccountId, amount: Balance, ) -> DispatchResult { let sender = ensure_signed(origin)?; let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; ensure!(sender_balance >= amount, "InsufficientBalance"); let remainder = sender_balance - amount; // .. snip Ok(()) } #[docify::export] /// Transfer `amount` from `origin` to `dest`. pub fn transfer_better_checked( origin: T::RuntimeOrigin, dest: T::AccountId, amount: Balance, ) -> DispatchResult { let sender = ensure_signed(origin)?; let sender_balance = Balances::::get(&sender).ok_or("NonExistentAccount")?; let remainder = sender_balance.checked_sub(amount).ok_or("InsufficientBalance")?; // .. snip Ok(()) } } #[cfg(any(test, doc))] pub(crate) mod tests { use crate::pallet::*; #[docify::export(testing_prelude)] use frame::testing_prelude::*; pub(crate) const ALICE: u64 = 1; pub(crate) const BOB: u64 = 2; pub(crate) const CHARLIE: u64 = 3; #[docify::export] // This runtime is only used for testing, so it should be somewhere like `#[cfg(test)] mod // tests { .. }` mod runtime { use super::*; // we need to reference our `mod pallet` as an identifier to pass to // `construct_runtime`. // YOU HAVE TO CHANGE THIS LINE BASED ON YOUR TEMPLATE use crate::pallet as pezpallet_currency; construct_runtime!( pub enum Runtime { // ---^^^^^^ This is where `enum Runtime` is defined. System: pezframe_system, Currency: pezpallet_currency, } ); #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)] impl pezframe_system::Config for Runtime { type Block = MockBlock; // within pallet we just said `::AccountId`, now we // finally specified it. type AccountId = u64; } // our simple pallet has nothing to be configured. impl pezpallet_currency::Config for Runtime {} } pub(crate) use runtime::*; #[allow(unused)] #[docify::export] fn new_test_state_basic() -> TestState { let mut state = TestState::new_empty(); let accounts = vec![(ALICE, 100), (BOB, 100)]; state.execute_with(|| { for (who, amount) in &accounts { Balances::::insert(who, amount); TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); } }); state } #[docify::export] pub(crate) struct StateBuilder { balances: Vec<(::AccountId, Balance)>, } #[docify::export(default_state_builder)] impl Default for StateBuilder { fn default() -> Self { Self { balances: vec![(ALICE, 100), (BOB, 100)] } } } #[docify::export(impl_state_builder_add)] impl StateBuilder { fn add_balance( mut self, who: ::AccountId, amount: Balance, ) -> Self { self.balances.push((who, amount)); self } } #[docify::export(impl_state_builder_build)] impl StateBuilder { pub(crate) fn build_and_execute(self, test: impl FnOnce() -> ()) { let mut ext = TestState::new_empty(); ext.execute_with(|| { for (who, amount) in &self.balances { Balances::::insert(who, amount); TotalIssuance::::mutate(|b| *b = Some(b.unwrap_or(0) + amount)); } }); ext.execute_with(test); // assertions that must always hold ext.execute_with(|| { assert_eq!( Balances::::iter().map(|(_, x)| x).sum::(), TotalIssuance::::get().unwrap_or_default() ); }) } } #[docify::export] #[test] fn first_test() { TestState::new_empty().execute_with(|| { // We expect Alice's account to have no funds. assert_eq!(Balances::::get(&ALICE), None); assert_eq!(TotalIssuance::::get(), None); // mint some funds into Alice's account. assert_ok!(Pallet::::mint_unsafe( RuntimeOrigin::signed(ALICE), ALICE, 100 )); // re-check the above assert_eq!(Balances::::get(&ALICE), Some(100)); assert_eq!(TotalIssuance::::get(), Some(100)); }) } #[docify::export] #[test] fn state_builder_works() { StateBuilder::default().build_and_execute(|| { assert_eq!(Balances::::get(&ALICE), Some(100)); assert_eq!(Balances::::get(&BOB), Some(100)); assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(200)); }); } #[docify::export] #[test] fn state_builder_add_balance() { StateBuilder::default().add_balance(CHARLIE, 42).build_and_execute(|| { assert_eq!(Balances::::get(&CHARLIE), Some(42)); assert_eq!(TotalIssuance::::get(), Some(242)); }) } #[test] #[should_panic] fn state_builder_duplicate_genesis_fails() { StateBuilder::default() .add_balance(CHARLIE, 42) .add_balance(CHARLIE, 43) .build_and_execute(|| { assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(242)); }) } #[docify::export] #[test] fn mint_works() { StateBuilder::default().build_and_execute(|| { // given the initial state, when: assert_ok!(Pallet::::mint_unsafe(RuntimeOrigin::signed(ALICE), BOB, 100)); // then: assert_eq!(Balances::::get(&BOB), Some(200)); assert_eq!(TotalIssuance::::get(), Some(300)); // given: assert_ok!(Pallet::::mint_unsafe( RuntimeOrigin::signed(ALICE), CHARLIE, 100 )); // then: assert_eq!(Balances::::get(&CHARLIE), Some(100)); assert_eq!(TotalIssuance::::get(), Some(400)); }); } #[docify::export] #[test] fn transfer_works() { StateBuilder::default().build_and_execute(|| { // given the initial state, when: assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); // then: assert_eq!(Balances::::get(&ALICE), Some(50)); assert_eq!(Balances::::get(&BOB), Some(150)); assert_eq!(TotalIssuance::::get(), Some(200)); // when: assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(BOB), ALICE, 50)); // then: assert_eq!(Balances::::get(&ALICE), Some(100)); assert_eq!(Balances::::get(&BOB), Some(100)); assert_eq!(TotalIssuance::::get(), Some(200)); }); } #[docify::export] #[test] fn transfer_from_non_existent_fails() { StateBuilder::default().build_and_execute(|| { // given the initial state, when: assert_err!( Pallet::::transfer(RuntimeOrigin::signed(CHARLIE), ALICE, 10), "NonExistentAccount" ); // then nothing has changed. assert_eq!(Balances::::get(&ALICE), Some(100)); assert_eq!(Balances::::get(&BOB), Some(100)); assert_eq!(Balances::::get(&CHARLIE), None); assert_eq!(TotalIssuance::::get(), Some(200)); }); } } } #[frame::pallet(dev_mode)] pub mod pezpallet_v2 { use super::pallet::Balance; use frame::prelude::*; #[docify::export(config_v2)] #[pallet::config] pub trait Config: pezframe_system::Config { /// The overarching event type of the runtime. #[allow(deprecated)] type RuntimeEvent: From> + IsType<::RuntimeEvent> + TryInto>; } #[pallet::pallet] pub struct Pallet(_); #[pallet::storage] pub type Balances = StorageMap<_, _, T::AccountId, Balance>; #[pallet::storage] pub type TotalIssuance = StorageValue<_, Balance>; #[docify::export] #[pallet::error] pub enum Error { /// Account does not exist. NonExistentAccount, /// Account does not have enough balance. InsufficientBalance, } #[docify::export] #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] pub enum Event { /// A transfer succeeded. Transferred { from: T::AccountId, to: T::AccountId, amount: Balance }, } #[pallet::call] impl Pallet { #[docify::export(transfer_v2)] pub fn transfer( origin: T::RuntimeOrigin, dest: T::AccountId, amount: Balance, ) -> DispatchResult { let sender = ensure_signed(origin)?; // ensure sender has enough balance, and if so, calculate what is left after `amount`. let sender_balance = Balances::::get(&sender).ok_or(Error::::NonExistentAccount)?; let remainder = sender_balance.checked_sub(amount).ok_or(Error::::InsufficientBalance)?; Balances::::mutate(&dest, |b| *b = Some(b.unwrap_or(0) + amount)); Balances::::insert(&sender, remainder); Self::deposit_event(Event::::Transferred { from: sender, to: dest, amount }); Ok(()) } } #[cfg(any(test, doc))] pub mod tests { use super::{super::pallet::tests::StateBuilder, *}; use frame::testing_prelude::*; const ALICE: u64 = 1; const BOB: u64 = 2; #[docify::export] pub mod runtime_v2 { use super::*; use crate::pezpallet_v2 as pezpallet_currency; construct_runtime!( pub enum Runtime { System: pezframe_system, Currency: pezpallet_currency, } ); #[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)] impl pezframe_system::Config for Runtime { type Block = MockBlock; type AccountId = u64; } impl pezpallet_currency::Config for Runtime { type RuntimeEvent = RuntimeEvent; } } pub(crate) use runtime_v2::*; #[docify::export(transfer_works_v2)] #[test] fn transfer_works() { StateBuilder::default().build_and_execute(|| { // skip the genesis block, as events are not deposited there and we need them for // the final assertion. System::set_block_number(ALICE); // given the initial state, when: assert_ok!(Pallet::::transfer(RuntimeOrigin::signed(ALICE), BOB, 50)); // then: assert_eq!(Balances::::get(&ALICE), Some(50)); assert_eq!(Balances::::get(&BOB), Some(150)); assert_eq!(TotalIssuance::::get(), Some(200)); // now we can also check that an event has been deposited: assert_eq!( System::read_events_for_pallet::>(), vec![Event::Transferred { from: ALICE, to: BOB, amount: 50 }] ); }); } } }