mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-22 11:25:42 +00:00
Add batching to fast-unstake pallet (#12394)
* implement a brand new batch with all tests passing. * fix benchmarks as well * make benchmarks more or less work * fix migration * add some testing * Update frame/fast-unstake/src/benchmarking.rs Co-authored-by: Roman Useinov <roman.useinov@gmail.com> * review comments * some fixes * fix review comments * fix build * fmt * fix benchmarks * fmt * update Co-authored-by: Roman Useinov <roman.useinov@gmail.com>
This commit is contained in:
@@ -584,6 +584,7 @@ impl pallet_staking::Config for Runtime {
|
|||||||
impl pallet_fast_unstake::Config for Runtime {
|
impl pallet_fast_unstake::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type ControlOrigin = frame_system::EnsureRoot<AccountId>;
|
type ControlOrigin = frame_system::EnsureRoot<AccountId>;
|
||||||
|
type BatchSize = ConstU32<128>;
|
||||||
type Deposit = ConstU128<{ DOLLARS }>;
|
type Deposit = ConstU128<{ DOLLARS }>;
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type Staking = Staking;
|
type Staking = Staking;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ std = [
|
|||||||
runtime-benchmarks = [
|
runtime-benchmarks = [
|
||||||
"frame-benchmarking/runtime-benchmarks",
|
"frame-benchmarking/runtime-benchmarks",
|
||||||
"frame-system/runtime-benchmarks",
|
"frame-system/runtime-benchmarks",
|
||||||
"sp-staking/runtime-benchmarks"
|
"sp-staking/runtime-benchmarks",
|
||||||
|
"pallet-staking/runtime-benchmarks"
|
||||||
]
|
]
|
||||||
try-runtime = ["frame-support/try-runtime"]
|
try-runtime = ["frame-support/try-runtime"]
|
||||||
|
|||||||
@@ -36,10 +36,15 @@ const MAX_VALIDATORS: u32 = 128;
|
|||||||
|
|
||||||
type CurrencyOf<T> = <T as Config>::Currency;
|
type CurrencyOf<T> = <T as Config>::Currency;
|
||||||
|
|
||||||
fn create_unexposed_nominator<T: Config>() -> T::AccountId {
|
fn create_unexposed_nominators<T: Config>() -> Vec<T::AccountId> {
|
||||||
let account = frame_benchmarking::account::<T::AccountId>("nominator_42", 0, USER_SEED);
|
(0..T::BatchSize::get())
|
||||||
fund_and_bond_account::<T>(&account);
|
.map(|i| {
|
||||||
account
|
let account =
|
||||||
|
frame_benchmarking::account::<T::AccountId>("unexposed_nominator", i, USER_SEED);
|
||||||
|
fund_and_bond_account::<T>(&account);
|
||||||
|
account
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fund_and_bond_account<T: Config>(account: &T::AccountId) {
|
fn fund_and_bond_account<T: Config>(account: &T::AccountId) {
|
||||||
@@ -90,21 +95,27 @@ fn on_idle_full_block<T: Config>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
benchmarks! {
|
benchmarks! {
|
||||||
// on_idle, we we don't check anyone, but fully unbond and move them to another pool.
|
// on_idle, we don't check anyone, but fully unbond them.
|
||||||
on_idle_unstake {
|
on_idle_unstake {
|
||||||
ErasToCheckPerBlock::<T>::put(1);
|
ErasToCheckPerBlock::<T>::put(1);
|
||||||
let who = create_unexposed_nominator::<T>();
|
for who in create_unexposed_nominators::<T>() {
|
||||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||||
RawOrigin::Signed(who.clone()).into(),
|
RawOrigin::Signed(who.clone()).into(),
|
||||||
));
|
));
|
||||||
|
}
|
||||||
|
|
||||||
// run on_idle once. This will check era 0.
|
// run on_idle once. This will check era 0.
|
||||||
assert_eq!(Head::<T>::get(), None);
|
assert_eq!(Head::<T>::get(), None);
|
||||||
on_idle_full_block::<T>();
|
on_idle_full_block::<T>();
|
||||||
assert_eq!(
|
|
||||||
|
assert!(matches!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest { stash: who.clone(), checked: vec![0].try_into().unwrap(), deposit: T::Deposit::get() })
|
Some(UnstakeRequest {
|
||||||
);
|
checked,
|
||||||
|
stashes,
|
||||||
|
..
|
||||||
|
}) if checked.len() == 1 && stashes.len() as u32 == T::BatchSize::get()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
on_idle_full_block::<T>();
|
on_idle_full_block::<T>();
|
||||||
@@ -112,7 +123,7 @@ benchmarks! {
|
|||||||
verify {
|
verify {
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
fast_unstake_events::<T>().last(),
|
fast_unstake_events::<T>().last(),
|
||||||
Some(Event::Unstaked { .. })
|
Some(Event::BatchFinished)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,10 +140,13 @@ benchmarks! {
|
|||||||
|
|
||||||
// setup staking with v validators and u eras of data (0..=u)
|
// setup staking with v validators and u eras of data (0..=u)
|
||||||
setup_staking::<T>(v, u);
|
setup_staking::<T>(v, u);
|
||||||
let who = create_unexposed_nominator::<T>();
|
|
||||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
let stashes = create_unexposed_nominators::<T>().into_iter().map(|s| {
|
||||||
RawOrigin::Signed(who.clone()).into(),
|
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||||
));
|
RawOrigin::Signed(s.clone()).into(),
|
||||||
|
));
|
||||||
|
(s, T::Deposit::get())
|
||||||
|
}).collect::<Vec<_>>();
|
||||||
|
|
||||||
// no one is queued thus far.
|
// no one is queued thus far.
|
||||||
assert_eq!(Head::<T>::get(), None);
|
assert_eq!(Head::<T>::get(), None);
|
||||||
@@ -141,20 +155,19 @@ benchmarks! {
|
|||||||
on_idle_full_block::<T>();
|
on_idle_full_block::<T>();
|
||||||
}
|
}
|
||||||
verify {
|
verify {
|
||||||
let checked: frame_support::BoundedVec<_, _> = (1..=u).rev().collect::<Vec<EraIndex>>().try_into().unwrap();
|
let checked = (1..=u).rev().collect::<Vec<EraIndex>>();
|
||||||
assert_eq!(
|
let request = Head::<T>::get().unwrap();
|
||||||
Head::<T>::get(),
|
assert_eq!(checked, request.checked.into_inner());
|
||||||
Some(UnstakeRequest { stash: who.clone(), checked, deposit: T::Deposit::get() })
|
|
||||||
);
|
|
||||||
assert!(matches!(
|
assert!(matches!(
|
||||||
fast_unstake_events::<T>().last(),
|
fast_unstake_events::<T>().last(),
|
||||||
Some(Event::Checking { .. })
|
Some(Event::BatchChecked { .. })
|
||||||
));
|
));
|
||||||
|
assert!(stashes.iter().all(|(s, _)| request.stashes.iter().find(|(ss, _)| ss == s).is_some()));
|
||||||
}
|
}
|
||||||
|
|
||||||
register_fast_unstake {
|
register_fast_unstake {
|
||||||
ErasToCheckPerBlock::<T>::put(1);
|
ErasToCheckPerBlock::<T>::put(1);
|
||||||
let who = create_unexposed_nominator::<T>();
|
let who = create_unexposed_nominators::<T>().get(0).cloned().unwrap();
|
||||||
whitelist_account!(who);
|
whitelist_account!(who);
|
||||||
assert_eq!(Queue::<T>::count(), 0);
|
assert_eq!(Queue::<T>::count(), 0);
|
||||||
|
|
||||||
@@ -166,7 +179,7 @@ benchmarks! {
|
|||||||
|
|
||||||
deregister {
|
deregister {
|
||||||
ErasToCheckPerBlock::<T>::put(1);
|
ErasToCheckPerBlock::<T>::put(1);
|
||||||
let who = create_unexposed_nominator::<T>();
|
let who = create_unexposed_nominators::<T>().get(0).cloned().unwrap();
|
||||||
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
assert_ok!(FastUnstake::<T>::register_fast_unstake(
|
||||||
RawOrigin::Signed(who.clone()).into(),
|
RawOrigin::Signed(who.clone()).into(),
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ mod tests;
|
|||||||
// NOTE: enable benchmarking in tests as well.
|
// NOTE: enable benchmarking in tests as well.
|
||||||
#[cfg(feature = "runtime-benchmarks")]
|
#[cfg(feature = "runtime-benchmarks")]
|
||||||
mod benchmarking;
|
mod benchmarking;
|
||||||
|
pub mod migrations;
|
||||||
pub mod types;
|
pub mod types;
|
||||||
pub mod weights;
|
pub mod weights;
|
||||||
|
|
||||||
@@ -82,7 +83,7 @@ pub mod pallet {
|
|||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
pallet_prelude::*,
|
pallet_prelude::*,
|
||||||
traits::{Defensive, ReservableCurrency},
|
traits::{Defensive, ReservableCurrency, StorageVersion},
|
||||||
};
|
};
|
||||||
use frame_system::pallet_prelude::*;
|
use frame_system::pallet_prelude::*;
|
||||||
use sp_runtime::{
|
use sp_runtime::{
|
||||||
@@ -103,7 +104,10 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||||
|
|
||||||
#[pallet::pallet]
|
#[pallet::pallet]
|
||||||
|
#[pallet::storage_version(STORAGE_VERSION)]
|
||||||
pub struct Pallet<T>(_);
|
pub struct Pallet<T>(_);
|
||||||
|
|
||||||
#[pallet::config]
|
#[pallet::config]
|
||||||
@@ -123,6 +127,11 @@ pub mod pallet {
|
|||||||
/// The origin that can control this pallet.
|
/// The origin that can control this pallet.
|
||||||
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
|
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
|
||||||
|
|
||||||
|
/// Batch size.
|
||||||
|
///
|
||||||
|
/// This many stashes are processed in each unstake request.
|
||||||
|
type BatchSize: Get<u32>;
|
||||||
|
|
||||||
/// The access to staking functionality.
|
/// The access to staking functionality.
|
||||||
type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
|
type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
|
||||||
|
|
||||||
@@ -132,8 +141,7 @@ pub mod pallet {
|
|||||||
|
|
||||||
/// The current "head of the queue" being unstaked.
|
/// The current "head of the queue" being unstaked.
|
||||||
#[pallet::storage]
|
#[pallet::storage]
|
||||||
pub type Head<T: Config> =
|
pub type Head<T: Config> = StorageValue<_, UnstakeRequest<T>, OptionQuery>;
|
||||||
StorageValue<_, UnstakeRequest<T::AccountId, MaxChecking<T>, BalanceOf<T>>, OptionQuery>;
|
|
||||||
|
|
||||||
/// The map of all accounts wishing to be unstaked.
|
/// The map of all accounts wishing to be unstaked.
|
||||||
///
|
///
|
||||||
@@ -158,13 +166,18 @@ pub mod pallet {
|
|||||||
Unstaked { stash: T::AccountId, result: DispatchResult },
|
Unstaked { stash: T::AccountId, result: DispatchResult },
|
||||||
/// A staker was slashed for requesting fast-unstake whilst being exposed.
|
/// A staker was slashed for requesting fast-unstake whilst being exposed.
|
||||||
Slashed { stash: T::AccountId, amount: BalanceOf<T> },
|
Slashed { stash: T::AccountId, amount: BalanceOf<T> },
|
||||||
/// A staker was partially checked for the given eras, but the process did not finish.
|
|
||||||
Checking { stash: T::AccountId, eras: Vec<EraIndex> },
|
|
||||||
/// Some internal error happened while migrating stash. They are removed as head as a
|
/// Some internal error happened while migrating stash. They are removed as head as a
|
||||||
/// consequence.
|
/// consequence.
|
||||||
Errored { stash: T::AccountId },
|
Errored { stash: T::AccountId },
|
||||||
/// An internal error happened. Operations will be paused now.
|
/// An internal error happened. Operations will be paused now.
|
||||||
InternalError,
|
InternalError,
|
||||||
|
/// A batch was partially checked for the given eras, but the process did not finish.
|
||||||
|
BatchChecked { eras: Vec<EraIndex> },
|
||||||
|
/// A batch was terminated.
|
||||||
|
///
|
||||||
|
/// This is always follows by a number of `Unstaked` or `Slashed` events, marking the end
|
||||||
|
/// of the batch. A new batch will be created upon next block.
|
||||||
|
BatchFinished,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[pallet::error]
|
#[pallet::error]
|
||||||
@@ -225,12 +238,7 @@ pub mod pallet {
|
|||||||
let stash_account =
|
let stash_account =
|
||||||
T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::<T>::NotController)?;
|
T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::<T>::NotController)?;
|
||||||
ensure!(!Queue::<T>::contains_key(&stash_account), Error::<T>::AlreadyQueued);
|
ensure!(!Queue::<T>::contains_key(&stash_account), Error::<T>::AlreadyQueued);
|
||||||
ensure!(
|
ensure!(!Self::is_head(&stash_account), Error::<T>::AlreadyHead);
|
||||||
Head::<T>::get()
|
|
||||||
.map_or(true, |UnstakeRequest { stash, .. }| stash_account != stash),
|
|
||||||
Error::<T>::AlreadyHead
|
|
||||||
);
|
|
||||||
|
|
||||||
ensure!(!T::Staking::is_unbonding(&stash_account)?, Error::<T>::NotFullyBonded);
|
ensure!(!T::Staking::is_unbonding(&stash_account)?, Error::<T>::NotFullyBonded);
|
||||||
|
|
||||||
// chill and fully unstake.
|
// chill and fully unstake.
|
||||||
@@ -260,19 +268,13 @@ pub mod pallet {
|
|||||||
let stash_account =
|
let stash_account =
|
||||||
T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::<T>::NotController)?;
|
T::Staking::stash_by_ctrl(&ctrl).map_err(|_| Error::<T>::NotController)?;
|
||||||
ensure!(Queue::<T>::contains_key(&stash_account), Error::<T>::NotQueued);
|
ensure!(Queue::<T>::contains_key(&stash_account), Error::<T>::NotQueued);
|
||||||
ensure!(
|
ensure!(!Self::is_head(&stash_account), Error::<T>::AlreadyHead);
|
||||||
Head::<T>::get()
|
|
||||||
.map_or(true, |UnstakeRequest { stash, .. }| stash_account != stash),
|
|
||||||
Error::<T>::AlreadyHead
|
|
||||||
);
|
|
||||||
let deposit = Queue::<T>::take(stash_account.clone());
|
let deposit = Queue::<T>::take(stash_account.clone());
|
||||||
|
|
||||||
if let Some(deposit) = deposit.defensive() {
|
if let Some(deposit) = deposit.defensive() {
|
||||||
let remaining = T::Currency::unreserve(&stash_account, deposit);
|
let remaining = T::Currency::unreserve(&stash_account, deposit);
|
||||||
if !remaining.is_zero() {
|
if !remaining.is_zero() {
|
||||||
frame_support::defensive!("`not enough balance to unreserve`");
|
Self::halt("not enough balance to unreserve");
|
||||||
ErasToCheckPerBlock::<T>::put(0);
|
|
||||||
Self::deposit_event(Event::<T>::InternalError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -291,6 +293,20 @@ pub mod pallet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Config> Pallet<T> {
|
impl<T: Config> Pallet<T> {
|
||||||
|
/// Returns `true` if `staker` is anywhere to be found in the `head`.
|
||||||
|
pub(crate) fn is_head(staker: &T::AccountId) -> bool {
|
||||||
|
Head::<T>::get().map_or(false, |UnstakeRequest { stashes, .. }| {
|
||||||
|
stashes.iter().any(|(stash, _)| stash == staker)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Halt the operations of this pallet.
|
||||||
|
pub(crate) fn halt(reason: &'static str) {
|
||||||
|
frame_support::defensive!(reason);
|
||||||
|
ErasToCheckPerBlock::<T>::put(0);
|
||||||
|
Self::deposit_event(Event::<T>::InternalError)
|
||||||
|
}
|
||||||
|
|
||||||
/// process up to `remaining_weight`.
|
/// process up to `remaining_weight`.
|
||||||
///
|
///
|
||||||
/// Returns the actual weight consumed.
|
/// Returns the actual weight consumed.
|
||||||
@@ -336,28 +352,30 @@ pub mod pallet {
|
|||||||
return T::DbWeight::get().reads(2)
|
return T::DbWeight::get().reads(2)
|
||||||
}
|
}
|
||||||
|
|
||||||
let UnstakeRequest { stash, mut checked, deposit } =
|
let UnstakeRequest { stashes, mut checked } = match Head::<T>::take().or_else(|| {
|
||||||
match Head::<T>::take().or_else(|| {
|
// NOTE: there is no order guarantees in `Queue`.
|
||||||
// NOTE: there is no order guarantees in `Queue`.
|
let stashes: BoundedVec<_, T::BatchSize> = Queue::<T>::drain()
|
||||||
Queue::<T>::drain()
|
.take(T::BatchSize::get() as usize)
|
||||||
.map(|(stash, deposit)| UnstakeRequest {
|
.collect::<Vec<_>>()
|
||||||
stash,
|
.try_into()
|
||||||
deposit,
|
.expect("take ensures bound is met; qed");
|
||||||
checked: Default::default(),
|
if stashes.is_empty() {
|
||||||
})
|
None
|
||||||
.next()
|
} else {
|
||||||
}) {
|
Some(UnstakeRequest { stashes, checked: Default::default() })
|
||||||
None => {
|
}
|
||||||
// There's no `Head` and nothing in the `Queue`, nothing to do here.
|
}) {
|
||||||
return T::DbWeight::get().reads(4)
|
None => {
|
||||||
},
|
// There's no `Head` and nothing in the `Queue`, nothing to do here.
|
||||||
Some(head) => head,
|
return T::DbWeight::get().reads(4)
|
||||||
};
|
},
|
||||||
|
Some(head) => head,
|
||||||
|
};
|
||||||
|
|
||||||
log!(
|
log!(
|
||||||
debug,
|
debug,
|
||||||
"checking {:?}, eras_to_check_per_block = {:?}, remaining_weight = {:?}",
|
"checking {:?} stashes, eras_to_check_per_block = {:?}, remaining_weight = {:?}",
|
||||||
stash,
|
stashes.len(),
|
||||||
eras_to_check_per_block,
|
eras_to_check_per_block,
|
||||||
remaining_weight
|
remaining_weight
|
||||||
);
|
);
|
||||||
@@ -365,9 +383,11 @@ pub mod pallet {
|
|||||||
// the range that we're allowed to check in this round.
|
// the range that we're allowed to check in this round.
|
||||||
let current_era = T::Staking::current_era();
|
let current_era = T::Staking::current_era();
|
||||||
let bonding_duration = T::Staking::bonding_duration();
|
let bonding_duration = T::Staking::bonding_duration();
|
||||||
|
|
||||||
// prune all the old eras that we don't care about. This will help us keep the bound
|
// prune all the old eras that we don't care about. This will help us keep the bound
|
||||||
// of `checked`.
|
// of `checked`.
|
||||||
checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration));
|
checked.retain(|e| *e >= current_era.saturating_sub(bonding_duration));
|
||||||
|
|
||||||
let unchecked_eras_to_check = {
|
let unchecked_eras_to_check = {
|
||||||
// get the last available `bonding_duration` eras up to current era in reverse
|
// get the last available `bonding_duration` eras up to current era in reverse
|
||||||
// order.
|
// order.
|
||||||
@@ -397,66 +417,75 @@ pub mod pallet {
|
|||||||
unchecked_eras_to_check
|
unchecked_eras_to_check
|
||||||
);
|
);
|
||||||
|
|
||||||
if unchecked_eras_to_check.is_empty() {
|
let unstake_stash = |stash: T::AccountId, deposit| {
|
||||||
let result = T::Staking::force_unstake(stash.clone());
|
let result = T::Staking::force_unstake(stash.clone());
|
||||||
|
|
||||||
let remaining = T::Currency::unreserve(&stash, deposit);
|
let remaining = T::Currency::unreserve(&stash, deposit);
|
||||||
if !remaining.is_zero() {
|
if !remaining.is_zero() {
|
||||||
frame_support::defensive!("`not enough balance to unreserve`");
|
Self::halt("not enough balance to unreserve");
|
||||||
ErasToCheckPerBlock::<T>::put(0);
|
|
||||||
Self::deposit_event(Event::<T>::InternalError)
|
|
||||||
} else {
|
} else {
|
||||||
log!(info, "unstaked {:?}, outcome: {:?}", stash, result);
|
log!(info, "unstaked {:?}, outcome: {:?}", stash, result);
|
||||||
Self::deposit_event(Event::<T>::Unstaked { stash, result });
|
Self::deposit_event(Event::<T>::Unstaked { stash, result });
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
<T as Config>::WeightInfo::on_idle_unstake()
|
let check_stash = |stash, deposit, eras_checked: &mut u32| {
|
||||||
} else {
|
|
||||||
// eras remaining to be checked.
|
|
||||||
let mut eras_checked = 0u32;
|
|
||||||
let is_exposed = unchecked_eras_to_check.iter().any(|e| {
|
let is_exposed = unchecked_eras_to_check.iter().any(|e| {
|
||||||
eras_checked.saturating_inc();
|
eras_checked.saturating_inc();
|
||||||
T::Staking::is_exposed_in_era(&stash, e)
|
T::Staking::is_exposed_in_era(&stash, e)
|
||||||
});
|
});
|
||||||
|
|
||||||
log!(
|
|
||||||
debug,
|
|
||||||
"checked {:?} eras, exposed? {}, (v: {:?}, u: {:?})",
|
|
||||||
eras_checked,
|
|
||||||
is_exposed,
|
|
||||||
validator_count,
|
|
||||||
unchecked_eras_to_check.len()
|
|
||||||
);
|
|
||||||
|
|
||||||
// NOTE: you can be extremely unlucky and get slashed here: You are not exposed in
|
|
||||||
// the last 28 eras, have registered yourself to be unstaked, midway being checked,
|
|
||||||
// you are exposed.
|
|
||||||
if is_exposed {
|
if is_exposed {
|
||||||
T::Currency::slash_reserved(&stash, deposit);
|
T::Currency::slash_reserved(&stash, deposit);
|
||||||
log!(info, "slashed {:?} by {:?}", stash, deposit);
|
log!(info, "slashed {:?} by {:?}", stash, deposit);
|
||||||
Self::deposit_event(Event::<T>::Slashed { stash, amount: deposit });
|
Self::deposit_event(Event::<T>::Slashed { stash, amount: deposit });
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
// Not exposed in these eras.
|
true
|
||||||
match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) {
|
}
|
||||||
Ok(_) => {
|
};
|
||||||
Head::<T>::put(UnstakeRequest {
|
|
||||||
stash: stash.clone(),
|
if unchecked_eras_to_check.is_empty() {
|
||||||
checked,
|
// `stash` is not exposed in any era now -- we can let go of them now.
|
||||||
deposit,
|
stashes.into_iter().for_each(|(stash, deposit)| unstake_stash(stash, deposit));
|
||||||
});
|
Self::deposit_event(Event::<T>::BatchFinished);
|
||||||
Self::deposit_event(Event::<T>::Checking {
|
<T as Config>::WeightInfo::on_idle_unstake()
|
||||||
stash,
|
} else {
|
||||||
|
// eras checked so far.
|
||||||
|
let mut eras_checked = 0u32;
|
||||||
|
|
||||||
|
let pre_length = stashes.len();
|
||||||
|
let stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize> = stashes
|
||||||
|
.into_iter()
|
||||||
|
.filter(|(stash, deposit)| {
|
||||||
|
check_stash(stash.clone(), *deposit, &mut eras_checked)
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.try_into()
|
||||||
|
.expect("filter can only lessen the length; still in bound; qed");
|
||||||
|
let post_length = stashes.len();
|
||||||
|
|
||||||
|
log!(
|
||||||
|
debug,
|
||||||
|
"checked {:?} eras, pre stashes: {:?}, post: {:?}",
|
||||||
|
eras_checked,
|
||||||
|
pre_length,
|
||||||
|
post_length,
|
||||||
|
);
|
||||||
|
|
||||||
|
match checked.try_extend(unchecked_eras_to_check.clone().into_iter()) {
|
||||||
|
Ok(_) =>
|
||||||
|
if stashes.is_empty() {
|
||||||
|
Self::deposit_event(Event::<T>::BatchFinished);
|
||||||
|
} else {
|
||||||
|
Head::<T>::put(UnstakeRequest { stashes, checked });
|
||||||
|
Self::deposit_event(Event::<T>::BatchChecked {
|
||||||
eras: unchecked_eras_to_check,
|
eras: unchecked_eras_to_check,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
// don't put the head back in -- there is an internal error in the
|
// don't put the head back in -- there is an internal error in the pallet.
|
||||||
// pallet.
|
Self::halt("checked is pruned via retain above")
|
||||||
frame_support::defensive!("`checked is pruned via retain above`");
|
},
|
||||||
ErasToCheckPerBlock::<T>::put(0);
|
|
||||||
Self::deposit_event(Event::<T>::InternalError);
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
<T as Config>::WeightInfo::on_idle_check(validator_count * eras_checked)
|
<T as Config>::WeightInfo::on_idle_check(validator_count * eras_checked)
|
||||||
|
|||||||
@@ -0,0 +1,77 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
use crate::{types::BalanceOf, *};
|
||||||
|
use frame_support::{
|
||||||
|
storage::unhashed,
|
||||||
|
traits::{Defensive, Get, GetStorageVersion, OnRuntimeUpgrade},
|
||||||
|
weights::Weight,
|
||||||
|
};
|
||||||
|
use sp_staking::EraIndex;
|
||||||
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
|
pub struct MigrateToV1<T>(sp_std::marker::PhantomData<T>);
|
||||||
|
impl<T: Config> OnRuntimeUpgrade for MigrateToV1<T> {
|
||||||
|
fn on_runtime_upgrade() -> Weight {
|
||||||
|
let current = Pallet::<T>::current_storage_version();
|
||||||
|
let onchain = Pallet::<T>::on_chain_storage_version();
|
||||||
|
|
||||||
|
log!(
|
||||||
|
info,
|
||||||
|
"Running migration with current storage version {:?} / onchain {:?}",
|
||||||
|
current,
|
||||||
|
onchain
|
||||||
|
);
|
||||||
|
|
||||||
|
if current == 1 && onchain == 0 {
|
||||||
|
// if a head exists, then we put them back into the queue.
|
||||||
|
if Head::<T>::exists() {
|
||||||
|
if let Some((stash, _, deposit)) =
|
||||||
|
unhashed::take::<(T::AccountId, Vec<EraIndex>, BalanceOf<T>)>(
|
||||||
|
&Head::<T>::hashed_key(),
|
||||||
|
)
|
||||||
|
.defensive()
|
||||||
|
{
|
||||||
|
Queue::<T>::insert(stash, deposit);
|
||||||
|
current.put::<Pallet<T>>();
|
||||||
|
} else {
|
||||||
|
// not much we can do here -- head is already deleted.
|
||||||
|
}
|
||||||
|
T::DbWeight::get().reads_writes(2, 3)
|
||||||
|
} else {
|
||||||
|
T::DbWeight::get().reads(2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log!(info, "Migration did not execute. This probably should be removed");
|
||||||
|
T::DbWeight::get().reads(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn pre_upgrade() -> Result<Vec<u8>, &'static str> {
|
||||||
|
assert_eq!(Pallet::<T>::on_chain_storage_version(), 0);
|
||||||
|
Ok(Default::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "try-runtime")]
|
||||||
|
fn post_upgrade(_: Vec<u8>) -> Result<(), &'static str> {
|
||||||
|
assert_eq!(Pallet::<T>::on_chain_storage_version(), 1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,8 +16,12 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
use crate::{self as fast_unstake};
|
use crate::{self as fast_unstake};
|
||||||
|
use frame_benchmarking::frame_support::assert_ok;
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
pallet_prelude::*, parameter_types, traits::ConstU64, weights::constants::WEIGHT_PER_SECOND,
|
pallet_prelude::*,
|
||||||
|
parameter_types,
|
||||||
|
traits::{ConstU64, Currency},
|
||||||
|
weights::constants::WEIGHT_PER_SECOND,
|
||||||
};
|
};
|
||||||
use sp_runtime::traits::{Convert, IdentityLookup};
|
use sp_runtime::traits::{Convert, IdentityLookup};
|
||||||
|
|
||||||
@@ -168,15 +172,17 @@ impl Convert<sp_core::U256, Balance> for U256ToBalance {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parameter_types! {
|
parameter_types! {
|
||||||
pub static DepositAmount: u128 = 7;
|
pub static Deposit: u128 = 7;
|
||||||
|
pub static BatchSize: u32 = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fast_unstake::Config for Runtime {
|
impl fast_unstake::Config for Runtime {
|
||||||
type RuntimeEvent = RuntimeEvent;
|
type RuntimeEvent = RuntimeEvent;
|
||||||
type Deposit = DepositAmount;
|
type Deposit = Deposit;
|
||||||
type Currency = Balances;
|
type Currency = Balances;
|
||||||
type Staking = Staking;
|
type Staking = Staking;
|
||||||
type ControlOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
type ControlOrigin = frame_system::EnsureRoot<Self::AccountId>;
|
||||||
|
type BatchSize = BatchSize;
|
||||||
type WeightInfo = ();
|
type WeightInfo = ();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -212,13 +218,13 @@ pub(crate) fn fast_unstake_events_since_last_call() -> Vec<super::Event<Runtime>
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct ExtBuilder {
|
pub struct ExtBuilder {
|
||||||
exposed_nominators: Vec<(AccountId, AccountId, Balance)>,
|
unexposed: Vec<(AccountId, AccountId, Balance)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for ExtBuilder {
|
impl Default for ExtBuilder {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
exposed_nominators: vec![
|
unexposed: vec![
|
||||||
(1, 2, 7 + 100),
|
(1, 2, 7 + 100),
|
||||||
(3, 4, 7 + 100),
|
(3, 4, 7 + 100),
|
||||||
(5, 6, 7 + 100),
|
(5, 6, 7 + 100),
|
||||||
@@ -255,6 +261,11 @@ impl ExtBuilder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn batch(self, size: u32) -> Self {
|
||||||
|
BatchSize::set(size);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn build(self) -> sp_io::TestExternalities {
|
pub(crate) fn build(self) -> sp_io::TestExternalities {
|
||||||
sp_tracing::try_init_simple();
|
sp_tracing::try_init_simple();
|
||||||
let mut storage =
|
let mut storage =
|
||||||
@@ -266,12 +277,12 @@ impl ExtBuilder {
|
|||||||
|
|
||||||
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
let _ = pallet_balances::GenesisConfig::<Runtime> {
|
||||||
balances: self
|
balances: self
|
||||||
.exposed_nominators
|
.unexposed
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(stash, _, balance)| (stash, balance * 2))
|
.map(|(stash, _, balance)| (stash, balance * 2))
|
||||||
.chain(
|
.chain(
|
||||||
self.exposed_nominators
|
self.unexposed
|
||||||
.clone()
|
.clone()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(_, ctrl, balance)| (ctrl, balance * 2)),
|
.map(|(_, ctrl, balance)| (ctrl, balance * 2)),
|
||||||
@@ -284,7 +295,7 @@ impl ExtBuilder {
|
|||||||
|
|
||||||
let _ = pallet_staking::GenesisConfig::<Runtime> {
|
let _ = pallet_staking::GenesisConfig::<Runtime> {
|
||||||
stakers: self
|
stakers: self
|
||||||
.exposed_nominators
|
.unexposed
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42])))
|
.map(|(x, y, z)| (x, y, z, pallet_staking::StakerStatus::Nominator(vec![42])))
|
||||||
.chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator)))
|
.chain(validators_range.map(|x| (x, x, 100, StakerStatus::Validator)))
|
||||||
@@ -307,6 +318,7 @@ impl ExtBuilder {
|
|||||||
// because we read this value as a measure of how many validators we have.
|
// because we read this value as a measure of how many validators we have.
|
||||||
pallet_staking::ValidatorCount::<Runtime>::put(VALIDATORS_PER_ERA as u32);
|
pallet_staking::ValidatorCount::<Runtime>::put(VALIDATORS_PER_ERA as u32);
|
||||||
});
|
});
|
||||||
|
|
||||||
ext
|
ext
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -347,3 +359,20 @@ pub fn assert_unstaked(stash: &AccountId) {
|
|||||||
assert!(!pallet_staking::Validators::<T>::contains_key(stash));
|
assert!(!pallet_staking::Validators::<T>::contains_key(stash));
|
||||||
assert!(!pallet_staking::Nominators::<T>::contains_key(stash));
|
assert!(!pallet_staking::Nominators::<T>::contains_key(stash));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_exposed_nominator(exposed: AccountId, era: u32) {
|
||||||
|
// create an exposed nominator in era 1
|
||||||
|
pallet_staking::ErasStakers::<T>::mutate(era, VALIDATORS_PER_ERA, |expo| {
|
||||||
|
expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance });
|
||||||
|
});
|
||||||
|
Balances::make_free_balance_be(&exposed, 100);
|
||||||
|
assert_ok!(Staking::bond(
|
||||||
|
RuntimeOrigin::signed(exposed),
|
||||||
|
exposed,
|
||||||
|
10,
|
||||||
|
pallet_staking::RewardDestination::Staked
|
||||||
|
));
|
||||||
|
assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed]));
|
||||||
|
// register the exposed one.
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed)));
|
||||||
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::{mock::*, types::*, weights::WeightInfo, Event};
|
use crate::{mock::*, types::*, weights::WeightInfo, Event};
|
||||||
use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency};
|
use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency};
|
||||||
use pallet_staking::{CurrentEra, IndividualExposure, RewardDestination};
|
use pallet_staking::{CurrentEra, RewardDestination};
|
||||||
|
|
||||||
use sp_runtime::traits::BadOrigin;
|
use sp_runtime::traits::BadOrigin;
|
||||||
use sp_staking::StakingInterface;
|
use sp_staking::StakingInterface;
|
||||||
@@ -107,9 +107,8 @@ fn cannot_register_if_head() {
|
|||||||
ErasToCheckPerBlock::<T>::put(1);
|
ErasToCheckPerBlock::<T>::put(1);
|
||||||
// Insert some Head item for stash
|
// Insert some Head item for stash
|
||||||
Head::<T>::put(UnstakeRequest {
|
Head::<T>::put(UnstakeRequest {
|
||||||
stash: 1,
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
checked: bounded_vec![],
|
checked: bounded_vec![],
|
||||||
deposit: DepositAmount::get(),
|
|
||||||
});
|
});
|
||||||
// Controller attempts to regsiter
|
// Controller attempts to regsiter
|
||||||
assert_noop!(
|
assert_noop!(
|
||||||
@@ -142,7 +141,7 @@ fn deregister_works() {
|
|||||||
|
|
||||||
// Controller account registers for fast unstake.
|
// Controller account registers for fast unstake.
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(<T as Config>::Currency::reserved_balance(&1), DepositAmount::get());
|
assert_eq!(<T as Config>::Currency::reserved_balance(&1), Deposit::get());
|
||||||
|
|
||||||
// Controller then changes mind and deregisters.
|
// Controller then changes mind and deregisters.
|
||||||
assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::deregister(RuntimeOrigin::signed(2)));
|
||||||
@@ -191,9 +190,8 @@ fn cannot_deregister_already_head() {
|
|||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
// Insert some Head item for stash.
|
// Insert some Head item for stash.
|
||||||
Head::<T>::put(UnstakeRequest {
|
Head::<T>::put(UnstakeRequest {
|
||||||
stash: 1,
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
checked: bounded_vec![],
|
checked: bounded_vec![],
|
||||||
deposit: DepositAmount::get(),
|
|
||||||
});
|
});
|
||||||
// Controller attempts to deregister
|
// Controller attempts to deregister
|
||||||
assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::<T>::AlreadyHead);
|
assert_noop!(FastUnstake::deregister(RuntimeOrigin::signed(2)), Error::<T>::AlreadyHead);
|
||||||
@@ -227,14 +225,14 @@ mod on_idle {
|
|||||||
|
|
||||||
// set up Queue item
|
// set up Queue item
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
// call on_idle with no remaining weight
|
// call on_idle with no remaining weight
|
||||||
FastUnstake::on_idle(System::block_number(), Weight::from_ref_time(0));
|
FastUnstake::on_idle(System::block_number(), Weight::from_ref_time(0));
|
||||||
|
|
||||||
// assert nothing changed in Queue and Head
|
// assert nothing changed in Queue and Head
|
||||||
assert_eq!(Head::<T>::get(), None);
|
assert_eq!(Head::<T>::get(), None);
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +245,7 @@ mod on_idle {
|
|||||||
|
|
||||||
// given
|
// given
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
assert_eq!(Queue::<T>::count(), 1);
|
assert_eq!(Queue::<T>::count(), 1);
|
||||||
assert_eq!(Head::<T>::get(), None);
|
assert_eq!(Head::<T>::get(), None);
|
||||||
@@ -262,13 +260,12 @@ mod on_idle {
|
|||||||
// then
|
// then
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![Event::Checking { stash: 1, eras: vec![3] }]
|
vec![Event::BatchChecked { eras: vec![3] }]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3]
|
checked: bounded_vec![3]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -282,13 +279,12 @@ mod on_idle {
|
|||||||
// then:
|
// then:
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![Event::Checking { stash: 1, eras: bounded_vec![2] }]
|
vec![Event::BatchChecked { eras: bounded_vec![2] }]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -308,13 +304,12 @@ mod on_idle {
|
|||||||
// then:
|
// then:
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![Event::Checking { stash: 1, eras: vec![1, 0] }]
|
vec![Event::BatchChecked { eras: vec![1, 0] }]
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -329,8 +324,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -348,7 +342,7 @@ mod on_idle {
|
|||||||
// then we finish the unbonding:
|
// then we finish the unbonding:
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![Event::Unstaked { stash: 1, result: Ok(()) }]
|
vec![Event::Unstaked { stash: 1, result: Ok(()) }, Event::BatchFinished],
|
||||||
);
|
);
|
||||||
assert_eq!(Head::<T>::get(), None,);
|
assert_eq!(Head::<T>::get(), None,);
|
||||||
|
|
||||||
@@ -371,7 +365,7 @@ mod on_idle {
|
|||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8)));
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(10)));
|
||||||
|
|
||||||
assert_eq!(<T as Config>::Currency::reserved_balance(&1), DepositAmount::get());
|
assert_eq!(<T as Config>::Currency::reserved_balance(&1), Deposit::get());
|
||||||
|
|
||||||
assert_eq!(Queue::<T>::count(), 5);
|
assert_eq!(Queue::<T>::count(), 5);
|
||||||
assert_eq!(Head::<T>::get(), None);
|
assert_eq!(Head::<T>::get(), None);
|
||||||
@@ -383,8 +377,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -404,8 +397,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(5, Deposit::get())],
|
||||||
stash: 5,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@@ -416,9 +408,10 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] },
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) },
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
Event::Checking { stash: 5, eras: vec![3, 2, 1, 0] }
|
Event::BatchFinished,
|
||||||
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -432,9 +425,9 @@ mod on_idle {
|
|||||||
|
|
||||||
// register multi accounts for fast unstake
|
// register multi accounts for fast unstake
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
||||||
assert_eq!(Queue::<T>::get(3), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(3), Some(Deposit::get()));
|
||||||
|
|
||||||
// assert 2 queue items are in Queue & None in Head to start with
|
// assert 2 queue items are in Queue & None in Head to start with
|
||||||
assert_eq!(Queue::<T>::count(), 2);
|
assert_eq!(Queue::<T>::count(), 2);
|
||||||
@@ -463,10 +456,12 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] },
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) },
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
Event::Checking { stash: 3, eras: vec![3, 2, 1, 0] },
|
Event::BatchFinished,
|
||||||
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 3, result: Ok(()) },
|
Event::Unstaked { stash: 3, result: Ok(()) },
|
||||||
|
Event::BatchFinished,
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -483,7 +478,7 @@ mod on_idle {
|
|||||||
|
|
||||||
// register for fast unstake
|
// register for fast unstake
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
// process on idle
|
// process on idle
|
||||||
next_block(true);
|
next_block(true);
|
||||||
@@ -495,8 +490,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -507,8 +501,9 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] },
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) }
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_unstaked(&1);
|
assert_unstaked(&1);
|
||||||
@@ -525,7 +520,7 @@ mod on_idle {
|
|||||||
|
|
||||||
// register for fast unstake
|
// register for fast unstake
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
// process on idle
|
// process on idle
|
||||||
next_block(true);
|
next_block(true);
|
||||||
@@ -537,8 +532,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -549,8 +543,9 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3, 2, 1, 0] },
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) }
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_unstaked(&1);
|
assert_unstaked(&1);
|
||||||
@@ -566,7 +561,7 @@ mod on_idle {
|
|||||||
|
|
||||||
// register for fast unstake
|
// register for fast unstake
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
// process on idle
|
// process on idle
|
||||||
next_block(true);
|
next_block(true);
|
||||||
@@ -578,8 +573,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3]
|
checked: bounded_vec![3]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -589,8 +583,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -600,8 +593,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1]
|
checked: bounded_vec![3, 2, 1]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -611,8 +603,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -624,11 +615,12 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3] },
|
Event::BatchChecked { eras: vec![3] },
|
||||||
Event::Checking { stash: 1, eras: vec![2] },
|
Event::BatchChecked { eras: vec![2] },
|
||||||
Event::Checking { stash: 1, eras: vec![1] },
|
Event::BatchChecked { eras: vec![1] },
|
||||||
Event::Checking { stash: 1, eras: vec![0] },
|
Event::BatchChecked { eras: vec![0] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) }
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_unstaked(&1);
|
assert_unstaked(&1);
|
||||||
@@ -647,14 +639,13 @@ mod on_idle {
|
|||||||
|
|
||||||
// register for fast unstake
|
// register for fast unstake
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
assert_eq!(Queue::<T>::get(1), Some(DepositAmount::get()));
|
assert_eq!(Queue::<T>::get(1), Some(Deposit::get()));
|
||||||
|
|
||||||
next_block(true);
|
next_block(true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3]
|
checked: bounded_vec![3]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -663,8 +654,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -673,8 +663,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1]
|
checked: bounded_vec![3, 2, 1]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -683,8 +672,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -698,10 +686,9 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
stash: 1,
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
// note era 0 is pruned to keep the vector length sane.
|
// note era 0 is pruned to keep the vector length sane.
|
||||||
checked: bounded_vec![3, 2, 1, 4],
|
checked: bounded_vec![3, 2, 1, 4],
|
||||||
deposit: DepositAmount::get(),
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -711,12 +698,13 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3] },
|
Event::BatchChecked { eras: vec![3] },
|
||||||
Event::Checking { stash: 1, eras: vec![2] },
|
Event::BatchChecked { eras: vec![2] },
|
||||||
Event::Checking { stash: 1, eras: vec![1] },
|
Event::BatchChecked { eras: vec![1] },
|
||||||
Event::Checking { stash: 1, eras: vec![0] },
|
Event::BatchChecked { eras: vec![0] },
|
||||||
Event::Checking { stash: 1, eras: vec![4] },
|
Event::BatchChecked { eras: vec![4] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) }
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
assert_unstaked(&1);
|
assert_unstaked(&1);
|
||||||
@@ -738,8 +726,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3]
|
checked: bounded_vec![3]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -748,8 +735,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -762,8 +748,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -772,8 +757,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -788,8 +772,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 4]
|
checked: bounded_vec![3, 2, 4]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -799,8 +782,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(1, Deposit::get())],
|
||||||
stash: 1,
|
|
||||||
checked: bounded_vec![3, 2, 4, 1]
|
checked: bounded_vec![3, 2, 4, 1]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -812,11 +794,12 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 1, eras: vec![3] },
|
Event::BatchChecked { eras: vec![3] },
|
||||||
Event::Checking { stash: 1, eras: vec![2] },
|
Event::BatchChecked { eras: vec![2] },
|
||||||
Event::Checking { stash: 1, eras: vec![4] },
|
Event::BatchChecked { eras: vec![4] },
|
||||||
Event::Checking { stash: 1, eras: vec![1] },
|
Event::BatchChecked { eras: vec![1] },
|
||||||
Event::Unstaked { stash: 1, result: Ok(()) }
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -831,30 +814,15 @@ mod on_idle {
|
|||||||
CurrentEra::<T>::put(BondingDuration::get());
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
// create an exposed nominator in era 1
|
// create an exposed nominator in era 1
|
||||||
let exposed = 666 as AccountId;
|
let exposed = 666;
|
||||||
pallet_staking::ErasStakers::<T>::mutate(1, VALIDATORS_PER_ERA, |expo| {
|
create_exposed_nominator(exposed, 1);
|
||||||
expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance });
|
|
||||||
});
|
|
||||||
Balances::make_free_balance_be(&exposed, 100);
|
|
||||||
assert_ok!(Staking::bond(
|
|
||||||
RuntimeOrigin::signed(exposed),
|
|
||||||
exposed,
|
|
||||||
10,
|
|
||||||
RewardDestination::Staked
|
|
||||||
));
|
|
||||||
assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed]));
|
|
||||||
|
|
||||||
Balances::make_free_balance_be(&exposed, 100_000);
|
|
||||||
// register the exposed one.
|
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed)));
|
|
||||||
|
|
||||||
// a few blocks later, we realize they are slashed
|
// a few blocks later, we realize they are slashed
|
||||||
next_block(true);
|
next_block(true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(exposed, Deposit::get())],
|
||||||
stash: exposed,
|
|
||||||
checked: bounded_vec![3]
|
checked: bounded_vec![3]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -862,8 +830,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(exposed, Deposit::get())],
|
||||||
stash: exposed,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -873,9 +840,10 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: exposed, eras: vec![3] },
|
Event::BatchChecked { eras: vec![3] },
|
||||||
Event::Checking { stash: exposed, eras: vec![2] },
|
Event::BatchChecked { eras: vec![2] },
|
||||||
Event::Slashed { stash: exposed, amount: DepositAmount::get() }
|
Event::Slashed { stash: exposed, amount: Deposit::get() },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -889,30 +857,16 @@ mod on_idle {
|
|||||||
ErasToCheckPerBlock::<T>::put(2);
|
ErasToCheckPerBlock::<T>::put(2);
|
||||||
CurrentEra::<T>::put(BondingDuration::get());
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
// create an exposed nominator in era 1
|
// create an exposed nominator in era 0
|
||||||
let exposed = 666 as AccountId;
|
let exposed = 666;
|
||||||
pallet_staking::ErasStakers::<T>::mutate(0, VALIDATORS_PER_ERA, |expo| {
|
create_exposed_nominator(exposed, 0);
|
||||||
expo.others.push(IndividualExposure { who: exposed, value: 0 as Balance });
|
|
||||||
});
|
|
||||||
Balances::make_free_balance_be(&exposed, DepositAmount::get() + 100);
|
|
||||||
assert_ok!(Staking::bond(
|
|
||||||
RuntimeOrigin::signed(exposed),
|
|
||||||
exposed,
|
|
||||||
10,
|
|
||||||
RewardDestination::Staked
|
|
||||||
));
|
|
||||||
assert_ok!(Staking::nominate(RuntimeOrigin::signed(exposed), vec![exposed]));
|
|
||||||
|
|
||||||
// register the exposed one.
|
|
||||||
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(exposed)));
|
|
||||||
|
|
||||||
// a few blocks later, we realize they are slashed
|
// a few blocks later, we realize they are slashed
|
||||||
next_block(true);
|
next_block(true);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(exposed, Deposit::get())],
|
||||||
stash: exposed,
|
|
||||||
checked: bounded_vec![3, 2]
|
checked: bounded_vec![3, 2]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -923,8 +877,9 @@ mod on_idle {
|
|||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
// we slash them
|
// we slash them
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: exposed, eras: vec![3, 2] },
|
Event::BatchChecked { eras: vec![3, 2] },
|
||||||
Event::Slashed { stash: exposed, amount: DepositAmount::get() }
|
Event::Slashed { stash: exposed, amount: Deposit::get() },
|
||||||
|
Event::BatchFinished
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -955,7 +910,7 @@ mod on_idle {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![Event::Slashed { stash: 100, amount: DepositAmount::get() }]
|
vec![Event::Slashed { stash: 100, amount: Deposit::get() }, Event::BatchFinished]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -967,7 +922,7 @@ mod on_idle {
|
|||||||
CurrentEra::<T>::put(BondingDuration::get());
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
// create a new validator that 100% not exposed.
|
// create a new validator that 100% not exposed.
|
||||||
Balances::make_free_balance_be(&42, 100 + DepositAmount::get());
|
Balances::make_free_balance_be(&42, 100 + Deposit::get());
|
||||||
assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked));
|
assert_ok!(Staking::bond(RuntimeOrigin::signed(42), 42, 10, RewardDestination::Staked));
|
||||||
assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default()));
|
assert_ok!(Staking::validate(RuntimeOrigin::signed(42), Default::default()));
|
||||||
|
|
||||||
@@ -979,8 +934,7 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
Head::<T>::get(),
|
Head::<T>::get(),
|
||||||
Some(UnstakeRequest {
|
Some(UnstakeRequest {
|
||||||
deposit: DepositAmount::get(),
|
stashes: bounded_vec![(42, Deposit::get())],
|
||||||
stash: 42,
|
|
||||||
checked: bounded_vec![3, 2, 1, 0]
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@@ -990,8 +944,255 @@ mod on_idle {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
fast_unstake_events_since_last_call(),
|
fast_unstake_events_since_last_call(),
|
||||||
vec![
|
vec![
|
||||||
Event::Checking { stash: 42, eras: vec![3, 2, 1, 0] },
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
Event::Unstaked { stash: 42, result: Ok(()) }
|
Event::Unstaked { stash: 42, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod batched {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn single_block_batched_successful() {
|
||||||
|
ExtBuilder::default().batch(3).build_and_execute(|| {
|
||||||
|
ErasToCheckPerBlock::<T>::put(BondingDuration::get() + 1);
|
||||||
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8)));
|
||||||
|
|
||||||
|
assert_eq!(Queue::<T>::count(), 4);
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![
|
||||||
|
(1, Deposit::get()),
|
||||||
|
(5, Deposit::get()),
|
||||||
|
(7, Deposit::get())
|
||||||
|
],
|
||||||
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
assert_eq!(Queue::<T>::count(), 1);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
assert_eq!(Queue::<T>::count(), 1);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fast_unstake_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::BatchChecked { eras: vec![3, 2, 1, 0] },
|
||||||
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::Unstaked { stash: 5, result: Ok(()) },
|
||||||
|
Event::Unstaked { stash: 7, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_block_batched_successful() {
|
||||||
|
ExtBuilder::default().batch(3).build_and_execute(|| {
|
||||||
|
ErasToCheckPerBlock::<T>::put(2);
|
||||||
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(6)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(8)));
|
||||||
|
|
||||||
|
assert_eq!(Queue::<T>::count(), 4);
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![
|
||||||
|
(1, Deposit::get()),
|
||||||
|
(5, Deposit::get()),
|
||||||
|
(7, Deposit::get())
|
||||||
|
],
|
||||||
|
checked: bounded_vec![3, 2]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![
|
||||||
|
(1, Deposit::get()),
|
||||||
|
(5, Deposit::get()),
|
||||||
|
(7, Deposit::get())
|
||||||
|
],
|
||||||
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fast_unstake_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::BatchChecked { eras: vec![3, 2] },
|
||||||
|
Event::BatchChecked { eras: vec![1, 0] },
|
||||||
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::Unstaked { stash: 5, result: Ok(()) },
|
||||||
|
Event::Unstaked { stash: 7, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_block_batched_some_fail() {
|
||||||
|
ExtBuilder::default().batch(4).build_and_execute(|| {
|
||||||
|
ErasToCheckPerBlock::<T>::put(2);
|
||||||
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
|
// register two good ones.
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
||||||
|
create_exposed_nominator(666, 1);
|
||||||
|
create_exposed_nominator(667, 3);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(Queue::<T>::count(), 4);
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![
|
||||||
|
(1, Deposit::get()),
|
||||||
|
(3, Deposit::get()),
|
||||||
|
(666, Deposit::get())
|
||||||
|
],
|
||||||
|
checked: bounded_vec![3, 2]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),],
|
||||||
|
checked: bounded_vec![3, 2, 1, 0]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// when
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fast_unstake_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::Slashed { stash: 667, amount: 7 },
|
||||||
|
Event::BatchChecked { eras: vec![3, 2] },
|
||||||
|
Event::Slashed { stash: 666, amount: 7 },
|
||||||
|
Event::BatchChecked { eras: vec![1, 0] },
|
||||||
|
Event::Unstaked { stash: 1, result: Ok(()) },
|
||||||
|
Event::Unstaked { stash: 3, result: Ok(()) },
|
||||||
|
Event::BatchFinished
|
||||||
|
]
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn multi_block_batched_all_fail_early_exit() {
|
||||||
|
ExtBuilder::default().batch(2).build_and_execute(|| {
|
||||||
|
ErasToCheckPerBlock::<T>::put(1);
|
||||||
|
CurrentEra::<T>::put(BondingDuration::get());
|
||||||
|
|
||||||
|
// register two bad ones.
|
||||||
|
create_exposed_nominator(666, 3);
|
||||||
|
create_exposed_nominator(667, 2);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assert_eq!(Queue::<T>::count(), 2);
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
// when we progress a block..
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// ..and register two good ones.
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(2)));
|
||||||
|
assert_ok!(FastUnstake::register_fast_unstake(RuntimeOrigin::signed(4)));
|
||||||
|
|
||||||
|
// then one of the bad ones is reaped.
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![(667, Deposit::get())],
|
||||||
|
checked: bounded_vec![3]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// when we go to next block
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
// then the head is empty, we early terminate the batch.
|
||||||
|
assert_eq!(Head::<T>::get(), None);
|
||||||
|
|
||||||
|
// upon next block, we will assemble a new head.
|
||||||
|
next_block(true);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
Head::<T>::get(),
|
||||||
|
Some(UnstakeRequest {
|
||||||
|
stashes: bounded_vec![(1, Deposit::get()), (3, Deposit::get()),],
|
||||||
|
checked: bounded_vec![3]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
fast_unstake_events_since_last_call(),
|
||||||
|
vec![
|
||||||
|
Event::Slashed { stash: 666, amount: Deposit::get() },
|
||||||
|
Event::BatchChecked { eras: vec![3] },
|
||||||
|
Event::Slashed { stash: 667, amount: Deposit::get() },
|
||||||
|
Event::BatchFinished,
|
||||||
|
Event::BatchChecked { eras: vec![3] }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,15 +17,14 @@
|
|||||||
|
|
||||||
//! Types used in the Fast Unstake pallet.
|
//! Types used in the Fast Unstake pallet.
|
||||||
|
|
||||||
use crate::Config;
|
use crate::{Config, MaxChecking};
|
||||||
use codec::{Decode, Encode, MaxEncodedLen};
|
use codec::{Decode, Encode, MaxEncodedLen};
|
||||||
use frame_support::{
|
use frame_support::{
|
||||||
traits::{Currency, Get},
|
traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||||
BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
|
||||||
};
|
};
|
||||||
use scale_info::TypeInfo;
|
use scale_info::TypeInfo;
|
||||||
use sp_staking::EraIndex;
|
use sp_staking::EraIndex;
|
||||||
use sp_std::{fmt::Debug, prelude::*};
|
use sp_std::prelude::*;
|
||||||
|
|
||||||
pub type BalanceOf<T> =
|
pub type BalanceOf<T> =
|
||||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||||
@@ -33,15 +32,10 @@ pub type BalanceOf<T> =
|
|||||||
#[derive(
|
#[derive(
|
||||||
Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen,
|
Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen,
|
||||||
)]
|
)]
|
||||||
pub struct UnstakeRequest<
|
#[scale_info(skip_type_params(T))]
|
||||||
AccountId: Eq + PartialEq + Debug,
|
pub struct UnstakeRequest<T: Config> {
|
||||||
MaxChecked: Get<u32>,
|
/// This list of stashes being processed in this request, and their corresponding deposit.
|
||||||
Balance: PartialEq + Debug,
|
pub(crate) stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize>,
|
||||||
> {
|
|
||||||
/// Their stash account.
|
|
||||||
pub(crate) stash: AccountId,
|
|
||||||
/// The list of eras for which they have been checked.
|
/// The list of eras for which they have been checked.
|
||||||
pub(crate) checked: BoundedVec<EraIndex, MaxChecked>,
|
pub(crate) checked: BoundedVec<EraIndex, MaxChecking<T>>,
|
||||||
/// Deposit to be slashed if the unstake was unsuccessful.
|
|
||||||
pub(crate) deposit: Balance,
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user