diff --git a/Cargo.lock b/Cargo.lock index 5e318dab087d1..4f5eae9c31b12 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6180,10 +6180,14 @@ dependencies = [ name = "pallet-safe-mode" version = "4.0.0-dev" dependencies = [ + "frame-benchmarking", "frame-support", "frame-system", + "pallet-balances", "parity-scale-codec", "scale-info", + "sp-core", + "sp-io", "sp-runtime", "sp-std", ] diff --git a/bin/node/cli/src/chain_spec.rs b/bin/node/cli/src/chain_spec.rs index 77e2f73dd6e18..b549f5f6ed0c7 100644 --- a/bin/node/cli/src/chain_spec.rs +++ b/bin/node/cli/src/chain_spec.rs @@ -365,6 +365,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, diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 735e8bff551d4..edf4873a50d0c 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -220,14 +220,26 @@ impl pallet_tx_pause::Config for Runtime { type PauseTooLongNames = ConstBool; } +parameter_types! { + // signed config + pub const EnableStakeAmount: Balance = 1 * DOLLARS; //TODO This needs to be something sensible for the implications of enablement! + pub const ExtendStakeAmount: Balance = 1 * DOLLARS; //TODO This needs to be something sensible for the implications of enablement! + pub BlockHeight: BlockNumber = System::block_number(); // TODO ensure this plus config below is correct +} + impl pallet_safe_mode::Config for Runtime { type Event = Event; + type Currency = Balances; type SafeModeFilter = Nothing; // TODO add TxPause pallet type EnableDuration = ConstU32<{ 2 * DAYS }>; type ExtendDuration = ConstU32<{ 1 * DAYS }>; - type EnableOrigin = EnsureRoot; - type ExtendOrigin = EnsureRoot; - type PreemptiveDisableOrigin = EnsureRoot; + type EnableOrigin = EnsureRootWithSuccess; + type ExtendOrigin = EnsureRootWithSuccess; + type DisableOrigin = EnsureRoot; + type RepayOrigin = EnsureRoot; + type EnableStakeAmount = EnableStakeAmount; + type ExtendStakeAmount = ExtendStakeAmount; + type WeightInfo = pallet_safe_mode::weights::SubstrateWeight; } impl frame_system::Config for Runtime { @@ -1795,6 +1807,7 @@ mod benches { [pallet_utility, Utility] [pallet_vesting, Vesting] [pallet_whitelist, Whitelist] + [pallet_safe_mode, SafeMode] ); } diff --git a/frame/remark/src/tests.rs b/frame/remark/src/tests.rs index 2278e3817b48a..bbee84282d212 100644 --- a/frame/remark/src/tests.rs +++ b/frame/remark/src/tests.rs @@ -20,7 +20,6 @@ use super::{Error, Event, Pallet as Remark}; use crate::mock::*; use frame_support::{assert_noop, assert_ok}; -use frame_system::RawOrigin; #[test] fn generates_event() { diff --git a/frame/safe-mode/Cargo.toml b/frame/safe-mode/Cargo.toml index 7ad7e83e1caf8..2ec371617b8cc 100644 --- a/frame/safe-mode/Cargo.toml +++ b/frame/safe-mode/Cargo.toml @@ -14,25 +14,32 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive"] } -scale-info = { version = "2.0.1", 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 = "6.0.0", default-features = false, path = "../../primitives/runtime" } sp-std = { version = "4.0.0", default-features = false, path = "../../primitives/std" } [dev-dependencies] +sp-core = { version = "6.0.0", path = "../../primitives/core" } +sp-std = { version = "4.0.0", path = "../../primitives/std" } +sp-io = { version = "6.0.0", path = "../../primitives/io" } +pallet-balances = { version = "4.0.0-dev", path = "../balances" } [features] default = ["std"] std = [ "codec/std", "scale-info/std", + "frame-benchmarking/std", "frame-support/std", "frame-system/std", "sp-runtime/std", "sp-std/std", ] runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", ] diff --git a/frame/safe-mode/src/benchmarking.rs b/frame/safe-mode/src/benchmarking.rs index 08f16881d7231..6028bd816fa74 100644 --- a/frame/safe-mode/src/benchmarking.rs +++ b/frame/safe-mode/src/benchmarking.rs @@ -16,3 +16,80 @@ // limitations under the License. #![cfg(feature = "runtime-benchmarks")] + +use super::{Call as SafeModeCall, Pallet as SafeMode, *}; + +use frame_benchmarking::{benchmarks, whitelisted_caller}; +use frame_support::traits::Currency; +use frame_system::{Pallet as System, RawOrigin}; +use sp_runtime::traits::Bounded; + +benchmarks! { + enable { + let caller: T::AccountId = whitelisted_caller(); + let origin = RawOrigin::Signed(caller.clone()); + T::Currency::make_free_balance_be(&caller, BalanceOf::::max_value()); + }: enable(origin) + verify { + assert_eq!( + SafeMode::::enabled().unwrap(), + System::::block_number() + T::EnableDuration::get() + ); + } + +// force_enable { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + +// extend { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + +// force_extend { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + +// force_disable { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + +// repay_stake { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + +// slash_stake { +// /* code to set the initial state */ +// }: { +// /* code to test the function benchmarked */ +// } +// verify { +// /* optional verification */ +// } + + impl_benchmark_test_suite!(SafeMode, crate::mock::new_test_ext(), crate::mock::Test); +} \ No newline at end of file diff --git a/frame/safe-mode/src/lib.rs b/frame/safe-mode/src/lib.rs index 234d20057d766..0d315f45a902b 100644 --- a/frame/safe-mode/src/lib.rs +++ b/frame/safe-mode/src/lib.rs @@ -17,22 +17,28 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(feature = "runtime-benchmarks")] +mod benchmarking; +#[cfg(test)] +pub mod mock; +#[cfg(test)] +mod tests; +pub mod weights; + use frame_support::{ pallet_prelude::*, traits::{ CallMetadata, Contains, Currency, Defensive, GetCallMetadata, PalletInfoAccess, ReservableCurrency, }, + weights::Weight, }; use frame_system::pallet_prelude::*; use sp_runtime::traits::Saturating; use sp_std::{convert::TryInto, prelude::*}; -mod benchmarking; -mod mock; -mod tests; - pub use pallet::*; +pub use weights::*; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -97,7 +103,7 @@ pub mod pallet { type RepayOrigin: EnsureOrigin; // Weight information for extrinsics in this pallet. - //type WeightInfo: WeightInfo; + type WeightInfo: WeightInfo; } #[pallet::error] @@ -149,10 +155,12 @@ pub mod pallet { /// This is set to `None` if the safe-mode is disabled. /// The safe-mode is automatically disabled when the current block number is greater than this. #[pallet::storage] + #[pallet::getter(fn enabled)] pub type Enabled = StorageValue<_, T::BlockNumber, OptionQuery>; /// Holds the stake that was reserved from a user at a specific block number. #[pallet::storage] + #[pallet::getter(fn stakes)] pub type Stakes = StorageDoubleMap< _, Twox64Concat, @@ -196,7 +204,8 @@ pub mod pallet { /// Reserves `EnableStakeAmount` from the caller's account. /// Errors if the safe-mode is already enabled. /// Can be permanently disabled by configuring `EnableStakeAmount` to `None`. - #[pallet::weight(0)] + #[pallet::weight(T::WeightInfo::enable())] + // #[pallet::weight(0)] pub fn enable(origin: OriginFor) -> DispatchResult { let who = ensure_signed(origin)?; diff --git a/frame/safe-mode/src/mock.rs b/frame/safe-mode/src/mock.rs index 95c25725a4f6d..04ab0024e492c 100644 --- a/frame/safe-mode/src/mock.rs +++ b/frame/safe-mode/src/mock.rs @@ -15,4 +15,281 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(any(test, feature = "runtime-benchmarks"))] +//! Test utilities for safe mode pallet. + +use super::*; +use crate as pallet_safe_mode; + +use frame_support::{ + parameter_types, + traits::{Everything, InsideBoth, SortedMembers}, +}; +use frame_system::{EnsureSignedBy, RawOrigin}; +use sp_core::H256; +use sp_runtime::{ + testing::Header, + traits::{BlakeTwo256, IdentityLookup}, + BuildStorage, +}; + +parameter_types! { + pub const BlockHashCount: u64 = 250; +} +impl frame_system::Config for Test { + type BaseCallFilter = InsideBoth; + type BlockWeights = (); + type BlockLength = (); + type Origin = Origin; + type Call = Call; + type Index = u64; + type BlockNumber = u64; + type Hash = H256; + type Hashing = BlakeTwo256; + type AccountId = u64; + type Lookup = IdentityLookup; + type Header = Header; + type Event = Event; + type BlockHashCount = BlockHashCount; + type DbWeight = (); + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + 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 Event = Event; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = MaxLocks; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +/// Filter to block balance pallet calls +pub struct MockSafeModeFilter; +impl Contains for MockSafeModeFilter { + fn contains(call: &Call) -> bool { + match call { + Call::System(_) | Call::SafeMode(_) => true, + Call::Balances(_) => false, + } + } +} + +/// An origin that can enable the safe-mode by force. +pub enum ForceEnableOrigin { + Weak, + Medium, + Strong, +} + +/// An origin that can extend the safe-mode by force. +pub enum ForceExtendOrigin { + Weak, + Medium, + Strong, +} + +impl ForceEnableOrigin { + /// The duration of how long the safe-mode will be enabled. + pub fn duration(&self) -> u64 { + match self { + Self::Weak => 5, + Self::Medium => 7, + Self::Strong => 11, + } + } + + /// Account id of the origin. + pub const fn acc(&self) -> u64 { + match self { + Self::Weak => 100, + Self::Medium => 101, + Self::Strong => 102, + } + } + + /// Signed origin. + pub fn signed(&self) -> ::Origin { + RawOrigin::Signed(self.acc()).into() + } +} + +impl ForceExtendOrigin { + /// The duration of how long the safe-mode will be extended. + pub fn duration(&self) -> u64 { + match self { + Self::Weak => 13, + Self::Medium => 17, + Self::Strong => 19, + } + } + + /// Account id of the origin. + pub const fn acc(&self) -> u64 { + match self { + Self::Weak => 200, + Self::Medium => 201, + Self::Strong => 202, + } + } + + /// Signed origin. + pub fn signed(&self) -> ::Origin { + RawOrigin::Signed(self.acc()).into() + } +} + +impl, O>> + From> + std::fmt::Debug> EnsureOrigin + for ForceEnableOrigin +{ + type Success = u64; + + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Signed(acc) if acc == ForceEnableOrigin::Weak.acc() => + Ok(ForceEnableOrigin::Weak.duration()), + RawOrigin::Signed(acc) if acc == ForceEnableOrigin::Medium.acc() => + Ok(ForceEnableOrigin::Medium.duration()), + RawOrigin::Signed(acc) if acc == ForceEnableOrigin::Strong.acc() => + Ok(ForceEnableOrigin::Strong.duration()), + r => Err(O::from(r)), + }) + } +} + +impl, O>> + From> + std::fmt::Debug> EnsureOrigin + for ForceExtendOrigin +{ + type Success = u64; + + fn try_origin(o: O) -> Result { + o.into().and_then(|o| match o { + RawOrigin::Signed(acc) if acc == ForceExtendOrigin::Weak.acc() => + Ok(ForceExtendOrigin::Weak.duration()), + RawOrigin::Signed(acc) if acc == ForceExtendOrigin::Medium.acc() => + Ok(ForceExtendOrigin::Medium.duration()), + RawOrigin::Signed(acc) if acc == ForceExtendOrigin::Strong.acc() => + Ok(ForceExtendOrigin::Strong.duration()), + r => Err(O::from(r)), + }) + } +} + +parameter_types! { + pub const EnableDuration: u64 = 3; + pub const ExtendDuration: u64 = 30; + pub const EnableStakeAmount: u64 = 100; //TODO This needs to be something sensible for the implications of enablement! + pub const ExtendStakeAmount: u64 = 100; //TODO This needs to be something sensible for the implications of enablement! + pub const DisableOrigin: u64 =3; + pub const RepayOrigin: u64 = 4; +} + +// Required impl to use some ::get() in tests +impl SortedMembers for DisableOrigin { + fn sorted_members() -> Vec { + vec![Self::get()] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} +impl SortedMembers for RepayOrigin { + fn sorted_members() -> Vec { + vec![Self::get()] + } + #[cfg(feature = "runtime-benchmarks")] + fn add(_m: &u64) {} +} + +impl Config for Test { + type Event = Event; + type Currency = Balances; + type SafeModeFilter = MockSafeModeFilter; + type EnableDuration = EnableDuration; + type ExtendDuration = ExtendDuration; + type EnableOrigin = ForceEnableOrigin; + type ExtendOrigin = ForceExtendOrigin; + type DisableOrigin = EnsureSignedBy; + type RepayOrigin = EnsureSignedBy; + type EnableStakeAmount = EnableStakeAmount; + type ExtendStakeAmount = ExtendStakeAmount; + type WeightInfo = (); +} + +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; +type Block = frame_system::mocking::MockBlock; + +frame_support::construct_runtime!( + pub enum Test where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system, + Balances: pallet_balances, + SafeMode: pallet_safe_mode, + } +); + +pub fn new_test_ext() -> sp_io::TestExternalities { + let mut t = frame_system::GenesisConfig::default().build_storage::().unwrap(); + + pallet_balances::GenesisConfig:: { + balances: vec![(0, 1234), (1, 5678), (2, 5678), (3, 5678), (4, 5678)], /* The 0 account + * is NOT a special + * origin, the + * rest may be. */ + } + .assimilate_storage(&mut t) + .unwrap(); + + // TODO requires a GenesisConfig impl + // GenesisBuild::::assimilate_storage( + // &pallet_safe_mode::GenesisConfig { + // enabled: None, + // stakes: None, + // }, + // &mut t, + // ) + // .unwrap(); + + let mut ext = sp_io::TestExternalities::new(t); + ext.execute_with(|| { + System::set_block_number(1); + }); + ext +} + +#[cfg(feature = "runtime-benchmarks")] +pub fn new_bench_ext() -> sp_io::TestExternalities { + GenesisConfig::default().build_storage().unwrap().into() +} + +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(); + } +} diff --git a/frame/safe-mode/src/tests.rs b/frame/safe-mode/src/tests.rs index ee33686e4a39f..4ccf4e8cd6411 100644 --- a/frame/safe-mode/src/tests.rs +++ b/frame/safe-mode/src/tests.rs @@ -15,4 +15,321 @@ // See the License for the specific language governing permissions and // limitations under the License. -#![cfg(test)] +//! Test utilities for the safe mode pallet. + +use super::*; +use crate::mock::{Call, *}; + +use frame_support::{assert_err, assert_noop, assert_ok, dispatch::Dispatchable}; + +// GENERAL FAIL/NEGATIVE TESTS --------------------- + +#[test] +fn fails_to_filter_calls_to_safe_mode_pallet() { + new_test_ext().execute_with(|| { + assert_ok!(SafeMode::enable(Origin::signed(0))); + let enabled_at_block = System::block_number(); + let call = Call::Balances(pallet_balances::Call::transfer { dest: 1, value: 1 }); + + assert_err!( + call.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + // TODO ^^^ consider refactor to throw a safe mode error, not generic `CallFiltered` + + next_block(); + assert_ok!(SafeMode::extend(Origin::signed(0))); + assert_ok!(SafeMode::force_extend(ForceExtendOrigin::Weak.signed())); + assert_err!( + call.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + assert_ok!(SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get()))); + assert_ok!(SafeMode::repay_stake( + Origin::signed(mock::RepayOrigin::get()), + 0, + enabled_at_block + )); + + next_block(); + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_err!( + call.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + assert_ok!(SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get()))); + assert_ok!(SafeMode::slash_stake( + Origin::signed(mock::RepayOrigin::get()), + 0, + enabled_at_block + 2 + )); + }); +} + +#[test] +fn fails_to_extend_if_not_enabled() { + new_test_ext().execute_with(|| { + assert_eq!(SafeMode::enabled(), None); + assert_noop!(SafeMode::extend(Origin::signed(2)), Error::::IsDisabled); + }); +} + +// GENERAL SUCCESS/POSITIVE TESTS --------------------- + +#[test] +fn can_automatically_disable_after_timeout() { + new_test_ext().execute_with(|| { + let enabled_at_block = System::block_number(); + assert_ok!(SafeMode::force_enable(ForceEnableOrigin::Weak.signed())); + run_to(ForceEnableOrigin::Weak.duration() + enabled_at_block + 1); + SafeMode::on_initialize(System::block_number()); + assert_eq!(SafeMode::enabled(), None); + }); +} + +#[test] +fn can_filter_balance_calls_when_enabled() { + new_test_ext().execute_with(|| { + let call = Call::Balances(pallet_balances::Call::transfer { dest: 1, value: 1 }); + + assert_ok!(call.clone().dispatch(Origin::signed(0))); + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_err!( + call.clone().dispatch(Origin::signed(0)), + frame_system::Error::::CallFiltered + ); + // TODO ^^^ consider refactor to throw a safe mode error, not generic `CallFiltered` + }); +} + +// SIGNED ORIGIN CALL TESTS --------------------- + +#[test] +fn can_enable_with_signed_origin() { + new_test_ext().execute_with(|| { + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_eq!( + SafeMode::enabled().unwrap(), + System::block_number() + mock::EnableDuration::get() + ); + assert_eq!(Balances::reserved_balance(0), mock::EnableStakeAmount::get()); + assert_noop!(SafeMode::enable(Origin::signed(0)), Error::::IsEnabled); + // Assert the stake. + assert_eq!(Stakes::::get(0, 1), Some(mock::EnableStakeAmount::get())); + }); +} + +#[test] +fn can_extend_with_signed_origin() { + new_test_ext().execute_with(|| { + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_ok!(SafeMode::extend(Origin::signed(0))); + assert_eq!( + SafeMode::enabled().unwrap(), + System::block_number() + mock::EnableDuration::get() + mock::ExtendDuration::get() + ); + assert_eq!( + Balances::reserved_balance(0), + mock::EnableStakeAmount::get() + mock::ExtendStakeAmount::get() + ); + }); +} + +#[test] +fn fails_signed_origin_when_explicit_origin_required() { + new_test_ext().execute_with(|| { + assert_eq!(SafeMode::enabled(), None); + let enabled_at_block = System::block_number(); + + assert_err!(SafeMode::force_enable(Origin::signed(0)), DispatchError::BadOrigin); + assert_err!(SafeMode::force_extend(Origin::signed(0)), DispatchError::BadOrigin); + assert_err!(SafeMode::force_disable(Origin::signed(0)), DispatchError::BadOrigin); + assert_err!( + SafeMode::slash_stake(Origin::signed(0), 0, enabled_at_block), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::repay_stake(Origin::signed(0), 0, enabled_at_block), + DispatchError::BadOrigin + ); + }); +} + +// CONFIGURED ORIGIN CALL TESTS --------------------- + +#[test] +fn fails_force_disable_if_not_enabled() { + new_test_ext().execute_with(|| { + assert_noop!( + SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get())), + Error::::IsDisabled + ); + assert_noop!( + SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get())), + Error::::IsDisabled + ); + }); +} + +#[test] +fn can_force_enable_with_config_origin() { + new_test_ext().execute_with(|| { + assert_ok!(SafeMode::force_enable(ForceEnableOrigin::Weak.signed())); + assert_eq!( + SafeMode::enabled().unwrap(), + System::block_number() + ForceEnableOrigin::Weak.duration() + ); + assert_noop!( + SafeMode::force_enable(ForceEnableOrigin::Weak.signed()), + Error::::IsEnabled + ); + assert_eq!(Balances::reserved_balance(ForceEnableOrigin::Weak.acc()), 0); + }); +} + +#[test] +fn can_force_disable_with_config_origin() { + new_test_ext().execute_with(|| { + assert_eq!(SafeMode::enabled(), None); + assert_err!( + SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get())), + Error::::IsDisabled + ); + assert_ok!(SafeMode::force_enable(ForceEnableOrigin::Weak.signed())); + assert_eq!(Balances::reserved_balance(ForceEnableOrigin::Weak.acc()), 0); + assert_ok!(SafeMode::force_disable(Origin::signed(mock::DisableOrigin::get()))); + }); +} + +#[test] +fn can_force_extend_with_config_origin() { + new_test_ext().execute_with(|| { + // Enable by `Weak` and extended by `Medium`. + assert_ok!(SafeMode::force_enable(ForceEnableOrigin::Weak.signed())); + assert_eq!( + SafeMode::enabled().unwrap(), + System::block_number() + ForceEnableOrigin::Weak.duration() + ); + assert_ok!(SafeMode::force_extend(ForceExtendOrigin::Medium.signed())); + assert_eq!( + SafeMode::enabled().unwrap(), + System::block_number() + + ForceEnableOrigin::Weak.duration() + + ForceExtendOrigin::Medium.duration() + ); + assert_eq!(Balances::reserved_balance(ForceEnableOrigin::Weak.acc()), 0); + assert_eq!(Balances::reserved_balance(mock::ExtendDuration::get()), 0); + }); +} + +#[test] +fn can_repay_stake_with_config_origin() { + new_test_ext().execute_with(|| { + let enabled_at_block = System::block_number(); + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_err!( + SafeMode::repay_stake(Origin::signed(mock::RepayOrigin::get()), 0, enabled_at_block), + Error::::IsEnabled + ); + run_to(mock::EnableDuration::get() + enabled_at_block + 1); + SafeMode::on_initialize(System::block_number()); + assert_ok!(SafeMode::repay_stake( + Origin::signed(mock::RepayOrigin::get()), + 0, + enabled_at_block + )); + // TODO: test accounting is correct + }); +} + +#[test] +fn can_slash_stake_with_config_origin() { + new_test_ext().execute_with(|| { + let enabled_at_block = System::block_number(); + assert_ok!(SafeMode::enable(Origin::signed(0))); + assert_err!( + SafeMode::slash_stake(Origin::signed(mock::RepayOrigin::get()), 0, enabled_at_block), + Error::::IsEnabled + ); + run_to(mock::EnableDuration::get() + enabled_at_block + 1); + SafeMode::on_initialize(System::block_number()); + assert_ok!(SafeMode::slash_stake( + Origin::signed(mock::RepayOrigin::get()), + 0, + enabled_at_block + )); + // TODO: test accounting is correct + }); +} + +#[test] +fn fails_when_explicit_origin_required() { + new_test_ext().execute_with(|| { + assert_eq!(SafeMode::enabled(), None); + let enabled_at_block = System::block_number(); + + assert_err!( + SafeMode::force_extend(ForceEnableOrigin::Weak.signed()), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::force_disable(ForceEnableOrigin::Weak.signed()), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::slash_stake(ForceEnableOrigin::Weak.signed(), 0, enabled_at_block), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::repay_stake(ForceEnableOrigin::Weak.signed(), 0, enabled_at_block), + DispatchError::BadOrigin + ); + + assert_err!( + SafeMode::force_enable(ForceExtendOrigin::Weak.signed()), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::force_disable(ForceExtendOrigin::Weak.signed()), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::slash_stake(ForceExtendOrigin::Weak.signed(), 0, enabled_at_block), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::repay_stake(ForceExtendOrigin::Weak.signed(), 0, enabled_at_block), + DispatchError::BadOrigin + ); + + assert_err!( + SafeMode::force_enable(Origin::signed(mock::DisableOrigin::get())), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::force_extend(Origin::signed(mock::DisableOrigin::get())), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::slash_stake(Origin::signed(mock::DisableOrigin::get()), 0, enabled_at_block), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::repay_stake(Origin::signed(mock::DisableOrigin::get()), 0, enabled_at_block), + DispatchError::BadOrigin + ); + + assert_err!( + SafeMode::force_enable(Origin::signed(mock::RepayOrigin::get())), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::force_extend(Origin::signed(mock::RepayOrigin::get())), + DispatchError::BadOrigin + ); + assert_err!( + SafeMode::force_disable(Origin::signed(mock::RepayOrigin::get())), + DispatchError::BadOrigin + ); + }); +} diff --git a/frame/safe-mode/src/weights.rs b/frame/safe-mode/src/weights.rs new file mode 100644 index 0000000000000..c96de6f978947 --- /dev/null +++ b/frame/safe-mode/src/weights.rs @@ -0,0 +1,82 @@ +// 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. + +//! Autogenerated weights for pallet_safe_mode +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev +//! DATE: 2022-09-15, STEPS: `1`, REPEAT: 1, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! HOSTNAME: `pop-os`, CPU: `12th Gen Intel(R) Core(TM) i7-12700H` +//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024 + +// Executed Command: +// ./target/release/substrate +// benchmark +// pallet +// --steps +// 1 +// --repeat +// 1 +// --extrinsic +// * +// --execution +// wasm +// --wasm-execution +// compiled +// --heap-pages +// 4096 +// --pallet +// pallet_safe_mode +// --chain +// dev +// --output +// frame/safe-mode/src/weights.rs +// --template=./.maintain/frame-weight-template.hbs + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] + +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; +use sp_std::marker::PhantomData; + +/// Weight functions needed for pallet_safe_mode. +pub trait WeightInfo { + fn enable() -> Weight; +} + +/// Weights for pallet_safe_mode using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + // Storage: SafeMode Stakes (r:1 w:1) + // Storage: SafeMode Enabled (r:1 w:1) + fn enable() -> Weight { + Weight::from_ref_time(51_688_000 as u64) + .saturating_add(T::DbWeight::get().reads(2 as u64)) + .saturating_add(T::DbWeight::get().writes(2 as u64)) + } +} + +// For backwards compatibility and tests +impl WeightInfo for () { + // Storage: SafeMode Stakes (r:1 w:1) + // Storage: SafeMode Enabled (r:1 w:1) + fn enable() -> Weight { + Weight::from_ref_time(51_688_000 as u64) + .saturating_add(RocksDbWeight::get().reads(2 as u64)) + .saturating_add(RocksDbWeight::get().writes(2 as u64)) + } +}