Skip to content
tests.rs 217 KiB
Newer Older
		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!(
			StakingLedgerInspect {
				active: 0,
				total: 500,
				stash: 101,
				legacy_claimed_rewards: bounded_vec![],
				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:
		assert_storage_noop!(assert!(
			Staking::withdraw_unbonded(RuntimeOrigin::signed(101), 0).is_ok()
		assert_eq!(
			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;
		// deferred to start of era 4.
		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
		);

		// cancel one of them.
		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(
			&[OffenceDetails {
				offender: (21, Staking::eras_stakers(active_era(), &21)),
			&[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(|| {
		assert_eq_uvec!(Session::validators(), vec![11, 21]);
		// 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);
			&[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }],
		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()));
		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);
#[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]);

		// 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 }
			]
		);

		// the offence for validator 10 wasn't slashable so it wasn't disabled
#[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));
#[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));

			advance_session();

			// disabled validators should carry-on through all sessions in the era
			assert!(!is_disabled(11));
			assert!(is_disabled(21));
			// validator 11 should now get disabled
			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]);

			advance_session();

			// and both are disabled in the last session of the era
			assert!(is_disabled(11));
			assert!(is_disabled(21));

			mock::start_active_era(2);

			// when a new era starts disabled validators get cleared
			assert!(!is_disabled(11));
			assert!(!is_disabled(21));
Gavin Wood's avatar
Gavin Wood 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);
Gavin Wood's avatar
Gavin Wood committed

		let part_for_11 = Perbill::from_rational::<u32>(1000, 1125);
		let part_for_101 = Perbill::from_rational::<u32>(125, 1125);
Gavin Wood's avatar
Gavin Wood committed

		// Check state
		Payee::<Test>::insert(11, RewardDestination::Account(11));
		Payee::<Test>::insert(101, RewardDestination::Account(101));
Gavin Wood's avatar
Gavin Wood committed

		Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
Gavin Wood's avatar
Gavin Wood committed
		// 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());
Gavin Wood's avatar
Gavin Wood committed

Gavin Wood's avatar
Gavin Wood committed

		Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
		// Increase total token issuance to affect the total payout.
Gavin Wood's avatar
Gavin Wood committed
		let _ = Balances::deposit_creating(&999, 1_000_000_000);
Gavin Wood's avatar
Gavin Wood committed
		// 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());
Gavin Wood's avatar
Gavin Wood committed
		assert!(total_payout_1 != total_payout_0);

Gavin Wood's avatar
Gavin Wood committed

		Pallet::<Test>::reward_by_ids(vec![(11, 1)]);
		// Increase total token issuance to affect the total payout.
Gavin Wood's avatar
Gavin Wood committed
		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());
Gavin Wood's avatar
Gavin Wood committed
		assert!(total_payout_2 != total_payout_0);
		assert!(total_payout_2 != total_payout_1);

		mock::start_active_era(HistoryDepth::get() + 1);
Gavin Wood's avatar
Gavin Wood committed

		let active_era = active_era();
Gavin Wood's avatar
Gavin Wood committed

		// 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);
Gavin Wood's avatar
Gavin Wood committed
		assert_noop!(
			Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 0, 0),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Era out of history
			Error::<Test>::InvalidEraToReward.with_weight(err_weight)
Gavin Wood's avatar
Gavin Wood committed
		);
		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));
Gavin Wood's avatar
Gavin Wood committed
		assert_noop!(
			Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 0),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Double claim
			Error::<Test>::AlreadyClaimed.with_weight(err_weight)
Gavin Wood's avatar
Gavin Wood committed
		);
		assert_noop!(
			Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, active_era, 0),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Era not finished yet
			Error::<Test>::InvalidEraToReward.with_weight(err_weight)
Gavin Wood's avatar
Gavin Wood committed
		);
Gavin Wood's avatar
Gavin Wood committed
		// 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),
Gavin Wood's avatar
Gavin Wood committed
		);
		assert_eq!(
			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(|| {
		assert_eq!(Balances::free_balance(11), 1000);
		let exposure = Staking::eras_stakers(active_era(), &11);
		assert_eq!(Balances::free_balance(101), 2000);
			&[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }],
		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]);
Gavin Wood's avatar
Gavin Wood committed

#[test]
fn six_session_delay() {
	ExtBuilder::default().initialize_first_session(false).build_and_execute(|| {
Gavin Wood's avatar
Gavin Wood committed
		use pallet_session::SessionManager;

		let val_set = Session::validators();
		let init_session = Session::current_index();
		let init_active_era = active_era();
Gavin Wood's avatar
Gavin Wood committed
		// 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())
		);
Gavin Wood's avatar
Gavin Wood committed
		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())
		);
Gavin Wood's avatar
Gavin Wood committed

		<Staking as SessionManager<_>>::end_session(init_session);
		<Staking as SessionManager<_>>::start_session(init_session + 1);
		assert_eq!(active_era(), init_active_era);

Gavin Wood's avatar
Gavin Wood committed
		<Staking as SessionManager<_>>::end_session(init_session + 1);
		<Staking as SessionManager<_>>::start_session(init_session + 2);
		assert_eq!(active_era(), init_active_era);
Gavin Wood's avatar
Gavin Wood committed

		// 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);

Gavin Wood's avatar
Gavin Wood committed
		<Staking as SessionManager<_>>::end_session(init_session + 3);
		<Staking as SessionManager<_>>::start_session(init_session + 4);
		assert_eq!(active_era(), init_active_era + 1);

Gavin Wood's avatar
Gavin Wood committed
		<Staking as SessionManager<_>>::end_session(init_session + 4);
		<Staking as SessionManager<_>>::start_session(init_session + 5);
		assert_eq!(active_era(), init_active_era + 1);
Gavin Wood's avatar
Gavin Wood committed

		// 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);
Gavin Wood's avatar
Gavin Wood committed

		// 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;
Gavin Wood's avatar
Gavin Wood committed
			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]));
Gavin Wood's avatar
Gavin Wood committed
		}
Gavin Wood's avatar
Gavin Wood committed

		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());
Gavin Wood's avatar
Gavin Wood committed

Gavin Wood's avatar
Gavin Wood committed
		mock::make_all_reward_payment(1);

		// 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;
Gavin Wood's avatar
Gavin Wood committed
		}

		// 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);
#[test]
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(|| {
		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);

		// 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.
			total_exposure += bond_amount;
		Staking::reward_by_ids(vec![(11, 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());
		// 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);
			assert!(Balances::free_balance(&(1000 + i)) > balance + i as Balance);
		// verify we no longer track rewards in `legacy_claimed_rewards` vec
		assert_eq!(
			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();
			RewardOnUnbalanceWasCalled::set(false);
			mock::make_all_reward_payment(i - 1);
			assert_eq_error_rate!(
				Balances::total_issuance(),
			);
			assert!(RewardOnUnbalanceWasCalled::get());

			// 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;
			bond_nominator(1000 + i, bond_amount, vec![11]);
			// with multi page reward payout, payout exposure is same as total exposure.
			total_exposure += bond_amount;