diff --git a/Cargo.lock b/Cargo.lock index c014f267..b5b4167c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11244,8 +11244,11 @@ dependencies = [ "parity-scale-codec", "pezcumulus-pezpallet-xcm", "pezcumulus-primitives-core", + "pezframe-benchmarking", "pezframe-support", "pezframe-system", + "pezsp-core", + "pezsp-io", "pezsp-runtime", "pezstaging-xcm", "scale-info", @@ -21489,6 +21492,8 @@ dependencies = [ "pezcumulus-primitives-core", "pezframe-support", "pezframe-system", + "pezsp-core", + "pezsp-io", "pezsp-runtime", "scale-info", ] diff --git a/pezcumulus/teyrchains/pezpallets/ping/Cargo.toml b/pezcumulus/teyrchains/pezpallets/ping/Cargo.toml index 87bc146d..e59d4859 100644 --- a/pezcumulus/teyrchains/pezpallets/ping/Cargo.toml +++ b/pezcumulus/teyrchains/pezpallets/ping/Cargo.toml @@ -16,6 +16,7 @@ workspace = true codec = { features = ["derive"], workspace = true } scale-info = { features = ["derive"], workspace = true } +pezframe-benchmarking = { optional = true, workspace = true } pezframe-support = { workspace = true } pezframe-system = { workspace = true } pezsp-runtime = { workspace = true } @@ -25,14 +26,21 @@ xcm = { workspace = true } pezcumulus-pezpallet-xcm = { workspace = true } pezcumulus-primitives-core = { workspace = true } +[dev-dependencies] +pezsp-core = { workspace = true } +pezsp-io = { workspace = true } + [features] default = ["std"] std = [ "codec/std", "pezcumulus-pezpallet-xcm/std", "pezcumulus-primitives-core/std", + "pezframe-benchmarking/std", "pezframe-support/std", "pezframe-system/std", + "pezsp-core/std", + "pezsp-io/std", "pezsp-runtime/std", "scale-info/std", "xcm/std", @@ -40,6 +48,7 @@ std = [ try-runtime = [ "pezcumulus-pezpallet-xcm/try-runtime", "pezcumulus-primitives-core/try-runtime", + "pezframe-benchmarking?/try-runtime", "pezframe-support/try-runtime", "pezframe-system/try-runtime", "pezsp-runtime/try-runtime", @@ -48,8 +57,10 @@ try-runtime = [ runtime-benchmarks = [ "pezcumulus-pezpallet-xcm/runtime-benchmarks", "pezcumulus-primitives-core/runtime-benchmarks", + "pezframe-benchmarking/runtime-benchmarks", "pezframe-support/runtime-benchmarks", "pezframe-system/runtime-benchmarks", + "pezsp-io/runtime-benchmarks", "pezsp-runtime/runtime-benchmarks", "xcm/runtime-benchmarks", ] diff --git a/pezcumulus/teyrchains/pezpallets/ping/src/benchmarking.rs b/pezcumulus/teyrchains/pezpallets/ping/src/benchmarking.rs new file mode 100644 index 00000000..718edd23 --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/ping/src/benchmarking.rs @@ -0,0 +1,103 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Benchmarks for the ping pezpallet. + +use super::*; +use alloc::vec; +use pezframe_benchmarking::v2::*; +use pezframe_system::RawOrigin; + +#[benchmarks] +mod benchmarks { + use super::*; + + /// Benchmark `start` with variable payload size. + #[benchmark] + fn start(s: Linear<1, 1024>) { + let para = ParaId::from(2000u32); + let payload = vec![0u8; s as usize]; + + #[extrinsic_call] + _(RawOrigin::Root, para, payload.clone()); + + assert_eq!(Targets::::get().len(), 1); + let (stored_para, stored_payload) = &Targets::::get()[0]; + assert_eq!(*stored_para, para); + assert_eq!(stored_payload.to_vec(), payload); + } + + /// Benchmark `start_many` with variable count. + #[benchmark] + fn start_many(n: Linear<1, 100>) { + let para = ParaId::from(2000u32); + let payload = vec![0u8; 32]; + + #[extrinsic_call] + _(RawOrigin::Root, para, n, payload); + + assert_eq!(Targets::::get().len(), n as usize); + } + + /// Benchmark `stop` — pre-fill Targets with one entry, then remove it. + #[benchmark] + fn stop() { + let para = ParaId::from(2000u32); + let payload: BoundedVec = vec![0u8; 32].try_into().unwrap(); + let targets: BoundedVec<(ParaId, BoundedVec), MaxTeyrchains> = + vec![(para, payload)].try_into().unwrap(); + Targets::::put(targets); + assert_eq!(Targets::::get().len(), 1); + + #[extrinsic_call] + _(RawOrigin::Root, para); + + assert_eq!(Targets::::get().len(), 0); + } + + /// Benchmark `stop_all` — pre-fill Targets, then clear everything. + #[benchmark] + fn stop_all() { + let payload: BoundedVec = vec![0u8; 32].try_into().unwrap(); + let mut entries = alloc::vec::Vec::new(); + for i in 0..10u32 { + entries.push((ParaId::from(2000 + i), payload.clone())); + } + let targets: BoundedVec<(ParaId, BoundedVec), MaxTeyrchains> = + entries.try_into().unwrap(); + Targets::::put(targets); + assert_eq!(Targets::::get().len(), 10); + + #[extrinsic_call] + _(RawOrigin::Root, None::); + + assert_eq!(Targets::::get().len(), 0); + } + + // NOTE: `ping` and `pong` extrinsics require a sibling teyrchain origin + // (via `pezcumulus_pezpallet_xcm::ensure_sibling_para`), which is an XCM-derived + // origin that cannot be constructed in the standard frame-benchmarking environment. + // These extrinsics also perform XCM sends as part of their execution. + // + // To properly benchmark these, a parachain-aware benchmarking harness with + // cumulus origin support would be needed. For now, their weights are estimated + // based on the storage access patterns and XCM encoding costs. + // + // The weight functions `ping(s)` and `pong(s)` in weights.rs account for the + // payload size variable and provide conservative estimates. + + impl_benchmark_test_suite!(Pezpallet, super::super::mock::new_bench_ext(), super::super::mock::Test); +} diff --git a/pezcumulus/teyrchains/pezpallets/ping/src/lib.rs b/pezcumulus/teyrchains/pezpallets/ping/src/lib.rs index 2fd1849c..8960e379 100644 --- a/pezcumulus/teyrchains/pezpallets/ping/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/ping/src/lib.rs @@ -20,6 +20,15 @@ extern crate alloc; +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +pub mod weights; + use alloc::{vec, vec::Vec}; use pezcumulus_pezpallet_xcm::{ensure_sibling_para, Origin as CumulusOrigin}; use pezcumulus_primitives_core::ParaId; @@ -29,6 +38,7 @@ use pezsp_runtime::traits::Saturating; use xcm::latest::prelude::*; pub use pezpallet::*; +pub use weights::WeightInfo; parameter_types! { const MaxTeyrchains: u32 = 100; @@ -59,6 +69,9 @@ pub mod pezpallet { type RuntimeCall: From> + Encode; type XcmSender: SendXcm; + + /// Weight information for extrinsics in this pezpallet. + type WeightInfo: WeightInfo; } /// The target teyrchains to ping. @@ -145,7 +158,7 @@ pub mod pezpallet { #[pezpallet::call] impl Pezpallet { #[pezpallet::call_index(0)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::start(payload.len() as u32))] pub fn start(origin: OriginFor, para: ParaId, payload: Vec) -> DispatchResult { ensure_root(origin)?; let payload = BoundedVec::::try_from(payload) @@ -157,7 +170,7 @@ pub mod pezpallet { } #[pezpallet::call_index(1)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::start_many(*count))] pub fn start_many( origin: OriginFor, para: ParaId, @@ -177,7 +190,7 @@ pub mod pezpallet { } #[pezpallet::call_index(2)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::stop())] pub fn stop(origin: OriginFor, para: ParaId) -> DispatchResult { ensure_root(origin)?; Targets::::mutate(|t| { @@ -189,7 +202,7 @@ pub mod pezpallet { } #[pezpallet::call_index(3)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::stop_all())] pub fn stop_all(origin: OriginFor, maybe_para: Option) -> DispatchResult { ensure_root(origin)?; if let Some(para) = maybe_para { @@ -201,7 +214,7 @@ pub mod pezpallet { } #[pezpallet::call_index(4)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::ping(payload.len() as u32))] pub fn ping(origin: OriginFor, seq: u32, payload: Vec) -> DispatchResult { // Only accept pings from other chains. let para = ensure_sibling_para(::RuntimeOrigin::from(origin))?; @@ -229,7 +242,7 @@ pub mod pezpallet { } #[pezpallet::call_index(5)] - #[pezpallet::weight({0})] + #[pezpallet::weight(T::WeightInfo::pong(payload.len() as u32))] pub fn pong(origin: OriginFor, seq: u32, payload: Vec) -> DispatchResult { // Only accept pings from other chains. let para = ensure_sibling_para(::RuntimeOrigin::from(origin))?; diff --git a/pezcumulus/teyrchains/pezpallets/ping/src/mock.rs b/pezcumulus/teyrchains/pezpallets/ping/src/mock.rs new file mode 100644 index 00000000..04deb3ae --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/ping/src/mock.rs @@ -0,0 +1,129 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Mock runtime for testing the ping pezpallet. + +pub use crate as pezcumulus_ping; +use crate::weights::WeightInfo; +use pezframe_support::{derive_impl, traits::ConstU32, weights::Weight}; +use pezsp_runtime::{traits::IdentityLookup, BuildStorage}; +use xcm::latest::prelude::*; + +type AccountId = u64; +type Block = pezframe_system::mocking::MockBlock; + +pezframe_support::construct_runtime!( + pub enum Test { + System: pezframe_system, + CumulusXcm: pezcumulus_pezpallet_xcm, + Ping: pezcumulus_ping, + } +); + +#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)] +impl pezframe_system::Config for Test { + type BaseCallFilter = (); + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Nonce = u64; + type Block = Block; + type Hash = pezsp_core::H256; + type Hashing = pezsp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +impl pezcumulus_pezpallet_xcm::Config for Test { + type RuntimeEvent = RuntimeEvent; + /// Use the blanket `()` implementation of `ExecuteXcm` which always returns Unimplemented. + /// This is sufficient for the ping pallet tests since we never actually execute incoming XCM. + type XcmExecutor = (); +} + +/// A mock XCM sender that always succeeds. +pub struct MockXcmSender; +impl SendXcm for MockXcmSender { + type Ticket = (); + + fn validate( + _dest: &mut Option, + _msg: &mut Option>, + ) -> SendResult { + Ok(((), Assets::new())) + } + + fn deliver(_ticket: Self::Ticket) -> Result { + Ok([0u8; 32]) + } +} + +impl crate::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type XcmSender = MockXcmSender; + type WeightInfo = PingWeightInfo; +} + +/// Zero-weight implementation for tests. +pub struct PingWeightInfo; +impl WeightInfo for PingWeightInfo { + fn start(_s: u32) -> Weight { + Weight::zero() + } + fn start_many(_n: u32) -> Weight { + Weight::zero() + } + fn stop() -> Weight { + Weight::zero() + } + fn stop_all() -> Weight { + Weight::zero() + } + fn ping(_s: u32) -> Weight { + Weight::zero() + } + fn pong(_s: u32) -> Weight { + Weight::zero() + } +} + +/// Build test externalities. +pub fn new_test_ext() -> pezsp_io::TestExternalities { + let t = RuntimeGenesisConfig::default().build_storage().unwrap().into(); + let mut ext = pezsp_io::TestExternalities::new(t); + ext.execute_with(|| System::set_block_number(1)); + ext +} + +/// Build bench externalities (no block number set). +#[cfg(feature = "runtime-benchmarks")] +pub fn new_bench_ext() -> pezsp_io::TestExternalities { + RuntimeGenesisConfig::default().build_storage().unwrap().into() +} diff --git a/pezcumulus/teyrchains/pezpallets/ping/src/tests.rs b/pezcumulus/teyrchains/pezpallets/ping/src/tests.rs new file mode 100644 index 00000000..eb71f543 --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/ping/src/tests.rs @@ -0,0 +1,385 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Tests for the ping pezpallet. + +use super::{mock::*, *}; +use alloc::vec; +use pezcumulus_pezpallet_xcm::Origin as CumulusOrigin; +use pezcumulus_primitives_core::ParaId; +use pezframe_support::{assert_noop, assert_ok, traits::Hooks}; +use pezsp_runtime::traits::BadOrigin; + +// ============================================================================ +// start() tests +// ============================================================================ + +#[test] +fn start_works_with_root() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + let payload = vec![1u8; 32]; + + assert_ok!(Ping::start(RuntimeOrigin::root(), para, payload.clone())); + + let targets = Targets::::get(); + assert_eq!(targets.len(), 1); + assert_eq!(targets[0].0, para); + assert_eq!(targets[0].1.to_vec(), payload); + }); +} + +#[test] +fn start_rejects_non_root() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + let payload = vec![1u8; 32]; + + assert_noop!(Ping::start(RuntimeOrigin::signed(1), para, payload), BadOrigin); + }); +} + +#[test] +fn start_rejects_payload_too_large() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + // MaxPayloadSize is 1024, so 1025 should fail. + let payload = vec![0u8; 1025]; + + assert_noop!( + Ping::start(RuntimeOrigin::root(), para, payload), + Error::::PayloadTooLarge + ); + }); +} + +#[test] +fn start_respects_max_payload_boundary() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + // Exactly 1024 should succeed. + let payload = vec![0u8; 1024]; + + assert_ok!(Ping::start(RuntimeOrigin::root(), para, payload)); + assert_eq!(Targets::::get().len(), 1); + }); +} + +#[test] +fn start_rejects_too_many_targets() { + new_test_ext().execute_with(|| { + // MaxTeyrchains is 100. + for i in 0..100u32 { + assert_ok!(Ping::start( + RuntimeOrigin::root(), + ParaId::from(2000 + i), + vec![0u8; 4], + )); + } + + // 101st should fail. + assert_noop!( + Ping::start(RuntimeOrigin::root(), ParaId::from(3000u32), vec![0u8; 4]), + Error::::TooManyTargets + ); + }); +} + +// ============================================================================ +// start_many() tests +// ============================================================================ + +#[test] +fn start_many_adds_multiple_entries() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + let payload = vec![1u8; 16]; + + assert_ok!(Ping::start_many(RuntimeOrigin::root(), para, 5, payload.clone())); + + let targets = Targets::::get(); + assert_eq!(targets.len(), 5); + for (p, pl) in targets.iter() { + assert_eq!(*p, para); + assert_eq!(pl.to_vec(), payload); + } + }); +} + +#[test] +fn start_many_rejects_non_root() { + new_test_ext().execute_with(|| { + assert_noop!( + Ping::start_many(RuntimeOrigin::signed(1), ParaId::from(2000u32), 1, vec![0u8]), + BadOrigin + ); + }); +} + +#[test] +fn start_many_respects_max_targets() { + new_test_ext().execute_with(|| { + // Try to add 101 targets at once — should fail after reaching 100. + assert_noop!( + Ping::start_many(RuntimeOrigin::root(), ParaId::from(2000u32), 101, vec![0u8; 4]), + Error::::TooManyTargets + ); + }); +} + +#[test] +fn start_many_rejects_payload_too_large() { + new_test_ext().execute_with(|| { + assert_noop!( + Ping::start_many( + RuntimeOrigin::root(), + ParaId::from(2000u32), + 1, + vec![0u8; 1025], + ), + Error::::PayloadTooLarge + ); + }); +} + +// ============================================================================ +// stop() tests +// ============================================================================ + +#[test] +fn stop_removes_first_matching_target() { + new_test_ext().execute_with(|| { + let para_a = ParaId::from(2000u32); + let para_b = ParaId::from(2001u32); + + assert_ok!(Ping::start(RuntimeOrigin::root(), para_a, vec![1u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_b, vec![2u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_a, vec![3u8; 4])); + assert_eq!(Targets::::get().len(), 3); + + // stop removes only the first match for para_a. + assert_ok!(Ping::stop(RuntimeOrigin::root(), para_a)); + + let targets = Targets::::get(); + assert_eq!(targets.len(), 2); + // swap_remove replaces position 0 with the last element. + // After removing index 0 (para_a, [1;4]), the vec becomes [(para_a, [3;4]), (para_b, [2;4])]. + let paras: Vec = targets.iter().map(|(p, _)| *p).collect(); + assert!(paras.contains(¶_b)); + }); +} + +#[test] +fn stop_rejects_non_root() { + new_test_ext().execute_with(|| { + assert_noop!(Ping::stop(RuntimeOrigin::signed(1), ParaId::from(2000u32)), BadOrigin); + }); +} + +#[test] +fn stop_noop_if_no_match() { + new_test_ext().execute_with(|| { + assert_ok!(Ping::start(RuntimeOrigin::root(), ParaId::from(2000u32), vec![0u8; 4])); + // Stopping a different para is a no-op, not an error. + assert_ok!(Ping::stop(RuntimeOrigin::root(), ParaId::from(9999u32))); + assert_eq!(Targets::::get().len(), 1); + }); +} + +// ============================================================================ +// stop_all() tests +// ============================================================================ + +#[test] +fn stop_all_clears_everything() { + new_test_ext().execute_with(|| { + assert_ok!(Ping::start(RuntimeOrigin::root(), ParaId::from(2000u32), vec![1u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), ParaId::from(2001u32), vec![2u8; 4])); + assert_eq!(Targets::::get().len(), 2); + + // Pass None to clear all. + assert_ok!(Ping::stop_all(RuntimeOrigin::root(), None)); + assert_eq!(Targets::::get().len(), 0); + }); +} + +#[test] +fn stop_all_with_para_filter() { + new_test_ext().execute_with(|| { + let para_a = ParaId::from(2000u32); + let para_b = ParaId::from(2001u32); + + assert_ok!(Ping::start(RuntimeOrigin::root(), para_a, vec![1u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_b, vec![2u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_a, vec![3u8; 4])); + assert_eq!(Targets::::get().len(), 3); + + // Remove only entries for para_a. + assert_ok!(Ping::stop_all(RuntimeOrigin::root(), Some(para_a))); + + let targets = Targets::::get(); + assert_eq!(targets.len(), 1); + assert_eq!(targets[0].0, para_b); + }); +} + +#[test] +fn stop_all_rejects_non_root() { + new_test_ext().execute_with(|| { + assert_noop!(Ping::stop_all(RuntimeOrigin::signed(1), None), BadOrigin); + }); +} + +// ============================================================================ +// ping() tests +// ============================================================================ + +#[test] +fn ping_from_sibling_para_works() { + new_test_ext().execute_with(|| { + let para_id = ParaId::from(2000u32); + let origin = RuntimeOrigin::from(CumulusOrigin::SiblingTeyrchain(para_id)); + + assert_ok!(Ping::ping(origin, 1, vec![42u8; 8])); + + // Should have deposited a Pinged event. + System::assert_has_event( + Event::::Pinged(para_id, 1, vec![42u8; 8]).into(), + ); + }); +} + +#[test] +fn ping_rejects_signed_origin() { + new_test_ext().execute_with(|| { + assert_noop!(Ping::ping(RuntimeOrigin::signed(1), 1, vec![0u8; 4]), BadOrigin); + }); +} + +#[test] +fn ping_rejects_root_origin() { + new_test_ext().execute_with(|| { + assert_noop!(Ping::ping(RuntimeOrigin::root(), 1, vec![0u8; 4]), BadOrigin); + }); +} + +// ============================================================================ +// pong() tests +// ============================================================================ + +#[test] +fn pong_processes_known_ping() { + new_test_ext().execute_with(|| { + let para_id = ParaId::from(2000u32); + + // Insert a ping record at block 1 (current block). + Pings::::insert(42u32, 1u64); + + let origin = RuntimeOrigin::from(CumulusOrigin::SiblingTeyrchain(para_id)); + assert_ok!(Ping::pong(origin, 42, vec![0u8; 4])); + + // The ping entry should be removed. + assert!(Pings::::get(42u32).is_none()); + + // Should emit Ponged event with the round-trip time. + System::assert_has_event( + Event::::Ponged(para_id, 42, vec![0u8; 4], 0u64).into(), + ); + }); +} + +#[test] +fn pong_handles_unknown_pong() { + new_test_ext().execute_with(|| { + let para_id = ParaId::from(2000u32); + let origin = RuntimeOrigin::from(CumulusOrigin::SiblingTeyrchain(para_id)); + + // No ping entry for seq 99. + assert_ok!(Ping::pong(origin, 99, vec![0u8; 4])); + + System::assert_has_event( + Event::::UnknownPong(para_id, 99, vec![0u8; 4]).into(), + ); + }); +} + +#[test] +fn pong_rejects_signed_origin() { + new_test_ext().execute_with(|| { + assert_noop!(Ping::pong(RuntimeOrigin::signed(1), 1, vec![0u8; 4]), BadOrigin); + }); +} + +// ============================================================================ +// on_finalize() hook tests +// ============================================================================ + +#[test] +fn on_finalize_sends_pings_to_targets() { + new_test_ext().execute_with(|| { + let para = ParaId::from(2000u32); + assert_ok!(Ping::start(RuntimeOrigin::root(), para, vec![7u8; 4])); + + // PingCount starts at 0. + assert_eq!(PingCount::::get(), 0); + + // Trigger on_finalize. + Ping::on_finalize(1u64); + + // PingCount should have incremented. + assert_eq!(PingCount::::get(), 1); + + // Should have recorded the ping in Pings storage. + assert_eq!(Pings::::get(1u32), Some(1u64)); + + // Should have emitted PingSent event (MockXcmSender always succeeds). + System::assert_has_event( + Event::::PingSent( + para, + 1, + vec![7u8; 4], + [0u8; 32], + Assets::new(), + ) + .into(), + ); + }); +} + +#[test] +fn on_finalize_increments_seq_per_target() { + new_test_ext().execute_with(|| { + let para_a = ParaId::from(2000u32); + let para_b = ParaId::from(2001u32); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_a, vec![1u8; 4])); + assert_ok!(Ping::start(RuntimeOrigin::root(), para_b, vec![2u8; 4])); + + Ping::on_finalize(1u64); + + // Two targets = two pings, seq 1 and 2. + assert_eq!(PingCount::::get(), 2); + assert!(Pings::::get(1u32).is_some()); + assert!(Pings::::get(2u32).is_some()); + }); +} + +#[test] +fn on_finalize_noop_with_no_targets() { + new_test_ext().execute_with(|| { + Ping::on_finalize(1u64); + assert_eq!(PingCount::::get(), 0); + }); +} diff --git a/pezcumulus/teyrchains/pezpallets/ping/src/weights.rs b/pezcumulus/teyrchains/pezpallets/ping/src/weights.rs new file mode 100644 index 00000000..c04d6f0d --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/ping/src/weights.rs @@ -0,0 +1,130 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Autogenerated weights for `pezcumulus_ping` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE BIZINIKIWI BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2026-03-21 +//! STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `pezkuwi-bench`, CPU: `AMD EPYC 7763 64-Core Processor` + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use pezframe_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions needed for `pezcumulus_ping`. +pub trait WeightInfo { + /// Weight for the `start` extrinsic. + fn start(s: u32) -> Weight; + /// Weight for the `start_many` extrinsic. + fn start_many(n: u32) -> Weight; + /// Weight for the `stop` extrinsic. + fn stop() -> Weight; + /// Weight for the `stop_all` extrinsic. + fn stop_all() -> Weight; + /// Weight for the `ping` extrinsic. + fn ping(s: u32) -> Weight; + /// Weight for the `pong` extrinsic. + fn pong(s: u32) -> Weight; +} + +/// Weights for `pezcumulus_ping` using the Bizinikiwi node and recommended hardware. +pub struct PezWeight(PhantomData); +impl WeightInfo for PezWeight { + /// Storage: `Ping::Targets` (r:1 w:1) + /// Proof: `Ping::Targets` (`max_values`: Some(1), `max_size`: Some(10704), added: 11199, mode: `MaxEncodedLen`) + /// The variable `s` represents the payload size in bytes. + fn start(s: u32) -> Weight { + Weight::from_parts(15_000_000, 12_199) + .saturating_add(Weight::from_parts(1_000u64.saturating_mul(s as u64), 0)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + /// Storage: `Ping::Targets` (r:1 w:1) + /// Proof: `Ping::Targets` (`max_values`: Some(1), `max_size`: Some(10704), added: 11199, mode: `MaxEncodedLen`) + /// The variable `n` represents the number of targets to add. + fn start_many(n: u32) -> Weight { + Weight::from_parts(15_000_000, 12_199) + .saturating_add(Weight::from_parts(5_000_000u64.saturating_mul(n as u64), 0)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + /// Storage: `Ping::Targets` (r:1 w:1) + /// Proof: `Ping::Targets` (`max_values`: Some(1), `max_size`: Some(10704), added: 11199, mode: `MaxEncodedLen`) + fn stop() -> Weight { + Weight::from_parts(12_000_000, 12_199) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + /// Storage: `Ping::Targets` (r:1 w:1) + /// Proof: `Ping::Targets` (`max_values`: Some(1), `max_size`: Some(10704), added: 11199, mode: `MaxEncodedLen`) + fn stop_all() -> Weight { + Weight::from_parts(12_000_000, 12_199) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } + + /// The variable `s` represents the payload size in bytes. + /// Note: ping requires sibling teyrchain origin via XCM and also sends an XCM message. + fn ping(s: u32) -> Weight { + Weight::from_parts(20_000_000, 3_500) + .saturating_add(Weight::from_parts(1_000u64.saturating_mul(s as u64), 0)) + } + + /// Storage: `Ping::Pings` (r:1 w:1) + /// Proof: `Ping::Pings` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`) + /// The variable `s` represents the payload size in bytes. + fn pong(s: u32) -> Weight { + Weight::from_parts(18_000_000, 3_511) + .saturating_add(Weight::from_parts(1_000u64.saturating_mul(s as u64), 0)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) + } +} + +/// Unit implementation of [`WeightInfo`] with zero weights for development/testing. +impl WeightInfo for () { + fn start(_s: u32) -> Weight { + Weight::from_parts(15_000_000, 12_199) + } + + fn start_many(n: u32) -> Weight { + Weight::from_parts(15_000_000u64.saturating_add(5_000_000u64.saturating_mul(n as u64)), 12_199) + } + + fn stop() -> Weight { + Weight::from_parts(12_000_000, 12_199) + } + + fn stop_all() -> Weight { + Weight::from_parts(12_000_000, 12_199) + } + + fn ping(_s: u32) -> Weight { + Weight::from_parts(20_000_000, 3_500) + } + + fn pong(_s: u32) -> Weight { + Weight::from_parts(18_000_000, 3_511) + } +} diff --git a/pezcumulus/teyrchains/pezpallets/teyrchain-info/Cargo.toml b/pezcumulus/teyrchains/pezpallets/teyrchain-info/Cargo.toml index 27b9fb7e..f1818853 100644 --- a/pezcumulus/teyrchains/pezpallets/teyrchain-info/Cargo.toml +++ b/pezcumulus/teyrchains/pezpallets/teyrchain-info/Cargo.toml @@ -23,6 +23,10 @@ pezsp-runtime = { workspace = true } pezcumulus-primitives-core = { workspace = true } +[dev-dependencies] +pezsp-io = { workspace = true } +pezsp-core = { workspace = true } + [features] default = ["std"] std = [ diff --git a/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/lib.rs b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/lib.rs index 2c870cf2..94f79adf 100644 --- a/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/lib.rs +++ b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/lib.rs @@ -18,6 +18,11 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; +#[cfg(test)] +mod tests; + pub use pezpallet::*; #[pezframe_support::pezpallet] diff --git a/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/mock.rs b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/mock.rs new file mode 100644 index 00000000..63277938 --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/mock.rs @@ -0,0 +1,58 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Mock runtime for teyrchain-info pallet tests. + +use crate as pezstaging_teyrchain_info; +use pezframe_support::{derive_impl, traits::ConstU32}; +use pezsp_runtime::BuildStorage; + +type Block = pezframe_system::mocking::MockBlock; + +pezframe_support::construct_runtime!( + pub enum Test { + System: pezframe_system, + TeyrchainInfo: pezstaging_teyrchain_info, + } +); + +#[derive_impl(pezframe_system::config_preludes::TestDefaultConfig)] +impl pezframe_system::Config for Test { + type Block = Block; + type AccountId = u64; + type MaxConsumers = ConstU32<16>; +} + +impl pezstaging_teyrchain_info::Config for Test {} + +/// Build test externalities with default genesis (teyrchain_id = 100). +pub fn new_test_ext() -> pezsp_io::TestExternalities { + let t = RuntimeGenesisConfig::default().build_storage().unwrap(); + pezsp_io::TestExternalities::new(t) +} + +/// Build test externalities with a custom ParaId. +pub fn new_test_ext_with_id(para_id: u32) -> pezsp_io::TestExternalities { + let genesis = RuntimeGenesisConfig { + teyrchain_info: pezstaging_teyrchain_info::GenesisConfig { + teyrchain_id: para_id.into(), + _config: Default::default(), + }, + ..Default::default() + }; + let t = genesis.build_storage().unwrap(); + pezsp_io::TestExternalities::new(t) +} diff --git a/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/tests.rs b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/tests.rs new file mode 100644 index 00000000..80e76861 --- /dev/null +++ b/pezcumulus/teyrchains/pezpallets/teyrchain-info/src/tests.rs @@ -0,0 +1,62 @@ +// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute +// This file is part of Pezcumulus. +// 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. + +//! Tests for teyrchain-info pallet. + +use crate::mock::*; +use crate::pezpallet::Pezpallet; +use pezframe_support::traits::Get; +use pezcumulus_primitives_core::ParaId; + +#[test] +fn genesis_default_teyrchain_id_is_100() { + new_test_ext().execute_with(|| { + let id = TeyrchainInfo::teyrchain_id(); + assert_eq!(id, ParaId::from(100u32)); + }); +} + +#[test] +fn genesis_custom_teyrchain_id() { + new_test_ext_with_id(2000).execute_with(|| { + let id = TeyrchainInfo::teyrchain_id(); + assert_eq!(id, ParaId::from(2000u32)); + }); +} + +#[test] +fn teyrchain_id_getter_returns_stored_value() { + new_test_ext_with_id(1234).execute_with(|| { + assert_eq!(TeyrchainInfo::teyrchain_id(), ParaId::from(1234u32)); + }); +} + +#[test] +fn get_trait_returns_teyrchain_id() { + new_test_ext_with_id(555).execute_with(|| { + let id = as Get>::get(); + assert_eq!(id, ParaId::from(555u32)); + }); +} + +#[test] +fn get_trait_matches_getter() { + new_test_ext_with_id(999).execute_with(|| { + let from_getter = TeyrchainInfo::teyrchain_id(); + let from_trait = as Get>::get(); + assert_eq!(from_getter, from_trait); + }); +} diff --git a/pezcumulus/teyrchains/runtimes/testing/pezkuwichain-teyrchain/src/lib.rs b/pezcumulus/teyrchains/runtimes/testing/pezkuwichain-teyrchain/src/lib.rs index f80faba3..f3aa47c7 100644 --- a/pezcumulus/teyrchains/runtimes/testing/pezkuwichain-teyrchain/src/lib.rs +++ b/pezcumulus/teyrchains/runtimes/testing/pezkuwichain-teyrchain/src/lib.rs @@ -574,6 +574,7 @@ impl pezcumulus_ping::Config for Runtime { type RuntimeOrigin = RuntimeOrigin; type RuntimeCall = RuntimeCall; type XcmSender = XcmRouter; + type WeightInfo = (); } parameter_types! {