Newer
Older
// 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![],
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
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,
DisableStrategy::WhenSlashed,
// 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().build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21]);
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::Chilled { stash: 11 },
Event::ForceEra { mode: Forcing::ForceNew },
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 chilled.
assert!(Validators::<Test>::iter().all(|(stash, _)| stash != 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_doesnt_disable_validator() {
ExtBuilder::default().build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21]);
let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);
// offence with no slash associated
on_offence_now(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
// it does NOT affect the nominator.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
André Silva
committed
// offence that slashes 25% of the bond
on_offence_now(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
);
// 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::Chilled { stash: 11 },
Event::ForceEra { mode: Forcing::ForceNew },
Event::SlashReported {
validator: 11,
fraction: Perbill::from_percent(0),
slash_era: 1
},
Event::Chilled { stash: 21 },
Event::SlashReported {
validator: 21,
fraction: Perbill::from_percent(25),
slash_era: 1
},
Event::Slashed { staker: 21, amount: 250 },
Event::Slashed { staker: 101, amount: 94 }
]
);
André Silva
committed
// the offence for validator 10 wasn't slashable so it wasn't disabled
assert!(!is_disabled(11));
André Silva
committed
// whereas validator 20 gets disabled
assert!(is_disabled(21));
André Silva
committed
});
}
#[test]
fn slashing_independent_of_disabling_validator() {
ExtBuilder::default().build_and_execute(|| {
mock::start_active_era(1);
assert_eq_uvec!(Session::validators(), vec![11, 21]);
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, BUT disabling
on_offence_in_era(
&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
now,
DisableStrategy::Always,
);
// nomination remains untouched.
assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]);
// offence that slashes 25% of the bond, BUT not disabling
on_offence_in_era(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::from_percent(25)],
now,
DisableStrategy::Never,
);
// 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::Chilled { stash: 11 },
Event::ForceEra { mode: Forcing::ForceNew },
Event::SlashReported {
validator: 11,
fraction: Perbill::from_percent(0),
slash_era: 1
},
Event::Chilled { stash: 21 },
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 10 was explicitly disabled
assert!(is_disabled(11));
// whereas validator 21 is explicitly not disabled
assert!(!is_disabled(21));
});
}
André Silva
committed
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
#[test]
fn offence_threshold_triggers_new_era() {
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!(
<Test as Config>::OffendingValidatorsThreshold::get(),
Perbill::from_percent(75),
);
// we have 4 validators and an offending validator threshold of 75%,
// once the third validator commits an offence a new era should be forced
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()],
);
assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
on_offence_now(
&[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
assert_eq!(ForceEra::<Test>::get(), Forcing::NotForcing);
on_offence_now(
&[OffenceDetails { offender: (31, exposure_31.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
assert_eq!(ForceEra::<Test>::get(), Forcing::ForceNew);
});
}
#[test]
fn disabled_validators_are_kept_disabled_for_whole_era() {
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!(<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: (11, exposure_11.clone()), reporters: vec![] }],
&[Perbill::zero()],
);
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 11 should not be disabled since the offence wasn't slashable
assert!(!is_disabled(11));
// 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(11));
assert!(is_disabled(21));
André Silva
committed
// validator 11 should now get disabled
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);
// Check state
Payee::<Test>::insert(11, RewardDestination::Controller);
Payee::<Test>::insert(101, RewardDestination::Controller);
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().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 still removed..
assert!(Validators::<Test>::iter().all(|(stash, _)| stash != 11));
// but 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);
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
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::Controller, amount: 111 },
Event::Rewarded { stash: 1064, dest: RewardDestination::Controller, amount: 111 },
]
));
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
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::Controller, amount: 111 },
Event::Rewarded { stash: 1066, dest: RewardDestination::Controller, 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
);
}
for i in 3..16 {
Staking::reward_by_ids(vec![(11, 1)]);
// compute and ensure the reward amount is greater than zero.
let payout = current_total_payout_for_duration(reward_time_per_era());
let pre_payout_total_issuance = Balances::total_issuance();
mock::start_active_era(i);
RewardOnUnbalanceWasCalled::set(false);
mock::make_all_reward_payment(i - 1);
assert_eq_error_rate!(
Balances::total_issuance(),
pre_payout_total_issuance + payout,
2
);
assert!(RewardOnUnbalanceWasCalled::get());
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
// verify we track rewards for each era and page
for page in 0..EraInfo::<Test>::get_page_count(i - 1, &11) {
assert_eq!(
EraInfo::<Test>::is_rewards_claimed_with_legacy_fallback(
i - 1,
Staking::ledger(11.into()).as_ref().unwrap(),
&11,
page
),
true
);
}
}
assert_eq!(Staking::claimed_rewards(14, &11), vec![0, 1]);
let last_era = 99;
let history_depth = HistoryDepth::get();
let last_reward_era = last_era - 1;
let first_claimable_reward_era = last_era - history_depth;
for i in 16..=last_era {
Staking::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(i);
}
// verify we clean up history as we go
for era in 0..15 {
assert_eq!(Staking::claimed_rewards(era, &11), Vec::<sp_staking::Page>::new());
}
// verify only page 0 is marked as claimed
assert_ok!(Staking::payout_stakers_by_page(
RuntimeOrigin::signed(1337),
11,
first_claimable_reward_era,
0
));
assert_eq!(Staking::claimed_rewards(first_claimable_reward_era, &11), vec![0]);
// verify page 0 and 1 are marked as claimed
assert_ok!(Staking::payout_stakers_by_page(
RuntimeOrigin::signed(1337),
11,
first_claimable_reward_era,
1
));
assert_eq!(Staking::claimed_rewards(first_claimable_reward_era, &11), vec![0, 1]);
// verify only page 0 is marked as claimed
assert_ok!(Staking::payout_stakers_by_page(
RuntimeOrigin::signed(1337),
11,
last_reward_era,
0
));
assert_eq!(Staking::claimed_rewards(last_reward_era, &11), vec![0]);
// verify page 0 and 1 are marked as claimed
assert_ok!(Staking::payout_stakers_by_page(
RuntimeOrigin::signed(1337),
11,
last_reward_era,
1
));
assert_eq!(Staking::claimed_rewards(last_reward_era, &11), vec![0, 1]);
// Out of order claims works.
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 69, 0));
assert_eq!(Staking::claimed_rewards(69, &11), vec![0]);
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 23, 1));
assert_eq!(Staking::claimed_rewards(23, &11), vec![1]);
assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 42, 0));
assert_eq!(Staking::claimed_rewards(42, &11), vec![0]);
});
}
#[test]
fn test_multi_page_payout_stakers_backward_compatible() {
// Test that payout_stakers work in general and that it pays the correct amount of reward.
ExtBuilder::default().has_stakers(false).build_and_execute(|| {
let balance = 1000;
// Track the exposure of the validator and all nominators.
let mut total_exposure = balance;
// Create a validator:
bond_validator(11, balance); // Default(64)
assert_eq!(Validators::<Test>::count(), 1);
let err_weight = <Test as Config>::WeightInfo::payout_stakers_alive_staked(0);
// Create nominators, targeting stash of validators
for i in 0..100 {
let bond_amount = balance + i as Balance;