Newer
Older
assert_eq!(Balances::free_balance(101), 2000);
// at the start of era 4, slashes from era 1 are processed,
// after being deferred for at least 2 full eras.
mock::start_active_era(4);
assert_eq!(Balances::free_balance(11), 900);
assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10));
assert!(matches!(
staking_events_since_last_call().as_slice(),
&[
Event::SlashReported { validator: 11, slash_era: 1, .. },
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 12 }
})
}
#[test]
fn retroactive_deferred_slashes_two_eras_before() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(BondingDuration::get(), 3);
mock::start_active_era(1);
let exposure_11_at_era1 = Staking::eras_stakers(active_era(), &11);
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
System::reset_events();
on_offence_in_era(
&[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }],
&[Perbill::from_percent(10)],
1, // should be deferred for two full eras, and applied at the beginning of era 4.
);
mock::start_active_era(4);
assert!(matches!(
staking_events_since_last_call().as_slice(),
&[
Event::SlashReported { validator: 11, slash_era: 1, .. },
..,
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 12 }
})
}
#[test]
fn retroactive_deferred_slashes_one_before() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
assert_eq!(BondingDuration::get(), 3);
mock::start_active_era(1);
let exposure_11_at_era1 = Staking::eras_stakers(active_era(), &11);
// unbond at slash era.
mock::start_active_era(2);
assert_ok!(Staking::chill(RuntimeOrigin::signed(11)));
assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 100));
System::reset_events();
on_offence_in_era(
&[OffenceDetails { offender: (11, exposure_11_at_era1), reporters: vec![] }],
&[Perbill::from_percent(10)],
2, // should be deferred for two full eras, and applied at the beginning of era 5.
);
mock::start_active_era(4);
assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000);
assert!(matches!(
staking_events_since_last_call().as_slice(),
&[
Event::SlashReported { validator: 11, slash_era: 2, .. },
..,
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 12 }
// their ledger has already been slashed.
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 staker_cannot_bail_deferred_slash() {
// as long as SlashDeferDuration is less than BondingDuration, this should not be possible.
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
mock::start_active_era(1);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
let exposure = Staking::eras_stakers(active_era(), &11);
let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value;
on_offence_now(
&[OffenceDetails {
offender: (11, Staking::eras_stakers(active_era(), &11)),
reporters: vec![],
}],
&[Perbill::from_percent(10)],
);
// now we chill
assert_ok!(Staking::chill(RuntimeOrigin::signed(101)));
assert_ok!(Staking::unbond(RuntimeOrigin::signed(101), 500));
assert_eq!(Staking::current_era().unwrap(), 1);
assert_eq!(active_era(), 1);
assert_eq!(
Ledger::<Test>::get(101).unwrap(),
active: 0,
total: 500,
stash: 101,
legacy_claimed_rewards: bounded_vec![],
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
unlocking: bounded_vec![UnlockChunk { era: 4u32, value: 500 }],
}
);
// no slash yet.
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
// no slash yet.
mock::start_active_era(2);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
assert_eq!(Staking::current_era().unwrap(), 2);
assert_eq!(active_era(), 2);
// no slash yet.
mock::start_active_era(3);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
assert_eq!(Staking::current_era().unwrap(), 3);
assert_eq!(active_era(), 3);
// and cannot yet unbond:
Staking::withdraw_unbonded(RuntimeOrigin::signed(101), 0).is_ok()
Ledger::<Test>::get(101).unwrap().unlocking.into_inner(),
vec![UnlockChunk { era: 4u32, value: 500 as Balance }],
);
// at the start of era 4, slashes from era 1 are processed,
// after being deferred for at least 2 full eras.
mock::start_active_era(4);
assert_eq!(Balances::free_balance(11), 900);
assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10));
// and the leftover of the funds can now be unbonded.
})
}
#[test]
fn remove_deferred() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
mock::start_active_era(1);
assert_eq!(Balances::free_balance(11), 1000);
let exposure = Staking::eras_stakers(active_era(), &11);
assert_eq!(Balances::free_balance(101), 2000);
let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value;
on_offence_now(
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(10)],
);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
mock::start_active_era(2);
// reported later, but deferred to start of era 4 as well.
System::reset_events();
on_offence_in_era(
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(15)],
1,
);
// fails if empty
assert_noop!(
Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![]),
Error::<Test>::EmptyTargets
);
assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0]));
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
mock::start_active_era(3);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
// at the start of era 4, slashes from era 1 are processed,
// after being deferred for at least 2 full eras.
mock::start_active_era(4);
// the first slash for 10% was cancelled, but the 15% one not.
assert!(matches!(
staking_events_since_last_call().as_slice(),
&[
Event::SlashReported { validator: 11, slash_era: 1, .. },
..,
Event::Slashed { staker: 11, amount: 50 },
Event::Slashed { staker: 101, amount: 7 }
let slash_10 = Perbill::from_percent(10);
let slash_15 = Perbill::from_percent(15);
let initial_slash = slash_10 * nominated_value;
let total_slash = slash_15 * nominated_value;
let actual_slash = total_slash - initial_slash;
// 5% slash (15 - 10) processed now.
assert_eq!(Balances::free_balance(11), 950);
assert_eq!(Balances::free_balance(101), 2000 - actual_slash);
})
}
#[test]
fn remove_multi_deferred() {
ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
mock::start_active_era(1);
assert_eq!(Balances::free_balance(11), 1000);
let exposure = Staking::eras_stakers(active_era(), &11);
assert_eq!(Balances::free_balance(101), 2000);
on_offence_now(
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(10)],
);
on_offence_now(
offender: (21, Staking::eras_stakers(active_era(), &21)),
reporters: vec![],
}],
&[Perbill::from_percent(10)],
);
on_offence_now(
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
&[OffenceDetails { offender: (42, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
on_offence_now(
&[OffenceDetails { offender: (69, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
assert_eq!(UnappliedSlashes::<Test>::get(&4).len(), 5);
// fails if list is not sorted
assert_noop!(
Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![2, 0, 4]),
Error::<Test>::NotSortedAndUnique
);
// fails if list is not unique
assert_noop!(
Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![0, 2, 2]),
Error::<Test>::NotSortedAndUnique
);
// fails if bad index
assert_noop!(
Staking::cancel_deferred_slash(RuntimeOrigin::root(), 1, vec![1, 2, 3, 4, 5]),
Error::<Test>::InvalidSlashIndex
);
assert_ok!(Staking::cancel_deferred_slash(RuntimeOrigin::root(), 4, vec![0, 2, 4]));
let slashes = UnappliedSlashes::<Test>::get(&4);
assert_eq!(slashes.len(), 2);
assert_eq!(slashes[0].validator, 21);
assert_eq!(slashes[1].validator, 42);
fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() {
ExtBuilder::default()
.validator_count(7)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.set_status(201, StakerStatus::Validator)
.set_status(202, StakerStatus::Validator)
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]);
// pre-slash balance
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
// 100 has approval for 11 as of now
assert!(Staking::nominators(101).unwrap().targets.contains(&11));
// 11 and 21 both have the support of 100
let exposure_11 = Staking::eras_stakers(active_era(), &11);
let exposure_21 = Staking::eras_stakers(active_era(), &21);
assert_eq!(exposure_11.total, 1000 + 125);
assert_eq!(exposure_21.total, 1000 + 375);
on_offence_now(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::from_percent(10)],
);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::StakersElected,
Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 },
Event::SlashReported {
validator: 11,
fraction: Perbill::from_percent(10),
slash_era: 1
},
Event::Slashed { staker: 11, amount: 100 },
Event::Slashed { staker: 101, amount: 12 },
]
);
// post-slash balance
let nominator_slash_amount_11 = 125 / 10;
assert_eq!(Balances::free_balance(11), 900);
assert_eq!(Balances::free_balance(101), 2000 - nominator_slash_amount_11);
// check that validator was disabled.
assert!(is_disabled(11));
// actually re-bond the slashed validator
assert_ok!(Staking::validate(RuntimeOrigin::signed(11), Default::default()));
mock::start_active_era(2);
let exposure_11 = Staking::eras_stakers(active_era(), &11);
let exposure_21 = Staking::eras_stakers(active_era(), &21);
// 11's own expo is reduced. sum of support from 11 is less (448), which is 500
// 900 + 146
assert!(matches!(exposure_11, Exposure { own: 900, total: 1046, .. }));
// 1000 + 342
assert!(matches!(exposure_21, Exposure { own: 1000, total: 1342, .. }));
assert_eq!(500 - 146 - 342, nominator_slash_amount_11);
});
}
André Silva
committed
#[test]
fn non_slashable_offence_disables_validator() {
ExtBuilder::default()
.validator_count(7)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.set_status(201, StakerStatus::Validator)
.set_status(202, StakerStatus::Validator)
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]);
André Silva
committed
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
André Silva
committed
// offence with no slash associated
on_offence_now(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
André Silva
committed
// it does NOT affect the nominator.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
// offence that slashes 25% of the bond
on_offence_now(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
André Silva
committed
// it DOES NOT affect the nominator.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::StakersElected,
Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 },
Event::SlashReported {
validator: 11,
fraction: Perbill::from_percent(0),
slash_era: 1
},
Event::SlashReported {
validator: 21,
fraction: Perbill::from_percent(25),
slash_era: 1
},
Event::Slashed { staker: 21, amount: 250 },
Event::Slashed { staker: 101, amount: 94 }
]
);
// the offence for validator 11 wasn't slashable but it is disabled
assert!(is_disabled(11));
// validator 21 gets disabled too
assert!(is_disabled(21));
});
André Silva
committed
}
#[test]
fn slashing_independent_of_disabling_validator() {
ExtBuilder::default()
.validator_count(5)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51]);
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
let now = Staking::active_era().unwrap().index;
// offence with no slash associated
on_offence_in_era(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
now,
);
// nomination remains untouched.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
// offence that slashes 25% of the bond
on_offence_in_era(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
now,
);
// nomination remains untouched.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
assert_eq!(
staking_events_since_last_call(),
vec![
Event::StakersElected,
Event::EraPaid { era_index: 0, validator_payout: 11075, remainder: 33225 },
Event::SlashReported {
validator: 11,
fraction: Perbill::from_percent(0),
slash_era: 1
},
Event::SlashReported {
validator: 21,
fraction: Perbill::from_percent(25),
slash_era: 1
},
Event::Slashed { staker: 21, amount: 250 },
Event::Slashed { staker: 101, amount: 94 }
]
);
// first validator is disabled but not slashed
assert!(is_disabled(11));
// second validator is slashed but not disabled
assert!(!is_disabled(21));
});
}
André Silva
committed
#[test]
fn offence_threshold_doesnt_trigger_new_era() {
André Silva
committed
ExtBuilder::default()
.validator_count(4)
.set_status(41, StakerStatus::Validator)
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41]);
assert_eq!(
UpToLimitDisablingStrategy::<DISABLING_LIMIT_FACTOR>::disable_limit(
Session::validators().len()
),
1
André Silva
committed
);
// we have 4 validators and an offending validator threshold of 1/3,
// even if the third validator commits an offence a new era should not be forced
André Silva
committed
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
let exposure_31 = Staking::eras_stakers(Staking::active_era().unwrap().index, &31);
on_offence_now(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
// 11 should be disabled because the byzantine threshold is 1
assert!(is_disabled(11));
André Silva
committed
assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
on_offence_now(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
// 21 should not be disabled because the number of disabled validators will be above the
// byzantine threshold
assert!(!is_disabled(21));
André Silva
committed
assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
on_offence_now(
&[OffenceDetails { offender: (31, exposure_31.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
// same for 31
assert!(!is_disabled(31));
assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
André Silva
committed
});
}
#[test]
fn disabled_validators_are_kept_disabled_for_whole_era() {
ExtBuilder::default()
.validator_count(7)
André Silva
committed
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.set_status(201, StakerStatus::Validator)
.set_status(202, StakerStatus::Validator)
André Silva
committed
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21, 31, 41, 51, 201, 202]);
André Silva
committed
assert_eq!(<Test as Config>::SessionsPerEra::get(), 3);
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
on_offence_now(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
// nominations are not updated.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
// validator 21 gets disabled since it got slashed
assert!(is_disabled(21));
André Silva
committed
advance_session();
// disabled validators should carry-on through all sessions in the era
assert!(is_disabled(21));
André Silva
committed
// validator 11 commits an offence
André Silva
committed
on_offence_now(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
// nominations are not updated.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
André Silva
committed
advance_session();
// and both are disabled in the last session of the era
assert!(is_disabled(11));
assert!(is_disabled(21));
André Silva
committed
mock::start_active_era(2);
// when a new era starts disabled validators get cleared
assert!(!is_disabled(11));
assert!(!is_disabled(21));
André Silva
committed
});
}
#[test]
fn claim_reward_at_the_last_era_and_no_double_claim_and_invalid_claim() {
// should check that:
// * rewards get paid until history_depth for both validators and nominators
// * an invalid era to claim doesn't update last_reward
// * double claim of one era fails
ExtBuilder::default().nominate(true).build_and_execute(|| {
// Consumed weight for all payout_stakers dispatches that fail
let err_weight = <Test as Config>::WeightInfo::payout_stakers_alive_staked(0);
let init_balance_11 = Balances::total_balance(&11);
let init_balance_101 = Balances::total_balance(&101);
let part_for_11 = Perbill::from_rational::<u32>(1000, 1125);
let part_for_101 = Perbill::from_rational::<u32>(125, 1125);
Payee::<Test>::insert(11, RewardDestination::Account(11));
Payee::<Test>::insert(101, RewardDestination::Account(101));
Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
// Compute total payout now for whole duration as other parameter won't change
let total_payout_0 = current_total_payout_for_duration(reward_time_per_era());
mock::start_active_era(1);
Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
// Increase total token issuance to affect the total payout.
let _ = Balances::deposit_creating(&999, 1_000_000_000);
// Compute total payout now for whole duration as other parameter won't change
let total_payout_1 = current_total_payout_for_duration(reward_time_per_era());
mock::start_active_era(2);
Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
// Increase total token issuance to affect the total payout.
let _ = Balances::deposit_creating(&999, 1_000_000_000);
// Compute total payout now for whole duration as other parameter won't change
let total_payout_2 = current_total_payout_for_duration(reward_time_per_era());
assert!(total_payout_2 != total_payout_0);
assert!(total_payout_2 != total_payout_1);
mock::start_active_era(HistoryDepth::get() + 1);
// This is the latest planned era in staking, not the active era
let current_era = Staking::current_era().unwrap();
// Last kept is 1:
assert!(current_era - HistoryDepth::get() == 1);
Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0),
Error::<Test>::InvalidEraToReward.with_weight(err_weight)
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0));
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0));
Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0),
Error::<Test>::AlreadyClaimed.with_weight(err_weight)
Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, active_era, 0),
Error::<Test>::InvalidEraToReward.with_weight(err_weight)
// Era 0 can't be rewarded anymore and current era can't be rewarded yet
// only era 1 and 2 can be rewarded.
assert_eq!(
Balances::total_balance(&11),
init_balance_11 + part_for_11 * (total_payout_1 + total_payout_2),
Balances::total_balance(&101),
init_balance_101 + part_for_101 * (total_payout_1 + total_payout_2),
#[test]
fn zero_slash_keeps_nominators() {
ExtBuilder::default()
.validator_count(7)
.set_status(41, StakerStatus::Validator)
.set_status(51, StakerStatus::Validator)
.set_status(201, StakerStatus::Validator)
.set_status(202, StakerStatus::Validator)
.build_and_execute(|| {
mock::start_active_era(1);
assert_eq!(Balances::free_balance(11), 1000);
let exposure = Staking::eras_stakers(active_era(), &11);
assert_eq!(Balances::free_balance(101), 2000);
on_offence_now(
&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
&[Perbill::from_percent(0)],
);
assert_eq!(Balances::free_balance(11), 1000);
assert_eq!(Balances::free_balance(101), 2000);
// 11 is not removed but disabled
assert!(Validators::<Test>::iter().any(|(stash, _)| stash == 11));
assert!(is_disabled(11));
// and their nominations are kept.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
});
ExtBuilder::default().initialize_first_session(false).build_and_execute(|| {
use pallet_session::SessionManager;
let val_set = Session::validators();
let init_session = Session::current_index();
let init_active_era = active_era();
// pallet-session is delaying session by one, thus the next session to plan is +2.
assert_eq!(<Staking as SessionManager<_>>::new_session(init_session + 2), None);
assert_eq!(
<Staking as SessionManager<_>>::new_session(init_session + 3),
Some(val_set.clone())
);
assert_eq!(<Staking as SessionManager<_>>::new_session(init_session + 4), None);
assert_eq!(<Staking as SessionManager<_>>::new_session(init_session + 5), None);
assert_eq!(
<Staking as SessionManager<_>>::new_session(init_session + 6),
Some(val_set.clone())
);
<Staking as SessionManager<_>>::end_session(init_session);
<Staking as SessionManager<_>>::start_session(init_session + 1);
assert_eq!(active_era(), init_active_era);
<Staking as SessionManager<_>>::end_session(init_session + 1);
<Staking as SessionManager<_>>::start_session(init_session + 2);
assert_eq!(active_era(), init_active_era);
// Reward current era
Staking::reward_by_ids(vec![(11, 1)]);
// New active era is triggered here.
<Staking as SessionManager<_>>::end_session(init_session + 2);
<Staking as SessionManager<_>>::start_session(init_session + 3);
assert_eq!(active_era(), init_active_era + 1);
<Staking as SessionManager<_>>::end_session(init_session + 3);
<Staking as SessionManager<_>>::start_session(init_session + 4);
assert_eq!(active_era(), init_active_era + 1);
<Staking as SessionManager<_>>::end_session(init_session + 4);
<Staking as SessionManager<_>>::start_session(init_session + 5);
assert_eq!(active_era(), init_active_era + 1);
// Reward current era
Staking::reward_by_ids(vec![(21, 2)]);
// New active era is triggered here.
<Staking as SessionManager<_>>::end_session(init_session + 5);
<Staking as SessionManager<_>>::start_session(init_session + 6);
assert_eq!(active_era(), init_active_era + 2);
// That reward are correct
assert_eq!(Staking::eras_reward_points(init_active_era).total, 1);
assert_eq!(Staking::eras_reward_points(init_active_era + 1).total, 2);
});
}
#[test]
fn test_nominators_over_max_exposure_page_size_are_rewarded() {
ExtBuilder::default().build_and_execute(|| {
// bond one nominator more than the max exposure page size to validator 11.
for i in 0..=MaxExposurePageSize::get() {
let stash = 10_000 + i as AccountId;
let balance = 10_000 + i as Balance;
assert_ok!(Staking::bond(
balance,
RewardDestination::Stash
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11]));
mock::start_active_era(1);
Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
// compute and ensure the reward amount is greater than zero.
let _ = current_total_payout_for_duration(reward_time_per_era());
mock::start_active_era(2);
// Assert nominators from 1 to Max are rewarded
let mut i: u32 = 0;
while i < MaxExposurePageSize::get() {
let stash = 10_000 + i as AccountId;
let balance = 10_000 + i as Balance;
assert!(Balances::free_balance(&stash) > balance);
i += 1;
// Assert overflowing nominators from page 1 are also rewarded
let stash = 10_000 + i as AccountId;
assert!(Balances::free_balance(&stash) > (10_000 + i) as Balance);
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
fn test_nominators_are_rewarded_for_all_exposure_page() {
ExtBuilder::default().build_and_execute(|| {
// 3 pages of exposure
let nominator_count = 2 * MaxExposurePageSize::get() + 1;
for i in 0..nominator_count {
let stash = 10_000 + i as AccountId;
let balance = 10_000 + i as Balance;
Balances::make_free_balance_be(&stash, balance);
assert_ok!(Staking::bond(
RuntimeOrigin::signed(stash),
balance,
RewardDestination::Stash
));
assert_ok!(Staking::nominate(RuntimeOrigin::signed(stash), vec![11]));
}
mock::start_active_era(1);
Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
// compute and ensure the reward amount is greater than zero.
let _ = current_total_payout_for_duration(reward_time_per_era());
mock::start_active_era(2);
mock::make_all_reward_payment(1);
assert_eq!(EraInfo::<Test>::get_page_count(1, &11), 3);
// Assert all nominators are rewarded according to their stake
for i in 0..nominator_count {
// balance of the nominator after the reward payout.
let current_balance = Balances::free_balance(&((10000 + i) as AccountId));
// balance of the nominator in the previous iteration.
let previous_balance = Balances::free_balance(&((10000 + i - 1) as AccountId));
// balance before the reward.
let original_balance = 10_000 + i as Balance;
assert!(current_balance > original_balance);
// since the stake of the nominator is increasing for each iteration, the final balance
// after the reward should also be higher than the previous iteration.
assert!(current_balance > previous_balance);
}
});
}
#[test]
fn test_multi_page_payout_stakers_by_page() {
// Test that payout_stakers work in general and that it pays the correct amount of reward.
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
// Track the exposure of the validator and all nominators.
let mut total_exposure = balance;
bond_validator(11, balance); // Default(64)
assert_eq!(Validators::<Test>::count(), 1);
// Create nominators, targeting stash of validators
for i in 0..100 {
let bond_amount = balance + i as Balance;
bond_nominator(1000 + i, bond_amount, vec![11]);
// with multi page reward payout, payout exposure is same as total exposure.
mock::start_active_era(1);
// Since `MaxExposurePageSize = 64`, there are two pages of validator exposure.
assert_eq!(EraInfo::<Test>::get_page_count(1, &11), 2);
// compute and ensure the reward amount is greater than zero.
let payout = current_total_payout_for_duration(reward_time_per_era());
mock::start_active_era(2);
// verify the exposures are calculated correctly.
let actual_exposure_0 = EraInfo::<Test>::get_paged_exposure(1, &11, 0).unwrap();
assert_eq!(actual_exposure_0.total(), total_exposure);
assert_eq!(actual_exposure_0.own(), 1000);
assert_eq!(actual_exposure_0.others().len(), 64);
let actual_exposure_1 = EraInfo::<Test>::get_paged_exposure(1, &11, 1).unwrap();
assert_eq!(actual_exposure_1.total(), total_exposure);
// own stake is only included once in the first page
assert_eq!(actual_exposure_1.own(), 0);
assert_eq!(actual_exposure_1.others().len(), 100 - 64);
let pre_payout_total_issuance = Balances::total_issuance();
RewardOnUnbalanceWasCalled::set(false);
System::reset_events();
let controller_balance_before_p0_payout = Balances::free_balance(&11);
// Payout rewards for first exposure page
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 0));
// verify `Rewarded` events are being executed
assert!(matches!(
staking_events_since_last_call().as_slice(),
&[
..,
Event::Rewarded { stash: 1063, dest: RewardDestination::Stash, amount: 111 },
Event::Rewarded { stash: 1064, dest: RewardDestination::Stash, amount: 111 },
]
));
let controller_balance_after_p0_payout = Balances::free_balance(&11);
// verify rewards have been paid out but still some left
assert!(Balances::total_issuance() > pre_payout_total_issuance);
assert!(Balances::total_issuance() < pre_payout_total_issuance + payout);
// verify the validator has been rewarded
assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout);
// Payout the second and last page of nominators
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 1, 1));
// verify `Rewarded` events are being executed for the second page.
let events = staking_events_since_last_call();
assert!(matches!(
events.as_slice(),
&[
Event::PayoutStarted { era_index: 1, validator_stash: 11 },
Event::Rewarded { stash: 1065, dest: RewardDestination::Stash, amount: 111 },
Event::Rewarded { stash: 1066, dest: RewardDestination::Stash, amount: 111 },
..
]
));
// verify the validator was not rewarded the second time
assert_eq!(Balances::free_balance(&11), controller_balance_after_p0_payout);
// verify all rewards have been paid out
assert_eq_error_rate!(Balances::total_issuance(), pre_payout_total_issuance + payout, 2);
assert!(RewardOnUnbalanceWasCalled::get());
// Top 64 nominators of validator 11 automatically paid out, including the validator
assert!(Balances::free_balance(&11) > balance);
for i in 0..100 {
assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance);
// verify we no longer track rewards in `legacy_claimed_rewards` vec
Staking::ledger(11.into()).unwrap(),
StakingLedgerInspect {
stash: 11,
total: 1000,
active: 1000,
unlocking: Default::default(),
legacy_claimed_rewards: bounded_vec![]
// verify rewards are tracked to prevent double claims
let ledger = Staking::ledger(11.into());
for page in 0..EraInfo::<Test>::get_page_count(1, &11) {
assert_eq!(
EraInfo::<Test>::is_rewards_claimed_with_legacy_fallback(
1,
ledger.as_ref().unwrap(),
&11,
page
),
true
);
}