From 8ef837ac8618559bab25aef5585ffa9789c80e1b Mon Sep 17 00:00:00 2001 From: Xavier Lau Date: Tue, 16 Apr 2024 04:01:02 +0800 Subject: [PATCH] Rate limit --- node/src/chain_spec/crab.rs | 2 + node/src/chain_spec/darwinia.rs | 2 + pallet/staking/src/benchmarking.rs | 9 ++++ pallet/staking/src/lib.rs | 42 +++++++++++++++- pallet/staking/src/migration/v2.rs | 4 +- pallet/staking/src/mock.rs | 1 + pallet/staking/src/tests.rs | 49 +++++++++++++++++-- pallet/staking/src/weights.rs | 7 +++ precompile/staking/src/mock.rs | 20 +++++--- runtime/crab/src/weights/darwinia_staking.rs | 3 ++ .../darwinia/src/weights/darwinia_staking.rs | 3 ++ .../pangolin/src/weights/darwinia_staking.rs | 3 ++ 12 files changed, 133 insertions(+), 12 deletions(-) diff --git a/node/src/chain_spec/crab.rs b/node/src/chain_spec/crab.rs index 01aac09e2..05b98d28a 100644 --- a/node/src/chain_spec/crab.rs +++ b/node/src/chain_spec/crab.rs @@ -200,6 +200,7 @@ pub fn genesis_config() -> ChainSpec { darwinia_staking: DarwiniaStakingConfig { now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), elapsed_time: 0, + max_unstake_ring: 10_000_000 * UNIT, collator_count: 6, collators: collators .iter() @@ -303,6 +304,7 @@ fn testnet_genesis( darwinia_staking: DarwiniaStakingConfig { now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), elapsed_time: 0, + max_unstake_ring: 10_000_000 * UNIT, collator_count: collators.len() as _, collators: collators.iter().map(|(a, _)| (a.to_owned(), UNIT)).collect(), }, diff --git a/node/src/chain_spec/darwinia.rs b/node/src/chain_spec/darwinia.rs index c1cc9fc1d..247871b15 100644 --- a/node/src/chain_spec/darwinia.rs +++ b/node/src/chain_spec/darwinia.rs @@ -196,6 +196,7 @@ pub fn genesis_config() -> ChainSpec { darwinia_staking: DarwiniaStakingConfig { now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), elapsed_time: 0, + max_unstake_ring: 10_000_000 * UNIT, collator_count: 5, collators: collators .iter() @@ -301,6 +302,7 @@ fn testnet_genesis( darwinia_staking: DarwiniaStakingConfig { now: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis(), elapsed_time: 0, + max_unstake_ring: 10_000_000 * UNIT, collator_count: collators.len() as _, collators: collators.iter().map(|(a, _)| (a.to_owned(), UNIT)).collect(), }, diff --git a/pallet/staking/src/benchmarking.rs b/pallet/staking/src/benchmarking.rs index f6f01c8ef..71b7d971d 100644 --- a/pallet/staking/src/benchmarking.rs +++ b/pallet/staking/src/benchmarking.rs @@ -158,6 +158,15 @@ mod benchmarks { _(RawOrigin::Signed(sender), a); } + #[benchmark] + fn set_max_unstake_ring() { + // Worst-case scenario: + // + // Set max unstake ring successfully. + #[extrinsic_call] + _(RawOrigin::Root, 1); + } + #[benchmark] fn set_collator_count() { // Worst-case scenario: diff --git a/pallet/staking/src/lib.rs b/pallet/staking/src/lib.rs index 225760e1b..6d4e9fbaa 100644 --- a/pallet/staking/src/lib.rs +++ b/pallet/staking/src/lib.rs @@ -166,8 +166,8 @@ pub mod pallet { pub enum Error { /// Exceed maximum deposit count. ExceedMaxDeposits, - /// Exceed maximum unstaking/unbonding count. - ExceedMaxUnstakings, + /// Exceed maximum unstake amount. + ExceedMaxUnstakeAmount, /// Deposit not found. DepositNotFound, /// You are not a staker. @@ -282,6 +282,20 @@ pub mod pallet { #[pallet::getter(fn elapsed_time)] pub type ElapsedTime = StorageValue<_, Moment, ValueQuery>; + /// Max unstake RING limit. + /// + /// The maximum RING amount that can be unstaked in a session. + #[pallet::storage] + #[pallet::getter(fn max_unstake_ring)] + pub type MaxUnstakeRing = StorageValue<_, Balance, ValueQuery>; + + /// Unstake accumulator. + /// + /// Tracks the total RING amount being unstaked in a session. + #[pallet::storage] + #[pallet::getter(fn accumulate_unstake)] + pub type AccumulateUnstake = StorageValue<_, Balance, ValueQuery>; + #[derive(DefaultNoBound)] #[pallet::genesis_config] pub struct GenesisConfig { @@ -289,6 +303,8 @@ pub mod pallet { pub now: Moment, /// The running time of Darwinia1. pub elapsed_time: Moment, + /// Max unstake RING limit. + pub max_unstake_ring: Balance, /// Genesis collator count. pub collator_count: u32, /// Genesis collator preferences. @@ -304,6 +320,7 @@ pub mod pallet { >::put(self.now); >::put(self.elapsed_time); + >::put(self.max_unstake_ring); >::put(self.collator_count); self.collators.iter().for_each(|(who, ring_amount)| { @@ -400,6 +417,8 @@ pub mod pallet { return Ok(()); } + let mut acc = >::get().saturating_add(ring_amount); + >::try_mutate(&who, |l| { let l = l.as_mut().ok_or(>::NotStaker)?; @@ -413,6 +432,8 @@ pub mod pallet { } for d in deposits { + acc = acc.saturating_add(T::Deposit::amount(&who, d).unwrap_or_default()); + l.deposits.remove( l.deposits .iter() @@ -426,6 +447,12 @@ pub mod pallet { DispatchResult::Ok(()) })?; + if acc <= >::get() { + >::put(acc); + } else { + Err(>::ExceedMaxUnstakeAmount)?; + } + Self::try_clean_ledger_of(&who); Ok(()) @@ -493,6 +520,17 @@ pub mod pallet { Ok(()) } + /// Set max unstake RING limit. + #[pallet::call_index(9)] + #[pallet::weight(::WeightInfo::set_max_unstake_ring())] + pub fn set_max_unstake_ring(origin: OriginFor, amount: Balance) -> DispatchResult { + ensure_root(origin)?; + + >::put(amount); + + Ok(()) + } + /// Set collator count. /// /// This will apply to the incoming session. diff --git a/pallet/staking/src/migration/v2.rs b/pallet/staking/src/migration/v2.rs index 353240d49..863a377e2 100644 --- a/pallet/staking/src/migration/v2.rs +++ b/pallet/staking/src/migration/v2.rs @@ -2,6 +2,7 @@ use core::marker::PhantomData; // darwinia use crate::*; +use dc_types::UNIT; // substrate use frame_support::traits::OnRuntimeUpgrade; #[cfg(feature = "try-runtime")] @@ -77,11 +78,12 @@ where return T::DbWeight::get().reads(r); } - let mut w = 4; + let mut w = 5; >::kill(); >::kill(); >::kill(); + >::put(10_000_000 * UNIT); >::translate::, _>(|a, o| { w += 6; diff --git a/pallet/staking/src/mock.rs b/pallet/staking/src/mock.rs index 8a5168b60..2ab28137f 100644 --- a/pallet/staking/src/mock.rs +++ b/pallet/staking/src/mock.rs @@ -368,6 +368,7 @@ impl ExtBuilder { .assimilate_storage(&mut storage) .unwrap(); darwinia_staking::GenesisConfig:: { + max_unstake_ring: 100 * UNIT, collator_count: self.collator_count, collators: if self.genesis_collator { (1..=self.collator_count).map(|i| (i, UNIT)).collect() diff --git a/pallet/staking/src/tests.rs b/pallet/staking/src/tests.rs index 377dfa3c8..6769fd3ab 100644 --- a/pallet/staking/src/tests.rs +++ b/pallet/staking/src/tests.rs @@ -199,6 +199,7 @@ fn unstake_should_work() { // Unstake 1 RING. assert_ok!(Staking::unstake(RuntimeOrigin::signed(1), UNIT, Vec::new())); + assert_eq!(Staking::accumulate_unstake(), UNIT); assert_eq!(Balances::free_balance(1), 995 * UNIT); assert_eq!( Staking::ledger_of(1).unwrap(), @@ -214,6 +215,7 @@ fn unstake_should_work() { // Unstake 1 deposit. Efflux::block(1); assert_ok!(Staking::unstake(RuntimeOrigin::signed(1), 0, vec![1])); + assert_eq!(Staking::accumulate_unstake(), 2 * UNIT); assert_eq!( Staking::ledger_of(1).unwrap(), Ledger { ring: 2 * UNIT, deposits: BoundedVec::truncate_from(vec![0, 2]) } @@ -250,17 +252,58 @@ fn unstake_should_work() { // Unstake 2 RING and 2 deposits. Efflux::block(1); assert_ok!(Staking::unstake(RuntimeOrigin::signed(1), 2 * UNIT, vec![0, 2])); + assert_eq!(Staking::accumulate_unstake(), 6 * UNIT); assert!(Staking::ledger_of(1).is_none()); assert_eq!( Deposit::deposit_of(1).unwrap(), ::MaxDeposits>>::truncate_from( vec![ - DepositS { id: 0, value: UNIT, start_time: 3, expired_time: 2635200003, in_use: false }, - DepositS { id: 1, value: UNIT, start_time: 3, expired_time: 2635200003, in_use: false }, - DepositS { id: 2, value: UNIT, start_time: 3, expired_time: 2635200003, in_use: false } + DepositS { + id: 0, + value: UNIT, + start_time: 3, + expired_time: 2635200003, + in_use: false + }, + DepositS { + id: 1, + value: UNIT, + start_time: 3, + expired_time: 2635200003, + in_use: false + }, + DepositS { + id: 2, + value: UNIT, + start_time: 3, + expired_time: 2635200003, + in_use: false + } ] ) ); + + // Prepare rate limit test data. + assert_ok!(Deposit::lock(RuntimeOrigin::signed(1), 94 * UNIT + 1, 1)); + assert_ok!(Staking::stake(RuntimeOrigin::signed(1), 94 * UNIT + 1, vec![3])); + + // Unstake 94 UNIT + 1. + assert_noop!( + Staking::unstake(RuntimeOrigin::signed(1), 94 * UNIT + 1, Vec::new()), + >::ExceedMaxUnstakeAmount + ); + + // Unstake 94 UNIT + 1. + assert_noop!( + Staking::unstake(RuntimeOrigin::signed(1), 0, vec![3]), + >::ExceedMaxUnstakeAmount + ); + + // Unstake RING(94 UNIT + 1) and deposit(94 UNIT + 1). + assert_noop!( + Staking::unstake(RuntimeOrigin::signed(1), 94 * UNIT + 1, vec![3]), + >::ExceedMaxUnstakeAmount + ); }); } diff --git a/pallet/staking/src/weights.rs b/pallet/staking/src/weights.rs index 397832e76..51dc53194 100644 --- a/pallet/staking/src/weights.rs +++ b/pallet/staking/src/weights.rs @@ -58,6 +58,7 @@ pub trait WeightInfo { fn nominate() -> Weight; fn chill() -> Weight; fn payout() -> Weight; + fn set_max_unstake_ring() -> Weight; fn set_collator_count() -> Weight; } @@ -171,6 +172,9 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads(4_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + fn set_max_unstake_ring() -> Weight { + Default::default() + } /// Storage: `DarwiniaStaking::CollatorCount` (r:0 w:1) /// Proof: `DarwiniaStaking::CollatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_collator_count() -> Weight { @@ -293,6 +297,9 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads(4_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn set_max_unstake_ring() -> Weight { + Default::default() + } /// Storage: `DarwiniaStaking::CollatorCount` (r:0 w:1) /// Proof: `DarwiniaStaking::CollatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_collator_count() -> Weight { diff --git a/precompile/staking/src/mock.rs b/precompile/staking/src/mock.rs index 8b5817f5a..0b492f7b7 100644 --- a/precompile/staking/src/mock.rs +++ b/precompile/staking/src/mock.rs @@ -268,16 +268,24 @@ impl ExtBuilder { } pub(crate) fn build(self) -> sp_io::TestExternalities { - let mut t = >::default() - .build_storage() - .expect("Frame system builds valid default genesis config"); + let mut storage = + >::default().build_storage().unwrap(); pallet_balances::GenesisConfig:: { balances: self.balances } - .assimilate_storage(&mut t) - .expect("Pallet balances storage can be assimilated"); + .assimilate_storage(&mut storage) + .unwrap(); + darwinia_staking::GenesisConfig:: { + max_unstake_ring: 500, + collator_count: 1, + ..Default::default() + } + .assimilate_storage(&mut storage) + .unwrap(); + + let mut ext = sp_io::TestExternalities::new(storage); - let mut ext = sp_io::TestExternalities::new(t); ext.execute_with(|| System::set_block_number(1)); + ext } } diff --git a/runtime/crab/src/weights/darwinia_staking.rs b/runtime/crab/src/weights/darwinia_staking.rs index 590d2384e..cfd61b156 100644 --- a/runtime/crab/src/weights/darwinia_staking.rs +++ b/runtime/crab/src/weights/darwinia_staking.rs @@ -168,6 +168,9 @@ impl darwinia_staking::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } + fn set_max_unstake_ring() -> Weight { + Default::default() + } /// Storage: `DarwiniaStaking::CollatorCount` (r:0 w:1) /// Proof: `DarwiniaStaking::CollatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_collator_count() -> Weight { diff --git a/runtime/darwinia/src/weights/darwinia_staking.rs b/runtime/darwinia/src/weights/darwinia_staking.rs index ebc10730e..412514971 100644 --- a/runtime/darwinia/src/weights/darwinia_staking.rs +++ b/runtime/darwinia/src/weights/darwinia_staking.rs @@ -168,6 +168,9 @@ impl darwinia_staking::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(35)) .saturating_add(T::DbWeight::get().writes(2)) } + fn set_max_unstake_ring() -> Weight { + Default::default() + } /// Storage: `DarwiniaStaking::CollatorCount` (r:0 w:1) /// Proof: `DarwiniaStaking::CollatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_collator_count() -> Weight { diff --git a/runtime/pangolin/src/weights/darwinia_staking.rs b/runtime/pangolin/src/weights/darwinia_staking.rs index 07b1d4b49..cf8708ca7 100644 --- a/runtime/pangolin/src/weights/darwinia_staking.rs +++ b/runtime/pangolin/src/weights/darwinia_staking.rs @@ -168,6 +168,9 @@ impl darwinia_staking::WeightInfo for WeightInfo { .saturating_add(T::DbWeight::get().reads(4)) .saturating_add(T::DbWeight::get().writes(2)) } + fn set_max_unstake_ring() -> Weight { + Default::default() + } /// Storage: `DarwiniaStaking::CollatorCount` (r:0 w:1) /// Proof: `DarwiniaStaking::CollatorCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_collator_count() -> Weight {