Skip to content

Commit

Permalink
feat: add solution for ex05
Browse files Browse the repository at this point in the history
  • Loading branch information
gdnathan committed Oct 14, 2022
1 parent b608c21 commit dcc7afe
Show file tree
Hide file tree
Showing 5 changed files with 352 additions and 0 deletions.
33 changes: 33 additions & 0 deletions exercises/ex05-reminder/README.md
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.
42 changes: 42 additions & 0 deletions exercises/ex05-reminder/reminder/Cargo.toml
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"]
90 changes: 90 additions & 0 deletions exercises/ex05-reminder/reminder/src/lib.rs
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(())
}
}
}
72 changes: 72 additions & 0 deletions exercises/ex05-reminder/reminder/src/mock.rs
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;
115 changes: 115 additions & 0 deletions exercises/ex05-reminder/reminder/src/tests.rs
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);
})
}
}

0 comments on commit dcc7afe

Please sign in to comment.