-
Notifications
You must be signed in to change notification settings - Fork 114
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
352 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<T>(_); | ||
|
||
#[pallet::config] | ||
pub trait Config: frame_system::Config { | ||
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>; | ||
} | ||
|
||
#[pallet::storage] | ||
#[pallet::getter(fn event_counter)] | ||
pub type EventCounter<T> = StorageValue<_, u32, ValueQuery>; | ||
|
||
#[pallet::storage] | ||
#[pallet::unbounded] | ||
#[pallet::getter(fn reminders)] | ||
pub type Reminders<T: Config> = | ||
StorageMap<_, Blake2_256, T::BlockNumber, Vec<Vec<u8>>, ValueQuery>; | ||
|
||
#[pallet::event] | ||
#[pallet::generate_deposit(pub(super) fn deposit_event)] | ||
pub enum Event<T: Config> { | ||
ReminderSet(T::BlockNumber, Vec<u8>), | ||
Reminder(Vec<u8>), | ||
RemindersExecuteds(u32), | ||
} | ||
|
||
#[pallet::error] | ||
pub enum Error<T> {} | ||
|
||
#[pallet::hooks] | ||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { | ||
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; | ||
} | ||
<EventCounter<T>>::mutate(|value| *value = event_counter); | ||
used_weight += T::DbWeight::get().writes(1); | ||
|
||
<Reminders<T>>::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<T: Config> Pallet<T> { | ||
#[pallet::weight(10_000 + T::DbWeight::get().reads(1))] | ||
pub fn schedule_reminder( | ||
origin: OriginFor<T>, | ||
at: T::BlockNumber, | ||
message: Vec<u8>, | ||
) -> DispatchResult { | ||
let _ = ensure_signed(origin)?; | ||
|
||
<Reminders<T>>::mutate(at, |reminders| reminders.push(message.clone())); | ||
Self::deposit_event(Event::ReminderSet(at, message)); | ||
|
||
Ok(()) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<TestRuntime>; | ||
type Block = frame_system::mocking::MockBlock<TestRuntime>; | ||
|
||
// 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<Self::AccountId>; | ||
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::<TestRuntime>().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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
use crate::mock::*; | ||
use frame_support::assert_ok; | ||
|
||
use frame_support::{ | ||
traits::{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(), | ||
)); | ||
<Reminder as OnInitialize<u64>>::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), <Vec<Vec<u8>>>::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(), | ||
)); | ||
<Reminder as OnInitialize<u64>>::on_initialize(2); | ||
assert_eq!(Reminder::event_counter(), 2); | ||
<Reminder as OnFinalize<u64>>::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(), | ||
)); | ||
<Reminder as OnInitialize<u64>>::on_initialize(2); | ||
<Reminder as OnFinalize<u64>>::on_finalize(2); | ||
assert_eq!(Reminder::event_counter(), 2); | ||
<Reminder as OnInitialize<u64>>::on_initialize(3); | ||
assert_eq!(Reminder::event_counter(), 0); | ||
}) | ||
} | ||
|
||
#[test] | ||
fn valid_weights() { | ||
new_test_ext().execute_with(|| { | ||
let db_weights: RuntimeDbWeight = | ||
<TestRuntime as frame_system::Config>::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!( | ||
<Reminder as OnInitialize<u64>>::on_initialize(2), | ||
db_weights.reads_writes(1, 2) | ||
); | ||
<Reminder as OnFinalize<u64>>::on_finalize(2); | ||
}) | ||
} | ||
} |