Skip to content
tests.rs 247 KiB
Newer Older
			// payee needs to be updated to a non-stash account.
			assert_ok!(<Staking as StakingInterface>::update_payee(&200, &201));

			// ensure the balance is not locked anymore
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &200), 0);

			// and they are marked as virtual stakers
			assert_eq!(Pallet::<Test>::is_virtual_staker(&200), true);
		});
	}

	#[test]
	fn virtual_nominators_are_lazily_slashed() {
		ExtBuilder::default().build_and_execute(|| {
			mock::start_active_era(1);
			let slash_percent = Perbill::from_percent(5);
			let initial_exposure = Staking::eras_stakers(active_era(), &11);
			// 101 is a nominator for 11
			assert_eq!(initial_exposure.others.first().unwrap().who, 101);
			// make 101 a virtual nominator
			<Staking as StakingUnchecked>::migrate_to_virtual_staker(&101);
			// set payee different to self.
			assert_ok!(<Staking as StakingInterface>::update_payee(&101, &102));

			// cache values
			let nominator_stake = Staking::ledger(101.into()).unwrap().active;
			let nominator_balance = balances(&101).0;
			let validator_stake = Staking::ledger(11.into()).unwrap().active;
			let validator_balance = balances(&11).0;
			let exposed_stake = initial_exposure.total;
			let exposed_validator = initial_exposure.own;
			let exposed_nominator = initial_exposure.others.first().unwrap().value;

			// 11 goes offline
			on_offence_now(
				&[OffenceDetails { offender: (11, initial_exposure.clone()), reporters: vec![] }],
				&[slash_percent],
			);

			let slash_amount = slash_percent * exposed_stake;
			let validator_share =
				Perbill::from_rational(exposed_validator, exposed_stake) * slash_amount;
			let nominator_share =
				Perbill::from_rational(exposed_nominator, exposed_stake) * slash_amount;

			// both slash amounts need to be positive for the test to make sense.
			assert!(validator_share > 0);
			assert!(nominator_share > 0);

			// both stakes must have been decreased pro-rata.
			assert_eq!(
				Staking::ledger(101.into()).unwrap().active,
				nominator_stake - nominator_share
			);
			assert_eq!(
				Staking::ledger(11.into()).unwrap().active,
				validator_stake - validator_share
			);

			// validator balance is slashed as usual
			assert_eq!(balances(&11).0, validator_balance - validator_share);
			// Because slashing happened.
			assert!(is_disabled(11));

			// but virtual nominator's balance is not slashed.
			assert_eq!(Balances::free_balance(&101), nominator_balance);
			// but slash is broadcasted to slash observers.
			assert_eq!(SlashObserver::get().get(&101).unwrap(), &nominator_share);
		})
	}
}
mod ledger {
	use super::*;

