mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-16 20:11:03 +00:00
[FRAME Core] New pallets: safe-mode and tx-pause (#12092)
* Add safe-mode Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add to kitchensink-runtime Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Spelling Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename to tx-pause Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add SafeMode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * fmt Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Automatically disable safe-mode in on_init… Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add permissionless enable+extend Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add repay+slash stake methods Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix stakes storage Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Genesis config for safe-mode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Genesis config for safe-mode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename ExtrinsicName to FunctionName Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Origin variable duration Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename FunctionName -> CallName Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename and docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Pallet safe mode tests (#12148) * Add safe-mode mock runtime * Add safe-mode tests * Add ForceEnable- and ForceExtendOrigin * Start dummy benchmarks Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Tests for `pallet-tx-pause` (#12259) * mock added * tests added * dummy benchmarks started * rename to active/inactive tests broken, in progress * Runtime types, fix tests * WIP safe mode and tx pause {continued} (#12371) * test coverage on safe mode and tx pause * benchmarks & tests * tests passing, use FullNameOf to check tx-pause unfilterables * naming updates Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Set block number Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * dummy weights generated, safe mode * add repay_reservation call with RepaymentDelay per #10033 feature requirements * make call name optional to allow pausing pallets, handle `Contains` correctly for this throughout, doc comments started * move to full_name notation for all interfaces, last commit introduced 1 more storage read. * refactor is_paused * safe math on safe mode * Make stuff compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup & renames Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup TxPause pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * fmt Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Refactor to fungibles::* and rename stuf Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix node config Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Typos Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove CausalHoldReason Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Refactor benchmarks and runtime configs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add traits Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove old code Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup safe-mode benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/safe-mode/Cargo.toml Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/safe-mode/Cargo.toml Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove getters Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update Cargo.lock Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove phantom Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix test Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove phantom Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Apply suggestions from code review Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Use new as Origin benchmarking syntax Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix node Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix tx-pause benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Renames * Remove duplicate test * Add docs * docs * Apply suggestions from code review Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> * Cleanup tests * docs * Cleanup GenesisConfigs * Doc traits * Remove PauseTooLongNames * docs * Use V2 benchmarking Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Use RuntimeHoldReason * Fix kitchensink runtime * Fix CI Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix CI Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Review Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename Stake to Deposit Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add Notify and test it Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix kitchensink Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/safe-mode/src/tests.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/safe-mode/src/tests.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/tx_pause.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/mock.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Simplify code * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Fixup merge Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make stuff compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make tx-pause compile again Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix features Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix more features Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * ".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_safe_mode * Update weights Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Dan Shields <35669742+NukeManDan@users.noreply.github.com> Co-authored-by: Dan Shields <nukemandan@protonmail.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
46bd466e48
commit
3710edfedc
@@ -0,0 +1,596 @@
|
||||
// This file is part of Substrate.
|
||||
|
||||
// Copyright (C) Parity Technologies (UK) Ltd.
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![deny(rustdoc::broken_intra_doc_links)]
|
||||
|
||||
mod benchmarking;
|
||||
pub mod mock;
|
||||
mod tests;
|
||||
pub mod weights;
|
||||
|
||||
use frame_support::{
|
||||
defensive_assert,
|
||||
pallet_prelude::*,
|
||||
traits::{
|
||||
fungible::{
|
||||
hold::{Inspect as FunHoldInspect, Mutate as FunHoldMutate},
|
||||
Inspect as FunInspect,
|
||||
},
|
||||
tokens::{Fortitude, Precision},
|
||||
CallMetadata, Contains, Defensive, GetCallMetadata, PalletInfoAccess, SafeModeNotify,
|
||||
},
|
||||
weights::Weight,
|
||||
DefaultNoBound,
|
||||
};
|
||||
use frame_system::pallet_prelude::*;
|
||||
use sp_arithmetic::traits::Zero;
|
||||
use sp_runtime::traits::Saturating;
|
||||
use sp_std::{convert::TryInto, prelude::*};
|
||||
|
||||
pub use pallet::*;
|
||||
pub use weights::*;
|
||||
|
||||
type BalanceOf<T> =
|
||||
<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
|
||||
#[frame_support::pallet]
|
||||
pub mod pallet {
|
||||
use super::*;
|
||||
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(PhantomData<T>);
|
||||
|
||||
#[pallet::config]
|
||||
pub trait Config: frame_system::Config {
|
||||
/// The overarching event type.
|
||||
type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
|
||||
|
||||
/// Currency type for this pallet, used for Deposits.
|
||||
type Currency: FunHoldInspect<Self::AccountId>
|
||||
+ FunHoldMutate<Self::AccountId, Reason = Self::RuntimeHoldReason>;
|
||||
|
||||
/// The hold reason when reserving funds for entering or extending the safe-mode.
|
||||
type RuntimeHoldReason: From<HoldReason>;
|
||||
|
||||
/// Contains all runtime calls in any pallet that can be dispatched even while the safe-mode
|
||||
/// is entered.
|
||||
///
|
||||
/// The safe-mode pallet cannot disable it's own calls, and does not need to be explicitly
|
||||
/// added here.
|
||||
type WhitelistedCalls: Contains<Self::RuntimeCall>;
|
||||
|
||||
/// For how many blocks the safe-mode will be entered by [`Pallet::enter`].
|
||||
#[pallet::constant]
|
||||
type EnterDuration: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// For how many blocks the safe-mode can be extended by each [`Pallet::extend`] call.
|
||||
///
|
||||
/// This does not impose a hard limit as the safe-mode can be extended multiple times.
|
||||
#[pallet::constant]
|
||||
type ExtendDuration: Get<BlockNumberFor<Self>>;
|
||||
|
||||
/// The amount that will be reserved upon calling [`Pallet::enter`].
|
||||
///
|
||||
/// `None` disallows permissionlessly enabling the safe-mode and is a sane default.
|
||||
#[pallet::constant]
|
||||
type EnterDepositAmount: Get<Option<BalanceOf<Self>>>;
|
||||
|
||||
/// The amount that will be reserved upon calling [`Pallet::extend`].
|
||||
///
|
||||
/// `None` disallows permissionlessly extending the safe-mode and is a sane default.
|
||||
#[pallet::constant]
|
||||
type ExtendDepositAmount: Get<Option<BalanceOf<Self>>>;
|
||||
|
||||
/// The origin that may call [`Pallet::force_enter`].
|
||||
///
|
||||
/// The `Success` value is the number of blocks that this origin can enter safe-mode for.
|
||||
type ForceEnterOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BlockNumberFor<Self>>;
|
||||
|
||||
/// The origin that may call [`Pallet::force_extend`].
|
||||
///
|
||||
/// The `Success` value is the number of blocks that this origin can extend the safe-mode.
|
||||
type ForceExtendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BlockNumberFor<Self>>;
|
||||
|
||||
/// The origin that may call [`Pallet::force_enter`].
|
||||
type ForceExitOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// The only origin that can force to release or slash a deposit.
|
||||
type ForceDepositOrigin: EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Notifies external logic when the safe-mode is being entered or exited.
|
||||
type Notify: SafeModeNotify;
|
||||
|
||||
/// The minimal duration a deposit will remain reserved after safe-mode is entered or
|
||||
/// extended, unless [`Pallet::force_release_deposit`] is successfully called sooner.
|
||||
///
|
||||
/// Every deposit is tied to a specific activation or extension, thus each deposit can be
|
||||
/// released independently after the delay for it has passed.
|
||||
///
|
||||
/// `None` disallows permissionlessly releasing the safe-mode deposits and is a sane
|
||||
/// default.
|
||||
#[pallet::constant]
|
||||
type ReleaseDelay: Get<Option<BlockNumberFor<Self>>>;
|
||||
|
||||
// Weight information for extrinsics in this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
pub enum Error<T> {
|
||||
/// The safe-mode is (already or still) entered.
|
||||
Entered,
|
||||
|
||||
/// The safe-mode is (already or still) exited.
|
||||
Exited,
|
||||
|
||||
/// This functionality of the pallet is disabled by the configuration.
|
||||
NotConfigured,
|
||||
|
||||
/// There is no balance reserved.
|
||||
NoDeposit,
|
||||
|
||||
/// The account already has a deposit reserved and can therefore not enter or extend again.
|
||||
AlreadyDeposited,
|
||||
|
||||
/// This deposit cannot be released yet.
|
||||
CannotReleaseYet,
|
||||
|
||||
/// An error from the underlying `Currency`.
|
||||
CurrencyError,
|
||||
}
|
||||
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
/// The safe-mode was entered until inclusively this block.
|
||||
Entered { until: BlockNumberFor<T> },
|
||||
|
||||
/// The safe-mode was extended until inclusively this block.
|
||||
Extended { until: BlockNumberFor<T> },
|
||||
|
||||
/// Exited the safe-mode for a specific reason.
|
||||
Exited { reason: ExitReason },
|
||||
|
||||
/// An account reserved funds for either entering or extending the safe-mode.
|
||||
DepositPlaced { account: T::AccountId, amount: BalanceOf<T> },
|
||||
|
||||
/// An account had a reserve released that was reserved.
|
||||
DepositReleased { account: T::AccountId, amount: BalanceOf<T> },
|
||||
|
||||
/// An account had reserve slashed that was reserved.
|
||||
DepositSlashed { account: T::AccountId, amount: BalanceOf<T> },
|
||||
|
||||
/// Could not hold funds for entering or extending the safe-mode.
|
||||
///
|
||||
/// This error comes from the underlying `Currency`.
|
||||
CannotDeposit,
|
||||
|
||||
/// Could not release funds for entering or extending the safe-mode.
|
||||
///
|
||||
/// This error comes from the underlying `Currency`.
|
||||
CannotRelease,
|
||||
}
|
||||
|
||||
/// The reason why the safe-mode was deactivated.
|
||||
#[derive(Copy, Clone, PartialEq, Eq, RuntimeDebug, Encode, Decode, TypeInfo, MaxEncodedLen)]
|
||||
pub enum ExitReason {
|
||||
/// The safe-mode was automatically deactivated after it's duration ran out.
|
||||
Timeout,
|
||||
|
||||
/// The safe-mode was forcefully deactivated by [`Pallet::force_exit`].
|
||||
Force,
|
||||
}
|
||||
|
||||
/// Contains the last block number that the safe-mode will remain entered in.
|
||||
///
|
||||
/// Set to `None` when safe-mode is exited.
|
||||
///
|
||||
/// Safe-mode is automatically exited when the current block number exceeds this value.
|
||||
#[pallet::storage]
|
||||
pub type EnteredUntil<T: Config> = StorageValue<_, BlockNumberFor<T>, OptionQuery>;
|
||||
|
||||
/// Holds the reserve that was taken from an account at a specific block number.
|
||||
///
|
||||
/// This helps governance to have an overview of outstanding deposits that should be returned or
|
||||
/// slashed.
|
||||
#[pallet::storage]
|
||||
pub type Deposits<T: Config> = StorageDoubleMap<
|
||||
_,
|
||||
Twox64Concat,
|
||||
T::AccountId,
|
||||
Twox64Concat,
|
||||
BlockNumberFor<T>,
|
||||
BalanceOf<T>,
|
||||
OptionQuery,
|
||||
>;
|
||||
|
||||
/// Configure the initial state of this pallet in the genesis block.
|
||||
#[pallet::genesis_config]
|
||||
#[derive(DefaultNoBound)]
|
||||
pub struct GenesisConfig<T: Config> {
|
||||
pub entered_until: Option<BlockNumberFor<T>>,
|
||||
}
|
||||
|
||||
#[pallet::genesis_build]
|
||||
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
|
||||
fn build(&self) {
|
||||
if let Some(block) = self.entered_until {
|
||||
EnteredUntil::<T>::put(block);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A reason for the pallet placing a hold on funds.
|
||||
#[pallet::composite_enum]
|
||||
pub enum HoldReason {
|
||||
/// Funds are held for entering or extending the safe-mode.
|
||||
#[codec(index = 0)]
|
||||
EnterOrExtend,
|
||||
}
|
||||
|
||||
#[pallet::call]
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Enter safe-mode permissionlessly for [`Config::EnterDuration`] blocks.
|
||||
///
|
||||
/// Reserves [`Config::EnterDepositAmount`] from the caller's account.
|
||||
/// Emits an [`Event::Entered`] event on success.
|
||||
/// Errors with [`Error::Entered`] if the safe-mode is already entered.
|
||||
/// Errors with [`Error::NotConfigured`] if the deposit amount is `None`.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(T::WeightInfo::enter())]
|
||||
pub fn enter(origin: OriginFor<T>) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
Self::do_enter(Some(who), T::EnterDuration::get()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Enter safe-mode by force for a per-origin configured number of blocks.
|
||||
///
|
||||
/// Emits an [`Event::Entered`] event on success.
|
||||
/// Errors with [`Error::Entered`] if the safe-mode is already entered.
|
||||
///
|
||||
/// Can only be called by the [`Config::ForceEnterOrigin`] origin.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(T::WeightInfo::force_enter())]
|
||||
pub fn force_enter(origin: OriginFor<T>) -> DispatchResult {
|
||||
let duration = T::ForceEnterOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::do_enter(None, duration).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Extend the safe-mode permissionlessly for [`Config::ExtendDuration`] blocks.
|
||||
///
|
||||
/// This accumulates on top of the current remaining duration.
|
||||
/// Reserves [`Config::ExtendDepositAmount`] from the caller's account.
|
||||
/// Emits an [`Event::Extended`] event on success.
|
||||
/// Errors with [`Error::Exited`] if the safe-mode is entered.
|
||||
/// Errors with [`Error::NotConfigured`] if the deposit amount is `None`.
|
||||
///
|
||||
/// This may be called by any signed origin with [`Config::ExtendDepositAmount`] free
|
||||
/// currency to reserve. This call can be disabled for all origins by configuring
|
||||
/// [`Config::ExtendDepositAmount`] to `None`.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(T::WeightInfo::extend())]
|
||||
pub fn extend(origin: OriginFor<T>) -> DispatchResult {
|
||||
let who = ensure_signed(origin)?;
|
||||
|
||||
Self::do_extend(Some(who), T::ExtendDuration::get()).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Extend the safe-mode by force for a per-origin configured number of blocks.
|
||||
///
|
||||
/// Emits an [`Event::Extended`] event on success.
|
||||
/// Errors with [`Error::Exited`] if the safe-mode is inactive.
|
||||
///
|
||||
/// Can only be called by the [`Config::ForceExtendOrigin`] origin.
|
||||
#[pallet::call_index(3)]
|
||||
#[pallet::weight(T::WeightInfo::force_extend())]
|
||||
pub fn force_extend(origin: OriginFor<T>) -> DispatchResult {
|
||||
let duration = T::ForceExtendOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::do_extend(None, duration).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Exit safe-mode by force.
|
||||
///
|
||||
/// Emits an [`Event::Exited`] with [`ExitReason::Force`] event on success.
|
||||
/// Errors with [`Error::Exited`] if the safe-mode is inactive.
|
||||
///
|
||||
/// Note: `safe-mode` will be automatically deactivated by [`Pallet::on_initialize`] hook
|
||||
/// after the block height is greater than the [`EnteredUntil`] storage item.
|
||||
/// Emits an [`Event::Exited`] with [`ExitReason::Timeout`] event when deactivated in the
|
||||
/// hook.
|
||||
#[pallet::call_index(4)]
|
||||
#[pallet::weight(T::WeightInfo::force_exit())]
|
||||
pub fn force_exit(origin: OriginFor<T>) -> DispatchResult {
|
||||
T::ForceExitOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::do_exit(ExitReason::Force).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Slash a deposit for an account that entered or extended safe-mode at a given
|
||||
/// historical block.
|
||||
///
|
||||
/// This can only be called while safe-mode is entered.
|
||||
///
|
||||
/// Emits a [`Event::DepositSlashed`] event on success.
|
||||
/// Errors with [`Error::Entered`] if safe-mode is entered.
|
||||
///
|
||||
/// Can only be called by the [`Config::ForceDepositOrigin`] origin.
|
||||
#[pallet::call_index(5)]
|
||||
#[pallet::weight(T::WeightInfo::force_slash_deposit())]
|
||||
pub fn force_slash_deposit(
|
||||
origin: OriginFor<T>,
|
||||
account: T::AccountId,
|
||||
block: BlockNumberFor<T>,
|
||||
) -> DispatchResult {
|
||||
T::ForceDepositOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::do_force_deposit(account, block).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Permissionlessly release a deposit for an account that entered safe-mode at a
|
||||
/// given historical block.
|
||||
///
|
||||
/// The call can be completely disabled by setting [`Config::ReleaseDelay`] to `None`.
|
||||
/// This cannot be called while safe-mode is entered and not until
|
||||
/// [`Config::ReleaseDelay`] blocks have passed since safe-mode was entered.
|
||||
///
|
||||
/// Emits a [`Event::DepositReleased`] event on success.
|
||||
/// Errors with [`Error::Entered`] if the safe-mode is entered.
|
||||
/// Errors with [`Error::CannotReleaseYet`] if [`Config::ReleaseDelay`] block have not
|
||||
/// passed since safe-mode was entered. Errors with [`Error::NoDeposit`] if the payee has no
|
||||
/// reserved currency at the block specified.
|
||||
#[pallet::call_index(6)]
|
||||
#[pallet::weight(T::WeightInfo::release_deposit())]
|
||||
pub fn release_deposit(
|
||||
origin: OriginFor<T>,
|
||||
account: T::AccountId,
|
||||
block: BlockNumberFor<T>,
|
||||
) -> DispatchResult {
|
||||
ensure_signed(origin)?;
|
||||
|
||||
Self::do_release(false, account, block).map_err(Into::into)
|
||||
}
|
||||
|
||||
/// Force to release a deposit for an account that entered safe-mode at a given
|
||||
/// historical block.
|
||||
///
|
||||
/// This can be called while safe-mode is still entered.
|
||||
///
|
||||
/// Emits a [`Event::DepositReleased`] event on success.
|
||||
/// Errors with [`Error::Entered`] if safe-mode is entered.
|
||||
/// Errors with [`Error::NoDeposit`] if the payee has no reserved currency at the
|
||||
/// specified block.
|
||||
///
|
||||
/// Can only be called by the [`Config::ForceDepositOrigin`] origin.
|
||||
#[pallet::call_index(7)]
|
||||
#[pallet::weight(T::WeightInfo::force_release_deposit())]
|
||||
pub fn force_release_deposit(
|
||||
origin: OriginFor<T>,
|
||||
account: T::AccountId,
|
||||
block: BlockNumberFor<T>,
|
||||
) -> DispatchResult {
|
||||
T::ForceDepositOrigin::ensure_origin(origin)?;
|
||||
|
||||
Self::do_release(true, account, block).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
#[pallet::hooks]
|
||||
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
|
||||
/// Automatically exits safe-mode when the current block number is greater than
|
||||
/// [`EnteredUntil`].
|
||||
fn on_initialize(current: BlockNumberFor<T>) -> Weight {
|
||||
let Some(limit) = EnteredUntil::<T>::get() else {
|
||||
return T::WeightInfo::on_initialize_noop();
|
||||
};
|
||||
|
||||
if current > limit {
|
||||
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");
|
||||
T::WeightInfo::on_initialize_exit()
|
||||
} else {
|
||||
T::WeightInfo::on_initialize_noop()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Logic for the [`crate::Pallet::enter`] and [`crate::Pallet::force_enter`] calls.
|
||||
pub(crate) fn do_enter(
|
||||
who: Option<T::AccountId>,
|
||||
duration: BlockNumberFor<T>,
|
||||
) -> Result<(), Error<T>> {
|
||||
ensure!(!Self::is_entered(), Error::<T>::Entered);
|
||||
|
||||
if let Some(who) = who {
|
||||
let amount = T::EnterDepositAmount::get().ok_or(Error::<T>::NotConfigured)?;
|
||||
Self::hold(who, amount)?;
|
||||
}
|
||||
|
||||
let until = <frame_system::Pallet<T>>::block_number().saturating_add(duration);
|
||||
EnteredUntil::<T>::put(until);
|
||||
Self::deposit_event(Event::Entered { until });
|
||||
T::Notify::entered();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Logic for the [`crate::Pallet::extend`] and [`crate::Pallet::force_extend`] calls.
|
||||
pub(crate) fn do_extend(
|
||||
who: Option<T::AccountId>,
|
||||
duration: BlockNumberFor<T>,
|
||||
) -> Result<(), Error<T>> {
|
||||
let mut until = EnteredUntil::<T>::get().ok_or(Error::<T>::Exited)?;
|
||||
|
||||
if let Some(who) = who {
|
||||
let amount = T::ExtendDepositAmount::get().ok_or(Error::<T>::NotConfigured)?;
|
||||
Self::hold(who, amount)?;
|
||||
}
|
||||
|
||||
until.saturating_accrue(duration);
|
||||
EnteredUntil::<T>::put(until);
|
||||
Self::deposit_event(Event::<T>::Extended { until });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Logic for the [`crate::Pallet::force_exit`] call.
|
||||
///
|
||||
/// Errors if safe-mode is already exited.
|
||||
pub(crate) fn do_exit(reason: ExitReason) -> Result<(), Error<T>> {
|
||||
let _until = EnteredUntil::<T>::take().ok_or(Error::<T>::Exited)?;
|
||||
Self::deposit_event(Event::Exited { reason });
|
||||
T::Notify::exited();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Logic for the [`crate::Pallet::release_deposit`] and
|
||||
/// [`crate::Pallet::force_release_deposit`] calls.
|
||||
pub(crate) fn do_release(
|
||||
force: bool,
|
||||
account: T::AccountId,
|
||||
block: BlockNumberFor<T>,
|
||||
) -> Result<(), Error<T>> {
|
||||
let amount = Deposits::<T>::take(&account, &block).ok_or(Error::<T>::NoDeposit)?;
|
||||
|
||||
if !force {
|
||||
ensure!(!Self::is_entered(), Error::<T>::Entered);
|
||||
|
||||
let delay = T::ReleaseDelay::get().ok_or(Error::<T>::NotConfigured)?;
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
ensure!(now > block.saturating_add(delay), Error::<T>::CannotReleaseYet);
|
||||
}
|
||||
|
||||
let amount = T::Currency::release(
|
||||
&&HoldReason::EnterOrExtend.into(),
|
||||
&account,
|
||||
amount,
|
||||
Precision::BestEffort,
|
||||
)
|
||||
.map_err(|_| Error::<T>::CurrencyError)?;
|
||||
Self::deposit_event(Event::<T>::DepositReleased { account, amount });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Logic for the [`crate::Pallet::slash_deposit`] call.
|
||||
pub(crate) fn do_force_deposit(
|
||||
account: T::AccountId,
|
||||
block: BlockNumberFor<T>,
|
||||
) -> Result<(), Error<T>> {
|
||||
let amount = Deposits::<T>::take(&account, block).ok_or(Error::<T>::NoDeposit)?;
|
||||
|
||||
let burned = T::Currency::burn_held(
|
||||
&&HoldReason::EnterOrExtend.into(),
|
||||
&account,
|
||||
amount,
|
||||
Precision::BestEffort,
|
||||
Fortitude::Force,
|
||||
)
|
||||
.map_err(|_| Error::<T>::CurrencyError)?;
|
||||
defensive_assert!(burned == amount, "Could not burn the full held amount");
|
||||
Self::deposit_event(Event::<T>::DepositSlashed { account, amount });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Place a hold for exactly `amount` and store it in `Deposits`.
|
||||
///
|
||||
/// Errors if the account already has a hold for the same reason.
|
||||
fn hold(who: T::AccountId, amount: BalanceOf<T>) -> Result<(), Error<T>> {
|
||||
let block = <frame_system::Pallet<T>>::block_number();
|
||||
if !T::Currency::balance_on_hold(&HoldReason::EnterOrExtend.into(), &who).is_zero() {
|
||||
return Err(Error::<T>::AlreadyDeposited.into())
|
||||
}
|
||||
|
||||
T::Currency::hold(&HoldReason::EnterOrExtend.into(), &who, amount)
|
||||
.map_err(|_| Error::<T>::CurrencyError)?;
|
||||
Deposits::<T>::insert(&who, block, amount);
|
||||
Self::deposit_event(Event::<T>::DepositPlaced { account: who, amount });
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Return whether `safe-mode` is entered.
|
||||
pub fn is_entered() -> bool {
|
||||
EnteredUntil::<T>::exists()
|
||||
}
|
||||
|
||||
/// Return whether the given call is allowed to be dispatched.
|
||||
pub fn is_allowed(call: &T::RuntimeCall) -> bool
|
||||
where
|
||||
T::RuntimeCall: GetCallMetadata,
|
||||
{
|
||||
let CallMetadata { pallet_name, .. } = call.get_call_metadata();
|
||||
// SAFETY: The `SafeMode` pallet is always allowed.
|
||||
if pallet_name == <Pallet<T> as PalletInfoAccess>::name() {
|
||||
return true
|
||||
}
|
||||
|
||||
if Self::is_entered() {
|
||||
T::WhitelistedCalls::contains(call)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> Contains<T::RuntimeCall> for Pallet<T>
|
||||
where
|
||||
T::RuntimeCall: GetCallMetadata,
|
||||
{
|
||||
/// Return whether the given call is allowed to be dispatched.
|
||||
fn contains(call: &T::RuntimeCall) -> bool {
|
||||
Pallet::<T>::is_allowed(call)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> frame_support::traits::SafeMode for Pallet<T> {
|
||||
type BlockNumber = BlockNumberFor<T>;
|
||||
|
||||
fn is_entered() -> bool {
|
||||
Self::is_entered()
|
||||
}
|
||||
|
||||
fn remaining() -> Option<BlockNumberFor<T>> {
|
||||
EnteredUntil::<T>::get().map(|until| {
|
||||
let now = <frame_system::Pallet<T>>::block_number();
|
||||
until.saturating_sub(now)
|
||||
})
|
||||
}
|
||||
|
||||
fn enter(duration: BlockNumberFor<T>) -> Result<(), frame_support::traits::SafeModeError> {
|
||||
Self::do_enter(None, duration).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn extend(duration: BlockNumberFor<T>) -> Result<(), frame_support::traits::SafeModeError> {
|
||||
Self::do_extend(None, duration).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn exit() -> Result<(), frame_support::traits::SafeModeError> {
|
||||
Self::do_exit(ExitReason::Force).map_err(Into::into)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Config> From<Error<T>> for frame_support::traits::SafeModeError {
|
||||
fn from(err: Error<T>) -> Self {
|
||||
match err {
|
||||
Error::<T>::Entered => Self::AlreadyEntered,
|
||||
Error::<T>::Exited => Self::AlreadyExited,
|
||||
_ => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user