Skip to content

Commit

Permalink
Pallet Benchmarking Tutorial (#291)
Browse files Browse the repository at this point in the history
* wip: benchmarking

* Add execute benchmarking

* Pallet benchmarking ready

* Update description

* Update where to go next from Pallet Testing

* Apply suggestions from code review

Co-authored-by: Nicolás Hussein <[email protected]>

* Apply suggestions

* Apply suggestions

* Apply suggestions from code review

Co-authored-by: Erin Shaben <[email protected]>

---------

Co-authored-by: Nicolás Hussein <[email protected]>
Co-authored-by: Erin Shaben <[email protected]>
  • Loading branch information
3 people authored Jan 9, 2025
1 parent 0d1d63b commit 99e38c3
Show file tree
Hide file tree
Showing 10 changed files with 1,128 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//! Benchmarking setup for pallet-template
#![cfg(feature = "runtime-benchmarks")]

use super::{Pallet as CustomPallet, *};
use frame_benchmarking::v2::*;
use frame_support::assert_ok;

#[benchmarks]
mod benchmarks {
use super::*;
#[cfg(test)]
use crate::pallet::Pallet as CustomPallet;
use frame_system::RawOrigin;

#[benchmark]
fn set_counter_value() {
#[extrinsic_call]
set_counter_value(RawOrigin::Root, 5);

assert_eq!(CounterValue::<T>::get(), Some(5u32.into()));
}

#[benchmark]
fn increment() {
let caller: T::AccountId = whitelisted_caller();

assert_ok!(CustomPallet::<T>::set_counter_value(
RawOrigin::Root.into(),
5u32
));

#[extrinsic_call]
increment(RawOrigin::Signed(caller.clone()), 1);

assert_eq!(CounterValue::<T>::get(), Some(6u32.into()));
assert_eq!(UserInteractions::<T>::get(caller), 1u32.into());
}

#[benchmark]
fn decrement() {
let caller: T::AccountId = whitelisted_caller();

assert_ok!(CustomPallet::<T>::set_counter_value(
RawOrigin::Root.into(),
5u32
));

#[extrinsic_call]
decrement(RawOrigin::Signed(caller.clone()), 1);

assert_eq!(CounterValue::<T>::get(), Some(4u32.into()));
assert_eq!(UserInteractions::<T>::get(caller), 1u32.into());
}

impl_benchmark_test_suite!(CustomPallet, crate::mock::new_test_ext(), crate::mock::Test);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// This is free and unencumbered software released into the public domain.
//
// Anyone is free to copy, modify, publish, use, compile, sell, or
// distribute this software, either in source code form or as a compiled
// binary, for any purpose, commercial or non-commercial, and by any
// means.
//
// In jurisdictions that recognize copyright laws, the author or authors
// of this software dedicate any and all copyright interest in the
// software to the public domain. We make this dedication for the benefit
// of the public at large and to the detriment of our heirs and
// successors. We intend this dedication to be an overt act of
// relinquishment in perpetuity of all present and future rights to this
// software under copyright law.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
//
// For more information, please refer to <http://unlicense.org>

frame_benchmarking::define_benchmarks!(
[frame_system, SystemBench::<Runtime>]
[pallet_balances, Balances]
[pallet_session, SessionBench::<Runtime>]
[pallet_timestamp, Timestamp]
[pallet_message_queue, MessageQueue]
[pallet_sudo, Sudo]
[pallet_collator_selection, CollatorSelection]
[cumulus_pallet_parachain_system, ParachainSystem]
[cumulus_pallet_xcmp_queue, XcmpQueue]
[custom_pallet, CustomPallet]
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub use pallet::*;

#[cfg(test)]
mod mock;

#[cfg(test)]
mod tests;

#[cfg(feature = "runtime-benchmarks")]
mod benchmarking;

pub mod weights;
use crate::weights::WeightInfo;

#[frame_support::pallet(dev_mode)]
pub mod pallet {
use super::*;
use frame_support::pallet_prelude::*;
use frame_system::pallet_prelude::*;

#[pallet::pallet]
pub struct Pallet<T>(_);

// Configuration trait for the pallet.
#[pallet::config]
pub trait Config: frame_system::Config {
// Defines the event type for the pallet.
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;

// Defines the maximum value the counter can hold.
#[pallet::constant]
type CounterMaxValue: Get<u32>;

/// A type representing the weights required by the dispatchables of this pallet.
type WeightInfo: WeightInfo;
}

#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// The counter value has been set to a new value by Root.
CounterValueSet {
/// The new value set.
counter_value: u32,
},
/// A user has successfully incremented the counter.
CounterIncremented {
/// The new value set.
counter_value: u32,
/// The account who incremented the counter.
who: T::AccountId,
/// The amount by which the counter was incremented.
incremented_amount: u32,
},
/// A user has successfully decremented the counter.
CounterDecremented {
/// The new value set.
counter_value: u32,
/// The account who decremented the counter.
who: T::AccountId,
/// The amount by which the counter was decremented.
decremented_amount: u32,
},
}

/// Storage for the current value of the counter.
#[pallet::storage]
pub type CounterValue<T> = StorageValue<_, u32>;

/// Storage map to track the number of interactions performed by each account.
#[pallet::storage]
pub type UserInteractions<T: Config> = StorageMap<_, Twox64Concat, T::AccountId, u32>;

#[pallet::error]
pub enum Error<T> {
/// The counter value exceeds the maximum allowed value.
CounterValueExceedsMax,
/// The counter value cannot be decremented below zero.
CounterValueBelowZero,
/// Overflow occurred in the counter.
CounterOverflow,
/// Overflow occurred in user interactions.
UserInteractionOverflow,
}

#[pallet::call]
impl<T: Config> Pallet<T> {
/// Set the value of the counter.
///
/// The dispatch origin of this call must be _Root_.
///
/// - `new_value`: The new value to set for the counter.
///
/// Emits `CounterValueSet` event when successful.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::set_counter_value())]
pub fn set_counter_value(origin: OriginFor<T>, new_value: u32) -> DispatchResult {
ensure_root(origin)?;

ensure!(
new_value <= T::CounterMaxValue::get(),
Error::<T>::CounterValueExceedsMax
);

CounterValue::<T>::put(new_value);

Self::deposit_event(Event::<T>::CounterValueSet {
counter_value: new_value,
});

Ok(())
}

/// Increment the counter by a specified amount.
///
/// This function can be called by any signed account.
///
/// - `amount_to_increment`: The amount by which to increment the counter.
///
/// Emits `CounterIncremented` event when successful.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::increment())]
pub fn increment(origin: OriginFor<T>, amount_to_increment: u32) -> DispatchResult {
let who = ensure_signed(origin)?;

let current_value = CounterValue::<T>::get().unwrap_or(0);

let new_value = current_value
.checked_add(amount_to_increment)
.ok_or(Error::<T>::CounterOverflow)?;

ensure!(
new_value <= T::CounterMaxValue::get(),
Error::<T>::CounterValueExceedsMax
);

CounterValue::<T>::put(new_value);

UserInteractions::<T>::try_mutate(&who, |interactions| -> Result<_, Error<T>> {
let new_interactions = interactions
.unwrap_or(0)
.checked_add(1)
.ok_or(Error::<T>::UserInteractionOverflow)?;
*interactions = Some(new_interactions); // Store the new value.

Ok(())
})?;

Self::deposit_event(Event::<T>::CounterIncremented {
counter_value: new_value,
who,
incremented_amount: amount_to_increment,
});

Ok(())
}

/// Decrement the counter by a specified amount.
///
/// This function can be called by any signed account.
///
/// - `amount_to_decrement`: The amount by which to decrement the counter.
///
/// Emits `CounterDecremented` event when successful.
#[pallet::call_index(2)]
#[pallet::weight(T::WeightInfo::decrement())]
pub fn decrement(origin: OriginFor<T>, amount_to_decrement: u32) -> DispatchResult {
let who = ensure_signed(origin)?;

let current_value = CounterValue::<T>::get().unwrap_or(0);

let new_value = current_value
.checked_sub(amount_to_decrement)
.ok_or(Error::<T>::CounterValueBelowZero)?;

CounterValue::<T>::put(new_value);

UserInteractions::<T>::try_mutate(&who, |interactions| -> Result<_, Error<T>> {
let new_interactions = interactions
.unwrap_or(0)
.checked_add(1)
.ok_or(Error::<T>::UserInteractionOverflow)?;
*interactions = Some(new_interactions); // Store the new value.

Ok(())
})?;

Self::deposit_event(Event::<T>::CounterDecremented {
counter_value: new_value,
who,
decremented_amount: amount_to_decrement,
});

Ok(())
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate as custom_pallet;
use frame_support::{derive_impl, parameter_types};
use sp_runtime::BuildStorage;

type Block = frame_system::mocking::MockBlock<Test>;

#[frame_support::runtime]
mod runtime {
#[runtime::runtime]
#[runtime::derive(
RuntimeCall,
RuntimeEvent,
RuntimeError,
RuntimeOrigin,
RuntimeFreezeReason,
RuntimeHoldReason,
RuntimeSlashReason,
RuntimeLockId,
RuntimeTask
)]
pub struct Test;

#[runtime::pallet_index(0)]
pub type System = frame_system::Pallet<Test>;

#[runtime::pallet_index(1)]
pub type CustomPallet = custom_pallet::Pallet<Test>;
}

// System pallet configuration
#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
impl frame_system::Config for Test {
type Block = Block;
}

// Custom pallet configuration.
parameter_types! {
pub const CounterMaxValue: u32 = 10;
}

impl custom_pallet::Config for Test {
type RuntimeEvent = RuntimeEvent;
type CounterMaxValue = CounterMaxValue;
type WeightInfo = custom_pallet::weights::SubstrateWeight<Test>;
}

// Test externalities initialization.
pub fn new_test_ext() -> sp_io::TestExternalities {
frame_system::GenesisConfig::<Test>::default()
.build_storage()
.unwrap()
.into()
}
Loading

0 comments on commit 99e38c3

Please sign in to comment.