	#[test]
	fn paired_account_works() {
		ExtBuilder::default().try_state(false).build_and_execute(|| {
			assert_ok!(Staking::bond(
				RuntimeOrigin::signed(10),
				100,
				RewardDestination::Account(10)
			));

			assert_eq!(<Bonded<Test>>::get(&10), Some(10));
			assert_eq!(
				StakingLedger::<Test>::paired_account(StakingAccount::Controller(10)),
				Some(10)
			);
			assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(10)), Some(10));

			assert_eq!(<Bonded<Test>>::get(&42), None);
			assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Controller(42)), None);
			assert_eq!(StakingLedger::<Test>::paired_account(StakingAccount::Stash(42)), None);

			// bond manually stash with different controller. This is deprecated but the migration
			// has not been complete yet (controller: 100, stash: 200)
			assert_ok!(bond_controller_stash(100, 200));
			assert_eq!(<Bonded<Test>>::get(&200), Some(100));
			assert_eq!(
				StakingLedger::<Test>::paired_account(StakingAccount::Controller(100)),
				Some(200)
			);
			assert_eq!(
				StakingLedger::<Test>::paired_account(StakingAccount::Stash(200)),
				Some(100)
			);
		})
	}

	#[test]
	fn get_ledger_works() {
		ExtBuilder::default().try_state(false).build_and_execute(|| {
			// stash does not exist
			assert!(StakingLedger::<Test>::get(StakingAccount::Stash(42)).is_err());

			// bonded and paired
			assert_eq!(<Bonded<Test>>::get(&11), Some(11));

			match StakingLedger::<Test>::get(StakingAccount::Stash(11)) {
				Ok(ledger) => {
					assert_eq!(ledger.controller(), Some(11));
					assert_eq!(ledger.stash, 11);
				},
				Err(_) => panic!("staking ledger must exist"),
			};

			// bond manually stash with different controller. This is deprecated but the migration
			// has not been complete yet (controller: 100, stash: 200)
			assert_ok!(bond_controller_stash(100, 200));
			assert_eq!(<Bonded<Test>>::get(&200), Some(100));

			match StakingLedger::<Test>::get(StakingAccount::Stash(200)) {
				Ok(ledger) => {
					assert_eq!(ledger.controller(), Some(100));
					assert_eq!(ledger.stash, 200);
				},
				Err(_) => panic!("staking ledger must exist"),
			};

			match StakingLedger::<Test>::get(StakingAccount::Controller(100)) {
				Ok(ledger) => {
					assert_eq!(ledger.controller(), Some(100));
					assert_eq!(ledger.stash, 200);
				},
				Err(_) => panic!("staking ledger must exist"),
			};
		})
	}

	#[test]
	fn get_ledger_bad_state_fails() {
		ExtBuilder::default().has_stakers(false).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// Case 1: double bonded but not corrupted:
			// stash 444 has controller 555:
			assert_eq!(Bonded::<Test>::get(444), Some(555));
			assert_eq!(Ledger::<Test>::get(555).unwrap().stash, 444);
			// stash 444 is also a controller of 333:
			assert_eq!(Bonded::<Test>::get(333), Some(444));
			assert_eq!(
				StakingLedger::<Test>::paired_account(StakingAccount::Stash(333)),
				Some(444)
			);
			assert_eq!(Ledger::<Test>::get(444).unwrap().stash, 333);
			// although 444 is double bonded (it is a controller and a stash of different ledgers),
			// we can safely retrieve the ledger and mutate it since the correct ledger is
			// returned.
			let ledger_result = StakingLedger::<Test>::get(StakingAccount::Stash(444));
			assert_eq!(ledger_result.unwrap().stash, 444); // correct ledger.
			let ledger_result = StakingLedger::<Test>::get(StakingAccount::Controller(444));
			assert_eq!(ledger_result.unwrap().stash, 333); // correct ledger.
			// fetching ledger 333 by its stash works.
			let ledger_result = StakingLedger::<Test>::get(StakingAccount::Stash(333));
			assert_eq!(ledger_result.unwrap().stash, 333);

			// Case 2: corrupted ledger bonding.
			// in this case, we simulate what happens when fetching a ledger by stash returns a
			// ledger with a different stash. when this happens, we return an error instead of the
			// ledger to prevent ledger mutations.
			let mut ledger = Ledger::<Test>::get(444).unwrap();
			assert_eq!(ledger.stash, 333);
			ledger.stash = 444;
			Ledger::<Test>::insert(444, ledger);

			// now, we are prevented from fetching the ledger by stash from 1. It's associated
			// controller (2) is now bonding a ledger with a different stash (2, not 1).
			assert!(StakingLedger::<Test>::get(StakingAccount::Stash(333)).is_err());
	#[test]
	fn bond_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert!(!StakingLedger::<Test>::is_bonded(StakingAccount::Stash(42)));
			assert!(<Bonded<Test>>::get(&42).is_none());

			let mut ledger: StakingLedger<Test> = StakingLedger::default_from(42);
			let reward_dest = RewardDestination::Account(10);

			assert_ok!(ledger.clone().bond(reward_dest));
			assert!(StakingLedger::<Test>::is_bonded(StakingAccount::Stash(42)));
			assert!(<Bonded<Test>>::get(&42).is_some());
			assert_eq!(<Payee<Test>>::get(&42), Some(reward_dest));

			// cannot bond again.
			assert!(ledger.clone().bond(reward_dest).is_err());

			// once bonded, update works as expected.
			ledger.legacy_claimed_rewards = bounded_vec![1];
			assert_ok!(ledger.update());
		})
	}

	#[test]
	fn bond_controller_cannot_be_stash_works() {
		ExtBuilder::default().build_and_execute(|| {
			let (stash, controller) = testing_utils::create_unique_stash_controller::<Test>(
				0,
				10,
				RewardDestination::Staked,
				false,
			)
			.unwrap();

			assert_eq!(Bonded::<Test>::get(stash), Some(controller));
			assert_eq!(Ledger::<Test>::get(controller).map(|l| l.stash), Some(stash));

			// existing controller should not be able become a stash.
			assert_noop!(
				Staking::bond(RuntimeOrigin::signed(controller), 10, RewardDestination::Staked),
				Error::<Test>::AlreadyPaired,
			);
		})
	}

	#[test]
	fn is_bonded_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert!(!StakingLedger::<Test>::is_bonded(StakingAccount::Stash(42)));
			assert!(!StakingLedger::<Test>::is_bonded(StakingAccount::Controller(42)));

			// adds entry to Bonded without Ledger pair (should not happen).
			<Bonded<Test>>::insert(42, 42);
			assert!(!StakingLedger::<Test>::is_bonded(StakingAccount::Controller(42)));

			assert_eq!(<Bonded<Test>>::get(&11), Some(11));
			assert!(StakingLedger::<Test>::is_bonded(StakingAccount::Stash(11)));
			assert!(StakingLedger::<Test>::is_bonded(StakingAccount::Controller(11)));

			<Bonded<Test>>::remove(42); // ensures try-state checks pass.
		})
	}

	#[test]
	#[allow(deprecated)]
	fn set_payee_errors_on_controller_destination() {
		ExtBuilder::default().build_and_execute(|| {
			Payee::<Test>::insert(11, RewardDestination::Staked);
			assert_noop!(
				Staking::set_payee(RuntimeOrigin::signed(11), RewardDestination::Controller),
				Error::<Test>::ControllerDeprecated
			);
			assert_eq!(Payee::<Test>::get(&11), Some(RewardDestination::Staked));
		})
	}

	#[test]
	#[allow(deprecated)]
	fn update_payee_migration_works() {
		ExtBuilder::default().build_and_execute(|| {
			// migrate a `Controller` variant to `Account` variant.
			Payee::<Test>::insert(11, RewardDestination::Controller);
			assert_eq!(Payee::<Test>::get(&11), Some(RewardDestination::Controller));
			assert_ok!(Staking::update_payee(RuntimeOrigin::signed(11), 11));
			assert_eq!(Payee::<Test>::get(&11), Some(RewardDestination::Account(11)));

			// Do not migrate a variant if not `Controller`.
			Payee::<Test>::insert(21, RewardDestination::Stash);
			assert_eq!(Payee::<Test>::get(&21), Some(RewardDestination::Stash));
			assert_noop!(
				Staking::update_payee(RuntimeOrigin::signed(11), 21),
				Error::<Test>::NotController
			);
			assert_eq!(Payee::<Test>::get(&21), Some(RewardDestination::Stash));

	#[test]
	fn deprecate_controller_batch_works_full_weight() {
		ExtBuilder::default().try_state(false).build_and_execute(|| {
			// Given:

			let start = 1001;
			let mut controllers: Vec<_> = vec![];
			for n in start..(start + MaxControllersInDeprecationBatch::get()).into() {
				let ctlr: u64 = n.into();
				let stash: u64 = (n + 10000).into();

				Ledger::<Test>::insert(
					ctlr,
					StakingLedger {
						controller: None,
						total: (10 + ctlr).into(),
						active: (10 + ctlr).into(),
						..StakingLedger::default_from(stash)
					},
				);
				Bonded::<Test>::insert(stash, ctlr);
				Payee::<Test>::insert(stash, RewardDestination::Staked);

				controllers.push(ctlr);
			}

			// When:

			let bounded_controllers: BoundedVec<
				_,
				<Test as Config>::MaxControllersInDeprecationBatch,
			> = BoundedVec::try_from(controllers).unwrap();

			// Only `AdminOrigin` can sign.
			assert_noop!(
				Staking::deprecate_controller_batch(
					RuntimeOrigin::signed(2),
					bounded_controllers.clone()
				),
				BadOrigin
			);

			let result =
				Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers);
			assert_ok!(result);
			assert_eq!(
				result.unwrap().actual_weight.unwrap(),
				<Test as Config>::WeightInfo::deprecate_controller_batch(
					<Test as Config>::MaxControllersInDeprecationBatch::get()
				)
			);

			// Then:

			for n in start..(start + MaxControllersInDeprecationBatch::get()).into() {
				let ctlr: u64 = n.into();
				let stash: u64 = (n + 10000).into();

				// Ledger no longer keyed by controller.
				assert_eq!(Ledger::<Test>::get(ctlr), None);
				// Bonded now maps to the stash.
				assert_eq!(Bonded::<Test>::get(stash), Some(stash));

				// Ledger is now keyed by stash.
				let ledger_updated = Ledger::<Test>::get(stash).unwrap();
				assert_eq!(ledger_updated.stash, stash);

				// Check `active` and `total` values match the original ledger set by controller.
				assert_eq!(ledger_updated.active, (10 + ctlr).into());
				assert_eq!(ledger_updated.total, (10 + ctlr).into());
			}
		})
	}

	#[test]
	fn deprecate_controller_batch_works_half_weight() {
		ExtBuilder::default().build_and_execute(|| {
			// Given:

			let start = 1001;
			let mut controllers: Vec<_> = vec![];
			for n in start..(start + MaxControllersInDeprecationBatch::get()).into() {
				let ctlr: u64 = n.into();

				// Only half of entries are unique pairs.
				let stash: u64 = if n % 2 == 0 { (n + 10000).into() } else { ctlr };

				Ledger::<Test>::insert(
					ctlr,
					StakingLedger { controller: None, ..StakingLedger::default_from(stash) },
				);
				Bonded::<Test>::insert(stash, ctlr);
				Payee::<Test>::insert(stash, RewardDestination::Staked);

				controllers.push(ctlr);
			}

			// When:
			let bounded_controllers: BoundedVec<
				_,
				<Test as Config>::MaxControllersInDeprecationBatch,
			> = BoundedVec::try_from(controllers.clone()).unwrap();

			let result =
				Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers);
			assert_ok!(result);
			assert_eq!(
				result.unwrap().actual_weight.unwrap(),
				<Test as Config>::WeightInfo::deprecate_controller_batch(controllers.len() as u32)
			);

			// Then:

			for n in start..(start + MaxControllersInDeprecationBatch::get()).into() {
				let unique_pair = n % 2 == 0;
				let ctlr: u64 = n.into();
				let stash: u64 = if unique_pair { (n + 10000).into() } else { ctlr };

				// Side effect of migration for unique pair.
				if unique_pair {
					assert_eq!(Ledger::<Test>::get(ctlr), None);
				}
				// Bonded maps to the stash.
				assert_eq!(Bonded::<Test>::get(stash), Some(stash));

				// Ledger is keyed by stash.
				let ledger_updated = Ledger::<Test>::get(stash).unwrap();
				assert_eq!(ledger_updated.stash, stash);
			}
		})
	}

	#[test]
	fn deprecate_controller_batch_skips_unmigrated_controller_payees() {
		ExtBuilder::default().try_state(false).build_and_execute(|| {
			// Given:

			let stash: u64 = 1000;
			let ctlr: u64 = 1001;

			Ledger::<Test>::insert(
				ctlr,
				StakingLedger { controller: None, ..StakingLedger::default_from(stash) },
			);
			Bonded::<Test>::insert(stash, ctlr);
			#[allow(deprecated)]
			Payee::<Test>::insert(stash, RewardDestination::Controller);

			// When:

			let bounded_controllers: BoundedVec<
				_,
				<Test as Config>::MaxControllersInDeprecationBatch,
			> = BoundedVec::try_from(vec![ctlr]).unwrap();

			let result =
				Staking::deprecate_controller_batch(RuntimeOrigin::root(), bounded_controllers);
			assert_ok!(result);
			assert_eq!(
				result.unwrap().actual_weight.unwrap(),
				<Test as Config>::WeightInfo::deprecate_controller_batch(1 as u32)
			);

			// Then:

			// Esure deprecation did not happen.
			assert_eq!(Ledger::<Test>::get(ctlr).is_some(), true);

			// Bonded still keyed by controller.
			assert_eq!(Bonded::<Test>::get(stash), Some(ctlr));

			// Ledger is still keyed by controller.
			let ledger_updated = Ledger::<Test>::get(ctlr).unwrap();
			assert_eq!(ledger_updated.stash, stash);
		})
	}

	#[test]
	fn deprecate_controller_batch_with_bad_state_ok() {
		ExtBuilder::default().has_stakers(false).nominate(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// now let's deprecate all the controllers for all the existing ledgers.
			let bounded_controllers: BoundedVec<
				_,
				<Test as Config>::MaxControllersInDeprecationBatch,
			> = BoundedVec::try_from(vec![333, 444, 555, 777]).unwrap();

			assert_ok!(Staking::deprecate_controller_batch(
				RuntimeOrigin::root(),
				bounded_controllers
			));

			assert_eq!(
				*staking_events().last().unwrap(),
				Event::ControllerBatchDeprecated { failures: 0 }
			);
		})
	}

	#[test]
	fn deprecate_controller_batch_with_bad_state_failures() {
		ExtBuilder::default().has_stakers(false).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// now let's deprecate all the controllers for all the existing ledgers.
			let bounded_controllers: BoundedVec<
				_,
				<Test as Config>::MaxControllersInDeprecationBatch,
			> = BoundedVec::try_from(vec![777, 555, 444, 333]).unwrap();

			assert_ok!(Staking::deprecate_controller_batch(
				RuntimeOrigin::root(),
				bounded_controllers
			));

			assert_eq!(
				*staking_events().last().unwrap(),
				Event::ControllerBatchDeprecated { failures: 2 }
			);
		})
	}

	#[test]
	fn set_controller_with_bad_state_ok() {
		ExtBuilder::default().has_stakers(false).nominate(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// in this case, setting controller works due to the ordering of the calls.
			assert_ok!(Staking::set_controller(RuntimeOrigin::signed(333)));
			assert_ok!(Staking::set_controller(RuntimeOrigin::signed(444)));
			assert_ok!(Staking::set_controller(RuntimeOrigin::signed(555)));
		})
	}

	#[test]
	fn set_controller_with_bad_state_fails() {
		ExtBuilder::default().has_stakers(false).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// setting the controller of ledger associated with stash 555 fails since its stash is a
			// controller of another ledger.
			assert_noop!(
				Staking::set_controller(RuntimeOrigin::signed(555)),
				Error::<Test>::BadState
			);
			assert_noop!(
				Staking::set_controller(RuntimeOrigin::signed(444)),
				Error::<Test>::BadState
			);
7543 7544 7545 7546 7547 7548 7549 7550 7551 7552 7553 7554 7555 7556 7557 7558 7559 7560 7561 7562 7563 7564 7565 7566 7567 7568 7569 7570 7571 7572 7573 7574 7575 7576 7577 7578 7579 7580 7581 7582 7583 7584 7585 7586 7587 7588 7589 7590 7591 7592 7593 7594 7595 7596 7597 7598 7599 7600 7601 7602 7603 7604 7605 7606 7607 7608 7609 7610 7611 7612 7613 7614 7615 7616 7617 7618 7619 7620 7621 7622 7623 7624 7625 7626 7627 7628 7629 7630 7631 7632 7633 7634 7635 7636 7637 7638 7639 7640 7641 7642 7643 7644 7645 7646 7647 7648 7649 7650 7651 7652 7653 7654 7655 7656 7657 7658 7659 7660 7661 7662 7663 7664 7665 7666 7667 7668 7669 7670 7671 7672 7673 7674 7675 7676 7677 7678 7679 7680 7681 7682 7683 7684 7685 7686 7687 7688 7689 7690 7691 7692 7693 7694 7695 7696 7697 7698 7699 7700 7701 7702 7703 7704 7705 7706 7707 7708 7709 7710 7711 7712 7713 7714 7715 7716 7717 7718 7719 7720 7721 7722 7723 7724 7725 7726 7727 7728 7729 7730 7731 7732 7733 7734 7735 7736 7737 7738 7739 7740 7741 7742 7743 7744 7745 7746 7747 7748 7749 7750 7751 7752 7753 7754 7755 7756 7757 7758 7759 7760 7761 7762 7763 7764 7765 7766 7767 7768 7769 7770 7771 7772 7773 7774 7775 7776 7777 7778 7779 7780 7781 7782 7783 7784 7785 7786 7787 7788 7789 7790 7791 7792 7793 7794 7795 7796 7797 7798 7799 7800 7801 7802 7803 7804 7805 7806 7807 7808 7809 7810 7811 7812 7813 7814 7815 7816 7817 7818 7819 7820 7821 7822 7823 7824 7825 7826 7827 7828 7829 7830 7831 7832 7833 7834 7835 7836 7837 7838 7839 7840 7841 7842 7843 7844 7845 7846 7847 7848 7849 7850 7851 7852 7853 7854 7855 7856 7857 7858 7859 7860 7861 7862 7863 7864 7865 7866 7867 7868 7869 7870 7871 7872 7873 7874 7875 7876 7877 7878 7879 7880 7881 7882 7883 7884 7885 7886 7887 7888 7889 7890 7891 7892 7893 7894 7895 7896 7897 7898 7899 7900 7901 7902 7903 7904 7905 7906 7907 7908 7909 7910 7911 7912 7913 7914 7915 7916 7917 7918 7919 7920 7921 7922 7923 7924 7925
			assert_ok!(Staking::set_controller(RuntimeOrigin::signed(333)));
		})
	}
}

