referrerpolicy=no-referrer-when-downgrade

pallet_safe_mode/
lib.rs

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//! # Safe Mode
19//!
20//! Trigger for stopping all extrinsics outside of a specific whitelist.
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//! Safe mode is entered via two paths (deposit or forced) until a set block number.
30//! The mode is exited when the block number is reached or a call to one of the exit extrinsics is
31//! made. A `WhitelistedCalls` configuration item contains all calls that can be executed while in
32//! safe mode.
33//!
34//! ### Primary Features
35//!
36//! - Entering safe mode can be via privileged origin or anyone who places a deposit.
37//! - Origin configuration items are separated for privileged entering and exiting safe mode.
38//! - A configurable duration sets the number of blocks after which the system will exit safe mode.
39//! - Safe mode may be extended beyond the configured exit by additional calls.
40//!
41//! ### Example
42//!
43//! Configuration of call filters:
44//!
45//! ```ignore
46//! impl frame_system::Config for Runtime {
47//!   // …
48//!   type BaseCallFilter = InsideBoth<DefaultFilter, SafeMode>;
49//!   // …
50//! }
51//! ```
52//!
53//! Entering safe mode with deposit:
54#![doc = docify::embed!("src/tests.rs", can_activate)]
55//!
56//! Entering safe mode via privileged origin:
57#![doc = docify::embed!("src/tests.rs", can_force_activate_with_config_origin)]
58//!
59//! Exiting safe mode via privileged origin:
60#![doc = docify::embed!("src/tests.rs", can_force_deactivate_with_config_origin)]
61//!
62//! ## Low Level / Implementation Details
63//!
64//! ### Use Cost
65//!
66//! A storage value (`EnteredUntil`) is used to store the block safe mode will be exited on.
67//! Using the call filter will require a db read of that storage on the first extrinsic.
68//! The storage will be added to the overlay and incur low cost for all additional calls.
69
70#![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
78use frame::{
79	prelude::{
80		fungible::hold::{Inspect, Mutate},
81		*,
82	},
83	traits::{fungible, CallMetadata, GetCallMetadata, SafeModeNotify},
84};
85
86pub use pallet::*;
87pub use weights::*;
88
89type BalanceOf<T> =
90	<<T as Config>::Currency as fungible::Inspect<<T as frame_system::Config>::AccountId>>::Balance;
91
92#[frame::pallet]
93pub mod pallet {
94	use super::*;
95
96	#[pallet::pallet]
97	pub struct Pallet<T>(PhantomData<T>);
98
99	#[pallet::config]
100	pub trait Config: frame_system::Config {
101		/// The overarching event type.
102		#[allow(deprecated)]
103		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
104
105		/// Currency type for this pallet, used for Deposits.
106		type Currency: Inspect<Self::AccountId>
107			+ Mutate<Self::AccountId, Reason = Self::RuntimeHoldReason>;
108
109		/// The hold reason when reserving funds for entering or extending the safe-mode.
110		type RuntimeHoldReason: From<HoldReason>;
111
112		/// Contains all runtime calls in any pallet that can be dispatched even while the safe-mode
113		/// is entered.
114		///
115		/// The safe-mode pallet cannot disable it's own calls, and does not need to be explicitly
116		/// added here.
117		type WhitelistedCalls: Contains<Self::RuntimeCall>;
118
119		/// For how many blocks the safe-mode will be entered by [`Pallet::enter`].
120		#[pallet::constant]
121		type EnterDuration: Get<BlockNumberFor<Self>>;
122
123		/// For how many blocks the safe-mode can be extended by each [`Pallet::extend`] call.
124		///
125		/// This does not impose a hard limit as the safe-mode can be extended multiple times.
126		#[pallet::constant]
127		type ExtendDuration: Get<BlockNumberFor<Self>>;
128
129		/// The amount that will be reserved upon calling [`Pallet::enter`].
130		///
131		/// `None` disallows permissionlessly enabling the safe-mode and is a sane default.
132		#[pallet::constant]
133		type EnterDepositAmount: Get<Option<BalanceOf<Self>>>;
134
135		/// The amount that will be reserved upon calling [`Pallet::extend`].
136		///
137		/// `None` disallows permissionlessly extending the safe-mode and is a sane default.
138		#[pallet::constant]
139		type ExtendDepositAmount: Get<Option<BalanceOf<Self>>>;
140
141		/// The origin that may call [`Pallet::force_enter`].
142		///
143		/// The `Success` value is the number of blocks that this origin can enter safe-mode for.
144		type ForceEnterOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BlockNumberFor<Self>>;
145
146		/// The origin that may call [`Pallet::force_extend`].
147		///
148		/// The `Success` value is the number of blocks that this origin can extend the safe-mode.
149		type ForceExtendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BlockNumberFor<Self>>;
150
151		/// The origin that may call [`Pallet::force_enter`].
152		type ForceExitOrigin: EnsureOrigin<Self::RuntimeOrigin>;
153
154		/// The only origin that can force to release or slash a deposit.
155		type ForceDepositOrigin: EnsureOrigin<Self::RuntimeOrigin>;
156
157		/// Notifies external logic when the safe-mode is being entered or exited.
158		type Notify: SafeModeNotify;
159
160		/// The minimal duration a deposit will remain reserved after safe-mode is entered or
161		/// extended, unless [`Pallet::force_release_deposit`] is successfully called sooner.
162		///
163		/// Every deposit is tied to a specific activation or extension, thus each deposit can be
164		/// released independently after the delay for it has passed.
165		///
166		/// `None` disallows permissionlessly releasing the safe-mode deposits and is a sane
167		/// default.
168		#[pallet::constant]
169		type ReleaseDelay: Get<Option<BlockNumberFor<Self>>>;
170
171		// Weight information for extrinsics in this pallet.
172		type WeightInfo: WeightInfo;
173	}
174
175	#[pallet::error]
176	pub enum Error<T> {
177		/// The safe-mode is (already or still) entered.
178		Entered,
179
180		/// The safe-mode is (already or still) exited.
181		Exited,
182
183		/// This functionality of the pallet is disabled by the configuration.
184		NotConfigured,
185
186		/// There is no balance reserved.
187		NoDeposit,
188
189		/// The account already has a deposit reserved and can therefore not enter or extend again.
190		AlreadyDeposited,
191
192		/// This deposit cannot be released yet.
193		CannotReleaseYet,
194
195		/// An error from the underlying `Currency`.
196		CurrencyError,
197	}
198
199	#[pallet::event]
200	#[pallet::generate_deposit(pub(super) fn deposit_event)]
201	pub enum Event<T: Config> {
202		/// The safe-mode was entered until inclusively this block.
203		Entered { until: BlockNumberFor<T> },
204
205		/// The safe-mode was extended until inclusively this block.
206		Extended { until: BlockNumberFor<T> },
207
208		/// Exited the safe-mode for a specific reason.
209		Exited { reason: ExitReason },
210
211		/// An account reserved funds for either entering or extending the safe-mode.
212		DepositPlaced { account: T::AccountId, amount: BalanceOf<T> },
213
214		/// An account had a reserve released that was reserved.
215		DepositReleased { account: T::AccountId, amount: BalanceOf<T> },
216
217		/// An account had reserve slashed that was reserved.
218		DepositSlashed { account: T::AccountId, amount: BalanceOf<T> },
219
220		/// Could not hold funds for entering or extending the safe-mode.
221		///
222		/// This error comes from the underlying `Currency`.
223		CannotDeposit,
224
225		/// Could not release funds for entering or extending the safe-mode.
226		///
227		/// This error comes from the underlying `Currency`.
228		CannotRelease,
229	}
230
231	/// The reason why the safe-mode was deactivated.
232	#[derive(
233		Copy,
234		Clone,
235		PartialEq,
236		Eq,
237		RuntimeDebug,
238		Encode,
239		Decode,
240		DecodeWithMemTracking,
241		TypeInfo,
242		MaxEncodedLen,
243	)]
244	pub enum ExitReason {
245		/// The safe-mode was automatically deactivated after it's duration ran out.
246		Timeout,
247
248		/// The safe-mode was forcefully deactivated by [`Pallet::force_exit`].
249		Force,
250	}
251
252	/// Contains the last block number that the safe-mode will remain entered in.
253	///
254	///  Set to `None` when safe-mode is exited.
255	///
256	/// Safe-mode is automatically exited when the current block number exceeds this value.
257	#[pallet::storage]
258	pub type EnteredUntil<T: Config> = StorageValue<_, BlockNumberFor<T>, OptionQuery>;
259
260	/// Holds the reserve that was taken from an account at a specific block number.
261	///
262	/// This helps governance to have an overview of outstanding deposits that should be returned or
263	/// slashed.
264	#[pallet::storage]
265	pub type Deposits<T: Config> = StorageDoubleMap<
266		_,
267		Twox64Concat,
268		T::AccountId,
269		Twox64Concat,
270		BlockNumberFor<T>,
271		BalanceOf<T>,
272		OptionQuery,
273	>;
274
275	/// Configure the initial state of this pallet in the genesis block.
276	#[pallet::genesis_config]
277	#[derive(DefaultNoBound)]
278	pub struct GenesisConfig<T: Config> {
279		pub entered_until: Option<BlockNumberFor<T>>,
280	}
281
282	#[pallet::genesis_build]
283	impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
284		fn build(&self) {
285			if let Some(block) = self.entered_until {
286				EnteredUntil::<T>::put(block);
287			}
288		}
289	}
290
291	/// A reason for the pallet placing a hold on funds.
292	#[pallet::composite_enum]
293	pub enum HoldReason {
294		/// Funds are held for entering or extending the safe-mode.
295		#[codec(index = 0)]
296		EnterOrExtend,
297	}
298
299	#[pallet::call]
300	impl<T: Config> Pallet<T> {
301		/// Enter safe-mode permissionlessly for [`Config::EnterDuration`] blocks.
302		///
303		/// Reserves [`Config::EnterDepositAmount`] from the caller's account.
304		/// Emits an [`Event::Entered`] event on success.
305		/// Errors with [`Error::Entered`] if the safe-mode is already entered.
306		/// Errors with [`Error::NotConfigured`] if the deposit amount is `None`.
307		#[pallet::call_index(0)]
308		#[pallet::weight(T::WeightInfo::enter())]
309		pub fn enter(origin: OriginFor<T>) -> DispatchResult {
310			let who = ensure_signed(origin)?;
311
312			Self::do_enter(Some(who), T::EnterDuration::get()).map_err(Into::into)
313		}
314
315		/// Enter safe-mode by force for a per-origin configured number of blocks.
316		///
317		/// Emits an [`Event::Entered`] event on success.
318		/// Errors with [`Error::Entered`] if the safe-mode is already entered.
319		///
320		/// Can only be called by the [`Config::ForceEnterOrigin`] origin.
321		#[pallet::call_index(1)]
322		#[pallet::weight(T::WeightInfo::force_enter())]
323		pub fn force_enter(origin: OriginFor<T>) -> DispatchResult {
324			let duration = T::ForceEnterOrigin::ensure_origin(origin)?;
325
326			Self::do_enter(None, duration).map_err(Into::into)
327		}
328
329		/// Extend the safe-mode permissionlessly for [`Config::ExtendDuration`] blocks.
330		///
331		/// This accumulates on top of the current remaining duration.
332		/// Reserves [`Config::ExtendDepositAmount`] from the caller's account.
333		/// Emits an [`Event::Extended`] event on success.
334		/// Errors with [`Error::Exited`] if the safe-mode is entered.
335		/// Errors with [`Error::NotConfigured`] if the deposit amount is `None`.
336		///
337		/// This may be called by any signed origin with [`Config::ExtendDepositAmount`] free
338		/// currency to reserve. This call can be disabled for all origins by configuring
339		/// [`Config::ExtendDepositAmount`] to `None`.
340		#[pallet::call_index(2)]
341		#[pallet::weight(T::WeightInfo::extend())]
342		pub fn extend(origin: OriginFor<T>) -> DispatchResult {
343			let who = ensure_signed(origin)?;
344
345			Self::do_extend(Some(who), T::ExtendDuration::get()).map_err(Into::into)
346		}
347
348		/// Extend the safe-mode by force for a per-origin configured number of blocks.
349		///
350		/// Emits an [`Event::Extended`] event on success.
351		/// Errors with [`Error::Exited`] if the safe-mode is inactive.
352		///
353		/// Can only be called by the [`Config::ForceExtendOrigin`] origin.
354		#[pallet::call_index(3)]
355		#[pallet::weight(T::WeightInfo::force_extend())]
356		pub fn force_extend(origin: OriginFor<T>) -> DispatchResult {
357			let duration = T::ForceExtendOrigin::ensure_origin(origin)?;
358
359			Self::do_extend(None, duration).map_err(Into::into)
360		}
361
362		/// Exit safe-mode by force.
363		///
364		/// Emits an [`Event::Exited`] with [`ExitReason::Force`] event on success.
365		/// Errors with [`Error::Exited`] if the safe-mode is inactive.
366		///
367		/// Note: `safe-mode` will be automatically deactivated by [`Pallet::on_initialize`] hook
368		/// after the block height is greater than the [`EnteredUntil`] storage item.
369		/// Emits an [`Event::Exited`] with [`ExitReason::Timeout`] event when deactivated in the
370		/// hook.
371		#[pallet::call_index(4)]
372		#[pallet::weight(T::WeightInfo::force_exit())]
373		pub fn force_exit(origin: OriginFor<T>) -> DispatchResult {
374			T::ForceExitOrigin::ensure_origin(origin)?;
375
376			Self::do_exit(ExitReason::Force).map_err(Into::into)
377		}
378
379		/// Slash a deposit for an account that entered or extended safe-mode at a given
380		/// historical block.
381		///
382		/// This can only be called while safe-mode is entered.
383		///
384		/// Emits a [`Event::DepositSlashed`] event on success.
385		/// Errors with [`Error::Entered`] if safe-mode is entered.
386		///
387		/// Can only be called by the [`Config::ForceDepositOrigin`] origin.
388		#[pallet::call_index(5)]
389		#[pallet::weight(T::WeightInfo::force_slash_deposit())]
390		pub fn force_slash_deposit(
391			origin: OriginFor<T>,
392			account: T::AccountId,
393			block: BlockNumberFor<T>,
394		) -> DispatchResult {
395			T::ForceDepositOrigin::ensure_origin(origin)?;
396
397			Self::do_force_deposit(account, block).map_err(Into::into)
398		}
399
400		/// Permissionlessly release a deposit for an account that entered safe-mode at a
401		/// given historical block.
402		///
403		/// The call can be completely disabled by setting [`Config::ReleaseDelay`] to `None`.
404		/// This cannot be called while safe-mode is entered and not until
405		/// [`Config::ReleaseDelay`] blocks have passed since safe-mode was entered.
406		///
407		/// Emits a [`Event::DepositReleased`] event on success.
408		/// Errors with [`Error::Entered`] if the safe-mode is entered.
409		/// Errors with [`Error::CannotReleaseYet`] if [`Config::ReleaseDelay`] block have not
410		/// passed since safe-mode was entered. Errors with [`Error::NoDeposit`] if the payee has no
411		/// reserved currency at the block specified.
412		#[pallet::call_index(6)]
413		#[pallet::weight(T::WeightInfo::release_deposit())]
414		pub fn release_deposit(
415			origin: OriginFor<T>,
416			account: T::AccountId,
417			block: BlockNumberFor<T>,
418		) -> DispatchResult {
419			ensure_signed(origin)?;
420
421			Self::do_release(false, account, block).map_err(Into::into)
422		}
423
424		/// Force to release a deposit for an account that entered safe-mode at a given
425		/// historical block.
426		///
427		/// This can be called while safe-mode is still entered.
428		///
429		/// Emits a [`Event::DepositReleased`] event on success.
430		/// Errors with [`Error::Entered`] if safe-mode is entered.
431		/// Errors with [`Error::NoDeposit`] if the payee has no reserved currency at the
432		/// specified block.
433		///
434		/// Can only be called by the [`Config::ForceDepositOrigin`] origin.
435		#[pallet::call_index(7)]
436		#[pallet::weight(T::WeightInfo::force_release_deposit())]
437		pub fn force_release_deposit(
438			origin: OriginFor<T>,
439			account: T::AccountId,
440			block: BlockNumberFor<T>,
441		) -> DispatchResult {
442			T::ForceDepositOrigin::ensure_origin(origin)?;
443
444			Self::do_release(true, account, block).map_err(Into::into)
445		}
446	}
447
448	#[pallet::hooks]
449	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
450		/// Automatically exits safe-mode when the current block number is greater than
451		/// [`EnteredUntil`].
452		fn on_initialize(current: BlockNumberFor<T>) -> Weight {
453			let Some(limit) = EnteredUntil::<T>::get() else {
454				return T::WeightInfo::on_initialize_noop()
455			};
456
457			if current > limit {
458				let _ = Self::do_exit(ExitReason::Timeout).defensive_proof("Only Errors if safe-mode is not entered. Safe-mode has already been checked to be entered; qed");
459				T::WeightInfo::on_initialize_exit()
460			} else {
461				T::WeightInfo::on_initialize_noop()
462			}
463		}
464	}
465}
466
467impl<T: Config> Pallet<T> {
468	/// Logic for the [`crate::Pallet::enter`] and [`crate::Pallet::force_enter`] calls.
469	pub(crate) fn do_enter(
470		who: Option<T::AccountId>,
471		duration: BlockNumberFor<T>,
472	) -> Result<(), Error<T>> {
473		ensure!(!Self::is_entered(), Error::<T>::Entered);
474
475		if let Some(who) = who {
476			let amount = T::EnterDepositAmount::get().ok_or(Error::<T>::NotConfigured)?;
477			Self::hold(who, amount)?;
478		}
479
480		let until = <frame_system::Pallet<T>>::block_number().saturating_add(duration);
481		EnteredUntil::<T>::put(until);
482		Self::deposit_event(Event::Entered { until });
483		T::Notify::entered();
484		Ok(())
485	}
486
487	/// Logic for the [`crate::Pallet::extend`] and [`crate::Pallet::force_extend`] calls.
488	pub(crate) fn do_extend(
489		who: Option<T::AccountId>,
490		duration: BlockNumberFor<T>,
491	) -> Result<(), Error<T>> {
492		let mut until = EnteredUntil::<T>::get().ok_or(Error::<T>::Exited)?;
493
494		if let Some(who) = who {
495			let amount = T::ExtendDepositAmount::get().ok_or(Error::<T>::NotConfigured)?;
496			Self::hold(who, amount)?;
497		}
498
499		until.saturating_accrue(duration);
500		EnteredUntil::<T>::put(until);
501		Self::deposit_event(Event::<T>::Extended { until });
502		Ok(())
503	}
504
505	/// Logic for the [`crate::Pallet::force_exit`] call.
506	///
507	/// Errors if safe-mode is already exited.
508	pub(crate) fn do_exit(reason: ExitReason) -> Result<(), Error<T>> {
509		let _until = EnteredUntil::<T>::take().ok_or(Error::<T>::Exited)?;
510		Self::deposit_event(Event::Exited { reason });
511		T::Notify::exited();
512		Ok(())
513	}
514
515	/// Logic for the [`crate::Pallet::release_deposit`] and
516	/// [`crate::Pallet::force_release_deposit`] calls.
517	pub(crate) fn do_release(
518		force: bool,
519		account: T::AccountId,
520		block: BlockNumberFor<T>,
521	) -> Result<(), Error<T>> {
522		let amount = Deposits::<T>::take(&account, &block).ok_or(Error::<T>::NoDeposit)?;
523
524		if !force {
525			ensure!(!Self::is_entered(), Error::<T>::Entered);
526
527			let delay = T::ReleaseDelay::get().ok_or(Error::<T>::NotConfigured)?;
528			let now = <frame_system::Pallet<T>>::block_number();
529			ensure!(now > block.saturating_add(delay), Error::<T>::CannotReleaseYet);
530		}
531
532		let amount = T::Currency::release(
533			&&HoldReason::EnterOrExtend.into(),
534			&account,
535			amount,
536			Precision::BestEffort,
537		)
538		.map_err(|_| Error::<T>::CurrencyError)?;
539		Self::deposit_event(Event::<T>::DepositReleased { account, amount });
540		Ok(())
541	}
542
543	/// Logic for the [`crate::Pallet::slash_deposit`] call.
544	pub(crate) fn do_force_deposit(
545		account: T::AccountId,
546		block: BlockNumberFor<T>,
547	) -> Result<(), Error<T>> {
548		let amount = Deposits::<T>::take(&account, block).ok_or(Error::<T>::NoDeposit)?;
549
550		let burned = T::Currency::burn_held(
551			&&HoldReason::EnterOrExtend.into(),
552			&account,
553			amount,
554			Precision::BestEffort,
555			Fortitude::Force,
556		)
557		.map_err(|_| Error::<T>::CurrencyError)?;
558		defensive_assert!(burned == amount, "Could not burn the full held amount");
559		Self::deposit_event(Event::<T>::DepositSlashed { account, amount });
560		Ok(())
561	}
562
563	/// Place a hold for exactly `amount` and store it in `Deposits`.
564	///
565	/// Errors if the account already has a hold for the same reason.
566	fn hold(who: T::AccountId, amount: BalanceOf<T>) -> Result<(), Error<T>> {
567		let block = <frame_system::Pallet<T>>::block_number();
568		if !T::Currency::balance_on_hold(&HoldReason::EnterOrExtend.into(), &who).is_zero() {
569			return Err(Error::<T>::AlreadyDeposited.into())
570		}
571
572		T::Currency::hold(&HoldReason::EnterOrExtend.into(), &who, amount)
573			.map_err(|_| Error::<T>::CurrencyError)?;
574		Deposits::<T>::insert(&who, block, amount);
575		Self::deposit_event(Event::<T>::DepositPlaced { account: who, amount });
576
577		Ok(())
578	}
579
580	/// Return whether `safe-mode` is entered.
581	pub fn is_entered() -> bool {
582		EnteredUntil::<T>::exists()
583	}
584
585	/// Return whether the given call is allowed to be dispatched.
586	pub fn is_allowed(call: &T::RuntimeCall) -> bool
587	where
588		T::RuntimeCall: GetCallMetadata,
589	{
590		let CallMetadata { pallet_name, .. } = call.get_call_metadata();
591		// SAFETY: The `SafeMode` pallet is always allowed.
592		if pallet_name == <Pallet<T> as PalletInfoAccess>::name() {
593			return true
594		}
595
596		if Self::is_entered() {
597			T::WhitelistedCalls::contains(call)
598		} else {
599			true
600		}
601	}
602}
603
604impl<T: Config> Contains<T::RuntimeCall> for Pallet<T>
605where
606	T::RuntimeCall: GetCallMetadata,
607{
608	/// Return whether the given call is allowed to be dispatched.
609	fn contains(call: &T::RuntimeCall) -> bool {
610		Pallet::<T>::is_allowed(call)
611	}
612}
613
614impl<T: Config> frame::traits::SafeMode for Pallet<T> {
615	type BlockNumber = BlockNumberFor<T>;
616
617	fn is_entered() -> bool {
618		Self::is_entered()
619	}
620
621	fn remaining() -> Option<BlockNumberFor<T>> {
622		EnteredUntil::<T>::get().map(|until| {
623			let now = <frame_system::Pallet<T>>::block_number();
624			until.saturating_sub(now)
625		})
626	}
627
628	fn enter(duration: BlockNumberFor<T>) -> Result<(), frame::traits::SafeModeError> {
629		Self::do_enter(None, duration).map_err(Into::into)
630	}
631
632	fn extend(duration: BlockNumberFor<T>) -> Result<(), frame::traits::SafeModeError> {
633		Self::do_extend(None, duration).map_err(Into::into)
634	}
635
636	fn exit() -> Result<(), frame::traits::SafeModeError> {
637		Self::do_exit(ExitReason::Force).map_err(Into::into)
638	}
639}
640
641impl<T: Config> From<Error<T>> for frame::traits::SafeModeError {
642	fn from(err: Error<T>) -> Self {
643		match err {
644			Error::<T>::Entered => Self::AlreadyEntered,
645			Error::<T>::Exited => Self::AlreadyExited,
646			_ => Self::Unknown,
647		}
648	}
649}