diff --git a/Cargo.lock b/Cargo.lock index 45902fed38..b3c47b4904 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3887,6 +3887,7 @@ dependencies = [ "substrate-wasm-builder", "supply", "traits", + "tx-pause", "vault-registry", "vault-registry-rpc-runtime-api", "xcm-builder", @@ -4033,6 +4034,7 @@ dependencies = [ "substrate-wasm-builder", "supply", "traits", + "tx-pause", "vault-registry", "vault-registry-rpc-runtime-api", "xcm", @@ -4442,6 +4444,7 @@ dependencies = [ "substrate-wasm-builder", "supply", "traits", + "tx-pause", "vault-registry", "vault-registry-rpc-runtime-api", "xcm", @@ -12584,6 +12587,7 @@ dependencies = [ "substrate-wasm-builder", "supply", "traits", + "tx-pause", "vault-registry", "vault-registry-rpc-runtime-api", "xcm", @@ -12690,6 +12694,7 @@ dependencies = [ "substrate-wasm-builder", "supply", "traits", + "tx-pause", "vault-registry", "vault-registry-rpc-runtime-api", "xcm", @@ -13144,6 +13149,24 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "tx-pause" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-support", + "frame-system", + "pallet-balances", + "pallet-proxy", + "pallet-utility", + "parity-scale-codec", + "scale-info", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "typenum" version = "1.16.0" diff --git a/crates/security/src/lib.rs b/crates/security/src/lib.rs index b0af617333..33937aaf5a 100644 --- a/crates/security/src/lib.rs +++ b/crates/security/src/lib.rs @@ -195,11 +195,6 @@ impl Pallet { } } - /// Checks if the Parachain has Shutdown - pub fn is_parachain_shutdown() -> bool { - Self::parachain_status() == StatusCode::Shutdown - } - /// Checks if the Parachain has a OracleOffline Error state pub fn is_parachain_error_oracle_offline() -> bool { Self::parachain_status() == StatusCode::Error && >::get().contains(&ErrorCode::OracleOffline) diff --git a/crates/security/src/tests.rs b/crates/security/src/tests.rs index ae3fabd83a..8f7599e747 100644 --- a/crates/security/src/tests.rs +++ b/crates/security/src/tests.rs @@ -18,17 +18,6 @@ macro_rules! assert_emitted { }; } -#[test] -fn test_get_and_set_status() { - run_test(|| { - let status_code = Security::parachain_status(); - assert_eq!(status_code, StatusCode::Running); - Security::set_status(StatusCode::Shutdown); - let status_code = Security::parachain_status(); - assert_eq!(status_code, StatusCode::Shutdown); - }) -} - #[test] fn test_is_ensure_parachain_running_succeeds() { run_test(|| { @@ -45,26 +34,6 @@ fn test_is_ensure_parachain_running_fails() { Security::ensure_parachain_status_running(), TestError::ParachainNotRunning ); - - Security::set_status(StatusCode::Shutdown); - assert_noop!( - Security::ensure_parachain_status_running(), - TestError::ParachainNotRunning - ); - }) -} - -#[test] -fn test_is_parachain_shutdown_succeeds() { - run_test(|| { - Security::set_status(StatusCode::Running); - assert!(!Security::is_parachain_shutdown()); - - Security::set_status(StatusCode::Error); - assert!(!Security::is_parachain_shutdown()); - - Security::set_status(StatusCode::Shutdown); - assert!(Security::is_parachain_shutdown()); }) } @@ -135,11 +104,6 @@ fn test_get_active_block_not_incremented_if_not_running() { Security::set_status(StatusCode::Error); Security::increment_active_block(); assert_eq!(Security::active_block_number(), initial_active_block); - - // not updated if there is shutdown - Security::set_status(StatusCode::Shutdown); - Security::increment_active_block(); - assert_eq!(Security::active_block_number(), initial_active_block); }) } diff --git a/crates/security/src/types.rs b/crates/security/src/types.rs index b166586c5d..88caa61803 100644 --- a/crates/security/src/types.rs +++ b/crates/security/src/types.rs @@ -10,8 +10,6 @@ pub enum StatusCode { Running = 0, /// An error has occurred. See Errors for more details. Error = 1, - /// BTC Parachain operation has been fully suspended. - Shutdown = 2, } impl Default for StatusCode { diff --git a/crates/tx-pause/Cargo.toml b/crates/tx-pause/Cargo.toml new file mode 100644 index 0000000000..a745d77fc6 --- /dev/null +++ b/crates/tx-pause/Cargo.toml @@ -0,0 +1,62 @@ +[package] +name = "tx-pause" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Apache-2.0" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME transaction pause pallet" +readme = "README.md" + +[package.metadata.docs.rs] +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.2.0", default-features = false, features = ["derive"] } + +frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +frame-support = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false, optional = true } +pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false, optional = true } +pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false, optional = true } + +[dev-dependencies] +sp-core = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +sp-io = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +pallet-utility = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } +pallet-proxy = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.31", default-features = false } + + +[features] +default = ["std"] +std = [ + "codec/std", + "scale-info/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "pallet-balances?/std", + "pallet-utility?/std", + "pallet-proxy?/std", + "sp-runtime/std", + "sp-std/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-support/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "pallet-balances/runtime-benchmarks", + "pallet-utility/runtime-benchmarks", + "pallet-proxy/runtime-benchmarks", +] +try-runtime = [ + "frame-support/try-runtime", + "frame-system/try-runtime", +] diff --git a/crates/tx-pause/src/benchmarking.rs b/crates/tx-pause/src/benchmarking.rs new file mode 100644 index 0000000000..c7c186d4b4 --- /dev/null +++ b/crates/tx-pause/src/benchmarking.rs @@ -0,0 +1,61 @@ +// 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. + +#![cfg(feature = "runtime-benchmarks")] + +use super::{Pallet as TxPause, *}; + +use frame_benchmarking::benchmarks; + +benchmarks! { + pause { + let full_name: FullNameOf = (name::(b"SomePalletName"), Some(name::(b"SomePalletName"))); + // let pallet_name: PalletNameOf = name::(b"SomePalletName"); + // let maybe_call_name: Option> = Some(name::(b"some_call_name")); + let origin = T::PauseOrigin::successful_origin(); + // let call = Call::::pause { full_name: full_name.clone() }; + // let call = Call::::pause { pallet_name: pallet_name.clone(), maybe_call_name: maybe_call_name.clone() }; + + }: _(origin, full_name.clone()) + verify { + assert!(TxPause::::paused_calls(full_name.clone()).is_some()) + } + + unpause { + let full_name: FullNameOf = (name::(b"SomePalletName"), Some(name::(b"SomePalletName"))); + let pause_origin = T::PauseOrigin::successful_origin(); + + TxPause::::pause( + pause_origin, + full_name.clone(), + )?; + + let unpause_origin = T::UnpauseOrigin::successful_origin(); + // let call = Call::::unpause { pallet_name: pallet_name.clone(), maybe_call_name: maybe_call_name.clone() }; + + }: _(unpause_origin, full_name.clone()) + verify { + assert!(TxPause::::paused_calls(full_name.clone()).is_none()) + + } + + impl_benchmark_test_suite!(TxPause, crate::mock::new_test_ext(), crate::mock::Test); +} + +pub fn name(bytes: &[u8]) -> BoundedVec { + bytes.to_vec().try_into().unwrap() +} diff --git a/crates/tx-pause/src/lib.rs b/crates/tx-pause/src/lib.rs new file mode 100644 index 0000000000..8aa5979ede --- /dev/null +++ b/crates/tx-pause/src/lib.rs @@ -0,0 +1,322 @@ +// 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. + +//! # Transaction Pause Pallet +//! +//! The Transaction Pause pallet provides a dynamic call filter that can be controlled with +//! extrinsics. This pallet may be used to disable dispatch of specific calls within a runtime. +//! +//! - [`Config`] +//! - [`Call`] +//! - [`Pallet`] +//! +//! ## Overview +//! +//! The Transaction Pause pallet provides functions for: +//! +//! - Setting a dynamic list of [`FullNameOf`] items that are matched against to filter these calls. +//! - Setting [`Config::WhitelistCallNames`] that cannot be paused by this pallet. +//! - Repatriating a reserved balance to a beneficiary account that exists. +//! - Transferring a balance between accounts (when not reserved). +//! - Slashing an account balance. +//! - Account creation and removal. +//! - Managing total issuance. +//! - Setting and managing locks. +//! +//! Can also be used as call-filter by the runtime together with the SafeMode +//! +//! TODO expand an link to safe mode in docs. +//! +//! ### Terminology +//! +//! - **Pause**: The ability to dispatch of a specific call becomes disabled. +//! - **Unpause**: The ability to dispatch of a specific call becomes enabled, if it was paused. +//! +//! ## Interface +//! +//! ### Dispatchable Functions +//! +//! - `pause` - Pause a pallet or optionally a specific call within a pallet. +//! - `unpause` - Unpause a pallet or optionally a specific call within a pallet. +//! +//! ## Usage +//! +//! The following examples show how to use the Transaction Pause pallet in your custom pallet. +//! TODO check doc links +//! ### Example within a runtime's [`pallet-frame-system::BaseCallFilter`] configuration: +//! +//! ```ignore +//! impl frame_system::Config for Runtime { +//! … +//! type BaseCallFilter = InsideBoth>; +//! … +//! } +//! ``` +//! +//! ## Genesis config +//! +//! The Transaction Pause pallet may be configured to pause calls on genesis with it's +//! [`GenesisConfig`]. +//! +//! ## Assumptions +//! +//! * TODO + +#![cfg_attr(not(feature = "std"), no_std)] + +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + +use frame_support::{ + dispatch::GetDispatchInfo, + pallet_prelude::*, + traits::{CallMetadata, Contains, GetCallMetadata, IsSubType, IsType}, +}; +use frame_system::pallet_prelude::*; +use sp_runtime::{traits::Dispatchable, DispatchResult}; +use sp_std::{convert::TryInto, prelude::*}; + +pub use pallet::*; +pub use weights::*; + +/// The stringy name of a pallet from [`GetCallMetadata`] for [`Config::RuntimeCall`] variants. +pub type PalletNameOf = BoundedVec::MaxNameLen>; +/// The stringy name of a call (within a pallet) from [`GetCallMetadata`] for +/// [`Config::RuntimeCall`] variants. +pub type CallNameOf = BoundedVec::MaxNameLen>; +/// A sully specified pallet ([`PalletNameOf`]) and optional call ([`CallNameOf`]) +/// to partially or fully specify an item a variant of a [`Config::RuntimeCall`]. +pub type FullNameOf = (PalletNameOf, Option>); + +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + pub struct Pallet(PhantomData); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// The overarching event type. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + /// The overarching call type. + type RuntimeCall: Parameter + + Dispatchable + + GetDispatchInfo + + GetCallMetadata + + From> + + IsSubType> + + IsType<::RuntimeCall>; + + /// The only origin that can pause calls. + type PauseOrigin: EnsureOrigin; + + /// The only origin that can un-pause calls. + type UnpauseOrigin: EnsureOrigin; + + /// Contains all calls that cannot be paused. + /// + /// The `TxMode` pallet cannot pause it's own calls, and does not need to be explicitly + /// added here. + type WhitelistCallNames: Contains>; + + /// Maximum length for pallet and call SCALE encoded string names. + /// + /// Too long names will not be truncated but handled like + /// [`Self::PauseTooLongNames`] specifies. + #[pallet::constant] + type MaxNameLen: Get; + + /// Specifies if functions and pallets with too long names should be treated as paused. + /// + /// Setting this to `true` ensures that all calls that + /// are callable, are also pause-able. + /// Otherwise there could be a situation where a call + /// is callable but not pause-able, which would could be exploited. + #[pallet::constant] + type PauseTooLongNames: Get; + + // Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + } + + #[pallet::error] + pub enum Error { + /// The call is (already or still) paused. + IsPaused, + + /// The call is (already or still) unpaused. + IsUnpaused, + + /// The call is listed as safe and cannot be paused. + IsUnpausable, + + // The pallet or call does not exist in the runtime. + NotFound, + } + + #[pallet::event] + #[pallet::generate_deposit(pub(super) fn deposit_event)] + pub enum Event { + /// This pallet, or a specific call is now paused. \[pallet_name, Option\] + SomethingPaused { full_name: FullNameOf }, + /// This pallet, or a specific call is now unpaused. \[pallet_name, Option\] + SomethingUnpaused { full_name: FullNameOf }, + } + + /// The set of calls that are explicitly paused. + #[pallet::storage] + #[pallet::getter(fn paused_calls)] + pub type PausedCalls = StorageMap<_, Blake2_128Concat, FullNameOf, (), OptionQuery>; + + /// Configure the initial state of this pallet in the genesis block. + #[pallet::genesis_config] + pub struct GenesisConfig { + /// The initially paused calls. + pub paused: Vec>, + pub _phantom: PhantomData, + } + + #[cfg(feature = "std")] + impl Default for GenesisConfig { + // NOTE: `derive(Default)` does not work together with `#[pallet::genesis_config]`. + // We therefore need to add a trivial default impl. + fn default() -> Self { + Self { + paused: Default::default(), + _phantom: PhantomData, + } + } + } + + #[pallet::genesis_build] + impl GenesisBuild for GenesisConfig { + fn build(&self) { + for (pallet_name, maybe_call_name) in &self.paused { + PausedCalls::::insert((pallet_name, maybe_call_name), ()); + } + } + } + + #[pallet::call] + impl Pallet { + /// Pause a call. + /// + /// Can only be called by [`Config::PauseOrigin`]. + /// Emits an [`Event::SomethingPaused`] event on success. + #[pallet::weight(T::WeightInfo::pause())] + pub fn pause(origin: OriginFor, full_name: FullNameOf) -> DispatchResult { + T::PauseOrigin::ensure_origin(origin)?; + + Self::ensure_can_pause(&full_name)?; + PausedCalls::::insert(&full_name, ()); + Self::deposit_event(Event::SomethingPaused { full_name }); + + Ok(()) + } + + /// Un-pause a call. + /// + /// Can only be called by [`Config::UnpauseOrigin`]. + /// Emits an [`Event::SomethingUnpaused`] event on success. + #[pallet::weight(T::WeightInfo::unpause())] + pub fn unpause(origin: OriginFor, full_name: FullNameOf) -> DispatchResult { + T::UnpauseOrigin::ensure_origin(origin)?; + + Self::ensure_can_unpause(&full_name)?; + PausedCalls::::remove(&full_name); + Self::deposit_event(Event::SomethingUnpaused { full_name }); + Ok(()) + } + } +} + +impl Pallet { + /// Return whether this pallet or call is paused. + pub fn is_paused_unbound(pallet_name: Vec, call_name: Vec) -> bool { + let pallet_name = PalletNameOf::::try_from(pallet_name); + let call_name = CallNameOf::::try_from(call_name); + + match (pallet_name, call_name) { + (Ok(pallet_name), Ok(call_name)) => { + Self::is_paused(&&>::from((pallet_name.clone(), Some(call_name.clone())))) + } + _ => T::PauseTooLongNames::get(), + } + } + + /// Return whether this pallet or call is paused + pub fn is_paused(full_name: &FullNameOf) -> bool { + let (pallet_name, maybe_call_name) = full_name; + // SAFETY: Everything that is whitelisted cannot be paused, + // including calls within paused pallets. + if T::WhitelistCallNames::contains(&>::from((pallet_name.clone(), maybe_call_name.clone()))) { + return false; + }; + // Check is pallet is paused. + if >::contains_key(>::from((pallet_name.clone(), None))) { + return true; + }; + // Check if call in a pallet is paused + >::contains_key(full_name) + } + + /// Ensure that this pallet or call can be paused. + pub fn ensure_can_pause(full_name: &FullNameOf) -> Result<(), Error> { + // The `TxPause` pallet can never be paused. + if full_name.0.as_ref() == ::name().as_bytes().to_vec() { + return Err(Error::::IsUnpausable); + } + if Self::is_paused(&full_name) { + return Err(Error::::IsPaused); + } + if T::WhitelistCallNames::contains(&full_name) { + return Err(Error::::IsUnpausable); + } + Ok(()) + } + + /// Ensure that this call can be un-paused. + pub fn ensure_can_unpause(full_name: &FullNameOf) -> Result<(), Error> { + if Self::is_paused(&full_name) { + // SAFETY: Everything that is paused, can be un-paused. + Ok(()) + } else { + Err(Error::IsUnpaused) + } + } +} + +impl Contains<::RuntimeCall> for Pallet +where + ::RuntimeCall: GetCallMetadata, +{ + /// Return whether the call is allowed to be dispatched. + fn contains(call: &::RuntimeCall) -> bool { + let CallMetadata { + pallet_name, + function_name, + } = call.get_call_metadata(); + !Pallet::::is_paused_unbound(pallet_name.into(), function_name.into()) + } +} diff --git a/crates/tx-pause/src/mock.rs b/crates/tx-pause/src/mock.rs new file mode 100644 index 0000000000..6775dd6b44 --- /dev/null +++ b/crates/tx-pause/src/mock.rs @@ -0,0 +1,250 @@ +// 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. + +//! Test utilities for transaction pause (tx pause) pallet. + +use super::*; +use crate as pallet_tx_pause; + +use frame_support::{ + parameter_types, + traits::{ConstU64, Everything, InsideBoth, InstanceFilter, 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 RuntimeOrigin = RuntimeOrigin; + type RuntimeCall = RuntimeCall; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + 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 RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_utility::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PalletsOrigin = OriginCaller; + type WeightInfo = (); +} + +#[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, RuntimeDebug, MaxEncodedLen, scale_info::TypeInfo, +)] +pub enum ProxyType { + Any, + JustTransfer, + JustUtility, +} +impl Default for ProxyType { + fn default() -> Self { + Self::Any + } +} +impl InstanceFilter for ProxyType { + fn filter(&self, c: &RuntimeCall) -> bool { + match self { + ProxyType::Any => true, + ProxyType::JustTransfer => { + matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. })) + } + ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }), + } + } + fn is_superset(&self, o: &Self) -> bool { + self == &ProxyType::Any || self == o + } +} +impl pallet_proxy::Config for Test { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type Currency = Balances; + type ProxyType = ProxyType; + type ProxyDepositBase = ConstU64<1>; + type ProxyDepositFactor = ConstU64<1>; + type MaxProxies = ConstU32<4>; + type WeightInfo = (); + type CallHasher = BlakeTwo256; + type MaxPending = ConstU32<2>; + type AnnouncementDepositBase = ConstU64<1>; + type AnnouncementDepositFactor = ConstU64<1>; +} + +parameter_types! { + pub const PauseOrigin: u64 = 1; + pub const UnpauseOrigin: u64 = 2; + pub const MaxNameLen: u32 = 50; + pub const PauseTooLongNames: bool = false; +} + +#[derive(Copy, Clone, Encode, Decode, RuntimeDebug, MaxEncodedLen, scale_info::TypeInfo)] +pub struct WhitelistCallNames; + +/// Contains used by `BaseCallFiler` so this impl whitelists `Balances::transfer_keep_alive` +/// and all DummyPallet calls. All others may be paused. +impl Contains> for WhitelistCallNames { + fn contains(full_name: &FullNameOf) -> bool { + let unpausables: Vec> = vec![ + ( + b"Balances".to_vec().try_into().unwrap(), + Some(b"transfer_keep_alive".to_vec().try_into().unwrap()), + ), + (b"DummyPallet".to_vec().try_into().unwrap(), None), + ]; + + for unpausable_call in unpausables { + let (pallet_name, maybe_call_name) = full_name; + if unpausable_call.1.is_none() { + return &unpausable_call.0 == pallet_name; + } else { + if &unpausable_call.0 == pallet_name { + return &unpausable_call.1 == maybe_call_name; + } + } + } + + false + } +} + +// 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 RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureSignedBy; + type UnpauseOrigin = EnsureSignedBy; + type WhitelistCallNames = WhitelistCallNames; + 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, + Utility: pallet_utility, + Proxy: pallet_proxy, + 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/crates/tx-pause/src/tests.rs b/crates/tx-pause/src/tests.rs new file mode 100644 index 0000000000..4ee3ceef47 --- /dev/null +++ b/crates/tx-pause/src/tests.rs @@ -0,0 +1,251 @@ +// 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. + +#![cfg(test)] + +use super::*; +use crate::mock::{RuntimeCall, *}; + +use frame_support::{assert_err, assert_noop, assert_ok, dispatch::Dispatchable}; + +// GENERAL SUCCESS/POSITIVE TESTS --------------------- + +#[test] +fn can_pause_specific_call() { + new_test_ext().execute_with(|| { + assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0))); + + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")) + )); + + assert_err!( + call_transfer(2, 1).dispatch(RuntimeOrigin::signed(2)), + frame_system::Error::::CallFiltered + ); + assert_ok!(call_transfer_keep_alive(3, 1).dispatch(RuntimeOrigin::signed(3))); + }); +} + +#[test] +fn can_pause_all_calls_in_pallet_except_on_whitelist() { + new_test_ext().execute_with(|| { + assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0))); + + let batch_call = RuntimeCall::Utility(pallet_utility::Call::batch { + calls: vec![call_transfer(1, 1)], + }); + assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0))); + + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Utility", None) + ),); + + assert_err!( + batch_call.clone().dispatch(RuntimeOrigin::signed(0)), + frame_system::Error::::CallFiltered + ); + }); +} + +#[test] +fn can_unpause_specific_call() { + new_test_ext().execute_with(|| { + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + )); + assert_err!( + call_transfer(2, 1).dispatch(RuntimeOrigin::signed(2)), + frame_system::Error::::CallFiltered + ); + + assert_ok!(TxPause::unpause( + RuntimeOrigin::signed(mock::UnpauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + )); + assert_ok!(call_transfer(4, 1).dispatch(RuntimeOrigin::signed(0))); + }); +} + +#[test] +fn can_filter_balance_in_batch_when_paused() { + new_test_ext().execute_with(|| { + let batch_call = RuntimeCall::Utility(pallet_utility::Call::batch { + calls: vec![call_transfer(1, 1)], + }); + + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + )); + + assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0))); + System::assert_last_event( + pallet_utility::Event::BatchInterrupted { + index: 0, + error: frame_system::Error::::CallFiltered.into(), + } + .into(), + ); + }); +} + +#[test] +fn can_filter_balance_in_proxy_when_paused() { + new_test_ext().execute_with(|| { + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + )); + + assert_ok!(Proxy::add_proxy( + RuntimeOrigin::signed(1), + 2, + ProxyType::JustTransfer, + 0 + )); + + assert_ok!(Proxy::proxy( + RuntimeOrigin::signed(2), + 1, + None, + Box::new(call_transfer(1, 1)) + )); + System::assert_last_event( + pallet_proxy::Event::ProxyExecuted { + result: DispatchError::from(frame_system::Error::::CallFiltered).into(), + } + .into(), + ); + }); +} + +// GENERAL FAIL/NEGATIVE TESTS --------------------- + +#[test] +fn fails_to_pause_self() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"TxPause", Some(b"pause")), + ), + Error::::IsUnpausable + ); + }); +} + +#[test] +fn fails_to_pause_unpausable_pallet() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"DummyPallet", Some(b"any_call")), + ), + Error::::IsUnpausable + ); + }); +} + +#[test] +fn fails_to_pause_unpausable_call_when_pallet_is_paused() { + new_test_ext().execute_with(|| { + assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0))); + + let batch_call = RuntimeCall::Utility(pallet_utility::Call::batch { + calls: vec![call_transfer(1, 1)], + }); + assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0))); + + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", None), + )); + + assert_ok!(call_transfer_keep_alive(3, 1).dispatch(RuntimeOrigin::signed(3))); + assert_err!( + call_transfer(2, 1).dispatch(RuntimeOrigin::signed(0)), + frame_system::Error::::CallFiltered + ); + }); +} + +#[test] +fn fails_to_pause_unpausable_call() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer_keep_alive")), + ), + Error::::IsUnpausable + ); + }); +} + +#[test] +fn fails_to_pause_already_paused_pallet() { + new_test_ext().execute_with(|| { + assert_ok!(TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + )); + + assert_noop!( + TxPause::pause( + RuntimeOrigin::signed(mock::PauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer")), + ), + Error::::IsPaused + ); + }); +} + +#[test] +fn fails_to_unpause_not_paused_pallet() { + new_test_ext().execute_with(|| { + assert_noop!( + TxPause::unpause( + RuntimeOrigin::signed(mock::UnpauseOrigin::get()), + full_name::(b"Balances", Some(b"transfer_keep_alive")), + ), + Error::::IsUnpaused + ); + }); +} + +pub fn call_transfer(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(pallet_balances::Call::transfer { dest, value }) +} + +pub fn call_transfer_keep_alive(dest: u64, value: u64) -> RuntimeCall { + RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { dest, value }) +} + +pub fn full_name(pallet_name_bytes: &[u8], maybe_call_name_bytes: Option<&[u8]>) -> FullNameOf { + match maybe_call_name_bytes { + Some(call_name_bytes) => >::from(( + pallet_name_bytes.to_vec().try_into().unwrap(), + Some(call_name_bytes.to_vec().try_into().unwrap()), + )), + None => >::from((pallet_name_bytes.to_vec().try_into().unwrap(), None)), + } +} diff --git a/crates/tx-pause/src/weights.rs b/crates/tx-pause/src/weights.rs new file mode 100644 index 0000000000..a4b408eef3 --- /dev/null +++ b/crates/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-10-15, 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() -> Weight; + fn unpause() -> 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:2 w:1) + fn pause() -> Weight { + Weight::from_ref_time(61_745_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } + // Storage: TxPause PausedCalls (r:2 w:1) + fn unpause() -> Weight { + Weight::from_ref_time(55_117_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(1 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: TxPause PausedCalls (r:2 w:1) + fn pause() -> Weight { + Weight::from_ref_time(61_745_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } + // Storage: TxPause PausedCalls (r:2 w:1) + fn unpause() -> Weight { + Weight::from_ref_time(55_117_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(1 as u64)) + } +} diff --git a/parachain/runtime/interlay/Cargo.toml b/parachain/runtime/interlay/Cargo.toml index aee2de2c62..b354f0ef39 100644 --- a/parachain/runtime/interlay/Cargo.toml +++ b/parachain/runtime/interlay/Cargo.toml @@ -97,6 +97,7 @@ supply = { path = "../../../crates/supply", default-features = false } collator-selection = { path = "../../../crates/collator-selection", default-features = false } clients-info = { path = "../../../crates/clients-info", default-features = false } traits = { path = "../../../crates/traits", default-features = false } +tx-pause = { path = "../../../crates/tx-pause", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -213,6 +214,7 @@ std = [ "collator-selection/std", "clients-info/std", "traits/std", + "tx-pause/std", "primitives/std", @@ -256,6 +258,7 @@ runtime-benchmarks = [ "redeem/runtime-benchmarks", "replace/runtime-benchmarks", "vault-registry/runtime-benchmarks", + "tx-pause/runtime-benchmarks", ] disable-runtime-api = [] try-runtime = [ @@ -296,6 +299,7 @@ try-runtime = [ "fee/try-runtime", "nomination/try-runtime", "clients-info/try-runtime", + "tx-pause/try-runtime", "democracy/try-runtime", "pallet-collective/try-runtime", "pallet-membership/try-runtime", diff --git a/parachain/runtime/interlay/src/lib.rs b/parachain/runtime/interlay/src/lib.rs index 7960c0ca20..ec733da7f4 100644 --- a/parachain/runtime/interlay/src/lib.rs +++ b/parachain/runtime/interlay/src/lib.rs @@ -172,23 +172,21 @@ impl Contains for BaseCallFilter { | RuntimeCall::Session(_) | RuntimeCall::Timestamp(_) | RuntimeCall::ParachainSystem(_) + | RuntimeCall::Sudo(_) | RuntimeCall::Democracy(_) | RuntimeCall::Escrow(_) | RuntimeCall::TechnicalCommittee(_) - | RuntimeCall::Sudo(_) ) { // always allow core calls true - } else if security::Pallet::::is_parachain_shutdown() { - // in shutdown mode, all non-core calls are disallowed - false } else if let RuntimeCall::PolkadotXcm(_) = call { // For security reasons, disallow usage of the xcm package by users. Sudo and // governance are still able to call these (sudo is explicitly white-listed, while // governance bypasses this call filter). false } else { - true + // normal operation: allow all calls that are not explicitly paused + TxPause::contains(call) } } } @@ -1136,6 +1134,22 @@ impl clients_info::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const MaxNameLen: u32 = 128; + pub const PauseTooLongNames: bool = false; +} + +impl tx_pause::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureRoot; + type UnpauseOrigin = EnsureRoot; + type WhitelistCallNames = Nothing; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -1152,6 +1166,7 @@ construct_runtime! { Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 7, Identity: pallet_identity::{Pallet, Call, Storage, Event} = 8, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 9, + TxPause: tx_pause::{Pallet, Call, Storage, Event} = 10, // # Tokens & Balances Currency: currency::{Pallet} = 20, diff --git a/parachain/runtime/kintsugi/Cargo.toml b/parachain/runtime/kintsugi/Cargo.toml index fc5e7cdd69..1e158f6c11 100644 --- a/parachain/runtime/kintsugi/Cargo.toml +++ b/parachain/runtime/kintsugi/Cargo.toml @@ -98,6 +98,7 @@ supply = { path = "../../../crates/supply", default-features = false } collator-selection = { path = "../../../crates/collator-selection", default-features = false } clients-info = { path = "../../../crates/clients-info", default-features = false } traits = { path = "../../../crates/traits", default-features = false } +tx-pause = { path = "../../../crates/tx-pause", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -219,6 +220,7 @@ std = [ "collator-selection/std", "clients-info/std", "traits/std", + "tx-pause/std", "primitives/std", @@ -262,6 +264,7 @@ runtime-benchmarks = [ "redeem/runtime-benchmarks", "replace/runtime-benchmarks", "vault-registry/runtime-benchmarks", + "tx-pause/runtime-benchmarks", ] disable-runtime-api = [] try-runtime = [ @@ -300,6 +303,7 @@ try-runtime = [ "fee/try-runtime", "nomination/try-runtime", "clients-info/try-runtime", + "tx-pause/try-runtime", "democracy/try-runtime", "pallet-collective/try-runtime", "pallet-membership/try-runtime", diff --git a/parachain/runtime/kintsugi/src/lib.rs b/parachain/runtime/kintsugi/src/lib.rs index 026a13e528..b40f958de7 100644 --- a/parachain/runtime/kintsugi/src/lib.rs +++ b/parachain/runtime/kintsugi/src/lib.rs @@ -167,25 +167,24 @@ impl Contains for BaseCallFilter { call, RuntimeCall::System(_) | RuntimeCall::Authorship(_) + | RuntimeCall::Session(_) | RuntimeCall::Timestamp(_) | RuntimeCall::ParachainSystem(_) + | RuntimeCall::Sudo(_) | RuntimeCall::Democracy(_) | RuntimeCall::Escrow(_) | RuntimeCall::TechnicalCommittee(_) - | RuntimeCall::Sudo(_) ) { // always allow core calls true - } else if security::Pallet::::is_parachain_shutdown() { - // in shutdown mode, all non-core calls are disallowed - false } else if let RuntimeCall::PolkadotXcm(_) = call { // For security reasons, disallow usage of the xcm package by users. Sudo and // governance are still able to call these (sudo is explicitly white-listed, while // governance bypasses this call filter). false } else { - true + // normal operation: allow all calls that are not explicitly paused + TxPause::contains(call) } } } @@ -1134,6 +1133,22 @@ impl clients_info::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const MaxNameLen: u32 = 128; + pub const PauseTooLongNames: bool = false; +} + +impl tx_pause::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureRoot; + type UnpauseOrigin = EnsureRoot; + type WhitelistCallNames = Nothing; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -1150,6 +1165,7 @@ construct_runtime! { Identity: pallet_identity::{Pallet, Call, Storage, Event} = 7, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 8, Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 9, + TxPause: tx_pause::{Pallet, Call, Storage, Event} = 10, // # Tokens & Balances Currency: currency::{Pallet} = 20, diff --git a/parachain/runtime/testnet-interlay/Cargo.toml b/parachain/runtime/testnet-interlay/Cargo.toml index d827b4188b..13a89580dd 100644 --- a/parachain/runtime/testnet-interlay/Cargo.toml +++ b/parachain/runtime/testnet-interlay/Cargo.toml @@ -99,6 +99,7 @@ collator-selection = { path = "../../../crates/collator-selection", default-feat clients-info = { path = "../../../crates/clients-info", default-features = false } loans = { path = "../../../crates/loans", default-features = false } traits = { path = "../../../crates/traits", default-features = false } +tx-pause = { path = "../../../crates/tx-pause", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -220,6 +221,7 @@ std = [ "clients-info/std", "loans/std", "traits/std", + "tx-pause/std", "primitives/std", @@ -265,6 +267,7 @@ runtime-benchmarks = [ "redeem/runtime-benchmarks", "replace/runtime-benchmarks", "vault-registry/runtime-benchmarks", + "tx-pause/runtime-benchmarks", ] disable-runtime-api = [] dev-interlay = [] @@ -306,6 +309,7 @@ try-runtime = [ "fee/try-runtime", "nomination/try-runtime", "clients-info/try-runtime", + "tx-pause/try-runtime", "loans/try-runtime", "democracy/try-runtime", "pallet-collective/try-runtime", diff --git a/parachain/runtime/testnet-interlay/src/lib.rs b/parachain/runtime/testnet-interlay/src/lib.rs index 9718d011b9..87ca687fe3 100644 --- a/parachain/runtime/testnet-interlay/src/lib.rs +++ b/parachain/runtime/testnet-interlay/src/lib.rs @@ -170,6 +170,7 @@ impl Contains for BaseCallFilter { call, RuntimeCall::System(_) | RuntimeCall::Authorship(_) + | RuntimeCall::Session(_) | RuntimeCall::Timestamp(_) | RuntimeCall::ParachainSystem(_) | RuntimeCall::Sudo(_) @@ -179,16 +180,14 @@ impl Contains for BaseCallFilter { ) { // always allow core calls true - } else if security::Pallet::::is_parachain_shutdown() { - // in shutdown mode, all non-core calls are disallowed - false } else if let RuntimeCall::PolkadotXcm(_) = call { // For security reasons, disallow usage of the xcm package by users. Sudo and // governance are still able to call these (sudo is explicitly white-listed, while // governance bypasses this call filter). false } else { - true + // normal operation: allow all calls that are not explicitly paused + TxPause::contains(call) } } } @@ -1106,6 +1105,22 @@ impl clients_info::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const MaxNameLen: u32 = 128; + pub const PauseTooLongNames: bool = false; +} + +impl tx_pause::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureRoot; + type UnpauseOrigin = EnsureRoot; + type WhitelistCallNames = Nothing; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + impl loans::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PalletId = LoansPalletId; @@ -1135,6 +1150,7 @@ construct_runtime! { Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 7, Identity: pallet_identity::{Pallet, Call, Storage, Event} = 8, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 9, + TxPause: tx_pause::{Pallet, Call, Storage, Event} = 10, // # Tokens & Balances Currency: currency::{Pallet} = 20, diff --git a/parachain/runtime/testnet-kintsugi/Cargo.toml b/parachain/runtime/testnet-kintsugi/Cargo.toml index 9158066d83..fd6e2372fc 100644 --- a/parachain/runtime/testnet-kintsugi/Cargo.toml +++ b/parachain/runtime/testnet-kintsugi/Cargo.toml @@ -100,6 +100,7 @@ clients-info = { path = "../../../crates/clients-info", default-features = false loans = { path = "../../../crates/loans", default-features = false } traits = { path = "../../../crates/traits", default-features = false } farming = { path = "../../../crates/farming", default-features = false } +tx-pause = { path = "../../../crates/tx-pause", default-features = false } primitives = { package = "interbtc-primitives", path = "../../../primitives", default-features = false } @@ -229,6 +230,7 @@ std = [ "loans/std", "traits/std", "farming/std", + "tx-pause/std", "primitives/std", @@ -281,6 +283,7 @@ runtime-benchmarks = [ "replace/runtime-benchmarks", "vault-registry/runtime-benchmarks", "farming/runtime-benchmarks", + "tx-pause/runtime-benchmarks", ] disable-runtime-api = [] dev-interlay = [] @@ -322,6 +325,7 @@ try-runtime = [ "fee/try-runtime", "nomination/try-runtime", "clients-info/try-runtime", + "tx-pause/try-runtime", "loans/try-runtime", "democracy/try-runtime", "farming/try-runtime", diff --git a/parachain/runtime/testnet-kintsugi/src/lib.rs b/parachain/runtime/testnet-kintsugi/src/lib.rs index 55b90ca66e..ea319cb5e0 100644 --- a/parachain/runtime/testnet-kintsugi/src/lib.rs +++ b/parachain/runtime/testnet-kintsugi/src/lib.rs @@ -172,6 +172,7 @@ impl Contains for BaseCallFilter { call, RuntimeCall::System(_) | RuntimeCall::Authorship(_) + | RuntimeCall::Session(_) | RuntimeCall::Timestamp(_) | RuntimeCall::ParachainSystem(_) | RuntimeCall::Sudo(_) @@ -181,16 +182,14 @@ impl Contains for BaseCallFilter { ) { // always allow core calls true - } else if security::Pallet::::is_parachain_shutdown() { - // in shutdown mode, all non-core calls are disallowed - false } else if let RuntimeCall::PolkadotXcm(_) = call { // For security reasons, disallow usage of the xcm package by users. Sudo and // governance are still able to call these (sudo is explicitly white-listed, while // governance bypasses this call filter). false } else { - true + // normal operation: allow all calls that are not explicitly paused + TxPause::contains(call) } } } @@ -1136,6 +1135,22 @@ impl clients_info::Config for Runtime { type WeightInfo = (); } +parameter_types! { + pub const MaxNameLen: u32 = 128; + pub const PauseTooLongNames: bool = false; +} + +impl tx_pause::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureRoot; + type UnpauseOrigin = EnsureRoot; + type WhitelistCallNames = Nothing; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + impl loans::Config for Runtime { type RuntimeEvent = RuntimeEvent; type PalletId = LoansPalletId; @@ -1165,6 +1180,7 @@ construct_runtime! { Identity: pallet_identity::{Pallet, Call, Storage, Event} = 7, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 8, Sudo: pallet_sudo::{Pallet, Call, Storage, Config, Event} = 9, + TxPause: tx_pause::{Pallet, Call, Storage, Event} = 10, // # Tokens & Balances Currency: currency::{Pallet} = 20, diff --git a/parachain/src/chain_spec/interlay.rs b/parachain/src/chain_spec/interlay.rs index 3974a4c98f..09838568d8 100644 --- a/parachain/src/chain_spec/interlay.rs +++ b/parachain/src/chain_spec/interlay.rs @@ -156,7 +156,7 @@ fn interlay_mainnet_genesis( aura: Default::default(), aura_ext: Default::default(), security: interlay_runtime::SecurityConfig { - initial_status: interlay_runtime::StatusCode::Shutdown, + initial_status: interlay_runtime::StatusCode::Error, }, asset_registry: Default::default(), tokens: Default::default(), diff --git a/parachain/src/chain_spec/kintsugi.rs b/parachain/src/chain_spec/kintsugi.rs index c65895673e..6bd1b56b17 100644 --- a/parachain/src/chain_spec/kintsugi.rs +++ b/parachain/src/chain_spec/kintsugi.rs @@ -160,7 +160,7 @@ fn kintsugi_mainnet_genesis( aura: Default::default(), aura_ext: Default::default(), security: kintsugi_runtime::SecurityConfig { - initial_status: kintsugi_runtime::StatusCode::Shutdown, + initial_status: kintsugi_runtime::StatusCode::Error, }, asset_registry: Default::default(), tokens: Default::default(), diff --git a/parachain/src/chain_spec/testnet_interlay.rs b/parachain/src/chain_spec/testnet_interlay.rs index fef18c88b1..702ea9ce3f 100644 --- a/parachain/src/chain_spec/testnet_interlay.rs +++ b/parachain/src/chain_spec/testnet_interlay.rs @@ -64,7 +64,6 @@ pub fn development_config(id: ParaId) -> InterlayTestnetChainSpec { ], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -125,7 +124,6 @@ pub fn staging_testnet_config(id: ParaId) -> InterlayTestnetChainSpec { )], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -151,7 +149,6 @@ fn testnet_genesis( authorized_oracles: Vec<(AccountId, Vec)>, id: ParaId, bitcoin_confirmations: u32, - start_shutdown: bool, ) -> testnet_interlay_runtime::GenesisConfig { testnet_interlay_runtime::GenesisConfig { system: testnet_interlay_runtime::SystemConfig { @@ -184,11 +181,7 @@ fn testnet_genesis( aura: Default::default(), aura_ext: Default::default(), security: testnet_interlay_runtime::SecurityConfig { - initial_status: if start_shutdown { - testnet_interlay_runtime::StatusCode::Shutdown - } else { - testnet_interlay_runtime::StatusCode::Error - }, + initial_status: testnet_interlay_runtime::StatusCode::Error, }, sudo: testnet_interlay_runtime::SudoConfig { // Assign network admin rights. diff --git a/parachain/src/chain_spec/testnet_kintsugi.rs b/parachain/src/chain_spec/testnet_kintsugi.rs index 0c8f652b06..5b543465be 100644 --- a/parachain/src/chain_spec/testnet_kintsugi.rs +++ b/parachain/src/chain_spec/testnet_kintsugi.rs @@ -54,7 +54,6 @@ pub fn local_config(id: ParaId) -> KintsugiTestnetChainSpec { )], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, vec![], @@ -108,7 +107,6 @@ pub fn development_config(id: ParaId) -> KintsugiTestnetChainSpec { ], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -169,7 +167,6 @@ pub fn staging_testnet_config(id: ParaId) -> KintsugiTestnetChainSpec { )], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -230,7 +227,6 @@ pub fn rococo_testnet_config(id: ParaId) -> KintsugiTestnetChainSpec { )], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -295,7 +291,6 @@ pub fn westend_testnet_config(id: ParaId) -> KintsugiTestnetChainSpec { )], id, DEFAULT_BITCOIN_CONFIRMATIONS, - false, ) }, Vec::new(), @@ -317,7 +312,6 @@ fn testnet_genesis( authorized_oracles: Vec<(AccountId, Vec)>, id: ParaId, bitcoin_confirmations: u32, - start_shutdown: bool, ) -> testnet_kintsugi_runtime::GenesisConfig { testnet_kintsugi_runtime::GenesisConfig { system: testnet_kintsugi_runtime::SystemConfig { @@ -350,11 +344,7 @@ fn testnet_genesis( aura: Default::default(), aura_ext: Default::default(), security: testnet_kintsugi_runtime::SecurityConfig { - initial_status: if start_shutdown { - testnet_kintsugi_runtime::StatusCode::Shutdown - } else { - testnet_kintsugi_runtime::StatusCode::Error - }, + initial_status: testnet_kintsugi_runtime::StatusCode::Error, }, sudo: testnet_kintsugi_runtime::SudoConfig { // Assign network admin rights. diff --git a/standalone/runtime/Cargo.toml b/standalone/runtime/Cargo.toml index 287b7bafa6..a2a8d13a85 100644 --- a/standalone/runtime/Cargo.toml +++ b/standalone/runtime/Cargo.toml @@ -80,6 +80,7 @@ clients-info = { path = "../../crates/clients-info", default-features = false } loans = { path = "../../crates/loans", default-features = false } traits = { path = "../../crates/traits", default-features = false } farming = { path = "../../crates/farming", default-features = false } +tx-pause = { path = "../../crates/tx-pause", default-features = false } primitives = { package = "interbtc-primitives", path = "../../primitives", default-features = false } @@ -186,6 +187,7 @@ std = [ "loans/std", "traits/std", "farming/std", + "tx-pause/std", "primitives/std", @@ -233,6 +235,7 @@ runtime-benchmarks = [ "replace/runtime-benchmarks", "vault-registry/runtime-benchmarks", "farming/runtime-benchmarks", + "tx-pause/runtime-benchmarks", "orml-asset-registry/runtime-benchmarks", diff --git a/standalone/runtime/src/lib.rs b/standalone/runtime/src/lib.rs index 6eaf408cdf..b4a854fe18 100644 --- a/standalone/runtime/src/lib.rs +++ b/standalone/runtime/src/lib.rs @@ -42,6 +42,7 @@ use sp_version::NativeVersion; use sp_version::RuntimeVersion; // A few exports that help ease life for downstream crates. +use frame_support::traits::Nothing; pub use frame_support::{ construct_runtime, dispatch::DispatchClass, @@ -183,8 +184,8 @@ impl Contains for BaseCallFilter { // always allow core calls true } else { - // disallow everything if shutdown - !security::Pallet::::is_parachain_shutdown() + // normal operation: allow all calls that are not explicitly paused + TxPause::contains(call) } } } @@ -1093,6 +1094,22 @@ impl loans::Config for Runtime { type OnExchangeRateChange = vault_registry::PoolManager; } +parameter_types! { + pub const MaxNameLen: u32 = 128; + pub const PauseTooLongNames: bool = false; +} + +impl tx_pause::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type RuntimeCall = RuntimeCall; + type PauseOrigin = EnsureRoot; + type UnpauseOrigin = EnsureRoot; + type WhitelistCallNames = Nothing; + type MaxNameLen = MaxNameLen; + type PauseTooLongNames = PauseTooLongNames; + type WeightInfo = (); +} + construct_runtime! { pub enum Runtime where Block = Block, @@ -1109,6 +1126,7 @@ construct_runtime! { Multisig: pallet_multisig::{Pallet, Call, Storage, Event} = 7, Identity: pallet_identity::{Pallet, Call, Storage, Event} = 8, Proxy: pallet_proxy::{Pallet, Call, Storage, Event} = 9, + TxPause: tx_pause::{Pallet, Call, Storage, Event} = 10, // # Tokens & Balances Currency: currency::{Pallet} = 20, diff --git a/standalone/runtime/tests/mock/mod.rs b/standalone/runtime/tests/mock/mod.rs index 4d11ab5c9d..2d9047ac58 100644 --- a/standalone/runtime/tests/mock/mod.rs +++ b/standalone/runtime/tests/mock/mod.rs @@ -163,6 +163,7 @@ pub type SecurityCall = security::Call; pub type SudoCall = pallet_sudo::Call; pub type SudoError = pallet_sudo::Error; +pub type SystemCall = frame_system::Call; pub type SystemPallet = frame_system::Pallet; pub type SystemError = frame_system::Error; @@ -181,6 +182,8 @@ pub type EscrowPallet = escrow::Pallet; pub type UtilityCall = pallet_utility::Call; +pub type TxPauseCall = tx_pause::Call; + pub type SchedulerCall = pallet_scheduler::Call; pub type SchedulerPallet = pallet_scheduler::Pallet; diff --git a/standalone/runtime/tests/test_btc_relay.rs b/standalone/runtime/tests/test_btc_relay.rs index 8488dfbbec..284dffb135 100644 --- a/standalone/runtime/tests/test_btc_relay.rs +++ b/standalone/runtime/tests/test_btc_relay.rs @@ -94,45 +94,6 @@ fn integration_test_submit_block_headers_and_verify_transaction_inclusion() { }) } -#[test] -fn integration_test_btc_relay_with_parachain_shutdown_fails() { - ExtBuilder::build().execute_with(|| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::BTCRelay(BTCRelayCall::verify_and_validate_transaction { - raw_merkle_proof: Default::default(), - confirmations: Default::default(), - raw_tx: Default::default(), - expected_btc: Default::default(), - recipient_btc_address: Default::default(), - op_return_id: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::BTCRelay(BTCRelayCall::verify_transaction_inclusion { - tx_id: Default::default(), - raw_merkle_proof: Default::default(), - confirmations: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::BTCRelay(BTCRelayCall::validate_transaction { - raw_tx: Default::default(), - expected_btc: Default::default(), - recipient_btc_address: Default::default(), - op_return_id: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered - ); - }) -} - #[test] fn integration_test_submit_fork_headers() { ExtBuilder::build().execute_without_relay_init(|| { diff --git a/standalone/runtime/tests/test_fee_pool.rs b/standalone/runtime/tests/test_fee_pool.rs index 85c12146c4..83bb1a2117 100644 --- a/standalone/runtime/tests/test_fee_pool.rs +++ b/standalone/runtime/tests/test_fee_pool.rs @@ -143,21 +143,6 @@ fn integration_test_estimate_vault_reward_rate() { }); } -#[test] -fn integration_test_fee_with_parachain_shutdown_fails() { - test_with(|vault_id_1| { - SecurityPallet::set_status(StatusCode::Shutdown); - assert_noop!( - RuntimeCall::Fee(FeeCall::withdraw_rewards { - vault_id: vault_id_1.clone(), - index: None - }) - .dispatch(origin_of(vault_id_1.account_id)), - SystemError::CallFiltered - ); - }) -} - #[test] fn test_vault_reward_withdrawal() { test_with(|vault_id_1| { diff --git a/standalone/runtime/tests/test_governance.rs b/standalone/runtime/tests/test_governance.rs index 9ee282040a..a8cdc69bd4 100644 --- a/standalone/runtime/tests/test_governance.rs +++ b/standalone/runtime/tests/test_governance.rs @@ -3,10 +3,14 @@ use crate::assert_eq; use mock::*; use democracy::{PropIndex, ReferendumIndex, ReferendumInfo, ReferendumStatus, Tally, Vote, VoteThreshold}; -use frame_support::traits::{Currency, Hooks}; +use frame_support::{ + assert_err_ignore_postinfo, + traits::{Currency, GetCallMetadata, Hooks}, +}; use orml_vesting::VestingSchedule; use sp_core::{Encode, Hasher}; use sp_runtime::traits::BlakeTwo256; +use tx_pause::FullNameOf; type DemocracyCall = democracy::Call; type DemocracyPallet = democracy::Pallet; @@ -168,67 +172,141 @@ fn launch_and_execute_referendum() { SchedulerPallet::on_initialize(act_height); } +fn full_name(pallet_name_bytes: &[u8], maybe_call_name_bytes: Option<&[u8]>) -> FullNameOf { + match maybe_call_name_bytes { + Some(call_name_bytes) => >::from(( + pallet_name_bytes.to_vec().try_into().unwrap(), + Some(call_name_bytes.to_vec().try_into().unwrap()), + )), + None => >::from((pallet_name_bytes.to_vec().try_into().unwrap(), None)), + } +} + #[test] -fn can_recover_from_shutdown_using_governance() { +fn can_pause_pallets() { test_with(|| { - // use sudo to set parachain status - assert_ok!(RuntimeCall::Sudo(SudoCall::sudo { - call: Box::new(RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Shutdown, - })), + let call = RuntimeCall::Tokens(TokensCall::transfer { + dest: account_of(ALICE), + currency_id: DEFAULT_NATIVE_CURRENCY, + amount: 123, + }); + + // sanity check: this call is allowed by default + assert_ok!(call.clone().dispatch(origin_of(account_of(ALICE)))); + + // pause calls to Tokens pallet.. + assert_ok!(RuntimeCall::TxPause(TxPauseCall::pause { + full_name: full_name(b"Tokens", None) }) - .dispatch(origin_of(account_of(ALICE)))); - assert!(SecurityPallet::is_parachain_shutdown()); + .dispatch(root())); - create_proposal( - RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Running, - }) - .encode(), - ); - launch_and_execute_referendum(); - assert!(!SecurityPallet::is_parachain_shutdown()); + assert_noop!(call.dispatch(origin_of(account_of(ALICE))), SystemError::CallFiltered); }) } #[test] -fn can_recover_from_shutdown_using_root() { +fn can_pause_functions() { test_with(|| { - // use sudo to set parachain status - assert_ok!(RuntimeCall::Sudo(SudoCall::sudo { - call: Box::new(RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Shutdown, - })), + let call = RuntimeCall::Tokens(TokensCall::transfer { + dest: account_of(ALICE), + currency_id: DEFAULT_NATIVE_CURRENCY, + amount: 123, + }); + + // sanity check: this call is allowed by default + assert_ok!(call.clone().dispatch(origin_of(account_of(ALICE)))); + + // pause call.. + assert_ok!(RuntimeCall::TxPause(TxPauseCall::pause { + full_name: full_name(b"Tokens", Some(b"transfer")) + }) + .dispatch(root())); + + assert_noop!(call.dispatch(origin_of(account_of(ALICE))), SystemError::CallFiltered); + + // other functions from the Tokens pallet should continue to function + assert_ok!(RuntimeCall::Tokens(TokensCall::transfer_all { + dest: account_of(ALICE), + currency_id: DEFAULT_NATIVE_CURRENCY, + keep_alive: false, }) .dispatch(origin_of(account_of(ALICE)))); + }) +} - // verify we cant execute normal calls - assert_noop!( - RuntimeCall::Tokens(TokensCall::transfer { +#[test] +fn pause_works_on_calls_in_batch() { + test_with(|| { + let call = RuntimeCall::Utility(UtilityCall::batch_all { + calls: vec![RuntimeCall::Tokens(TokensCall::transfer { dest: account_of(ALICE), currency_id: DEFAULT_NATIVE_CURRENCY, amount: 123, - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered - ); + })], + }); - // use sudo to set parachain status back to running - assert_ok!(RuntimeCall::Sudo(SudoCall::sudo { - call: Box::new(RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Running, - })) + // sanity check: this call is allowed by default + assert_ok!(call.clone().dispatch(origin_of(account_of(ALICE)))); + + // pause call.. + assert_ok!(RuntimeCall::TxPause(TxPauseCall::pause { + full_name: full_name(b"Tokens", Some(b"transfer")) }) - .dispatch(origin_of(account_of(ALICE)))); + .dispatch(root())); - // verify that we can execute normal calls again - assert_ok!(RuntimeCall::Tokens(TokensCall::transfer { + // we need assert_err_ignore_postinfo since the call does execute, it only bails when the + // nested call inside the batch gets dispatched + assert_err_ignore_postinfo!(call.dispatch(origin_of(account_of(ALICE))), SystemError::CallFiltered); + }) +} + +#[test] +fn can_not_use_txpause_to_brick_parachain() { + test_with(|| { + let call = RuntimeCall::Tokens(TokensCall::transfer { dest: account_of(ALICE), currency_id: DEFAULT_NATIVE_CURRENCY, amount: 123, - }) - .dispatch(origin_of(account_of(ALICE)))); - }); + }); + + // sanity check: this call is allowed by default + assert_ok!(call.clone().dispatch(origin_of(account_of(ALICE)))); + + // tx-pause pallet itself is unpausable.. + assert_noop!( + RuntimeCall::TxPause(TxPauseCall::pause { + full_name: full_name(b"TxPause", None) + }) + .dispatch(root()), + tx_pause::Error::::IsUnpausable + ); + let pausable_pallets = RuntimeCall::get_module_names() + .into_iter() + .filter(|&&pallet| pallet != "TxPause"); + for pallet in pausable_pallets { + assert_ok!(RuntimeCall::TxPause(TxPauseCall::pause { + full_name: full_name(pallet.as_bytes(), None) + }) + .dispatch(root())); + } + + // sanity check: this call is now paused + assert_noop!( + call.clone().dispatch(origin_of(account_of(ALICE))), + SystemError::CallFiltered + ); + + create_proposal( + RuntimeCall::TxPause(TxPauseCall::unpause { + full_name: full_name(b"Tokens", None), + }) + .encode(), + ); + launch_and_execute_referendum(); + + // verify that call is indeed unpaused + assert_ok!(call.clone().dispatch(origin_of(account_of(ALICE)))); + }) } #[test] @@ -857,7 +935,7 @@ fn test_sudo_is_disabled_if_key_is_none() { // first a sanity check: sudo works if key is set assert_ok!(RuntimeCall::Sudo(SudoCall::sudo { call: Box::new(RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Shutdown, + status_code: StatusCode::Error, })), }) .dispatch(origin_of(account_of(ALICE))),); @@ -871,7 +949,7 @@ fn test_sudo_is_disabled_if_key_is_none() { assert_noop!( RuntimeCall::Sudo(SudoCall::sudo { call: Box::new(RuntimeCall::Security(SecurityCall::set_parachain_status { - status_code: StatusCode::Shutdown, + status_code: StatusCode::Error, })), }) .dispatch(origin_of(account_of(ALICE))), diff --git a/standalone/runtime/tests/test_issue.rs b/standalone/runtime/tests/test_issue.rs index 6c13992b60..2a346a7498 100644 --- a/standalone/runtime/tests/test_issue.rs +++ b/standalone/runtime/tests/test_issue.rs @@ -149,22 +149,6 @@ mod request_issue_tests { use super::{assert_eq, *}; - /// Request fails if parachain is shutdown - #[test] - fn integration_test_issue_request_precond_not_shutdown() { - test_with(|vault_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - assert_noop!( - RuntimeCall::Issue(IssueCall::request_issue { - amount: 0, - vault_id: vault_id, - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - }); - } - /// Request fails if relay is not initialized #[test] fn integration_test_issue_request_precond_relay_initialized() { @@ -574,23 +558,6 @@ fn integration_test_withdraw_after_request_issue() { mod execute_pending_issue_tests { use super::{assert_eq, *}; - /// Execute fails if parachain is shut down - #[test] - fn integration_test_issue_execute_precond_not_shutdown() { - test_with(|_currency_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::Issue(IssueCall::execute_issue { - issue_id: Default::default(), - merkle_proof: Default::default(), - raw_tx: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - }); - } /// Execute fails if corresponding request doesn't exist #[test] @@ -998,21 +965,6 @@ mod execute_cancelled_issue_tests { mod cancel_issue_tests { use super::{assert_eq, *}; - /// Cancel fails when parachain is shutdown - #[test] - fn integration_test_issue_cancel_precond_not_shutdown() { - test_with(|_currency_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - assert_noop!( - RuntimeCall::Issue(IssueCall::cancel_issue { - issue_id: H256([0; 32]), - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - }); - } - /// Cancel fails if issue request does not exist #[test] fn integration_test_issue_cancel_precond_issue_exists() { diff --git a/standalone/runtime/tests/test_nomination.rs b/standalone/runtime/tests/test_nomination.rs index 6d0b2d862b..314f71b64a 100644 --- a/standalone/runtime/tests/test_nomination.rs +++ b/standalone/runtime/tests/test_nomination.rs @@ -120,7 +120,6 @@ mod spec_based_tests { fn integration_test_nomination_with_parachain_shutdown_status_fails() { // Checked PRECONDITION: The BTC Parachain status in the Security component be `RUNNING:0`. test_with(|_| { - // nomination_with_non_running_status_fails(StatusCode::Shutdown); nomination_with_non_running_status_fails(StatusCode::Error); }); } diff --git a/standalone/runtime/tests/test_oracle.rs b/standalone/runtime/tests/test_oracle.rs index d760d53529..ce83224298 100644 --- a/standalone/runtime/tests/test_oracle.rs +++ b/standalone/runtime/tests/test_oracle.rs @@ -1,14 +1,2 @@ mod mock; use mock::{assert_eq, *}; - -#[test] -fn integration_test_oracle_with_parachain_shutdown_fails() { - ExtBuilder::build().execute_with(|| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::Oracle(OracleCall::feed_values { values: vec![] }).dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered - ); - }) -} diff --git a/standalone/runtime/tests/test_redeem.rs b/standalone/runtime/tests/test_redeem.rs index 20257ec4f7..fc6c8f0cbf 100644 --- a/standalone/runtime/tests/test_redeem.rs +++ b/standalone/runtime/tests/test_redeem.rs @@ -72,74 +72,13 @@ mod spec_based_tests { use primitives::VaultCurrencyPair; use super::{assert_eq, *}; - #[test] - fn integration_test_redeem_with_parachain_shutdown_status_fails() { - // PRECONDITION: The BTC Parachain status in the Security component - test_with(|vault_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - assert_noop!( - RuntimeCall::Redeem(RedeemCall::request_redeem { - amount_wrapped: 1500, - btc_address: BtcAddress::random(), - vault_id: vault_id.clone(), - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Redeem(RedeemCall::execute_redeem { - redeem_id: Default::default(), - merkle_proof: Default::default(), - raw_tx: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Redeem(RedeemCall::cancel_redeem { - redeem_id: Default::default(), - reimburse: false - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - assert_noop!( - RuntimeCall::Redeem(RedeemCall::cancel_redeem { - redeem_id: Default::default(), - reimburse: true - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Redeem(RedeemCall::liquidation_redeem { - currencies: vault_id.currencies.clone(), - amount_wrapped: 1000 - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Redeem(RedeemCall::mint_tokens_for_reimbursed_redeem { - currency_pair: vault_id.currencies.clone(), - redeem_id: Default::default() - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - }); - } #[test] fn integration_test_redeem_with_parachain_error_status_fails() { // PRECONDITION: The BTC Parachain status in the Security component test_with(|vault_id| { // `liquidation_redeem` and `execute_redeem` are not tested here - // because they only require the parachain status not to be `Shutdown` + // because they are allowed even in error SecurityPallet::set_status(StatusCode::Error); assert_noop!( @@ -1221,40 +1160,6 @@ mod spec_based_tests { } } -#[test] -fn integration_test_redeem_parachain_status_shutdown_fails() { - test_with(|vault_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::Issue(IssueCall::request_issue { - amount: 0, - vault_id: vault_id.clone(), - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Issue(IssueCall::cancel_issue { - issue_id: H256([0; 32]), - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - - assert_noop!( - RuntimeCall::Issue(IssueCall::execute_issue { - issue_id: H256([0; 32]), - merkle_proof: vec![0u8; 32], - raw_tx: vec![0u8; 32] - }) - .dispatch(origin_of(account_of(ALICE))), - SystemError::CallFiltered, - ); - }); -} - mod execute_redeem_payment_limits { use super::{assert_eq, *}; diff --git a/standalone/runtime/tests/test_replace.rs b/standalone/runtime/tests/test_replace.rs index 3fa6771036..c887acacff 100644 --- a/standalone/runtime/tests/test_replace.rs +++ b/standalone/runtime/tests/test_replace.rs @@ -310,27 +310,7 @@ mod accept_replace_tests { } mod request_replace_tests { - use primitives::VaultCurrencyPair; - use super::{assert_eq, *}; - #[test] - fn integration_test_replace_should_fail_if_not_running() { - test_without_initialization(|_currency_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::Replace(ReplaceCall::request_replace { - currency_pair: VaultCurrencyPair { - collateral: Token(DOT), - wrapped: Token(DOT), - }, - amount: 0, - }) - .dispatch(origin_of(account_of(OLD_VAULT))), - SystemError::CallFiltered, - ); - }); - } #[test] fn integration_test_replace_request_replace_at_capacity_succeeds() { @@ -698,54 +678,6 @@ mod execute_replace_payment_limits { } } -#[test] -fn integration_test_replace_with_parachain_shutdown_fails() { - test_with(|old_vault_id, new_vault_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::Replace(ReplaceCall::request_replace { - currency_pair: old_vault_id.currencies.clone(), - amount: old_vault_id.wrapped(0).amount(), - }) - .dispatch(origin_of(old_vault_id.account_id.clone())), - SystemError::CallFiltered, - ); - assert_noop!( - withdraw_replace(&old_vault_id, old_vault_id.wrapped(0)), - SystemError::CallFiltered - ); - assert_noop!( - accept_replace( - &old_vault_id, - &new_vault_id, - old_vault_id.wrapped(0), - griefing(0), - Default::default() - ), - SystemError::CallFiltered - ); - - assert_noop!( - RuntimeCall::Replace(ReplaceCall::execute_replace { - replace_id: Default::default(), - merkle_proof: Default::default(), - raw_tx: Default::default() - }) - .dispatch(origin_of(account_of(OLD_VAULT))), - SystemError::CallFiltered - ); - - assert_noop!( - RuntimeCall::Replace(ReplaceCall::cancel_replace { - replace_id: Default::default() - }) - .dispatch(origin_of(account_of(OLD_VAULT))), - SystemError::CallFiltered - ); - }) -} - #[test] fn integration_test_replace_cancel_replace() { test_with(|old_vault_id, new_vault_id| { diff --git a/standalone/runtime/tests/test_vault_registry.rs b/standalone/runtime/tests/test_vault_registry.rs index d8978afa19..ad8e18f574 100644 --- a/standalone/runtime/tests/test_vault_registry.rs +++ b/standalone/runtime/tests/test_vault_registry.rs @@ -304,54 +304,6 @@ mod withdraw_collateral_test { } } -#[test] -fn integration_test_vault_registry_with_parachain_shutdown_fails() { - test_with(|vault_id| { - SecurityPallet::set_status(StatusCode::Shutdown); - - assert_noop!( - RuntimeCall::VaultRegistry(VaultRegistryCall::register_vault { - currency_pair: vault_id.currencies.clone(), - collateral: 0, - }) - .dispatch(origin_of(account_of(VAULT))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::Nomination(NominationCall::deposit_collateral { - vault_id: vault_id.clone(), - amount: 0 - }) - .dispatch(origin_of(account_of(VAULT))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::Nomination(NominationCall::withdraw_collateral { - vault_id: vault_id.clone(), - index: None, - amount: 0 - }) - .dispatch(origin_of(account_of(VAULT))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::VaultRegistry(VaultRegistryCall::register_public_key { - public_key: Default::default() - }) - .dispatch(origin_of(account_of(VAULT))), - SystemError::CallFiltered - ); - assert_noop!( - RuntimeCall::VaultRegistry(VaultRegistryCall::accept_new_issues { - currency_pair: vault_id.currencies.clone(), - accept_new_issues: false - }) - .dispatch(origin_of(account_of(VAULT))), - SystemError::CallFiltered - ); - }); -} - #[test] fn integration_test_vault_registry_undercollateralization_liquidation() { test_with(|vault_id| { diff --git a/standalone/src/chain_spec.rs b/standalone/src/chain_spec.rs index f8bf473fc2..5b68d24cc0 100644 --- a/standalone/src/chain_spec.rs +++ b/standalone/src/chain_spec.rs @@ -91,7 +91,6 @@ pub fn local_config() -> ChainSpec { "Bob".as_bytes().to_vec(), )], 0, - false, ) }, vec![], @@ -150,7 +149,6 @@ pub fn beta_testnet_config() -> ChainSpec { "Interlay".as_bytes().to_vec(), )], 1, - false, ) }, Vec::new(), @@ -200,7 +198,6 @@ pub fn development_config() -> ChainSpec { ), ], 1, - false, ) }, Vec::new(), @@ -240,7 +237,6 @@ fn testnet_genesis( endowed_accounts: Vec, authorized_oracles: Vec<(AccountId, Vec)>, bitcoin_confirmations: u32, - start_shutdown: bool, ) -> GenesisConfig { GenesisConfig { system: SystemConfig { @@ -255,11 +251,7 @@ fn testnet_genesis( authorities: initial_authorities.iter().map(|x| (x.1.clone(), 1)).collect(), }, security: SecurityConfig { - initial_status: if start_shutdown { - StatusCode::Shutdown - } else { - StatusCode::Error - }, + initial_status: StatusCode::Error, }, sudo: SudoConfig { // Assign network admin rights.