From 7f8688e7755267db7457330d08e7e5dcd3ff4d09 Mon Sep 17 00:00:00 2001 From: Dan Shields <35669742+NukeManDan@users.noreply.github.com> Date: Wed, 21 Sep 2022 15:05:03 -0600 Subject: [PATCH] Tests for `pallet-tx-pause` (#12259) * mock added * tests added * dummy benchmarks started --- Cargo.lock | 5 + bin/node/cli/src/chain_spec.rs | 2 + bin/node/runtime/src/lib.rs | 9 ++ bin/node/testing/src/genesis.rs | 2 + frame/tx-pause/Cargo.toml | 13 ++- frame/tx-pause/src/benchmarking.rs | 40 ++++++++ frame/tx-pause/src/lib.rs | 20 ++-- frame/tx-pause/src/mock.rs | 159 ++++++++++++++++++++++++++++- frame/tx-pause/src/tests.rs | 149 +++++++++++++++++++++++++++ frame/tx-pause/src/weights.rs | 93 +++++++++++++++++ 10 files changed, 483 insertions(+), 9 deletions(-) create mode 100644 frame/tx-pause/src/weights.rs diff --git a/Cargo.lock b/Cargo.lock index 4f5eae9c31b12..1e624fe65b33d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6509,10 +6509,15 @@ dependencies = [ name = "pallet-tx-pause" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "parity-scale-codec", "scale-info", + "sp-core", + "sp-io", + "sp-runtime", "sp-std", ] diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index b549f5f6ed0c7..61e051120ffbe 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -373,6 +373,8 @@ pub fn testnet_genesis( min_join_bond: 1 * DOLLARS, ..Default::default() }, + safe_mode: Default::default(), + tx_pause: Default::default(), } } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index edf4873a50d0c..ff377c6e6ebc2 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -218,6 +218,14 @@ impl pallet_tx_pause::Config for Runtime { type UnpauseOrigin = EnsureRoot; type MaxNameLen = ConstU32<256>; type PauseTooLongNames = ConstBool; + type WeightInfo = pallet_tx_pause::weights::SubstrateWeight; +} + +parameter_types! { + // signed config + pub const EnableStakeAmount: Balance = 1 * DOLLARS; //TODO This needs to be something sensible for the implications of enablement! + pub const ExtendStakeAmount: Balance = 1 * DOLLARS; //TODO This needs to be something sensible for the implications of enablement! + pub BlockHeight: BlockNumber = System::block_number(); // TODO ensure this plus config below is correct } parameter_types! { @@ -1807,6 +1815,7 @@ mod benches { [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] + [pallet_tx_pause, TxPause] [pallet_safe_mode, SafeMode] ); } diff --git a/bin/node/testing/src/genesis.rs b/bin/node/testing/src/genesis.rs index 1eb7318db52da..2c857bcba01fc 100644 --- a/bin/node/testing/src/genesis.rs +++ b/bin/node/testing/src/genesis.rs @@ -95,5 +95,7 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec) -> Gen alliance: Default::default(), alliance_motion: Default::default(), nomination_pools: Default::default(), + safe_mode: Default::default(), + tx_pause: Default::default(), } } diff --git a/frame/tx-pause/Cargo.toml b/frame/tx-pause/Cargo.toml index 6b001ae9c386f..da32e18ef387c 100644 --- a/frame/tx-pause/Cargo.toml +++ b/frame/tx-pause/Cargo.toml @@ -14,23 +14,34 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } +scale-info = { version = "2.0.1", default-features = false, features = ["derive"] } +sp-runtime = { version = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } + [dev-dependencies] +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } + [features] default = ["std"] std = [ "codec/std", "scale-info/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", + "sp-runtime/std", "sp-std/std", ] runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/tx-pause/src/benchmarking.rs b/frame/tx-pause/src/benchmarking.rs index 08f16881d7231..d605e8fef826a 100644 --- a/frame/tx-pause/src/benchmarking.rs +++ b/frame/tx-pause/src/benchmarking.rs @@ -16,3 +16,43 @@ // limitations under the License. #![cfg(feature = "runtime-benchmarks")] + +use super::{Pallet as TxPause, *}; + +use frame_benchmarking::benchmarks; +use frame_support::traits::UnfilteredDispatchable; + +benchmarks! { + pause_call { + let pallet: PalletNameOf = b"SomePalletName".to_vec().try_into().unwrap(); + let function: FunctionNameOf = b"some_fn_name".to_vec().try_into().unwrap(); + let origin = T::PauseOrigin::successful_origin(); + let call = Call::::pause_call { pallet: pallet.clone(), function: function.clone() }; + + }: { call.dispatch_bypass_filter(origin)?} + verify { + assert![TxPause::::paused_calls((pallet.clone(),function.clone())).is_some()] + } + + unpause_call { + let pallet: PalletNameOf = b"SomePalletName".to_vec().try_into().unwrap(); + let function: FunctionNameOf = b"some_fn_name".to_vec().try_into().unwrap(); + let pause_origin = T::PauseOrigin::successful_origin(); + + // Set + TxPause::::pause_call( + pause_origin, + pallet.clone(), + function.clone(), + )?; + + let unpause_origin = T::UnpauseOrigin::successful_origin(); + let call = Call::::unpause_call { pallet: pallet.clone(), function: function.clone() }; + + }: { call.dispatch_bypass_filter(unpause_origin)?} + verify { + assert![TxPause::::paused_calls((pallet.clone(),function.clone())).is_none()] + } + + impl_benchmark_test_suite!(TxPause, crate::mock::new_test_ext(), crate::mock::Test); +} diff --git a/frame/tx-pause/src/lib.rs b/frame/tx-pause/src/lib.rs index e4c90028dc860..cfa31d3d8d6cb 100644 --- a/frame/tx-pause/src/lib.rs +++ b/frame/tx-pause/src/lib.rs @@ -17,6 +17,14 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + use frame_support::{ pallet_prelude::*, traits::{CallMetadata, Contains, GetCallMetadata}, @@ -24,11 +32,8 @@ use frame_support::{ use frame_system::pallet_prelude::*; use sp_std::{convert::TryInto, prelude::*}; -mod benchmarking; -mod mock; -mod tests; - pub use pallet::*; +pub use weights::*; pub type PalletNameOf = BoundedVec::MaxNameLen>; pub type CallNameOf = BoundedVec::MaxNameLen>; @@ -74,7 +79,7 @@ pub mod pallet { type PauseTooLongNames: Get; // Weight information for extrinsics in this pallet. - //type WeightInfo: WeightInfo; + type WeightInfo: WeightInfo; } #[pallet::error] @@ -100,6 +105,7 @@ pub mod pallet { /// The set of calls that are explicitly paused. #[pallet::storage] + #[pallet::getter(fn paused_calls)] pub type PausedCalls = StorageMap<_, Blake2_128Concat, (PalletNameOf, CallNameOf), (), OptionQuery>; @@ -135,7 +141,7 @@ pub mod pallet { /// /// Can only be called by [`Config::PauseOrigin`]. /// Emits an [`Event::CallPaused`] event on success. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::pause_call())] pub fn pause_call( origin: OriginFor, pallet: PalletNameOf, @@ -154,7 +160,7 @@ pub mod pallet { /// /// Can only be called by [`Config::UnpauseOrigin`]. /// Emits an [`Event::CallUnpaused`] event on success. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::unpause_call())] pub fn unpause_call( origin: OriginFor, pallet: PalletNameOf, diff --git a/frame/tx-pause/src/mock.rs b/frame/tx-pause/src/mock.rs index 95c25725a4f6d..a3a128893f348 100644 --- a/frame/tx-pause/src/mock.rs +++ b/frame/tx-pause/src/mock.rs @@ -15,4 +15,161 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(any(test, feature = "runtime-benchmarks"))] +//! Test utilities for transaction pause (tx pause) pallet. + +use super::*; +use crate as pallet_tx_pause; + +use frame_support::{ + parameter_types, + traits::{Everything, InsideBoth, SortedMembers}, +}; +use frame_system::EnsureSignedBy; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, +}; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = InsideBoth; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +parameter_types! { + pub const ExistentialDeposit: u64 = 1; + pub const MaxLocks: u32 = 10; +} +impl pallet_balances::Config for Test { + type Balance = u64; + type DustRemoval = (); + type Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +parameter_types! { + pub const PauseOrigin: u64 = 1; + pub const UnpauseOrigin: u64 = 2; + pub const MaxNameLen: u32 = 50; + pub const PauseTooLongNames: bool = false; +} + +pub struct MockUnpausablePallets; + +impl Contains> for MockUnpausablePallets { + fn contains(pallet: &PalletNameOf) -> bool { + let unpausables: Vec> = + vec![b"UnpausablePallet".to_vec().try_into().unwrap()]; + + unpausables.iter().any(|i| i == pallet) + } +} +// Required impl to use some ::get() in tests +impl SortedMembers for PauseOrigin { + fn sorted_members() -> Vec { + vec![Self::get()] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} +impl SortedMembers for UnpauseOrigin { + fn sorted_members() -> Vec { + vec![Self::get()] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +impl Config for Test { + type Event = Event; + type PauseOrigin = EnsureSignedBy; + type UnpauseOrigin = EnsureSignedBy; + type UnpausablePallets = MockUnpausablePallets; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + TxPause: pallet_tx_pause, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + // The 0 account is NOT a special origin. The rest may be: + balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)], + } + .assimilate_storage(&mut t) + .unwrap(); + + GenesisBuild::::assimilate_storage( + &pallet_tx_pause::GenesisConfig { paused: vec![], _phantom: Default::default() }, + &mut t, + ) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +pub fn next_block() { + TxPause::on_finalize(System::block_number()); + Balances::on_finalize(System::block_number()); + System::on_finalize(System::block_number()); + System::set_block_number(System::block_number() + 1); + System::on_initialize(System::block_number()); + Balances::on_initialize(System::block_number()); + TxPause::on_initialize(System::block_number()); +} + +pub fn run_to(n: u64) { + while System::block_number() < n { + next_block(); + } +} diff --git a/frame/tx-pause/src/tests.rs b/frame/tx-pause/src/tests.rs index ee33686e4a39f..30d9ec7600bfc 100644 --- a/frame/tx-pause/src/tests.rs +++ b/frame/tx-pause/src/tests.rs @@ -16,3 +16,152 @@ // limitations under the License. #![cfg(test)] + +use super::*; +use crate::mock::{Call, *}; + +use frame_support::{assert_err, assert_noop, assert_ok, dispatch::Dispatchable}; + +// GENERAL SUCCESS/POSITIVE TESTS --------------------- + +#[test] +fn can_set_arbitrary_pause() { + new_test_ext().execute_with(|| { + assert_ok!(TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"SomePallet"), + name(b"some_function"), + )); + }); +} + +#[test] +fn can_pause_system_call() { + new_test_ext().execute_with(|| { + let call = Call::System(frame_system::Call::remark { remark: vec![] }); + + assert_ok!(TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"System"), + name(b"remark"), + )); + + assert_err!( + call.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + }); +} + +#[test] +fn can_pause_specific_call() { + new_test_ext().execute_with(|| { + let call_paused = Call::Balances(pallet_balances::Call::transfer { dest: 1, value: 1 }); + let call_not_paused = + Call::Balances(pallet_balances::Call::transfer_keep_alive { dest: 1, value: 1 }); + + assert_ok!(TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"Balances"), + name(b"transfer"), + )); + + assert_err!( + call_paused.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + assert_ok!(call_not_paused.clone().dispatch(Origin::signed(0))); + }); +} + +#[test] +fn can_unpause_specific_call() { + new_test_ext().execute_with(|| { + let call_paused = Call::Balances(pallet_balances::Call::transfer { dest: 1, value: 1 }); + + assert_ok!(TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"Balances"), + name(b"transfer"), + )); + assert_err!( + call_paused.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + + assert_ok!(TxPause::unpause_call( + Origin::signed(mock::UnpauseOrigin::get()), + name(b"Balances"), + name(b"transfer"), + )); + assert_ok!(call_paused.clone().dispatch(Origin::signed(0))); + }); +} + +// GENERAL FAIL/NEGATIVE TESTS --------------------- + +#[test] +fn fails_to_pause_self() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"TxPause"), + name(b"should_not_matter"), + ), + Error::::IsUnpausable + ); + }); +} + +#[test] +fn fails_to_pause_unpausable_pallet() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"UnpausablePallet"), + name(b"should_not_matter"), + ), + Error::::IsUnpausable + ); + }); +} + +#[test] +fn fails_to_pause_already_paused_pallet() { + new_test_ext().execute_with(|| { + assert_ok!(TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"SomePallet"), + name(b"some_function"), + )); + + assert_noop!( + TxPause::pause_call( + Origin::signed(mock::PauseOrigin::get()), + name(b"SomePallet"), + name(b"some_function"), + ), + Error::::IsPaused + ); + }); +} + +#[test] +fn fails_to_unpause_not_paused_pallet() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::unpause_call( + Origin::signed(mock::UnpauseOrigin::get()), + name(b"SomePallet"), + name(b"some_function"), + ), + Error::::IsUnpaused + ); + }); +} + +fn name(bytes: &[u8]) -> BoundedVec { + bytes.to_vec().try_into().unwrap() +} diff --git a/frame/tx-pause/src/weights.rs b/frame/tx-pause/src/weights.rs new file mode 100644 index 0000000000000..10a296924b660 --- /dev/null +++ b/frame/tx-pause/src/weights.rs @@ -0,0 +1,93 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 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. + +//! Autogenerated weights for pallet_tx_pause +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-19, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `pop-os`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --steps +// 1 +// --repeat +// 1 +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --heap-pages +// 4096 +// --pallet +// pallet_tx_pause +// --chain +// dev +// --output +// frame/tx-pause/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_tx_pause. +pub trait WeightInfo { + fn pause_call() -> Weight; + fn unpause_call() -> Weight; +} + +/// Weights for pallet_tx_pause using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: TxPause PausedCalls (r:1 w:1) + fn pause_call() -> Weight { + Weight::from_ref_time(55_270_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: TxPause PausedCalls (r:1 w:1) + fn unpause_call() -> Weight { + Weight::from_ref_time(55_290_000 as u64) + .saturating_add(T::DbWeight::get().reads(1 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: TxPause PausedCalls (r:1 w:1) + fn pause_call() -> Weight { + Weight::from_ref_time(55_270_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: TxPause PausedCalls (r:1 w:1) + fn unpause_call() -> Weight { + Weight::from_ref_time(55_290_000 as u64) + .saturating_add(RocksDbWeight::get().reads(1 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +}