diff --git a/pallets/staking/src/mock.rs b/pallets/staking/src/mock.rs index 21a35e06e..4c71b12b8 100644 --- a/pallets/staking/src/mock.rs +++ b/pallets/staking/src/mock.rs @@ -63,6 +63,7 @@ frame_support::construct_runtime!( Historical: pallet_session_historical, BagsList: pallet_bags_list, Parameters: pallet_parameters, + Slashing: pallet_slashing, } ); @@ -418,6 +419,43 @@ impl entropy_shared::AttestationHandler for MockAttestationHandler { fn request_quote(_attestee: &AccountId, _nonce: [u8; 32]) {} } +type IdentificationTuple = (u64, pallet_staking::Exposure); +type Offence = pallet_slashing::UnresponsivenessOffence; + +parameter_types! { + pub static Offences: Vec = vec![]; +} + +/// A mock offence report handler. +pub struct OffenceHandler; +impl sp_staking::offence::ReportOffence + for OffenceHandler +{ + fn report_offence( + _reporters: Vec, + offence: Offence, + ) -> Result<(), sp_staking::offence::OffenceError> { + Offences::mutate(|l| l.push(offence)); + Ok(()) + } + + fn is_known_offence(_offenders: &[IdentificationTuple], _time_slot: &SessionIndex) -> bool { + false + } +} + +parameter_types! { + pub const ReportThreshold: u32 = 5; +} + +impl pallet_slashing::Config for Test { + type RuntimeEvent = RuntimeEvent; + type AuthorityId = UintAuthorityId; + type ReportThreshold = ReportThreshold; + type ValidatorSet = Historical; + type ReportUnresponsiveness = OffenceHandler; +} + impl pallet_staking_extension::Config for Test { type AttestationHandler = MockAttestationHandler; type Currency = Balances; diff --git a/pallets/staking/src/tests.rs b/pallets/staking/src/tests.rs index 92125b2a9..922c3aae2 100644 --- a/pallets/staking/src/tests.rs +++ b/pallets/staking/src/tests.rs @@ -844,3 +844,61 @@ fn it_stops_chill_when_signer_or_next_signer() { ); }); } + +#[test] +fn cannot_report_outside_of_signer_set() { + new_test_ext().execute_with(|| { + // These mappings come from the mock GenesisConfig + let (alice_validator, alice_tss) = (5, 7); + let (_bob_validator, bob_tss) = (6, 8); + + let (_not_validator, not_tss) = (33, 33); + + // We only want Alice to be part of the signing committee for the test. + Signers::::put(vec![alice_validator]); + + // A TSS which doesn't have a `ValidatorId` cannot report another peer + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(not_tss), bob_tss), + Error::::NoThresholdKey + ); + + // A validator which isn't part of the signing committee cannot report another peer + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(bob_tss), alice_tss), + Error::::NotSigner + ); + + // An offender that does not have a `ValidatorId` cannot be reported + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(alice_tss), not_tss), + Error::::NoThresholdKey + ); + + // An offender which isn't part of the signing committee cannot be reported + assert_noop!( + Staking::report_unstable_peer(RuntimeOrigin::signed(alice_tss), bob_tss), + Error::::NotSigner + ); + }) +} + +#[test] +fn can_report_unstable_peer() { + new_test_ext().execute_with(|| { + // These mappings come from the mock GenesisConfig + let (alice_validator, alice_tss) = (5, 7); + let (bob_validator, bob_tss) = (6, 8); + + Signers::::put(vec![alice_validator, bob_validator]); + + // The TSS accounts are used for reports. We expect the accompanying validator to be + // reported though. + assert_ok!(Staking::report_unstable_peer( + RuntimeOrigin::signed(alice_tss), + bob_tss + )); + + assert_eq!(>::failed_registrations(bob_validator), 1); + }) +}