Skip to content
tests.rs 144 KiB
Newer Older
				reporters: vec![],
			}],
			&[Perbill::from_percent(0)],
			1,
		);

		// not forcing for zero-slash and previous span.
		assert_eq!(Staking::force_era(), Forcing::NotForcing);
		assert!(<Validators<Test>>::contains_key(11));
Gavin Wood's avatar
Gavin Wood committed
		assert!(Session::validators().contains(&11));

		on_offence_in_era(
			&[OffenceDetails {
				offender: (
					11,
Gavin Wood's avatar
Gavin Wood committed
					Staking::eras_stakers(Staking::active_era().unwrap().index, 11),
			// NOTE: A 100% slash here would clean up the account, causing de-registration.
			&[Perbill::from_percent(95)],
			1,
		);

		// or non-zero.
		assert_eq!(Staking::force_era(), Forcing::NotForcing);
		assert!(<Validators<Test>>::contains_key(11));
		assert!(Session::validators().contains(&11));
#[test]
fn reporters_receive_their_slice() {
	// This test verifies that the reporters of the offence receive their slice from the slashed
	// amount.
	ExtBuilder::default().build_and_execute(|| {
		// The reporters' reward is calculated from the total exposure.
Kian Paimani's avatar
Kian Paimani committed
		let initial_balance = 1125;

Gavin Wood's avatar
Gavin Wood committed
		assert_eq!(Staking::eras_stakers(Staking::active_era().unwrap().index, 11).total, initial_balance);
			&[OffenceDetails {
				offender: (
					11,
Gavin Wood's avatar
Gavin Wood committed
					Staking::eras_stakers(Staking::active_era().unwrap().index, 11),
				),
				reporters: vec![1, 2],
			}],
			&[Perbill::from_percent(50)],
		);

		// F1 * (reward_proportion * slash - 0)
		// 50% * (10% * initial_balance / 2)
		let reward = (initial_balance / 20) / 2;
		let reward_each = reward / 2; // split into two pieces.
		assert_eq!(Balances::free_balance(1), 10 + reward_each);
		assert_eq!(Balances::free_balance(2), 20 + reward_each);
fn subsequent_reports_in_same_span_pay_out_less() {
	// This test verifies that the reporters of the offence receive their slice from the slashed
	// amount, but less and less if they submit multiple reports in one span.
	ExtBuilder::default().build_and_execute(|| {
		// The reporters' reward is calculated from the total exposure.
		let initial_balance = 1125;

Gavin Wood's avatar
Gavin Wood committed
		assert_eq!(Staking::eras_stakers(Staking::active_era().unwrap().index, 11).total, initial_balance);
		on_offence_now(
			&[OffenceDetails {
				offender: (
					11,
Gavin Wood's avatar
Gavin Wood committed
					Staking::eras_stakers(Staking::active_era().unwrap().index, 11),
				),
				reporters: vec![1],
			}],
			&[Perbill::from_percent(20)],
		);

		// F1 * (reward_proportion * slash - 0)
		// 50% * (10% * initial_balance * 20%)
		let reward = (initial_balance / 5) / 20;
		assert_eq!(Balances::free_balance(1), 10 + reward);

		on_offence_now(
			&[OffenceDetails {
				offender: (
					11,
Gavin Wood's avatar
Gavin Wood committed
					Staking::eras_stakers(Staking::active_era().unwrap().index, 11),
				),
				reporters: vec![1],
			}],
			&[Perbill::from_percent(50)],
		);

		let prior_payout = reward;

		// F1 * (reward_proportion * slash - prior_payout)
		// 50% * (10% * (initial_balance / 2) - prior_payout)
		let reward = ((initial_balance / 20) - prior_payout) / 2;
		assert_eq!(Balances::free_balance(1), 10 + prior_payout + reward);
	});
}

#[test]
fn invulnerables_are_not_slashed() {
	// For invulnerable validators no slashing is performed.
	ExtBuilder::default().invulnerables(vec![11]).build_and_execute(|| {
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(21), 2000);
Gavin Wood's avatar
Gavin Wood committed
		let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 21);
		let initial_balance = Staking::slashable_balance_of(&21);

		let nominator_balances: Vec<_> = exposure.others
			.iter().map(|o| Balances::free_balance(&o.who)).collect();

		on_offence_now(
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (21, Staking::eras_stakers(Staking::active_era().unwrap().index, 21)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(50), Perbill::from_percent(20)],
		);
		// The validator 11 hasn't been slashed, but 21 has been.
		assert_eq!(Balances::free_balance(11), 1000);
		// 2000 - (0.2 * initial_balance)
		assert_eq!(Balances::free_balance(21), 2000 - (2 * initial_balance / 10));

		// ensure that nominators were slashed as well.
		for (initial_balance, other) in nominator_balances.into_iter().zip(exposure.others) {
			assert_eq!(
				Balances::free_balance(&other.who),
				initial_balance - (2 * other.value / 10),
			);
		}
}

#[test]
fn dont_slash_if_fraction_is_zero() {
	// Don't slash if the fraction is zero.
	ExtBuilder::default().build_and_execute(|| {
		assert_eq!(Balances::free_balance(11), 1000);
			&[OffenceDetails {
				offender: (
					11,
Gavin Wood's avatar
Gavin Wood committed
					Staking::eras_stakers(Staking::active_era().unwrap().index, 11),
				),
				reporters: vec![],
			}],
			&[Perbill::from_percent(0)],
		);

		// The validator hasn't been slashed. The new era is not forced.
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Staking::force_era(), Forcing::ForceNew);
	});
}

#[test]
fn only_slash_for_max_in_era() {
	// multiple slashes within one era are only applied if it is more than any previous slash in the
	// same era.
	ExtBuilder::default().build_and_execute(|| {
		assert_eq!(Balances::free_balance(11), 1000);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(50)],
		);

		// The validator has been slashed and has been force-chilled.
		assert_eq!(Balances::free_balance(11), 500);
		assert_eq!(Staking::force_era(), Forcing::ForceNew);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(25)],
		);

		// The validator has not been slashed additionally.
		assert_eq!(Balances::free_balance(11), 500);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(60)],
		);

		// The validator got slashed 10% more.
		assert_eq!(Balances::free_balance(11), 400);
	})
}

