diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index c396b008fa4c5..2b4a255dcf606 100644 --- a/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -20,6 +20,7 @@ mod mock; pub(crate) const LOG_TARGET: &str = "tests::e2e-epm"; +use frame_support::assert_ok; use mock::*; use sp_core::Get; use sp_npos_elections::{to_supports, StakedAssignment}; @@ -204,3 +205,72 @@ fn continous_slashes_below_offending_threshold() { } }); } + +#[test] +/// When validators are slashed, they are chilled and removed from the current `VoterList`. Thus, +/// the slashed validator should not be considered in the next validator set. However, if the +/// slashed validator sets its intention to validate again in the same era when it was slashed and +/// chilled, the validator may not be removed from the active validator set across eras, provided +/// it would selected in the subsequent era if there was no slash. Nominators of the slashed +/// validator will also be slashed and chilled, as expected, but the nomination intentions will +/// remain after the validator re-set the intention to be validating again. +/// +/// This behaviour is due to removing implicit chill upon slash +/// . +/// +/// Related to . +fn set_validation_intention_after_chilled() { + use frame_election_provider_support::SortedListProvider; + use pallet_staking::{Event, Forcing, Nominators}; + + let staking_builder = StakingExtBuilder::default(); + let epm_builder = EpmExtBuilder::default(); + + ExtBuilder::default() + .staking(staking_builder) + .epm(epm_builder) + .build_and_execute(|| { + assert_eq!(active_era(), 0); + // validator is part of the validator set. + assert!(Session::validators().contains(&81)); + assert!(::VoterList::contains(&81)); + + // nominate validator 81. + assert_ok!(Staking::nominate(RuntimeOrigin::signed(21), vec![81])); + assert_eq!(Nominators::::get(21).unwrap().targets, vec![81]); + + // 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(&81); + assert!(Session::validators().contains(&81)); + assert!(!::VoterList::contains(&81)); + assert_eq!( + staking_events(), + [ + Event::Chilled { stash: 81 }, + Event::ForceEra { mode: Forcing::ForceNew }, + Event::SlashReported { + validator: 81, + slash_era: 0, + fraction: Perbill::from_percent(10) + } + ], + ); + + // after the nominator is slashed and chilled, the nominations remain. + assert_eq!(Nominators::::get(21).unwrap().targets, vec![81]); + + // validator sets intention to stake again in the same era it was chilled. + assert_ok!(Staking::validate(RuntimeOrigin::signed(81), Default::default())); + + // progress era and check that the slashed validator is still part of the validator + // set. + assert!(start_next_active_era().is_ok()); + assert_eq!(active_era(), 1); + assert!(Session::validators().contains(&81)); + assert!(::VoterList::contains(&81)); + + // nominations are still active as before the slash. + assert_eq!(Nominators::::get(21).unwrap().targets, vec![81]); + }) +} diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs index e1abbb909cbb1..490179e91ddda 100644 --- a/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs @@ -124,11 +124,11 @@ impl pallet_balances::Config for Runtime { type DustRemoval = (); type ExistentialDeposit = ExistentialDeposit; type AccountStore = System; - type WeightInfo = (); + type MaxHolds = ConstU32<1>; + type MaxFreezes = traits::ConstU32<1>; type HoldIdentifier = (); type FreezeIdentifier = (); - type MaxHolds = traits::ConstU32<1>; - type MaxFreezes = traits::ConstU32<1>; + type WeightInfo = (); } impl pallet_timestamp::Config for Runtime { @@ -781,3 +781,11 @@ pub(crate) fn set_minimum_election_score( .map(|_| ()) .map_err(|_| ()) } + +pub(crate) fn staking_events() -> Vec> { + System::events() + .into_iter() + .map(|r| r.event) + .filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None }) + .collect::>() +}