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); + }) + } +}