use super::*;
use crate::{session_rotation::Eras, slashing};
use pallet_staking_async_rc_client as rc_client;
use sp_runtime::{Perquintill, Rounding};
use sp_staking::StakingInterface;
use std::collections::BTreeMap;
#[test]
fn nominators_also_get_slashed_pro_rata() {
ExtBuilder::default()
.validator_count(4)
.set_status(41, StakerStatus::Validator)
.build_and_execute(|| {
let initial_exposure = Staking::eras_stakers(active_era(), &11);
assert_eq!(
initial_exposure,
Exposure {
total: 1250,
own: 1000,
others: vec![IndividualExposure { who: 101, value: 250 }]
}
);
let nominator_stake = Staking::ledger(101.into()).unwrap().active;
let nominator_balance = asset::stakeable_balance::<Test>(&101);
let validator_stake = Staking::ledger(11.into()).unwrap().active;
let validator_balance = asset::stakeable_balance::<Test>(&11);
let exposed_stake = initial_exposure.total;
let exposed_validator = initial_exposure.own;
let exposed_nominator = initial_exposure.others.first().unwrap().value;
add_slash(11);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
}]
);
assert_eq!(SlashDeferDuration::get(), 0);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 25 }
]
);
assert!(Staking::ledger(101.into()).unwrap().active < nominator_stake);
assert!(Staking::ledger(11.into()).unwrap().active < validator_stake);
let slash_amount = Perbill::from_percent(10) * exposed_stake;
let validator_share =
Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount;
let nominator_share =
Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount;
assert!(validator_share > 0);
assert!(nominator_share > 0);
assert_eq!(
Staking::ledger(101.into()).unwrap().active,
nominator_stake - nominator_share
);
assert_eq!(
Staking::ledger(11.into()).unwrap().active,
validator_stake - validator_share
);
assert_eq!(
asset::stakeable_balance::<Test>(&101), nominator_balance - nominator_share,
);
assert_eq!(
asset::stakeable_balance::<Test>(&11), validator_balance - validator_share,
);
});
}
#[test]
fn slashing_performed_according_exposure() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
assert_eq!(Staking::eras_stakers(active_era(), &11).own, 1000);
add_slash_with_percent(11, 50);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(50)
}]
);
assert_eq!(SlashDeferDuration::get(), 0);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 500 },
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000 / 2);
});
}
#[test]
fn offence_doesnt_force_new_era() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(ForceEra::<T>::get(), Forcing::NotForcing);
add_slash(11);
assert_eq!(ForceEra::<T>::get(), Forcing::NotForcing);
});
}
#[test]
fn offence_ensures_new_era_without_clobbering() {
ExtBuilder::default().build_and_execute(|| {
assert_ok!(Staking::force_new_era_always(RuntimeOrigin::root()));
assert_eq!(ForceEra::<T>::get(), Forcing::ForceAlways);
add_slash(11);
assert_eq!(ForceEra::<T>::get(), Forcing::ForceAlways);
});
}
#[test]
fn add_slash_works() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
assert_eq_uvec!(session_validators(), vec![11, 21]);
add_slash(11);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 100 },
]
);
assert!(Validators::<T>::contains_key(11));
});
}
#[test]
fn only_first_reporter_receive_the_slice() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
assert_eq!(Staking::eras_stakers(active_era(), &11).total, 1000);
let initial_balance_1 = asset::total_balance::<T>(&1);
let initial_balance_2 = asset::total_balance::<T>(&2);
<Staking as rc_client::AHStakingInterface>::on_new_offences(
session_mock::Session::current_index(),
vec![rc_client::Offence {
offender: 11,
reporters: vec![1, 2],
slash_fraction: Perbill::from_percent(50),
}],
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(50)
},
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 500 },
]
);
let reward = 500 / 10;
assert_eq!(asset::total_balance::<T>(&1), initial_balance_1 + reward);
assert_eq!(asset::total_balance::<T>(&2), initial_balance_2);
});
}
#[test]
fn subsequent_reports_pay_out_reward_based_on_net_slash() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
let initial_balance = 1000;
assert_eq!(Staking::eras_stakers(active_era(), &11).total, initial_balance);
let initial_balance_1 = asset::total_balance::<T>(&1);
<Staking as rc_client::AHStakingInterface>::on_new_offences(
session_mock::Session::current_index(),
vec![rc_client::Offence {
offender: 11,
reporters: vec![1],
slash_fraction: Perbill::from_percent(20),
}],
);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(20)
}]
);
Session::roll_next();
let slash = Perbill::from_percent(20) * initial_balance;
let reward = SlashRewardFraction::<T>::get() * slash;
assert_eq!(slash, 200);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: slash },
]
);
assert_eq!(reward, 20);
assert_eq!(asset::total_balance::<T>(&1), initial_balance_1 + reward);
<Staking as rc_client::AHStakingInterface>::on_new_offences(
session_mock::Session::current_index(),
vec![rc_client::Offence {
offender: 11,
reporters: vec![1],
slash_fraction: Perbill::from_percent(50),
}],
);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(50)
}]
);
Session::roll_next();
let prior_slash = slash;
let prior_reward = reward;
let slash = Perbill::from_percent(50) * initial_balance - prior_slash;
assert_eq!(slash, 300);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: slash },
]
);
let reward = SlashRewardFraction::<T>::get() * slash;
assert_eq!(reward, 30);
assert_eq!(asset::total_balance::<T>(&1), initial_balance_1 + prior_reward + reward);
});
}
#[test]
fn deferred_slashes_are_deferred() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
let exposure = Staking::eras_stakers(active_era(), &11);
let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value;
assert_eq!(Eras::<T>::exposure_page_count(1, &11), 1);
add_slash(11);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
}]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 },]
);
assert_eq!(Nominators::<T>::get(101).unwrap().targets, vec![11, 21]);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_until_active_era(2);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 },
Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_until_active_era(3);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 },
Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_next();
assert_eq!(asset::stakeable_balance::<T>(&11), 900);
assert_eq!(asset::stakeable_balance::<T>(&101), 500 - (nominated_value / 10));
assert_eq!(
staking_events_since_last_call(),
vec![
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 25 }
]
);
})
}
#[test]
fn retroactive_deferred_slashes_two_eras_before() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(BondingDuration::get(), 3);
assert_eq!(Nominators::<T>::get(101).unwrap().targets, vec![11, 21]);
Session::roll_until_active_era(2);
let _ = staking_events_since_last_call();
add_slash_in_era(11, 1, Perbill::from_percent(10));
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
}]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 }]
);
Session::roll_until_active_era(3);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 },
Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 }
]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 25 }
]
);
})
}
#[test]
fn retroactive_deferred_slashes_one_before() {
ExtBuilder::default()
.slash_defer_duration(2)
.nominate(false)
.build_and_execute(|| {
assert_eq!(BondingDuration::get(), 3);
Session::roll_until_active_era(2);
assert_ok!(Staking::chill(RuntimeOrigin::signed(11)));
assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 100));
Session::roll_until_active_era(3);
let _ = staking_events_since_last_call();
add_slash_in_era(11, 2, Perbill::from_percent(10));
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(10)
}]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed { offence_era: 2, slash_era: 4, offender: 11, page: 0 }]
);
Session::roll_until_active_era(4);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::SessionRotated { starting_session: 10, active_era: 3, planned_era: 4 },
Event::PagedElectionProceeded { page: 0, result: Ok(2) },
Event::SessionRotated { starting_session: 11, active_era: 3, planned_era: 4 },
Event::EraPaid { era_index: 3, validator_payout: 7500, remainder: 7500 },
Event::SessionRotated { starting_session: 12, active_era: 4, planned_era: 4 }
]
);
assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::Slashed { staker: 11, amount: 100 }]
);
assert_eq!(Staking::ledger(11.into()).unwrap().total, 900);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000));
assert_eq!(Staking::ledger(11.into()).unwrap().total, 900);
})
}
#[test]
fn dont_slash_if_fraction_is_zero() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
add_slash_with_percent(11, 0);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported { offence_era: 1, validator: 11, fraction: Zero::zero() }]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(ForceEra::<T>::get(), Forcing::NotForcing);
});
}
#[test]
fn only_slash_validator_for_max_in_era() {
ExtBuilder::default().nominate(false).build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
add_slash_with_percent(11, 50);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(50)
},
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 500 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 500);
add_slash_with_percent(11, 25);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(25)
},]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 500);
add_slash_with_percent(11, 60);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(60)
},
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: 100 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 400);
})
}
#[test]
fn really_old_offences_are_ignored() {
ExtBuilder::default()
.slash_defer_duration(27)
.bonding_duration(28)
.build_and_execute(|| {
Session::roll_until_active_era(100);
let expected_oldest_reportable_offence = active_era() - (SlashDeferDuration::get() - 1);
assert_eq!(expected_oldest_reportable_offence, 74);
staking_events_since_last_call();
add_slash_in_era(11, 72, Perbill::from_percent(10));
add_slash_in_era(21, 73, Perbill::from_percent(10));
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceTooOld {
offence_era: 72,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::OffenceTooOld {
offence_era: 73,
validator: 21,
fraction: Perbill::from_percent(10)
},
]
);
assert!(OffenceQueue::<Test>::iter_prefix(72).next().is_none());
assert!(OffenceQueue::<Test>::iter_prefix(73).next().is_none());
assert!(!OffenceQueueEras::<Test>::get().unwrap_or_default().contains(&72));
assert!(!OffenceQueueEras::<Test>::get().unwrap_or_default().contains(&73));
add_slash_in_era(11, 74, Perbill::from_percent(10));
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 74,
validator: 11,
fraction: Perbill::from_percent(10)
}]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed {
offence_era: 74,
slash_era: 101,
offender: 11,
page: 0
},]
);
Session::roll_until_active_era(101);
staking_events_since_last_call();
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 25 }
]
);
});
}
#[test]
fn nominator_is_slashed_by_max_for_validator_in_era() {
ExtBuilder::default().build_and_execute(|| {
Session::roll_until_active_era(3);
let validator_one = 11;
let validator_two = 21;
let nominator = 101;
assert_eq!(asset::stakeable_balance::<T>(&validator_one), 1000);
assert_eq!(asset::stakeable_balance::<T>(&validator_two), 1000);
assert_eq!(asset::stakeable_balance::<T>(&nominator), 500);
assert_eq!(Staking::slashable_balance_of(&validator_two), 1000);
let exposure_v1 = Staking::eras_stakers(active_era(), &11);
let exposure_v2 = Staking::eras_stakers(active_era(), &21);
let nominated_value_v1 = exposure_v1.others.iter().find(|o| o.who == 101).unwrap().value;
let nominated_value_v2 = exposure_v2.others.iter().find(|o| o.who == 101).unwrap().value;
staking_events_since_last_call();
let slash_era = 2;
add_slash_in_era(validator_one, slash_era, Perbill::from_percent(10));
Session::roll_next();
let slash_v1_amount = Perbill::from_percent(10) * 1000u128;
assert_eq!(slash_v1_amount, 100);
let first_slash_nominator_amount = Perbill::from_percent(10) * nominated_value_v1;
assert_eq!(first_slash_nominator_amount, 25);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: slash_era,
validator: validator_one,
fraction: Perbill::from_percent(10)
},
Event::SlashComputed {
offence_era: slash_era,
slash_era,
offender: validator_one,
page: 0
},
Event::Slashed { staker: validator_one, amount: slash_v1_amount },
Event::Slashed { staker: nominator, amount: first_slash_nominator_amount }
]
);
assert_eq!(asset::stakeable_balance::<T>(&validator_one), 1000 - slash_v1_amount);
assert_eq!(asset::stakeable_balance::<T>(&101), 500 - first_slash_nominator_amount);
add_slash_in_era(validator_two, slash_era, Perbill::from_percent(30));
Session::roll_next();
let slash_v2_amount = Perbill::from_percent(30) * 1000u128;
assert_eq!(slash_v2_amount, 300);
let second_slash_nominator_amount = Perbill::from_percent(30) * nominated_value_v2;
assert_eq!(second_slash_nominator_amount, 75);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: slash_era,
validator: validator_two,
fraction: Perbill::from_percent(30)
},
Event::SlashComputed {
offence_era: slash_era,
slash_era,
offender: validator_two,
page: 0
},
Event::Slashed { staker: validator_two, amount: slash_v2_amount },
Event::Slashed { staker: nominator, amount: second_slash_nominator_amount }
]
);
assert_eq!(asset::stakeable_balance::<T>(&validator_one), 900);
let v2_stakeable = asset::stakeable_balance::<T>(&validator_two);
assert_eq!(v2_stakeable, 1000 - slash_v2_amount);
let nominator_slashable_balance = Staking::slashable_balance_of(&101);
assert_eq!(
nominator_slashable_balance,
500 - first_slash_nominator_amount - second_slash_nominator_amount
);
add_slash_in_era(validator_one, slash_era, Perbill::from_percent(20));
Session::roll_next();
let third_slash_nominator_amount = Perbill::from_percent(10) * nominated_value_v1;
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: slash_era,
validator: validator_one,
fraction: Perbill::from_percent(20),
},
Event::SlashComputed {
offence_era: slash_era,
slash_era,
offender: validator_one,
page: 0
},
Event::Slashed {
staker: validator_one,
amount: Perbill::from_percent(10) * 1000u128, },
Event::Slashed {
staker: nominator,
amount: third_slash_nominator_amount,
},
]
);
assert_eq!(
asset::stakeable_balance::<T>(&validator_one),
1000 - slash_v1_amount - (Perbill::from_percent(10) * 1000u128)
);
assert_eq!(
asset::stakeable_balance::<T>(&nominator),
500 - first_slash_nominator_amount -
second_slash_nominator_amount -
third_slash_nominator_amount
);
assert_eq!(asset::stakeable_balance::<T>(&21), v2_stakeable);
});
}
#[test]
fn fully_slashed_account_can_be_reaped() {
ExtBuilder::default()
.existential_deposit(2)
.balance_factor(2)
.build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 2000);
add_slash_with_percent(11, 10);
Session::roll_next();
assert_eq!(asset::stakeable_balance::<T>(&11), 2000 - 200);
add_slash_with_percent(11, 100);
Session::roll_next();
assert_eq!(asset::stakeable_balance::<T>(&11), 0);
assert_eq!(asset::total_balance::<T>(&11), ExistentialDeposit::get());
assert_ok!(Staking::reap_stash(RuntimeOrigin::signed(20), 11, 0));
})
}
#[test]
fn garbage_collection_on_window_pruning() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
let now = active_era();
let exposure = Staking::eras_stakers(now, &11);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value;
add_slash(11);
Session::roll_next();
assert_eq!(asset::stakeable_balance::<T>(&11), 900);
assert_eq!(asset::stakeable_balance::<T>(&101), 500 - (nominated_value / 10));
assert!(ValidatorSlashInEra::<T>::get(&now, &11).is_some());
for era in (0..(HistoryDepth::get() + 1)).map(|offset| offset + now + 1) {
assert!(ValidatorSlashInEra::<T>::get(&now, &11).is_some());
Session::roll_until_active_era(era);
}
let prune_era = now;
while EraPruningState::<T>::get(prune_era).is_some() {
assert_ok!(Staking::prune_era_step(RuntimeOrigin::signed(10), prune_era));
}
assert!(ValidatorSlashInEra::<T>::get(&now, &11).is_none());
})
}
#[test]
fn staker_cannot_bail_deferred_slash() {
ExtBuilder::default()
.slash_defer_duration(2)
.bonding_duration(3)
.build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
add_slash(11);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 }
]
);
assert_ok!(Staking::chill(RuntimeOrigin::signed(101)));
assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 500));
assert_eq!(CurrentEra::<T>::get().unwrap(), 1);
assert_eq!(active_era(), 1);
assert_eq!(
Ledger::<T>::get(101).unwrap(),
StakingLedgerInspect {
active: 0,
total: 500,
stash: 101,
unlocking: bounded_vec![UnlockChunk { era: 4u32, value: 500 }],
}
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_until_active_era(2);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_until_active_era(3);
let _ = staking_events_since_last_call();
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
assert_eq!(CurrentEra::<T>::get().unwrap(), 3);
assert_eq!(active_era(), 3);
assert_storage_noop!(assert!(Staking::withdraw_unbonded(
RuntimeOrigin::signed(101),
0
)
.is_ok()));
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 25 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 900);
assert_eq!(asset::stakeable_balance::<T>(&101), 500 - 25);
})
}
#[test]
fn remove_deferred() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
add_slash(11);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 }
]
);
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
Session::roll_until_active_era(2);
let _ = staking_events_since_last_call();
add_slash_in_era(11, 1, Perbill::from_percent(15));
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(15)
},
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 }
]
);
assert_eq!(
UnappliedSlashes::<T>::iter_prefix(&3).collect::<Vec<_>>(),
vec![
(
(11, Perbill::from_percent(10), 0),
UnappliedSlash {
validator: 11,
own: 100,
others: bounded_vec![(101, 25)],
reporter: None,
payout: (100 + 25) / 10
}
),
(
(11, Perbill::from_percent(15), 0),
UnappliedSlash {
validator: 11,
own: 50,
others: bounded_vec![(101, 12)],
reporter: None,
payout: (50 + 12) / 10
}
),
]
);
assert_noop!(
Staking::cancel_deferred_slash(RuntimeOrigin::root(), 3, vec![]),
Error::<T>::EmptyTargets
);
assert_ok!(Staking::cancel_deferred_slash(
RuntimeOrigin::root(),
3,
vec![(11, Perbill::from_percent(12))],
));
assert_eq!(CancelledSlashes::<T>::get(&3), vec![(11, Perbill::from_percent(12))]);
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashCancelled { slash_era: 3, validator: 11 }]
);
Session::roll_until_active_era(3);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 2);
let _ = staking_events_since_last_call();
Session::roll_next();
assert_eq!(staking_events_since_last_call(), vec![]);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 1);
Session::roll_next();
let events = staking_events_since_last_call();
assert_eq!(events.len(), 2);
assert_eq!(events[0], Event::Slashed { staker: 11, amount: 50 });
assert_eq!(events[1], Event::Slashed { staker: 101, amount: 12 });
assert_eq!(asset::stakeable_balance::<T>(&11), 950);
assert_eq!(asset::stakeable_balance::<T>(&101), 488);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 0);
assert_eq!(CancelledSlashes::<T>::get(&3), vec![]);
})
}
#[test]
fn remove_multi_deferred() {
ExtBuilder::default()
.slash_defer_duration(2)
.validator_count(4)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
add_slash_with_percent(11, 10);
add_slash_with_percent(21, 10);
add_slash_with_percent(41, 25);
Session::roll_next();
Session::roll_next();
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 1,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::OffenceReported {
offence_era: 1,
validator: 21,
fraction: Perbill::from_percent(10)
},
Event::OffenceReported {
offence_era: 1,
validator: 41,
fraction: Perbill::from_percent(25)
},
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 41, page: 0 },
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 21, page: 0 },
Event::SlashComputed { offence_era: 1, slash_era: 3, offender: 11, page: 0 },
]
);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 3);
assert_ok!(Staking::cancel_deferred_slash(
RuntimeOrigin::root(),
3,
vec![(11, Perbill::from_percent(10)), (21, Perbill::from_percent(10))]
));
let cancelled = CancelledSlashes::<T>::get(&3);
assert_eq!(cancelled.len(), 2);
assert!(cancelled.contains(&(11, Perbill::from_percent(10))));
assert!(cancelled.contains(&(21, Perbill::from_percent(10))));
let slashes = UnappliedSlashes::<T>::iter_prefix(&3).collect::<Vec<_>>();
assert_eq!(slashes.len(), 3);
Session::roll_until_active_era(3);
let _ = staking_events_since_last_call();
let mut slash_events = vec![];
for _ in 0..3 {
Session::roll_next();
let events = staking_events_since_last_call();
for event in events {
if let Event::Slashed { .. } = event {
slash_events.push(event);
}
}
}
assert_eq!(slash_events.len(), 1);
assert_eq!(slash_events[0], Event::Slashed { staker: 41, amount: 1000 });
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 0);
assert_eq!(CancelledSlashes::<T>::get(&3), vec![]);
})
}
#[test]
fn cancel_all_slashes_with_100_percent() {
ExtBuilder::default()
.validator_count(4)
.slash_defer_duration(2)
.build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
assert_eq!(active_era(), 1);
for i in 1..=10 {
add_slash_with_percent(11, i * 5);
Session::roll_next();
}
let events = staking_events_since_last_call();
let reported_count =
events.iter().filter(|e| matches!(e, Event::OffenceReported { .. })).count();
let computed_count =
events.iter().filter(|e| matches!(e, Event::SlashComputed { .. })).count();
assert_eq!(reported_count, 10);
assert_eq!(computed_count, 10);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 10);
assert_ok!(Staking::cancel_deferred_slash(
RuntimeOrigin::root(),
3,
vec![(11, Perbill::from_percent(100))],
));
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashCancelled { slash_era: 3, validator: 11 }]
);
let cancelled = CancelledSlashes::<T>::get(&3);
assert_eq!(cancelled.len(), 1);
assert_eq!(cancelled[0], (11, Perbill::from_percent(100)));
Session::roll_until_active_era(3);
let _ = staking_events_since_last_call();
for _ in 0..10 {
Session::roll_next();
let events = staking_events_since_last_call();
let slash_events: Vec<_> =
events.iter().filter(|e| matches!(e, Event::Slashed { .. })).collect();
assert_eq!(slash_events.len(), 0);
}
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
assert_eq!(asset::stakeable_balance::<T>(&101), 500);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&3).count(), 0);
assert_eq!(CancelledSlashes::<T>::get(&3), vec![]);
})
}
#[test]
fn apply_slash_rejects_cancelled_slashes() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
add_slash(11);
Session::roll_next();
let _ = staking_events_since_last_call();
assert_eq!(active_era(), 1);
let slash_era = 3;
let slash_key = (11, Perbill::from_percent(10), 0);
assert!(UnappliedSlashes::<T>::contains_key(&slash_era, &slash_key));
assert_ok!(Staking::cancel_deferred_slash(
RuntimeOrigin::root(),
slash_era,
vec![(11, Perbill::from_percent(10))],
));
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashCancelled { slash_era, validator: 11 }]
);
let cancelled = CancelledSlashes::<T>::get(&slash_era);
assert_eq!(cancelled, vec![(11, Perbill::from_percent(10))]);
Session::roll_until_active_era(3);
assert_noop!(
Staking::apply_slash(RuntimeOrigin::signed(1), slash_era, slash_key),
Error::<T>::CancelledSlash
);
assert!(UnappliedSlashes::<T>::contains_key(&slash_era, &slash_key));
assert_eq!(asset::stakeable_balance::<T>(&11), 1000);
})
}
#[test]
fn proportional_slash_stop_slashing_if_remaining_zero() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
let c = |era, value| UnlockChunk::<Balance> { era, value };
let unlocking = bounded_vec![c(1, 10), c(2, 10)];
let mut ledger = StakingLedger::<T>::new(123, 20);
ledger.total = 40;
ledger.unlocking = unlocking;
assert_eq!(BondingDuration::get(), 3);
assert_eq!(ledger.slash(18, 1, 0), 18);
});
}
#[test]
fn proportional_ledger_slash_works() {
ExtBuilder::default().nominate(true).build_and_execute(|| {
let c = |era, value| UnlockChunk::<Balance> { era, value };
let mut ledger = StakingLedger::<T>::new(123, 10);
assert_eq!(BondingDuration::get(), 3);
assert_eq!(ledger.slash(5, 1, 0), 5);
assert_eq!(ledger.total, 5);
assert_eq!(ledger.active, 5);
assert_eq!(LedgerSlashPerEra::get().0, 5);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
assert_eq!(ledger.slash(11, 1, 0), 5);
assert_eq!(ledger.total, 0);
assert_eq!(ledger.active, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10)];
ledger.total = 2 * 10;
ledger.active = 0;
assert_eq!(ledger.slash(20, 0, 0), 20);
assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0)]));
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.total = 4 * 100;
ledger.active = 0;
assert_eq!(ledger.slash(140, 0, 3), 140);
assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 30), c(7, 30)]);
assert_eq!(ledger.total, 4 * 100 - 140);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 30), (7, 30)]));
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.total = 4 * 100;
ledger.active = 0;
assert_eq!(ledger.slash(15, 0, 3), 15);
assert_eq!(ledger.unlocking, vec![c(4, 100), c(5, 100), c(6, 100 - 8), c(7, 100 - 7)]);
assert_eq!(ledger.total, 4 * 100 - 15);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(6, 92), (7, 93)]));
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
ledger.total = 40 + 10 + 100 + 250 + 500;
assert_eq!(ledger.slash(900 / 2, 0, 0), 450);
assert_eq!(ledger.active, 500 / 2);
assert_eq!(
ledger.unlocking,
vec![c(4, 40 / 2), c(5, 100 / 2), c(6, 10 / 2), c(7, 250 / 2)]
);
assert_eq!(ledger.total, 900 / 2);
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 40 / 2), (5, 100 / 2), (6, 10 / 2), (7, 250 / 2)])
);
ledger.unlocking = bounded_vec![];
ledger.active = 500;
ledger.total = 500;
assert_eq!(ledger.slash(500 / 4, 0, 0), 500 / 4);
assert_eq!(ledger.active, 3 * 500 / 4);
assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, ledger.active);
assert_eq!(LedgerSlashPerEra::get().0, 3 * 500 / 4);
assert_eq!(LedgerSlashPerEra::get().1, Default::default());
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
ledger.total = 40 + 10 + 100 + 250 + 500; assert_eq!(ledger.total, 900);
assert_eq!(
ledger.slash(
900 / 2,
25,
0
),
450
);
assert_eq!(ledger.active, 500 / 2);
assert_eq!(ledger.unlocking, vec![c(5, 100 / 2), c(7, 150)]);
assert_eq!(ledger.total, 900 / 2);
assert_eq!(LedgerSlashPerEra::get().0, 500 / 2);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 0), (5, 100 / 2), (6, 0), (7, 150)])
);
ledger.unlocking = bounded_vec![c(4, 40), c(5, 100), c(6, 10), c(7, 250)];
ledger.active = 500;
ledger.total = 40 + 10 + 100 + 250 + 500; assert_eq!(
ledger.slash(
500 + 10 + 250 + 100 / 2, 0,
3
),
500 + 250 + 10 + 100 / 2
);
assert_eq!(ledger.active, 0);
assert_eq!(ledger.unlocking, vec![c(4, 40), c(5, 100 / 2)]);
assert_eq!(ledger.total, 90);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 100 / 2), (6, 0), (7, 0)]));
ledger.unlocking = bounded_vec![c(4, 100), c(5, 100), c(6, 100), c(7, 100)];
ledger.active = 100;
ledger.total = 5 * 100;
assert_eq!(
ledger.slash(
351, 50, 3
),
400
);
assert_eq!(ledger.active, 0);
assert_eq!(ledger.unlocking, vec![c(4, 100)]);
assert_eq!(ledger.total, 100);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(5, 0), (6, 0), (7, 0)]));
let slash = u64::MAX as Balance * 2;
let value = slash - (10 * 4);
ledger.active = 10;
ledger.unlocking = bounded_vec![c(4, 10), c(5, 10), c(6, 10), c(7, value)];
ledger.total = value + 40;
let slash_amount = ledger.slash(slash, 0, 0);
assert_eq_error_rate!(slash_amount, slash, 5);
assert_eq!(ledger.active, 0); assert_eq!(ledger.unlocking, vec![]);
assert_eq!(ledger.total, 0);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(LedgerSlashPerEra::get().1, BTreeMap::from([(4, 0), (5, 0), (6, 0), (7, 0)]));
use sp_runtime::PerThing as _;
let slash = u64::MAX as Balance * 2;
let value = u64::MAX as Balance * 2;
let unit = 100;
assert!(slash.checked_mul(value).is_none());
assert!(slash.checked_mul(unit).is_some());
ledger.unlocking = bounded_vec![c(4, unit), c(5, value), c(6, unit), c(7, unit)];
ledger.active = unit;
ledger.total = unit * 4 + value;
assert_eq!(ledger.slash(slash, 0, 0), slash);
let affected_balance = value + unit * 4;
let ratio = Perquintill::from_rational_with_rounding(slash, affected_balance, Rounding::Up)
.unwrap();
let unit_slashed = {
let unit_slash = ratio.mul_ceil(unit);
unit - unit_slash
};
let value_slashed = {
let value_slash = ratio.mul_ceil(value);
value - value_slash
};
assert_eq!(ledger.active, unit_slashed);
assert_eq!(ledger.unlocking, vec![c(5, value_slashed), c(7, 32)]);
assert_eq!(ledger.total, value_slashed + 32);
assert_eq!(LedgerSlashPerEra::get().0, 0);
assert_eq!(
LedgerSlashPerEra::get().1,
BTreeMap::from([(4, 0), (5, value_slashed), (6, 0), (7, 32)])
);
});
}
#[test]
fn withdrawals_are_blocked_for_unprocessed_and_unapplied_slashes() {
ExtBuilder::default()
.slash_defer_duration(2)
.bonding_duration(3)
.add_staker(61, 1000, StakerStatus::Validator)
.add_staker(71, 1000, StakerStatus::Validator)
.add_staker(81, 1000, StakerStatus::Validator)
.add_staker(91, 1000, StakerStatus::Validator)
.session_per_era(1)
.period(1)
.validator_count(6)
.build_and_execute(|| {
let _expected_era_length = 2;
let validator = 11;
let nominator = 301;
bond_nominator(nominator, 500, vec![validator]);
Session::roll_until_active_era(2);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(nominator), 100));
Session::roll_until_active_era(3);
assert_ok!(Staking::unbond(RuntimeOrigin::signed(nominator), 150));
Session::roll_until_active_era(4);
Session::roll_next();
add_slash_in_era(21, 3, Perbill::from_percent(10));
add_slash_in_era(61, 3, Perbill::from_percent(10));
add_slash_in_era(71, 3, Perbill::from_percent(10));
add_slash_in_era(81, 3, Perbill::from_percent(10));
add_slash_in_era(91, 3, Perbill::from_percent(10));
Session::roll_until_active_era(6);
assert_eq!(active_era(), 6);
let expected_chunks: BoundedVec<UnlockChunk<Balance>, MaxUnlockingChunks> = bounded_vec![
UnlockChunk { era: 5, value: 100 },
UnlockChunk { era: 6, value: 150 },
];
assert_eq!(Ledger::<T>::get(nominator).unwrap().unlocking, expected_chunks);
assert_eq!(era_unprocessed_offence_count(3), 5 - 3);
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![3]);
let nominator_balance_pre_withdraw = Balances::free_balance(&nominator);
assert_eq!(nominator_balance_pre_withdraw, 1);
assert_eq!(era_unapplied_slash_count(5), 1);
assert_noop!(
Staking::withdraw_unbonded(RuntimeOrigin::signed(nominator), 0),
Error::<T>::UnappliedSlashesInPreviousEra
);
apply_pending_slashes_from_previous_era();
assert_eq!(era_unapplied_slash_count(5), 0);
assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(nominator), 0));
let nominator_balance_post_withdraw_1 = Balances::free_balance(&nominator);
assert_eq!(nominator_balance_post_withdraw_1, nominator_balance_pre_withdraw + 100);
Session::roll_next();
assert_eq!(era_unapplied_slash_count(5), 1);
apply_pending_slashes_from_previous_era();
assert_eq!(era_unprocessed_offence_count(3), 1);
assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(nominator), 0));
assert_eq!(Balances::free_balance(&nominator), nominator_balance_post_withdraw_1);
Session::roll_next();
assert_eq!(active_era(), 7);
assert_eq!(era_unapplied_slash_count(5), 1);
assert_eq!(era_unapplied_slash_count(6), 0);
assert_eq!(era_unprocessed_offence_count(3), 0);
assert_eq!(OffenceQueueEras::<T>::get(), None);
assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(nominator), 0));
assert_eq!(Balances::free_balance(&nominator), nominator_balance_post_withdraw_1 + 150);
apply_pending_slashes_from_era(5);
});
}
mod paged_slashing {
use super::*;
use crate::slashing::OffenceRecord;
#[test]
fn offence_processed_in_multi_block() {
ExtBuilder::default()
.has_stakers(false)
.slash_defer_duration(3)
.build_and_execute(|| {
let base_stake = 1000;
bond_validator(11, base_stake);
assert_eq!(Validators::<T>::count(), 1);
let mut exposure_counter = base_stake;
let expected_page_count = 4;
for i in 0..200 {
let bond_amount = base_stake + i as Balance;
bond_nominator(1000 + i, bond_amount, vec![11]);
exposure_counter += bond_amount;
}
Session::roll_until_active_era(2);
let _ = staking_events_since_last_call();
assert_eq!(
ErasStakersOverview::<T>::get(2, 11).expect("exposure should exist"),
PagedExposureMetadata {
total: exposure_counter,
own: base_stake,
page_count: expected_page_count,
nominator_count: 200,
}
);
add_slash(11);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
validator: 11,
fraction: Perbill::from_percent(10),
offence_era: 2
}]
);
assert_eq!(
OffenceQueue::<T>::get(2, 11).unwrap(),
slashing::OffenceRecord {
reporter: None,
reported_era: 2,
exposure_page: expected_page_count - 1,
slash_fraction: Perbill::from_percent(10),
prior_slash_fraction: Perbill::zero(),
}
);
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![2]);
assert_eq!(ProcessingOffence::<T>::get(), None);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 0);
Session::roll_next();
assert_eq!(
staking_events_since_last_call().as_slice(),
vec![Event::SlashComputed {
offence_era: 2,
slash_era: 5,
offender: 11,
page: expected_page_count - 1
},]
);
assert_eq!(OffenceQueue::<T>::get(2, 11), None);
assert_eq!(OffenceQueueEras::<T>::get(), None);
assert_eq!(
ProcessingOffence::<T>::get(),
Some((
2,
11,
OffenceRecord {
reporter: None,
reported_era: 2,
exposure_page: 2,
slash_fraction: Perbill::from_percent(10),
prior_slash_fraction: Perbill::zero(),
}
))
);
let slashes = UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>();
assert_eq!(slashes.len(), 1);
let (slash_key, unapplied_slash) = &slashes[0];
assert_eq!(*slash_key, (11, Perbill::from_percent(10), expected_page_count - 1));
assert_eq!(unapplied_slash.own, 0);
assert_eq!(unapplied_slash.validator, 11);
assert_eq!(unapplied_slash.others.len(), 200 % 64);
Session::roll_next();
assert_eq!(OffenceQueue::<T>::get(2, 11), None);
assert_eq!(OffenceQueueEras::<T>::get(), None);
assert_eq!(
ProcessingOffence::<T>::get(),
Some((
2,
11,
OffenceRecord {
reporter: None,
reported_era: 2,
exposure_page: 1,
slash_fraction: Perbill::from_percent(10),
prior_slash_fraction: Perbill::zero(),
}
))
);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 2);
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed {
offence_era: 2,
slash_era: 5,
offender: 11,
page: expected_page_count - 2
},]
);
Session::roll_next();
Session::roll_next();
assert!(ProcessingOffence::<T>::get().is_none());
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 4);
Session::roll_until_active_era(5);
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 4);
Session::roll_next();
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 3);
Session::roll_next();
Session::roll_next();
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 1);
Session::roll_next();
assert_eq!(UnappliedSlashes::<T>::iter_prefix(&5).collect::<Vec<_>>().len(), 0);
assert_eq!(asset::staked::<T>(&11), 1000 - 100);
for i in 0..200 {
let original_stake = 1000 + i as Balance;
let expected_slash = Perbill::from_percent(10) * original_stake;
assert_eq!(asset::staked::<T>(&(1000 + i)), original_stake - expected_slash);
}
})
}
#[test]
fn offence_discarded_correctly() {
ExtBuilder::default().slash_defer_duration(3).build_and_execute(|| {
Session::roll_until_active_era(2);
let _ = staking_events_since_last_call();
add_slash(11);
let queued_offence_one = OffenceQueue::<T>::get(2, 11).unwrap();
assert_eq!(queued_offence_one.slash_fraction, Perbill::from_percent(10));
assert_eq!(queued_offence_one.prior_slash_fraction, Perbill::zero());
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![2]);
add_slash_with_percent(11, 5);
assert_eq!(OffenceQueue::<T>::get(2, 11).unwrap(), queued_offence_one);
add_slash_with_percent(11, 15);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(10)
},
Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(5)
},
Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(15)
}
]
);
let overwritten_offence = OffenceQueue::<T>::get(2, 11).unwrap();
assert!(overwritten_offence.slash_fraction > queued_offence_one.slash_fraction);
assert_eq!(overwritten_offence.slash_fraction, Perbill::from_percent(15));
assert_eq!(overwritten_offence.prior_slash_fraction, Perbill::zero());
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![2]);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed { offence_era: 2, slash_era: 5, offender: 11, page: 0 }]
);
assert!(OffenceQueue::<T>::get(2, 11).is_none());
assert!(OffenceQueueEras::<T>::get().is_none());
assert!(UnappliedSlashes::<T>::contains_key(2 + 3, (11, Perbill::from_percent(15), 0)));
add_slash_with_percent(11, 14);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(14)
},]
);
assert!(OffenceQueue::<T>::get(2, 11).is_none());
assert!(OffenceQueueEras::<T>::get().is_none());
add_slash_with_percent(11, 16);
assert_eq!(
staking_events_since_last_call(),
vec![Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: Perbill::from_percent(16)
},]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call(),
vec![Event::SlashComputed { offence_era: 2, slash_era: 5, offender: 11, page: 0 }]
);
let slash_one =
UnappliedSlashes::<T>::get(2 + 3, (11, Perbill::from_percent(15), 0)).unwrap();
let slash_two =
UnappliedSlashes::<T>::get(2 + 3, (11, Perbill::from_percent(16), 0)).unwrap();
assert!(slash_one.own > slash_two.own);
});
}
#[test]
fn offence_eras_queued_correctly() {
ExtBuilder::default().build_and_execute(|| {
assert_eq!(Staking::status(&11).unwrap(), StakerStatus::Validator);
assert_eq!(Staking::status(&21).unwrap(), StakerStatus::Validator);
Session::roll_until_active_era(2);
add_slash_in_era(11, 2, Perbill::from_percent(10));
add_slash_in_era(21, 2, Perbill::from_percent(10));
add_slash_in_era(11, 1, Perbill::from_percent(10));
add_slash_in_era(21, 1, Perbill::from_percent(10));
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![1, 2]);
Session::roll_next();
Session::roll_next();
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![2]);
Session::roll_next();
assert_eq!(OffenceQueueEras::<T>::get().unwrap(), vec![2]);
Session::roll_next();
assert_eq!(OffenceQueueEras::<T>::get(), None);
});
}
#[test]
fn non_deferred_slash_applied_instantly() {
ExtBuilder::default().build_and_execute(|| {
Session::roll_until_active_era(2);
let validator_stake = asset::staked::<T>(&11);
let slash_fraction = Perbill::from_percent(10);
let expected_slash = slash_fraction * validator_stake;
let _ = staking_events_since_last_call();
add_slash_in_era(11, 1, slash_fraction);
assert_eq!(
staking_events_since_last_call().as_slice(),
vec![Event::OffenceReported {
validator: 11,
fraction: Perbill::from_percent(10),
offence_era: 1
}]
);
Session::roll_next();
assert_eq!(
staking_events_since_last_call().as_slice(),
vec![
Event::SlashComputed { offence_era: 1, slash_era: 1, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: expected_slash },
Event::Slashed { staker: 101, amount: 25 },
]
);
assert_eq!(asset::staked::<T>(&11), validator_stake - expected_slash);
});
}
#[test]
fn validator_with_no_exposure_slashed() {
ExtBuilder::default().build_and_execute(|| {
let validator_stake = asset::staked::<T>(&11);
let slash_fraction = Perbill::from_percent(10);
let expected_slash = slash_fraction * validator_stake;
assert_ok!(Staking::nominate(RuntimeOrigin::signed(101), vec![21]));
Session::roll_until_active_era(2);
assert_eq!(ErasStakersOverview::<T>::get(2, 11).unwrap().page_count, 0,);
let _ = staking_events_since_last_call();
add_slash_with_percent(11, 10);
Session::roll_next();
assert_eq!(asset::staked::<T>(&11), validator_stake - expected_slash);
assert_eq!(
staking_events_since_last_call().as_slice(),
vec![
Event::OffenceReported {
offence_era: 2,
validator: 11,
fraction: slash_fraction
},
Event::SlashComputed { offence_era: 2, slash_era: 2, offender: 11, page: 0 },
Event::Slashed { staker: 11, amount: expected_slash },
]
);
});
}
}