Skip to content
tests.rs 143 KiB
Newer Older
			.offchain_phragmen_ext()
			.build()
			.execute_with(|| {
				run_to_block(11);
				// submission is not yet allowed
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);

				// create all the indices just to build the solution.
				Staking::create_stakers_snapshot();
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				Staking::kill_stakers_snapshot();

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenEarlySubmission,
				);
			})
	}

	#[test]
	fn weak_solution_is_rejected() {
		// A solution which is weaker than what we currently have on-chain is rejected.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.has_stakers(false)
			.validator_count(4)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				// a good solution
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				assert_ok!(Staking::submit_election_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				// a bad solution
				let (compact, winners, score) = horrible_phragmen_with_post_processing(false);
				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenWeakSubmission,
				);
			})
	}

	#[test]
	fn better_solution_is_accepted() {
		// A solution which is better than what we currently have on-chain is accepted.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				// a meeeeh solution
				let (compact, winners, score) = horrible_phragmen_with_post_processing(false);
				assert_ok!(Staking::submit_election_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				// a better solution
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				assert_ok!(Staking::submit_election_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));
			})
	}

	#[test]
	fn offchain_worker_runs_when_window_open() {
		// at the end of the first finalized block with ElectionStatus::open(_), it should execute.
		let mut ext = ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(2)
			.build();
		let state = offchainify(&mut ext, 0);
		ext.execute_with(|| {
			run_to_block(12);

			// local key 11 is in the elected set.
			assert_eq_uvec!(Session::validators(), vec![11, 21]);
			assert_eq!(state.read().transactions.len(), 0);
			Staking::offchain_worker(12);
			assert_eq!(state.read().transactions.len(), 1);

			let encoded = state.read().transactions[0].clone();
			let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();

			let call = extrinsic.call;
			let inner = match call {
				mock::Call::Staking(inner) => inner,
			};

			assert_eq!(
				<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
					TransactionSource::Local,
					&inner,
				),
				TransactionValidity::Ok(ValidTransaction {
					priority: UnsignedPriority::get() + 1125, // the proposed slot stake.
					requires: vec![],
					provides: vec![("StakingOffchain", current_era()).encode()],
					longevity: 3,
					propagate: false,
				})
			)
		})
	}

	#[test]
	fn offchain_worker_runs_with_equalise() {
		// Offchain worker equalises based on the number provided by randomness. See the difference
		// in the priority, which comes from the computed score.
		let mut ext = ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(2)
			.max_offchain_iterations(2)
			.build();
		let state = offchainify(&mut ext, 2);
		ext.execute_with(|| {
			run_to_block(12);

			// local key 11 is in the elected set.
			assert_eq_uvec!(Session::validators(), vec![11, 21]);
			assert_eq!(state.read().transactions.len(), 0);
			Staking::offchain_worker(12);
			assert_eq!(state.read().transactions.len(), 1);

			let encoded = state.read().transactions[0].clone();
			let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();

			let call = extrinsic.call;
			let inner = match call {
				mock::Call::Staking(inner) => inner,
			};

			assert_eq!(
				<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
					TransactionSource::Local,
					&inner,
				),
				TransactionValidity::Ok(ValidTransaction {
					// the proposed slot stake, with equalize.
					priority: UnsignedPriority::get() + 1250,
					requires: vec![],
					provides: vec![("StakingOffchain", active_era()).encode()],
					longevity: 3,
					propagate: false,
				})
			)
		})
	}

	#[test]
	fn mediocre_submission_from_authority_is_early_rejected() {
		let mut ext = ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.build();
		let state = offchainify(&mut ext, 0);
		ext.execute_with(|| {
			run_to_block(12);
			// put a good solution on-chain
			let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
			assert_ok!(Staking::submit_election_solution(
				Origin::signed(10),
				winners,
				compact,
				score,
			),);

			// now run the offchain worker in the same chain state.
			Staking::offchain_worker(12);
			assert_eq!(state.read().transactions.len(), 1);

			let encoded = state.read().transactions[0].clone();
			let extrinsic: Extrinsic = Decode::decode(&mut &*encoded).unwrap();

			let call = extrinsic.call;
			let inner = match call {
				mock::Call::Staking(inner) => inner,
			};

			// pass this call to ValidateUnsigned
			assert_eq!(
				<Staking as sp_runtime::traits::ValidateUnsigned>::validate_unsigned(
					TransactionSource::Local,
					&inner,
				),
				TransactionValidity::Err(
					InvalidTransaction::Custom(<Error<Test>>::PhragmenWeakSubmission.as_u8()).into(),
				),
			)
		})
	}

	#[test]
	fn invalid_phragmen_result_correct_number_of_winners() {
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				ValidatorCount::put(3);
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				ValidatorCount::put(4);

				assert_eq!(winners.len(), 3);

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusWinnerCount,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_correct_number_of_winners_1() {
		// if we have too little validators, then the number of candidates is the bound.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(8) // we simply cannot elect 8
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				ValidatorCount::put(3);
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				ValidatorCount::put(4);

				assert_eq!(winners.len(), 3);

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusWinnerCount,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_correct_number_of_winners_2() {
		// if we have too little validators, then the number of candidates is the bound.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(8) // we simply cannot elect 8
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});

				assert_eq!(winners.len(), 4);

				// all good. We chose 4 and it works.
				assert_ok!(Staking::submit_election_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				),);
			})
	}

	#[test]
	fn invalid_phragmen_result_out_of_bound_nominator_index() {
		// A nominator index which is simply invalid
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
				assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
				let (mut compact, winners, score) = prepare_submission_with(true, 2, |_| {});

				// index 9 doesn't exist.
				compact.votes1.push((9, 2));

				// The error type sadly cannot be more specific now.
				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusCompact,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_out_of_bound_validator_index() {
		// A validator index which is out of bound
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
				assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
				let (mut compact, winners, score) = prepare_submission_with(true, 2, |_| {});

				// index 4 doesn't exist.
				compact.votes1.push((3, 4));

				// The error type sadly cannot be more specific now.
				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusCompact,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_out_of_bound_winner_index() {
		// A winner index which is simply invalid
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
				assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
				let (compact, _, score) = prepare_submission_with(true, 2, |_| {});

				// index 4 doesn't exist.
				let winners = vec![0, 1, 2, 4];

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusWinner,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_non_winner_validator_index() {
		// An edge that points to a correct validator index who is NOT a winner. This is very
		// similar to the test that raises `PhragmenBogusNomination`.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(2) // we select only 2.
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				assert_eq!(Staking::snapshot_nominators().unwrap().len(), 5 + 4);
				assert_eq!(Staking::snapshot_validators().unwrap().len(), 4);
				let (compact, winners, score) = prepare_submission_with(true, 2, |a| {
					a.iter_mut()
						.find(|x| x.who == 5)
						// all 3 cannot be among the winners. Although, all of them are validator
						// candidates.
						.map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]);
				});

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusEdge,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_wrong_self_vote() {
		// A self vote for someone else.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				let (compact, winners, score) = prepare_submission_with(true, 2, |a| {
					// mutate a self vote to target someone else. That someone else is still among the
					// winners
					a.iter_mut().find(|x| x.who == 11).map(|x| {
						x.distribution
							.iter_mut()
							.find(|y| y.0 == 11)
							.map(|y| y.0 = 21)
					});
				});

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusSelfVote,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_wrong_self_vote_2() {
		// A self validator voting for someone else next to self vote.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				let (compact, winners, score) = prepare_submission_with(true, 2, |a| {
					// Remove the self vote.
					a.retain(|x| x.who != 11);
					// add is as a new double vote
					a.push(StakedAssignment {
						who: 11,
						distribution: vec![(11, 50), (21, 50)],
					});
				});

				// This raises score issue.
				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusSelfVote,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_over_stake() {
		// Someone's edge ratios sums to more than 100%.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				// Note: we don't reduce here to be able to tweak votes3. votes3 will vanish if you
				// reduce.
				let (mut compact, winners, score) = prepare_submission_with(false, 0, |_| {});

				if let Some(c) = compact.votes3.iter_mut().find(|x| x.0 == 0) {
					// by default it should have been (0, [(2, 33%), (1, 33%)], 0)
					// now the sum is above 100%
					c.1 = [(2, percent(66)), (1, percent(66))];
				}

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusCompact,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_under_stake() {
		// at the time of this writing, we cannot under stake someone. The compact assignment works
		// in a way that some of the stakes are presented by the submitter, and the last one is read
		// from chain by subtracting the rest from total. Hence, the sum is always correct.
		// This test is only here as a demonstration.
	}

	#[test]
	fn invalid_phragmen_result_invalid_target_stealing() {
		// A valid voter who voted for someone who is a candidate, and is a correct winner, but is
		// actually NOT nominated by this nominator.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				let (compact, winners, score) = prepare_submission_with(false, 0, |a| {
					// 3 only voted for 20 and 40. We add a fake vote to 30. The stake sum is still
					// correctly 100.
					a.iter_mut()
						.find(|x| x.who == 3)
						.map(|x| x.distribution = vec![(21, 50), (41, 30), (31, 20)]);
				});

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusNomination,
				);
			})
	}

	#[test]
	fn nomination_slash_filter_is_checked() {
		// If a nominator has voted for someone who has been recently slashed, that particular
		// nomination should be disabled for the upcoming election. A solution must respect this
		// rule.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();

				// finalize the round with fallback. This is needed since all nominator submission
				// are in era zero and we want this one to pass with no problems.
				run_to_block(15);

				// go to the next session to trigger mock::start_era and bump the active era
				run_to_block(20);

				// slash 10. This must happen outside of the election window.
				let offender_expo = Staking::eras_stakers(Staking::active_era().unwrap().index, 11);
				on_offence_now(
					&[OffenceDetails {
						offender: (11, offender_expo.clone()),
						reporters: vec![],
					}],
					&[Perbill::from_percent(50)],
				);

				// validate 10 again for the next round. But this guy will not have the votes that
				// it should have had from 1 and 2.
				assert_ok!(Staking::validate(
					Origin::signed(10),
					Default::default()
				));

				// open the election window and create snapshots.
				run_to_block(32);

				// a solution that has been prepared after the slash.
				let (compact, winners, score) = prepare_submission_with(false, 0, |a| {
					// no one is allowed to vote for 10, except for itself.
					a.into_iter()
						.filter(|s| s.who != 11)
						.for_each(|s|
							assert!(s.distribution.iter().find(|(t, _)| *t == 11).is_none())
						);
				});

				// can be submitted.
				assert_ok!(Staking::submit_election_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				// a wrong solution.
				let (compact, winners, score) = prepare_submission_with(false, 0, |a| {
					// add back the vote that has been filtered out.
					a.push(StakedAssignment {
						who: 1,
						distribution: vec![(11, 100)]
					});
				});

				// is rejected.
				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenSlashedNomination,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_wrong_score() {
		// A valid voter who's total distributed stake is more than what they bond
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				build_offchain_phragmen_test_ext();
				run_to_block(12);

				let (compact, winners, mut score) = prepare_submission_with(true, 2, |_| {});
				score[0] += 1;

				assert_noop!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusScore,
				);
			})
	}

	#[test]
	fn offchain_storage_is_set() {
		let mut ext = ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.build();
		let state = offchainify(&mut ext, 0);

		ext.execute_with(|| {
			use offchain_election::OFFCHAIN_HEAD_DB;
			use sp_runtime::offchain::storage::StorageValueRef;

			run_to_block(12);

			Staking::offchain_worker(12);
			// it works
			assert_eq!(state.read().transactions.len(), 1);

			// and it is set
			let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB);
			assert_eq!(storage.get::<BlockNumber>().unwrap().unwrap(), 12);
		})
	}

	#[test]
	fn offchain_storage_prevents_duplicate() {
		let mut ext = ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.build();
		let _ = offchainify(&mut ext, 0);

		ext.execute_with(|| {
			use offchain_election::OFFCHAIN_HEAD_DB;
			use sp_runtime::offchain::storage::StorageValueRef;
			let storage = StorageValueRef::persistent(&OFFCHAIN_HEAD_DB);

			run_to_block(12);

			// first run -- ok
			assert_eq!(
				offchain_election::set_check_offchain_execution_status::<Test>(12),
				Ok(()),
			);
			assert_eq!(storage.get::<BlockNumber>().unwrap().unwrap(), 12);

			// re-execute after the next. not allowed.
			assert_eq!(
				offchain_election::set_check_offchain_execution_status::<Test>(13),
				Err("recently executed."),
			);

			// a fork like situation -- re-execute 10, 11, 12. But it won't go through.
			assert_eq!(
				offchain_election::set_check_offchain_execution_status::<Test>(10),
				Err("fork."),
			);
			assert_eq!(
				offchain_election::set_check_offchain_execution_status::<Test>(11),
				Err("fork."),
			);
			assert_eq!(
				offchain_election::set_check_offchain_execution_status::<Test>(12),
				Err("recently executed."),
			);
		})
	}

	#[test]
	#[should_panic]
	fn offence_is_blocked_when_window_open() {
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.validator_count(4)
			.has_stakers(false)
			.build()
			.execute_with(|| {
				run_to_block(12);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));

				let offender_expo = Staking::eras_stakers(Staking::active_era().unwrap().index, 10);

				// panic from the impl in mock
				on_offence_now(
					&[OffenceDetails {
						offender: (10, offender_expo.clone()),
						reporters: vec![],
					}],
					&[Perbill::from_percent(10)],
				);
			})
	}
}

fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_validator() {
	ExtBuilder::default().build_and_execute(|| {
		mock::start_era(1);
		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);

		// 11 and 21 both have the support of 100
		let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
		let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);

		assert_eq!(exposure_11.total, 1000 + 125);
		assert_eq!(exposure_21.total, 1000 + 375);
			&[OffenceDetails {
				offender: (11, exposure_11.clone()),
				reporters: vec![],
			}],
		// 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
		);

		// This is the best way to check that the validator was chilled; `get` will
		// return default value.
		for (stash, _) in <Staking as Store>::Validators::iter() {
			assert!(stash != 11);
		}

		let nominations = <Staking as Store>::Nominators::get(&101).unwrap();

		// and make sure that the vote will be ignored even if the validator
		// re-registers.
		let last_slash = <Staking as Store>::SlashingSpans::get(&11)
			.unwrap()
			.last_nonzero_slash();
		assert!(nominations.submitted_in < last_slash);

		// actually re-bond the slashed validator
		assert_ok!(Staking::validate(Origin::signed(10), Default::default()));

		mock::start_era(2);
		let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, &11);
		let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21);

		// 10 is re-elected, but without the support of 100
		assert_eq!(exposure_11.total, 900);

		// 20 is re-elected, with the (almost) entire support of 100
		assert_eq!(exposure_21.total, 1000 + 500 - nominator_slash_amount_11);
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(|| {
Gavin Wood's avatar
Gavin Wood committed
		let init_balance_10 = Balances::total_balance(&10);
		let init_balance_100 = Balances::total_balance(&100);

		let part_for_10 = Perbill::from_rational_approximation::<u32>(1000, 1125);
		let part_for_100 = Perbill::from_rational_approximation::<u32>(125, 1125);

		// Check state
		Payee::<Test>::insert(11, RewardDestination::Controller);
		Payee::<Test>::insert(101, RewardDestination::Controller);

		<Module<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(3000);
		assert!(total_payout_0 > 10); // Test is meaningful if reward something

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

		<Module<Test>>::reward_by_ids(vec![(11, 1)]);
		// Change total issuance in order to modify 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(3000);
		assert!(total_payout_1 > 10); // Test is meaningful if reward something
		assert!(total_payout_1 != total_payout_0);

		mock::start_era(2);
Gavin Wood's avatar
Gavin Wood committed

		<Module<Test>>::reward_by_ids(vec![(11, 1)]);
		// Change total issuance in order to modify 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(3000);
		assert!(total_payout_2 > 10); // Test is meaningful if reward something
		assert!(total_payout_2 != total_payout_0);
		assert!(total_payout_2 != total_payout_1);

		mock::start_era(Staking::history_depth() + 1);
Gavin Wood's avatar
Gavin Wood committed

		let active_era = Staking::active_era().unwrap().index;

		// 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 - Staking::history_depth() == 1);
		assert_noop!(
			Staking::payout_stakers(Origin::signed(1337), 11, 0),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Era out of history
			Error::<Test>::InvalidEraToReward
		);
		assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 1));
		assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 2));
Gavin Wood's avatar
Gavin Wood committed
		assert_noop!(
			Staking::payout_stakers(Origin::signed(1337), 11, 2),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Double claim
			Error::<Test>::AlreadyClaimed
Gavin Wood's avatar
Gavin Wood committed
		);
		assert_noop!(
			Staking::payout_stakers(Origin::signed(1337), 11, active_era),
Gavin Wood's avatar
Gavin Wood committed
			// Fail: Era not finished yet
			Error::<Test>::InvalidEraToReward
		);
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(&10),
			init_balance_10 + part_for_10 * (total_payout_1 + total_payout_2),
		);
		assert_eq!(
			Balances::total_balance(&100),
			init_balance_100 + part_for_100 * (total_payout_1 + total_payout_2),
		);
	});
}

#[test]
fn zero_slash_keeps_nominators() {
	ExtBuilder::default().build_and_execute(|| {
		mock::start_era(1);
		assert_eq!(Balances::free_balance(11), 1000);
		let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 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);

		// This is the best way to check that the validator was chilled; `get` will
		// return default value.
		for (stash, _) in <Staking as Store>::Validators::iter() {
			assert!(stash != 11);
		}

		let nominations = <Staking as Store>::Nominators::get(&101).unwrap();

		// and make sure that the vote will not be ignored, because the slash was
		// zero.
		let last_slash = <Staking as Store>::SlashingSpans::get(&11).unwrap().last_nonzero_slash();
		assert!(nominations.submitted_in >= last_slash);
	});
}
Gavin Wood's avatar
Gavin Wood committed

#[test]
fn six_session_delay() {
	ExtBuilder::default().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 = Staking::active_era().unwrap().index;
		// 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()));