#[test]
fn garbage_collection_after_slashing() {
	// ensures that `SlashingSpans` and `SpanSlash` of an account is removed after reaping.
	ExtBuilder::default().existential_deposit(2).build_and_execute(|| {
		assert_eq!(Balances::free_balance(11), 256_000);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
		);

		assert_eq!(Balances::free_balance(11), 256_000 - 25_600);
		assert!(<Staking as crate::Store>::SlashingSpans::get(&11).is_some());
		assert_eq!(<Staking as crate::Store>::SpanSlash::get(&(11, 0)).amount_slashed(), &25_600);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(100)],
		);

		// validator and nominator slash in era are garbage-collected by era change,
		// so we don't test those here.

		assert_eq!(Balances::free_balance(11), 0);
Gavin Wood's avatar
Gavin Wood committed
		assert_eq!(Balances::total_balance(&11), 0);

		assert_ok!(Staking::reap_stash(Origin::NONE, 11));

		assert!(<Staking as crate::Store>::SlashingSpans::get(&11).is_none());
		assert_eq!(<Staking as crate::Store>::SpanSlash::get(&(11, 0)).amount_slashed(), &0);
	})
}

#[test]
fn garbage_collection_on_window_pruning() {
	// ensures that `ValidatorSlashInEra` and `NominatorSlashInEra` are cleared after
	// `BondingDuration`.
	ExtBuilder::default().build_and_execute(|| {
		mock::start_era(1);
		assert_eq!(Balances::free_balance(11), 1000);
		let now = Staking::active_era().unwrap().index;
		let exposure = Staking::eras_stakers(now, 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, Staking::eras_stakers(now, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
		);

		assert_eq!(Balances::free_balance(11), 900);
		assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10));

		assert!(<Staking as crate::Store>::ValidatorSlashInEra::get(&now, &11).is_some());
		assert!(<Staking as crate::Store>::NominatorSlashInEra::get(&now, &101).is_some());

		// + 1 because we have to exit the bonding window.
		for era in (0..(BondingDuration::get() + 1)).map(|offset| offset + now + 1) {
			assert!(<Staking as crate::Store>::ValidatorSlashInEra::get(&now, &11).is_some());
			assert!(<Staking as crate::Store>::NominatorSlashInEra::get(&now, &101).is_some());

			mock::start_era(era);
		}

		assert!(<Staking as crate::Store>::ValidatorSlashInEra::get(&now, &11).is_none());
		assert!(<Staking as crate::Store>::NominatorSlashInEra::get(&now, &101).is_none());
	})
}

