Repository: paritytech/substrate
File: frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs (Rust)

Code before:
/// be below ED. In that case, only the stash (or root) can kill the ledger entry by calling
/// `withdraw_unbonded` after the bonding period has passed.
///
/// Related to <https://github.com/paritytech/substrate/issues/14246>.
fn ledger_consistency_active_balance_below_ed() {
	use pallet_staking::Error;

	ExtBuilder::default()
		.staking(StakingExtBuilder::default())
		.build_and_execute(|| {
			assert_eq!(Staking::ledger(&11).unwrap().active, 1000);

			// unbonding total of active stake fails because the active ledger balance would fall
			// below the `MinNominatorBond`.
			assert_err!(
				Staking::unbond(RuntimeOrigin::signed(11), 1000),
				Error::<Runtime>::InsufficientBond
			);
			assert_eq!(Staking::ledger(&11).unwrap().active, 1000);

			// however, chilling works as expected.
			assert_ok!(Staking::chill(RuntimeOrigin::signed(11)));

			// now unbonding the full active balance works, since remainer of the active balance is
			// not enforced to be below `MinNominatorBond` if the stash has been chilled.
			assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000),);

			// the active balance of the ledger entry is 0, while total balance is 1000 until
			// `withdraw_unbonded` is called.
			assert_eq!(Staking::ledger(&11).unwrap().active, 0);
			assert_eq!(Staking::ledger(&11).unwrap().total, 1000);

			// trying to withdraw the unbonded balance won't work yet because not enough bonding
			// eras have passed.
			assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
			assert_eq!(Staking::ledger(&11).unwrap().total, 1000);

			// tries to reap stash after chilling, which fails since the stash total balance is
			// above ED.
			assert_err!(
				Staking::reap_stash(RuntimeOrigin::signed(11), 21, 0),
				Error::<Runtime>::FundedTarget,
			);

			// after advancing `BondingDuration` eras, the `withdraw_unbonded` will unlock the
			// chunks and the ledger entry will be cleared, since the ledger active balance is 0.
			advance_eras(<Runtime as pallet_staking::Config>::BondingDuration::get() as usize);
			assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0));
			assert_eq!(Staking::ledger(&11), None);
		});
}

Review comment (suggestion):
```suggestion
			assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000));
```

Code after:
		// nominate validator 81.
		assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![41]));
		assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);

		// validator is slashed. it is removed from the `VoterList` through chilling but in the
		// current era, the validator is still part of the active validator set.
		add_slash(&41);
		assert!(Session::validators().contains(&41));
		assert!(!<Runtime as pallet_staking::Config>::VoterList::contains(&41));
		assert_eq!(
			staking_events(),
			[
				Event::Chilled { stash: 41 },
				Event::ForceEra { mode: Forcing::ForceNew },
				Event::SlashReported {
					validator: 41,
					slash_era: 0,
					fraction: Perbill::from_percent(10)
				}
			],
		);

		// after the nominator is slashed and chilled, the nominations remain.
		assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);

		// validator sets intention to stake again in the same era it was chilled.
		assert_ok!(Staking::validate(RuntimeOrigin::signed(41), Default::default()));

		// progress era and check that the slashed validator is still part of the validator
		// set.
		assert!(start_next_active_era(pool_state).is_ok());
		assert_eq!(active_era(), 1);
		assert!(Session::validators().contains(&41));
		assert!(<Runtime as pallet_staking::Config>::VoterList::contains(&41));

		// nominations are still active as before the slash.
		assert_eq!(Nominators::<Runtime>::get(21).unwrap().targets, vec![41]);
	})
}

#[test]
/// Active ledger balance may fall below ED if account chills before unbounding.
///
/// Unbonding call fails if the remaining ledger's stash balance falls below the existential
/// deposit. However, if the stash is chilled before unbonding, the ledger's active balance may
/// be below ED. In that case, only the stash (or root) can kill the ledger entry by calling
/// `withdraw_unbonded` after the bonding period has passed.
///
/// Related to <https://github.com/paritytech/substrate/issues/14246>.
fn ledger_consistency_active_balance_below_ed() {
	use pallet_staking::{Error, Event};