1#![doc = docify::embed!("src/tests.rs", can_pause_specific_call)]
56#![doc = docify::embed!("src/tests.rs", can_unpause_specific_call)]
58#![doc = docify::embed!("src/tests.rs", can_pause_all_calls_in_pallet_except_on_whitelist)]
60#![cfg_attr(not(feature = "std"), no_std)]
68#![deny(rustdoc::broken_intra_doc_links)]
69
70mod benchmarking;
71pub mod mock;
72mod tests;
73pub mod weights;
74
75extern crate alloc;
76
77use alloc::vec::Vec;
78use frame::{
79 prelude::*,
80 traits::{TransactionPause, TransactionPauseError},
81};
82pub use pallet::*;
83pub use weights::*;
84
85pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
87
88pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
91
92pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
95
96#[frame::pallet]
97pub mod pallet {
98 use super::*;
99
100 #[pallet::pallet]
101 pub struct Pallet<T>(PhantomData<T>);
102
103 #[pallet::config]
104 pub trait Config: frame_system::Config {
105 #[allow(deprecated)]
107 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
108
109 type RuntimeCall: Parameter
111 + Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
112 + GetDispatchInfo
113 + GetCallMetadata
114 + From<frame_system::Call<Self>>
115 + IsSubType<Call<Self>>
116 + IsType<<Self as frame_system::Config>::RuntimeCall>;
117
118 type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
120
121 type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
123
124 type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
129
130 #[pallet::constant]
134 type MaxNameLen: Get<u32>;
135
136 type WeightInfo: WeightInfo;
138 }
139
140 #[pallet::storage]
142 pub type PausedCalls<T: Config> =
143 StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
144
145 #[pallet::error]
146 pub enum Error<T> {
147 IsPaused,
149
150 IsUnpaused,
152
153 Unpausable,
155
156 NotFound,
158 }
159
160 #[pallet::event]
161 #[pallet::generate_deposit(pub(super) fn deposit_event)]
162 pub enum Event<T: Config> {
163 CallPaused { full_name: RuntimeCallNameOf<T> },
165 CallUnpaused { full_name: RuntimeCallNameOf<T> },
167 }
168
169 #[pallet::genesis_config]
171 #[derive(DefaultNoBound)]
172 pub struct GenesisConfig<T: Config> {
173 pub paused: Vec<RuntimeCallNameOf<T>>,
175 }
176
177 #[pallet::genesis_build]
178 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
179 fn build(&self) {
180 for call in &self.paused {
181 Pallet::<T>::ensure_can_pause(&call).expect("Genesis data is known good; qed");
182 PausedCalls::<T>::insert(&call, ());
183 }
184 }
185 }
186
187 #[pallet::call]
188 impl<T: Config> Pallet<T> {
189 #[pallet::call_index(0)]
194 #[pallet::weight(T::WeightInfo::pause())]
195 pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
196 T::PauseOrigin::ensure_origin(origin)?;
197
198 Self::do_pause(full_name).map_err(Into::into)
199 }
200
201 #[pallet::call_index(1)]
206 #[pallet::weight(T::WeightInfo::unpause())]
207 pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
208 T::UnpauseOrigin::ensure_origin(origin)?;
209
210 Self::do_unpause(ident).map_err(Into::into)
211 }
212 }
213}
214
215impl<T: Config> Pallet<T> {
216 pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
217 Self::ensure_can_pause(&ident)?;
218 PausedCalls::<T>::insert(&ident, ());
219 Self::deposit_event(Event::CallPaused { full_name: ident });
220
221 Ok(())
222 }
223
224 pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
225 Self::ensure_can_unpause(&ident)?;
226 PausedCalls::<T>::remove(&ident);
227 Self::deposit_event(Event::CallUnpaused { full_name: ident });
228
229 Ok(())
230 }
231
232 pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
234 if T::WhitelistedCalls::contains(full_name) {
235 return false;
236 }
237
238 <PausedCalls<T>>::contains_key(full_name)
239 }
240
241 pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
243 let pallet = PalletNameOf::<T>::try_from(pallet);
244 let call = PalletCallNameOf::<T>::try_from(call);
245
246 match (pallet, call) {
247 (Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
248 _ => true,
249 }
250 }
251
252 pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
254 if full_name.0.as_slice() == <Self as PalletInfoAccess>::name().as_bytes() {
256 return Err(Error::<T>::Unpausable);
257 }
258
259 if T::WhitelistedCalls::contains(&full_name) {
260 return Err(Error::<T>::Unpausable);
261 }
262 if Self::is_paused(&full_name) {
263 return Err(Error::<T>::IsPaused);
264 }
265 Ok(())
266 }
267
268 pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
270 if Self::is_paused(&full_name) {
271 Ok(())
273 } else {
274 Err(Error::IsUnpaused)
275 }
276 }
277}
278
279impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
280where
281 <T as frame_system::Config>::RuntimeCall: GetCallMetadata,
282{
283 fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
285 let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
286 !Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
287 }
288}
289
290impl<T: Config> TransactionPause for Pallet<T> {
291 type CallIdentifier = RuntimeCallNameOf<T>;
292
293 fn is_paused(full_name: Self::CallIdentifier) -> bool {
294 Self::is_paused(&full_name)
295 }
296
297 fn can_pause(full_name: Self::CallIdentifier) -> bool {
298 Self::ensure_can_pause(&full_name).is_ok()
299 }
300
301 fn pause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
302 Self::do_pause(full_name).map_err(Into::into)
303 }
304
305 fn unpause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
306 Self::do_unpause(full_name).map_err(Into::into)
307 }
308}
309
310impl<T: Config> From<Error<T>> for TransactionPauseError {
311 fn from(err: Error<T>) -> Self {
312 match err {
313 Error::<T>::NotFound => Self::NotFound,
314 Error::<T>::Unpausable => Self::Unpausable,
315 Error::<T>::IsPaused => Self::AlreadyPaused,
316 Error::<T>::IsUnpaused => Self::AlreadyUnpaused,
317 _ => Self::Unknown,
318 }
319 }
320}