mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-13 09:21:05 +00:00
Only maintain at most 1 UnlockChunk per era (#10670)
* Only maintain at most 1 `UnlockChunk` per era * Bound `unlocking` * Run cargo +nightly-2021-10-29 fmt * Make benchmarks stuff compile * Update frame/staking/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Remove DerefMut; Implement neccesary methods directly * Doc comments for new BoundedVec methods * Fix benchmarks * wip bonded_vec macro * Correct rust doc * Apply suggestions from code review Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> * Update staking::Config impls * Add MaxUnlockingChunks to more places * Use defensive saturating add * FMT Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
This commit is contained in:
@@ -558,6 +558,7 @@ impl pallet_staking::Config for Runtime {
|
||||
// Alternatively, use pallet_staking::UseNominatorsMap<Runtime> 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<Runtime>;
|
||||
type BenchmarkingConfig = StakingBenchmarkingConfig;
|
||||
}
|
||||
|
||||
@@ -198,6 +198,7 @@ impl pallet_staking::Config for Test {
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -206,6 +206,7 @@ impl pallet_staking::Config for Test {
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -176,6 +176,7 @@ impl pallet_staking::Config for Test {
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ impl pallet_staking::Config for Test {
|
||||
type OffendingValidatorsThreshold = ();
|
||||
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
|
||||
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
|
||||
@@ -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::<T>();
|
||||
@@ -652,7 +652,7 @@ benchmarks! {
|
||||
let mut staking_ledger = Ledger::<T>::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::<T>::insert(controller.clone(), staking_ledger.clone());
|
||||
let original_bonded: BalanceOf<T> = 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::<T>::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::<T>(0, 100, Default::default())?;
|
||||
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
|
||||
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
|
||||
@@ -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::<T>::insert(controller, staking_ledger);
|
||||
let slash_amount = T::Currency::minimum_balance() * 10u32.into();
|
||||
|
||||
@@ -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<T> = <<T as Config>::Currency as Currency<
|
||||
<T as frame_system::Config>::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<AccountId, Balance: HasCompact> {
|
||||
/// 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<UnlockChunk<Balance>>,
|
||||
/// 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<UnlockChunk<Balance>, MaxUnlockingChunks>,
|
||||
/// List of eras for which the stakers behind a validator have claimed rewards. Only updated
|
||||
/// for validators.
|
||||
pub claimed_rewards: Vec<EraIndex>,
|
||||
@@ -463,7 +469,7 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
|
||||
stash,
|
||||
total: Zero::zero(),
|
||||
active: Zero::zero(),
|
||||
unlocking: vec![],
|
||||
unlocking: Default::default(),
|
||||
claimed_rewards: vec![],
|
||||
}
|
||||
}
|
||||
@@ -472,7 +478,7 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
|
||||
/// total by the sum of their balances.
|
||||
fn consolidate_unlocked(self, current_era: EraIndex) -> Self {
|
||||
let mut total = self.total;
|
||||
let unlocking = self
|
||||
let unlocking: BoundedVec<_, _> = self
|
||||
.unlocking
|
||||
.into_iter()
|
||||
.filter(|chunk| {
|
||||
@@ -483,7 +489,11 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
|
||||
false
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
.collect::<Vec<_>>()
|
||||
.try_into()
|
||||
.expect(
|
||||
"filtering items from a bounded vec always leaves length less than bounds. qed",
|
||||
);
|
||||
|
||||
Self {
|
||||
stash: self.stash,
|
||||
|
||||
@@ -271,6 +271,7 @@ impl crate::pallet::pallet::Config for Test {
|
||||
type GenesisElectionProvider = Self::ElectionProvider;
|
||||
// NOTE: consider a macro and use `UseNominatorsMap<Self>` as well.
|
||||
type SortedListProvider = BagsList;
|
||||
type MaxUnlockingChunks = ConstU32<32>;
|
||||
type BenchmarkingConfig = TestBenchmarkingConfig;
|
||||
type WeightInfo = ();
|
||||
}
|
||||
|
||||
@@ -928,7 +928,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
stash: voter.clone(),
|
||||
active: stake,
|
||||
total: stake,
|
||||
unlocking: vec![],
|
||||
unlocking: Default::default(),
|
||||
claimed_rewards: vec![],
|
||||
},
|
||||
);
|
||||
@@ -946,7 +946,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
stash: target.clone(),
|
||||
active: stake,
|
||||
total: stake,
|
||||
unlocking: vec![],
|
||||
unlocking: Default::default(),
|
||||
claimed_rewards: vec![],
|
||||
},
|
||||
);
|
||||
@@ -983,7 +983,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
stash: v.clone(),
|
||||
active: stake,
|
||||
total: stake,
|
||||
unlocking: vec![],
|
||||
unlocking: Default::default(),
|
||||
claimed_rewards: vec![],
|
||||
},
|
||||
);
|
||||
@@ -1004,7 +1004,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
|
||||
stash: v.clone(),
|
||||
active: stake,
|
||||
total: stake,
|
||||
unlocking: vec![],
|
||||
unlocking: Default::default(),
|
||||
claimed_rewards: vec![],
|
||||
},
|
||||
);
|
||||
|
||||
@@ -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<Self::AccountId>;
|
||||
|
||||
/// 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<u32>;
|
||||
|
||||
/// 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::<T>::NotController)?;
|
||||
ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::<T>::NoMoreChunks,);
|
||||
ensure!(
|
||||
ledger.unlocking.len() < MaxUnlockingChunks::get() as usize,
|
||||
Error::<T>::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::<T>::NoMoreChunks)?;
|
||||
};
|
||||
// NOTE: ledger must be updated prior to calling `Self::weight_of`.
|
||||
Self::update_ledger(&controller, &ledger);
|
||||
|
||||
@@ -1348,10 +1366,10 @@ pub mod pallet {
|
||||
///
|
||||
/// # <weight>
|
||||
/// - 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.
|
||||
/// # </weight>
|
||||
#[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))]
|
||||
#[pallet::weight(T::WeightInfo::rebond(MaxUnlockingChunks::get() as u32))]
|
||||
pub fn rebond(
|
||||
origin: OriginFor<T>,
|
||||
#[pallet::compact] value: BalanceOf<T>,
|
||||
|
||||
@@ -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::<Test>::NoMoreChunks);
|
||||
|
||||
mock::start_active_era(3);
|
||||
current_era += 2;
|
||||
mock::start_active_era(current_era);
|
||||
|
||||
assert_noop!(Staking::unbond(Origin::signed(10), 1), Error::<Test>::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![]
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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<T, S> BoundedVec<T, S> {
|
||||
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<R>(&mut self, range: R) -> sp_std::vec::Drain<'_, T>
|
||||
where
|
||||
R: RangeBounds<usize>,
|
||||
{
|
||||
self.0.drain(range)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, S: Get<u32>> From<BoundedVec<T, S>> for Vec<T> {
|
||||
|
||||
Reference in New Issue
Block a user