Skip to content
tests.rs 240 KiB
Newer Older
		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
			);
7323 7324 7325 7326 7327 7328 7329 7330 7331 7332 7333 7334 7335 7336 7337 7338 7339 7340 7341 7342 7343 7344 7345 7346 7347 7348 7349 7350 7351 7352 7353 7354 7355 7356 7357 7358 7359 7360 7361 7362 7363 7364 7365 7366 7367 7368 7369 7370 7371 7372 7373 7374 7375 7376 7377 7378 7379 7380 7381 7382 7383 7384 7385 7386 7387 7388 7389 7390 7391 7392 7393 7394 7395 7396 7397 7398 7399 7400 7401 7402 7403 7404 7405 7406 7407 7408 7409 7410 7411 7412 7413 7414 7415 7416 7417 7418 7419 7420 7421 7422 7423 7424 7425 7426 7427 7428 7429 7430 7431 7432 7433 7434 7435 7436 7437 7438 7439 7440 7441 7442 7443 7444 7445 7446 7447 7448 7449 7450 7451 7452 7453 7454 7455 7456 7457 7458 7459 7460 7461 7462 7463 7464 7465 7466 7467 7468 7469 7470 7471 7472 7473 7474 7475 7476 7477 7478 7479 7480 7481 7482 7483 7484 7485 7486 7487 7488 7489 7490 7491 7492 7493 7494 7495 7496 7497 7498 7499 7500 7501 7502 7503 7504 7505 7506 7507 7508 7509 7510 7511 7512 7513 7514 7515 7516 7517 7518 7519 7520 7521 7522 7523 7524 7525 7526 7527 7528 7529 7530 7531 7532 7533 7534 7535 7536 7537 7538 7539 7540 7541 7542 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
			assert_ok!(Staking::set_controller(RuntimeOrigin::signed(333)));
		})
	}
}

mod ledger_recovery {
	use super::*;
	use frame_support::traits::InspectLockableCurrency;

	#[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()));