diff --git a/.ai/pages/parachains-customize-runtime-pallet-development-pallet-testing.md b/.ai/pages/parachains-customize-runtime-pallet-development-pallet-testing.md index b26d07b1f..61f94af8d 100644 --- a/.ai/pages/parachains-customize-runtime-pallet-development-pallet-testing.md +++ b/.ai/pages/parachains-customize-runtime-pallet-development-pallet-testing.md @@ -1,133 +1,726 @@ --- -title: Pallet Testing -description: Learn how to efficiently test pallets in the Polkadot SDK, ensuring the reliability and security of your pallets operations. +title: Pallet Unit Testing +description: Learn how to write comprehensive unit tests for your custom pallets using mock runtimes, ensuring reliability and correctness before deployment. categories: Parachains url: https://docs.polkadot.com/parachains/customize-runtime/pallet-development/pallet-testing/ --- -# Pallet Testing +# Pallet Unit Testing ## Introduction -Unit testing in the Polkadot SDK helps ensure that the functions provided by a pallet behave as expected. It also confirms that data and events associated with a pallet are processed correctly during interactions. The Polkadot SDK offers a set of APIs to create a test environment to simulate runtime and mock transaction execution for extrinsics and queries. +Unit testing in the Polkadot SDK helps ensure that the functions provided by a pallet behave as expected. It also confirms that data and events associated with a pallet are processed correctly during interactions. With your mock runtime in place from the previous guide, you can now write comprehensive tests that verify your pallet's behavior in isolation. -To begin unit testing, you must first set up a mock runtime that simulates blockchain behavior, incorporating the necessary pallets. For a deeper understanding, consult the [Mock Runtime](/parachains/customize-runtime/pallet-development/mock-runtime/){target=\_blank} guide. +In this guide, you'll learn how to: -## Writing Unit Tests +- Structure test modules effectively +- Test dispatchable functions +- Verify storage changes +- Check event emission +- Test error conditions +- Use genesis configurations in tests -Once the mock runtime is in place, the next step is to write unit tests that evaluate the functionality of your pallet. Unit tests allow you to test specific pallet features in isolation, ensuring that each function behaves correctly under various conditions. These tests typically reside in your pallet module's `test.rs` file. +## Prerequisites -Unit tests in the Polkadot SDK use the Rust testing framework, and the mock runtime you've defined earlier will serve as the test environment. Below are the typical steps involved in writing unit tests for a pallet. +Before you begin, ensure you have: -The tests confirm that: +- Completed the [Make a Custom Pallet](/parachains/customize-runtime/pallet-development/create-a-pallet/) guide +- Completed the [Mock Your Runtime](/parachains/customize-runtime/pallet-development/mock-runtime/) guide +- The custom counter pallet with mock runtime in `pallets/pallet-custom` +- Basic understanding of [Rust testing](https://doc.rust-lang.org/book/ch11-00-testing.html){target=\_blank} -- **Pallets initialize correctly**: At the start of each test, the system should initialize with block number 0, and the pallets should be in their default states. -- **Pallets modify each other's state**: The second test shows how one pallet can trigger changes in another pallet's internal state, confirming proper cross-pallet interactions. -- **State transitions between blocks are seamless**: By simulating block transitions, the tests validate that the runtime responds correctly to changes in the block number. +## Understanding FRAME Testing Tools -Testing pallet interactions within the runtime is critical for ensuring the blockchain behaves as expected under real-world conditions. Writing integration tests allows validation of how pallets function together, preventing issues that might arise when the system is fully assembled. +FRAME provides specialized testing macros and utilities that make pallet testing more efficient: -This approach provides a comprehensive view of the runtime's functionality, ensuring the blockchain is stable and reliable. +### Assertion Macros -### Test Initialization +- **[`assert_ok!`](https://paritytech.github.io/polkadot-sdk/master/frame_support/macro.assert_ok.html){target=\_blank}** - Asserts that a dispatchable call succeeds. +- **[`assert_noop!`](https://paritytech.github.io/polkadot-sdk/master/frame_support/macro.assert_noop.html){target=\_blank}** - Asserts that a call fails without changing state (no operation). +- **`assert_eq!`** - Standard Rust equality assertion. -Each test starts by initializing the runtime environment, typically using the `new_test_ext()` function, which sets up the mock storage and environment. +!!!info "`assert_noop!` Explained" + Use `assert_noop!` to ensure the operation fails without any state changes. This is critical for testing error conditions - it verifies both that the error occurs AND that no storage was modified. + +### System Pallet Test Helpers + +The [`frame_system`](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html){target=\_blank} pallet provides useful methods for testing: + +- **[`System::events()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.events){target=\_blank}** - Returns all events emitted during the test. +- **[`System::assert_last_event()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_last_event){target=\_blank}** - Asserts the last event matches expectations. +- **[`System::set_block_number()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.set_block_number){target=\_blank}** - Sets the current block number. + +!!!info "Events and Block Number" + Events are not emitted on block 0 (genesis block). If you need to test events, ensure you set the block number to at least 1 using `System::set_block_number(1)`. + +### Origin Types + +- **[`RuntimeOrigin::root()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.Root){target=\_blank}** - Root/sudo origin for privileged operations. +- **[`RuntimeOrigin::signed(account)`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.Signed){target=\_blank}** - Signed origin from a specific account. +- **[`RuntimeOrigin::none()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.None){target=\_blank}** - No origin (typically fails for most operations). + +Learn more about origins in the [FRAME Origin reference document](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_origin/index.html){target=\_blank}. + +## Create the Tests Module + +Create a new file for your tests within the pallet directory: + +1. Navigate to your pallet directory: + + ```bash + cd pallets/pallet-custom/src + ``` + +2. Create a new file named `tests.rs`: + + ```bash + touch tests.rs + ``` + +3. Open `src/lib.rs` and add the tests module declaration after the mock module: + + ```rust title="src/lib.rs" + #![cfg_attr(not(feature = "std"), no_std)] + + pub use pallet::*; + + #[cfg(test)] + mod mock; + + #[cfg(test)] + mod tests; + + #[frame::pallet] + pub mod pallet { + // ... existing pallet code + } + ``` + +## Set Up the Test Module + +Open `src/tests.rs` and add the basic structure with necessary imports: + +```rust +use crate::{mock::*, Error, Event}; +use frame::deps::frame_support::{assert_noop, assert_ok}; +use frame::deps::sp_runtime::DispatchError; +``` + +This setup imports: + +- The mock runtime and test utilities from `mock.rs` +- Your pallet's `Error` and `Event` types +- FRAME's assertion macros via `frame::deps` +- `DispatchError` for testing origin checks + +???+ code "Complete Pallet Code Reference" + Here's the complete pallet code that you'll be testing throughout this guide: + + ```rust + #![cfg_attr(not(feature = "std"), no_std)] + + pub use pallet::*; + + #[frame::pallet] + pub mod pallet { + use frame::prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type CounterMaxValue: Get; + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + CounterValueSet { + new_value: u32, + }, + CounterIncremented { + new_value: u32, + who: T::AccountId, + amount: u32, + }, + CounterDecremented { + new_value: u32, + who: T::AccountId, + amount: u32, + }, + } + + #[pallet::error] + pub enum Error { + NoneValue, + Overflow, + Underflow, + CounterMaxValueExceeded, + } + + #[pallet::storage] + pub type CounterValue = StorageValue<_, u32, ValueQuery>; + + #[pallet::storage] + pub type UserInteractions = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + u32, + ValueQuery + >; + + #[pallet::genesis_config] + #[derive(DefaultNoBound)] + pub struct GenesisConfig { + pub initial_counter_value: u32, + pub initial_user_interactions: Vec<(T::AccountId, u32)>, + } + + #[pallet::genesis_build] + impl BuildGenesisConfig for GenesisConfig { + fn build(&self) { + CounterValue::::put(self.initial_counter_value); + for (account, count) in &self.initial_user_interactions { + UserInteractions::::insert(account, count); + } + } + } + + #[pallet::call] + impl Pallet { + #[pallet::call_index(0)] + #[pallet::weight(0)] + pub fn set_counter_value(origin: OriginFor, new_value: u32) -> DispatchResult { + ensure_root(origin)?; + ensure!(new_value <= T::CounterMaxValue::get(), Error::::CounterMaxValueExceeded); + CounterValue::::put(new_value); + Self::deposit_event(Event::CounterValueSet { new_value }); + Ok(()) + } + + #[pallet::call_index(1)] + #[pallet::weight(0)] + pub fn increment(origin: OriginFor, amount: u32) -> DispatchResult { + let who = ensure_signed(origin)?; + let current_value = CounterValue::::get(); + let new_value = current_value.checked_add(amount).ok_or(Error::::Overflow)?; + ensure!(new_value <= T::CounterMaxValue::get(), Error::::CounterMaxValueExceeded); + CounterValue::::put(new_value); + UserInteractions::::mutate(&who, |count| { + *count = count.saturating_add(1); + }); + Self::deposit_event(Event::CounterIncremented { new_value, who, amount }); + Ok(()) + } + + #[pallet::call_index(2)] + #[pallet::weight(0)] + pub fn decrement(origin: OriginFor, amount: u32) -> DispatchResult { + let who = ensure_signed(origin)?; + let current_value = CounterValue::::get(); + let new_value = current_value.checked_sub(amount).ok_or(Error::::Underflow)?; + CounterValue::::put(new_value); + UserInteractions::::mutate(&who, |count| { + *count = count.saturating_add(1); + }); + Self::deposit_event(Event::CounterDecremented { new_value, who, amount }); + Ok(()) + } + } + } + + ``` + +## Write Your First Test + +Let's start with a simple test to verify the increment function works correctly. + +### Test Basic Increment + +Test that the increment function increases counter value and emits events. ```rust #[test] -fn test_pallet_functionality() { +fn increment_works() { new_test_ext().execute_with(|| { - // Test logic goes here + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 1u64; + + // Increment by 50 + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 50)); + assert_eq!(crate::CounterValue::::get(), 50); + + // Check event was emitted + System::assert_last_event( + Event::CounterIncremented { + new_value: 50, + who: account, + amount: 50, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); }); } ``` -### Function Call Testing +Run your first test: + +```bash +cargo test --package pallet-custom increment_works +``` + +You should see: + +``` +running 1 test +test tests::increment_works ... ok +``` + +Congratulations! You've written and run your first pallet test. + +## Test Error Conditions + +Now let's test that our pallet correctly handles errors. Error testing is crucial to ensure your pallet fails safely. -Call the pallet's extrinsics or functions to simulate user interaction or internal logic. Use the `assert_ok!` macro to check for successful execution and `assert_err!` to verify that errors are correctly handled. +### Test Overflow Protection + +Test that incrementing at u32::MAX fails with Overflow error. ```rust #[test] -fn it_works_for_valid_input() { - new_test_ext().execute_with(|| { - // Call an extrinsic or function - assert_ok!(TemplateModule::some_function(Origin::signed(1), valid_param)); +fn increment_fails_on_overflow() { + new_test_ext_with_counter(u32::MAX).execute_with(|| { + // Attempt to increment when at max u32 should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 1), + Error::::Overflow + ); }); } +``` + +Test overflow protection: +```bash +cargo test --package pallet-custom increment_fails_on_overflow +``` + +### Test Underflow Protection + +Test that decrementing below zero fails with Underflow error. + +```rust #[test] -fn it_fails_for_invalid_input() { - new_test_ext().execute_with(|| { - // Call an extrinsic with invalid input and expect an error - assert_err!( - TemplateModule::some_function(Origin::signed(1), invalid_param), - Error::::InvalidInput +fn decrement_fails_on_underflow() { + new_test_ext_with_counter(10).execute_with(|| { + // Attempt to decrement below zero should fail + assert_noop!( + CustomPallet::decrement(RuntimeOrigin::signed(1), 11), + Error::::Underflow ); }); } ``` -### Storage Testing +Verify underflow protection: -After calling a function or extrinsic in your pallet, it's essential to verify that the state changes in the pallet's storage match the expected behavior to ensure data is updated correctly based on the actions taken. +```bash +cargo test --package pallet-custom decrement_fails_on_underflow +``` + +## Test Access Control + +Verify that origin checks work correctly and unauthorized access is prevented. -The following example shows how to test the storage behavior before and after the function call: +### Test Root-Only Access + +Test that set_counter_value requires root origin and rejects signed origins. ```rust #[test] -fn test_storage_update_on_extrinsic_call() { +fn set_counter_value_requires_root() { new_test_ext().execute_with(|| { - // Check the initial storage state (before the call) - assert_eq!(Something::::get(), None); + let alice = 1u64; - // Dispatch a signed extrinsic, which modifies storage - assert_ok!(TemplateModule::do_something(RuntimeOrigin::signed(1), 42)); + // When: non-root user tries to set counter + // Then: should fail with BadOrigin + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::signed(alice), 100), + DispatchError::BadOrigin + ); - // Validate that the storage has been updated as expected (after the call) - assert_eq!(Something::::get(), Some(42)); + // But root should succeed + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); }); } +``` + +Test access control: +```bash +cargo test --package pallet-custom set_counter_value_requires_root ``` -### Event Testing +## Test Event Emission + +Verify that events are emitted correctly with the right data. + +### Test Event Data + +The `increment_works` test (shown earlier) already demonstrates event testing by: +1. Setting the block number to 1 to enable event emission +2. Calling the dispatchable function +3. Using `System::assert_last_event()` to verify the correct event was emitted with expected data -It's also crucial to test the events that your pallet emits during execution. By default, events generated in a pallet using the [`#generate_deposit`](https://paritytech.github.io/polkadot-sdk/master/frame_support/pallet_macros/attr.generate_deposit.html){target=\_blank} macro are stored under the system's event storage key (system/events) as [`EventRecord`](https://paritytech.github.io/polkadot-sdk/master/frame_system/struct.EventRecord.html){target=\_blank} entries. These can be accessed using [`System::events()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.events){target=\_blank} or verified with specific helper methods provided by the system pallet, such as [`assert_has_event`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_has_event){target=\_blank} and [`assert_last_event`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_last_event){target=\_blank}. +This pattern applies to all dispatchables that emit events. For a dedicated event-only test focusing on the `set_counter_value` function: -Here's an example of testing events in a mock runtime: +Test that set_counter_value updates storage and emits correct event. ```rust #[test] -fn it_emits_events_on_success() { +fn set_counter_value_works() { new_test_ext().execute_with(|| { - // Call an extrinsic or function - assert_ok!(TemplateModule::some_function(Origin::signed(1), valid_param)); + // Set block number to 1 so events are registered + System::set_block_number(1); - // Verify that the expected event was emitted - assert!(System::events().iter().any(|record| { - record.event == Event::TemplateModule(TemplateEvent::SomeEvent) - })); + // Set counter to 100 + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); + + // Check event was emitted + System::assert_last_event(Event::CounterValueSet { new_value: 100 }.into()); }); } ``` -Some key considerations are: +Run the event test: -- **Block number**: Events are not emitted on the genesis block, so you need to set the block number using [`System::set_block_number()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.set_block_number){target=\_blank} to ensure events are triggered. -- **Converting events**: Use `.into()` when instantiating your pallet's event to convert it into a generic event type, as required by the system's event storage. +```bash +cargo test --package pallet-custom set_counter_value_works +``` -## Where to Go Next +## Test Genesis Configuration + +Verify that genesis configuration works correctly. + +### Test Genesis Setup -- Dive into the full implementation of the [`mock.rs`](https://github.com/paritytech/polkadot-sdk/blob/master/templates/solochain/pallets/template/src/mock.rs){target=\_blank} and [`test.rs`](https://github.com/paritytech/polkadot-sdk/blob/master/templates/solochain/pallets/template/src/tests.rs){target=\_blank} files in the [Solochain Template](https://github.com/paritytech/polkadot-sdk/tree/master/templates/solochain){target=_blank}. +Test that genesis configuration correctly initializes counter and user interactions. + +```rust +#[test] +fn genesis_config_works() { + new_test_ext_with_interactions(42, vec![(1, 5), (2, 10)]).execute_with(|| { + // Check initial counter value + assert_eq!(crate::CounterValue::::get(), 42); + + // Check initial user interactions + assert_eq!(crate::UserInteractions::::get(1), 5); + assert_eq!(crate::UserInteractions::::get(2), 10); + }); +} +``` + +Test genesis configuration: + +```bash +cargo test --package pallet-custom genesis_config_works +``` + +## Run All Tests + +Now run all your tests together: + +```bash +cargo test --package pallet-custom +``` + +You should see all tests passing: + +``` +running 15 tests +test mock::__construct_runtime_integrity_test::runtime_integrity_tests ... ok +test mock::test_genesis_config_builds ... ok +test tests::decrement_fails_on_underflow ... ok +test tests::decrement_tracks_multiple_interactions ... ok +test tests::decrement_works ... ok +test tests::different_users_tracked_separately ... ok +test tests::genesis_config_works ... ok +test tests::increment_fails_on_overflow ... ok +test tests::increment_respects_max_value ... ok +test tests::increment_tracks_multiple_interactions ... ok +test tests::increment_works ... ok +test tests::mixed_increment_and_decrement_works ... ok +test tests::set_counter_value_requires_root ... ok +test tests::set_counter_value_respects_max_value ... ok +test tests::set_counter_value_works ... ok + +test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +``` + +!!!note "Mock Runtime Tests" + You'll notice 2 additional tests from the `mock` module: + + - `mock::__construct_runtime_integrity_test::runtime_integrity_tests` - Auto-generated test that validates runtime construction + - `mock::test_genesis_config_builds` - Validates that genesis configuration builds correctly + + These tests are automatically generated from your mock runtime setup and help ensure the test environment itself is valid. + +Congratulations! You have a well-tested pallet covering the essential testing patterns! + +These tests demonstrate comprehensive coverage including basic operations, error conditions, access control, event emission, state management, and genesis configuration. As you build more complex pallets, you'll apply these same patterns to test additional functionality. + +??? code "Full Test Suite Code" + Here's the complete `tests.rs` file for quick reference: + + ```rust + use crate::{mock::*, Error, Event}; + use frame::deps::frame_support::{assert_noop, assert_ok}; + use frame::deps::sp_runtime::DispatchError; + + #[test] + fn set_counter_value_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + // Set counter to 100 + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); + + // Check event was emitted + System::assert_last_event(Event::CounterValueSet { new_value: 100 }.into()); + }); + } + + #[test] + fn set_counter_value_requires_root() { + new_test_ext().execute_with(|| { + // Attempt to set counter with non-root origin should fail + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::signed(1), 100), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn set_counter_value_respects_max_value() { + new_test_ext().execute_with(|| { + // Attempt to set counter above max value (1000) should fail + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::root(), 1001), + Error::::CounterMaxValueExceeded + ); + + // Setting to exactly max value should work + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 1000)); + assert_eq!(crate::CounterValue::::get(), 1000); + }); + } + + #[test] + fn increment_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 1u64; + + // Increment by 50 + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 50)); + assert_eq!(crate::CounterValue::::get(), 50); + + // Check event was emitted + System::assert_last_event( + Event::CounterIncremented { + new_value: 50, + who: account, + amount: 50, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); + }); + } + + #[test] + fn increment_tracks_multiple_interactions() { + new_test_ext().execute_with(|| { + let account = 1u64; + + // Increment multiple times + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 10)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 20)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 30)); + + // Check counter value + assert_eq!(crate::CounterValue::::get(), 60); + + // Check user interactions were tracked (should be 3) + assert_eq!(crate::UserInteractions::::get(account), 3); + }); + } + + #[test] + fn increment_fails_on_overflow() { + new_test_ext_with_counter(u32::MAX).execute_with(|| { + // Attempt to increment when at max u32 should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 1), + Error::::Overflow + ); + }); + } + + #[test] + fn increment_respects_max_value() { + new_test_ext_with_counter(950).execute_with(|| { + // Incrementing past max value (1000) should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 51), + Error::::CounterMaxValueExceeded + ); + + // Incrementing to exactly max value should work + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(1), 50)); + assert_eq!(crate::CounterValue::::get(), 1000); + }); + } + + #[test] + fn decrement_works() { + new_test_ext_with_counter(100).execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 2u64; + + // Decrement by 30 + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 30)); + assert_eq!(crate::CounterValue::::get(), 70); + + // Check event was emitted + System::assert_last_event( + Event::CounterDecremented { + new_value: 70, + who: account, + amount: 30, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); + }); + } + + #[test] + fn decrement_fails_on_underflow() { + new_test_ext_with_counter(10).execute_with(|| { + // Attempt to decrement below zero should fail + assert_noop!( + CustomPallet::decrement(RuntimeOrigin::signed(1), 11), + Error::::Underflow + ); + }); + } + + #[test] + fn decrement_tracks_multiple_interactions() { + new_test_ext_with_counter(100).execute_with(|| { + let account = 3u64; + + // Decrement multiple times + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 10)); + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 20)); + + // Check counter value + assert_eq!(crate::CounterValue::::get(), 70); + + // Check user interactions were tracked (should be 2) + assert_eq!(crate::UserInteractions::::get(account), 2); + }); + } + + #[test] + fn mixed_increment_and_decrement_works() { + new_test_ext_with_counter(50).execute_with(|| { + let account = 4u64; + + // Mix of increment and decrement + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 25)); + assert_eq!(crate::CounterValue::::get(), 75); + + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 15)); + assert_eq!(crate::CounterValue::::get(), 60); + + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 10)); + assert_eq!(crate::CounterValue::::get(), 70); + + // Check user interactions were tracked (should be 3) + assert_eq!(crate::UserInteractions::::get(account), 3); + }); + } + + #[test] + fn different_users_tracked_separately() { + new_test_ext().execute_with(|| { + let account1 = 1u64; + let account2 = 2u64; + + // User 1 increments + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account1), 10)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account1), 10)); + + // User 2 decrements + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account2), 5)); + + // Check counter value (10 + 10 - 5 = 15) + assert_eq!(crate::CounterValue::::get(), 15); + + // Check user interactions are tracked separately + assert_eq!(crate::UserInteractions::::get(account1), 2); + assert_eq!(crate::UserInteractions::::get(account2), 1); + }); + } + + #[test] + fn genesis_config_works() { + new_test_ext_with_interactions(42, vec![(1, 5), (2, 10)]).execute_with(|| { + // Check initial counter value + assert_eq!(crate::CounterValue::::get(), 42); + + // Check initial user interactions + assert_eq!(crate::UserInteractions::::get(1), 5); + assert_eq!(crate::UserInteractions::::get(2), 10); + }); + } + ``` + +## Where to Go Next
-- Guide __Benchmarking__ +- Guide __Add Your Custom Pallet to the Runtime__ --- - Explore methods to measure the performance and execution cost of your pallet. + Your pallet is tested and ready! Learn how to integrate it into your runtime. - [:octicons-arrow-right-24: Reference](/develop/parachains/testing/benchmarking) + [:octicons-arrow-right-24: Integrate](/parachains/customize-runtime/pallet-development/add-to-runtime/)
diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml deleted file mode 100644 index de0b6da9e..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml +++ /dev/null @@ -1,102 +0,0 @@ -[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] -docify = { workspace = true } -substrate-wasm-builder = { optional = true, workspace = true, default-features = true } - -[dependencies] -codec = { features = ["derive"], workspace = true } -cumulus-pallet-parachain-system.workspace = true -docify = { workspace = true } -hex-literal = { optional = true, workspace = true, default-features = true } -log = { workspace = true } -pallet-parachain-template = { path = "../pallets/template", default-features = false } -polkadot-sdk = { workspace = true, features = [ - "pallet-utility", - "cumulus-pallet-aura-ext", - "cumulus-pallet-session-benchmarking", - "cumulus-pallet-weight-reclaim", - "cumulus-pallet-xcm", - "cumulus-pallet-xcmp-queue", - "cumulus-primitives-aura", - "cumulus-primitives-core", - "cumulus-primitives-utility", - "pallet-aura", - "pallet-authorship", - "pallet-balances", - "pallet-collator-selection", - "pallet-message-queue", - "pallet-session", - "pallet-sudo", - "pallet-timestamp", - "pallet-transaction-payment", - "pallet-transaction-payment-rpc-runtime-api", - "pallet-xcm", - "parachains-common", - "polkadot-parachain-primitives", - "polkadot-runtime-common", - "runtime", - "staging-parachain-info", - "staging-xcm", - "staging-xcm-builder", - "staging-xcm-executor", -], default-features = false } -scale-info = { features = ["derive"], workspace = true } -serde_json = { workspace = true, default-features = false, features = [ - "alloc", -] } -smallvec = { workspace = true, default-features = true } - -custom-pallet = { path = "../pallets/custom-pallet", default-features = false } - -[features] -default = ["std"] -std = [ - "codec/std", - "cumulus-pallet-parachain-system/std", - "log/std", - "pallet-parachain-template/std", - "polkadot-sdk/std", - "scale-info/std", - "serde_json/std", - "substrate-wasm-builder", - "custom-pallet/std", -] - -runtime-benchmarks = [ - "cumulus-pallet-parachain-system/runtime-benchmarks", - "hex-literal", - "pallet-parachain-template/runtime-benchmarks", - "polkadot-sdk/runtime-benchmarks", -] - -try-runtime = [ - "cumulus-pallet-parachain-system/try-runtime", - "pallet-parachain-template/try-runtime", - "polkadot-sdk/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/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/build.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/build.rs deleted file mode 100644 index 83200815b..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/build.rs +++ /dev/null @@ -1,17 +0,0 @@ -#[cfg(all(feature = "std", feature = "metadata-hash"))] -#[docify::export(template_enable_metadata_hash)] -fn main() { - substrate_wasm_builder::WasmBuilder::init_with_defaults() - .enable_metadata_hash("UNIT", 12) - .build(); -} - -#[cfg(all(feature = "std", not(feature = "metadata-hash")))] -fn main() { - substrate_wasm_builder::WasmBuilder::build_using_defaults(); -} - -/// The wasm builder is deactivated when compiling -/// this crate for wasm to speed up the compilation. -#[cfg(not(feature = "std"))] -fn main() {} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/apis.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/apis.rs deleted file mode 100644 index d01d678be..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/apis.rs +++ /dev/null @@ -1,308 +0,0 @@ -// 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 - -// External crates imports -use alloc::vec::Vec; - -use polkadot_sdk::*; - -use frame_support::{ - genesis_builder_helper::{build_state, get_preset}, - weights::Weight, -}; -use pallet_aura::Authorities; -use sp_api::impl_runtime_apis; -use sp_consensus_aura::sr25519::AuthorityId as AuraId; -use sp_core::{crypto::KeyTypeId, OpaqueMetadata}; -use sp_runtime::{ - traits::Block as BlockT, - transaction_validity::{TransactionSource, TransactionValidity}, - ApplyExtrinsicResult, -}; -use sp_version::RuntimeVersion; - -// Local module imports -use super::{ - AccountId, Balance, Block, ConsensusHook, Executive, InherentDataExt, Nonce, ParachainSystem, - Runtime, RuntimeCall, RuntimeGenesisConfig, SessionKeys, System, TransactionPayment, - SLOT_DURATION, VERSION, -}; - -// we move some impls outside so we can easily use them with `docify`. -impl Runtime { - #[docify::export] - fn impl_slot_duration() -> sp_consensus_aura::SlotDuration { - sp_consensus_aura::SlotDuration::from_millis(SLOT_DURATION) - } - - #[docify::export] - fn impl_can_build_upon( - included_hash: ::Hash, - slot: cumulus_primitives_aura::Slot, - ) -> bool { - ConsensusHook::can_build_upon(included_hash, slot) - } -} - -impl_runtime_apis! { - impl sp_consensus_aura::AuraApi for Runtime { - fn slot_duration() -> sp_consensus_aura::SlotDuration { - Runtime::impl_slot_duration() - } - - fn authorities() -> Vec { - Authorities::::get().into_inner() - } - } - - impl cumulus_primitives_aura::AuraUnincludedSegmentApi for Runtime { - fn can_build_upon( - included_hash: ::Hash, - slot: cumulus_primitives_aura::Slot, - ) -> bool { - Runtime::impl_can_build_upon(included_hash, slot) - } - } - - impl sp_api::Core for Runtime { - fn version() -> RuntimeVersion { - VERSION - } - - fn execute_block(block: Block) { - Executive::execute_block(block) - } - - fn initialize_block(header: &::Header) -> sp_runtime::ExtrinsicInclusionMode { - Executive::initialize_block(header) - } - } - - impl sp_api::Metadata for Runtime { - fn metadata() -> OpaqueMetadata { - OpaqueMetadata::new(Runtime::metadata().into()) - } - - fn metadata_at_version(version: u32) -> Option { - Runtime::metadata_at_version(version) - } - - fn metadata_versions() -> Vec { - Runtime::metadata_versions() - } - } - - impl sp_block_builder::BlockBuilder for Runtime { - fn apply_extrinsic(extrinsic: ::Extrinsic) -> ApplyExtrinsicResult { - Executive::apply_extrinsic(extrinsic) - } - - fn finalize_block() -> ::Header { - Executive::finalize_block() - } - - fn inherent_extrinsics(data: sp_inherents::InherentData) -> Vec<::Extrinsic> { - data.create_extrinsics() - } - - fn check_inherents( - block: Block, - data: sp_inherents::InherentData, - ) -> sp_inherents::CheckInherentsResult { - data.check_extrinsics(&block) - } - } - - impl sp_transaction_pool::runtime_api::TaggedTransactionQueue for Runtime { - fn validate_transaction( - source: TransactionSource, - tx: ::Extrinsic, - block_hash: ::Hash, - ) -> TransactionValidity { - Executive::validate_transaction(source, tx, block_hash) - } - } - - impl sp_offchain::OffchainWorkerApi for Runtime { - fn offchain_worker(header: &::Header) { - Executive::offchain_worker(header) - } - } - - impl sp_session::SessionKeys for Runtime { - fn generate_session_keys(seed: Option>) -> Vec { - SessionKeys::generate(seed) - } - - fn decode_session_keys( - encoded: Vec, - ) -> Option, KeyTypeId)>> { - SessionKeys::decode_into_raw_public_keys(&encoded) - } - } - - impl frame_system_rpc_runtime_api::AccountNonceApi for Runtime { - fn account_nonce(account: AccountId) -> Nonce { - System::account_nonce(account) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentApi for Runtime { - fn query_info( - uxt: ::Extrinsic, - len: u32, - ) -> pallet_transaction_payment_rpc_runtime_api::RuntimeDispatchInfo { - TransactionPayment::query_info(uxt, len) - } - fn query_fee_details( - uxt: ::Extrinsic, - len: u32, - ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_fee_details(uxt, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - impl pallet_transaction_payment_rpc_runtime_api::TransactionPaymentCallApi - for Runtime - { - fn query_call_info( - call: RuntimeCall, - len: u32, - ) -> pallet_transaction_payment::RuntimeDispatchInfo { - TransactionPayment::query_call_info(call, len) - } - fn query_call_fee_details( - call: RuntimeCall, - len: u32, - ) -> pallet_transaction_payment::FeeDetails { - TransactionPayment::query_call_fee_details(call, len) - } - fn query_weight_to_fee(weight: Weight) -> Balance { - TransactionPayment::weight_to_fee(weight) - } - fn query_length_to_fee(length: u32) -> Balance { - TransactionPayment::length_to_fee(length) - } - } - - impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info(header) - } - } - - #[cfg(feature = "try-runtime")] - impl frame_try_runtime::TryRuntime for Runtime { - fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) { - use super::configs::RuntimeBlockWeights; - - let weight = Executive::try_runtime_upgrade(checks).unwrap(); - (weight, RuntimeBlockWeights::get().max_block) - } - - fn execute_block( - block: Block, - state_root_check: bool, - signature_check: bool, - select: frame_try_runtime::TryStateSelect, - ) -> Weight { - // NOTE: intentional unwrap: we don't want to propagate the error backwards, and want to - // have a backtrace here. - Executive::try_execute_block(block, state_root_check, signature_check, select).unwrap() - } - } - - #[cfg(feature = "runtime-benchmarks")] - impl frame_benchmarking::Benchmark for Runtime { - fn benchmark_metadata(extra: bool) -> ( - Vec, - Vec, - ) { - use frame_benchmarking::{Benchmarking, BenchmarkList}; - use polkadot_sdk::frame_support::traits::StorageInfoTrait; - use frame_system_benchmarking::Pallet as SystemBench; - use cumulus_pallet_session_benchmarking::Pallet as SessionBench; - use super::*; - - let mut list = Vec::::new(); - list_benchmarks!(list, extra); - - let storage_info = AllPalletsWithSystem::storage_info(); - (list, storage_info) - } - - fn dispatch_benchmark( - config: frame_benchmarking::BenchmarkConfig - ) -> Result, alloc::string::String> { - use frame_benchmarking::{BenchmarkError, Benchmarking, BenchmarkBatch}; - use super::*; - - use frame_system_benchmarking::Pallet as SystemBench; - impl frame_system_benchmarking::Config for Runtime { - fn setup_set_code_requirements(code: &Vec) -> Result<(), BenchmarkError> { - ParachainSystem::initialize_for_set_code_benchmark(code.len() as u32); - Ok(()) - } - - fn verify_set_code() { - System::assert_last_event(cumulus_pallet_parachain_system::Event::::ValidationFunctionStored.into()); - } - } - - use cumulus_pallet_session_benchmarking::Pallet as SessionBench; - impl cumulus_pallet_session_benchmarking::Config for Runtime {} - - use polkadot_sdk::frame_support::traits::WhitelistedStorageKeys; - let whitelist = AllPalletsWithSystem::whitelisted_storage_keys(); - - let mut batches = Vec::::new(); - let params = (&config, &whitelist); - add_benchmarks!(params, batches); - - if batches.is_empty() { return Err("Benchmark not found for this pallet.".into()) } - Ok(batches) - } - } - - impl sp_genesis_builder::GenesisBuilder for Runtime { - fn build_state(config: Vec) -> sp_genesis_builder::Result { - build_state::(config) - } - - fn get_preset(id: &Option) -> Option> { - get_preset::(id, crate::genesis_config_presets::get_preset) - } - - fn preset_names() -> Vec { - crate::genesis_config_presets::preset_names() - } - } -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/benchmarks.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/benchmarks.rs deleted file mode 100644 index 179187b05..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/benchmarks.rs +++ /dev/null @@ -1,37 +0,0 @@ -// 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 - -polkadot_sdk::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/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs deleted file mode 100644 index 035d1c75b..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs +++ /dev/null @@ -1,344 +0,0 @@ -// 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; - -use polkadot_sdk::{staging_parachain_info as parachain_info, staging_xcm as xcm, *}; -#[cfg(not(feature = "runtime-benchmarks"))] -use polkadot_sdk::{staging_xcm_builder as xcm_builder, staging_xcm_executor as xcm_executor}; - -// 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 MaxConsumers = frame_support::traits::ConstU32<16>; -} - -/// Configure the pallet weight reclaim tx. -impl cumulus_pallet_weight_reclaim::Config for Runtime { - type WeightInfo = (); -} - -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; - type DoneSlashHandler = (); -} - -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>; - type WeightInfo = (); -} - -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; - type SelectCore = cumulus_pallet_parachain_system::DefaultCoreSelector; -} - -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 DisablingStrategy = (); - 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/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/xcm_config.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/xcm_config.rs deleted file mode 100644 index 85a513ac8..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/xcm_config.rs +++ /dev/null @@ -1,216 +0,0 @@ -use crate::{ - AccountId, AllPalletsWithSystem, Balances, ParachainInfo, ParachainSystem, PolkadotXcm, - Runtime, RuntimeCall, RuntimeEvent, RuntimeOrigin, WeightToFee, XcmpQueue, -}; - -use polkadot_sdk::{ - staging_xcm as xcm, staging_xcm_builder as xcm_builder, staging_xcm_executor as xcm_executor, *, -}; - -use frame_support::{ - parameter_types, - traits::{ConstU32, Contains, Everything, Nothing}, - weights::Weight, -}; -use frame_system::EnsureRoot; -use pallet_xcm::XcmPassthrough; -use polkadot_parachain_primitives::primitives::Sibling; -use polkadot_runtime_common::impls::ToAuthor; -use polkadot_sdk::{ - polkadot_sdk_frame::traits::Disabled, - staging_xcm_builder::{DenyRecursively, DenyThenTry}, -}; -use xcm::latest::prelude::*; -use xcm_builder::{ - AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowTopLevelPaidExecutionFrom, - DenyReserveTransferToRelayChain, EnsureXcmOrigin, FixedWeightBounds, - FrameTransactionalProcessor, FungibleAdapter, IsConcrete, NativeAsset, ParentIsPreset, - RelayChainAsNative, SiblingParachainAsNative, SiblingParachainConvertsVia, - SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, - TrailingSetTopicAsId, UsingComponents, WithComputedOrigin, WithUniqueTopic, -}; -use xcm_executor::XcmExecutor; - -parameter_types! { - pub const RelayLocation: Location = Location::parent(); - pub const RelayNetwork: Option = None; - pub RelayChainOrigin: RuntimeOrigin = cumulus_pallet_xcm::Origin::Relay.into(); - // For the real deployment, it is recommended to set `RelayNetwork` according to the relay chain - // and prepend `UniversalLocation` with `GlobalConsensus(RelayNetwork::get())`. - pub UniversalLocation: InteriorLocation = Parachain(ParachainInfo::parachain_id().into()).into(); -} - -/// Type for specifying how a `Location` can be converted into an `AccountId`. This is used -/// when determining ownership of accounts for asset transacting and when attempting to use XCM -/// `Transact` in order to determine the dispatch Origin. -pub type LocationToAccountId = ( - // The parent (Relay-chain) origin converts to the parent `AccountId`. - ParentIsPreset, - // Sibling parachain origins convert to AccountId via the `ParaId::into`. - SiblingParachainConvertsVia, - // Straight up local `AccountId32` origins just alias directly to `AccountId`. - AccountId32Aliases, -); - -/// Means for transacting assets on this chain. -pub type LocalAssetTransactor = FungibleAdapter< - // Use this currency: - Balances, - // Use this currency when it is a fungible asset matching the given location or name: - IsConcrete, - // Do a simple punn to convert an AccountId32 Location into a native chain account ID: - LocationToAccountId, - // Our chain's account ID type (we can't get away without mentioning it explicitly): - AccountId, - // We don't track any teleports. - (), ->; - -/// This is the type we use to convert an (incoming) XCM origin into a local `Origin` instance, -/// ready for dispatching a transaction with Xcm's `Transact`. There is an `OriginKind` which can -/// biases the kind of local `Origin` it will become. -pub type XcmOriginToTransactDispatchOrigin = ( - // Sovereign account converter; this attempts to derive an `AccountId` from the origin location - // using `LocationToAccountId` and then turn that into the usual `Signed` origin. Useful for - // foreign chains who want to have a local sovereign account on this chain which they control. - SovereignSignedViaLocation, - // Native converter for Relay-chain (Parent) location; will convert to a `Relay` origin when - // recognized. - RelayChainAsNative, - // Native converter for sibling Parachains; will convert to a `SiblingPara` origin when - // recognized. - SiblingParachainAsNative, - // Native signed account converter; this just converts an `AccountId32` origin into a normal - // `RuntimeOrigin::Signed` origin of the same 32-byte value. - SignedAccountId32AsNative, - // Xcm origins can be represented natively under the Xcm pallet's Xcm origin. - XcmPassthrough, -); - -parameter_types! { - // One XCM operation is 1_000_000_000 weight - almost certainly a conservative estimate. - pub UnitWeightCost: Weight = Weight::from_parts(1_000_000_000, 64 * 1024); - pub const MaxInstructions: u32 = 100; - pub const MaxAssetsIntoHolding: u32 = 64; -} - -pub struct ParentOrParentsExecutivePlurality; -impl Contains for ParentOrParentsExecutivePlurality { - fn contains(location: &Location) -> bool { - matches!( - location.unpack(), - (1, []) - | ( - 1, - [Plurality { - id: BodyId::Executive, - .. - }] - ) - ) - } -} - -pub type Barrier = TrailingSetTopicAsId< - DenyThenTry< - DenyRecursively, - ( - TakeWeightCredit, - WithComputedOrigin< - ( - AllowTopLevelPaidExecutionFrom, - AllowExplicitUnpaidExecutionFrom, - // ^^^ Parent and its exec plurality get free execution - ), - UniversalLocation, - ConstU32<8>, - >, - ), - >, ->; - -pub struct XcmConfig; -impl xcm_executor::Config for XcmConfig { - type RuntimeCall = RuntimeCall; - type XcmSender = XcmRouter; - type XcmEventEmitter = PolkadotXcm; - // How to withdraw and deposit an asset. - type AssetTransactor = LocalAssetTransactor; - type OriginConverter = XcmOriginToTransactDispatchOrigin; - type IsReserve = NativeAsset; - type IsTeleporter = (); // Teleporting is disabled. - type UniversalLocation = UniversalLocation; - type Barrier = Barrier; - type Weigher = FixedWeightBounds; - type Trader = - UsingComponents>; - type ResponseHandler = PolkadotXcm; - type AssetTrap = PolkadotXcm; - type AssetClaims = PolkadotXcm; - type SubscriptionService = PolkadotXcm; - type PalletInstancesInfo = AllPalletsWithSystem; - type MaxAssetsIntoHolding = MaxAssetsIntoHolding; - type AssetLocker = (); - type AssetExchanger = (); - type FeeManager = (); - type MessageExporter = (); - type UniversalAliases = Nothing; - type CallDispatcher = RuntimeCall; - type SafeCallFilter = Everything; - type Aliasers = Nothing; - type TransactionalProcessor = FrameTransactionalProcessor; - type HrmpNewChannelOpenRequestHandler = (); - type HrmpChannelAcceptedHandler = (); - type HrmpChannelClosingHandler = (); - type XcmRecorder = PolkadotXcm; -} - -/// Converts a local signed origin into an XCM location. Forms the basis for local origins -/// sending/executing XCMs. -pub type LocalOriginToLocation = SignedToAccountId32; - -/// The means for routing XCM messages which are not for local execution into the right message -/// queues. -pub type XcmRouter = WithUniqueTopic<( - // Two routers - use UMP to communicate with the relay chain: - cumulus_primitives_utility::ParentAsUmp, - // ..and XCMP to communicate with the sibling chains. - XcmpQueue, -)>; - -impl pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type SendXcmOrigin = EnsureXcmOrigin; - type XcmRouter = XcmRouter; - type ExecuteXcmOrigin = EnsureXcmOrigin; - type XcmExecuteFilter = Nothing; - // ^ Disable dispatchable execute on the XCM pallet. - // Needs to be `Everything` for local testing. - type XcmExecutor = XcmExecutor; - type XcmTeleportFilter = Everything; - type XcmReserveTransferFilter = Nothing; - type Weigher = FixedWeightBounds; - type UniversalLocation = UniversalLocation; - type RuntimeOrigin = RuntimeOrigin; - type RuntimeCall = RuntimeCall; - - const VERSION_DISCOVERY_QUEUE_SIZE: u32 = 100; - // ^ Override for AdvertisedXcmVersion default - type AdvertisedXcmVersion = pallet_xcm::CurrentXcmVersion; - type Currency = Balances; - type CurrencyMatcher = (); - type TrustedLockers = (); - type SovereignAccountOf = LocationToAccountId; - type MaxLockers = ConstU32<8>; - type WeightInfo = pallet_xcm::TestWeightInfo; - type AdminOrigin = EnsureRoot; - type MaxRemoteLockConsumers = ConstU32<0>; - type RemoteLockConsumerIdentifier = (); - // Aliasing is disabled: xcm_executor::Config::Aliasers is set to `Nothing`. - type AuthorizedAliasConsideration = Disabled; -} - -impl cumulus_pallet_xcm::Config for Runtime { - type RuntimeEvent = RuntimeEvent; - type XcmExecutor = XcmExecutor; -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/genesis_config_presets.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/genesis_config_presets.rs deleted file mode 100644 index a17fa8c91..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/genesis_config_presets.rs +++ /dev/null @@ -1,134 +0,0 @@ -use crate::{ - AccountId, BalancesConfig, CollatorSelectionConfig, ParachainInfoConfig, PolkadotXcmConfig, - RuntimeGenesisConfig, SessionConfig, SessionKeys, SudoConfig, EXISTENTIAL_DEPOSIT, -}; - -use alloc::{vec, vec::Vec}; - -use polkadot_sdk::{staging_xcm as xcm, *}; - -use cumulus_primitives_core::ParaId; -use frame_support::build_struct_json_patch; -use parachains_common::AuraId; -use serde_json::Value; -use sp_genesis_builder::PresetId; -use sp_keyring::Sr25519Keyring; - -/// The default XCM version to set in genesis config. -const SAFE_XCM_VERSION: u32 = xcm::prelude::XCM_VERSION; -/// Parachain id used for genesis config presets of parachain template. -#[docify::export_content] -pub const PARACHAIN_ID: u32 = 1000; - -/// Generate the session keys from individual elements. -/// -/// The input must be a tuple of individual keys (a single arg for now since we have just one key). -pub fn template_session_keys(keys: AuraId) -> SessionKeys { - SessionKeys { aura: keys } -} - -fn testnet_genesis( - invulnerables: Vec<(AccountId, AuraId)>, - endowed_accounts: Vec, - root: AccountId, - id: ParaId, -) -> Value { - build_struct_json_patch!(RuntimeGenesisConfig { - balances: BalancesConfig { - balances: endowed_accounts - .iter() - .cloned() - .map(|k| (k, 1u128 << 60)) - .collect::>(), - }, - parachain_info: ParachainInfoConfig { parachain_id: id }, - collator_selection: CollatorSelectionConfig { - invulnerables: invulnerables - .iter() - .cloned() - .map(|(acc, _)| acc) - .collect::>(), - candidacy_bond: EXISTENTIAL_DEPOSIT * 16, - }, - session: SessionConfig { - keys: invulnerables - .into_iter() - .map(|(acc, aura)| { - ( - acc.clone(), // account id - acc, // validator id - template_session_keys(aura), // session keys - ) - }) - .collect::>(), - }, - polkadot_xcm: PolkadotXcmConfig { - safe_xcm_version: Some(SAFE_XCM_VERSION) - }, - sudo: SudoConfig { key: Some(root) }, - }) -} - -fn local_testnet_genesis() -> Value { - testnet_genesis( - // initial collators. - vec![ - ( - Sr25519Keyring::Alice.to_account_id(), - Sr25519Keyring::Alice.public().into(), - ), - ( - Sr25519Keyring::Bob.to_account_id(), - Sr25519Keyring::Bob.public().into(), - ), - ], - Sr25519Keyring::well_known() - .map(|k| k.to_account_id()) - .collect(), - Sr25519Keyring::Alice.to_account_id(), - PARACHAIN_ID.into(), - ) -} - -fn development_config_genesis() -> Value { - testnet_genesis( - // initial collators. - vec![ - ( - Sr25519Keyring::Alice.to_account_id(), - Sr25519Keyring::Alice.public().into(), - ), - ( - Sr25519Keyring::Bob.to_account_id(), - Sr25519Keyring::Bob.public().into(), - ), - ], - Sr25519Keyring::well_known() - .map(|k| k.to_account_id()) - .collect(), - Sr25519Keyring::Alice.to_account_id(), - PARACHAIN_ID.into(), - ) -} - -/// Provides the JSON representation of predefined genesis config for given `id`. -pub fn get_preset(id: &PresetId) -> Option> { - let patch = match id.as_ref() { - sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET => local_testnet_genesis(), - sp_genesis_builder::DEV_RUNTIME_PRESET => development_config_genesis(), - _ => return None, - }; - Some( - serde_json::to_string(&patch) - .expect("serialization to json is expected to work. qed.") - .into_bytes(), - ) -} - -/// List of supported presets. -pub fn preset_names() -> Vec { - vec![ - PresetId::from(sp_genesis_builder::DEV_RUNTIME_PRESET), - PresetId::from(sp_genesis_builder::LOCAL_TESTNET_RUNTIME_PRESET), - ] -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs deleted file mode 100644 index 0d943404e..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs +++ /dev/null @@ -1,331 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -// `construct_runtime!` does a lot of recursion and requires us to increase the limit to 256. -#![recursion_limit = "256"] - -// Make the WASM binary available. -#[cfg(feature = "std")] -include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs")); - -pub mod apis; -#[cfg(feature = "runtime-benchmarks")] -mod benchmarks; -pub mod configs; -mod genesis_config_presets; -mod weights; - -extern crate alloc; -use alloc::vec::Vec; -use smallvec::smallvec; - -use polkadot_sdk::{staging_parachain_info as parachain_info, *}; - -use sp_runtime::{ - generic, impl_opaque_keys, - traits::{BlakeTwo256, IdentifyAccount, Verify}, - MultiSignature, -}; - -#[cfg(feature = "std")] -use sp_version::NativeVersion; -use sp_version::RuntimeVersion; - -use frame_support::weights::{ - constants::WEIGHT_REF_TIME_PER_SECOND, Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, - WeightToFeePolynomial, -}; -pub use genesis_config_presets::PARACHAIN_ID; -pub use sp_consensus_aura::sr25519::AuthorityId as AuraId; -pub use sp_runtime::{MultiAddress, Perbill, Permill}; - -use weights::ExtrinsicBaseWeight; - -/// Alias to 512-bit hash when used in the context of a transaction signature on the chain. -pub type Signature = MultiSignature; - -/// Some way of identifying an account on the chain. We intentionally make it equivalent -/// to the public key of our transaction signing scheme. -pub type AccountId = <::Signer as IdentifyAccount>::AccountId; - -/// Balance of an account. -pub type Balance = u128; - -/// Index of a transaction in the chain. -pub type Nonce = u32; - -/// A hash of some data used by the chain. -pub type Hash = sp_core::H256; - -/// An index to a block. -pub type BlockNumber = u32; - -/// The address format for describing accounts. -pub type Address = MultiAddress; - -/// Block header type as expected by this runtime. -pub type Header = generic::Header; - -/// Block type as expected by this runtime. -pub type Block = generic::Block; - -/// A Block signed with a Justification -pub type SignedBlock = generic::SignedBlock; - -/// BlockId type as expected by this runtime. -pub type BlockId = generic::BlockId; - -/// The extension to the basic transaction logic. -#[docify::export(template_signed_extra)] -pub type TxExtension = cumulus_pallet_weight_reclaim::StorageWeightReclaim< - Runtime, - ( - frame_system::CheckNonZeroSender, - frame_system::CheckSpecVersion, - frame_system::CheckTxVersion, - frame_system::CheckGenesis, - frame_system::CheckEra, - frame_system::CheckNonce, - frame_system::CheckWeight, - pallet_transaction_payment::ChargeTransactionPayment, - frame_metadata_hash_extension::CheckMetadataHash, - ), ->; - -/// Unchecked extrinsic type as expected by this runtime. -pub type UncheckedExtrinsic = - generic::UncheckedExtrinsic; - -/// All migrations of the runtime, aside from the ones declared in the pallets. -/// -/// This can be a tuple of types, each implementing `OnRuntimeUpgrade`. -#[allow(unused_parens)] -type Migrations = (); - -/// Executive: handles dispatch to the various modules. -pub type Executive = frame_executive::Executive< - Runtime, - Block, - frame_system::ChainContext, - Runtime, - AllPalletsWithSystem, - Migrations, ->; - -/// Handles converting a weight scalar to a fee value, based on the scale and granularity of the -/// node's balance type. -/// -/// This should typically create a mapping between the following ranges: -/// - `[0, MAXIMUM_BLOCK_WEIGHT]` -/// - `[Balance::min, Balance::max]` -/// -/// Yet, it can be used for any other sort of change to weight-fee. Some examples being: -/// - Setting it to `0` will essentially disable the weight fee. -/// - Setting it to `1` will cause the literal `#[weight = x]` values to be charged. -pub struct WeightToFee; -impl WeightToFeePolynomial for WeightToFee { - type Balance = Balance; - fn polynomial() -> WeightToFeeCoefficients { - // in Rococo, extrinsic base weight (smallest non-zero weight) is mapped to 1 MILLI_UNIT: - // in our template, we map to 1/10 of that, or 1/10 MILLI_UNIT - let p = MILLI_UNIT / 10; - let q = 100 * Balance::from(ExtrinsicBaseWeight::get().ref_time()); - smallvec![WeightToFeeCoefficient { - degree: 1, - negative: false, - coeff_frac: Perbill::from_rational(p % q, q), - coeff_integer: p / q, - }] - } -} - -/// Opaque types. These are used by the CLI to instantiate machinery that don't need to know -/// the specifics of the runtime. They can then be made to be agnostic over specific formats -/// of data like extrinsics, allowing for them to continue syncing the network through upgrades -/// to even the core data structures. -pub mod opaque { - use super::*; - pub use polkadot_sdk::sp_runtime::OpaqueExtrinsic as UncheckedExtrinsic; - use polkadot_sdk::sp_runtime::{ - generic, - traits::{BlakeTwo256, Hash as HashT}, - }; - - /// Opaque block header type. - pub type Header = generic::Header; - /// Opaque block type. - pub type Block = generic::Block; - /// Opaque block identifier type. - pub type BlockId = generic::BlockId; - /// Opaque block hash type. - pub type Hash = ::Output; -} - -impl_opaque_keys! { - pub struct SessionKeys { - pub aura: Aura, - } -} - -#[sp_version::runtime_version] -pub const VERSION: RuntimeVersion = RuntimeVersion { - spec_name: alloc::borrow::Cow::Borrowed("parachain-template-runtime"), - impl_name: alloc::borrow::Cow::Borrowed("parachain-template-runtime"), - authoring_version: 1, - spec_version: 1, - impl_version: 0, - apis: apis::RUNTIME_API_VERSIONS, - transaction_version: 1, - system_version: 1, -}; - -#[docify::export] -mod block_times { - /// This determines the average expected block time that we are targeting. Blocks will be - /// produced at a minimum duration defined by `SLOT_DURATION`. `SLOT_DURATION` is picked up by - /// `pallet_timestamp` which is in turn picked up by `pallet_aura` to implement `fn - /// slot_duration()`. - /// - /// Change this to adjust the block time. - pub const MILLI_SECS_PER_BLOCK: u64 = 6000; - - // NOTE: Currently it is not possible to change the slot duration after the chain has started. - // Attempting to do so will brick block production. - pub const SLOT_DURATION: u64 = MILLI_SECS_PER_BLOCK; -} -pub use block_times::*; - -// Time is measured by number of blocks. -pub const MINUTES: BlockNumber = 60_000 / (MILLI_SECS_PER_BLOCK as BlockNumber); -pub const HOURS: BlockNumber = MINUTES * 60; -pub const DAYS: BlockNumber = HOURS * 24; - -// Unit = the base number of indivisible units for balances -pub const UNIT: Balance = 1_000_000_000_000; -pub const MILLI_UNIT: Balance = 1_000_000_000; -pub const MICRO_UNIT: Balance = 1_000_000; - -/// The existential deposit. Set to 1/10 of the Connected Relay Chain. -pub const EXISTENTIAL_DEPOSIT: Balance = MILLI_UNIT; - -/// We assume that ~5% of the block weight is consumed by `on_initialize` handlers. This is -/// used to limit the maximal weight of a single extrinsic. -const AVERAGE_ON_INITIALIZE_RATIO: Perbill = Perbill::from_percent(5); - -/// We allow `Normal` extrinsics to fill up the block up to 75%, the rest can be used by -/// `Operational` extrinsics. -const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); - -#[docify::export(max_block_weight)] -/// We allow for 2 seconds of compute with a 6 second average block time. -const MAXIMUM_BLOCK_WEIGHT: Weight = Weight::from_parts( - WEIGHT_REF_TIME_PER_SECOND.saturating_mul(2), - cumulus_primitives_core::relay_chain::MAX_POV_SIZE as u64, -); - -#[docify::export] -mod async_backing_params { - /// Maximum number of blocks simultaneously accepted by the Runtime, not yet included - /// into the relay chain. - pub(crate) const UNINCLUDED_SEGMENT_CAPACITY: u32 = 3; - /// How many parachain blocks are processed by the relay chain per parent. Limits the - /// number of blocks authored per slot. - pub(crate) const BLOCK_PROCESSING_VELOCITY: u32 = 1; - /// Relay chain slot duration, in milliseconds. - pub(crate) const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000; -} -pub(crate) use async_backing_params::*; - -#[docify::export] -/// Aura consensus hook -type ConsensusHook = cumulus_pallet_aura_ext::FixedVelocityConsensusHook< - Runtime, - RELAY_CHAIN_SLOT_DURATION_MILLIS, - BLOCK_PROCESSING_VELOCITY, - UNINCLUDED_SEGMENT_CAPACITY, ->; - -/// The version information used to identify this runtime when compiled natively. -#[cfg(feature = "std")] -pub fn native_version() -> NativeVersion { - NativeVersion { - runtime_version: VERSION, - can_author_with: Default::default(), - } -} - -// Create the runtime by composing the FRAME pallets that were previously configured. -#[frame_support::runtime] -mod runtime { - #[runtime::runtime] - #[runtime::derive( - RuntimeCall, - RuntimeEvent, - RuntimeError, - RuntimeOrigin, - RuntimeFreezeReason, - RuntimeHoldReason, - RuntimeSlashReason, - RuntimeLockId, - RuntimeTask, - RuntimeViewFunction - )] - pub struct Runtime; - - #[runtime::pallet_index(0)] - pub type System = frame_system; - #[runtime::pallet_index(1)] - pub type ParachainSystem = cumulus_pallet_parachain_system; - #[runtime::pallet_index(2)] - pub type Timestamp = pallet_timestamp; - #[runtime::pallet_index(3)] - pub type ParachainInfo = parachain_info; - #[runtime::pallet_index(4)] - pub type WeightReclaim = cumulus_pallet_weight_reclaim; - - // Monetary stuff. - #[runtime::pallet_index(10)] - pub type Balances = pallet_balances; - #[runtime::pallet_index(11)] - pub type TransactionPayment = pallet_transaction_payment; - - // Governance - #[runtime::pallet_index(15)] - pub type Sudo = pallet_sudo; - - // Collator support. The order of these 4 are important and shall not change. - #[runtime::pallet_index(20)] - pub type Authorship = pallet_authorship; - #[runtime::pallet_index(21)] - pub type CollatorSelection = pallet_collator_selection; - #[runtime::pallet_index(22)] - pub type Session = pallet_session; - #[runtime::pallet_index(23)] - pub type Aura = pallet_aura; - #[runtime::pallet_index(24)] - pub type AuraExt = cumulus_pallet_aura_ext; - - // XCM helpers. - #[runtime::pallet_index(30)] - pub type XcmpQueue = cumulus_pallet_xcmp_queue; - #[runtime::pallet_index(31)] - pub type PolkadotXcm = pallet_xcm; - #[runtime::pallet_index(32)] - pub type CumulusXcm = cumulus_pallet_xcm; - #[runtime::pallet_index(33)] - pub type MessageQueue = pallet_message_queue; - - // Template - #[runtime::pallet_index(50)] - pub type TemplatePallet = pallet_parachain_template; - - #[runtime::pallet_index(51)] - pub type Utility = pallet_utility; - - #[runtime::pallet_index(52)] - pub type CustomPallet = custom_pallet; -} - -#[docify::export(register_validate_block)] -cumulus_pallet_parachain_system::register_validate_block! { - Runtime = Runtime, - BlockExecutor = cumulus_pallet_aura_ext::BlockExecutor::, -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/block_weights.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/block_weights.rs deleted file mode 100644 index d38703407..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/block_weights.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod constants { - use polkadot_sdk::*; - - use frame_support::{ - parameter_types, - weights::{constants, Weight}, - }; - - parameter_types! { - /// Importing a block with 0 Extrinsics. - pub const BlockExecutionWeight: Weight = - Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(5_000_000), 0); - } - - #[cfg(test)] - mod test_weights { - use polkadot_sdk::*; - - use frame_support::weights::constants; - - /// Checks that the weight exists and is sane. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - let w = super::constants::BlockExecutionWeight::get(); - - // At least 100 µs. - assert!( - w.ref_time() >= 100u64 * constants::WEIGHT_REF_TIME_PER_MICROS, - "Weight should be at least 100 µs." - ); - // At most 50 ms. - assert!( - w.ref_time() <= 50u64 * constants::WEIGHT_REF_TIME_PER_MILLIS, - "Weight should be at most 50 ms." - ); - } - } -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/extrinsic_weights.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/extrinsic_weights.rs deleted file mode 100644 index 685a20af6..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/extrinsic_weights.rs +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod constants { - use polkadot_sdk::*; - - use frame_support::{ - parameter_types, - weights::{constants, Weight}, - }; - - parameter_types! { - /// Executing a NO-OP `System::remarks` Extrinsic. - pub const ExtrinsicBaseWeight: Weight = - Weight::from_parts(constants::WEIGHT_REF_TIME_PER_NANOS.saturating_mul(125_000), 0); - } - - #[cfg(test)] - mod test_weights { - use polkadot_sdk::*; - - use frame_support::weights::constants; - - /// Checks that the weight exists and is sane. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - let w = super::constants::ExtrinsicBaseWeight::get(); - - // At least 10 µs. - assert!( - w.ref_time() >= 10u64 * constants::WEIGHT_REF_TIME_PER_MICROS, - "Weight should be at least 10 µs." - ); - // At most 1 ms. - assert!( - w.ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, - "Weight should be at most 1 ms." - ); - } - } -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/mod.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/mod.rs deleted file mode 100644 index b473d49e2..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -//! Expose the auto generated weight files. - -pub mod block_weights; -pub mod extrinsic_weights; -pub mod paritydb_weights; -pub mod rocksdb_weights; - -pub use block_weights::constants::BlockExecutionWeight; -pub use extrinsic_weights::constants::ExtrinsicBaseWeight; -pub use rocksdb_weights::constants::RocksDbWeight; diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/paritydb_weights.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/paritydb_weights.rs deleted file mode 100644 index d97a2752b..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/paritydb_weights.rs +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod constants { - use polkadot_sdk::*; - - use frame_support::{ - parameter_types, - weights::{constants, RuntimeDbWeight}, - }; - - parameter_types! { - /// `ParityDB` can be enabled with a feature flag, but is still experimental. These weights - /// are available for brave runtime engineers who may want to try this out as default. - pub const ParityDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 8_000 * constants::WEIGHT_REF_TIME_PER_NANOS, - write: 50_000 * constants::WEIGHT_REF_TIME_PER_NANOS, - }; - } - - #[cfg(test)] - mod test_db_weights { - use polkadot_sdk::*; - - use super::constants::ParityDbWeight as W; - use frame_support::weights::constants; - - /// Checks that all weights exist and have sane values. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - // At least 1 µs. - assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, - "Read weight should be at least 1 µs." - ); - assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, - "Write weight should be at least 1 µs." - ); - // At most 1 ms. - assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, - "Read weight should be at most 1 ms." - ); - assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, - "Write weight should be at most 1 ms." - ); - } - } -} diff --git a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/rocksdb_weights.rs b/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/rocksdb_weights.rs deleted file mode 100644 index 2c7c73107..000000000 --- a/.snippets/code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/weights/rocksdb_weights.rs +++ /dev/null @@ -1,67 +0,0 @@ -// This file is part of Substrate. - -// Copyright (C) Parity Technologies (UK) Ltd. -// SPDX-License-Identifier: Apache-2.0 - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod constants { - use polkadot_sdk::*; - - use frame_support::{ - parameter_types, - weights::{constants, RuntimeDbWeight}, - }; - - parameter_types! { - /// By default, Substrate uses `RocksDB`, so this will be the weight used throughout - /// the runtime. - pub const RocksDbWeight: RuntimeDbWeight = RuntimeDbWeight { - read: 25_000 * constants::WEIGHT_REF_TIME_PER_NANOS, - write: 100_000 * constants::WEIGHT_REF_TIME_PER_NANOS, - }; - } - - #[cfg(test)] - mod test_db_weights { - use polkadot_sdk::*; - - use super::constants::RocksDbWeight as W; - use frame_support::weights::constants; - - /// Checks that all weights exist and have sane values. - // NOTE: If this test fails but you are sure that the generated values are fine, - // you can delete it. - #[test] - fn sane() { - // At least 1 µs. - assert!( - W::get().reads(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, - "Read weight should be at least 1 µs." - ); - assert!( - W::get().writes(1).ref_time() >= constants::WEIGHT_REF_TIME_PER_MICROS, - "Write weight should be at least 1 µs." - ); - // At most 1 ms. - assert!( - W::get().reads(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, - "Read weight should be at most 1 ms." - ); - assert!( - W::get().writes(1).ref_time() <= constants::WEIGHT_REF_TIME_PER_MILLIS, - "Write weight should be at most 1 ms." - ); - } - } -} diff --git a/parachains/customize-runtime/pallet-development/add-pallet-to-runtime.md b/parachains/customize-runtime/pallet-development/add-pallet-to-runtime.md deleted file mode 100644 index 3aa7939c6..000000000 --- a/parachains/customize-runtime/pallet-development/add-pallet-to-runtime.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Add Pallets to the Runtime -description: Add pallets to your runtime for custom functionality. Learn to configure and integrate pallets in Polkadot SDK-based blockchains. -tutorial_badge: Beginner -categories: Basics, Parachains ---- - -# Add Pallets to the Runtime - -## Introduction - -In previous tutorials, you learned how to [create a custom pallet](/tutorials/polkadot-sdk/parachains/zero-to-hero/build-custom-pallet/){target=\_blank} and [test it](/tutorials/polkadot-sdk/parachains/zero-to-hero/pallet-unit-testing/){target=\_blank}. The next step is to include this pallet in your runtime, integrating it into the core logic of your blockchain. - -This tutorial will guide you through adding two pallets to your runtime: the custom pallet you previously developed and the [utility pallet](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/index.html){target=\_blank}. This standard Polkadot SDK pallet provides powerful dispatch functionality. The utility pallet offers, for example, batch dispatch, a stateless operation that enables executing multiple calls in a single transaction. - -## Add the Pallets as Dependencies - -First, you'll update the runtime's `Cargo.toml` file to include the Utility pallet and your custom pallets as dependencies for the runtime. Follow these steps: - -1. Open the `runtime/Cargo.toml` file and locate the `[dependencies]` section. Add pallet-utility as one of the features for the `polkadot-sdk` dependency with the following line: - - ```toml hl_lines="4" title="runtime/Cargo.toml" - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/runtime/Cargo.toml:19:19' - ... - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml:26:27' - ... - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml:55:55' - ``` - -2. In the same `[dependencies]` section, add the custom pallet that you built from scratch with the following line: - - ```toml hl_lines="3" title="Cargo.toml" - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/runtime/Cargo.toml:19:19' - ... - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/runtime/Cargo.toml:30:30' - ``` - -3. In the `[features]` section, add the custom pallet to the `std` feature list: - - ```toml hl_lines="5" title="Cargo.toml" - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/runtime/Cargo.toml:32:34' - ... - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/runtime/Cargo.toml:43:43' - ... - ] - ``` - -3. Save the changes and close the `Cargo.toml` file. - - Once you have saved your file, it should look like the following: - - ???- code "runtime/Cargo.toml" - - ```rust title="runtime/Cargo.toml" - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/Cargo.toml' - ``` - -Update your root parachain template's `Cargo.toml` file to include your custom pallet as a dependency. Follow these steps: - -1. Open the `./Cargo.toml` file and locate the `[workspace]` section. - - Make sure the `custom-pallet` is a member of the workspace: - - ```toml hl_lines="4" title="Cargo.toml" - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/Cargo.toml:8:14' - ``` - -???- code "./Cargo.toml" - - ```rust title="./Cargo.toml" - --8<-- 'https://raw.githubusercontent.com/papermoonio/zero-to-hero-tutorial/refs/heads/v0.0.4-complete-tutorial/Cargo.toml' - ``` - - -### Update the Runtime Configuration - -Configure the pallets by implementing their `Config` trait and update the runtime macro to include the new pallets: - -1. Add the `OriginCaller` import: - - ```rust title="mod.rs" hl_lines="8" - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs:59:60' - ... - ``` - -2. Implement the [`Config`](https://paritytech.github.io/polkadot-sdk/master/pallet_utility/pallet/trait.Config.html){target=\_blank} trait for both pallets at the end of the `runtime/src/config/mod.rs` file: - - ```rust title="mod.rs" hl_lines="8-25" - ... - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs:320:332' - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/configs/mod.rs:334:342' - } - ``` - -3. Locate the `#[frame_support::runtime]` macro in the `runtime/src/lib.rs` file and add the pallets: - - ```rust hl_lines="9-14" title="lib.rs" - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs:256:259' - ... - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs:270:271' - - --8<-- 'code/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/src/lib.rs:320:324' - } - ``` - -## Recompile the Runtime - -After adding and configuring your pallets in the runtime, the next step is to ensure everything is set up correctly. To do this, recompile the runtime with the following command (make sure you're in the project's root directory): - -```bash -cargo build --release -``` - -This command ensures the runtime compiles without errors, validates the pallet configurations, and prepares the build for subsequent testing or deployment. - -## Run Your Chain Locally - -Launch your parachain locally and start producing blocks: - -!!!tip - Generated chain TestNet specifications include development accounts "Alice" and "Bob." These accounts are pre-funded with native parachain currency, allowing you to sign and send TestNet transactions. Take a look at the [Polkadot.js Accounts section](https://polkadot.js.org/apps/#/accounts){target=\_blank} to view the development accounts for your chain. - -1. Create a new chain specification file with the updated runtime: - - ```bash - chain-spec-builder create -t development \ - --relay-chain paseo \ - --para-id 1000 \ - --runtime ./target/release/wbuild/parachain-template-runtime/parachain_template_runtime.compact.compressed.wasm \ - named-preset development - ``` - -2. Start the omni node with the generated chain specification: - - ```bash - polkadot-omni-node --chain ./chain_spec.json --dev - ``` - -3. Verify you can interact with the new pallets using the [Polkadot.js Apps](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2F127.0.0.1%3A9944#/extrinsics){target=\_blank} interface. Navigate to the **Extrinsics** tab and check that you can see both pallets: - - - Utility pallet - - ![](/images/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/add-pallets-to-runtime-01.webp) - - - - Custom pallet - - ![](/images/parachains/customize-runtime/pallet-development/add-pallet-to-runtime/add-pallets-to-runtime-02.webp) - -## Where to Go Next - -
- -- Tutorial __Deploy on Paseo TestNet__ - - --- - - Deploy your Polkadot SDK blockchain on Paseo! Follow this step-by-step guide for a seamless journey to a successful TestNet deployment. - - [:octicons-arrow-right-24: Get Started](/tutorials/polkadot-sdk/parachains/zero-to-hero/deploy-to-testnet/) - -- Tutorial __Pallet Benchmarking (Optional)__ - - --- - - 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/pallet-benchmarking/) - -
diff --git a/parachains/customize-runtime/pallet-development/pallet-testing.md b/parachains/customize-runtime/pallet-development/pallet-testing.md index 6dc75eb5e..1bc102974 100644 --- a/parachains/customize-runtime/pallet-development/pallet-testing.md +++ b/parachains/customize-runtime/pallet-development/pallet-testing.md @@ -1,86 +1,611 @@ --- -title: Pallet Testing -description: Learn how to efficiently test pallets in the Polkadot SDK, ensuring the reliability and security of your pallets operations. +title: Pallet Unit Testing +description: Learn how to write comprehensive unit tests for your custom pallets using mock runtimes, ensuring reliability and correctness before deployment. categories: Parachains --- -# Pallet Testing +# Pallet Unit Testing ## Introduction -Unit testing in the Polkadot SDK helps ensure that the functions provided by a pallet behave as expected. It also confirms that data and events associated with a pallet are processed correctly during interactions. The Polkadot SDK offers a set of APIs to create a test environment to simulate runtime and mock transaction execution for extrinsics and queries. +Unit testing in the Polkadot SDK helps ensure that the functions provided by a pallet behave as expected. It also confirms that data and events associated with a pallet are processed correctly during interactions. With your mock runtime in place from the [previous guide](/parachains/customize-runtime/pallet-development/mock-runtime/), you can now write comprehensive tests that verify your pallet's behavior in isolation. -To begin unit testing, you must first set up a mock runtime that simulates blockchain behavior, incorporating the necessary pallets. For a deeper understanding, consult the [Mock Runtime](/parachains/customize-runtime/pallet-development/mock-runtime/){target=\_blank} guide. +In this guide, you'll learn how to: -## Writing Unit Tests +- Structure test modules effectively. +- Test dispatchable functions. +- Verify storage changes. +- Check event emission. +- Test error conditions. +- Use genesis configurations in tests. -Once the mock runtime is in place, the next step is to write unit tests that evaluate the functionality of your pallet. Unit tests allow you to test specific pallet features in isolation, ensuring that each function behaves correctly under various conditions. These tests typically reside in your pallet module's `test.rs` file. +## Prerequisites -Unit tests in the Polkadot SDK use the Rust testing framework, and the mock runtime you've defined earlier will serve as the test environment. Below are the typical steps involved in writing unit tests for a pallet. +Before you begin, ensure you: -The tests confirm that: +- Completed the [Make a Custom Pallet](/parachains/customize-runtime/pallet-development/create-a-pallet/) guide. +- Completed the [Mock Your Runtime](/parachains/customize-runtime/pallet-development/mock-runtime/) guide. +- Configured custom counter pallet with mock runtime in `pallets/pallet-custom`. +- Understood the basics of [Rust testing](https://doc.rust-lang.org/book/ch11-00-testing.html){target=\_blank}. -- **Pallets initialize correctly**: At the start of each test, the system should initialize with block number 0, and the pallets should be in their default states. -- **Pallets modify each other's state**: The second test shows how one pallet can trigger changes in another pallet's internal state, confirming proper cross-pallet interactions. -- **State transitions between blocks are seamless**: By simulating block transitions, the tests validate that the runtime responds correctly to changes in the block number. +## Understanding FRAME Testing Tools -Testing pallet interactions within the runtime is critical for ensuring the blockchain behaves as expected under real-world conditions. Writing integration tests allows validation of how pallets function together, preventing issues that might arise when the system is fully assembled. +[FRAME](/reference/glossary/#frame-framework-for-runtime-aggregation-of-modularized-entities){target=\_blank} provides specialized testing macros and utilities that make pallet testing more efficient: -This approach provides a comprehensive view of the runtime's functionality, ensuring the blockchain is stable and reliable. +### Assertion Macros -### Test Initialization +- **[`assert_ok!`](https://paritytech.github.io/polkadot-sdk/master/frame_support/macro.assert_ok.html){target=\_blank}** - Asserts that a dispatchable call succeeds. +- **[`assert_noop!`](https://paritytech.github.io/polkadot-sdk/master/frame_support/macro.assert_noop.html){target=\_blank}** - Asserts that a call fails without changing state (no operation). +- **[`assert_eq!`](https://doc.rust-lang.org/std/macro.assert_eq.html){target=\_blank}** - Standard Rust equality assertion. -Each test starts by initializing the runtime environment, typically using the `new_test_ext()` function, which sets up the mock storage and environment. +!!!info "`assert_noop!` Explained" + Use `assert_noop!` to ensure the operation fails without any state changes. This is critical for testing error conditions - it verifies both that the error occurs AND that no storage was modified. + +### System Pallet Test Helpers + +The [`frame_system`](https://paritytech.github.io/polkadot-sdk/master/frame_system/index.html){target=\_blank} pallet provides useful methods for testing: + +- **[`System::events()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.events){target=\_blank}** - Returns all events emitted during the test. +- **[`System::assert_last_event()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_last_event){target=\_blank}** - Asserts the last event matches expectations. +- **[`System::set_block_number()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.set_block_number){target=\_blank}** - Sets the current block number. + +!!!info "Events and Block Number" + Events are not emitted on block 0 (genesis block). If you need to test events, ensure you set the block number to at least 1 using `System::set_block_number(1)`. + +### Origin Types + +- **[`RuntimeOrigin::root()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.Root){target=\_blank}** - Root/sudo origin for privileged operations. +- **[`RuntimeOrigin::signed(account)`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.Signed){target=\_blank}** - Signed origin from a specific account. +- **[`RuntimeOrigin::none()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/enum.RawOrigin.html#variant.None){target=\_blank}** - No origin (typically fails for most operations). + +Learn more about origins in the [FRAME Origin reference document](https://paritytech.github.io/polkadot-sdk/master/polkadot_sdk_docs/reference_docs/frame_origin/index.html){target=\_blank}. + +## Create the Tests Module + +Create a new file for your tests within the pallet directory: + +1. Navigate to your pallet directory: + + ```bash + cd pallets/pallet-custom/src + ``` + +2. Create a new file named `tests.rs`: + + ```bash + touch tests.rs + ``` + +3. Open `src/lib.rs` and add the tests module declaration after the mock module: + + ```rust title="src/lib.rs" + #![cfg_attr(not(feature = "std"), no_std)] + + pub use pallet::*; + + #[cfg(test)] + mod mock; + + #[cfg(test)] + mod tests; + + #[frame::pallet] + pub mod pallet { + // ... existing pallet code + } + ``` + +## Set Up the Test Module + +Open `src/tests.rs` and add the basic structure with necessary imports: + +```rust +use crate::{mock::*, Error, Event}; +use frame::deps::frame_support::{assert_noop, assert_ok}; +use frame::deps::sp_runtime::DispatchError; +``` + +This setup imports: + +- The mock runtime and test utilities from `mock.rs` +- Your pallet's `Error` and `Event` types +- FRAME's assertion macros via `frame::deps` +- `DispatchError` for testing origin checks + +???+ code "Complete Pallet Code Reference" + Here's the complete pallet code that you'll be testing throughout this guide: + + ```rust + ---8<-- 'code/parachains/customize-runtime/pallet-development/create-a-pallet/lib-complete.rs' + ``` + +## Write Your First Test + +Let's start with a simple test to verify the increment function works correctly. + +### Test Basic Increment + +Test that the increment function increases counter value and emits events. + +```rust +#[test] +fn increment_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 1u64; + + // Increment by 50 + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 50)); + assert_eq!(crate::CounterValue::::get(), 50); + + // Check event was emitted + System::assert_last_event( + Event::CounterIncremented { + new_value: 50, + who: account, + amount: 50, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); + }); +} +``` + +Run your first test: + +```bash +cargo test --package pallet-custom increment_works +``` + +You should see: + +``` +running 1 test +test tests::increment_works ... ok +``` + +Congratulations! You've written and run your first pallet test. + +## Test Error Conditions + +Now let's test that our pallet correctly handles errors. Error testing is crucial to ensure your pallet fails safely. + +### Test Overflow Protection + +Test that incrementing at u32::MAX fails with Overflow error. ```rust ---8<-- 'code/parachains/customize-runtime/pallet-development/pallet-testing/test-initialization.rs' +#[test] +fn increment_fails_on_overflow() { + new_test_ext_with_counter(u32::MAX).execute_with(|| { + // Attempt to increment when at max u32 should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 1), + Error::::Overflow + ); + }); +} +``` + +Test overflow protection: + +```bash +cargo test --package pallet-custom increment_fails_on_overflow ``` -### Function Call Testing +### Test Underflow Protection -Call the pallet's extrinsics or functions to simulate user interaction or internal logic. Use the `assert_ok!` macro to check for successful execution and `assert_err!` to verify that errors are correctly handled. +Test that decrementing below zero fails with Underflow error. ```rust ---8<-- 'code/parachains/customize-runtime/pallet-development/pallet-testing/function-call-testing.rs' +#[test] +fn decrement_fails_on_underflow() { + new_test_ext_with_counter(10).execute_with(|| { + // Attempt to decrement below zero should fail + assert_noop!( + CustomPallet::decrement(RuntimeOrigin::signed(1), 11), + Error::::Underflow + ); + }); +} ``` -### Storage Testing +Verify underflow protection: + +```bash +cargo test --package pallet-custom decrement_fails_on_underflow +``` + +## Test Access Control + +Verify that origin checks work correctly and unauthorized access is prevented. -After calling a function or extrinsic in your pallet, it's essential to verify that the state changes in the pallet's storage match the expected behavior to ensure data is updated correctly based on the actions taken. +### Test Root-Only Access -The following example shows how to test the storage behavior before and after the function call: +Test that set_counter_value requires root origin and rejects signed origins. ```rust ---8<-- 'code/parachains/customize-runtime/pallet-development/pallet-testing/storage-testing.rs' +#[test] +fn set_counter_value_requires_root() { + new_test_ext().execute_with(|| { + let alice = 1u64; + + // When: non-root user tries to set counter + // Then: should fail with BadOrigin + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::signed(alice), 100), + DispatchError::BadOrigin + ); + + // But root should succeed + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); + }); +} ``` -### Event Testing +Test access control: + +```bash +cargo test --package pallet-custom set_counter_value_requires_root +``` + +## Test Event Emission + +Verify that events are emitted correctly with the right data. + +### Test Event Data -It's also crucial to test the events that your pallet emits during execution. By default, events generated in a pallet using the [`#generate_deposit`](https://paritytech.github.io/polkadot-sdk/master/frame_support/pallet_macros/attr.generate_deposit.html){target=\_blank} macro are stored under the system's event storage key (system/events) as [`EventRecord`](https://paritytech.github.io/polkadot-sdk/master/frame_system/struct.EventRecord.html){target=\_blank} entries. These can be accessed using [`System::events()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.events){target=\_blank} or verified with specific helper methods provided by the system pallet, such as [`assert_has_event`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_has_event){target=\_blank} and [`assert_last_event`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.assert_last_event){target=\_blank}. +The [`increment_works`](/parachains/customize-runtime/pallet-development/pallet-testing/#test-basic-increment) test (shown earlier) already demonstrates event testing by: -Here's an example of testing events in a mock runtime: +1. Setting the block number to 1 to enable event emission. +2. Calling the dispatchable function. +3. Using `System::assert_last_event()` to verify the correct event was emitted with expected data. + +This pattern applies to all dispatchables that emit events. For a dedicated event-only test focusing on the `set_counter_value` function: + +Test that set_counter_value updates storage and emits correct event. ```rust ---8<-- 'code/parachains/customize-runtime/pallet-development/pallet-testing/event-testing.rs' +#[test] +fn set_counter_value_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + // Set counter to 100 + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); + + // Check event was emitted + System::assert_last_event(Event::CounterValueSet { new_value: 100 }.into()); + }); +} ``` -Some key considerations are: +Run the event test: -- **Block number**: Events are not emitted on the genesis block, so you need to set the block number using [`System::set_block_number()`](https://paritytech.github.io/polkadot-sdk/master/frame_system/pallet/struct.Pallet.html#method.set_block_number){target=\_blank} to ensure events are triggered. -- **Converting events**: Use `.into()` when instantiating your pallet's event to convert it into a generic event type, as required by the system's event storage. +```bash +cargo test --package pallet-custom set_counter_value_works +``` -## Where to Go Next +## Test Genesis Configuration + +Verify that genesis configuration works correctly. + +### Test Genesis Setup + +Test that genesis configuration correctly initializes counter and user interactions. + +```rust +#[test] +fn genesis_config_works() { + new_test_ext_with_interactions(42, vec![(1, 5), (2, 10)]).execute_with(|| { + // Check initial counter value + assert_eq!(crate::CounterValue::::get(), 42); + + // Check initial user interactions + assert_eq!(crate::UserInteractions::::get(1), 5); + assert_eq!(crate::UserInteractions::::get(2), 10); + }); +} +``` -- Dive into the full implementation of the [`mock.rs`](https://github.com/paritytech/polkadot-sdk/blob/master/templates/solochain/pallets/template/src/mock.rs){target=\_blank} and [`test.rs`](https://github.com/paritytech/polkadot-sdk/blob/master/templates/solochain/pallets/template/src/tests.rs){target=\_blank} files in the [Solochain Template](https://github.com/paritytech/polkadot-sdk/tree/master/templates/solochain){target=_blank}. +Test genesis configuration: + +```bash +cargo test --package pallet-custom genesis_config_works +``` + +## Run All Tests + +Now run all your tests together: + +```bash +cargo test --package pallet-custom +``` + +You should see all tests passing: + +
+ $ cargo test --package pallet-custom + running 15 tests + test mock::__construct_runtime_integrity_test::runtime_integrity_tests ... ok + test mock::test_genesis_config_builds ... ok + test tests::decrement_fails_on_underflow ... ok + test tests::decrement_tracks_multiple_interactions ... ok + test tests::decrement_works ... ok + test tests::different_users_tracked_separately ... ok + test tests::genesis_config_works ... ok + test tests::increment_fails_on_overflow ... ok + test tests::increment_respects_max_value ... ok + test tests::increment_tracks_multiple_interactions ... ok + test tests::increment_works ... ok + test tests::mixed_increment_and_decrement_works ... ok + test tests::set_counter_value_requires_root ... ok + test tests::set_counter_value_respects_max_value ... ok + test tests::set_counter_value_works ... ok + + test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out +
+ +!!!note "Mock Runtime Tests" + You'll notice 2 additional tests from the `mock` module: + + - `mock::__construct_runtime_integrity_test::runtime_integrity_tests` - Auto-generated test that validates runtime construction + - `mock::test_genesis_config_builds` - Validates that genesis configuration builds correctly + + These tests are automatically generated from your mock runtime setup and help ensure the test environment itself is valid. + +Congratulations! You have a well-tested pallet covering the essential testing patterns! + +These tests demonstrate comprehensive coverage including basic operations, error conditions, access control, event emission, state management, and genesis configuration. As you build more complex pallets, you'll apply these same patterns to test additional functionality. + +??? code "Full Test Suite Code" + Here's the complete `tests.rs` file for quick reference: + + ```rust + use crate::{mock::*, Error, Event}; + use frame::deps::frame_support::{assert_noop, assert_ok}; + use frame::deps::sp_runtime::DispatchError; + + #[test] + fn set_counter_value_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + // Set counter to 100 + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 100)); + assert_eq!(crate::CounterValue::::get(), 100); + + // Check event was emitted + System::assert_last_event(Event::CounterValueSet { new_value: 100 }.into()); + }); + } + + #[test] + fn set_counter_value_requires_root() { + new_test_ext().execute_with(|| { + // Attempt to set counter with non-root origin should fail + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::signed(1), 100), + DispatchError::BadOrigin + ); + }); + } + + #[test] + fn set_counter_value_respects_max_value() { + new_test_ext().execute_with(|| { + // Attempt to set counter above max value (1000) should fail + assert_noop!( + CustomPallet::set_counter_value(RuntimeOrigin::root(), 1001), + Error::::CounterMaxValueExceeded + ); + + // Setting to exactly max value should work + assert_ok!(CustomPallet::set_counter_value(RuntimeOrigin::root(), 1000)); + assert_eq!(crate::CounterValue::::get(), 1000); + }); + } + + #[test] + fn increment_works() { + new_test_ext().execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 1u64; + + // Increment by 50 + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 50)); + assert_eq!(crate::CounterValue::::get(), 50); + + // Check event was emitted + System::assert_last_event( + Event::CounterIncremented { + new_value: 50, + who: account, + amount: 50, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); + }); + } + + #[test] + fn increment_tracks_multiple_interactions() { + new_test_ext().execute_with(|| { + let account = 1u64; + + // Increment multiple times + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 10)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 20)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 30)); + + // Check counter value + assert_eq!(crate::CounterValue::::get(), 60); + + // Check user interactions were tracked (should be 3) + assert_eq!(crate::UserInteractions::::get(account), 3); + }); + } + + #[test] + fn increment_fails_on_overflow() { + new_test_ext_with_counter(u32::MAX).execute_with(|| { + // Attempt to increment when at max u32 should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 1), + Error::::Overflow + ); + }); + } + + #[test] + fn increment_respects_max_value() { + new_test_ext_with_counter(950).execute_with(|| { + // Incrementing past max value (1000) should fail + assert_noop!( + CustomPallet::increment(RuntimeOrigin::signed(1), 51), + Error::::CounterMaxValueExceeded + ); + + // Incrementing to exactly max value should work + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(1), 50)); + assert_eq!(crate::CounterValue::::get(), 1000); + }); + } + + #[test] + fn decrement_works() { + new_test_ext_with_counter(100).execute_with(|| { + // Set block number to 1 so events are registered + System::set_block_number(1); + + let account = 2u64; + + // Decrement by 30 + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 30)); + assert_eq!(crate::CounterValue::::get(), 70); + + // Check event was emitted + System::assert_last_event( + Event::CounterDecremented { + new_value: 70, + who: account, + amount: 30, + } + .into(), + ); + + // Check user interactions were tracked + assert_eq!(crate::UserInteractions::::get(account), 1); + }); + } + + #[test] + fn decrement_fails_on_underflow() { + new_test_ext_with_counter(10).execute_with(|| { + // Attempt to decrement below zero should fail + assert_noop!( + CustomPallet::decrement(RuntimeOrigin::signed(1), 11), + Error::::Underflow + ); + }); + } + + #[test] + fn decrement_tracks_multiple_interactions() { + new_test_ext_with_counter(100).execute_with(|| { + let account = 3u64; + + // Decrement multiple times + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 10)); + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 20)); + + // Check counter value + assert_eq!(crate::CounterValue::::get(), 70); + + // Check user interactions were tracked (should be 2) + assert_eq!(crate::UserInteractions::::get(account), 2); + }); + } + + #[test] + fn mixed_increment_and_decrement_works() { + new_test_ext_with_counter(50).execute_with(|| { + let account = 4u64; + + // Mix of increment and decrement + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 25)); + assert_eq!(crate::CounterValue::::get(), 75); + + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account), 15)); + assert_eq!(crate::CounterValue::::get(), 60); + + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account), 10)); + assert_eq!(crate::CounterValue::::get(), 70); + + // Check user interactions were tracked (should be 3) + assert_eq!(crate::UserInteractions::::get(account), 3); + }); + } + + #[test] + fn different_users_tracked_separately() { + new_test_ext().execute_with(|| { + let account1 = 1u64; + let account2 = 2u64; + + // User 1 increments + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account1), 10)); + assert_ok!(CustomPallet::increment(RuntimeOrigin::signed(account1), 10)); + + // User 2 decrements + assert_ok!(CustomPallet::decrement(RuntimeOrigin::signed(account2), 5)); + + // Check counter value (10 + 10 - 5 = 15) + assert_eq!(crate::CounterValue::::get(), 15); + + // Check user interactions are tracked separately + assert_eq!(crate::UserInteractions::::get(account1), 2); + assert_eq!(crate::UserInteractions::::get(account2), 1); + }); + } + + #[test] + fn genesis_config_works() { + new_test_ext_with_interactions(42, vec![(1, 5), (2, 10)]).execute_with(|| { + // Check initial counter value + assert_eq!(crate::CounterValue::::get(), 42); + + // Check initial user interactions + assert_eq!(crate::UserInteractions::::get(1), 5); + assert_eq!(crate::UserInteractions::::get(2), 10); + }); + } + ``` + +## Where to Go Next
-- Guide __Benchmarking__ +- Guide __Add Your Custom Pallet to the Runtime__ --- - Explore methods to measure the performance and execution cost of your pallet. + Your pallet is tested and ready! Learn how to integrate it into your runtime. - [:octicons-arrow-right-24: Reference](/develop/parachains/testing/benchmarking) + [:octicons-arrow-right-24: Integrate](/parachains/customize-runtime/pallet-development/add-to-runtime/) -
\ No newline at end of file +