diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index 9ff19760c7..0b0c033ae8 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -558,6 +558,7 @@ impl pallet_staking::Config for Runtime { // Alternatively, use pallet_staking::UseNominatorsMap to just use the nominators map. // Note that the aforementioned does not scale to a very large number of nominators. type SortedListProvider = BagsList; + type MaxUnlockingChunks = ConstU32<32>; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; } diff --git a/substrate/frame/babe/src/mock.rs b/substrate/frame/babe/src/mock.rs index 2e4b6023f1..152ec5ab20 100644 --- a/substrate/frame/babe/src/mock.rs +++ b/substrate/frame/babe/src/mock.rs @@ -198,6 +198,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/substrate/frame/grandpa/src/mock.rs b/substrate/frame/grandpa/src/mock.rs index d07f3136d9..9dac33f979 100644 --- a/substrate/frame/grandpa/src/mock.rs +++ b/substrate/frame/grandpa/src/mock.rs @@ -206,6 +206,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/substrate/frame/offences/benchmarking/src/mock.rs b/substrate/frame/offences/benchmarking/src/mock.rs index 3b5e640867..22c9af0f4c 100644 --- a/substrate/frame/offences/benchmarking/src/mock.rs +++ b/substrate/frame/offences/benchmarking/src/mock.rs @@ -176,6 +176,7 @@ impl pallet_staking::Config for Test { type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type SortedListProvider = pallet_staking::UseNominatorsMap; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/substrate/frame/session/benchmarking/src/mock.rs b/substrate/frame/session/benchmarking/src/mock.rs index 37305437ca..a9328b6546 100644 --- a/substrate/frame/session/benchmarking/src/mock.rs +++ b/substrate/frame/session/benchmarking/src/mock.rs @@ -181,6 +181,7 @@ impl pallet_staking::Config for Test { type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; + type MaxUnlockingChunks = ConstU32<32>; type SortedListProvider = pallet_staking::UseNominatorsMap; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); diff --git a/substrate/frame/staking/src/benchmarking.rs b/substrate/frame/staking/src/benchmarking.rs index 65a1ee92e2..7593522055 100644 --- a/substrate/frame/staking/src/benchmarking.rs +++ b/substrate/frame/staking/src/benchmarking.rs @@ -618,7 +618,7 @@ benchmarks! { } rebond { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let l in 1 .. MaxUnlockingChunks::get() as u32; // clean up any existing state. clear_validators_and_nominators::(); @@ -652,7 +652,7 @@ benchmarks! { let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap() } Ledger::::insert(controller.clone(), staking_ledger.clone()); let original_bonded: BalanceOf = staking_ledger.active; @@ -702,7 +702,7 @@ benchmarks! { stash: stash.clone(), active: T::Currency::minimum_balance() - One::one(), total: T::Currency::minimum_balance() - One::one(), - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }; Ledger::::insert(&controller, l); @@ -788,7 +788,7 @@ benchmarks! { #[extra] do_slash { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let l in 1 .. MaxUnlockingChunks::get() as u32; let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { @@ -796,7 +796,7 @@ benchmarks! { era: EraIndex::zero(), }; for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()).unwrap(); } Ledger::::insert(controller, staking_ledger); let slash_amount = T::Currency::minimum_balance() * 10u32.into(); diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs index 22143ff1ee..d833ac86fe 100644 --- a/substrate/frame/staking/src/lib.rs +++ b/substrate/frame/staking/src/lib.rs @@ -301,6 +301,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact}; use frame_support::{ + parameter_types, traits::{ConstU32, Currency, Get}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, @@ -347,6 +348,10 @@ type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; +parameter_types! { + pub MaxUnlockingChunks: u32 = 32; +} + /// Information regarding the active era (era in used in session). #[derive(Encode, Decode, RuntimeDebug, TypeInfo)] pub struct ActiveEraInfo { @@ -446,9 +451,10 @@ pub struct StakingLedger { /// rounds. #[codec(compact)] pub active: Balance, - /// Any balance that is becoming free, which may eventually be transferred out - /// of the stash (assuming it doesn't get slashed first). - pub unlocking: Vec>, + /// Any balance that is becoming free, which may eventually be transferred out of the stash + /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first + /// in, first out queue where the new (higher value) eras get pushed on the back. + pub unlocking: BoundedVec, MaxUnlockingChunks>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. pub claimed_rewards: Vec, @@ -463,7 +469,7 @@ impl Self { let mut total = self.total; - let unlocking = self + let unlocking: BoundedVec<_, _> = self .unlocking .into_iter() .filter(|chunk| { @@ -483,7 +489,11 @@ impl>() + .try_into() + .expect( + "filtering items from a bounded vec always leaves length less than bounds. qed", + ); Self { stash: self.stash, diff --git a/substrate/frame/staking/src/mock.rs b/substrate/frame/staking/src/mock.rs index 95f305dfdd..12d3804b4e 100644 --- a/substrate/frame/staking/src/mock.rs +++ b/substrate/frame/staking/src/mock.rs @@ -271,6 +271,7 @@ impl crate::pallet::pallet::Config for Test { type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs index ae20550cd4..5cd0d0107f 100644 --- a/substrate/frame/staking/src/pallet/impls.rs +++ b/substrate/frame/staking/src/pallet/impls.rs @@ -928,7 +928,7 @@ impl ElectionDataProvider for Pallet { stash: voter.clone(), active: stake, total: stake, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); @@ -946,7 +946,7 @@ impl ElectionDataProvider for Pallet { stash: target.clone(), active: stake, total: stake, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); @@ -983,7 +983,7 @@ impl ElectionDataProvider for Pallet { stash: v.clone(), active: stake, total: stake, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); @@ -1004,7 +1004,7 @@ impl ElectionDataProvider for Pallet { stash: v.clone(), active: stake, total: stake, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs index f53e6b50d7..9d3b438dae 100644 --- a/substrate/frame/staking/src/pallet/mod.rs +++ b/substrate/frame/staking/src/pallet/mod.rs @@ -21,8 +21,8 @@ use frame_election_provider_support::SortedListProvider; use frame_support::{ pallet_prelude::*, traits::{ - Currency, CurrencyToVote, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier, - LockableCurrency, OnUnbalanced, UnixTime, + Currency, CurrencyToVote, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, + LockIdentifier, LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, }; @@ -40,12 +40,11 @@ pub use impls::*; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, - Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, - RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + Exposure, Forcing, MaxUnlockingChunks, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, + Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; -pub const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; #[frame_support::pallet] @@ -157,6 +156,10 @@ pub mod pallet { /// the bags-list is not desired, [`impls::UseNominatorsMap`] is likely the desired option. type SortedListProvider: SortedListProvider; + /// The maximum number of `unlocking` chunks a [`StakingLedger`] can have. Effectively + /// determines how many unique eras a staker may be unbonding in. + type MaxUnlockingChunks: Get; + /// Some parameters of the benchmarking. type BenchmarkingConfig: BenchmarkingConfig; @@ -772,7 +775,7 @@ pub mod pallet { stash, total: value, active: value, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: (last_reward_era..current_era).collect(), }; Self::update_ledger(&controller, &item); @@ -837,7 +840,7 @@ pub mod pallet { /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move /// the funds out of management ready for transfer. /// - /// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`) + /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need /// to be called first to remove some of the chunks (if possible). /// @@ -854,7 +857,10 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::::NoMoreChunks,); + ensure!( + ledger.unlocking.len() < MaxUnlockingChunks::get() as usize, + Error::::NoMoreChunks, + ); let mut value = value.min(ledger.active); @@ -881,7 +887,19 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - ledger.unlocking.push(UnlockChunk { value, era }); + if let Some(mut chunk) = + ledger.unlocking.last_mut().filter(|chunk| chunk.era == era) + { + // To keep the chunk count down, we only keep one chunk per era. Since + // `unlocking` is a FiFo queue, if a chunk exists for `era` we know that it will + // be the last one. + chunk.value = chunk.value.defensive_saturating_add(value) + } else { + ledger + .unlocking + .try_push(UnlockChunk { value, era }) + .map_err(|_| Error::::NoMoreChunks)?; + }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); @@ -1348,10 +1366,10 @@ pub mod pallet { /// /// # /// - Time complexity: O(L), where L is unlocking chunks - /// - Bounded by `MAX_UNLOCKING_CHUNKS`. + /// - Bounded by `MaxUnlockingChunks`. /// - Storage changes: Can't increase storage, only decrease it. /// # - #[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))] + #[pallet::weight(T::WeightInfo::rebond(MaxUnlockingChunks::get() as u32))] pub fn rebond( origin: OriginFor, #[pallet::compact] value: BalanceOf, diff --git a/substrate/frame/staking/src/tests.rs b/substrate/frame/staking/src/tests.rs index 4073c069fb..0fc6cd27a2 100644 --- a/substrate/frame/staking/src/tests.rs +++ b/substrate/frame/staking/src/tests.rs @@ -17,10 +17,10 @@ //! Tests for the module. -use super::{Event, *}; +use super::{Event, MaxUnlockingChunks, *}; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_noop, assert_ok, + assert_noop, assert_ok, bounded_vec, dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{Currency, Get, ReservableCurrency}, @@ -104,7 +104,7 @@ fn basic_setup_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] }) ); @@ -115,7 +115,7 @@ fn basic_setup_works() { stash: 21, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] }) ); @@ -138,7 +138,7 @@ fn basic_setup_works() { stash: 101, total: 500, active: 500, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] }) ); @@ -382,7 +382,7 @@ fn staking_should_work() { stash: 3, total: 1500, active: 1500, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![0], }) ); @@ -936,7 +936,7 @@ fn reward_destination_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -959,7 +959,7 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![0], }) ); @@ -987,7 +987,7 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![0, 1], }) ); @@ -1016,7 +1016,7 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![0, 1, 2], }) ); @@ -1081,7 +1081,7 @@ fn bond_extra_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1098,7 +1098,7 @@ fn bond_extra_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1112,7 +1112,7 @@ fn bond_extra_works() { stash: 11, total: 1000000, active: 1000000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1150,7 +1150,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1168,7 +1168,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1189,7 +1189,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1207,7 +1207,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1220,7 +1220,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1236,7 +1236,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: vec![] }), ); @@ -1252,7 +1252,7 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 100, active: 100, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] }), ); @@ -1262,23 +1262,35 @@ fn bond_extra_and_withdraw_unbonded_works() { #[test] fn too_many_unbond_calls_should_not_work() { ExtBuilder::default().build_and_execute(|| { - // locked at era 0 until 3 - for _ in 0..MAX_UNLOCKING_CHUNKS - 1 { + let mut current_era = 0; + // locked at era MaxUnlockingChunks - 1 until 3 + for i in 0..MaxUnlockingChunks::get() - 1 { + // There is only 1 chunk per era, so we need to be in a new era to create a chunk. + current_era = i as u32; + mock::start_active_era(current_era); assert_ok!(Staking::unbond(Origin::signed(10), 1)); } - mock::start_active_era(1); + current_era += 1; + mock::start_active_era(current_era); - // locked at era 1 until 4 + // This chunk is locked at `current_era` through `current_era + 2` (because BondingDuration + // == 3). assert_ok!(Staking::unbond(Origin::signed(10), 1)); + assert_eq!( + Staking::ledger(&10).unwrap().unlocking.len(), + MaxUnlockingChunks::get() as usize + ); // can't do more. assert_noop!(Staking::unbond(Origin::signed(10), 1), Error::::NoMoreChunks); - mock::start_active_era(3); + current_era += 2; + mock::start_active_era(current_era); assert_noop!(Staking::unbond(Origin::signed(10), 1), Error::::NoMoreChunks); - // free up. + // free up everything except the most recently added chunk. assert_ok!(Staking::withdraw_unbonded(Origin::signed(10), 0)); + assert_eq!(Staking::ledger(&10).unwrap().unlocking.len(), 1); // Can add again. assert_ok!(Staking::unbond(Origin::signed(10), 1)); @@ -1310,7 +1322,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1329,7 +1341,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 2 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1342,7 +1354,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1355,7 +1367,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: vec![], }) ); @@ -1368,7 +1380,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 5 }], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: vec![], }) ); @@ -1381,7 +1393,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1396,11 +1408,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![ - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 300, era: 5 }, - ], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: vec![], }) ); @@ -1413,10 +1421,7 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: vec![ - UnlockChunk { value: 300, era: 5 }, - UnlockChunk { value: 100, era: 5 }, - ], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: vec![], }) ); @@ -1443,7 +1448,7 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1458,7 +1463,7 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 2 + 3 },], + unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], claimed_rewards: vec![], }) ); @@ -1473,9 +1478,9 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 300, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 300, era: 3 + 3 }, + UnlockChunk { value: 300, era: 3 + 3 } ], claimed_rewards: vec![], }) @@ -1491,10 +1496,10 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 100, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 300, era: 3 + 3 }, - UnlockChunk { value: 200, era: 4 + 3 }, + UnlockChunk { value: 200, era: 4 + 3 } ], claimed_rewards: vec![], }) @@ -1508,9 +1513,9 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 500, - unlocking: vec![ + unlocking: bounded_vec![ UnlockChunk { value: 400, era: 2 + 3 }, - UnlockChunk { value: 100, era: 3 + 3 }, + UnlockChunk { value: 100, era: 3 + 3 } ], claimed_rewards: vec![], }) @@ -1540,7 +1545,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1553,7 +1558,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 200, - unlocking: vec![UnlockChunk { value: 800, era: 1 + 3 }], + unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], claimed_rewards: vec![], }) ); @@ -1568,7 +1573,7 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -1604,7 +1609,7 @@ fn reward_to_stake_works() { stash: 21, total: 69, active: 69, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); @@ -1665,7 +1670,7 @@ fn reap_stash_works() { stash: 11, total: 5, active: 5, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }, ); @@ -1784,7 +1789,7 @@ fn bond_with_no_staked_value() { stash: 1, active: 0, total: 5, - unlocking: vec![UnlockChunk { value: 5, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], claimed_rewards: vec![], }) ); @@ -3354,7 +3359,7 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![1] }) ); @@ -3376,7 +3381,7 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: (1..=14).collect() }) ); @@ -3397,7 +3402,7 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![15, 98] }) ); @@ -3412,7 +3417,7 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![15, 23, 42, 69, 98] }) ); @@ -3607,7 +3612,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 9, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![], }) ); @@ -3619,7 +3624,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: (0..5).collect(), }) ); @@ -3631,7 +3636,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 13, total: 1000, active: 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: (15..99).collect(), }) ); @@ -3850,7 +3855,7 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] } ); @@ -3864,7 +3869,7 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], claimed_rewards: vec![] } ); @@ -3887,7 +3892,7 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] } ); @@ -3901,7 +3906,7 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], + unlocking: bounded_vec![UnlockChunk { value: 10 * 1000, era: 3 }], claimed_rewards: vec![] } ); @@ -3928,7 +3933,7 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: 1000 * ed, active: 1000 * ed, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] } ); @@ -3945,7 +3950,7 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: ed, active: ed, - unlocking: vec![], + unlocking: Default::default(), claimed_rewards: vec![] } ); diff --git a/substrate/frame/support/src/storage/bounded_vec.rs b/substrate/frame/support/src/storage/bounded_vec.rs index 206a7e5d4e..4e513258f9 100644 --- a/substrate/frame/support/src/storage/bounded_vec.rs +++ b/substrate/frame/support/src/storage/bounded_vec.rs @@ -25,7 +25,7 @@ use crate::{ }; use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use core::{ - ops::{Deref, Index, IndexMut}, + ops::{Deref, Index, IndexMut, RangeBounds}, slice::SliceIndex, }; use sp_std::{marker::PhantomData, prelude::*}; @@ -178,6 +178,19 @@ impl BoundedVec { pub fn iter_mut(&mut self) -> core::slice::IterMut<'_, T> { self.0.iter_mut() } + + /// Exactly the same semantics as [`slice::last_mut`]. + pub fn last_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Exact same semantics as [`Vec::drain`]. + pub fn drain(&mut self, range: R) -> sp_std::vec::Drain<'_, T> + where + R: RangeBounds, + { + self.0.drain(range) + } } impl> From> for Vec {