Skip to content

Commit

Permalink
feat: add solution for ex05
Browse files Browse the repository at this point in the history
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 aef97bd.

adding hook exercice (solution)

Revert "adding hook exercice (solution)"

This reverts commit aef97bd.
  • Loading branch information
gdnathan committed Oct 14, 2022
1 parent b608c21 commit 2d1b2c8
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::{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(),
));
<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 2d1b2c8

Please sign in to comment.