Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

rework staking::reap_stash #10178

Merged
merged 6 commits into from
Nov 14, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1425,33 +1425,37 @@ pub mod pallet {
Ok(())
}

/// Remove all data structure concerning a staker/stash once its balance is at the minimum.
/// This is essentially equivalent to `withdraw_unbonded` except it can be called by anyone
/// and the target `stash` must have no funds left beyond the ED.
/// Remove all data structure concerning a staker/stash once it is at a state where it can
/// be considered `dust` in staking system. The requirements are:
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
///
/// This can be called from any origin.
/// 1. the `total_balance` of the stash is below existential deposit.
/// 2. or, the `ledger.total` of the stash is below existential deposit.
///
/// - `stash`: The stash account to reap. Its balance must be zero.
/// The former can happen in cases like slashed, the latter when a fully unbonded account
/// still receives staking rewards in `RewardDestination::Staked`.
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
///
/// # <weight>
/// Complexity: O(S) where S is the number of slashing spans on the account.
/// DB Weight:
/// - Reads: Stash Account, Bonded, Slashing Spans, Locks
/// - Writes: Bonded, Slashing Spans (if S > 0), Ledger, Payee, Validators, Nominators,
/// Stash Account, Locks
/// - Writes Each: SpanSlash * S
/// # </weight>
/// It can be called by anyone, as long as `stash` meets the above requirements.
///
/// Refunds the transaction fees upon successful execution.
#[pallet::weight(T::WeightInfo::reap_stash(*num_slashing_spans))]
pub fn reap_stash(
_origin: OriginFor<T>,
origin: OriginFor<T>,
stash: T::AccountId,
num_slashing_spans: u32,
) -> DispatchResult {
let at_minimum = T::Currency::total_balance(&stash) == T::Currency::minimum_balance();
ensure!(at_minimum, Error::<T>::FundedTarget);
) -> DispatchResultWithPostInfo {
let _ = ensure_signed(origin)?;

let ed = T::Currency::minimum_balance();
let reapable = T::Currency::total_balance(&stash) <= ed ||
shawntabrizi marked this conversation as resolved.
Show resolved Hide resolved
Self::ledger(Self::bonded(stash.clone()).ok_or(Error::<T>::NotStash)?)
.map(|l| l.total)
.unwrap_or_default() < ed;
ensure!(reapable, Error::<T>::FundedTarget);

Self::kill_stash(&stash, num_slashing_spans)?;
T::Currency::remove_lock(STAKING_ID, &stash);
Ok(())

Ok(Pays::No.into())
}

/// Remove the given nominations from the calling validator.
Expand Down
108 changes: 16 additions & 92 deletions frame/staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1633,115 +1633,39 @@ fn reward_to_stake_works() {
}

#[test]
fn on_free_balance_zero_stash_removes_validator() {
// Tests that validator storage items are cleaned up when stash is empty
// Tests that storage items are untouched when controller is empty
fn reap_stash_works() {
ExtBuilder::default()
.existential_deposit(10)
.balance_factor(10)
.build_and_execute(|| {
// Check the balance of the validator account
// given
assert_eq!(Balances::free_balance(10), 10);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(11), 10 * 1000);
// Check these two accounts are bonded
assert_eq!(Staking::bonded(&11), Some(10));

// Set payee information
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash));

// Check storage items that should be cleaned up
assert!(<Ledger<Test>>::contains_key(&10));
assert!(<Bonded<Test>>::contains_key(&11));
assert!(<Validators<Test>>::contains_key(&11));
assert!(<Payee<Test>>::contains_key(&11));

// Reduce free_balance of controller to 0
let _ = Balances::slash(&10, Balance::max_value());

// Check the balance of the stash account has not been touched
assert_eq!(Balances::free_balance(11), 10 * 1000);
// Check these two accounts are still bonded
assert_eq!(Staking::bonded(&11), Some(10));

// Check storage items have not changed
assert!(<Ledger<Test>>::contains_key(&10));
assert!(<Bonded<Test>>::contains_key(&11));
assert!(<Validators<Test>>::contains_key(&11));
assert!(<Payee<Test>>::contains_key(&11));

// Reduce free_balance of stash to 0
let _ = Balances::slash(&11, Balance::max_value());
// Check total balance of stash
assert_eq!(Balances::total_balance(&11), 10);

// Reap the stash
assert_ok!(Staking::reap_stash(Origin::none(), 11, 0));

// Check storage items do not exist
assert!(!<Ledger<Test>>::contains_key(&10));
assert!(!<Bonded<Test>>::contains_key(&11));
assert!(!<Validators<Test>>::contains_key(&11));
assert!(!<Nominators<Test>>::contains_key(&11));
assert!(!<Payee<Test>>::contains_key(&11));
});
}