mod ledger_recovery {
	use super::*;

	#[test]
	fn inspect_recovery_ledger_simple_works() {
		ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// non corrupted ledger.
			assert_eq!(Staking::inspect_bond_state(&11).unwrap(), LedgerIntegrityState::Ok);

			// non bonded stash.
			assert!(Bonded::<Test>::get(&1111).is_none());
			assert!(Staking::inspect_bond_state(&1111).is_err());

			// double bonded but not corrupted.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
		})
	}

	#[test]
	fn inspect_recovery_ledger_corupted_killed_works() {
		ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333);

			// get into corrupted and killed ledger state by killing a corrupted ledger:
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			// kill(333)
			// (444, 444) -> corrupted and None.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// 333 is corrupted since it's controller is linking 444 ledger.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
			// 444 however is OK.
			assert_eq!(Staking::inspect_bond_state(&444).unwrap(), LedgerIntegrityState::Ok);

			// kill the corrupted ledger that is associated with stash 333.
			assert_ok!(StakingLedger::<Test>::kill(&333));

			// 333 bond is no more but it returns `BadState` because the lock on this stash is
			// still set (see checks below).
			assert_eq!(Staking::inspect_bond_state(&333), Err(Error::<Test>::BadState));
			// now the *other* ledger associated with 444 has been corrupted and killed (None).
			assert_eq!(
				Staking::inspect_bond_state(&444),
				Ok(LedgerIntegrityState::CorruptedKilled)
			);

			// side effects on 333 - ledger, bonded, payee, lock should be completely empty.
			// however, 333 lock remains.
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // NOK
			assert!(Bonded::<Test>::get(&333).is_none()); // OK
			assert!(Payee::<Test>::get(&333).is_none()); // OK
			assert!(Ledger::<Test>::get(&444).is_none()); // OK

			// side effects on 444 - ledger, bonded, payee, lock should remain be intact.
			// however, 444 lock was removed.
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), 0); // NOK
			assert!(Bonded::<Test>::get(&444).is_some()); // OK
			assert!(Payee::<Test>::get(&444).is_some()); // OK
			assert!(Ledger::<Test>::get(&555).is_none()); // NOK

			assert!(Staking::do_try_state(System::block_number()).is_err());
		})
	}

	#[test]
	fn inspect_recovery_ledger_corupted_killed_other_works() {
		ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333);

			// get into corrupted and killed ledger state by killing a corrupted ledger:
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			// kill(444)
			// (333, 444) -> corrupted and None
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// 333 is corrupted since it's controller is linking 444 ledger.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
			// 444 however is OK.
			assert_eq!(Staking::inspect_bond_state(&444).unwrap(), LedgerIntegrityState::Ok);

			// kill the *other* ledger that is double bonded but not corrupted.
			assert_ok!(StakingLedger::<Test>::kill(&444));

			// now 333 is corrupted and None through the *other* ledger being killed.
			assert_eq!(
				Staking::inspect_bond_state(&333).unwrap(),
				LedgerIntegrityState::CorruptedKilled,
			);
			// 444 is cleaned and not a stash anymore; no lock left behind.
			assert_eq!(Ledger::<Test>::get(&444), None);
			assert_eq!(Staking::inspect_bond_state(&444), Err(Error::<Test>::NotStash));

			// side effects on 333 - ledger, bonded, payee, lock should be intact.
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before); // OK
			assert_eq!(Bonded::<Test>::get(&333), Some(444)); // OK
			assert!(Payee::<Test>::get(&333).is_some()); // OK
											 // however, ledger associated with its controller was killed.
			assert!(Ledger::<Test>::get(&444).is_none()); // NOK

			// side effects on 444 - ledger, bonded, payee, lock should be completely removed.
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), 0); // OK
			assert!(Bonded::<Test>::get(&444).is_none()); // OK
			assert!(Payee::<Test>::get(&444).is_none()); // OK
			assert!(Ledger::<Test>::get(&555).is_none()); // OK

			assert!(Staking::do_try_state(System::block_number()).is_err());
		})
	}

	#[test]
	fn inspect_recovery_ledger_lock_corrupted_works() {
		ExtBuilder::default().has_stakers(true).try_state(false).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// get into lock corrupted ledger state by bond_extra on a ledger that is double bonded
			// with a corrupted ledger.
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			//  bond_extra(333, 10) -> lock corrupted on 444
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);
			bond_extra_no_checks(&333, 10);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// 333 is corrupted since it's controller is linking 444 ledger.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
			// 444 ledger is not corrupted but locks got out of sync.
			assert_eq!(
				Staking::inspect_bond_state(&444).unwrap(),
				LedgerIntegrityState::LockCorrupted
			);
		})
	}

	// Corrupted ledger restore.
	//
	// * Double bonded and corrupted ledger.
	#[test]
	fn restore_ledger_corrupted_works() {
		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// get into corrupted and killed ledger state.
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// recover the ledger bonded by 333 stash.
			assert_ok!(Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None));

			// try-state checks are ok now.
			assert_ok!(Staking::do_try_state(System::block_number()));
		})
	}

	// Corrupted and killed ledger restore.
	//
	// * Double bonded and corrupted ledger.
	// * Ledger killed by own controller.
	#[test]
	fn restore_ledger_corrupted_killed_works() {
		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// ledger.total == lock
			let total_444_before_corruption = Balances::balance_locked(crate::STAKING_ID, &444);

			// get into corrupted and killed ledger state by killing a corrupted ledger:
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			// kill(333)
			// (444, 444) -> corrupted and None.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			// kill the corrupted ledger that is associated with stash 333.
			assert_ok!(StakingLedger::<Test>::kill(&333));

			// 333 bond is no more but it returns `BadState` because the lock on this stash is
			// still set (see checks below).
			assert_eq!(Staking::inspect_bond_state(&333), Err(Error::<Test>::BadState));
			// now the *other* ledger associated with 444 has been corrupted and killed (None).
			assert!(Staking::ledger(StakingAccount::Stash(444)).is_err());

			// try-state should fail.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// recover the ledger bonded by 333 stash.
			assert_ok!(Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None));

			// for the try-state checks to pass, we also need to recover the stash 444 which is
			// corrupted too by proxy of kill(333). Currently, both the lock and the ledger of 444
			// have been cleared so we need to provide the new amount to restore the ledger.
			assert_noop!(
				Staking::restore_ledger(RuntimeOrigin::root(), 444, None, None, None),
				Error::<Test>::CannotRestoreLedger
			);

			assert_ok!(Staking::restore_ledger(
				RuntimeOrigin::root(),
				444,
				None,
				Some(total_444_before_corruption),
				None,
			));

			// try-state checks are ok now.
			assert_ok!(Staking::do_try_state(System::block_number()));
		})
	}

	// Corrupted and killed by *other* ledger restore.
	//
	// * Double bonded and corrupted ledger.
	// * Ledger killed by own controller.
	#[test]
	fn restore_ledger_corrupted_killed_other_works() {
		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
			setup_double_bonded_ledgers();

			// get into corrupted and killed ledger state by killing a corrupted ledger:
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			// kill(444)
			// (333, 444) -> corrupted and None
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// 333 is corrupted since it's controller is linking 444 ledger.
			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Corrupted);
			// 444 however is OK.
			assert_eq!(Staking::inspect_bond_state(&444).unwrap(), LedgerIntegrityState::Ok);

			// kill the *other* ledger that is double bonded but not corrupted.
			assert_ok!(StakingLedger::<Test>::kill(&444));

			// recover the ledger bonded by 333 stash.
			assert_ok!(Staking::restore_ledger(RuntimeOrigin::root(), 333, None, None, None));

			// 444 does not need recover in this case since it's been killed successfully.
			assert_eq!(Staking::inspect_bond_state(&444), Err(Error::<Test>::NotStash));

			// try-state checks are ok now.
			assert_ok!(Staking::do_try_state(System::block_number()));
		})
	}

	// Corrupted with bond_extra.
	//
	// * Double bonded and corrupted ledger.
	// * Corrupted ledger calls `bond_extra`
	#[test]
	fn restore_ledger_corrupted_bond_extra_works() {
		ExtBuilder::default().has_stakers(true).build_and_execute(|| {
			setup_double_bonded_ledgers();

			let lock_333_before = Balances::balance_locked(crate::STAKING_ID, &333);
			let lock_444_before = Balances::balance_locked(crate::STAKING_ID, &444);

			// get into corrupted and killed ledger state by killing a corrupted ledger:
			// init state:
			//  (333, 444)
			//  (444, 555)
			// set_controller(444) to 444
			//  (333, 444) -> corrupted
			//  (444, 444)
			// bond_extra(444, 40) -> OK
			// bond_extra(333, 30) -> locks out of sync

			assert_eq!(Staking::inspect_bond_state(&333).unwrap(), LedgerIntegrityState::Ok);
			set_controller_no_checks(&444);

			// now try-state fails.
			assert!(Staking::do_try_state(System::block_number()).is_err());

			// if 444 bonds extra, the locks remain in sync.
			bond_extra_no_checks(&444, 40);
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), lock_333_before);
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), lock_444_before + 40);

			// however if 333 bonds extra, the wrong lock is updated.
			bond_extra_no_checks(&333, 30);
			assert_eq!(
				Balances::balance_locked(crate::STAKING_ID, &333),
				lock_444_before + 40 + 30
			); //not OK
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), lock_444_before + 40); // OK

			// recover the ledger bonded by 333 stash. Note that the total/lock needs to be
			// re-written since on-chain data lock has become out of sync.
			assert_ok!(Staking::restore_ledger(
				RuntimeOrigin::root(),
				333,
				None,
				Some(lock_333_before + 30),
				None
			));

			// now recover 444 that although it's not corrupted, its lock and ledger.total are out
			// of sync. in which case, we need to explicitly set the ledger's lock and amount,
			// otherwise the ledger recover will fail.
			assert_noop!(
				Staking::restore_ledger(RuntimeOrigin::root(), 444, None, None, None),
				Error::<Test>::CannotRestoreLedger
			);

			//and enforcing a new ledger lock/total on this non-corrupted ledger will work.
			assert_ok!(Staking::restore_ledger(
				RuntimeOrigin::root(),
				444,
				None,
				Some(lock_444_before + 40),
				None
			));

			// double-check that ledgers got to expected state and bond_extra done during the
			// corrupted state is part of the recovered ledgers.
			let ledger_333 = Bonded::<Test>::get(&333).and_then(Ledger::<Test>::get).unwrap();
			let ledger_444 = Bonded::<Test>::get(&444).and_then(Ledger::<Test>::get).unwrap();

			assert_eq!(ledger_333.total, lock_333_before + 30);
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &333), ledger_333.total);
			assert_eq!(ledger_444.total, lock_444_before + 40);
			assert_eq!(Balances::balance_locked(crate::STAKING_ID, &444), ledger_444.total);

			// try-state checks are ok now.
			assert_ok!(Staking::do_try_state(System::block_number()));