1// This file is part of Substrate.
23// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
56// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
1718//! # Transaction Pause
19//!
20//! Allows dynamic, chain-state-based pausing and unpausing of specific extrinsics via call filters.
21//!
22//! ## Pallet API
23//!
24//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
25//! including its configuration trait, dispatchables, storage items, events, and errors.
26//!
27//! ## Overview
28//!
29//! A dynamic call filter that can be controlled with extrinsics.
30//!
31//! Pausing an extrinsic means that the extrinsic CANNOT be called again until it is unpaused.
32//! The exception is calls that use `dispatch_bypass_filter`, typically only with the root origin.
33//!
34//! ### Primary Features
35//!
36//! - Calls that should never be paused can be added to a whitelist.
37//! - Separate origins are configurable for pausing and pausing.
38//! - Pausing is triggered using the string representation of the call.
39//! - Pauses can target a single extrinsic or an entire pallet.
40//! - Pauses can target future extrinsics or pallets.
41//!
42//! ### Example
43//!
44//! Configuration of call filters:
45//!
46//! ```ignore
47//! impl frame_system::Config for Runtime {
48//! // …
49//! type BaseCallFilter = InsideBoth<DefaultFilter, TxPause>;
50//! // …
51//! }
52//! ```
53//!
54//! Pause specific all:
55#![doc = docify::embed!("src/tests.rs", can_pause_specific_call)]
56//!
57//! Unpause specific all:
58#![doc = docify::embed!("src/tests.rs", can_unpause_specific_call)]
59//!
60//! Pause all calls in a pallet:
61#![doc = docify::embed!("src/tests.rs", can_pause_all_calls_in_pallet_except_on_whitelist)]
62//!
63//! ## Low Level / Implementation Details
64//!
65//! ### Use Cost
66//!
67//! A storage map (`PausedCalls`) is used to store currently paused calls.
68//! Using the call filter will require a db read of that storage on each extrinsic.
6970#![cfg_attr(not(feature = "std"), no_std)]
71#![deny(rustdoc::broken_intra_doc_links)]
7273mod benchmarking;
74pub mod mock;
75mod tests;
76pub mod weights;
7778extern crate alloc;
7980use alloc::vec::Vec;
81use frame::{
82 prelude::*,
83 traits::{TransactionPause, TransactionPauseError},
84};
85pub use pallet::*;
86pub use weights::*;
8788/// The stringy name of a pallet from [`GetCallMetadata`] for [`Config::RuntimeCall`] variants.
89pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
9091/// The stringy name of a call (within a pallet) from [`GetCallMetadata`] for
92/// [`Config::RuntimeCall`] variants.
93pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
9495/// A fully specified pallet ([`PalletNameOf`]) and optional call ([`PalletCallNameOf`])
96/// to partially or fully specify an item a variant of a [`Config::RuntimeCall`].
97pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
9899#[frame::pallet]
100pub mod pallet {
101use super::*;
102103#[pallet::pallet]
104pub struct Pallet<T>(PhantomData<T>);
105106#[pallet::config]
107pub trait Config: frame_system::Config {
108/// The overarching event type.
109#[allow(deprecated)]
110type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
111112/// The overarching call type.
113type RuntimeCall: Parameter
114 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
115 + GetDispatchInfo
116 + GetCallMetadata
117 + From<frame_system::Call<Self>>
118 + IsSubType<Call<Self>>
119 + IsType<<Self as frame_system::Config>::RuntimeCall>;
120121/// The only origin that can pause calls.
122type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
123124/// The only origin that can un-pause calls.
125type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
126127/// Contains all calls that cannot be paused.
128 ///
129 /// The `TxMode` pallet cannot pause its own calls, and does not need to be explicitly
130 /// added here.
131type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
132133/// Maximum length for pallet name and call name SCALE encoded string names.
134 ///
135 /// TOO LONG NAMES WILL BE TREATED AS PAUSED.
136#[pallet::constant]
137type MaxNameLen: Get<u32>;
138139// Weight information for extrinsics in this pallet.
140type WeightInfo: WeightInfo;
141 }
142143/// The set of calls that are explicitly paused.
144#[pallet::storage]
145pub type PausedCalls<T: Config> =
146 StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
147148#[pallet::error]
149pub enum Error<T> {
150/// The call is paused.
151IsPaused,
152153/// The call is unpaused.
154IsUnpaused,
155156/// The call is whitelisted and cannot be paused.
157Unpausable,
158159// The pallet or call does not exist in the runtime.
160NotFound,
161 }
162163#[pallet::event]
164 #[pallet::generate_deposit(pub(super) fn deposit_event)]
165pub enum Event<T: Config> {
166/// This pallet, or a specific call is now paused.
167CallPaused { full_name: RuntimeCallNameOf<T> },
168/// This pallet, or a specific call is now unpaused.
169CallUnpaused { full_name: RuntimeCallNameOf<T> },
170 }
171172/// Configure the initial state of this pallet in the genesis block.
173#[pallet::genesis_config]
174 #[derive(DefaultNoBound)]
175pub struct GenesisConfig<T: Config> {
176/// Initially paused calls.
177pub paused: Vec<RuntimeCallNameOf<T>>,
178 }
179180#[pallet::genesis_build]
181impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
182fn build(&self) {
183for call in &self.paused {
184 Pallet::<T>::ensure_can_pause(&call).expect("Genesis data is known good; qed");
185 PausedCalls::<T>::insert(&call, ());
186 }
187 }
188 }
189190#[pallet::call]
191impl<T: Config> Pallet<T> {
192/// Pause a call.
193 ///
194 /// Can only be called by [`Config::PauseOrigin`].
195 /// Emits an [`Event::CallPaused`] event on success.
196#[pallet::call_index(0)]
197 #[pallet::weight(T::WeightInfo::pause())]
198pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
199 T::PauseOrigin::ensure_origin(origin)?;
200201Self::do_pause(full_name).map_err(Into::into)
202 }
203204/// Un-pause a call.
205 ///
206 /// Can only be called by [`Config::UnpauseOrigin`].
207 /// Emits an [`Event::CallUnpaused`] event on success.
208#[pallet::call_index(1)]
209 #[pallet::weight(T::WeightInfo::unpause())]
210pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
211 T::UnpauseOrigin::ensure_origin(origin)?;
212213Self::do_unpause(ident).map_err(Into::into)
214 }
215 }
216}
217218impl<T: Config> Pallet<T> {
219pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
220Self::ensure_can_pause(&ident)?;
221 PausedCalls::<T>::insert(&ident, ());
222Self::deposit_event(Event::CallPaused { full_name: ident });
223224Ok(())
225 }
226227pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
228Self::ensure_can_unpause(&ident)?;
229 PausedCalls::<T>::remove(&ident);
230Self::deposit_event(Event::CallUnpaused { full_name: ident });
231232Ok(())
233 }
234235/// Return whether this call is paused.
236pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
237if T::WhitelistedCalls::contains(full_name) {
238return false
239}
240241 <PausedCalls<T>>::contains_key(full_name)
242 }
243244/// Same as [`Self::is_paused`] but for inputs unbound by max-encoded-len.
245pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
246let pallet = PalletNameOf::<T>::try_from(pallet);
247let call = PalletCallNameOf::<T>::try_from(call);
248249match (pallet, call) {
250 (Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
251_ => true,
252 }
253 }
254255/// Ensure that this call can be paused.
256pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
257// SAFETY: The `TxPause` pallet can never pause itself.
258if full_name.0.as_slice() == <Self as PalletInfoAccess>::name().as_bytes() {
259return Err(Error::<T>::Unpausable)
260 }
261262if T::WhitelistedCalls::contains(&full_name) {
263return Err(Error::<T>::Unpausable)
264 }
265if Self::is_paused(&full_name) {
266return Err(Error::<T>::IsPaused)
267 }
268Ok(())
269 }
270271/// Ensure that this call can be un-paused.
272pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
273if Self::is_paused(&full_name) {
274// SAFETY: Everything that is paused, can be un-paused.
275Ok(())
276 } else {
277Err(Error::IsUnpaused)
278 }
279 }
280}
281282impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
283where
284<T as frame_system::Config>::RuntimeCall: GetCallMetadata,
285{
286/// Return whether the call is allowed to be dispatched.
287fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
288let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
289 !Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
290 }
291}
292293impl<T: Config> TransactionPause for Pallet<T> {
294type CallIdentifier = RuntimeCallNameOf<T>;
295296fn is_paused(full_name: Self::CallIdentifier) -> bool {
297Self::is_paused(&full_name)
298 }
299300fn can_pause(full_name: Self::CallIdentifier) -> bool {
301Self::ensure_can_pause(&full_name).is_ok()
302 }
303304fn pause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
305Self::do_pause(full_name).map_err(Into::into)
306 }
307308fn unpause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
309Self::do_unpause(full_name).map_err(Into::into)
310 }
311}
312313impl<T: Config> From<Error<T>> for TransactionPauseError {
314fn from(err: Error<T>) -> Self {
315match err {
316 Error::<T>::NotFound => Self::NotFound,
317 Error::<T>::Unpausable => Self::Unpausable,
318 Error::<T>::IsPaused => Self::AlreadyPaused,
319 Error::<T>::IsUnpaused => Self::AlreadyUnpaused,
320_ => Self::Unknown,
321 }
322 }
323}