#[test]
fn slashing_nominators_by_span_max() {
	ExtBuilder::default().build_and_execute(|| {
		mock::start_era(1);
		mock::start_era(2);
		mock::start_era(3);
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(21), 2000);
		assert_eq!(Balances::free_balance(101), 2000);
		assert_eq!(Staking::slashable_balance_of(&21), 1000);

Gavin Wood's avatar
Gavin Wood committed
		let exposure_11 = Staking::eras_stakers(Staking::active_era().unwrap().index, 11);
		let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, 21);
		let nominated_value_11 = exposure_11.others.iter().find(|o| o.who == 101).unwrap().value;
		let nominated_value_21 = exposure_21.others.iter().find(|o| o.who == 101).unwrap().value;

		on_offence_in_era(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
			2,
		);

		assert_eq!(Balances::free_balance(11), 900);

		let slash_1_amount = Perbill::from_percent(10) * nominated_value_11;
		assert_eq!(Balances::free_balance(101), 2000 - slash_1_amount);

		let expected_spans = vec![
			slashing::SlashingSpan { index: 1, start: 4, length: None },
			slashing::SlashingSpan { index: 0, start: 0, length: Some(4) },
		];

		let get_span = |account| <Staking as crate::Store>::SlashingSpans::get(&account).unwrap();

		assert_eq!(
			get_span(11).iter().collect::<Vec<_>>(),
			expected_spans,
		);

		assert_eq!(
			get_span(101).iter().collect::<Vec<_>>(),
			expected_spans,
		);

		// second slash: higher era, higher value, same span.
		on_offence_in_era(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (21, Staking::eras_stakers(Staking::active_era().unwrap().index, 21)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(30)],
			3,
		);

		// 11 was not further slashed, but 21 and 101 were.
		assert_eq!(Balances::free_balance(11), 900);
		assert_eq!(Balances::free_balance(21), 1700);

		let slash_2_amount = Perbill::from_percent(30) * nominated_value_21;
		assert!(slash_2_amount > slash_1_amount);

		// only the maximum slash in a single span is taken.
		assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount);

		// third slash: in same era and on same validator as first, higher
		// in-era value, but lower slash value than slash 2.
		on_offence_in_era(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(20)],
			2,
		);

		// 11 was further slashed, but 21 and 101 were not.
		assert_eq!(Balances::free_balance(11), 800);
		assert_eq!(Balances::free_balance(21), 1700);

		let slash_3_amount = Perbill::from_percent(20) * nominated_value_21;
		assert!(slash_3_amount < slash_2_amount);
		assert!(slash_3_amount > slash_1_amount);

		// only the maximum slash in a single span is taken.
		assert_eq!(Balances::free_balance(101), 2000 - slash_2_amount);
	});
}

#[test]
fn slashes_are_summed_across_spans() {
	ExtBuilder::default().build_and_execute(|| {
		mock::start_era(1);
		mock::start_era(2);
		mock::start_era(3);
		assert_eq!(Balances::free_balance(21), 2000);
		assert_eq!(Staking::slashable_balance_of(&21), 1000);

		let get_span = |account| <Staking as crate::Store>::SlashingSpans::get(&account).unwrap();

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (21, Staking::eras_stakers(Staking::active_era().unwrap().index, 21)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
		);

		let expected_spans = vec![
			slashing::SlashingSpan { index: 1, start: 4, length: None },
			slashing::SlashingSpan { index: 0, start: 0, length: Some(4) },
		];

		assert_eq!(get_span(21).iter().collect::<Vec<_>>(), expected_spans);
		assert_eq!(Balances::free_balance(21), 1900);

		// 21 has been force-chilled. re-signal intent to validate.
		Staking::validate(Origin::signed(20), Default::default()).unwrap();

		mock::start_era(4);

		assert_eq!(Staking::slashable_balance_of(&21), 900);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (21, Staking::eras_stakers(Staking::active_era().unwrap().index, 21)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
		);

		let expected_spans = vec![
			slashing::SlashingSpan { index: 2, start: 5, length: None },
			slashing::SlashingSpan { index: 1, start: 4, length: Some(1) },
			slashing::SlashingSpan { index: 0, start: 0, length: Some(4) },
		];

		assert_eq!(get_span(21).iter().collect::<Vec<_>>(), expected_spans);
		assert_eq!(Balances::free_balance(21), 1810);
	});
}

