Skip to content

Commit 9232f49

Browse files
Ank4nTarekkMA
authored andcommitted
[Staking] Not allow reap stash for virtual stakers (paritytech#4311)
Related to paritytech#3905. Since virtual stakers does not have a real balance, they should not be allowed to be reaped. The proper reaping for agents slashed will be done in a separate PR.
1 parent e7c3326 commit 9232f49

4 files changed

Lines changed: 113 additions & 1 deletion

File tree

prdoc/pr_4311.prdoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
title: Not allow reap stash for virtual stakers.
2+
3+
doc:
4+
- audience: Runtime Dev
5+
description: |
6+
Add guards to staking dispathables to prevent virtual stakers to be reaped.
7+
8+
crates:
9+
- name: pallet-staking

substrate/frame/staking/src/pallet/impls.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,12 @@ impl<T: Config> Pallet<T> {
8787
StakingLedger::<T>::paired_account(Stash(stash.clone()))
8888
}
8989

90-
/// Inspects and returns the corruption state of a ledger and bond, if any.
90+
/// Inspects and returns the corruption state of a ledger and direct bond, if any.
9191
///
9292
/// Note: all operations in this method access directly the `Bonded` and `Ledger` storage maps
9393
/// instead of using the [`StakingLedger`] API since the bond and/or ledger may be corrupted.
94+
/// It is also meant to check state for direct bonds and may not work as expected for virtual
95+
/// bonds.
9496
pub(crate) fn inspect_bond_state(
9597
stash: &T::AccountId,
9698
) -> Result<LedgerIntegrityState, Error<T>> {

substrate/frame/staking/src/pallet/mod.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -868,6 +868,8 @@ pub mod pallet {
868868
RewardDestinationRestricted,
869869
/// Not enough funds available to withdraw.
870870
NotEnoughFunds,
871+
/// Operation not allowed for virtual stakers.
872+
VirtualStakerNotAllowed,
871873
}
872874

873875
#[pallet::hooks]
@@ -1634,6 +1636,9 @@ pub mod pallet {
16341636
) -> DispatchResultWithPostInfo {
16351637
let _ = ensure_signed(origin)?;
16361638

1639+
// virtual stakers should not be allowed to be reaped.
1640+
ensure!(!Self::is_virtual_staker(&stash), Error::<T>::VirtualStakerNotAllowed);
1641+
16371642
let ed = T::Currency::minimum_balance();
16381643
let reapable = T::Currency::total_balance(&stash) < ed ||
16391644
Self::ledger(Stash(stash.clone())).map(|l| l.total).unwrap_or_default() < ed;
@@ -1994,6 +1999,9 @@ pub mod pallet {
19941999
) -> DispatchResult {
19952000
T::AdminOrigin::ensure_origin(origin)?;
19962001

2002+
// cannot restore ledger for virtual stakers.
2003+
ensure!(!Self::is_virtual_staker(&stash), Error::<T>::VirtualStakerNotAllowed);
2004+
19972005
let current_lock = T::Currency::balance_locked(crate::STAKING_ID, &stash);
19982006
let stash_balance = T::Currency::free_balance(&stash);
19992007

substrate/frame/staking/src/tests.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7205,6 +7205,99 @@ mod staking_unchecked {
72057205
assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_share);
72067206
})
72077207
}
7208+
7209+
#[test]
7210+
fn virtual_stakers_cannot_be_reaped() {
7211+
ExtBuilder::default()
7212+
// we need enough validators such that disables are allowed.
7213+
.validator_count(7)
7214+
.set_status(41, StakerStatus::Validator)
7215+
.set_status(51, StakerStatus::Validator)
7216+
.set_status(201, StakerStatus::Validator)
7217+
.set_status(202, StakerStatus::Validator)
7218+
.build_and_execute(|| {
7219+
// make 101 only nominate 11.
7220+
assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![11]));
7221+
7222+
mock::start_active_era(1);
7223+
7224+
// slash all stake.
7225+
let slash_percent = Perbill::from_percent(100);
7226+
let initial_exposure = Staking::eras_stakers(active_era(), &11);
7227+
// 101 is a nominator for 11
7228+
assert_eq!(initial_exposure.others.first().unwrap().who, 101);
7229+
// make 101 a virtual nominator
7230+
<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101);
7231+
// set payee different to self.
7232+
assert_ok!(<Staking as StakingInterface>::update_payee(&101, &102));
7233+
7234+
// cache values
7235+
let validator_balance = Balances::free_balance(&11);
7236+
let validator_stake = Staking::ledger(11.into()).unwrap().total;
7237+
let nominator_balance = Balances::free_balance(&101);
7238+
let nominator_stake = Staking::ledger(101.into()).unwrap().total;
7239+
7240+
// 11 goes offline
7241+
on_offence_now(
7242+
&[OffenceDetails {
7243+
offender: (11, initial_exposure.clone()),
7244+
reporters: vec![],
7245+
}],
7246+
&[slash_percent],
7247+
);
7248+
7249+
// both stakes must have been decreased to 0.
7250+
assert_eq!(Staking::ledger(101.into()).unwrap().active, 0);
7251+
assert_eq!(Staking::ledger(11.into()).unwrap().active, 0);
7252+
7253+
// all validator stake is slashed
7254+
assert_eq_error_rate!(
7255+
validator_balance - validator_stake,
7256+
Balances::free_balance(&11),
7257+
1
7258+
);
7259+
// Because slashing happened.
7260+
assert!(is_disabled(11));
7261+
7262+
// Virtual nominator's balance is not slashed.
7263+
assert_eq!(Balances::free_balance(&101), nominator_balance);
7264+
// Slash is broadcasted to slash observers.
7265+
assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_stake);
7266+
7267+
// validator can be reaped.
7268+
assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(10), 11, u32::MAX));
7269+
// nominator is a virtual staker and cannot be reaped.
7270+
assert_noop!(
7271+
Staking::reap_stash(RuntimeOrigin::signed(10), 101, u32::MAX),
7272+
Error::<Test>::VirtualStakerNotAllowed
7273+
);
7274+
})
7275+
}
7276+
7277+
#[test]
7278+
fn restore_ledger_not_allowed_for_virtual_stakers() {
7279+
ExtBuilder::default().has_stakers(true).build_and_execute(|| {
7280+
setup_double_bonded_ledgers();
7281+
assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
7282+
set_controller_no_checks(&444);
7283+
// 333 is corrupted
7284+
assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
7285+
// migrate to virtual staker.
7286+
<Staking as StakingUnchecked>::migrate_to_virtual_staker(&333);
7287+
7288+
// recover the ledger won't work for virtual staker
7289+
assert_noop!(
7290+
Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None),
7291+
Error::<Test>::VirtualStakerNotAllowed
7292+
);
7293+
7294+
// migrate 333 back to normal staker
7295+
<VirtualStakers<Test>>::remove(333);
7296+
7297+
// try restore again
7298+
assert_ok!(Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None));
7299+
})
7300+
}
72087301
}
72097302
mod ledger {
72107303
use super::*;

0 commit comments

Comments
 (0)