Skip to content
tests.rs 135 KiB
Newer Older
					.last()
					.unwrap(),
				RawEvent::StakingElection(ElectionCompute::OnChain),
			);
		})
	}

	#[test]
	#[ignore] // This takes a few mins
	fn offchain_wont_work_if_snapshot_fails() {
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.build()
			.execute_with(|| {
				run_to_block(12);
				assert!(Staking::snapshot_validators().is_some());
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));

				// validate more than the limit
				let limit: NominatorIndex = ValidatorIndex::max_value() as NominatorIndex + 1;
				let ctrl = 1_000_000;
				for i in 0..limit {
					bond_validator((1000 + i).into(), (1000 + i + ctrl).into(), 100);
				}

				// window stays closed since no snapshot was taken.
				run_to_block(27);
				assert!(Staking::snapshot_validators().is_none());
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
			})
	}

	#[test]
	fn staking_is_locked_when_election_window_open() {
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.election_lookahead(3)
			.build()
			.execute_with(|| {
				run_to_block(12);
				assert!(Staking::snapshot_validators().is_some());
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));

				// chill et. al. are now not allowed.
				assert_noop!(
					Staking::chill(Origin::signed(10)),
					Error::<Test>::CallNotAllowed,
				);
			})
	}

	#[test]
	fn signed_result_can_be_submitted() {
		// should check that we have a new validator set normally, event says that it comes from
		// offchain.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.build()
			.execute_with(|| {
				run_to_block(12);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));
				assert!(Staking::snapshot_validators().is_some());

				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				assert_ok!(submit_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				let queued_result = Staking::queued_elected().unwrap();
				assert_eq!(queued_result.compute, ElectionCompute::Signed);
				assert_eq!(
					System::events()
						.into_iter()
						.map(|r| r.event)
						.filter_map(|e| {
							if let MetaEvent::staking(inner) = e {
								Some(inner)
							} else {
								None
							}
						})
						.last()
						.unwrap(),
					RawEvent::SolutionStored(ElectionCompute::Signed),
				);

				run_to_block(15);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);

				assert_eq!(
					System::events()
						.into_iter()
						.map(|r| r.event)
						.filter_map(|e| {
							if let MetaEvent::staking(inner) = e {
								Some(inner)
							} else {
								None
							}
						})
						.last()
						.unwrap(),
					RawEvent::StakingElection(ElectionCompute::Signed),
				);
			})
	}

	#[test]
	fn signed_result_can_be_submitted_later() {
		// same as `signed_result_can_be_submitted` but at a later block.
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.build()
			.execute_with(|| {
				run_to_block(14);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(12));

				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				assert_ok!(submit_solution(Origin::signed(10), winners, compact, score));

				let queued_result = Staking::queued_elected().unwrap();
				assert_eq!(queued_result.compute, ElectionCompute::Signed);

				run_to_block(15);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);

				assert_eq!(
					System::events()
						.into_iter()
						.map(|r| r.event)
						.filter_map(|e| {
							if let MetaEvent::staking(inner) = e {
								Some(inner)
							} else {
								None
							}
						})
						.last()
						.unwrap(),
					RawEvent::StakingElection(ElectionCompute::Signed),
				);
			})
	}

	#[test]
	fn early_solution_submission_is_rejected() {
		// should check that we have a new validator set normally, event says that it comes from
		// offchain.
		ExtBuilder::default()
			.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_err_with_weight!(
					Staking::submit_election_solution(
						Origin::signed(10),
						winners.clone(),
						compact.clone(),
						ElectionSize::default(),
					),
					Error::<Test>::PhragmenEarlySubmission,
					Some(<Test as frame_system::Trait>::DbWeight::get().reads(1)),
				);
			})
	}

	#[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!(submit_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				// a bad solution
				let (compact, winners, score) = horrible_phragmen_with_post_processing(false);
				assert_err_with_weight!(
					submit_solution(
						Origin::signed(10),
						winners.clone(),
						compact.clone(),
						score,
					),
					Error::<Test>::PhragmenWeakSubmission,
					Some(<Test as frame_system::Trait>::DbWeight::get().reads(3))
				);
			})
	}

	#[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!(submit_solution(
					Origin::signed(10),
					winners,
					compact,
					score,
				));

				// a better solution
				let (compact, winners, score) = prepare_submission_with(true, 2, |_| {});
				assert_ok!(submit_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 balance_solution.
					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!(submit_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!(
					submit_solution(
						Origin::signed(10),
						winners,
						compact,
						score,
					),
					Error::<Test>::PhragmenBogusWinnerCount,
				);
			})
	}

	#[test]
	fn invalid_phragmen_result_solution_size() {
		ExtBuilder::default()
			.offchain_phragmen_ext()
			.build()
			.execute_with(|| {
				run_to_block(12);

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

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

	#[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!(
						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!(submit_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!(
						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!(
						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!(
						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!(
						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!(
						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!(
						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!(
						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!(
						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!(submit_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!(
						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!(
						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() {