Skip to content

Commit dcc7afe

Browse files
committed
feat: add solution for ex05
1 parent b608c21 commit dcc7afe

File tree

5 files changed

+352
-0
lines changed

5 files changed

+352
-0
lines changed

exercises/ex05-reminder/README.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Hooks
2+
3+
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.
4+
5+
## What to do?
6+
7+
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.
8+
For this, you will have a storage map that contains a list of event as value, and a blocknumber as a key.
9+
You will also have a storage that count how many event have been processed, and an extrinsic to schedule events.
10+
11+
the aim is to use the `on_initialize` hook to first reset the counter, execute events, and increase the new counter,
12+
and the `on_finalize` hook emit the events
13+
14+
on_initialize also return the weight used in the hook. We placed a first occurence so let you see how it works.
15+
(tip: on_initialize can be done in one read and two writes !)
16+
17+
We placed some helpful comments in the code 😉.
18+
19+
You will succeed once every tests passes :).
20+
Launch the tests by running:
21+
22+
```sh
23+
$ cargo test
24+
```
25+
26+
## some links
27+
28+
* Transaction lifecycle: https://docs.substrate.io/fundamentals/transaction-lifecycle/
29+
* Hooks technical documentation: https://paritytech.github.io/substrate/master/frame_support/traits/trait.Hooks.html#method.on_idle
30+
31+
## What to focus on
32+
33+
Storage and extrinsics are already completed, you only need to focus on the hooks logic.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
[package]
2+
authors = ["Nathan Gardet-Derc"]
3+
edition = "2021"
4+
license = "Apache-2.0"
5+
name = "pallet-reminder"
6+
publish = false
7+
repository = "https://github.com/substrate-developer-hub/substrate-rusty-node/"
8+
version = "0.1.0"
9+
10+
[package.metadata.docs.rs]
11+
targets = ["x86_64-unknown-linux-gnu"]
12+
13+
[dependencies]
14+
codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = [
15+
"derive",
16+
] }
17+
scale-info = { version = "2.0.1", default-features = false, features = [
18+
"derive",
19+
] }
20+
21+
frame-benchmarking = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22", optional = true }
22+
frame-support = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" }
23+
frame-system = { version = "4.0.0-dev", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" }
24+
syn = { version = "=1.0.96" }
25+
26+
[dev-dependencies]
27+
sp-core = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" }
28+
sp-io = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" }
29+
sp-runtime = { version = "6.0.0", default-features = false, git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.22" }
30+
31+
[features]
32+
default = ["std"]
33+
std = [
34+
"codec/std",
35+
"scale-info/std",
36+
"frame-support/std",
37+
"frame-system/std",
38+
"frame-benchmarking/std",
39+
]
40+
41+
runtime-benchmarks = ["frame-benchmarking/runtime-benchmarks"]
42+
try-runtime = ["frame-support/try-runtime"]
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#![cfg_attr(not(feature = "std"), no_std)]
2+
3+
pub use pallet::*;
4+
5+
#[cfg(test)]
6+
mod mock;
7+
8+
#[cfg(test)]
9+
mod tests;
10+
11+
#[frame_support::pallet]
12+
pub mod pallet {
13+
use super::*;
14+
use frame_support::pallet_prelude::*;
15+
use frame_system::pallet_prelude::*;
16+
17+
#[pallet::pallet]
18+
#[pallet::generate_store(pub(super) trait Store)]
19+
pub struct Pallet<T>(_);
20+
21+
#[pallet::config]
22+
pub trait Config: frame_system::Config {
23+
type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
24+
}
25+
26+
#[pallet::storage]
27+
#[pallet::getter(fn event_counter)]
28+
pub type EventCounter<T> = StorageValue<_, u32, ValueQuery>;
29+
30+
#[pallet::storage]
31+
#[pallet::unbounded]
32+
#[pallet::getter(fn reminders)]
33+
pub type Reminders<T: Config> =
34+
StorageMap<_, Blake2_256, T::BlockNumber, Vec<Vec<u8>>, ValueQuery>;
35+
36+
#[pallet::event]
37+
#[pallet::generate_deposit(pub(super) fn deposit_event)]
38+
pub enum Event<T: Config> {
39+
ReminderSet(T::BlockNumber, Vec<u8>),
40+
Reminder(Vec<u8>),
41+
RemindersExecuteds(u32),
42+
}
43+
44+
#[pallet::error]
45+
pub enum Error<T> {}
46+
47+
#[pallet::hooks]
48+
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
49+
fn on_initialize(n: T::BlockNumber) -> Weight {
50+
let mut used_weight = 0;
51+
let reminders = Self::reminders(n);
52+
used_weight += T::DbWeight::get().reads(1);
53+
let mut event_counter = 0;
54+
55+
for reminder in reminders {
56+
Self::deposit_event(Event::Reminder(reminder.clone()));
57+
event_counter += 1;
58+
}
59+
<EventCounter<T>>::mutate(|value| *value = event_counter);
60+
used_weight += T::DbWeight::get().writes(1);
61+
62+
<Reminders<T>>::remove(n);
63+
used_weight += T::DbWeight::get().writes(1);
64+
65+
used_weight
66+
}
67+
68+
fn on_finalize(_: T::BlockNumber) {
69+
let count = Self::event_counter();
70+
Self::deposit_event(Event::RemindersExecuteds(count));
71+
}
72+
}
73+
74+
#[pallet::call]
75+
impl<T: Config> Pallet<T> {
76+
#[pallet::weight(10_000 + T::DbWeight::get().reads(1))]
77+
pub fn schedule_reminder(
78+
origin: OriginFor<T>,
79+
at: T::BlockNumber,
80+
message: Vec<u8>,
81+
) -> DispatchResult {
82+
let _ = ensure_signed(origin)?;
83+
84+
<Reminders<T>>::mutate(at, |reminders| reminders.push(message.clone()));
85+
Self::deposit_event(Event::ReminderSet(at, message));
86+
87+
Ok(())
88+
}
89+
}
90+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate as pallet_reminder;
2+
use frame_support::{
3+
parameter_types,
4+
traits::{ConstU16, ConstU64},
5+
weights::RuntimeDbWeight,
6+
};
7+
use frame_system as system;
8+
use sp_core::H256;
9+
use sp_runtime::{
10+
testing::Header,
11+
traits::{BlakeTwo256, IdentityLookup},
12+
};
13+
14+
type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
15+
type Block = frame_system::mocking::MockBlock<TestRuntime>;
16+
17+
// Configure a mock runtime to test the pallet.
18+
frame_support::construct_runtime!(
19+
pub enum TestRuntime where
20+
Block = Block,
21+
NodeBlock = Block,
22+
UncheckedExtrinsic = UncheckedExtrinsic,
23+
{
24+
System: frame_system,
25+
Reminder: pallet_reminder,
26+
}
27+
);
28+
29+
parameter_types! {
30+
pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight {read: 1, write: 10000};
31+
}
32+
33+
impl system::Config for TestRuntime {
34+
type AccountData = ();
35+
type AccountId = u64;
36+
type BaseCallFilter = frame_support::traits::Everything;
37+
type BlockHashCount = ConstU64<250>;
38+
type BlockLength = ();
39+
type BlockNumber = u64;
40+
type BlockWeights = ();
41+
type Call = Call;
42+
type DbWeight = DbWeight;
43+
type Event = Event;
44+
type Hash = H256;
45+
type Hashing = BlakeTwo256;
46+
type Header = Header;
47+
type Index = u64;
48+
type Lookup = IdentityLookup<Self::AccountId>;
49+
type MaxConsumers = frame_support::traits::ConstU32<16>;
50+
type OnKilledAccount = ();
51+
type OnNewAccount = ();
52+
type OnSetCode = ();
53+
type Origin = Origin;
54+
type PalletInfo = PalletInfo;
55+
type SS58Prefix = ConstU16<42>;
56+
type SystemWeightInfo = ();
57+
type Version = ();
58+
}
59+
60+
impl pallet_reminder::Config for TestRuntime {
61+
type Event = Event;
62+
}
63+
64+
pub fn new_test_ext() -> sp_io::TestExternalities {
65+
let t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
66+
let mut ext = sp_io::TestExternalities::new(t);
67+
ext.execute_with(|| System::set_block_number(1));
68+
ext
69+
}
70+
71+
// Mock users AccountId
72+
pub const ALICE: u64 = 1;
Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
use crate::mock::*;
2+
use frame_support::assert_ok;
3+
4+
use frame_support::{
5+
traits::{OnFinalize, OnInitialize},
6+
weights::RuntimeDbWeight,
7+
};
8+
9+
mod mint {
10+
use super::*;
11+
12+
#[test]
13+
fn schedule() {
14+
new_test_ext().execute_with(|| {
15+
assert_ok!(Reminder::schedule_reminder(
16+
Origin::signed(ALICE),
17+
1,
18+
"test".as_bytes().to_vec(),
19+
));
20+
assert_eq!(
21+
Reminder::reminders(1),
22+
vec! {
23+
"test".as_bytes().to_vec()
24+
}
25+
)
26+
})
27+
}
28+
29+
#[test]
30+
fn execution_and_cleanup() {
31+
new_test_ext().execute_with(|| {
32+
assert_ok!(Reminder::schedule_reminder(
33+
Origin::signed(ALICE),
34+
2,
35+
"test".as_bytes().to_vec(),
36+
));
37+
assert_ok!(Reminder::schedule_reminder(
38+
Origin::signed(ALICE),
39+
2,
40+
"test2".as_bytes().to_vec(),
41+
));
42+
<Reminder as OnInitialize<u64>>::on_initialize(2);
43+
System::assert_last_event(crate::Event::Reminder("test2".as_bytes().to_vec()).into());
44+
System::assert_has_event(crate::Event::Reminder("test".as_bytes().to_vec()).into());
45+
46+
//check if events have been removed from the storage after being emitted
47+
assert_eq!(Reminder::reminders(2), <Vec<Vec<u8>>>::new())
48+
})
49+
}
50+
51+
#[test]
52+
fn counting_events() {
53+
new_test_ext().execute_with(|| {
54+
assert_ok!(Reminder::schedule_reminder(
55+
Origin::signed(ALICE),
56+
2,
57+
"test".as_bytes().to_vec(),
58+
));
59+
assert_ok!(Reminder::schedule_reminder(
60+
Origin::signed(ALICE),
61+
2,
62+
"test2".as_bytes().to_vec(),
63+
));
64+
<Reminder as OnInitialize<u64>>::on_initialize(2);
65+
assert_eq!(Reminder::event_counter(), 2);
66+
<Reminder as OnFinalize<u64>>::on_finalize(2);
67+
System::assert_last_event(Event::Reminder(crate::Event::RemindersExecuteds(2)));
68+
})
69+
}
70+
71+
#[test]
72+
fn reset_counter() {
73+
new_test_ext().execute_with(|| {
74+
assert_ok!(Reminder::schedule_reminder(
75+
Origin::signed(ALICE),
76+
2,
77+
"test".as_bytes().to_vec(),
78+
));
79+
assert_ok!(Reminder::schedule_reminder(
80+
Origin::signed(ALICE),
81+
2,
82+
"test2".as_bytes().to_vec(),
83+
));
84+
<Reminder as OnInitialize<u64>>::on_initialize(2);
85+
<Reminder as OnFinalize<u64>>::on_finalize(2);
86+
assert_eq!(Reminder::event_counter(), 2);
87+
<Reminder as OnInitialize<u64>>::on_initialize(3);
88+
assert_eq!(Reminder::event_counter(), 0);
89+
})
90+
}
91+
92+
#[test]
93+
fn valid_weights() {
94+
new_test_ext().execute_with(|| {
95+
let db_weights: RuntimeDbWeight =
96+
<TestRuntime as frame_system::Config>::DbWeight::get();
97+
98+
assert_ok!(Reminder::schedule_reminder(
99+
Origin::signed(ALICE),
100+
2,
101+
"test".as_bytes().to_vec(),
102+
));
103+
assert_ok!(Reminder::schedule_reminder(
104+
Origin::signed(ALICE),
105+
2,
106+
"test2".as_bytes().to_vec(),
107+
));
108+
assert_eq!(
109+
<Reminder as OnInitialize<u64>>::on_initialize(2),
110+
db_weights.reads_writes(1, 2)
111+
);
112+
<Reminder as OnFinalize<u64>>::on_finalize(2);
113+
})
114+
}
115+
}

0 commit comments

Comments
 (0)