Files
pezkuwi-sdk/bizinikiwi/pezframe/staking-async/ahm-test/src/ah/test.rs
T
pezkuwichain cf463fe8ee FAZ 1 Complete: Workspace compile fixes, warning cleanup, version bumps
- Fixed is_using_frame_crate() macro to check for pezframe/pezkuwi_sdk
- Removed disable_pezframe_system_supertrait_check temporary bypasses
- Feature-gated storage-benchmark and teyrchain-benchmarks code
- Fixed dead_code warnings with underscore prefix (_Header)
- Removed unused imports and shadowing use statements
- Version bumps: procedural-tools 10.0.1, benchmarking-cli 32.0.1,
  docs 0.0.2, minimal-runtime 0.0.1, yet-another-teyrchain 0.6.1, umbrella 0.1.2
- Updated MAINNET_ROADMAP.md with FAZ 1 completion status
2026-01-02 11:41:09 +03:00

1017 lines
27 KiB
Rust

// This file is part of Bizinikiwi.
// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
// SPDX-License-Identifier: Apache-2.0
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use crate::ah::mock::*;
use pezframe::prelude::Perbill;
use pezframe_support::assert_ok;
use pezpallet_election_provider_multi_block::{Event as ElectionEvent, Phase};
use pezpallet_staking_async::{
self as staking_async, session_rotation::Rotator, ActiveEra, ActiveEraInfo, CurrentEra,
Event as StakingEvent,
};
use pezpallet_staking_async_rc_client::{
self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport,
};
// Tests that are specific to Asset Hub.
#[test]
fn on_receive_session_report() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// GIVEN genesis state of ah
assert_eq!(System::block_number(), 1);
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 0);
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
// WHEN session ends on RC and session report is received by AH.
let session_report = rc_client::SessionReport {
end_index: 0,
validator_points: (1..9).into_iter().map(|v| (v as AccountId, v * 10)).collect(),
activation_timestamp: None,
leftover: false,
};
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
session_report.clone(),
));
// THEN end 0, start 1, plan 2
let era_points = staking_async::ErasRewardPoints::<T>::get(&0);
assert_eq!(era_points.total, 360);
assert_eq!(era_points.individual.get(&1), Some(&10));
assert_eq!(era_points.individual.get(&4), Some(&40));
assert_eq!(era_points.individual.get(&7), Some(&70));
assert_eq!(era_points.individual.get(&8), Some(&80));
assert_eq!(era_points.individual.get(&9), None);
// assert no era changed yet.
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
assert_eq!(
staking_events_since_last_call(),
vec![StakingEvent::SessionRotated {
starting_session: 1,
active_era: 0,
planned_era: 0
}]
);
assert_eq!(election_events_since_last_call(), vec![]);
// roll two more sessions...
for i in 1..3 {
// roll some random number of blocks.
roll_many(10);
// send the session report.
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: i,
validator_points: vec![(1, 10)],
activation_timestamp: None,
leftover: false,
}
));
let era_points = staking_async::ErasRewardPoints::<T>::get(&0);
assert_eq!(era_points.total, 360 + i * 10);
assert_eq!(era_points.individual.get(&1), Some(&(10 + i * 10)));
assert_eq!(
staking_events_since_last_call(),
vec![StakingEvent::SessionRotated {
starting_session: i + 1,
active_era: 0,
planned_era: 0
}]
);
}
// Next session we will begin election.
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: 3,
validator_points: vec![(1, 10)],
activation_timestamp: None,
leftover: false,
}
));
assert_eq!(
staking_events_since_last_call(),
vec![StakingEvent::SessionRotated {
starting_session: 4,
active_era: 0,
// planned era 1 indicates election start signal is sent.
planned_era: 1
}]
);
assert_eq!(
election_events_since_last_call(),
// Snapshot phase has started which will run for 3 blocks
vec![ElectionEvent::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) }]
);
// roll 3 blocks for signed phase, and one for the transition.
roll_many(3 + 1);
assert_eq!(
election_events_since_last_call(),
// Signed phase has started which will run for 3 blocks.
vec![ElectionEvent::PhaseTransitioned {
from: Phase::Snapshot(0),
to: Phase::Signed(3)
}]
);
// roll some blocks until election result is exported.
roll_many(15);
assert_eq!(
election_events_since_last_call(),
vec![
ElectionEvent::PhaseTransitioned {
from: Phase::Signed(0),
to: Phase::SignedValidation(6)
},
ElectionEvent::PhaseTransitioned {
from: Phase::SignedValidation(0),
to: Phase::Unsigned(3)
},
ElectionEvent::PhaseTransitioned { from: Phase::Unsigned(0), to: Phase::Done },
]
);
// no staking event while election ongoing.
assert_eq!(staking_events_since_last_call(), vec![]);
// no xcm message sent yet.
assert_eq!(LocalQueue::get().unwrap(), vec![]);
// normal conditions, validator set can be sent.
// next 3 block exports the election result to staking.
roll_many(3);
assert_eq!(
staking_events_since_last_call(),
vec![
StakingEvent::PagedElectionProceeded { page: 2, result: Ok(4) },
StakingEvent::PagedElectionProceeded { page: 1, result: Ok(0) },
StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }
]
);
assert_eq!(
election_events_since_last_call(),
vec![
ElectionEvent::PhaseTransitioned { from: Phase::Done, to: Phase::Export(1) },
ElectionEvent::PhaseTransitioned { from: Phase::Export(0), to: Phase::Off }
]
);
// New validator set xcm message is sent to RC.
assert_eq!(
LocalQueue::get().unwrap(),
vec![(
// this is the block number at which the message was sent.
43,
OutgoingMessages::ValidatorSet(ValidatorSetReport {
new_validator_set: vec![3, 5, 6, 8],
id: 1,
prune_up_to: None,
leftover: false
})
)]
);
})
}
#[test]
fn validator_set_send_fail_retries() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// GIVEN genesis state of ah
assert_eq!(System::block_number(), 1);
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 0);
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
// first session comes in.
let session_report = rc_client::SessionReport {
end_index: 0,
validator_points: (1..9).into_iter().map(|v| (v as AccountId, v * 10)).collect(),
activation_timestamp: None,
leftover: false,
};
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
session_report.clone(),
));
// flush some events.
let _ = staking_events_since_last_call();
// roll two more sessions...
for i in 1..3 {
// roll some random number of blocks.
roll_many(10);
// send the session report.
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: i,
validator_points: vec![(1, 10)],
activation_timestamp: None,
leftover: false,
}
));
let era_points = staking_async::ErasRewardPoints::<T>::get(&0);
assert_eq!(era_points.total, 360 + i * 10);
assert_eq!(era_points.individual.get(&1), Some(&(10 + i * 10)));
assert_eq!(
staking_events_since_last_call(),
vec![StakingEvent::SessionRotated {
starting_session: i + 1,
active_era: 0,
planned_era: 0
}]
);
}
// Next session we will begin election.
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: 3,
validator_points: vec![(1, 10)],
activation_timestamp: None,
leftover: false,
}
));
assert_eq!(
staking_events_since_last_call(),
vec![StakingEvent::SessionRotated {
starting_session: 4,
active_era: 0,
// planned era 1 indicates election start signal is sent.
planned_era: 1
}]
);
assert_eq!(
election_events_since_last_call(),
// Snapshot phase has started which will run for 3 blocks
vec![ElectionEvent::PhaseTransitioned { from: Phase::Off, to: Phase::Snapshot(3) }]
);
// roll 3 blocks for signed phase, and one for the transition.
roll_many(3 + 1);
assert_eq!(
election_events_since_last_call(),
// Signed phase has started which will run for 3 blocks.
vec![ElectionEvent::PhaseTransitioned {
from: Phase::Snapshot(0),
to: Phase::Signed(3)
}]
);
// roll some blocks until election result is exported.
roll_many(15);
assert_eq!(
election_events_since_last_call(),
vec![
ElectionEvent::PhaseTransitioned {
from: Phase::Signed(0),
to: Phase::SignedValidation(6)
},
ElectionEvent::PhaseTransitioned {
from: Phase::SignedValidation(0),
to: Phase::Unsigned(3)
},
ElectionEvent::PhaseTransitioned { from: Phase::Unsigned(0), to: Phase::Done },
]
);
// no staking event while election ongoing.
assert_eq!(staking_events_since_last_call(), vec![]);
// no xcm message sent yet.
assert_eq!(LocalQueue::get().unwrap(), vec![]);
// bad condition -- validator set cannot be sent.
// assume the next validator set cannot be sent.
NextRelayDeliveryFails::set(true);
let _ = rc_client_events_since_last_call();
roll_many(3);
assert_eq!(
staking_events_since_last_call(),
vec![
StakingEvent::PagedElectionProceeded { page: 2, result: Ok(4) },
StakingEvent::PagedElectionProceeded { page: 1, result: Ok(0) },
StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }
]
);
assert_eq!(
election_events_since_last_call(),
vec![
ElectionEvent::PhaseTransitioned { from: Phase::Done, to: Phase::Export(1) },
ElectionEvent::PhaseTransitioned { from: Phase::Export(0), to: Phase::Off }
]
);
// but..
// nothing is queued
assert!(LocalQueue::get().unwrap().is_empty());
// rc-client has an event
assert_eq!(
rc_client_events_since_last_call(),
vec![rc_client::Event::Unexpected(UnexpectedKind::ValidatorSetSendFailed)]
);
// the buffer is set
assert!(matches!(OutgoingValidatorSet::<T>::get(), Some((_, 2))));
// next block it is retried and sent fine
roll_next();
assert_eq!(
LocalQueue::get().unwrap(),
vec![(
// this is the block number at which the message was sent.
44,
OutgoingMessages::ValidatorSet(ValidatorSetReport {
new_validator_set: vec![3, 5, 6, 8],
id: 1,
prune_up_to: None,
leftover: false
})
)]
);
// buffer is clear
assert!(OutgoingValidatorSet::<T>::get().is_none());
});
}
#[test]
fn roll_many_eras() {
// todo:
// - Ensure rewards can be claimed at correct era.
// - assert outgoing messages, including id and prune_up_to.
ExtBuilder::default().local_queue().build().execute_with(|| {
let mut session_counter: u32 = 0;
let mut roll_session = |activate: bool| {
let activation_timestamp = if activate {
let current_era = CurrentEra::<T>::get().unwrap();
Some((current_era as u64 * 1000, current_era as u32))
} else {
None
};
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: session_counter,
validator_points: vec![(1, 10)],
activation_timestamp,
leftover: false,
}
));
// increment session for the next iteration.
session_counter += 1;
// run session blocks.
roll_many(60);
};
for era in 0..50 {
// --- first 3 idle session
for _ in 0..3 {
roll_session(false);
assert_eq!(ActiveEra::<T>::get().unwrap().index, era);
assert_eq!(CurrentEra::<T>::get().unwrap(), era);
}
// ensure validator set not sent yet to RC.
// queue size same as in last iteration.
assert_eq!(LocalQueue::get().unwrap().len() as u32, era);
// --- plan era session
roll_session(false);
assert_eq!(ActiveEra::<T>::get().unwrap().index, era);
assert_eq!(CurrentEra::<T>::get().unwrap(), era + 1);
// ensure new validator set sent to RC.
// length increases by 1.
assert_eq!(LocalQueue::get().unwrap().len() as u32, era + 1);
// --- 5th starting session, idle
roll_session(false);
assert_eq!(ActiveEra::<T>::get().unwrap().index, era);
assert_eq!(CurrentEra::<T>::get().unwrap(), era + 1);
// --- 6th the era rotation session
roll_session(true);
assert_eq!(ActiveEra::<T>::get().unwrap().index, era + 1);
assert_eq!(CurrentEra::<T>::get().unwrap(), era + 1);
}
});
}
#[test]
fn receives_old_session_report() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// Initial state
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 0);
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), None);
// Receive report for end of 0, start of 1 and plan 2.
let session_report = rc_client::SessionReport {
end_index: 0,
validator_points: vec![(5, 50)],
activation_timestamp: None,
leftover: false,
};
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
session_report.clone(),
));
// then
assert_eq!(
rc_client_events_since_last_call(),
vec![rc_client::Event::SessionReportReceived {
end_index: 0,
activation_timestamp: None,
validator_points_counts: 1,
leftover: false
}]
);
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SessionRotated {
starting_session: 1,
active_era: 0,
planned_era: 0
}]
);
// reward points are not added
assert_eq!(staking_async::ErasRewardPoints::<T>::get(&0).total, 50);
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), Some(0));
// then send it again, this is basically dropped, although it returns `Ok()`
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
session_report
));
// no storage is changed
assert_eq!(staking_async::ErasRewardPoints::<T>::get(&0).total, 50);
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), Some(0));
})
}
#[test]
fn receives_session_report_in_future() {
ExtBuilder::default().local_queue().build().execute_with(|| {
// Initial state
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 0);
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), None);
// Receive report for end of 1, start of 1 and plan 2.
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: 0,
validator_points: vec![(5, 50)],
activation_timestamp: None,
leftover: false,
},
));
// then
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), Some(0));
assert_eq!(
rc_client_events_since_last_call(),
vec![rc_client::Event::SessionReportReceived {
end_index: 0,
activation_timestamp: None,
validator_points_counts: 1,
leftover: false
}]
);
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SessionRotated {
starting_session: 1,
active_era: 0,
planned_era: 0
}]
);
// reward points are added
assert_eq!(staking_async::ErasRewardPoints::<T>::get(&0).total, 50);
// skip end_index 1, send 2
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: 2,
validator_points: vec![(5, 50)],
activation_timestamp: None,
leftover: false,
},
));
// our tracker of last session is updated (and has skipped `1`)
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), Some(2));
assert_eq!(
rc_client_events_since_last_call(),
vec![
rc_client::Event::Unexpected(UnexpectedKind::SessionSkipped),
rc_client::Event::SessionReportReceived {
end_index: 2,
activation_timestamp: None,
validator_points_counts: 1,
leftover: false
}
]
);
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SessionRotated {
starting_session: 3,
active_era: 0,
planned_era: 0
}]
);
// reward points are accumulated
assert_eq!(staking_async::ErasRewardPoints::<T>::get(&0).total, 100);
})
}
#[test]
fn session_report_burst() {
// note: there is also an e2e `session_report_burst` test
ExtBuilder::default().local_queue().build().execute_with(|| {
// Initial state
assert_eq!(CurrentEra::<T>::get(), Some(0));
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 0);
assert_eq!(ActiveEra::<T>::get(), Some(ActiveEraInfo { index: 0, start: Some(0) }));
assert_eq!(rc_client::LastSessionReportEndingIndex::<T>::get(), None);
// then send 20 sessions all at once. This is enough to schedule multiple elections, but we
// only schedule one.
for s in 1..=20 {
assert_ok!(rc_client::Pezpallet::<T>::relay_session_report(
RuntimeOrigin::root(),
rc_client::SessionReport {
end_index: s,
validator_points: vec![(5, 50)],
activation_timestamp: None,
leftover: false,
},
));
// all are processed fine, in one go
assert_eq!(
rc_client_events_since_last_call(),
vec![rc_client::Event::SessionReportReceived {
end_index: s,
activation_timestamp: None,
validator_points_counts: 1,
leftover: false
}]
);
// and we have collected reward points.
assert_eq!(staking_async::ErasRewardPoints::<T>::get(&0).total, 50 * s);
}
// crucially, election has started, but we have not done anything else.
Rotator::<T>::assert_election_ongoing();
assert!(matches!(
&staking_events_since_last_call()[..],
&[
staking_async::Event::SessionRotated {
starting_session: 2,
active_era: 0,
planned_era: 0
},
..,
staking_async::Event::SessionRotated {
starting_session: 21,
active_era: 0,
planned_era: 1
}
]
));
})
}
#[test]
fn on_offence_current_era() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let active_validators = roll_until_next_active(0);
assert_eq!(Rotator::<Runtime>::active_era_start_session_index(), 5);
assert_eq!(active_validators, vec![3, 5, 6, 8]);
// flush the events.
let _ = staking_events_since_last_call();
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
vec![
(
5,
rc_client::Offence {
offender: 5,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
),
(
5,
rc_client::Offence {
offender: 3,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
)
]
));
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::OffenceReported {
offence_era: 1,
validator: 5,
fraction: Perbill::from_percent(50)
},
staking_async::Event::OffenceReported {
offence_era: 1,
validator: 3,
fraction: Perbill::from_percent(50)
}
]
);
// 2 blocks to process these offences, and they are deferred.
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SlashComputed {
offence_era: 1,
slash_era: 3,
offender: 5,
page: 0
},]
);
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SlashComputed {
offence_era: 1,
slash_era: 3,
offender: 3,
page: 0
}]
);
// skip two eras
assert_eq!(SlashDeferredDuration::get(), 2);
roll_until_next_active(5);
roll_until_next_active(10);
let _ = staking_events_since_last_call();
// 2 blocks to apply the slashes
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::Slashed { staker: 3, amount: 50 },]
);
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::Slashed { staker: 5, amount: 50 },
staking_async::Event::Slashed { staker: 110, amount: 50 }
]
);
});
}
#[test]
fn on_offence_current_era_instant_apply() {
ExtBuilder::default()
.local_queue()
.slash_defer_duration(0)
.build()
.execute_with(|| {
let active_validators = roll_until_next_active(0);
assert_eq!(Rotator::<Runtime>::era_start_session_index(1), Some(5));
assert_eq!(active_validators, vec![3, 5, 6, 8]);
// flush the events.
let _ = staking_events_since_last_call();
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
vec![
(
5,
rc_client::Offence {
offender: 5,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
),
(
5,
rc_client::Offence {
offender: 3,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
)
]
));
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::OffenceReported {
offence_era: 1,
validator: 5,
fraction: Perbill::from_percent(50)
},
staking_async::Event::OffenceReported {
offence_era: 1,
validator: 3,
fraction: Perbill::from_percent(50)
}
]
);
// 2 blocks to process these offences, and they are applied on the spot.
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::SlashComputed {
offence_era: 1,
slash_era: 1,
offender: 5,
page: 0
},
staking_async::Event::Slashed { staker: 5, amount: 50 },
staking_async::Event::Slashed { staker: 110, amount: 50 }
]
);
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::SlashComputed {
offence_era: 1,
slash_era: 1,
offender: 3,
page: 0
},
staking_async::Event::Slashed { staker: 3, amount: 50 }
]
);
});
}
#[test]
fn on_offence_non_validator() {
ExtBuilder::default()
.slash_defer_duration(0)
.local_queue()
.build()
.execute_with(|| {
let active_validators = roll_until_next_active(0);
assert_eq!(Rotator::<Runtime>::era_start_session_index(1), Some(5));
assert_eq!(active_validators, vec![3, 5, 6, 8]);
// flush the events.
let _ = staking_events_since_last_call();
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
vec![(
5,
rc_client::Offence {
// this offender is unknown to the staking pezpallet.
offender: 666,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
)]
));
// nada
assert_eq!(staking_events_since_last_call(), vec![]);
});
}
#[test]
fn on_offence_previous_era() {
ExtBuilder::default().local_queue().build().execute_with(|| {
let _ = roll_until_next_active(0);
let _ = roll_until_next_active(5);
let active_validators = roll_until_next_active(10);
assert_eq!(active_validators, vec![3, 5, 6, 8]);
assert_eq!(Rotator::<Runtime>::active_era(), 3);
// flush the events.
let _ = staking_events_since_last_call();
// GIVEN slash defer duration of 2 eras, active era = 3.
assert_eq!(SlashDeferredDuration::get(), 2);
assert_eq!(Rotator::<Runtime>::era_start_session_index(1), Some(5));
// 1 era is reserved for the application of slashes.
let oldest_reportable_era =
Rotator::<Runtime>::active_era() - (SlashDeferredDuration::get() - 1);
assert_eq!(oldest_reportable_era, 2);
// WHEN we report an offence older than Era 2 (oldest reportable era).
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
// offence is in era 1
vec![(
5,
rc_client::Offence {
offender: 3,
reporters: vec![],
slash_fraction: Perbill::from_percent(30),
}
)]
));
// THEN offence is ignored.
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::OffenceTooOld {
offence_era: 1,
validator: 3,
fraction: Perbill::from_percent(30)
}]
);
// WHEN: report an offence for the session belonging to the previous era
assert_eq!(Rotator::<Runtime>::era_start_session_index(2), Some(10));
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
// offence is in era 2
vec![(
10,
rc_client::Offence {
offender: 3,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
)]
));
// THEN: offence is reported.
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::OffenceReported {
offence_era: 2,
validator: 3,
fraction: Perbill::from_percent(50)
}]
);
// computed in the next block (will be applied in era 4)
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::SlashComputed {
offence_era: 2,
slash_era: 4,
offender: 3,
page: 0
},]
);
// roll to the next era.
roll_until_next_active(15);
// ensure we are in era 4.
assert_eq!(Rotator::<Runtime>::active_era(), 4);
// clear staking events.
let _ = staking_events_since_last_call();
// the next block applies the slashes.
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::Slashed { staker: 3, amount: 50 }]
);
// nothing left
roll_next();
assert_eq!(staking_events_since_last_call(), vec![]);
});
}
#[test]
fn on_offence_previous_era_instant_apply() {
ExtBuilder::default()
.slash_defer_duration(0)
.local_queue()
.build()
.execute_with(|| {
let _ = roll_until_next_active(0);
let _ = roll_until_next_active(5);
let active_validators = roll_until_next_active(10);
assert_eq!(active_validators, vec![3, 5, 6, 8]);
assert_eq!(Rotator::<Runtime>::active_era(), 3);
// flush the events.
let _ = staking_events_since_last_call();
// report an offence for the session belonging to the previous era
assert_eq!(Rotator::<Runtime>::era_start_session_index(1), Some(5));
assert_ok!(rc_client::Pezpallet::<Runtime>::relay_new_offence_paged(
RuntimeOrigin::root(),
// offence is in era 1
vec![(
5,
rc_client::Offence {
offender: 3,
reporters: vec![],
slash_fraction: Perbill::from_percent(50),
}
)]
));
// reported
assert_eq!(
staking_events_since_last_call(),
vec![staking_async::Event::OffenceReported {
offence_era: 1,
validator: 3,
fraction: Perbill::from_percent(50)
}]
);
// applied right away
roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
staking_async::Event::SlashComputed {
offence_era: 1,
slash_era: 1,
offender: 3,
page: 0
},
staking_async::Event::Slashed { staker: 3, amount: 50 }
]
);
// nothing left
roll_next();
assert_eq!(staking_events_since_last_call(), vec![]);
});
}