1
// This file is part of Substrate.
2

            
3
// Copyright (C) Parity Technologies (UK) Ltd.
4
// SPDX-License-Identifier: Apache-2.0
5

            
6
// 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.
17

            
18
//! # Transaction Pause
19
//!
20
//! Allows dynamic, chain-state-based pausing and unpausing of specific extrinsics via call filters.
21
//!
22
//! ## WARNING
23
//!
24
//! NOT YET AUDITED. DO NOT USE IN PRODUCTION.
25
//!
26
//! ## Pallet API
27
//!
28
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
29
//! including its configuration trait, dispatchables, storage items, events, and errors.
30
//!
31
//! ## Overview
32
//!
33
//! A dynamic call filter that can be controlled with extrinsics.
34
//!
35
//! Pausing an extrinsic means that the extrinsic CANNOT be called again until it is unpaused.
36
//! The exception is calls that use `dispatch_bypass_filter`, typically only with the root origin.
37
//!
38
//! ### Primary Features
39
//!
40
//! - Calls that should never be paused can be added to a whitelist.
41
//! - Separate origins are configurable for pausing and pausing.
42
//! - Pausing is triggered using the string representation of the call.
43
//! - Pauses can target a single extrinsic or an entire pallet.
44
//! - Pauses can target future extrinsics or pallets.
45
//!
46
//! ### Example
47
//!
48
//! Configuration of call filters:
49
//!
50
//! ```ignore
51
//! impl frame_system::Config for Runtime {
52
//!   // …
53
//!   type BaseCallFilter = InsideBoth<DefaultFilter, TxPause>;
54
//!   // …
55
//! }
56
//! ```
57
//!
58
//! Pause specific all:
59
#![doc = docify::embed!("src/tests.rs", can_pause_specific_call)]
60
//!
61
//! Unpause specific all:
62
#![doc = docify::embed!("src/tests.rs", can_unpause_specific_call)]
63
//!
64
//! Pause all calls in a pallet:
65
#![doc = docify::embed!("src/tests.rs", can_pause_all_calls_in_pallet_except_on_whitelist)]
66
//!
67
//! ## Low Level / Implementation Details
68
//!
69
//! ### Use Cost
70
//!
71
//! A storage map (`PausedCalls`) is used to store currently paused calls.
72
//! Using the call filter will require a db read of that storage on each extrinsic.
73

            
74
#![cfg_attr(not(feature = "std"), no_std)]
75
#![deny(rustdoc::broken_intra_doc_links)]
76

            
77
mod benchmarking;
78
pub mod mock;
79
mod tests;
80
pub mod weights;
81

            
82
extern crate alloc;
83

            
84
use alloc::vec::Vec;
85
use frame_support::{
86
	dispatch::GetDispatchInfo,
87
	pallet_prelude::*,
88
	traits::{CallMetadata, Contains, GetCallMetadata, IsSubType, IsType},
89
	DefaultNoBound,
90
};
91
use frame_system::pallet_prelude::*;
92
use sp_runtime::{traits::Dispatchable, DispatchResult};
93

            
94
pub use pallet::*;
95
pub use weights::*;
96

            
97
/// The stringy name of a pallet from [`GetCallMetadata`] for [`Config::RuntimeCall`] variants.
98
pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
99

            
100
/// The stringy name of a call (within a pallet) from [`GetCallMetadata`] for
101
/// [`Config::RuntimeCall`] variants.
102
pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
103

            
104
/// A fully specified pallet ([`PalletNameOf`]) and optional call ([`PalletCallNameOf`])
105
/// to partially or fully specify an item a variant of a  [`Config::RuntimeCall`].
106
pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
107

            
108
#[frame_support::pallet]
109
pub mod pallet {
110
	use super::*;
111

            
112
	#[pallet::pallet]
113
	pub struct Pallet<T>(PhantomData<T>);
114

            
115
	#[pallet::config]
116
	pub trait Config: frame_system::Config {
117
		/// The overarching event type.
118
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
119

            
120
		/// The overarching call type.
121
		type RuntimeCall: Parameter
122
			+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
123
			+ GetDispatchInfo
124
			+ GetCallMetadata
125
			+ From<frame_system::Call<Self>>
126
			+ IsSubType<Call<Self>>
127
			+ IsType<<Self as frame_system::Config>::RuntimeCall>;
128

            
129
		/// The only origin that can pause calls.
130
		type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
131

            
132
		/// The only origin that can un-pause calls.
133
		type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
134

            
135
		/// Contains all calls that cannot be paused.
136
		///
137
		/// The `TxMode` pallet cannot pause its own calls, and does not need to be explicitly
138
		/// added here.
139
		type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
140

            
141
		/// Maximum length for pallet name and call name SCALE encoded string names.
142
		///
143
		/// TOO LONG NAMES WILL BE TREATED AS PAUSED.
144
		#[pallet::constant]
145
		type MaxNameLen: Get<u32>;
146

            
147
		// Weight information for extrinsics in this pallet.
148
		type WeightInfo: WeightInfo;
149
	}
150

            
151
	/// The set of calls that are explicitly paused.
152
	#[pallet::storage]
153
	pub type PausedCalls<T: Config> =
154
		StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
155

            
156
	#[pallet::error]
157
	pub enum Error<T> {
158
		/// The call is paused.
159
		IsPaused,
160

            
161
		/// The call is unpaused.
162
		IsUnpaused,
163

            
164
		/// The call is whitelisted and cannot be paused.
165
		Unpausable,
166

            
167
		// The pallet or call does not exist in the runtime.
168
		NotFound,
169
	}
170

            
171
	#[pallet::event]
172
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
173
	pub enum Event<T: Config> {
174
		/// This pallet, or a specific call is now paused.
175
		CallPaused { full_name: RuntimeCallNameOf<T> },
176
		/// This pallet, or a specific call is now unpaused.
177
		CallUnpaused { full_name: RuntimeCallNameOf<T> },
178
	}
179

            
180
	/// Configure the initial state of this pallet in the genesis block.
181
	#[pallet::genesis_config]
182
	#[derive(DefaultNoBound)]
183
	pub struct GenesisConfig<T: Config> {
184
		/// Initially paused calls.
185
		pub paused: Vec<RuntimeCallNameOf<T>>,
186
	}
187

            
188
	#[pallet::genesis_build]
189
	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
190
		fn build(&self) {
191
			for call in &self.paused {
192
				Pallet::<T>::ensure_can_pause(&call).expect("Genesis data is known good; qed");
193
				PausedCalls::<T>::insert(&call, ());
194
			}
195
		}
196
	}