#[test]
fn deferred_slashes_are_deferred() {
	ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
		mock::start_era(1);
		assert_eq!(Balances::free_balance(11), 1000);
Gavin Wood's avatar
Gavin Wood committed
		let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 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 {
Gavin Wood's avatar
Gavin Wood committed
					offender: (11, Staking::eras_stakers(Staking::active_era().unwrap().index, 11)),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(10)],
		);

		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(101), 2000);
		mock::start_era(2);
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(101), 2000);
		mock::start_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_era(4);
		assert_eq!(Balances::free_balance(11), 900);
		assert_eq!(Balances::free_balance(101), 2000 - (nominated_value / 10));
	ExtBuilder::default().slash_defer_duration(2).build_and_execute(|| {
		mock::start_era(1);
		assert_eq!(Balances::free_balance(11), 1000);
Gavin Wood's avatar
Gavin Wood committed
		let exposure = Staking::eras_stakers(Staking::active_era().unwrap().index, 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_era(2);

		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(Origin::ROOT, 1, vec![]),
			Error::<Test>::EmptyTargets
		);

		assert_ok!(Staking::cancel_deferred_slash(Origin::ROOT, 1, vec![0]));
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(101), 2000);
		mock::start_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_era(4);

		// the first slash for 10% was cancelled, so no effect.
		assert_eq!(Balances::free_balance(11), 1000);
		assert_eq!(Balances::free_balance(101), 2000);
		mock::start_era(5);

		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_era(1);
		assert_eq!(Balances::free_balance(11), 1000);
Gavin Wood's avatar
Gavin Wood committed
		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(10)],
		);

		on_offence_now(
			&[
				OffenceDetails {
Gavin Wood's avatar
Gavin Wood committed
					offender: (21, Staking::eras_stakers(Staking::active_era().unwrap().index, 21)),
					reporters: vec![],
				}
			],
			&[Perbill::from_percent(10)],
		);

		on_offence_now(
			&[
				OffenceDetails {
					offender: (11, exposure.clone()),
					reporters: vec![],
				},
			],
			&[Perbill::from_percent(25)],
		);

		on_offence_now(
			&[
				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!(<Staking as Store>::UnappliedSlashes::get(&1).len(), 5);

		// fails if list is not sorted
		assert_noop!(
			Staking::cancel_deferred_slash(Origin::ROOT, 1, vec![2, 0, 4]),
			Error::<Test>::NotSortedAndUnique
		);
		// fails if list is not unique
		assert_noop!(
			Staking::cancel_deferred_slash(Origin::ROOT, 1, vec![0, 2, 2]),
			Error::<Test>::NotSortedAndUnique
		);
		// fails if bad index
		assert_noop!(
			Staking::cancel_deferred_slash(Origin::ROOT, 1, vec![1, 2, 3, 4, 5]),
			Error::<Test>::InvalidSlashIndex
		);

		assert_ok!(Staking::cancel_deferred_slash(Origin::ROOT, 1, vec![0, 2, 4]));

		let slashes = <Staking as Store>::UnappliedSlashes::get(&1);
		assert_eq!(slashes.len(), 2);
		assert_eq!(slashes[0].validator, 21);
		assert_eq!(slashes[1].validator, 42);
mod offchain_phragmen {
	use crate::*;
	use frame_support::{assert_noop, assert_ok};
	use sp_runtime::transaction_validity::TransactionSource;
	use mock::*;
	use parking_lot::RwLock;
	use sp_core::offchain::{
		testing::{PoolState, TestOffchainExt, TestTransactionPoolExt},
		OffchainExt, TransactionPoolExt,
	};
	use sp_io::TestExternalities;
	use sp_phragmen::StakedAssignment;
	use frame_support::traits::OffchainWorker;
	use std::sync::Arc;
	use substrate_test_utils::assert_eq_uvec;

	fn percent(x: u16) -> OffchainAccuracy {
		OffchainAccuracy::from_percent(x)
	}

	/// setup a new set of validators and nominator storage items independent of the parent mock
	/// file. This produces a edge graph that can be reduced.
	fn build_offchain_phragmen_test_ext() {
		for i in (10..=40).step_by(10) {
			// Note: we respect the convention of the mock (10, 11 pairs etc.) since these accounts
			// have corresponding keys in session which makes everything more ergonomic and
			// realistic.
			bond_validator(i + 1, i, 100);
		}

		let mut voter = 1;
		bond_nominator(voter, 1000 + voter, 100, vec![11]);
		voter = 2;
		bond_nominator(voter, 1000 + voter, 100, vec![11, 11]);
		voter = 3;
		bond_nominator(voter, 1000 + voter, 100, vec![21, 41]);
		voter = 4;
		bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]);
		voter = 5;
		bond_nominator(voter, 1000 + voter, 100, vec![21, 31, 41]);
	}

	fn offchainify(ext: &mut TestExternalities) -> Arc<RwLock<PoolState>> {
		let (offchain, _state) = TestOffchainExt::new();
		let (pool, state) = TestTransactionPoolExt::new();

		ext.register_extension(OffchainExt::new(offchain));
		ext.register_extension(TransactionPoolExt::new(pool));

		state
	}

	#[test]
	fn is_current_session_final_works() {
		ExtBuilder::default()
			.session_per_era(3)
			.build()
			.execute_with(|| {
				mock::start_era(1);
				assert_eq!(Session::current_index(), 3);
				assert_eq!(Staking::current_era(), Some(1));
				assert_eq!(Staking::is_current_session_final(), false);

				start_session(4);
				assert_eq!(Session::current_index(), 4);
				assert_eq!(Staking::current_era(), Some(1));
				assert_eq!(Staking::is_current_session_final(), true);

				start_session(5);
				assert_eq!(Session::current_index(), 5);
				// era changed.
				assert_eq!(Staking::current_era(), Some(2));
				assert_eq!(Staking::is_current_session_final(), false);
			})
	}

	#[test]
	fn offchain_election_flag_is_triggered() {
		ExtBuilder::default()
			.session_per_era(5)
			.session_length(10)
			.election_lookahead(3)
			.build()
			.execute_with(|| {
				run_to_block(7);
				assert_session_era!(0, 0);

				run_to_block(10);
				assert_session_era!(1, 0);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
				assert!(Staking::snapshot_nominators().is_none());
				assert!(Staking::snapshot_validators().is_none());

				run_to_block(36);
				assert_session_era!(3, 0);

				// fist era has session 0, which has 0 blocks length, so we have in total 40 blocks
				// in the era.
				run_to_block(37);
				assert_session_era!(3, 0);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37));
				assert!(Staking::snapshot_nominators().is_some());
				assert!(Staking::snapshot_validators().is_some());

				run_to_block(38);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37));

				run_to_block(39);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(37));

				run_to_block(40);
				assert_session_era!(4, 0);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
				assert!(Staking::snapshot_nominators().is_none());
				assert!(Staking::snapshot_validators().is_none());

				run_to_block(86);
				assert_session_era!(8, 1);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
				assert!(Staking::snapshot_nominators().is_none());
				assert!(Staking::snapshot_validators().is_none());

				// second era onwards has 50 blocks per era.
				run_to_block(87);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(87));
				assert!(Staking::snapshot_nominators().is_some());
				assert!(Staking::snapshot_validators().is_some());

				run_to_block(90);
				assert_session_era!(9, 1);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
				assert!(Staking::snapshot_nominators().is_none());
				assert!(Staking::snapshot_validators().is_none());
			})
	}

	#[test]
	fn offchain_election_flag_is_triggered_when_forcing() {
		ExtBuilder::default()
			.session_per_era(5)
			.session_length(10)
			.election_lookahead(3)
			.build()
			.execute_with(|| {
				run_to_block(7);
				assert_session_era!(0, 0);

				run_to_block(12);
				ForceEra::put(Forcing::ForceNew);
				run_to_block(13);
				assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);

				run_to_block(17); // instead of 47
				assert_eq!(Staking::era_election_status(), ElectionStatus::Open(17));
			})
	}

	#[test]
	fn election_on_chain_fallback_works() {
		ExtBuilder::default().build_and_execute(|| {
			start_session(1);
			start_session(2);
			assert_eq!(Staking::era_election_status(), ElectionStatus::Closed);
			// some election must have happened by now.
			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::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, |_| {});
				assert_ok!(Staking::submit_election_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 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, |_| {});
				assert_ok!(Staking::submit_election_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, |_| {});
				Staking::kill_stakers_snapshot();