1#![doc = docify::embed!("src/tests.rs", can_pause_specific_call)]
56#![doc = docify::embed!("src/tests.rs", can_unpause_specific_call)]
59#![doc = docify::embed!("src/tests.rs", can_pause_all_calls_in_pallet_except_on_whitelist)]
62#![cfg_attr(not(feature = "std"), no_std)]
71#![deny(rustdoc::broken_intra_doc_links)]
72
73mod benchmarking;
74pub mod mock;
75mod tests;
76pub mod weights;
77
78extern crate alloc;
79
80use alloc::vec::Vec;
81use frame::{
82 prelude::*,
83 traits::{TransactionPause, TransactionPauseError},
84};
85pub use pallet::*;
86pub use weights::*;
87
88pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
90
91pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
94
95pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
98
99#[frame::pallet]
100pub mod pallet {
101 use super::*;
102
103 #[pallet::pallet]
104 pub struct Pallet<T>(PhantomData<T>);
105
106 #[pallet::config]
107 pub trait Config: frame_system::Config {
108 #[allow(deprecated)]
110 type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
111
112 type 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>;
120
121 type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
123
124 type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
126
127 type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
132
133 #[pallet::constant]
137 type MaxNameLen: Get<u32>;
138
139 type WeightInfo: WeightInfo;
141 }
142
143 #[pallet::storage]
145 pub type PausedCalls<T: Config> =
146 StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
147
148 #[pallet::error]
149 pub enum Error<T> {
150 IsPaused,
152
153 IsUnpaused,
155
156 Unpausable,
158
159 NotFound,
161 }
162
163 #[pallet::event]
164 #[pallet::generate_deposit(pub(super) fn deposit_event)]
165 pub enum Event<T: Config> {
166 CallPaused { full_name: RuntimeCallNameOf<T> },
168 CallUnpaused { full_name: RuntimeCallNameOf<T> },
170 }
171
172 #[pallet::genesis_config]
174 #[derive(DefaultNoBound)]
175 pub struct GenesisConfig<T: Config> {
176 pub paused: Vec<RuntimeCallNameOf<T>>,
178 }
179
180 #[pallet::genesis_build]
181 impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
182 fn build(&self) {
183 for 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 }
189
190 #[pallet::call]
191 impl<T: Config> Pallet<T> {
192 #[pallet::call_index(0)]
197 #[pallet::weight(T::WeightInfo::pause())]
198 pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
199 T::PauseOrigin::ensure_origin(origin)?;
200
201 Self::do_pause(full_name).map_err(Into::into)
202 }
203
204 #[pallet::call_index(1)]
209 #[pallet::weight(T::WeightInfo::unpause())]
210 pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
211 T::UnpauseOrigin::ensure_origin(origin)?;
212
213 Self::do_unpause(ident).map_err(Into::into)
214 }
215 }
216}
217
218impl<T: Config> Pallet<T> {
219 pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
220 Self::ensure_can_pause(&ident)?;
221 PausedCalls::<T>::insert(&ident, ());
222 Self::deposit_event(Event::CallPaused { full_name: ident });
223
224 Ok(())
225 }
226
227 pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
228 Self::ensure_can_unpause(&ident)?;
229 PausedCalls::<T>::remove(&ident);
230 Self::deposit_event(Event::CallUnpaused { full_name: ident });
231
232 Ok(())
233 }
234
235 pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
237 if T::WhitelistedCalls::contains(full_name) {
238 return false
239 }
240
241 <PausedCalls<T>>::contains_key(full_name)
242 }
243
244 pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
246 let pallet = PalletNameOf::<T>::try_from(pallet);
247 let call = PalletCallNameOf::<T>::try_from(call);
248
249 match (pallet, call) {
250 (Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
251 _ => true,
252 }
253 }
254
255 pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
257 if full_name.0.as_slice() == <Self as PalletInfoAccess>::name().as_bytes() {
259 return Err(Error::<T>::Unpausable)
260 }
261
262 if T::WhitelistedCalls::contains(&full_name) {
263 return Err(Error::<T>::Unpausable)
264 }
265 if Self::is_paused(&full_name) {
266 return Err(Error::<T>::IsPaused)
267 }
268 Ok(())
269 }
270
271 pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
273 if Self::is_paused(&full_name) {
274 Ok(())
276 } else {
277 Err(Error::IsUnpaused)
278 }
279 }
280}
281
282impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
283where
284 <T as frame_system::Config>::RuntimeCall: GetCallMetadata,
285{
286 fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
288 let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
289 !Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
290 }
291}
292
293impl<T: Config> TransactionPause for Pallet<T> {
294 type CallIdentifier = RuntimeCallNameOf<T>;
295
296 fn is_paused(full_name: Self::CallIdentifier) -> bool {
297 Self::is_paused(&full_name)
298 }
299
300 fn can_pause(full_name: Self::CallIdentifier) -> bool {
301 Self::ensure_can_pause(&full_name).is_ok()
302 }
303
304 fn pause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
305 Self::do_pause(full_name).map_err(Into::into)
306 }
307
308 fn unpause(full_name: Self::CallIdentifier) -> Result<(), TransactionPauseError> {
309 Self::do_unpause(full_name).map_err(Into::into)
310 }
311}
312
313impl<T: Config> From<Error<T>> for TransactionPauseError {
314 fn from(err: Error<T>) -> Self {
315 match 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}