From 2d1b2c8c67b8ce01f6068b7ab7c09e0001b6d36f Mon Sep 17 00:00:00 2001 From: Nathan GD Date: Tue, 27 Sep 2022 15:42:43 +0100 Subject: [PATCH] feat: add solution for ex05 added solutions fix(ex05): set dbWeight for frame_system on mock runtime was set to (), meaning reads and write had a weight of 0, becomming untestable fix(ex05): set dbWeight for frame_system on mock runtime was set to (), meaning reads and write had a weight of 0, becomming untestable format code adding hook exercice (solution) Revert "adding hook exercice (solution)" This reverts commit aef97bd232dd27fb32d42e456f37c4fff25843ec. adding hook exercice (solution) Revert "adding hook exercice (solution)" This reverts commit aef97bd232dd27fb32d42e456f37c4fff25843ec. --- exercises/ex05-reminder/README.md | 33 +++++ exercises/ex05-reminder/reminder/Cargo.toml | 42 +++++++ exercises/ex05-reminder/reminder/src/lib.rs | 90 ++++++++++++++ exercises/ex05-reminder/reminder/src/mock.rs | 72 +++++++++++ exercises/ex05-reminder/reminder/src/tests.rs | 115 ++++++++++++++++++ 5 files changed, 352 insertions(+) create mode 100644 exercises/ex05-reminder/README.md create mode 100644 exercises/ex05-reminder/reminder/Cargo.toml create mode 100644 exercises/ex05-reminder/reminder/src/lib.rs create mode 100644 exercises/ex05-reminder/reminder/src/mock.rs create mode 100644 exercises/ex05-reminder/reminder/src/tests.rs diff --git a/exercises/ex05-reminder/README.md b/exercises/ex05-reminder/README.md new file mode 100644 index 0000000..5d37bf4 --- /dev/null +++ b/exercises/ex05-reminder/README.md @@ -0,0 +1,33 @@ +# Hooks + +Substrate offers a way to automatically execute code on some events (when a block is being created or finalized, when there is a runtime upgrade...) allowing to add more specific logic to the chain. + +## What to do? + +The aim of this exercice is to schedule an event to be sent at a specific blocknumber, and notify (by another event) how many scheduled events have been processed every blocks. +For this, you will have a storage map that contains a list of event as value, and a blocknumber as a key. +You will also have a storage that count how many event have been processed, and an extrinsic to schedule events. + +the aim is to use the `on_initialize` hook to first reset the counter, execute events, and increase the new counter, +and the `on_finalize` hook emit the events + +on_initialize also return the weight used in the hook. We placed a first occurence so let you see how it works. +(tip: on_initialize can be done in one read and two writes !) + +We placed some helpful comments in the code 😉. + +You will succeed once every tests passes :). +Launch the tests by running: + +```sh +$ cargo test +``` + +## some links + +* Transaction lifecycle: https://docs.substrate.io/fundamentals/transaction-lifecycle/ +* Hooks technical documentation: https://paritytech.github.io/substrate/master/frame_support/traits/trait.Hooks.html#method.on_idle + +## What to focus on + +Storage and extrinsics are already completed, you only need to focus on the hooks logic. diff --git a/exercises/ex05-reminder/reminder/Cargo.toml b/exercises/ex05-reminder/reminder/Cargo.toml new file mode 100644 index 0000000..f27a064 --- /dev/null +++ b/exercises/ex05-reminder/reminder/Cargo.toml @@ -0,0 +1,42 @@ +[package] +authors = ["Nathan Gardet-Derc"] +edition = "2021" +license = "Apache-2.0" +name = "pallet-reminder" +publish = false +repository = "https://github.com/substrate-developer-hub/substrate-rusty-node/" +version = "0.1.0" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [ + "derive", +] } +scale-info = { version = "2.0.1", default-features = false, features = [ + "derive", +] } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22", optional = true } +frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" } +frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" } +syn = { version = "=1.0.96" } + +[dev-dependencies] +sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" } +sp-io = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" } +sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" } + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-support/std", + "frame-system/std", + "frame-benchmarking/std", +] + +runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"] +try-runtime = ["frame-support/try-runtime"] diff --git a/exercises/ex05-reminder/reminder/src/lib.rs b/exercises/ex05-reminder/reminder/src/lib.rs new file mode 100644 index 0000000..5c3510e --- /dev/null +++ b/exercises/ex05-reminder/reminder/src/lib.rs @@ -0,0 +1,90 @@ +#![cfg_attr(not(feature = "std"), no_std)] + +pub use pallet::*; + +#[cfg(test)] +mod mock; + +#[cfg(test)] +mod tests; + +#[frame_support::pallet] +pub mod pallet { + use super::*; + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type Event: From> + IsType<::Event>; + } + + #[pallet::storage] + #[pallet::getter(fn event_counter)] + pub type EventCounter = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + #[pallet::unbounded] + #[pallet::getter(fn reminders)] + pub type Reminders = + StorageMap<_, Blake2_256, T::BlockNumber, Vec>, ValueQuery>; + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + ReminderSet(T::BlockNumber, Vec), + Reminder(Vec), + RemindersExecuteds(u32), + } + + #[pallet::error] + pub enum Error {} + + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_initialize(n: T::BlockNumber) -> Weight { + let mut used_weight = 0; + let reminders = Self::reminders(n); + used_weight += T::DbWeight::get().reads(1); + let mut event_counter = 0; + + for reminder in reminders { + Self::deposit_event(Event::Reminder(reminder.clone())); + event_counter += 1; + } + >::mutate(|value| *value = event_counter); + used_weight += T::DbWeight::get().writes(1); + + >::remove(n); + used_weight += T::DbWeight::get().writes(1); + + used_weight + } + + fn on_finalize(_: T::BlockNumber) { + let count = Self::event_counter(); + Self::deposit_event(Event::RemindersExecuteds(count)); + } + } + + #[pallet::call] + impl Pallet { + #[pallet::weight(10_000 + T::DbWeight::get().reads(1))] + pub fn schedule_reminder( + origin: OriginFor, + at: T::BlockNumber, + message: Vec, + ) -> DispatchResult { + let _ = ensure_signed(origin)?; + + >::mutate(at, |reminders| reminders.push(message.clone())); + Self::deposit_event(Event::ReminderSet(at, message)); + + Ok(()) + } + } +} diff --git a/exercises/ex05-reminder/reminder/src/mock.rs b/exercises/ex05-reminder/reminder/src/mock.rs new file mode 100644 index 0000000..04f2802 --- /dev/null +++ b/exercises/ex05-reminder/reminder/src/mock.rs @@ -0,0 +1,72 @@ +use crate as pallet_reminder; +use frame_support::{ + parameter_types, + traits::{ConstU16, ConstU64}, + weights::RuntimeDbWeight, +}; +use frame_system as system; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +// Configure a mock runtime to test the pallet. +frame_support::construct_runtime!( + pub enum TestRuntime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Reminder: pallet_reminder, + } +); + +parameter_types! { + pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight {read: 1, write: 10000}; +} + +impl system::Config for TestRuntime { + type AccountData = (); + type AccountId = u64; + type BaseCallFilter = frame_support::traits::Everything; + type BlockHashCount = ConstU64<250>; + type BlockLength = (); + type BlockNumber = u64; + type BlockWeights = (); + type Call = Call; + type DbWeight = DbWeight; + type Event = Event; + type Hash = H256; + type Hashing = BlakeTwo256; + type Header = Header; + type Index = u64; + type Lookup = IdentityLookup; + type MaxConsumers = frame_support::traits::ConstU32<16>; + type OnKilledAccount = (); + type OnNewAccount = (); + type OnSetCode = (); + type Origin = Origin; + type PalletInfo = PalletInfo; + type SS58Prefix = ConstU16<42>; + type SystemWeightInfo = (); + type Version = (); +} + +impl pallet_reminder::Config for TestRuntime { + type Event = Event; +} + +pub fn new_test_ext() -> sp_io::TestExternalities { + let t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +// Mock users AccountId +pub const ALICE: u64 = 1; diff --git a/exercises/ex05-reminder/reminder/src/tests.rs b/exercises/ex05-reminder/reminder/src/tests.rs new file mode 100644 index 0000000..06e2557 --- /dev/null +++ b/exercises/ex05-reminder/reminder/src/tests.rs @@ -0,0 +1,115 @@ +use crate::mock::*; +use frame_support::assert_ok; + +use frame_support::{ + traits::{Get, OnFinalize, OnInitialize}, + weights::RuntimeDbWeight, +}; + +mod mint { + use super::*; + + #[test] + fn schedule() { + new_test_ext().execute_with(|| { + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 1, + "test".as_bytes().to_vec(), + )); + assert_eq!( + Reminder::reminders(1), + vec! { + "test".as_bytes().to_vec() + } + ) + }) + } + + #[test] + fn execution_and_cleanup() { + new_test_ext().execute_with(|| { + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test".as_bytes().to_vec(), + )); + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test2".as_bytes().to_vec(), + )); + >::on_initialize(2); + System::assert_last_event(crate::Event::Reminder("test2".as_bytes().to_vec()).into()); + System::assert_has_event(crate::Event::Reminder("test".as_bytes().to_vec()).into()); + + //check if events have been removed from the storage after being emitted + assert_eq!(Reminder::reminders(2), >>::new()) + }) + } + + #[test] + fn counting_events() { + new_test_ext().execute_with(|| { + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test".as_bytes().to_vec(), + )); + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test2".as_bytes().to_vec(), + )); + >::on_initialize(2); + assert_eq!(Reminder::event_counter(), 2); + >::on_finalize(2); + System::assert_last_event(Event::Reminder(crate::Event::RemindersExecuteds(2))); + }) + } + + #[test] + fn reset_counter() { + new_test_ext().execute_with(|| { + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test".as_bytes().to_vec(), + )); + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test2".as_bytes().to_vec(), + )); + >::on_initialize(2); + >::on_finalize(2); + assert_eq!(Reminder::event_counter(), 2); + >::on_initialize(3); + assert_eq!(Reminder::event_counter(), 0); + }) + } + + #[test] + fn valid_weights() { + new_test_ext().execute_with(|| { + let db_weights: RuntimeDbWeight = + ::DbWeight::get(); + + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test".as_bytes().to_vec(), + )); + assert_ok!(Reminder::schedule_reminder( + Origin::signed(ALICE), + 2, + "test2".as_bytes().to_vec(), + )); + assert_eq!( + >::on_initialize(2), + db_weights.reads_writes(1, 2) + ); + >::on_finalize(2); + }) + } +}