[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:
Oliver Tale-Yazdi
2023-08-25 02:56:00 +02:00
committed by GitHub
parent 46bd466e48
commit 3710edfedc
22 changed files with 3339 additions and 6 deletions
+39
View File
@@ -4281,6 +4281,7 @@ dependencies = [
"pallet-referenda",
"pallet-remark",
"pallet-root-testing",
"pallet-safe-mode",
"pallet-salary",
"pallet-scheduler",
"pallet-session",
@@ -4298,6 +4299,7 @@ dependencies = [
"pallet-transaction-payment-rpc-runtime-api",
"pallet-transaction-storage",
"pallet-treasury",
"pallet-tx-pause",
"pallet-uniques",
"pallet-utility",
"pallet-vesting",
@@ -7430,6 +7432,25 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-safe-mode"
version = "4.0.0-dev"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"pallet-balances",
"pallet-proxy",
"pallet-utility",
"parity-scale-codec",
"scale-info",
"sp-arithmetic",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-salary"
version = "4.0.0-dev"
@@ -7798,6 +7819,24 @@ dependencies = [
"sp-std",
]
[[package]]
name = "pallet-tx-pause"
version = "4.0.0-dev"
dependencies = [
"frame-benchmarking",
"frame-support",
"frame-system",
"pallet-balances",
"pallet-proxy",
"pallet-utility",
"parity-scale-codec",
"scale-info",
"sp-core",
"sp-io",
"sp-runtime",
"sp-std",
]
[[package]]
name = "pallet-uniques"
version = "4.0.0-dev"
+2
View File
@@ -188,6 +188,8 @@ members = [
"frame/recovery",
"frame/referenda",
"frame/remark",
"frame/tx-pause",
"frame/safe-mode",
"frame/salary",
"frame/scheduler",
"frame/scored-pool",
+2
View File
@@ -358,6 +358,8 @@ pub fn testnet_genesis(
transaction_storage: Default::default(),
transaction_payment: Default::default(),
alliance: Default::default(),
safe_mode: Default::default(),
tx_pause: Default::default(),
alliance_motion: Default::default(),
nomination_pools: NominationPoolsConfig {
min_create_bond: 10 * DOLLARS,
+8
View File
@@ -129,6 +129,8 @@ pallet-transaction-storage = { version = "4.0.0-dev", default-features = false,
pallet-uniques = { version = "4.0.0-dev", default-features = false, path = "../../../frame/uniques" }
pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/vesting" }
pallet-whitelist = { version = "4.0.0-dev", default-features = false, path = "../../../frame/whitelist" }
pallet-tx-pause = { version = "4.0.0-dev", default-features = false, path = "../../../frame/tx-pause" }
pallet-safe-mode = { version = "4.0.0-dev", default-features = false, path = "../../../frame/safe-mode" }
[build-dependencies]
substrate-wasm-builder = { version = "5.0.0-dev", path = "../../../utils/wasm-builder", optional = true }
@@ -200,6 +202,7 @@ std = [
"pallet-referenda/std",
"pallet-remark/std",
"pallet-root-testing/std",
"pallet-safe-mode/std",
"pallet-salary/std",
"pallet-scheduler/std",
"pallet-session-benchmarking?/std",
@@ -216,6 +219,7 @@ std = [
"pallet-transaction-payment/std",
"pallet-transaction-storage/std",
"pallet-treasury/std",
"pallet-tx-pause/std",
"pallet-uniques/std",
"pallet-utility/std",
"pallet-vesting/std",
@@ -290,6 +294,7 @@ runtime-benchmarks = [
"pallet-recovery/runtime-benchmarks",
"pallet-referenda/runtime-benchmarks",
"pallet-remark/runtime-benchmarks",
"pallet-safe-mode/runtime-benchmarks",
"pallet-salary/runtime-benchmarks",
"pallet-scheduler/runtime-benchmarks",
"pallet-session-benchmarking/runtime-benchmarks",
@@ -301,6 +306,7 @@ runtime-benchmarks = [
"pallet-tips/runtime-benchmarks",
"pallet-transaction-storage/runtime-benchmarks",
"pallet-treasury/runtime-benchmarks",
"pallet-tx-pause/runtime-benchmarks",
"pallet-uniques/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"pallet-vesting/runtime-benchmarks",
@@ -360,6 +366,7 @@ try-runtime = [
"pallet-referenda/try-runtime",
"pallet-remark/try-runtime",
"pallet-root-testing/try-runtime",
"pallet-safe-mode/try-runtime",
"pallet-salary/try-runtime",
"pallet-scheduler/try-runtime",
"pallet-session/try-runtime",
@@ -373,6 +380,7 @@ try-runtime = [
"pallet-transaction-payment/try-runtime",
"pallet-transaction-storage/try-runtime",
"pallet-treasury/try-runtime",
"pallet-tx-pause/try-runtime",
"pallet-uniques/try-runtime",
"pallet-utility/try-runtime",
"pallet-vesting/try-runtime",
+70 -6
View File
@@ -37,9 +37,9 @@ use frame_support::{
traits::{
fungible::{Balanced, Credit, ItemOf},
tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount},
AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Currency, EitherOfDiverse,
EqualPrivilegeOnly, Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem,
LockIdentifier, Nothing, OnUnbalanced, WithdrawReasons,
AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency,
EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter,
KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, WithdrawReasons,
},
weights::{
constants::{
@@ -64,6 +64,7 @@ use pallet_nis::WithMaximumOf;
use pallet_session::historical as pallet_session_historical;
pub use pallet_transaction_payment::{CurrencyAdapter, Multiplier, TargetedFeeAdjustment};
use pallet_transaction_payment::{FeeDetails, RuntimeDispatchInfo};
use pallet_tx_pause::RuntimeCallNameOf;
use sp_api::impl_runtime_apis;
use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId;
use sp_consensus_grandpa::AuthorityId as GrandpaId;
@@ -220,8 +221,67 @@ parameter_types! {
const_assert!(NORMAL_DISPATCH_RATIO.deconstruct() >= AVERAGE_ON_INITIALIZE_RATIO.deconstruct());
/// Calls that can bypass the safe-mode pallet.
pub struct SafeModeWhitelistedCalls;
impl Contains<RuntimeCall> for SafeModeWhitelistedCalls {
fn contains(call: &RuntimeCall) -> bool {
match call {
RuntimeCall::System(_) | RuntimeCall::SafeMode(_) | RuntimeCall::TxPause(_) => true,
_ => false,
}
}
}
/// Calls that cannot be paused by the tx-pause pallet.
pub struct TxPauseWhitelistedCalls;
/// Whitelist `Balances::transfer_keep_alive`, all others are pauseable.
impl Contains<RuntimeCallNameOf<Runtime>> for TxPauseWhitelistedCalls {
fn contains(full_name: &RuntimeCallNameOf<Runtime>) -> bool {
match (full_name.0.as_slice(), full_name.1.as_slice()) {
(b"Balances", b"transfer_keep_alive") => true,
_ => false,
}
}
}
impl pallet_tx_pause::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PauseOrigin = EnsureRoot<AccountId>;
type UnpauseOrigin = EnsureRoot<AccountId>;
type WhitelistedCalls = TxPauseWhitelistedCalls;
type MaxNameLen = ConstU32<256>;
type WeightInfo = pallet_tx_pause::weights::SubstrateWeight<Runtime>;
}
parameter_types! {
pub const EnterDuration: BlockNumber = 4 * HOURS;
pub const EnterDepositAmount: Balance = 2_000_000 * DOLLARS;
pub const ExtendDuration: BlockNumber = 2 * HOURS;
pub const ExtendDepositAmount: Balance = 1_000_000 * DOLLARS;
pub const ReleaseDelay: u32 = 2 * DAYS;
}
impl pallet_safe_mode::Config for Runtime {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type RuntimeHoldReason = RuntimeHoldReason;
type WhitelistedCalls = SafeModeWhitelistedCalls;
type EnterDuration = EnterDuration;
type EnterDepositAmount = EnterDepositAmount;
type ExtendDuration = ExtendDuration;
type ExtendDepositAmount = ExtendDepositAmount;
type ForceEnterOrigin = EnsureRootWithSuccess<AccountId, ConstU32<9>>;
type ForceExtendOrigin = EnsureRootWithSuccess<AccountId, ConstU32<11>>;
type ForceExitOrigin = EnsureRoot<AccountId>;
type ForceDepositOrigin = EnsureRoot<AccountId>;
type ReleaseDelay = ReleaseDelay;
type Notify = ();
type WeightInfo = pallet_safe_mode::weights::SubstrateWeight<Runtime>;
}
impl frame_system::Config for Runtime {
type BaseCallFilter = Everything;
type BaseCallFilter = InsideBoth<SafeMode, TxPause>;
type BlockWeights = RuntimeBlockWeights;
type BlockLength = RuntimeBlockLength;
type DbWeight = RocksDbWeight;
@@ -1682,8 +1742,8 @@ impl pallet_core_fellowship::Config for Runtime {
type Balance = Balance;
type ParamsOrigin = frame_system::EnsureRoot<AccountId>;
type InductOrigin = pallet_core_fellowship::EnsureInducted<Runtime, (), 1>;
type ApproveOrigin = frame_system::EnsureRootWithSuccess<AccountId, ConstU16<9>>;
type PromoteOrigin = frame_system::EnsureRootWithSuccess<AccountId, ConstU16<9>>;
type ApproveOrigin = EnsureRootWithSuccess<AccountId, ConstU16<9>>;
type PromoteOrigin = EnsureRootWithSuccess<AccountId, ConstU16<9>>;
type EvidenceSize = ConstU32<16_384>;
}
@@ -2021,6 +2081,8 @@ construct_runtime!(
FastUnstake: pallet_fast_unstake,
MessageQueue: pallet_message_queue,
Pov: frame_benchmarking_pallet_pov,
TxPause: pallet_tx_pause,
SafeMode: pallet_safe_mode,
Statement: pallet_statement,
Broker: pallet_broker,
}
@@ -2153,6 +2215,8 @@ mod benches {
[pallet_utility, Utility]
[pallet_vesting, Vesting]
[pallet_whitelist, Whitelist]
[pallet_tx_pause, TxPause]
[pallet_safe_mode, SafeMode]
);
}
@@ -101,6 +101,8 @@ pub fn config_endowed(code: Option<&[u8]>, extra_endowed: Vec<AccountId>) -> Run
alliance: Default::default(),
alliance_motion: Default::default(),
nomination_pools: Default::default(),
safe_mode: Default::default(),
tx_pause: Default::default(),
glutton: GluttonConfig {
compute: Default::default(),
storage: Default::default(),
+67
View File
@@ -0,0 +1,67 @@
[package]
name = "pallet-safe-mode"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME safe-mode pallet"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../primitives/arithmetic" }
sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" }
pallet-balances = { version = "4.0.0-dev", path = "../balances", default-features = false, optional = true }
pallet-utility = { version = "4.0.0-dev", path = "../utility", default-features = false, optional = true }
pallet-proxy = { version = "4.0.0-dev", path = "../proxy", default-features = false, optional = true }
[dev-dependencies]
sp-core = { version = "21.0.0", path = "../../primitives/core" }
sp-io = { version = "23.0.0", path = "../../primitives/io" }
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
pallet-utility = { version = "4.0.0-dev", path = "../utility" }
pallet-proxy = { version = "4.0.0-dev", path = "../proxy" }
[features]
default = [ "std" ]
std = [
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-balances?/std",
"pallet-proxy?/std",
"pallet-utility?/std",
"scale-info/std",
"sp-arithmetic/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances?/try-runtime",
"pallet-proxy?/try-runtime",
"pallet-utility?/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,236 @@
// 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(feature = "runtime-benchmarks")]
use super::{Pallet as SafeMode, *};
use frame_benchmarking::v2::*;
use frame_support::traits::{fungible::Mutate as FunMutate, UnfilteredDispatchable};
use frame_system::{Pallet as System, RawOrigin};
use sp_runtime::traits::{Bounded, One, Zero};
#[benchmarks(where T::Currency: FunMutate<T::AccountId>)]
mod benchmarks {
use super::*;
/// `on_initialize` doing nothing.
#[benchmark]
fn on_initialize_noop() {
#[block]
{
SafeMode::<T>::on_initialize(1u32.into());
}
}
/// `on_initialize` exiting since the until block is in the past.
#[benchmark]
fn on_initialize_exit() {
EnteredUntil::<T>::put(&BlockNumberFor::<T>::zero());
assert!(SafeMode::<T>::is_entered());
#[block]
{
SafeMode::<T>::on_initialize(1u32.into());
}
assert!(!SafeMode::<T>::is_entered());
}
/// Permissionless enter - if configured.
#[benchmark]
fn enter() -> Result<(), BenchmarkError> {
T::EnterDepositAmount::get().ok_or_else(|| BenchmarkError::Weightless)?;
let caller: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(caller.clone());
T::Currency::set_balance(&caller, init_bal::<T>());
#[extrinsic_call]
_(origin);
assert_eq!(
EnteredUntil::<T>::get().unwrap(),
System::<T>::block_number() + T::EnterDuration::get()
);
Ok(())
}
/// Forceful enter - if configured.
#[benchmark]
fn force_enter() -> Result<(), BenchmarkError> {
let force_origin =
T::ForceEnterOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
let duration = T::ForceEnterOrigin::ensure_origin(force_origin.clone()).unwrap();
#[extrinsic_call]
_(force_origin as T::RuntimeOrigin);
assert_eq!(EnteredUntil::<T>::get().unwrap(), System::<T>::block_number() + duration);
Ok(())
}
/// Permissionless extend - if configured.
#[benchmark]
fn extend() -> Result<(), BenchmarkError> {
T::ExtendDepositAmount::get().ok_or_else(|| BenchmarkError::Weightless)?;
let alice: T::AccountId = whitelisted_caller();
T::Currency::set_balance(&alice, init_bal::<T>());
System::<T>::set_block_number(1u32.into());
assert!(SafeMode::<T>::do_enter(None, 1u32.into()).is_ok());
#[extrinsic_call]
_(RawOrigin::Signed(alice));
assert_eq!(
EnteredUntil::<T>::get().unwrap(),
System::<T>::block_number() + 1u32.into() + T::ExtendDuration::get()
);
Ok(())
}
/// Forceful extend - if configured.
#[benchmark]
fn force_extend() -> Result<(), BenchmarkError> {
let force_origin = T::ForceExtendOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
System::<T>::set_block_number(1u32.into());
assert!(SafeMode::<T>::do_enter(None, 1u32.into()).is_ok());
let duration = T::ForceExtendOrigin::ensure_origin(force_origin.clone()).unwrap();
let call = Call::<T>::force_extend {};
#[block]
{
call.dispatch_bypass_filter(force_origin)?;
}
assert_eq!(
EnteredUntil::<T>::get().unwrap(),
System::<T>::block_number() + 1u32.into() + duration
);
Ok(())
}
/// Forceful exit - if configured.
#[benchmark]
fn force_exit() -> Result<(), BenchmarkError> {
let force_origin =
T::ForceExitOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
assert!(SafeMode::<T>::do_enter(None, 1u32.into()).is_ok());
#[extrinsic_call]
_(force_origin as T::RuntimeOrigin);
assert_eq!(EnteredUntil::<T>::get(), None);
Ok(())
}
/// Permissionless release of a deposit - if configured.
#[benchmark]
fn release_deposit() -> Result<(), BenchmarkError> {
let delay = T::ReleaseDelay::get().ok_or_else(|| BenchmarkError::Weightless)?;
let alice: T::AccountId = whitelisted_caller();
let origin = RawOrigin::Signed(alice.clone());
T::Currency::set_balance(&alice, init_bal::<T>());
// Mock the storage. This is needed in case the `EnterDepositAmount` is zero.
let block: BlockNumberFor<T> = 1u32.into();
let bal: BalanceOf<T> = 1u32.into();
Deposits::<T>::insert(&alice, &block, &bal);
T::Currency::hold(&HoldReason::EnterOrExtend.into(), &alice, bal)?;
EnteredUntil::<T>::put(&block);
assert!(SafeMode::<T>::do_exit(ExitReason::Force).is_ok());
System::<T>::set_block_number(delay + One::one() + 2u32.into());
System::<T>::on_initialize(System::<T>::block_number());
SafeMode::<T>::on_initialize(System::<T>::block_number());
#[extrinsic_call]
_(origin, alice.clone(), 1u32.into());
assert!(!Deposits::<T>::contains_key(&alice, &block));
assert_eq!(T::Currency::balance(&alice), init_bal::<T>());
Ok(())
}
/// Forceful release of a deposit - if configured.
#[benchmark]
fn force_release_deposit() -> Result<(), BenchmarkError> {
let force_origin = T::ForceDepositOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let alice: T::AccountId = whitelisted_caller();
T::Currency::set_balance(&alice, init_bal::<T>());
// Mock the storage. This is needed in case the `EnterDepositAmount` is zero.
let block: BlockNumberFor<T> = 1u32.into();
let bal: BalanceOf<T> = 1u32.into();
Deposits::<T>::insert(&alice, &block, &bal);
T::Currency::hold(&&HoldReason::EnterOrExtend.into(), &alice, bal)?;
EnteredUntil::<T>::put(&block);
assert_eq!(T::Currency::balance(&alice), init_bal::<T>() - 1u32.into());
assert!(SafeMode::<T>::do_exit(ExitReason::Force).is_ok());
System::<T>::set_block_number(System::<T>::block_number() + One::one());
System::<T>::on_initialize(System::<T>::block_number());
SafeMode::<T>::on_initialize(System::<T>::block_number());
#[extrinsic_call]
_(force_origin as T::RuntimeOrigin, alice.clone(), block);
assert!(!Deposits::<T>::contains_key(&alice, block));
assert_eq!(T::Currency::balance(&alice), init_bal::<T>());
Ok(())
}
#[benchmark]
fn force_slash_deposit() -> Result<(), BenchmarkError> {
let force_origin = T::ForceDepositOrigin::try_successful_origin()
.map_err(|_| BenchmarkError::Weightless)?;
let alice: T::AccountId = whitelisted_caller();
T::Currency::set_balance(&alice, init_bal::<T>());
// Mock the storage. This is needed in case the `EnterDepositAmount` is zero.
let block: BlockNumberFor<T> = 1u32.into();
let bal: BalanceOf<T> = 1u32.into();
Deposits::<T>::insert(&alice, &block, &bal);
T::Currency::hold(&&HoldReason::EnterOrExtend.into(), &alice, bal)?;
EnteredUntil::<T>::put(&block);
assert!(SafeMode::<T>::do_exit(ExitReason::Force).is_ok());
#[extrinsic_call]
_(force_origin as T::RuntimeOrigin, alice.clone(), block);
assert!(!Deposits::<T>::contains_key(&alice, block));
assert_eq!(T::Currency::balance(&alice), init_bal::<T>() - 1u32.into());
Ok(())
}
fn init_bal<T: Config>() -> BalanceOf<T> {
BalanceOf::<T>::max_value() / 10u32.into()
}
impl_benchmark_test_suite!(SafeMode, crate::mock::new_test_ext(), crate::mock::Test);
}
+596
View File
@@ -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,
}
}
}
+270
View File
@@ -0,0 +1,270 @@
// 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.
//! Tests and test utilities for safe mode pallet.
#![cfg(test)]
use super::*;
use crate as pallet_safe_mode;
use frame_support::{
parameter_types,
traits::{ConstU64, Everything, InsideBoth, InstanceFilter, IsInVec, SafeModeNotify},
};
use frame_system::EnsureSignedBy;
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
impl frame_system::Config for Test {
type BaseCallFilter = InsideBoth<Everything, SafeMode>;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type Block = Block;
type RuntimeEvent = RuntimeEvent;
type BlockHashCount = ConstU64<250>;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
/// Identifies a hold on an account's balance.
#[derive(
Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, TypeInfo,
)]
pub enum HoldReason {
/// The safe-mode pallet holds funds since an account either entered or extended the safe-mode.
SafeMode,
}
impl pallet_balances::Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ConstU64<2>;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = ();
type MaxReserves = ConstU32<10>;
type ReserveIdentifier = [u8; 8];
type RuntimeHoldReason = RuntimeHoldReason;
type FreezeIdentifier = ();
type MaxHolds = ConstU32<10>;
type MaxFreezes = ConstU32<0>;
}
impl pallet_utility::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
/// Mocked proxies to check that the safe-mode also works with the proxy pallet.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
RuntimeDebug,
MaxEncodedLen,
scale_info::TypeInfo,
)]
pub enum ProxyType {
Any,
JustTransfer,
JustUtility,
}
impl Default for ProxyType {
fn default() -> Self {
Self::Any
}
}
impl InstanceFilter<RuntimeCall> for ProxyType {
fn filter(&self, c: &RuntimeCall) -> bool {
match self {
ProxyType::Any => true,
ProxyType::JustTransfer => {
matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. }))
},
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
}
}
fn is_superset(&self, o: &Self) -> bool {
self == &ProxyType::Any || self == o
}
}
impl pallet_proxy::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type Currency = Balances;
type ProxyType = ProxyType;
type ProxyDepositBase = ConstU64<1>;
type ProxyDepositFactor = ConstU64<1>;
type MaxProxies = ConstU32<4>;
type WeightInfo = ();
type CallHasher = BlakeTwo256;
type MaxPending = ConstU32<2>;
type AnnouncementDepositBase = ConstU64<1>;
type AnnouncementDepositFactor = ConstU64<1>;
}
/// The calls that can always bypass safe-mode.
pub struct WhitelistedCalls;
impl Contains<RuntimeCall> for WhitelistedCalls {
fn contains(call: &RuntimeCall) -> bool {
match call {
RuntimeCall::Balances(_) => false,
_ => true,
}
}
}
parameter_types! {
pub const EnterDuration: u64 = 7;
pub const ExtendDuration: u64 = 10;
pub const EnterDepositAmount: u64 = 100;
pub const ExtendDepositAmount: u64 = 100;
pub const ReleaseDelay: u64 = 20;
pub const SafeModeHoldReason: HoldReason = HoldReason::SafeMode;
pub const ForceEnterWeak: u64 = 3;
pub const ForceEnterStrong: u64 = 5;
pub const ForceExtendWeak: u64 = 11;
pub const ForceExtendStrong: u64 = 15;
// NOTE: The account ID maps to the duration. Easy for testing.
pub ForceEnterOrigins: Vec<u64> = vec![ForceEnterWeak::get(), ForceEnterStrong::get()];
pub ForceExtendOrigins: Vec<u64> = vec![ForceExtendWeak::get(), ForceExtendStrong::get()];
pub storage Notifications: Vec<(u64, bool)> = vec![];
}
pub struct MockedNotify;
impl SafeModeNotify for MockedNotify {
fn entered() {
let mut ns = Notifications::get();
ns.push((<frame_system::Pallet<Test>>::block_number(), true));
Notifications::set(&ns);
}
fn exited() {
let mut ns = Notifications::get();
ns.push((<frame_system::Pallet<Test>>::block_number(), false));
Notifications::set(&ns);
}
}
frame_support::ord_parameter_types! {
pub const ForceExitOrigin: u64 = 100;
pub const ForceDepositOrigin: u64 = 200;
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type Currency = Balances;
type RuntimeHoldReason = RuntimeHoldReason;
type WhitelistedCalls = WhitelistedCalls;
type EnterDuration = EnterDuration;
type EnterDepositAmount = EnterDepositAmount;
type ExtendDuration = ExtendDuration;
type ExtendDepositAmount = ExtendDepositAmount;
type ForceEnterOrigin = EnsureSignedBy<IsInVec<ForceEnterOrigins>, u64>;
type ForceExtendOrigin = EnsureSignedBy<IsInVec<ForceExtendOrigins>, u64>;
type ForceExitOrigin = EnsureSignedBy<ForceExitOrigin, Self::AccountId>;
type ForceDepositOrigin = EnsureSignedBy<ForceDepositOrigin, Self::AccountId>;
type ReleaseDelay = ReleaseDelay;
type Notify = MockedNotify;
type WeightInfo = ();
}
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Utility: pallet_utility,
Proxy: pallet_proxy,
SafeMode: pallet_safe_mode,
}
);
pub const BAL_ACC0: u64 = 1234;
pub const BAL_ACC1: u64 = 5678;
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
// The 0 account is NOT a special origin, the rest may be.
balances: vec![(0, BAL_ACC0), (1, BAL_ACC1), (2, 5678), (3, 5678), (4, 5678)],
}
.assimilate_storage(&mut t)
.unwrap();
pallet_safe_mode::GenesisConfig::<Test> { entered_until: None }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
pub fn next_block() {
SafeMode::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
SafeMode::on_initialize(System::block_number());
}
pub fn run_to(n: u64) {
while System::block_number() < n {
next_block();
}
}
+613
View File
@@ -0,0 +1,613 @@
// 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.
//! Test utilities for the safe mode pallet.
#![cfg(test)]
use super::*;
use crate::mock::{RuntimeCall, *};
use frame_support::{assert_err, assert_noop, assert_ok, dispatch::Dispatchable, traits::Currency};
use sp_runtime::TransactionOutcome;
/// Do something hypothetically by rolling back any changes afterwards.
///
/// Returns the original result of the closure.
macro_rules! hypothetically {
( $e:expr ) => {
frame_support::storage::transactional::with_transaction(
|| -> TransactionOutcome<Result<_, sp_runtime::DispatchError>> {
sp_runtime::TransactionOutcome::Rollback(Ok($e))
},
)
.expect("Always returning Ok; qed")
};
}
/// Assert something to be [*hypothetically*] `Ok` without actually committing it.
///
/// Reverts any storage changes made by the closure.
macro_rules! hypothetically_ok {
($e:expr $(, $args:expr)* $(,)?) => {
let result = hypothetically!($e);
assert_ok!(result $(, $args)*);
};
}
#[test]
fn fails_to_filter_calls_to_safe_mode_pallet() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
let activated_at_block = System::block_number();
assert_err!(
call_transfer().dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
next_block();
assert_ok!(SafeMode::extend(RuntimeOrigin::signed(1)));
assert_ok!(SafeMode::force_extend(signed(ForceExtendStrong::get())));
assert_err!(
call_transfer().dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
assert_ok!(SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceExitOrigin::get())));
assert_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block
));
next_block();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_err!(
call_transfer().dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
assert_ok!(SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceExitOrigin::get())));
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block + 2
));
});
}
#[test]
fn fails_to_activate_if_activated() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_noop!(SafeMode::enter(RuntimeOrigin::signed(2)), Error::<Test>::Entered);
});
}
#[test]
fn fails_to_extend_if_not_activated() {
new_test_ext().execute_with(|| {
assert_eq!(EnteredUntil::<Test>::get(), None);
assert_noop!(SafeMode::extend(RuntimeOrigin::signed(2)), Error::<Test>::Exited);
});
}
#[test]
fn fails_to_force_release_deposits_with_wrong_block() {
new_test_ext().execute_with(|| {
let activated_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
run_to(mock::EnterDuration::get() + activated_at_block + 1);
assert_err!(
SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block + 1
),
Error::<Test>::NoDeposit
);
assert_err!(
SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block + 1
),
Error::<Test>::NoDeposit
);
});
}
#[test]
fn fails_to_release_deposits_too_early() {
new_test_ext().execute_with(|| {
let activated_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceExitOrigin::get())));
assert_err!(
SafeMode::release_deposit(RuntimeOrigin::signed(2), 0, activated_at_block),
Error::<Test>::CannotReleaseYet
);
run_to(activated_at_block + mock::ReleaseDelay::get() + 1);
assert_ok!(SafeMode::release_deposit(RuntimeOrigin::signed(2), 0, activated_at_block));
});
}
// GENERAL SUCCESS/POSITIVE TESTS ---------------------
#[test]
fn can_automatically_deactivate_after_timeout() {
new_test_ext().execute_with(|| {
let activated_at_block = System::block_number();
assert_ok!(SafeMode::force_enter(signed(ForceEnterWeak::get())));
run_to(1 + activated_at_block + ForceEnterWeak::get());
assert_eq!(EnteredUntil::<Test>::get(), None);
});
}
#[test]
fn can_filter_balance_calls_when_activated() {
new_test_ext().execute_with(|| {
assert_ok!(call_transfer().dispatch(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_err!(
call_transfer().dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
});
}
#[test]
fn can_filter_balance_in_batch_when_activated() {
new_test_ext().execute_with(|| {
let batch_call =
RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![call_transfer()] });
assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0)));
System::assert_last_event(
pallet_utility::Event::BatchInterrupted {
index: 0,
error: frame_system::Error::<Test>::CallFiltered.into(),
}
.into(),
);
});
}
#[test]
fn can_filter_balance_in_proxy_when_activated() {
new_test_ext().execute_with(|| {
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::JustTransfer, 0));
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, Box::new(call_transfer())));
System::assert_last_event(pallet_proxy::Event::ProxyExecuted { result: Ok(()) }.into());
assert_ok!(SafeMode::force_enter(signed(ForceEnterWeak::get())));
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, Box::new(call_transfer())));
System::assert_last_event(
pallet_proxy::Event::ProxyExecuted {
result: DispatchError::from(frame_system::Error::<Test>::CallFiltered).into(),
}
.into(),
);
});
}
#[test]
fn can_activate() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_eq!(
EnteredUntil::<Test>::get().unwrap(),
System::block_number() + mock::EnterDuration::get()
);
assert_eq!(Balances::reserved_balance(0), mock::EnterDepositAmount::get());
assert_eq!(Notifications::get(), vec![(1, true)]);
assert_noop!(SafeMode::enter(RuntimeOrigin::signed(0)), Error::<Test>::Entered);
assert_eq!(Notifications::get(), vec![(1, true)]);
// Assert the deposit.
assert_eq!(Deposits::<Test>::get(0, 1), Some(mock::EnterDepositAmount::get()));
});
}
#[test]
fn notify_works() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_eq!(Notifications::get(), vec![(1, true)]);
run_to(10);
assert_eq!(Notifications::get(), vec![(1, true), (9, false)]);
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(1)));
assert_ok!(SafeMode::extend(RuntimeOrigin::signed(2)));
run_to(30);
assert_eq!(Notifications::get(), vec![(1, true), (9, false), (10, true), (28, false)]);
});
}
#[test]
fn cannot_extend() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_err!(SafeMode::extend(RuntimeOrigin::signed(0)), Error::<Test>::AlreadyDeposited);
assert_eq!(
EnteredUntil::<Test>::get().unwrap(),
System::block_number() + mock::EnterDuration::get()
);
assert_eq!(Balances::reserved_balance(0), mock::EnterDepositAmount::get());
assert_eq!(Notifications::get(), vec![(1, true)]);
});
}
#[test]
fn fails_signed_origin_when_explicit_origin_required() {
new_test_ext().execute_with(|| {
assert_eq!(EnteredUntil::<Test>::get(), None);
let activated_at_block = System::block_number();
assert_err!(SafeMode::force_enter(RuntimeOrigin::signed(0)), DispatchError::BadOrigin);
assert_err!(SafeMode::force_extend(RuntimeOrigin::signed(0)), DispatchError::BadOrigin);
assert_err!(SafeMode::force_exit(RuntimeOrigin::signed(0)), DispatchError::BadOrigin);
assert_err!(
SafeMode::force_slash_deposit(RuntimeOrigin::signed(0), 0, activated_at_block),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_release_deposit(RuntimeOrigin::signed(0), 0, activated_at_block),
DispatchError::BadOrigin
);
});
}
// CONFIGURED ORIGIN CALL TESTS ---------------------
#[test]
fn fails_force_deactivate_if_not_activated() {
new_test_ext().execute_with(|| {
assert_noop!(
SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceExitOrigin::get())),
Error::<Test>::Exited
);
assert_noop!(
SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceExitOrigin::get())),
Error::<Test>::Exited
);
assert!(Notifications::get().is_empty());
});
}
#[test]
fn can_force_activate_with_config_origin() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::force_enter(signed(ForceEnterStrong::get())));
assert_eq!(Notifications::get(), vec![(1, true)]);
assert_eq!(
EnteredUntil::<Test>::get().unwrap(),
System::block_number() + ForceEnterStrong::get()
);
assert_noop!(
SafeMode::force_enter(signed(ForceEnterStrong::get())),
Error::<Test>::Entered
);
assert_eq!(Notifications::get(), vec![(1, true)]);
});
}
#[test]
fn can_force_deactivate_with_config_origin() {
new_test_ext().execute_with(|| {
assert_eq!(EnteredUntil::<Test>::get(), None);
assert_err!(
SafeMode::force_exit(RuntimeOrigin::signed(ForceExitOrigin::get())),
Error::<Test>::Exited
);
assert_ok!(SafeMode::force_enter(signed(ForceEnterWeak::get())));
assert_ok!(SafeMode::force_exit(RuntimeOrigin::signed(ForceExitOrigin::get())));
assert_eq!(Notifications::get(), vec![(1, true), (1, false)]);
});
}
#[test]
fn can_force_extend_with_config_origin() {
new_test_ext().execute_with(|| {
// Activated by `Weak` and extended by `Medium`.
assert_ok!(SafeMode::force_enter(signed(ForceEnterWeak::get())));
assert_eq!(
EnteredUntil::<Test>::get().unwrap(),
System::block_number() + ForceEnterWeak::get()
);
assert_ok!(SafeMode::force_extend(signed(ForceExtendWeak::get())));
assert_eq!(
EnteredUntil::<Test>::get().unwrap(),
System::block_number() + ForceEnterWeak::get() + ForceExtendWeak::get()
);
});
}
#[test]
fn can_force_release_deposit_with_config_origin() {
new_test_ext().execute_with(|| {
let activated_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
hypothetically_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block
),);
run_to(mock::EnterDuration::get() + activated_at_block + 1);
assert_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block
));
assert_eq!(Balances::free_balance(&0), BAL_ACC0); // accounts set in mock genesis
Balances::make_free_balance_be(&0, BAL_ACC0);
let activated_and_extended_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::extend(RuntimeOrigin::signed(1)));
run_to(
mock::EnterDuration::get() +
mock::ExtendDuration::get() +
activated_and_extended_at_block +
1,
);
assert_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_and_extended_at_block
));
assert_eq!(Balances::free_balance(&0), BAL_ACC0); // accounts set in mock genesis
});
}
#[test]
fn can_release_deposit_while_entered() {
new_test_ext().execute_with(|| {
assert_eq!(System::block_number(), 1);
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert!(SafeMode::is_entered());
assert_eq!(Balances::free_balance(&0), BAL_ACC0 - mock::EnterDepositAmount::get());
// We could slash in the same block or any later.
for i in 0..mock::EnterDuration::get() + 10 {
run_to(i);
hypothetically_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
));
}
// Now once we slash once
assert_ok!(SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),);
assert_eq!(Balances::free_balance(&0), BAL_ACC0);
// ... it wont work ever again.
assert_err!(
SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),
Error::<Test>::NoDeposit
);
});
}
#[test]
fn can_slash_deposit_while_entered() {
new_test_ext().execute_with(|| {
assert_eq!(System::block_number(), 1);
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert!(SafeMode::is_entered());
// We could slash in the same block or any later.
for i in 0..mock::EnterDuration::get() + 10 {
run_to(i);
hypothetically_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
));
}
// Now once we slash once
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),);
// ... it wont work ever again.
assert_err!(
SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),
Error::<Test>::NoDeposit
);
});
}
#[test]
fn can_slash_deposit_from_extend_block() {
new_test_ext().execute_with(|| {
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::extend(RuntimeOrigin::signed(1)));
assert_eq!(Balances::free_balance(&0), BAL_ACC0 - mock::EnterDepositAmount::get());
assert_eq!(Balances::free_balance(&1), BAL_ACC1 - mock::ExtendDepositAmount::get());
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),);
assert_eq!(Balances::free_balance(&0), BAL_ACC0 - mock::EnterDepositAmount::get());
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
1,
1
),);
assert_eq!(Balances::free_balance(&1), BAL_ACC1 - mock::ExtendDepositAmount::get());
// But never again.
assert_err!(
SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
1
),
Error::<Test>::NoDeposit
);
assert_err!(
SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
1,
1
),
Error::<Test>::NoDeposit
);
});
}
#[test]
fn can_slash_deposit_with_config_origin() {
new_test_ext().execute_with(|| {
let activated_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
hypothetically_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block
),);
run_to(mock::EnterDuration::get() + activated_at_block + 1);
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_at_block
));
// accounts set in mock genesis
assert_eq!(Balances::free_balance(&0), BAL_ACC0 - mock::EnterDepositAmount::get());
Balances::make_free_balance_be(&0, BAL_ACC0);
let activated_and_extended_at_block = System::block_number();
assert_ok!(SafeMode::enter(RuntimeOrigin::signed(0)));
assert_ok!(SafeMode::extend(RuntimeOrigin::signed(1)));
run_to(
mock::EnterDuration::get() +
mock::ExtendDuration::get() +
activated_and_extended_at_block +
1,
);
assert_ok!(SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceDepositOrigin::get()),
0,
activated_and_extended_at_block
));
// accounts set in mock genesis
assert_eq!(Balances::free_balance(&0), BAL_ACC0 - mock::EnterDepositAmount::get());
});
}
#[test]
fn fails_when_explicit_origin_required() {
new_test_ext().execute_with(|| {
assert_eq!(EnteredUntil::<Test>::get(), None);
let activated_at_block = System::block_number();
assert_err!(SafeMode::force_extend(signed(1)), DispatchError::BadOrigin);
assert_err!(SafeMode::force_exit(signed(1)), DispatchError::BadOrigin);
assert_err!(
SafeMode::force_slash_deposit(signed(1), 0, activated_at_block),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_release_deposit(signed(1), 0, activated_at_block),
DispatchError::BadOrigin
);
assert_err!(SafeMode::force_enter(signed(1)), DispatchError::BadOrigin);
assert_err!(SafeMode::force_exit(signed(1)), DispatchError::BadOrigin);
assert_err!(
SafeMode::force_slash_deposit(signed(1), 0, activated_at_block),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_release_deposit(signed(1), 0, activated_at_block),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_enter(RuntimeOrigin::signed(mock::ForceExitOrigin::get())),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_extend(RuntimeOrigin::signed(mock::ForceExitOrigin::get())),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_slash_deposit(
RuntimeOrigin::signed(mock::ForceExitOrigin::get()),
0,
activated_at_block
),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_release_deposit(
RuntimeOrigin::signed(mock::ForceExitOrigin::get()),
0,
activated_at_block
),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_enter(RuntimeOrigin::signed(mock::ForceDepositOrigin::get())),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_extend(RuntimeOrigin::signed(mock::ForceDepositOrigin::get())),
DispatchError::BadOrigin
);
assert_err!(
SafeMode::force_exit(RuntimeOrigin::signed(mock::ForceDepositOrigin::get())),
DispatchError::BadOrigin
);
});
}
fn call_transfer() -> RuntimeCall {
RuntimeCall::Balances(pallet_balances::Call::transfer { dest: 1, value: 1 })
}
fn signed(who: u64) -> RuntimeOrigin {
RuntimeOrigin::signed(who)
}
+321
View File
@@ -0,0 +1,321 @@
// 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.
//! Autogenerated weights for `pallet_safe_mode`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-08-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-aahe6cbd-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
// Executed Command:
// target/production/substrate-node
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json
// --pallet=pallet_safe_mode
// --chain=dev
// --header=./HEADER-APACHE2
// --output=./frame/safe-mode/src/weights.rs
// --template=./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pallet_safe_mode`.
pub trait WeightInfo {
fn on_initialize_noop() -> Weight;
fn on_initialize_exit() -> Weight;
fn enter() -> Weight;
fn force_enter() -> Weight;
fn extend() -> Weight;
fn force_extend() -> Weight;
fn force_exit() -> Weight;
fn release_deposit() -> Weight;
fn force_release_deposit() -> Weight;
fn force_slash_deposit() -> Weight;
}
/// Weights for `pallet_safe_mode` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn on_initialize_noop() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `1489`
// Minimum execution time: 2_500_000 picoseconds.
Weight::from_parts(2_594_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn on_initialize_exit() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 8_868_000 picoseconds.
Weight::from_parts(9_415_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::Deposits` (r:0 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
fn enter() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `3550`
// Minimum execution time: 50_541_000 picoseconds.
Weight::from_parts(51_558_000, 3550)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_enter() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `1489`
// Minimum execution time: 10_489_000 picoseconds.
Weight::from_parts(10_833_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::Deposits` (r:0 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
fn extend() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `3550`
// Minimum execution time: 50_818_000 picoseconds.
Weight::from_parts(51_873_000, 3550)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(3_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_extend() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 10_843_000 picoseconds.
Weight::from_parts(11_314_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_exit() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 10_382_000 picoseconds.
Weight::from_parts(10_814_000, 1489)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn release_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 42_828_000 picoseconds.
Weight::from_parts(43_752_000, 3550)
.saturating_add(T::DbWeight::get().reads(3_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn force_release_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 40_196_000 picoseconds.
Weight::from_parts(41_298_000, 3550)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn force_slash_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 33_660_000 picoseconds.
Weight::from_parts(34_426_000, 3550)
.saturating_add(T::DbWeight::get().reads(2_u64))
.saturating_add(T::DbWeight::get().writes(2_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn on_initialize_noop() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `1489`
// Minimum execution time: 2_500_000 picoseconds.
Weight::from_parts(2_594_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn on_initialize_exit() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 8_868_000 picoseconds.
Weight::from_parts(9_415_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::Deposits` (r:0 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
fn enter() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `3550`
// Minimum execution time: 50_541_000 picoseconds.
Weight::from_parts(51_558_000, 3550)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_enter() -> Weight {
// Proof Size summary in bytes:
// Measured: `142`
// Estimated: `1489`
// Minimum execution time: 10_489_000 picoseconds.
Weight::from_parts(10_833_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::Deposits` (r:0 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
fn extend() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `3550`
// Minimum execution time: 50_818_000 picoseconds.
Weight::from_parts(51_873_000, 3550)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(3_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_extend() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 10_843_000 picoseconds.
Weight::from_parts(11_314_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::EnteredUntil` (r:1 w:1)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
fn force_exit() -> Weight {
// Proof Size summary in bytes:
// Measured: `169`
// Estimated: `1489`
// Minimum execution time: 10_382_000 picoseconds.
Weight::from_parts(10_814_000, 1489)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `SafeMode::EnteredUntil` (r:1 w:0)
/// Proof: `SafeMode::EnteredUntil` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn release_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 42_828_000 picoseconds.
Weight::from_parts(43_752_000, 3550)
.saturating_add(RocksDbWeight::get().reads(3_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn force_release_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 40_196_000 picoseconds.
Weight::from_parts(41_298_000, 3550)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
/// Storage: `SafeMode::Deposits` (r:1 w:1)
/// Proof: `SafeMode::Deposits` (`max_values`: None, `max_size`: Some(68), added: 2543, mode: `MaxEncodedLen`)
/// Storage: `Balances::Holds` (r:1 w:1)
/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
fn force_slash_deposit() -> Weight {
// Proof Size summary in bytes:
// Measured: `292`
// Estimated: `3550`
// Minimum execution time: 33_660_000 picoseconds.
Weight::from_parts(34_426_000, 3550)
.saturating_add(RocksDbWeight::get().reads(2_u64))
.saturating_add(RocksDbWeight::get().writes(2_u64))
}
}
+6
View File
@@ -116,6 +116,12 @@ pub use messages::{
TransformOrigin,
};
mod safe_mode;
pub use safe_mode::{SafeMode, SafeModeError, SafeModeNotify};
mod tx_pause;
pub use tx_pause::{TransactionPause, TransactionPauseError};
#[cfg(feature = "try-runtime")]
mod try_runtime;
#[cfg(feature = "try-runtime")]
@@ -0,0 +1,76 @@
// 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.
//! Types to put the runtime into safe mode.
/// Can put the runtime into a safe mode.
///
/// When the runtime entered safe mode, transaction processing for most general transactions is
/// paused.
pub trait SafeMode {
/// Block number type.
type BlockNumber;
/// Whether safe mode is entered.
fn is_entered() -> bool {
Self::remaining().is_some()
}
/// How many more blocks safe mode will stay entered.
///
/// If this returns `0`, then safe mode will exit in the next block.
fn remaining() -> Option<Self::BlockNumber>;
/// Enter safe mode for `duration` blocks.
///
/// Should error when already entered with `AlreadyEntered`.
fn enter(duration: Self::BlockNumber) -> Result<(), SafeModeError>;
/// Extend safe mode for `duration` blocks.
///
/// Should error when not entered with `AlreadyExited`.
fn extend(duration: Self::BlockNumber) -> Result<(), SafeModeError>;
/// Exit safe mode immediately.
///
/// This takes effect already in the same block.
fn exit() -> Result<(), SafeModeError>;
}
/// The error type for [`SafeMode`].
pub enum SafeModeError {
/// Safe mode is already entered.
AlreadyEntered,
/// Safe mode is already exited.
AlreadyExited,
/// Unknown error.
Unknown,
}
/// A trait to notify when the runtime enters or exits safe mode.
pub trait SafeModeNotify {
/// Called when the runtime enters safe mode.
fn entered();
/// Called when the runtime exits safe mode.
fn exited();
}
impl SafeModeNotify for () {
fn entered() {}
fn exited() {}
}
@@ -0,0 +1,66 @@
// 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.
//! Types to pause calls in the runtime.
/// Can pause specific transactions from being processed.
///
/// Note that paused transactions will not be queued for later execution. Instead they will be
/// dropped.
pub trait TransactionPause {
/// How to unambiguously identify a call.
///
/// For example `(pallet_index, call_index)`.
type CallIdentifier;
/// Whether this call is paused.
fn is_paused(call: Self::CallIdentifier) -> bool;
/// Whether this call can be paused.
///
/// This holds for the current block, but may change in the future.
fn can_pause(call: Self::CallIdentifier) -> bool;
/// Pause this call immediately.
///
/// This takes effect in the same block and must succeed if `can_pause` returns `true`.
fn pause(call: Self::CallIdentifier) -> Result<(), TransactionPauseError>;
/// Unpause this call immediately.
///
/// This takes effect in the same block and must succeed if `is_paused` returns `true`. This
/// invariant is important to not have un-resumable calls.
fn unpause(call: Self::CallIdentifier) -> Result<(), TransactionPauseError>;
}
/// The error type for [`TransactionPause`].
pub enum TransactionPauseError {
/// The call could not be found in the runtime.
///
/// This is a permanent error but could change after a runtime upgrade.
NotFound,
/// Call cannot be paused.
///
/// This may or may not resolve in a future block.
Unpausable,
/// Call is already paused.
AlreadyPaused,
/// Call is already unpaused.
AlreadyUnpaused,
/// Unknown error.
Unknown,
}
+8
View File
@@ -248,6 +248,14 @@ pub mod pallet {
/// The basic call filter to use in Origin. All origins are built with this filter as base,
/// except Root.
///
/// This works as a filter for each incoming call. The call needs to pass this filter in
/// order to dispatch. Otherwise it will be rejected with `CallFiltered`. This can be
/// bypassed via `dispatch_bypass_filter` which should only be accessible by root. The
/// filter can be composed of sub-filters by nesting for example
/// [`frame_support::traits::InsideBoth`], [`frame_support::traits::TheseExcept`] or
/// [`frame_support::traits::EverythingBut`] et al. The default would be
/// [`frame_support::traits::Everything`].
#[pallet::no_default]
type BaseCallFilter: Contains<Self::RuntimeCall>;
+66
View File
@@ -0,0 +1,66 @@
[package]
name = "pallet-tx-pause"
version = "4.0.0-dev"
authors = ["Parity Technologies <admin@parity.io>"]
edition = "2021"
license = "Apache-2.0"
homepage = "https://substrate.io"
repository = "https://github.com/paritytech/substrate/"
description = "FRAME transaction pause pallet"
readme = "README.md"
[package.metadata.docs.rs]
targets = ["x86_64-unknown-linux-gnu"]
[dependencies]
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
scale-info = { version = "2.0.1", default-features = false, features = ["derive"] }
sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" }
sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" }
pallet-balances = { version = "4.0.0-dev", path = "../balances", default-features = false, optional = true }
pallet-utility = { version = "4.0.0-dev", path = "../utility", default-features = false, optional = true }
pallet-proxy = { version = "4.0.0-dev", path = "../proxy", default-features = false, optional = true }
[dev-dependencies]
sp-core = { version = "21.0.0", path = "../../primitives/core" }
sp-io = { version = "23.0.0", path = "../../primitives/io" }
pallet-balances = { version = "4.0.0-dev", path = "../balances" }
pallet-utility = { version = "4.0.0-dev", path = "../utility" }
pallet-proxy = { version = "4.0.0-dev", path = "../proxy" }
[features]
default = [ "std" ]
std = [
"codec/std",
"frame-benchmarking/std",
"frame-support/std",
"frame-system/std",
"pallet-balances?/std",
"pallet-proxy?/std",
"pallet-utility?/std",
"scale-info/std",
"sp-core/std",
"sp-io/std",
"sp-runtime/std",
"sp-std/std",
]
runtime-benchmarks = [
"frame-benchmarking/runtime-benchmarks",
"frame-support/runtime-benchmarks",
"frame-system/runtime-benchmarks",
"pallet-balances/runtime-benchmarks",
"pallet-proxy/runtime-benchmarks",
"pallet-utility/runtime-benchmarks",
"sp-runtime/runtime-benchmarks",
]
try-runtime = [
"frame-support/try-runtime",
"frame-system/try-runtime",
"pallet-balances?/try-runtime",
"pallet-proxy?/try-runtime",
"pallet-utility?/try-runtime",
"sp-runtime/try-runtime",
]
@@ -0,0 +1,59 @@
// This file is part of Substrate.
// Copyright (C) 2022 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(feature = "runtime-benchmarks")]
use super::{Pallet as TxPause, *};
use frame_benchmarking::v2::*;
#[benchmarks]
mod benchmarks {
use super::*;
#[benchmark]
fn pause() {
let origin = T::PauseOrigin::try_successful_origin()
.expect("Tx-pause pallet is not usable without pause origin");
let full_name = name::<T>();
#[extrinsic_call]
_(origin as T::RuntimeOrigin, full_name.clone());
assert!(PausedCalls::<T>::get(full_name).is_some());
}
#[benchmark]
fn unpause() {
let unpause_origin = T::UnpauseOrigin::try_successful_origin()
.expect("Tx-pause pallet is not usable without pause origin");
let full_name = name::<T>();
TxPause::<T>::do_pause(full_name.clone()).unwrap();
#[extrinsic_call]
_(unpause_origin as T::RuntimeOrigin, full_name.clone());
assert!(PausedCalls::<T>::get(full_name).is_none());
}
impl_benchmark_test_suite!(TxPause, crate::mock::new_test_ext(), crate::mock::Test);
}
/// Longest possible name.
fn name<T: Config>() -> RuntimeCallNameOf<T> {
let max_len = T::MaxNameLen::get() as usize;
(vec![1; max_len].try_into().unwrap(), vec![1; max_len].try_into().unwrap())
}
+277
View File
@@ -0,0 +1,277 @@
// This file is part of Substrate.
// Copyright (C) 2022 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::{
dispatch::GetDispatchInfo,
pallet_prelude::*,
traits::{CallMetadata, Contains, GetCallMetadata, IsSubType, IsType},
DefaultNoBound,
};
use frame_system::pallet_prelude::*;
use sp_runtime::{traits::Dispatchable, DispatchResult};
use sp_std::{convert::TryInto, prelude::*};
pub use pallet::*;
pub use weights::*;
/// The stringy name of a pallet from [`GetCallMetadata`] for [`Config::RuntimeCall`] variants.
pub type PalletNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
/// The stringy name of a call (within a pallet) from [`GetCallMetadata`] for
/// [`Config::RuntimeCall`] variants.
pub type PalletCallNameOf<T> = BoundedVec<u8, <T as Config>::MaxNameLen>;
/// A fully specified pallet ([`PalletNameOf`]) and optional call ([`PalletCallNameOf`])
/// to partially or fully specify an item a variant of a [`Config::RuntimeCall`].
pub type RuntimeCallNameOf<T> = (PalletNameOf<T>, PalletCallNameOf<T>);
#[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>;
/// The overarching call type.
type RuntimeCall: Parameter
+ Dispatchable<RuntimeOrigin = Self::RuntimeOrigin>
+ GetDispatchInfo
+ GetCallMetadata
+ From<frame_system::Call<Self>>
+ IsSubType<Call<Self>>
+ IsType<<Self as frame_system::Config>::RuntimeCall>;
/// The only origin that can pause calls.
type PauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// The only origin that can un-pause calls.
type UnpauseOrigin: EnsureOrigin<Self::RuntimeOrigin>;
/// Contains all calls that cannot be paused.
///
/// The `TxMode` pallet cannot pause its own calls, and does not need to be explicitly
/// added here.
type WhitelistedCalls: Contains<RuntimeCallNameOf<Self>>;
/// Maximum length for pallet name and call name SCALE encoded string names.
///
/// TOO LONG NAMES WILL BE TREATED AS PAUSED.
#[pallet::constant]
type MaxNameLen: Get<u32>;
// Weight information for extrinsics in this pallet.
type WeightInfo: WeightInfo;
}
/// The set of calls that are explicitly paused.
#[pallet::storage]
pub type PausedCalls<T: Config> =
StorageMap<_, Blake2_128Concat, RuntimeCallNameOf<T>, (), OptionQuery>;
#[pallet::error]
pub enum Error<T> {
/// The call is paused.
IsPaused,
/// The call is unpaused.
IsUnpaused,
/// The call is whitelisted and cannot be paused.
Unpausable,
// The pallet or call does not exist in the runtime.
NotFound,
}
#[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> {
/// This pallet, or a specific call is now paused.
CallPaused { full_name: RuntimeCallNameOf<T> },
/// This pallet, or a specific call is now unpaused.
CallUnpaused { full_name: RuntimeCallNameOf<T> },
}
/// Configure the initial state of this pallet in the genesis block.
#[pallet::genesis_config]
#[derive(DefaultNoBound)]
pub struct GenesisConfig<T: Config> {
/// Initially paused calls.
pub paused: Vec<RuntimeCallNameOf<T>>,
}
#[pallet::genesis_build]
impl<T: Config> BuildGenesisConfig for GenesisConfig<T> {
fn build(&self) {
for call in &self.paused {
Pallet::<T>::ensure_can_pause(&call).expect("Genesis data is known good; qed");
PausedCalls::<T>::insert(&call, ());
}
}
}
#[pallet::call]
impl<T: Config> Pallet<T> {
/// Pause a call.
///
/// Can only be called by [`Config::PauseOrigin`].
/// Emits an [`Event::CallPaused`] event on success.
#[pallet::call_index(0)]
#[pallet::weight(T::WeightInfo::pause())]
pub fn pause(origin: OriginFor<T>, full_name: RuntimeCallNameOf<T>) -> DispatchResult {
T::PauseOrigin::ensure_origin(origin)?;
Self::do_pause(full_name).map_err(Into::into)
}
/// Un-pause a call.
///
/// Can only be called by [`Config::UnpauseOrigin`].
/// Emits an [`Event::CallUnpaused`] event on success.
#[pallet::call_index(1)]
#[pallet::weight(T::WeightInfo::unpause())]
pub fn unpause(origin: OriginFor<T>, ident: RuntimeCallNameOf<T>) -> DispatchResult {
T::UnpauseOrigin::ensure_origin(origin)?;
Self::do_unpause(ident).map_err(Into::into)
}
}
}
impl<T: Config> Pallet<T> {
pub(crate) fn do_pause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
Self::ensure_can_pause(&ident)?;
PausedCalls::<T>::insert(&ident, ());
Self::deposit_event(Event::CallPaused { full_name: ident });
Ok(())
}
pub(crate) fn do_unpause(ident: RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
Self::ensure_can_unpause(&ident)?;
PausedCalls::<T>::remove(&ident);
Self::deposit_event(Event::CallUnpaused { full_name: ident });
Ok(())
}
/// Return whether this call is paused.
pub fn is_paused(full_name: &RuntimeCallNameOf<T>) -> bool {
if T::WhitelistedCalls::contains(full_name) {
return false
}
<PausedCalls<T>>::contains_key(full_name)
}
/// Same as [`Self::is_paused`] but for inputs unbound by max-encoded-len.
pub fn is_paused_unbound(pallet: Vec<u8>, call: Vec<u8>) -> bool {
let pallet = PalletNameOf::<T>::try_from(pallet);
let call = PalletCallNameOf::<T>::try_from(call);
match (pallet, call) {
(Ok(pallet), Ok(call)) => Self::is_paused(&(pallet, call)),
_ => true,
}
}
/// Ensure that this call can be paused.
pub fn ensure_can_pause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
// SAFETY: The `TxPause` pallet can never pause itself.
if full_name.0.as_ref() == <Self as PalletInfoAccess>::name().as_bytes().to_vec() {
return Err(Error::<T>::Unpausable)
}
if T::WhitelistedCalls::contains(&full_name) {
return Err(Error::<T>::Unpausable)
}
if Self::is_paused(&full_name) {
return Err(Error::<T>::IsPaused)
}
Ok(())
}
/// Ensure that this call can be un-paused.
pub fn ensure_can_unpause(full_name: &RuntimeCallNameOf<T>) -> Result<(), Error<T>> {
if Self::is_paused(&full_name) {
// SAFETY: Everything that is paused, can be un-paused.
Ok(())
} else {
Err(Error::IsUnpaused)
}
}
}
impl<T: pallet::Config> Contains<<T as frame_system::Config>::RuntimeCall> for Pallet<T>
where
<T as frame_system::Config>::RuntimeCall: GetCallMetadata,
{
/// Return whether the call is allowed to be dispatched.
fn contains(call: &<T as frame_system::Config>::RuntimeCall) -> bool {
let CallMetadata { pallet_name, function_name } = call.get_call_metadata();
!Pallet::<T>::is_paused_unbound(pallet_name.into(), function_name.into())
}
}
impl<T: Config> frame_support::traits::TransactionPause for Pallet<T> {
type CallIdentifier = RuntimeCallNameOf<T>;
fn is_paused(full_name: Self::CallIdentifier) -> bool {
Self::is_paused(&full_name)
}
fn can_pause(full_name: Self::CallIdentifier) -> bool {
Self::ensure_can_pause(&full_name).is_ok()
}
fn pause(
full_name: Self::CallIdentifier,
) -> Result<(), frame_support::traits::TransactionPauseError> {
Self::do_pause(full_name).map_err(Into::into)
}
fn unpause(
full_name: Self::CallIdentifier,
) -> Result<(), frame_support::traits::TransactionPauseError> {
Self::do_unpause(full_name).map_err(Into::into)
}
}
impl<T: Config> From<Error<T>> for frame_support::traits::TransactionPauseError {
fn from(err: Error<T>) -> Self {
match err {
Error::<T>::NotFound => Self::NotFound,
Error::<T>::Unpausable => Self::Unpausable,
Error::<T>::IsPaused => Self::AlreadyPaused,
Error::<T>::IsUnpaused => Self::AlreadyUnpaused,
_ => Self::Unknown,
}
}
}
+226
View File
@@ -0,0 +1,226 @@
// This file is part of Substrate.
// Copyright (C) 2022 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.
//! Tests and test utilities for transaction pause pallet.
#![cfg(test)]
use super::*;
use crate as pallet_tx_pause;
use frame_support::{
parameter_types,
traits::{ConstU64, Everything, InsideBoth, InstanceFilter},
};
use frame_system::EnsureSignedBy;
use sp_core::H256;
use sp_runtime::{
traits::{BlakeTwo256, IdentityLookup},
BuildStorage,
};
parameter_types! {
pub const BlockHashCount: u64 = 250;
}
impl frame_system::Config for Test {
type BaseCallFilter = InsideBoth<Everything, TxPause>;
type BlockWeights = ();
type BlockLength = ();
type RuntimeOrigin = RuntimeOrigin;
type RuntimeCall = RuntimeCall;
type Nonce = u64;
type Hash = H256;
type Hashing = BlakeTwo256;
type AccountId = u64;
type Lookup = IdentityLookup<Self::AccountId>;
type RuntimeEvent = RuntimeEvent;
type Block = Block;
type BlockHashCount = BlockHashCount;
type DbWeight = ();
type Version = ();
type PalletInfo = PalletInfo;
type AccountData = pallet_balances::AccountData<u64>;
type OnNewAccount = ();
type OnKilledAccount = ();
type SystemWeightInfo = ();
type SS58Prefix = ();
type OnSetCode = ();
type MaxConsumers = frame_support::traits::ConstU32<16>;
}
parameter_types! {
pub const ExistentialDeposit: u64 = 1;
pub const MaxLocks: u32 = 10;
}
impl pallet_balances::Config for Test {
type Balance = u64;
type DustRemoval = ();
type RuntimeEvent = RuntimeEvent;
type ExistentialDeposit = ExistentialDeposit;
type AccountStore = System;
type WeightInfo = ();
type MaxLocks = MaxLocks;
type MaxReserves = ();
type ReserveIdentifier = [u8; 8];
type FreezeIdentifier = ();
type RuntimeHoldReason = RuntimeHoldReason;
type MaxHolds = ConstU32<0>;
type MaxFreezes = ConstU32<0>;
}
impl pallet_utility::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PalletsOrigin = OriginCaller;
type WeightInfo = ();
}
/// Mocked proxies to check that tx-pause also works with the proxy pallet.
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Encode,
Decode,
RuntimeDebug,
MaxEncodedLen,
scale_info::TypeInfo,
)]
pub enum ProxyType {
Any,
JustTransfer,
JustUtility,
}
impl Default for ProxyType {
fn default() -> Self {
Self::Any
}
}
impl InstanceFilter<RuntimeCall> for ProxyType {
fn filter(&self, c: &RuntimeCall) -> bool {
match self {
ProxyType::Any => true,
ProxyType::JustTransfer => {
matches!(c, RuntimeCall::Balances(pallet_balances::Call::transfer { .. }))
},
ProxyType::JustUtility => matches!(c, RuntimeCall::Utility { .. }),
}
}
fn is_superset(&self, o: &Self) -> bool {
self == &ProxyType::Any || self == o
}
}
impl pallet_proxy::Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type Currency = Balances;
type ProxyType = ProxyType;
type ProxyDepositBase = ConstU64<1>;
type ProxyDepositFactor = ConstU64<1>;
type MaxProxies = ConstU32<4>;
type WeightInfo = ();
type CallHasher = BlakeTwo256;
type MaxPending = ConstU32<2>;
type AnnouncementDepositBase = ConstU64<1>;
type AnnouncementDepositFactor = ConstU64<1>;
}
parameter_types! {
pub const MaxNameLen: u32 = 50;
}
frame_support::ord_parameter_types! {
pub const PauseOrigin: u64 = 1;
pub const UnpauseOrigin: u64 = 2;
}
/// Calls that are never allowed to be paused.
pub struct WhitelistedCalls;
impl Contains<RuntimeCallNameOf<Test>> for WhitelistedCalls {
fn contains(full_name: &RuntimeCallNameOf<Test>) -> bool {
match (full_name.0.as_slice(), full_name.1.as_slice()) {
(b"Balances", b"transfer_keep_alive") => true,
_ => false,
}
}
}
impl Config for Test {
type RuntimeEvent = RuntimeEvent;
type RuntimeCall = RuntimeCall;
type PauseOrigin = EnsureSignedBy<PauseOrigin, Self::AccountId>;
type UnpauseOrigin = EnsureSignedBy<UnpauseOrigin, Self::AccountId>;
type WhitelistedCalls = WhitelistedCalls;
type MaxNameLen = MaxNameLen;
type WeightInfo = ();
}
type Block = frame_system::mocking::MockBlock<Test>;
frame_support::construct_runtime!(
pub enum Test
{
System: frame_system,
Balances: pallet_balances,
Utility: pallet_utility,
Proxy: pallet_proxy,
TxPause: pallet_tx_pause,
}
);
pub fn new_test_ext() -> sp_io::TestExternalities {
let mut t = frame_system::GenesisConfig::<Test>::default().build_storage().unwrap();
pallet_balances::GenesisConfig::<Test> {
// The 0 account is NOT a special origin. The rest may be:
balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)],
}
.assimilate_storage(&mut t)
.unwrap();
pallet_tx_pause::GenesisConfig::<Test> { paused: vec![] }
.assimilate_storage(&mut t)
.unwrap();
let mut ext = sp_io::TestExternalities::new(t);
ext.execute_with(|| {
System::set_block_number(1);
});
ext
}
pub fn next_block() {
TxPause::on_finalize(System::block_number());
Balances::on_finalize(System::block_number());
System::on_finalize(System::block_number());
System::set_block_number(System::block_number() + 1);
System::on_initialize(System::block_number());
Balances::on_initialize(System::block_number());
TxPause::on_initialize(System::block_number());
}
pub fn run_to(n: u64) {
while System::block_number() < n {
next_block();
}
}
+222
View File
@@ -0,0 +1,222 @@
// This file is part of Substrate.
// Copyright (C) 2022 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(test)]
use super::*;
use crate::mock::{RuntimeCall, *};
use frame_support::{assert_err, assert_noop, assert_ok, dispatch::Dispatchable};
// GENERAL SUCCESS/POSITIVE TESTS ---------------------
#[test]
fn can_pause_specific_call() {
new_test_ext().execute_with(|| {
assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0)));
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer")
));
assert_err!(
call_transfer(2, 1).dispatch(RuntimeOrigin::signed(2)),
frame_system::Error::<Test>::CallFiltered
);
assert_ok!(call_transfer_keep_alive(3, 1).dispatch(RuntimeOrigin::signed(3)));
});
}
#[test]
fn can_pause_all_calls_in_pallet_except_on_whitelist() {
new_test_ext().execute_with(|| {
assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0)));
let batch_call =
RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![call_transfer(1, 1)] });
assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0)));
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Utility", b"batch")
),);
assert_err!(
batch_call.clone().dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
});
}
#[test]
fn can_unpause_specific_call() {
new_test_ext().execute_with(|| {
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_err!(
call_transfer(2, 1).dispatch(RuntimeOrigin::signed(2)),
frame_system::Error::<Test>::CallFiltered
);
assert_ok!(TxPause::unpause(
RuntimeOrigin::signed(mock::UnpauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_ok!(call_transfer(4, 1).dispatch(RuntimeOrigin::signed(0)));
});
}
#[test]
fn can_filter_balance_in_batch_when_paused() {
new_test_ext().execute_with(|| {
let batch_call =
RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![call_transfer(1, 1)] });
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0)));
System::assert_last_event(
pallet_utility::Event::BatchInterrupted {
index: 0,
error: frame_system::Error::<Test>::CallFiltered.into(),
}
.into(),
);
});
}
#[test]
fn can_filter_balance_in_proxy_when_paused() {
new_test_ext().execute_with(|| {
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_ok!(Proxy::add_proxy(RuntimeOrigin::signed(1), 2, ProxyType::JustTransfer, 0));
assert_ok!(Proxy::proxy(RuntimeOrigin::signed(2), 1, None, Box::new(call_transfer(1, 1))));
System::assert_last_event(
pallet_proxy::Event::ProxyExecuted {
result: DispatchError::from(frame_system::Error::<Test>::CallFiltered).into(),
}
.into(),
);
});
}
// GENERAL FAIL/NEGATIVE TESTS ---------------------
#[test]
fn fails_to_pause_self() {
new_test_ext().execute_with(|| {
assert_noop!(
TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"TxPause", b"pause"),
),
Error::<Test>::Unpausable
);
});
}
#[test]
fn fails_to_pause_unpausable_call_when_other_call_is_paused() {
new_test_ext().execute_with(|| {
assert_ok!(call_transfer(1, 1).dispatch(RuntimeOrigin::signed(0)));
let batch_call =
RuntimeCall::Utility(pallet_utility::Call::batch { calls: vec![call_transfer(1, 1)] });
assert_ok!(batch_call.clone().dispatch(RuntimeOrigin::signed(0)));
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_ok!(call_transfer_keep_alive(3, 1).dispatch(RuntimeOrigin::signed(3)));
assert_err!(
call_transfer(2, 1).dispatch(RuntimeOrigin::signed(0)),
frame_system::Error::<Test>::CallFiltered
);
});
}
#[test]
fn fails_to_pause_unpausable_call() {
new_test_ext().execute_with(|| {
assert_noop!(
TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer_keep_alive"),
),
Error::<Test>::Unpausable
);
});
}
#[test]
fn fails_to_pause_already_paused_pallet() {
new_test_ext().execute_with(|| {
assert_ok!(TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
));
assert_noop!(
TxPause::pause(
RuntimeOrigin::signed(mock::PauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer"),
),
Error::<Test>::IsPaused
);
});
}
#[test]
fn fails_to_unpause_not_paused_pallet() {
new_test_ext().execute_with(|| {
assert_noop!(
TxPause::unpause(
RuntimeOrigin::signed(mock::UnpauseOrigin::get()),
full_name::<Test>(b"Balances", b"transfer_keep_alive"),
),
Error::<Test>::IsUnpaused
);
});
}
pub fn call_transfer(dest: u64, value: u64) -> RuntimeCall {
RuntimeCall::Balances(pallet_balances::Call::transfer { dest, value })
}
pub fn call_transfer_keep_alive(dest: u64, value: u64) -> RuntimeCall {
RuntimeCall::Balances(pallet_balances::Call::transfer_keep_alive { dest, value })
}
pub fn full_name<T: Config>(pallet_name: &[u8], call_name: &[u8]) -> RuntimeCallNameOf<T> {
<RuntimeCallNameOf<T>>::from((
pallet_name.to_vec().try_into().unwrap(),
call_name.to_vec().try_into().unwrap(),
))
}
+107
View File
@@ -0,0 +1,107 @@
// 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.
//! Autogenerated weights for `pallet_tx_pause`
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2023-08-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! WORST CASE MAP SIZE: `1000000`
//! HOSTNAME: `runner-aahe6cbd-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
// Executed Command:
// target/production/substrate-node
// benchmark
// pallet
// --steps=50
// --repeat=20
// --extrinsic=*
// --wasm-execution=compiled
// --heap-pages=4096
// --json-file=/builds/parity/mirrors/substrate/.git/.artifacts/bench.json
// --pallet=pallet_tx_pause
// --chain=dev
// --header=./HEADER-APACHE2
// --output=./frame/tx-pause/src/weights.rs
// --template=./.maintain/frame-weight-template.hbs
#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
#![allow(missing_docs)]
use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}};
use core::marker::PhantomData;
/// Weight functions needed for `pallet_tx_pause`.
pub trait WeightInfo {
fn pause() -> Weight;
fn unpause() -> Weight;
}
/// Weights for `pallet_tx_pause` using the Substrate node and recommended hardware.
pub struct SubstrateWeight<T>(PhantomData<T>);
impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
/// Storage: `TxPause::PausedCalls` (r:1 w:1)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn pause() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3997`
// Minimum execution time: 15_096_000 picoseconds.
Weight::from_parts(15_437_000, 3997)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
/// Storage: `TxPause::PausedCalls` (r:1 w:1)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn unpause() -> Weight {
// Proof Size summary in bytes:
// Measured: `565`
// Estimated: `3997`
// Minimum execution time: 21_546_000 picoseconds.
Weight::from_parts(22_178_000, 3997)
.saturating_add(T::DbWeight::get().reads(1_u64))
.saturating_add(T::DbWeight::get().writes(1_u64))
}
}
// For backwards compatibility and tests.
impl WeightInfo for () {
/// Storage: `TxPause::PausedCalls` (r:1 w:1)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn pause() -> Weight {
// Proof Size summary in bytes:
// Measured: `3`
// Estimated: `3997`
// Minimum execution time: 15_096_000 picoseconds.
Weight::from_parts(15_437_000, 3997)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
/// Storage: `TxPause::PausedCalls` (r:1 w:1)
/// Proof: `TxPause::PausedCalls` (`max_values`: None, `max_size`: Some(532), added: 3007, mode: `MaxEncodedLen`)
fn unpause() -> Weight {
// Proof Size summary in bytes:
// Measured: `565`
// Estimated: `3997`
// Minimum execution time: 21_546_000 picoseconds.
Weight::from_parts(22_178_000, 3997)
.saturating_add(RocksDbWeight::get().reads(1_u64))
.saturating_add(RocksDbWeight::get().writes(1_u64))
}
}