197

            
198
	#[pallet::call]
199
	impl<T: Config> Pallet<T> {
200
		/// Pause a call.
201
		///
202
		/// Can only be called by [`Config::PauseOrigin`].
203
		/// Emits an [`Event::CallPaused`] event on success.
204
		#[pallet::call_index(0)]
205
		#[pallet::weight(T::WeightInfo::pause())]
206
		pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
207
			T::PauseOrigin::ensure_origin(origin)?;
208

            
209
			Self::do_pause(full_name).map_err(Into::into)
210
		}
211

            
212
		/// Un-pause a call.
213
		///
214
		/// Can only be called by [`Config::UnpauseOrigin`].
215
		/// Emits an [`Event::CallUnpaused`] event on success.
216
		#[pallet::call_index(1)]
217
		#[pallet::weight(T::WeightInfo::unpause())]
218
		pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
219
			T::UnpauseOrigin::ensure_origin(origin)?;
220

            
221
			Self::do_unpause(ident).map_err(Into::into)
222
		}
223
	}
224
}
225

            
226
impl<T: Config> Pallet<T> {
227
	pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
228
		Self::ensure_can_pause(&ident)?;
229
		PausedCalls::<T>::insert(&ident, ());
230
		Self::deposit_event(Event::CallPaused { full_name: ident });
231

            
232
		Ok(())
233
	}
234

            
235
	pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
236
		Self::ensure_can_unpause(&ident)?;
237
		PausedCalls::<T>::remove(&ident);
238
		Self::deposit_event(Event::CallUnpaused { full_name: ident });
239

            
240
		Ok(())
241
	}
242

            
243
	/// Return whether this call is paused.
244
	pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
245
		if T::WhitelistedCalls::contains(full_name) {
246
			return false
247
		}
248

            
249
		<PausedCalls<T>>::contains_key(full_name)
250
	}
251

            
252
	/// Same as [`Self::is_paused`] but for inputs unbound by max-encoded-len.
253
	pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
254
		let pallet = PalletNameOf::<T>::try_from(pallet);
255
		let call = PalletCallNameOf::<T>::try_from(call);
256

            
257
		match (pallet, call) {
258
			(Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
259
			_ => true,
260
		}
261
	}
262

            
263
	/// Ensure that this call can be paused.
264
	pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
265
		// SAFETY: The `TxPause` pallet can never pause itself.
266
		if full_name.0.as_slice() == <Self as PalletInfoAccess>::name().as_bytes() {
267
			return Err(Error::<T>::Unpausable)
268
		}
269

            
270
		if T::WhitelistedCalls::contains(&full_name) {
271
			return Err(Error::<T>::Unpausable)
272
		}
273
		if Self::is_paused(&full_name) {
274
			return Err(Error::<T>::IsPaused)
275
		}
276
		Ok(())
277
	}
278

            
279
	/// Ensure that this call can be un-paused.
280
	pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
281
		if Self::is_paused(&full_name) {
282
			// SAFETY: Everything that is paused, can be un-paused.
283
			Ok(())
284
		} else {
285
			Err(Error::IsUnpaused)
286
		}
287
	}
288
}
289

            
290
impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
291
where
292
	<T as frame_system::Config>::RuntimeCall: GetCallMetadata,
293
{
294
	/// Return whether the call is allowed to be dispatched.
295
	fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
296
		let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
297
		!Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
298
	}
299
}
300

            
301
impl<T: Config> frame_support::traits::TransactionPause for Pallet<T> {
302
	type CallIdentifier = RuntimeCallNameOf<T>;
303

            
304
	fn is_paused(full_name: Self::CallIdentifier) -> bool {
305
		Self::is_paused(&full_name)
306
	}
307

            
308
	fn can_pause(full_name: Self::CallIdentifier) -> bool {
309
		Self::ensure_can_pause(&full_name).is_ok()
310
	}
311

            
312
	fn pause(
313
		full_name: Self::CallIdentifier,
314
	) -> Result<(), frame_support::traits::TransactionPauseError> {
315
		Self::do_pause(full_name).map_err(Into::into)
316
	}
317

            
318
	fn unpause(
319
		full_name: Self::CallIdentifier,
320
	) -> Result<(), frame_support::traits::TransactionPauseError> {
321
		Self::do_unpause(full_name).map_err(Into::into)
322
	}
323
}
324

            
325
impl<T: Config> From<Error<T>> for frame_support::traits::TransactionPauseError {
326
	fn from(err: Error<T>) -> Self {
327
		match err {
328
			Error::<T>::NotFound => Self::NotFound,
329
			Error::<T>::Unpausable => Self::Unpausable,
330
			Error::<T>::IsPaused => Self::AlreadyPaused,
331
			Error::<T>::IsUnpaused => Self::AlreadyUnpaused,
332
			_ => Self::Unknown,
333
		}
334
	}
335
}