#[test]
fn on_free_balance_zero_stash_removes_nominator() {
// Tests that nominator storage items are cleaned up when stash is empty
// Tests that storage items are untouched when controller is empty
ExtBuilder::default()
.existential_deposit(10)
.balance_factor(10)
.build_and_execute(|| {
// Make 10 a nominator
assert_ok!(Staking::nominate(Origin::signed(10), vec![20]));
// Check that account 10 is a nominator
assert!(<Nominators<Test>>::contains_key(11));
// Check the balance of the nominator account
assert_eq!(Balances::free_balance(10), 10);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(11), 10_000);

// Set payee information
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash));

// Check storage items that should be cleaned up
assert!(<Ledger<Test>>::contains_key(&10));
assert!(<Bonded<Test>>::contains_key(&11));
assert!(<Nominators<Test>>::contains_key(&11));
assert!(<Payee<Test>>::contains_key(&11));

// Reduce free_balance of controller to 0
let _ = Balances::slash(&10, Balance::max_value());
// Check total balance of account 10
assert_eq!(Balances::total_balance(&10), 0);

// Check the balance of the stash account has not been touched
assert_eq!(Balances::free_balance(11), 10_000);
// Check these two accounts are still bonded
assert_eq!(Staking::bonded(&11), Some(10));

// Check storage items have not changed
assert!(<Ledger<Test>>::contains_key(&10));
assert!(<Bonded<Test>>::contains_key(&11));
assert!(<Nominators<Test>>::contains_key(&11));
assert!(<Payee<Test>>::contains_key(&11));
// stash is not reapable
assert_noop!(
Staking::reap_stash(Origin::signed(20), 11, 0),
Error::<Test>::FundedTarget
);
// controller or any other account is not reapable
assert_noop!(Staking::reap_stash(Origin::signed(20), 10, 0), Error::<Test>::NotStash);

// Reduce free_balance of stash to 0
let _ = Balances::slash(&11, Balance::max_value());
// Check total balance of stash
assert_eq!(Balances::total_balance(&11), 10);
// slash the stash to 5
let _ = Balances::slash(&11, 10 * 1000 - 5);

// Reap the stash
assert_ok!(Staking::reap_stash(Origin::none(), 11, 0));
// reap-able
assert_ok!(Staking::reap_stash(Origin::signed(20), 11, 0));

// Check storage items do not exist
// then
assert!(!<Ledger<Test>>::contains_key(&10));
assert!(!<Bonded<Test>>::contains_key(&11));
assert!(!<Validators<Test>>::contains_key(&11));
assert!(!<Nominators<Test>>::contains_key(&11));
assert!(!<Payee<Test>>::contains_key(&11));
});
}
Expand Down Expand Up @@ -2556,10 +2480,10 @@ fn garbage_collection_after_slashing() {

// reap_stash respects num_slashing_spans so that weight is accurate
assert_noop!(
Staking::reap_stash(Origin::none(), 11, 0),
Staking::reap_stash(Origin::signed(20), 11, 0),
Error::<Test>::IncorrectSlashingSpans
);
assert_ok!(Staking::reap_stash(Origin::none(), 11, 2));
assert_ok!(Staking::reap_stash(Origin::signed(20), 11, 2));

assert!(<Staking as crate::Store>::SlashingSpans::get(&11).is_none());
assert_eq!(<Staking as crate::Store>::SpanSlash::get(&(11, 0)).amount_slashed(), &0);
Expand Down