mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-05-10 05:17:59 +00:00
[FRAME Core] New pallets: safe-mode and tx-pause (#12092)
* Add safe-mode Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add to kitchensink-runtime Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Spelling Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename to tx-pause Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add SafeMode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * fmt Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Automatically disable safe-mode in on_init… Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add permissionless enable+extend Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add repay+slash stake methods Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix stakes storage Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Genesis config for safe-mode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Genesis config for safe-mode pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename ExtrinsicName to FunctionName Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Origin variable duration Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename FunctionName -> CallName Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename and docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Pallet safe mode tests (#12148) * Add safe-mode mock runtime * Add safe-mode tests * Add ForceEnable- and ForceExtendOrigin * Start dummy benchmarks Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Tests for `pallet-tx-pause` (#12259) * mock added * tests added * dummy benchmarks started * rename to active/inactive tests broken, in progress * Runtime types, fix tests * WIP safe mode and tx pause {continued} (#12371) * test coverage on safe mode and tx pause * benchmarks & tests * tests passing, use FullNameOf to check tx-pause unfilterables * naming updates Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Set block number Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * dummy weights generated, safe mode * add repay_reservation call with RepaymentDelay per #10033 feature requirements * make call name optional to allow pausing pallets, handle `Contains` correctly for this throughout, doc comments started * move to full_name notation for all interfaces, last commit introduced 1 more storage read. * refactor is_paused * safe math on safe mode * Make stuff compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup & renames Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup TxPause pallet Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * fmt Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Refactor to fungibles::* and rename stuf Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix node config Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Typos Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove CausalHoldReason Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Refactor benchmarks and runtime configs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add traits Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove old code Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Cleanup safe-mode benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/safe-mode/Cargo.toml Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/safe-mode/Cargo.toml Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove getters Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update Cargo.lock Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove phantom Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix test Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Remove phantom Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Apply suggestions from code review Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Use new as Origin benchmarking syntax Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix node Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix tx-pause benches Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Renames * Remove duplicate test * Add docs * docs * Apply suggestions from code review Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> * Cleanup tests * docs * Cleanup GenesisConfigs * Doc traits * Remove PauseTooLongNames * docs * Use V2 benchmarking Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Use RuntimeHoldReason * Fix kitchensink runtime * Fix CI Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix CI Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Review Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Rename Stake to Deposit Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add docs Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Add Notify and test it Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix kitchensink Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Update frame/safe-mode/src/tests.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/safe-mode/src/tests.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/tx_pause.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/tx-pause/src/mock.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Simplify code * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/src/traits/safe_mode.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Fixup merge Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make stuff compile Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Make tx-pause compile again Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix features Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * Fix more features Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> * ".git/.scripts/commands/bench/bench.sh" --subcommand=pallet --runtime=dev --target_dir=substrate --pallet=pallet_safe_mode * Update weights Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> --------- Signed-off-by: Oliver Tale-Yazdi <oliver.tale-yazdi@parity.io> Co-authored-by: Dan Shields <35669742+NukeManDan@users.noreply.github.com> Co-authored-by: Dan Shields <nukemandan@protonmail.com> Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: command-bot <>
This commit is contained in:
committed by
GitHub
parent
46bd466e48
commit
3710edfedc
Generated
+39
@@ -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"
|
||||
|
||||
@@ -188,6 +188,8 @@ members = [
|
||||
"frame/recovery",
|
||||
"frame/referenda",
|
||||
"frame/remark",
|
||||
"frame/tx-pause",
|
||||
"frame/safe-mode",
|
||||
"frame/salary",
|
||||
"frame/scheduler",
|
||||
"frame/scored-pool",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
Generated
+321
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
))
|
||||
}
|
||||
Generated
+107
@@ -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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user