diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarking.rs b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarking.rs new file mode 100644 index 00000000..4951522c --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarking.rs @@ -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::::get(), Some(5u32.into())); + } + + #[benchmark] + fn increment() { + let caller: T::AccountId = whitelisted_caller(); + + assert_ok!(CustomPallet::::set_counter_value( + RawOrigin::Root.into(), + 5u32 + )); + + #[extrinsic_call] + increment(RawOrigin::Signed(caller.clone()), 1); + + assert_eq!(CounterValue::::get(), Some(6u32.into())); + assert_eq!(UserInteractions::::get(caller), 1u32.into()); + } + + #[benchmark] + fn decrement() { + let caller: T::AccountId = whitelisted_caller(); + + assert_ok!(CustomPallet::::set_counter_value( + RawOrigin::Root.into(), + 5u32 + )); + + #[extrinsic_call] + decrement(RawOrigin::Signed(caller.clone()), 1); + + assert_eq!(CounterValue::::get(), Some(4u32.into())); + assert_eq!(UserInteractions::::get(caller), 1u32.into()); + } + + impl_benchmark_test_suite!(CustomPallet, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarks.rs b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarks.rs new file mode 100644 index 00000000..48dbd73e --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarks.rs @@ -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 + +frame_benchmarking::define_benchmarks!( + [frame_system, SystemBench::] + [pallet_balances, Balances] + [pallet_session, SessionBench::] + [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] +); diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs new file mode 100644 index 00000000..7405ce1d --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs @@ -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(_); + + // Configuration trait for the pallet. + #[pallet::config] + pub trait Config: frame_system::Config { + // Defines the event type for the pallet. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Defines the maximum value the counter can hold. + #[pallet::constant] + type CounterMaxValue: Get; + + /// 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 { + /// 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 = StorageValue<_, u32>; + + /// Storage map to track the number of interactions performed by each account. + #[pallet::storage] + pub type UserInteractions = StorageMap<_, Twox64Concat, T::AccountId, u32>; + + #[pallet::error] + pub enum Error { + /// 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 Pallet { + /// 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, new_value: u32) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + new_value <= T::CounterMaxValue::get(), + Error::::CounterValueExceedsMax + ); + + CounterValue::::put(new_value); + + Self::deposit_event(Event::::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, amount_to_increment: u32) -> DispatchResult { + let who = ensure_signed(origin)?; + + let current_value = CounterValue::::get().unwrap_or(0); + + let new_value = current_value + .checked_add(amount_to_increment) + .ok_or(Error::::CounterOverflow)?; + + ensure!( + new_value <= T::CounterMaxValue::get(), + Error::::CounterValueExceedsMax + ); + + CounterValue::::put(new_value); + + UserInteractions::::try_mutate(&who, |interactions| -> Result<_, Error> { + let new_interactions = interactions + .unwrap_or(0) + .checked_add(1) + .ok_or(Error::::UserInteractionOverflow)?; + *interactions = Some(new_interactions); // Store the new value. + + Ok(()) + })?; + + Self::deposit_event(Event::::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, amount_to_decrement: u32) -> DispatchResult { + let who = ensure_signed(origin)?; + + let current_value = CounterValue::::get().unwrap_or(0); + + let new_value = current_value + .checked_sub(amount_to_decrement) + .ok_or(Error::::CounterValueBelowZero)?; + + CounterValue::::put(new_value); + + UserInteractions::::try_mutate(&who, |interactions| -> Result<_, Error> { + let new_interactions = interactions + .unwrap_or(0) + .checked_add(1) + .ok_or(Error::::UserInteractionOverflow)?; + *interactions = Some(new_interactions); // Store the new value. + + Ok(()) + })?; + + Self::deposit_event(Event::::CounterDecremented { + counter_value: new_value, + who, + decremented_amount: amount_to_decrement, + }); + + Ok(()) + } + } +} diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mock.rs b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mock.rs new file mode 100644 index 00000000..47f8e1cc --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mock.rs @@ -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; + +#[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; + + #[runtime::pallet_index(1)] + pub type CustomPallet = custom_pallet::Pallet; +} + +// 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 externalities initialization. +pub fn new_test_ext() -> sp_io::TestExternalities { + frame_system::GenesisConfig::::default() + .build_storage() + .unwrap() + .into() +} \ No newline at end of file diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mod.rs b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mod.rs new file mode 100644 index 00000000..fed95842 --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mod.rs @@ -0,0 +1,332 @@ +// 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 + +mod xcm_config; + +// Substrate and Polkadot dependencies +use cumulus_pallet_parachain_system::RelayNumberMonotonicallyIncreases; +use cumulus_primitives_core::{AggregateMessageOrigin, ParaId}; +use frame_support::{ + derive_impl, + dispatch::DispatchClass, + parameter_types, + traits::{ + ConstBool, ConstU32, ConstU64, ConstU8, EitherOfDiverse, TransformOrigin, VariantCountOf, + }, + weights::{ConstantMultiplier, Weight}, + PalletId, +}; +use frame_system::{ + limits::{BlockLength, BlockWeights}, + EnsureRoot, +}; +use pallet_xcm::{EnsureXcm, IsVoiceOfBody}; +use parachains_common::message_queue::{NarrowOriginToSibling, ParaIdToSibling}; +use polkadot_runtime_common::{ + xcm_sender::NoPriceForMessageDelivery, BlockHashCount, SlowAdjustingFeeUpdate, +}; +use sp_consensus_aura::sr25519::AuthorityId as AuraId; +use sp_runtime::Perbill; +use sp_version::RuntimeVersion; +use xcm::latest::prelude::BodyId; + +// Local module imports +use super::OriginCaller; +use super::{ + weights::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight}, + AccountId, Aura, Balance, Balances, Block, BlockNumber, CollatorSelection, ConsensusHook, Hash, + MessageQueue, Nonce, PalletInfo, ParachainSystem, Runtime, RuntimeCall, RuntimeEvent, + RuntimeFreezeReason, RuntimeHoldReason, RuntimeOrigin, RuntimeTask, Session, SessionKeys, + System, WeightToFee, XcmpQueue, AVERAGE_ON_INITIALIZE_RATIO, EXISTENTIAL_DEPOSIT, HOURS, + MAXIMUM_BLOCK_WEIGHT, MICRO_UNIT, NORMAL_DISPATCH_RATIO, SLOT_DURATION, VERSION, +}; +use xcm_config::{RelayLocation, XcmOriginToTransactDispatchOrigin}; + +parameter_types! { + pub const Version: RuntimeVersion = VERSION; + + // This part is copied from Substrate's `bin/node/runtime/src/lib.rs`. + // The `RuntimeBlockLength` and `RuntimeBlockWeights` exist here because the + // `DeletionWeightLimit` and `DeletionQueueDepth` depend on those to parameterize + // the lazy contract deletion. + pub RuntimeBlockLength: BlockLength = + BlockLength::max_with_normal_ratio(5 * 1024 * 1024, NORMAL_DISPATCH_RATIO); + pub RuntimeBlockWeights: BlockWeights = BlockWeights::builder() + .base_block(BlockExecutionWeight::get()) + .for_class(DispatchClass::all(), |weights| { + weights.base_extrinsic = ExtrinsicBaseWeight::get(); + }) + .for_class(DispatchClass::Normal, |weights| { + weights.max_total = Some(NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT); + }) + .for_class(DispatchClass::Operational, |weights| { + weights.max_total = Some(MAXIMUM_BLOCK_WEIGHT); + // Operational transactions have some extra reserved space, so that they + // are included even if block reached `MAXIMUM_BLOCK_WEIGHT`. + weights.reserved = Some( + MAXIMUM_BLOCK_WEIGHT - NORMAL_DISPATCH_RATIO * MAXIMUM_BLOCK_WEIGHT + ); + }) + .avg_block_initialization(AVERAGE_ON_INITIALIZE_RATIO) + .build_or_panic(); + pub const SS58Prefix: u16 = 42; +} + +/// The default types are being injected by [`derive_impl`](`frame_support::derive_impl`) from +/// [`ParaChainDefaultConfig`](`struct@frame_system::config_preludes::ParaChainDefaultConfig`), +/// but overridden as needed. +#[derive_impl(frame_system::config_preludes::ParaChainDefaultConfig)] +impl frame_system::Config for Runtime { + /// The identifier used to distinguish between accounts. + type AccountId = AccountId; + /// The index type for storing how many extrinsics an account has signed. + type Nonce = Nonce; + /// The type for hashing blocks and tries. + type Hash = Hash; + /// The block type. + type Block = Block; + /// Maximum number of block number to block hash mappings to keep (oldest pruned first). + type BlockHashCount = BlockHashCount; + /// Runtime version. + type Version = Version; + /// The data to be stored in an account. + type AccountData = pallet_balances::AccountData; + /// The weight of database operations that the runtime can invoke. + type DbWeight = RocksDbWeight; + /// Block & extrinsics weights: base values and limits. + type BlockWeights = RuntimeBlockWeights; + /// The maximum length of a block (in bytes). + type BlockLength = RuntimeBlockLength; + /// This is used as an identifier of the chain. 42 is the generic substrate prefix. + type SS58Prefix = SS58Prefix; + /// The action to take on a Runtime Upgrade + //type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type OnSetCode = cumulus_pallet_parachain_system::ParachainSetCode; + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_timestamp::Config for Runtime { + /// A timestamp: milliseconds since the unix epoch. + type Moment = u64; + type OnTimestampSet = Aura; + type MinimumPeriod = ConstU64<0>; + type WeightInfo = (); +} + +impl pallet_authorship::Config for Runtime { + type FindAuthor = pallet_session::FindAccountFromAuthorIndex; + type EventHandler = (CollatorSelection,); +} + +parameter_types! { + pub const ExistentialDeposit: Balance = EXISTENTIAL_DEPOSIT; +} + +impl pallet_balances::Config for Runtime { + type MaxLocks = ConstU32<50>; + /// The type for recording an account's balance. + type Balance = Balance; + /// The ubiquitous event type. + type RuntimeEvent = RuntimeEvent; + type DustRemoval = (); + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = pallet_balances::weights::SubstrateWeight; + type MaxReserves = ConstU32<50>; + type ReserveIdentifier = [u8; 8]; + type RuntimeHoldReason = RuntimeHoldReason; + type RuntimeFreezeReason = RuntimeFreezeReason; + type FreezeIdentifier = RuntimeFreezeReason; + type MaxFreezes = VariantCountOf; +} + +parameter_types! { + /// Relay Chain `TransactionByteFee` / 10 + pub const TransactionByteFee: Balance = 10 * MICRO_UNIT; +} + +impl pallet_transaction_payment::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type OnChargeTransaction = pallet_transaction_payment::FungibleAdapter; + type WeightToFee = WeightToFee; + type LengthToFee = ConstantMultiplier; + type FeeMultiplierUpdate = SlowAdjustingFeeUpdate; + type OperationalFeeMultiplier = ConstU8<5>; +} + +impl pallet_sudo::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type WeightInfo = (); +} + +parameter_types! { + pub const ReservedXcmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const ReservedDmpWeight: Weight = MAXIMUM_BLOCK_WEIGHT.saturating_div(4); + pub const RelayOrigin: AggregateMessageOrigin = AggregateMessageOrigin::Parent; +} + +impl cumulus_pallet_parachain_system::Config for Runtime { + type WeightInfo = (); + type RuntimeEvent = RuntimeEvent; + type OnSystemEvent = (); + type SelfParaId = parachain_info::Pallet; + type OutboundXcmpMessageSource = XcmpQueue; + type DmpQueue = frame_support::traits::EnqueueWithOrigin; + type ReservedDmpWeight = ReservedDmpWeight; + type XcmpMessageHandler = XcmpQueue; + type ReservedXcmpWeight = ReservedXcmpWeight; + type CheckAssociatedRelayNumber = RelayNumberMonotonicallyIncreases; + type ConsensusHook = ConsensusHook; +} + +impl parachain_info::Config for Runtime {} + +parameter_types! { + pub MessageQueueServiceWeight: Weight = Perbill::from_percent(35) * RuntimeBlockWeights::get().max_block; +} + +impl pallet_message_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + #[cfg(feature = "runtime-benchmarks")] + type MessageProcessor = pallet_message_queue::mock_helpers::NoopMessageProcessor< + cumulus_primitives_core::AggregateMessageOrigin, + >; + #[cfg(not(feature = "runtime-benchmarks"))] + type MessageProcessor = xcm_builder::ProcessXcmMessage< + AggregateMessageOrigin, + xcm_executor::XcmExecutor, + RuntimeCall, + >; + type Size = u32; + // The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin: + type QueueChangeHandler = NarrowOriginToSibling; + type QueuePausedQuery = NarrowOriginToSibling; + type HeapSize = sp_core::ConstU32<{ 103 * 1024 }>; + type MaxStale = sp_core::ConstU32<8>; + type ServiceWeight = MessageQueueServiceWeight; + type IdleMaxServiceWeight = (); +} + +impl cumulus_pallet_aura_ext::Config for Runtime {} + +impl cumulus_pallet_xcmp_queue::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ChannelInfo = ParachainSystem; + type VersionWrapper = (); + // Enqueue XCMP messages from siblings for later processing. + type XcmpQueue = TransformOrigin; + type MaxInboundSuspended = sp_core::ConstU32<1_000>; + type MaxActiveOutboundChannels = ConstU32<128>; + type MaxPageSize = ConstU32<{ 1 << 16 }>; + type ControllerOrigin = EnsureRoot; + type ControllerOriginConverter = XcmOriginToTransactDispatchOrigin; + type WeightInfo = (); + type PriceForSiblingDelivery = NoPriceForMessageDelivery; +} + +parameter_types! { + pub const Period: u32 = 6 * HOURS; + pub const Offset: u32 = 0; +} + +impl pallet_session::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type ValidatorId = ::AccountId; + // we don't have stash and controller, thus we don't need the convert as well. + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ShouldEndSession = pallet_session::PeriodicSessions; + type NextSessionRotation = pallet_session::PeriodicSessions; + type SessionManager = CollatorSelection; + // Essentially just Aura, but let's be pedantic. + type SessionHandler = ::KeyTypeIdProviders; + type Keys = SessionKeys; + type WeightInfo = (); +} + +#[docify::export(aura_config)] +impl pallet_aura::Config for Runtime { + type AuthorityId = AuraId; + type DisabledValidators = (); + type MaxAuthorities = ConstU32<100_000>; + type AllowMultipleBlocksPerSlot = ConstBool; + type SlotDuration = ConstU64; +} + +parameter_types! { + pub const PotId: PalletId = PalletId(*b"PotStake"); + pub const SessionLength: BlockNumber = 6 * HOURS; + // StakingAdmin pluralistic body. + pub const StakingAdminBodyId: BodyId = BodyId::Defense; +} + +/// We allow root and the StakingAdmin to execute privileged collator selection operations. +pub type CollatorSelectionUpdateOrigin = EitherOfDiverse< + EnsureRoot, + EnsureXcm>, +>; + +impl pallet_collator_selection::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type Currency = Balances; + type UpdateOrigin = CollatorSelectionUpdateOrigin; + type PotId = PotId; + type MaxCandidates = ConstU32<100>; + type MinEligibleCollators = ConstU32<4>; + type MaxInvulnerables = ConstU32<20>; + // Should be a multiple of session or things will get inconsistent. + type KickThreshold = Period; + type ValidatorId = ::AccountId; + type ValidatorIdOf = pallet_collator_selection::IdentityCollator; + type ValidatorRegistration = Session; + type WeightInfo = (); +} + +/// Configure the pallet template in pallets/template. +impl pallet_parachain_template::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = pallet_parachain_template::weights::SubstrateWeight; +} + +// Configure utility pallet. +impl pallet_utility::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = pallet_utility::weights::SubstrateWeight; +} + +// Define counter max value runtime constant. +parameter_types! { + pub const CounterMaxValue: u32 = 500; +} + +// Configure custom pallet. +impl custom_pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type CounterMaxValue = CounterMaxValue; + type WeightInfo = custom_pallet::weights::SubstrateWeight; +} diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml new file mode 100644 index 00000000..333c4d51 --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml @@ -0,0 +1,36 @@ +[package] +name = "custom-pallet" +version = "0.1.0" +license.workspace = true +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true + +[dependencies] +codec = { features = ["derive"], workspace = true } +scale-info = { features = ["derive"], workspace = true } +frame-support.workspace = true +frame-system.workspace = true +frame-benchmarking = { optional = true, workspace = true } + +[dev-dependencies] +sp-core = { workspace = true, default-features = true } +sp-io = { workspace = true, default-features = true } +sp-runtime = { workspace = true, default-features = true } + +[features] +default = ["std"] +std = [ + "codec/std", + "frame-support/std", + "frame-system/std", + "scale-info/std", + "frame-benchmarking?/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", +] diff --git a/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/runtime-cargo.toml b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/runtime-cargo.toml new file mode 100644 index 00000000..6670d815 --- /dev/null +++ b/.snippets/code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/runtime-cargo.toml @@ -0,0 +1,199 @@ +[package] +name = "parachain-template-runtime" +description = "A parachain runtime template built with Substrate and Cumulus, part of Polkadot Sdk." +version = "0.1.0" +license = "Unlicense" +authors.workspace = true +homepage.workspace = true +repository.workspace = true +edition.workspace = true +publish = false + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[build-dependencies] +substrate-wasm-builder = { optional = true, workspace = true, default-features = true } +docify = { workspace = true } + +[dependencies] +codec = { features = ["derive"], workspace = true } +hex-literal = { optional = true, workspace = true, default-features = true } +log = { workspace = true } +scale-info = { features = ["derive"], workspace = true } +smallvec = { workspace = true, default-features = true } +docify = { workspace = true } +serde_json = { workspace = true, default-features = false } +pallet-parachain-template.workspace = true +frame-benchmarking = { optional = true, workspace = true } +frame-executive.workspace = true +frame-metadata-hash-extension.workspace = true +frame-support = { features = ["experimental"], workspace = true } +frame-system.workspace = true +frame-system-benchmarking = { optional = true, workspace = true } +frame-system-rpc-runtime-api.workspace = true +frame-try-runtime = { optional = true, workspace = true } +pallet-aura.workspace = true +pallet-authorship.workspace = true +pallet-balances.workspace = true +pallet-message-queue.workspace = true +pallet-session.workspace = true +pallet-sudo.workspace = true +pallet-timestamp.workspace = true +pallet-transaction-payment.workspace = true +pallet-transaction-payment-rpc-runtime-api.workspace = true +sp-api.workspace = true +sp-block-builder.workspace = true +sp-consensus-aura.workspace = true +sp-core.workspace = true +sp-genesis-builder.workspace = true +sp-inherents.workspace = true +sp-offchain.workspace = true +sp-runtime.workspace = true +sp-session.workspace = true +sp-transaction-pool.workspace = true +sp-version.workspace = true +pallet-xcm.workspace = true +polkadot-parachain-primitives.workspace = true +polkadot-runtime-common.workspace = true +xcm.workspace = true +xcm-builder.workspace = true +xcm-executor.workspace = true +cumulus-pallet-aura-ext.workspace = true +cumulus-pallet-parachain-system.workspace = true +cumulus-pallet-session-benchmarking.workspace = true +cumulus-pallet-xcm.workspace = true +cumulus-pallet-xcmp-queue.workspace = true +cumulus-primitives-aura.workspace = true +cumulus-primitives-core.workspace = true +cumulus-primitives-utility.workspace = true +cumulus-primitives-storage-weight-reclaim.workspace = true +pallet-collator-selection.workspace = true +parachains-common.workspace = true +parachain-info.workspace = true +pallet-utility = { version = "38.0.0", default-features = false } +custom-pallet = { path = "../pallets/custom-pallet", default-features = false } + +[features] +default = ["std"] +std = [ + "codec/std", + "cumulus-pallet-aura-ext/std", + "cumulus-pallet-parachain-system/std", + "cumulus-pallet-session-benchmarking/std", + "cumulus-pallet-xcm/std", + "cumulus-pallet-xcmp-queue/std", + "cumulus-primitives-aura/std", + "cumulus-primitives-core/std", + "cumulus-primitives-storage-weight-reclaim/std", + "cumulus-primitives-utility/std", + "frame-benchmarking?/std", + "frame-executive/std", + "frame-metadata-hash-extension/std", + "frame-support/std", + "frame-system-benchmarking?/std", + "frame-system-rpc-runtime-api/std", + "frame-system/std", + "frame-try-runtime?/std", + "log/std", + "pallet-aura/std", + "pallet-authorship/std", + "pallet-balances/std", + "pallet-collator-selection/std", + "pallet-message-queue/std", + "pallet-parachain-template/std", + "pallet-session/std", + "pallet-sudo/std", + "pallet-timestamp/std", + "pallet-transaction-payment-rpc-runtime-api/std", + "pallet-transaction-payment/std", + "pallet-xcm/std", + "parachain-info/std", + "parachains-common/std", + "polkadot-parachain-primitives/std", + "polkadot-runtime-common/std", + "scale-info/std", + "serde_json/std", + "sp-api/std", + "sp-block-builder/std", + "sp-consensus-aura/std", + "sp-core/std", + "sp-genesis-builder/std", + "sp-inherents/std", + "sp-offchain/std", + "sp-runtime/std", + "sp-session/std", + "sp-transaction-pool/std", + "sp-version/std", + "substrate-wasm-builder", + "xcm-builder/std", + "xcm-executor/std", + "xcm/std", + "pallet-utility/std", + "custom-pallet/std", +] + +runtime-benchmarks = [ + "cumulus-pallet-parachain-system/runtime-benchmarks", + "cumulus-pallet-session-benchmarking/runtime-benchmarks", + "cumulus-pallet-xcmp-queue/runtime-benchmarks", + "cumulus-primitives-core/runtime-benchmarks", + "cumulus-primitives-utility/runtime-benchmarks", + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "hex-literal", + "pallet-balances/runtime-benchmarks", + "pallet-collator-selection/runtime-benchmarks", + "pallet-message-queue/runtime-benchmarks", + "pallet-parachain-template/runtime-benchmarks", + "pallet-sudo/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", + "pallet-xcm/runtime-benchmarks", + "parachains-common/runtime-benchmarks", + "polkadot-parachain-primitives/runtime-benchmarks", + "polkadot-runtime-common/runtime-benchmarks", + "sp-runtime/runtime-benchmarks", + "xcm-builder/runtime-benchmarks", + "xcm-executor/runtime-benchmarks", + "custom-pallet/runtime-benchmarks", +] + +try-runtime = [ + "cumulus-pallet-aura-ext/try-runtime", + "cumulus-pallet-parachain-system/try-runtime", + "cumulus-pallet-xcm/try-runtime", + "cumulus-pallet-xcmp-queue/try-runtime", + "frame-executive/try-runtime", + "frame-support/try-runtime", + "frame-system/try-runtime", + "frame-try-runtime/try-runtime", + "pallet-aura/try-runtime", + "pallet-authorship/try-runtime", + "pallet-balances/try-runtime", + "pallet-collator-selection/try-runtime", + "pallet-message-queue/try-runtime", + "pallet-parachain-template/try-runtime", + "pallet-session/try-runtime", + "pallet-sudo/try-runtime", + "pallet-timestamp/try-runtime", + "pallet-transaction-payment/try-runtime", + "pallet-xcm/try-runtime", + "parachain-info/try-runtime", + "polkadot-runtime-common/try-runtime", + "sp-runtime/try-runtime", +] + +# Enable the metadata hash generation. +# +# This is hidden behind a feature because it increases the compile time. +# The wasm binary needs to be compiled twice, once to fetch the metadata, +# generate the metadata hash and then a second time with the +# `RUNTIME_METADATA_HASH` environment variable set for the `CheckMetadataHash` +# extension. +metadata-hash = ["substrate-wasm-builder/metadata-hash"] + +# A convenience feature for enabling things when doing a build +# for an on-chain release. +on-chain-release-build = ["metadata-hash"] diff --git a/tutorials/polkadot-sdk/parachains/zero-to-hero/.pages b/tutorials/polkadot-sdk/parachains/zero-to-hero/.pages index f6ac6e1c..b209fd5c 100644 --- a/tutorials/polkadot-sdk/parachains/zero-to-hero/.pages +++ b/tutorials/polkadot-sdk/parachains/zero-to-hero/.pages @@ -4,6 +4,7 @@ nav: - 'Set Up a Template': set-up-a-template.md - 'Build a Custom Pallet': build-custom-pallet.md - 'Pallet Unit Testing': pallet-unit-testing.md + - 'Pallet Benchmarking': pallet-benchmarking.md - 'Add Pallets to the Runtime': add-pallets-to-runtime.md - 'Deploy to Paseo TestNet': deploy-to-testnet.md - 'Obtain Coretime': obtain-coretime.md \ No newline at end of file diff --git a/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking.md b/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking.md new file mode 100644 index 00000000..deabfdec --- /dev/null +++ b/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking.md @@ -0,0 +1,212 @@ +--- +title: Pallet Benchmarking +description: Learn how to benchmark Polkadot SDK-based pallets, assigning precise weights to extrinsics for accurate fee calculation and runtime optimization. +--- + +## Introduction + +After implementing and testing your pallet with a mock runtime in the [Pallet Unit Testing +](/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing/){target=\_blank} tutorial, the next crucial step is benchmarking. Benchmarking assigns precise [weight](/polkadot-protocol/glossary/#weight){target=\_blank} to each extrinsic, measuring their computational and storage costs. These derived weights enable accurate fee calculation and resource allocation within the runtime. + +This tutorial demonstrates how to: + +- Configure your development environment for benchmarking +- Create and implement benchmark tests for your extrinsics +- Apply benchmark results to your pallet's extrinsics + +For comprehensive information about benchmarking concepts, refer to the [Benchmarking](/develop/parachains/testing/benchmarking/){target=\_blank} guide. + +## Environment Setup + +Follow these steps to prepare your environment for pallet benchmarking: + +1. Install the [`frame-omni-bencher`](https://crates.io/crates/frame-omni-bencher){target=\_blank} command-line tool: + + ```bash + cargo install frame-omni-bencher + ``` + +2. Update your pallet's `Cargo.toml` file in the `pallets/custom-pallet` directory with the following modifications: + 1. Add the [`frame-benchmarking`](https://docs.rs/frame-benchmarking/latest/frame_benchmarking/){target=\_blank} dependency: + + ```toml hl_lines="3" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml:10:10' + ... + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml:15:15' + ``` + + 2. Enable benchmarking in the `std` features: + ```toml hl_lines="6" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml:24:30' + ``` + + 3. Add the `runtime-benchmarks` feature flag: + ```toml hl_lines="3-8" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml:22:22' + ... + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/pallet-cargo.toml:31:36' + ``` + +3. Add your pallet to the runtime's benchmark configuration: + 1. Register your pallet in `runtime/src/benchmarks.rs`: + ```rust hl_lines="11" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarks.rs:26:37' + ``` + + 2. Enable runtime benchmarking for your pallet in `runtime/Cargo.toml`: + ```toml hl_lines="25" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/runtime-cargo.toml:136:161' + ``` + +4. Set up the benchmarking module in your pallet: + 1. Create a new `benchmarking.rs` file in your pallet directory: + ```bash + touch benchmarking.rs + ``` + + 2. Add the benchmarking module to your pallet. In the pallet `lib.rs` file add the following: + ```rust hl_lines="9-10" + --8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs:1:12' + ``` + + !!!note + The `benchmarking` module is gated behind the `runtime-benchmarks` feature flag. It will only be compiled when this flag is explicitly enabled in your project's `Cargo.toml` or via the `--features runtime-benchmarks` compilation flag. + +## Implement Benchmark Tests + +When writing benchmarking tests for your pallet, you'll create specialized test functions for each extrinsic, similar to unit tests. These tests use the mock runtime you created earlier for testing, allowing you to leverage its utility functions. + +Every benchmark test must follow a three-step pattern: + +1. **Setup** - perform any necessary setup before calling the extrinsic. This might include creating accounts, setting initial states, or preparing test data +2. **Execute the extrinsic** - execute the actual extrinsic using the [`#[extrinsic_call]`](https://paritytech.github.io/polkadot-sdk/master/frame_benchmarking/v2/attr.extrinsic_call.html){target=\_blank} macro. This must be a single line that calls your extrinsic function with the origin as its first argument +3. **Verification** - check that the extrinsic worked correctly within the benchmark context by checking the expected state changes + +Check the following example on how to benchmark the `increment` extrinsic: + +```rust +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarking.rs:23:37' +``` + +This benchmark test: + +1. Creates a whitelisted caller and sets an initial counter value of 5 +2. Calls the increment extrinsic to increase the counter by 1 +3. Verifies that the counter was properly incremented to 6 and that the user's interaction was recorded in storage + +This example demonstrates how to properly set up state, execute an extrinsic, and verify its effects during benchmarking. + +Now, implement the complete set of benchmark tests. Copy the following content in the `benchmarking.rs` file: + +```rust +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/benchmarking.rs' +``` + +!!!note + The [`#[benchmark]`](https://paritytech.github.io/polkadot-sdk/master/frame_benchmarking/v2/attr.benchmark.html){target=\_blank} macro marks these functions as benchmark tests, while the `#[extrinsic_call]` macro specifically identifies which line contains the extrinsic being measured. For more information, check the [frame_benchmarking](https://paritytech.github.io/polkadot-sdk/master/frame_benchmarking/v2/index.html){target=\_blank} Rust docs. + +## Execute the Benchmarking + +After implementing your benchmark test suite, you'll need to execute the tests and generate the weights for your extrinsics. This process involves building your runtime with benchmarking features enabled and using the `frame-omni-bencher` CLI tool. To do that, follow these steps: + +1. Build your runtime with the `runtime-benchmarks` feature enabled: + + ```bash + cargo build --features runtime-benchmarks --release + ``` + + This special build includes all the necessary benchmarking code that's normally excluded from production builds. + +2. Create a `weights.rs` file in your pallet's `src/` directory. This file will store the auto-generated weight calculations: + + ```bash + touch weights.rs + ``` + +3. Before running the benchmarking tool, you'll need a template file that defines how weight information should be formatted. Download the official template from the Polkadot SDK repository and save it in your project folders for future use: + + ```bash + mkdir ./pallets/benchmarking && \ + curl https://raw.githubusercontent.com/paritytech/polkadot-sdk/refs/tags/polkadot-stable2412/substrate/.maintain/frame-weight-template.hbs \ + --output ./pallets/benchmarking/frame-weight-template.hbs + ``` + +4. Execute the benchmarking process using the `frame-omni-bencher` CLI: + + ```bash + frame-omni-bencher v1 benchmark pallet \ + --runtime target/release/wbuild/parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm \ + --pallet "custom_pallet" \ + --extrinsic "" \ + --template ./pallets/benchmarking/frame-weight-template.hbs \ + --output ./pallets/custom-pallet/src/weights.rs + ``` + +When the benchmarking process completes, your `weights.rs` file will contain auto-generated code with weight calculations for each of your pallet's extrinsics. These weights help ensure fair and accurate fee calculations when your pallet is used in a production environment. + +## Add Benchmarking Weights to the Pallet + +After generating the weight calculations, you need to integrate these weights into your pallet's code. This integration ensures your pallet properly accounts for computational costs in its extrinsics. + +First, add the necessary module imports to your pallet. These imports make the weights available to your code: + +```rust hl_lines="4-5" +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs:11:15' +``` + +Next, update your pallet's `Config` trait to include weight information. Define the `WeightInfo` type: + +```rust hl_lines="11-12" +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/lib.rs:26:38' +``` + +Now you can assign weights to your extrinsics. Here's how to add weight calculations to the `set_counter_value` function: + +```rust hl_lines="2" +#[pallet::call_index(0)] +#[pallet::weight(T::WeightInfo::set_counter_value())] +pub fn set_counter_value(origin: OriginFor, new_value: u32) -> DispatchResult { + ensure_root(origin)?; + + ensure!( + new_value <= T::CounterMaxValue::get(), + Error::::CounterValueExceedsMax + ); + + CounterValue::::put(new_value); + + Self::deposit_event(Event::::CounterValueSet { + counter_value: new_value, + }); + + Ok(()) +} +``` + +For testing purposes, you need to implement the weight calculations in your mock runtime. Open `custom-pallet/src/mock.rs` and add: + +```rust hl_lines="4" +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mock.rs:41:45' +``` + +Finally, configure the actual weight values in your production runtime. In `runtime/src/config/mod.rs`, add: + +```rust hl_lines="5" +--8<-- 'code/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/mod.rs:327:332' +``` + +Your pallet is now complete with full testing and benchmarking support, ready for production use. + +## Where to Go Next + +
+ +- Tutorial __Add Pallets to the Runtime__ + + --- + + Enhance your runtime with custom functionality! Learn how to add, configure, and integrate pallets in Polkadot SDK-based blockchains. + + [:octicons-arrow-right-24: Get Started](/tutorials/polkadot-sdk/parachains/zero-to-hero/add-pallets-to-runtime/) + +
\ No newline at end of file diff --git a/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing.md b/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing.md index 936026d7..2c0d3651 100644 --- a/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing.md +++ b/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing.md @@ -163,12 +163,12 @@ After running the test suite, you should see the following output in your termin
-- Tutorial __Add Pallets to the Runtime__ +- Tutorial __Pallet Benchmarking__ --- - Enhance your runtime with custom functionality! Learn how to add, configure, and integrate pallets in Polkadot SDK-based blockchains. + Discover how to measure extrinsic costs and assign precise weights to optimize your pallet for accurate fees and runtime performance. - [:octicons-arrow-right-24: Get Started](/tutorials/polkadot-sdk/parachains/zero-to-hero/add-pallets-to-runtime/) + [:octicons-arrow-right-24: Get Started](/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-benchmarking/)
\ No newline at end of file