// This file is part of Substrate.

// Copyright (C) Parity Technologies (UK) Ltd.
// SPDX-License-Identifier: Apache-2.0

// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.

use super::*;
use crate::{mock::*, Event};
use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop};
use pallet_balances::Event as BEvent;
use sp_runtime::{
	bounded_btree_map,
	traits::{BadOrigin, Dispatchable},
	FixedU128,
};

macro_rules! unbonding_pools_with_era {
	($($k:expr => $v:expr),* $(,)?) => {{
		use ::core::iter::{Iterator, IntoIterator};
		let not_bounded: BTreeMap<_, _> = Iterator::collect(IntoIterator::into_iter([$(($k, $v),)*]));
		BoundedBTreeMap::<EraIndex, UnbondPool<T>, TotalUnbondingPools<T>>::try_from(not_bounded).unwrap()
	}};
}

macro_rules! member_unbonding_eras {
	($( $any:tt )*) => {{
		let x: BoundedBTreeMap<EraIndex, Balance, MaxUnbonding> = bounded_btree_map!($( $any )*);
		x
	}};
}

pub const DEFAULT_ROLES: PoolRoles<AccountId> =
	PoolRoles { depositor: 10, root: Some(900), nominator: Some(901), bouncer: Some(902) };

fn deposit_rewards(r: u128) {
	let b = Currency::free_balance(&default_reward_account()).checked_add(r).unwrap();
	Currency::set_balance(&default_reward_account(), b);
}

fn remove_rewards(r: u128) {
	let b = Currency::free_balance(&default_reward_account()).checked_sub(r).unwrap();
	Currency::set_balance(&default_reward_account(), b);
}

#[test]
fn test_setup_works() {
	ExtBuilder::default().build_and_execute(|| {
		assert_eq!(BondedPools::<Runtime>::count(), 1);
		assert_eq!(RewardPools::<Runtime>::count(), 1);
		assert_eq!(SubPoolsStorage::<Runtime>::count(), 0);
		assert_eq!(PoolMembers::<Runtime>::count(), 1);
		assert_eq!(StakingMock::bonding_duration(), 3);
		assert!(Metadata::<T>::contains_key(1));

		// initial member.
		assert_eq!(TotalValueLocked::<T>::get(), 10);

		let last_pool = LastPoolId::<Runtime>::get();
		assert_eq!(
			BondedPool::<Runtime>::get(last_pool).unwrap(),
			BondedPool::<Runtime> {
				id: last_pool,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 10,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			}
		);
		assert_eq!(
			RewardPools::<Runtime>::get(last_pool).unwrap(),
			RewardPool::<Runtime> {
				last_recorded_reward_counter: Zero::zero(),
				last_recorded_total_payouts: 0,
				total_rewards_claimed: 0,
				total_commission_claimed: 0,
				total_commission_pending: 0,
			}
		);
		assert_eq!(
			PoolMembers::<Runtime>::get(10).unwrap(),
			PoolMember::<Runtime> { pool_id: last_pool, points: 10, ..Default::default() }
		);

		let bonded_account = Pools::create_bonded_account(last_pool);
		let reward_account = Pools::create_reward_account(last_pool);

		// the bonded_account should be bonded by the depositor's funds.
		assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10);
		assert_eq!(StakingMock::total_stake(&bonded_account).unwrap(), 10);

		// but not nominating yet.
		assert!(Nominations::get().is_none());

		// reward account should have an initial ED in it.
		assert_eq!(Currency::free_balance(&reward_account), Currency::minimum_balance());
	})
}

mod bonded_pool {
	use super::*;
	#[test]
	fn balance_to_point_works() {
		ExtBuilder::default().build_and_execute(|| {
			let mut bonded_pool = BondedPool::<Runtime> {
				id: 123123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 100,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			};

			// 1 points : 1 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			assert_eq!(bonded_pool.balance_to_point(10), 10);
			assert_eq!(bonded_pool.balance_to_point(0), 0);

			// 2 points : 1 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 50);
			assert_eq!(bonded_pool.balance_to_point(10), 20);

			// 1 points : 2 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			bonded_pool.points = 50;
			assert_eq!(bonded_pool.balance_to_point(10), 5);

			// 100 points : 0 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
			bonded_pool.points = 100;
			assert_eq!(bonded_pool.balance_to_point(10), 100 * 10);

			// 0 points : 100 balance
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			bonded_pool.points = 0;
			assert_eq!(bonded_pool.balance_to_point(10), 10);

			// 10 points : 3 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 30);
			bonded_pool.points = 100;
			assert_eq!(bonded_pool.balance_to_point(10), 33);

			// 2 points : 3 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 300);
			bonded_pool.points = 200;
			assert_eq!(bonded_pool.balance_to_point(10), 6);

			// 4 points : 9 balance ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 900);
			bonded_pool.points = 400;
			assert_eq!(bonded_pool.balance_to_point(90), 40);
		})
	}

	#[test]
	fn points_to_balance_works() {
		ExtBuilder::default().build_and_execute(|| {
			// 1 balance : 1 points ratio
			let mut bonded_pool = BondedPool::<Runtime> {
				id: 123123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 100,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			};

			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			assert_eq!(bonded_pool.points_to_balance(10), 10);
			assert_eq!(bonded_pool.points_to_balance(0), 0);

			// 2 balance : 1 points ratio
			bonded_pool.points = 50;
			assert_eq!(bonded_pool.points_to_balance(10), 20);

			// 100 balance : 0 points ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			bonded_pool.points = 0;
			assert_eq!(bonded_pool.points_to_balance(10), 0);

			// 0 balance : 100 points ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 0);
			bonded_pool.points = 100;
			assert_eq!(bonded_pool.points_to_balance(10), 0);

			// 10 balance : 3 points ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 100);
			bonded_pool.points = 30;
			assert_eq!(bonded_pool.points_to_balance(10), 33);

			// 2 balance : 3 points ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 200);
			bonded_pool.points = 300;
			assert_eq!(bonded_pool.points_to_balance(10), 6);

			// 4 balance : 9 points ratio
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), 400);
			bonded_pool.points = 900;
			assert_eq!(bonded_pool.points_to_balance(90), 40);
		})
	}

	#[test]
	fn api_points_to_balance_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert!(BondedPool::<Runtime>::get(1).is_some());
			assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 10);

			// slash half of the pool's balance. expected result of `fn api_points_to_balance`
			// to be 1/2 of the pool's balance.
			StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
			assert_eq!(Pallet::<Runtime>::api_points_to_balance(1, 10), 5);

			// if pool does not exist, points to balance ratio is 0.
			assert_eq!(BondedPool::<Runtime>::get(2), None);
			assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0);
		})
	}

	#[test]
	fn api_balance_to_points_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 0), 0);
			assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 10);

			// slash half of the pool's balance. expect result of `fn api_balance_to_points`
			// to be 2 * of the balance to add to the pool.
			StakingMock::slash_by(1, Pools::depositor_min_bond() / 2);
			assert_eq!(Pallet::<Runtime>::api_balance_to_points(1, 10), 20);

			// if pool does not exist, balance to points ratio is 0.
			assert_eq!(BondedPool::<Runtime>::get(2), None);
			assert_eq!(Pallet::<Runtime>::api_points_to_balance(2, 10), 0);
		})
	}

	#[test]
	fn ok_to_join_with_works() {
		ExtBuilder::default().build_and_execute(|| {
			let pool = BondedPool::<Runtime> {
				id: 123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 100,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			};

			let max_points_to_balance: u128 =
				<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();

			// Simulate a 100% slashed pool
			StakingMock::set_bonded_balance(pool.bonded_account(), 0);
			assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);

			// Simulate a slashed pool at `MaxPointsToBalance` + 1 slashed pool
			StakingMock::set_bonded_balance(
				pool.bonded_account(),
				max_points_to_balance.saturating_add(1),
			);
			assert_ok!(pool.ok_to_join());

			// Simulate a slashed pool at `MaxPointsToBalance`
			StakingMock::set_bonded_balance(pool.bonded_account(), max_points_to_balance);
			assert_noop!(pool.ok_to_join(), Error::<Runtime>::OverflowRisk);

			StakingMock::set_bonded_balance(
				pool.bonded_account(),
				Balance::MAX / max_points_to_balance,
			);

			// and a sanity check
			StakingMock::set_bonded_balance(
				pool.bonded_account(),
				Balance::MAX / max_points_to_balance - 1,
			);
			assert_ok!(pool.ok_to_join());
		});
	}

	#[test]
	fn points_and_balance_conversions_are_safe() {
		ExtBuilder::default().build_and_execute(|| {
			let bonded_pool = BondedPool::<Runtime> {
				id: 123123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: u128::MAX,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			};
			StakingMock::set_bonded_balance(bonded_pool.bonded_account(), u128::MAX);

			// Max out the points and balance of the pool and make sure the conversion works as
			// expected and does not overflow.
			assert_eq!(bonded_pool.balance_to_point(u128::MAX), u128::MAX);
			assert_eq!(bonded_pool.points_to_balance(u128::MAX), u128::MAX);
		})
	}
}

mod reward_pool {
	use super::*;
	use crate::mock::RewardImbalance::{Deficit, Surplus};

	#[test]
	fn ed_change_causes_reward_deficit() {
		ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
			// original ED
			ExistentialDeposit::set(5);

			// 11 joins the pool
			Currency::set_balance(&11, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));

			// new delegator does not have any pending rewards
			assert_eq!(pending_rewards_for_delegator(11), 0);

			// give the pool some rewards
			deposit_rewards(100);

			// all existing delegator has pending rewards
			assert_eq!(pending_rewards_for_delegator(11), 90);
			assert_eq!(pending_rewards_for_delegator(10), 10);
			assert_eq!(reward_imbalance(1), Surplus(0));

			// 12 joins the pool.
			Currency::set_balance(&12, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(12), 100, 1));

			// Current reward balance is committed to last recorded reward counter of
			// the pool before the increase in ED.
			let bonded_pool = BondedPools::<Runtime>::get(1).unwrap();
			let reward_pool = RewardPools::<Runtime>::get(1).unwrap();
			assert_eq!(
				reward_pool.last_recorded_reward_counter,
				reward_pool
					.current_reward_counter(1, bonded_pool.points, Perbill::zero())
					.unwrap()
					.0
			);

			// reward pool before ED increase and reward counter getting committed.
			let reward_pool_1 = RewardPools::<Runtime>::get(1).unwrap();

			// increase ED from 5 to 50
			ExistentialDeposit::set(50);

			// There is now an expected deficit of ed_diff
			assert_eq!(reward_imbalance(1), Deficit(45));

			// 13 joins the pool which commits the reward counter to reward pool.
			Currency::set_balance(&13, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(13), 100, 1));

			// still a deficit
			assert_eq!(reward_imbalance(1), Deficit(45));

			// reward pool after ED increase
			let reward_pool_2 = RewardPools::<Runtime>::get(1).unwrap();

			// last recorded total payout does not decrease even as ED increases.
			assert_eq!(
				reward_pool_1.last_recorded_total_payouts,
				reward_pool_2.last_recorded_total_payouts
			);

			// Topping up pool decreases deficit
			deposit_rewards(10);
			assert_eq!(reward_imbalance(1), Deficit(35));

			// top up the pool to remove the deficit
			deposit_rewards(35);
			// No deficit anymore
			assert_eq!(reward_imbalance(1), Surplus(0));

			// fix the ed deficit
			assert_ok!(Currency::mint_into(&10, 45));
			assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1));
		});
	}

	#[test]
	fn ed_adjust_fixes_reward_deficit() {
		ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
			// Given: pool has a reward deficit

			// original ED
			ExistentialDeposit::set(5);

			// 11 joins the pool
			Currency::set_balance(&11, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));

			// Pool some rewards
			deposit_rewards(100);

			// 12 joins the pool.
			Currency::set_balance(&12, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(12), 10, 1));

			// When: pool ends up in reward deficit
			// increase ED
			ExistentialDeposit::set(50);
			assert_eq!(reward_imbalance(1), Deficit(45));

			// clear events
			pool_events_since_last_call();

			// Then: Anyone can permissionlessly can adjust ED deposit.

			// make sure caller has enough funds..
			assert_ok!(Currency::mint_into(&99, 100));
			let pre_balance = Currency::free_balance(&99);
			// adjust ED
			assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(99), 1));
			// depositor's balance should decrease by 45
			assert_eq!(Currency::free_balance(&99), pre_balance - 45);
			assert_eq!(reward_imbalance(1), Surplus(0));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::MinBalanceDeficitAdjusted { pool_id: 1, amount: 45 },]
			);

			// Trying to top up again does not work
			assert_err!(
				Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1),
				Error::<T>::NothingToAdjust
			);

			// When: ED is decreased and reward account has excess ED frozen
			ExistentialDeposit::set(5);

			// And:: adjust ED deposit is called
			let pre_balance = Currency::free_balance(&100);
			assert_ok!(Pools::adjust_pool_deposit(RuntimeOrigin::signed(100), 1));

			// Then: excess ED is claimed by the caller
			assert_eq!(Currency::free_balance(&100), pre_balance + 45);

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::MinBalanceExcessAdjusted { pool_id: 1, amount: 45 },]
			);
		});
	}

	#[test]
	fn topping_up_does_not_work_for_pools_with_no_deficit() {
		ExtBuilder::default().max_members_per_pool(Some(5)).build_and_execute(|| {
			// 11 joins the pool
			Currency::set_balance(&11, 500);
			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 90, 1));

			// Pool some rewards
			deposit_rewards(100);
			assert_eq!(reward_imbalance(1), Surplus(0));

			// Topping up fails
			assert_err!(
				Pools::adjust_pool_deposit(RuntimeOrigin::signed(10), 1),
				Error::<T>::NothingToAdjust
			);
		});
	}
}

mod unbond_pool {
	use super::*;

	#[test]
	fn points_to_issue_works() {
		ExtBuilder::default().build_and_execute(|| {
			// 1 points : 1 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 100 };
			assert_eq!(unbond_pool.balance_to_point(10), 10);
			assert_eq!(unbond_pool.balance_to_point(0), 0);

			// 2 points : 1 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 50 };
			assert_eq!(unbond_pool.balance_to_point(10), 20);

			// 1 points : 2 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 50, balance: 100 };
			assert_eq!(unbond_pool.balance_to_point(10), 5);

			// 100 points : 0 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 0 };
			assert_eq!(unbond_pool.balance_to_point(10), 100 * 10);

			// 0 points : 100 balance
			let unbond_pool = UnbondPool::<Runtime> { points: 0, balance: 100 };
			assert_eq!(unbond_pool.balance_to_point(10), 10);

			// 10 points : 3 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 30 };
			assert_eq!(unbond_pool.balance_to_point(10), 33);

			// 2 points : 3 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 200, balance: 300 };
			assert_eq!(unbond_pool.balance_to_point(10), 6);

			// 4 points : 9 balance ratio
			let unbond_pool = UnbondPool::<Runtime> { points: 400, balance: 900 };
			assert_eq!(unbond_pool.balance_to_point(90), 40);
		})
	}

	#[test]
	fn balance_to_unbond_works() {
		// 1 balance : 1 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 100 };
		assert_eq!(unbond_pool.point_to_balance(10), 10);
		assert_eq!(unbond_pool.point_to_balance(0), 0);

		// 1 balance : 2 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 50 };
		assert_eq!(unbond_pool.point_to_balance(10), 5);

		// 2 balance : 1 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 50, balance: 100 };
		assert_eq!(unbond_pool.point_to_balance(10), 20);

		// 100 balance : 0 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 0, balance: 100 };
		assert_eq!(unbond_pool.point_to_balance(10), 0);

		// 0 balance : 100 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 100, balance: 0 };
		assert_eq!(unbond_pool.point_to_balance(10), 0);

		// 10 balance : 3 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 30, balance: 100 };
		assert_eq!(unbond_pool.point_to_balance(10), 33);

		// 2 balance : 3 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 300, balance: 200 };
		assert_eq!(unbond_pool.point_to_balance(10), 6);

		// 4 balance : 9 points ratio
		let unbond_pool = UnbondPool::<Runtime> { points: 900, balance: 400 };
		assert_eq!(unbond_pool.point_to_balance(90), 40);
	}
}

mod sub_pools {
	use super::*;

	#[test]
	fn maybe_merge_pools_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert_eq!(TotalUnbondingPools::<Runtime>::get(), 5);
			assert_eq!(BondingDuration::get(), 3);
			assert_eq!(PostUnbondingPoolsWindow::get(), 2);

			// Given
			let mut sub_pool_0 = SubPools::<Runtime> {
				no_era: UnbondPool::<Runtime>::default(),
				with_era: unbonding_pools_with_era! {
					0 => UnbondPool::<Runtime> { points: 10, balance: 10 },
					1 => UnbondPool::<Runtime> { points: 10, balance: 10 },
					2 => UnbondPool::<Runtime> { points: 20, balance: 20 },
					3 => UnbondPool::<Runtime> { points: 30, balance: 30 },
					4 => UnbondPool::<Runtime> { points: 40, balance: 40 },
				},
			};

			// When `current_era < TotalUnbondingPools`,
			let sub_pool_1 = sub_pool_0.clone().maybe_merge_pools(0);

			// Then it exits early without modifications
			assert_eq!(sub_pool_1, sub_pool_0);

			// When `current_era == TotalUnbondingPools`,
			let sub_pool_1 = sub_pool_1.maybe_merge_pools(1);

			// Then it exits early without modifications
			assert_eq!(sub_pool_1, sub_pool_0);

			// When  `current_era - TotalUnbondingPools == 0`,
			let mut sub_pool_1 = sub_pool_1.maybe_merge_pools(2);

			// Then era 0 is merged into the `no_era` pool
			sub_pool_0.no_era = sub_pool_0.with_era.remove(&0).unwrap();
			assert_eq!(sub_pool_1, sub_pool_0);

			// Given we have entries for era 1..=5
			sub_pool_1
				.with_era
				.try_insert(5, UnbondPool::<Runtime> { points: 50, balance: 50 })
				.unwrap();
			sub_pool_0
				.with_era
				.try_insert(5, UnbondPool::<Runtime> { points: 50, balance: 50 })
				.unwrap();

			// When `current_era - TotalUnbondingPools == 1`
			let sub_pool_2 = sub_pool_1.maybe_merge_pools(3);
			let era_1_pool = sub_pool_0.with_era.remove(&1).unwrap();

			// Then era 1 is merged into the `no_era` pool
			sub_pool_0.no_era.points += era_1_pool.points;
			sub_pool_0.no_era.balance += era_1_pool.balance;
			assert_eq!(sub_pool_2, sub_pool_0);

			// When `current_era - TotalUnbondingPools == 5`, so all pools with era <= 4 are removed
			let sub_pool_3 = sub_pool_2.maybe_merge_pools(7);

			// Then all eras <= 5 are merged into the `no_era` pool
			for era in 2..=5 {
				let to_merge = sub_pool_0.with_era.remove(&era).unwrap();
				sub_pool_0.no_era.points += to_merge.points;
				sub_pool_0.no_era.balance += to_merge.balance;
			}
			assert_eq!(sub_pool_3, sub_pool_0);
		});
	}
}

mod join {
	use sp_runtime::TokenError;

	use super::*;

	#[test]
	fn join_works() {
		let bonded = |points, member_counter| BondedPool::<Runtime> {
			id: 1,
			inner: BondedPoolInner {
				commission: Commission::default(),
				member_counter,
				points,
				roles: DEFAULT_ROLES,
				state: PoolState::Open,
			},
		};
		ExtBuilder::default().with_check(0).build_and_execute(|| {
			// Given
			Currency::set_balance(&11, ExistentialDeposit::get() + 2);
			assert!(!PoolMembers::<Runtime>::contains_key(11));
			assert_eq!(TotalValueLocked::<T>::get(), 10);

			// When
			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
				]
			);
			assert_eq!(TotalValueLocked::<T>::get(), 12);

			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap(),
				PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
			);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12, 2));

			// Given
			// The bonded balance is slashed in half
			StakingMock::slash_by(1, 6);

			// And
			Currency::set_balance(&12, ExistentialDeposit::get() + 12);
			assert!(!PoolMembers::<Runtime>::contains_key(12));

			// When
			assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1));

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolSlashed { pool_id: 1, balance: 6 },
					Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
				]
			);
			assert_eq!(TotalValueLocked::<T>::get(), 18);

			assert_eq!(
				PoolMembers::<Runtime>::get(12).unwrap(),
				PoolMember::<Runtime> { pool_id: 1, points: 24, ..Default::default() }
			);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12 + 24, 3));
		});
	}

	#[test]
	fn join_errors_correctly() {
		ExtBuilder::default().with_check(0).build_and_execute(|| {
			// 10 is already part of the default pool created.
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().pool_id, 1);

			assert_noop!(
				Pools::join(RuntimeOrigin::signed(10), 420, 123),
				Error::<Runtime>::AccountBelongsToOtherPool
			);

			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), 420, 123),
				Error::<Runtime>::PoolNotFound
			);

			// Force the pools bonded balance to 0, simulating a 100% slash
			StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0);
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), 420, 1),
				Error::<Runtime>::OverflowRisk
			);

			// Given a mocked bonded pool
			BondedPool::<Runtime> {
				id: 123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 100,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			}
			.put();

			// and reward pool
			RewardPools::<Runtime>::insert(123, RewardPool::<Runtime> { ..Default::default() });

			// Force the points:balance ratio to `MaxPointsToBalance` (100/10)
			let max_points_to_balance: u128 =
				<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();

			StakingMock::set_bonded_balance(
				Pools::create_bonded_account(123),
				max_points_to_balance,
			);
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), 420, 123),
				Error::<Runtime>::OverflowRisk
			);

			StakingMock::set_bonded_balance(
				Pools::create_bonded_account(123),
				Balance::MAX / max_points_to_balance,
			);
			// Balance needs to be gt Balance::MAX / `MaxPointsToBalance`
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), 5, 123),
				TokenError::FundsUnavailable,
			);

			StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);

			// Cannot join a pool that isn't open
			unsafe_set_state(123, PoolState::Blocked);
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123),
				Error::<Runtime>::NotOpen
			);

			unsafe_set_state(123, PoolState::Destroying);
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), max_points_to_balance, 123),
				Error::<Runtime>::NotOpen
			);

			// Given
			MinJoinBond::<Runtime>::put(100);

			// Then
			assert_noop!(
				Pools::join(RuntimeOrigin::signed(11), 99, 123),
				Error::<Runtime>::MinimumBondNotMet
			);
		});
	}

	#[test]
	#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
	#[cfg_attr(not(debug_assertions), should_panic)]
	fn join_panics_when_reward_pool_not_found() {
		ExtBuilder::default().build_and_execute(|| {
			StakingMock::set_bonded_balance(Pools::create_bonded_account(123), 100);
			BondedPool::<Runtime> {
				id: 123,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 100,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			}
			.put();
			let _ = Pools::join(RuntimeOrigin::signed(11), 420, 123);
		});
	}

	#[test]
	fn join_max_member_limits_are_respected() {
		ExtBuilder::default().build_and_execute(|| {
			// Given
			assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(3));
			for i in 1..3 {
				let account = i + 100;
				Currency::set_balance(&account, 100 + Currency::minimum_balance());

				assert_ok!(Pools::join(RuntimeOrigin::signed(account), 100, 1));
			}

			Currency::set_balance(&103, 100 + Currency::minimum_balance());

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 101, pool_id: 1, bonded: 100, joined: true },
					Event::Bonded { member: 102, pool_id: 1, bonded: 100, joined: true }
				]
			);

			assert_noop!(
				Pools::join(RuntimeOrigin::signed(103), 100, 1),
				Error::<Runtime>::MaxPoolMembers
			);

			// Given
			assert_eq!(PoolMembers::<Runtime>::count(), 3);
			assert_eq!(MaxPoolMembers::<Runtime>::get(), Some(4));

			Currency::set_balance(&104, 100 + Currency::minimum_balance());
			assert_ok!(Pools::create(RuntimeOrigin::signed(104), 100, 104, 104, 104));

			let pool_account = BondedPools::<Runtime>::iter()
				.find(|(_, bonded_pool)| bonded_pool.roles.depositor == 104)
				.map(|(pool_account, _)| pool_account)
				.unwrap();

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 104, pool_id: 2 },
					Event::Bonded { member: 104, pool_id: 2, bonded: 100, joined: true }
				]
			);

			assert_noop!(
				Pools::join(RuntimeOrigin::signed(103), 100, pool_account),
				Error::<Runtime>::MaxPoolMembers
			);
		});
	}
}

mod claim_payout {
	use super::*;

	fn del(points: Balance, last_recorded_reward_counter: u128) -> PoolMember<Runtime> {
		PoolMember {
			pool_id: 1,
			points,
			last_recorded_reward_counter: last_recorded_reward_counter.into(),
			unbonding_eras: Default::default(),
		}
	}

	fn del_float(points: Balance, last_recorded_reward_counter: f64) -> PoolMember<Runtime> {
		PoolMember {
			pool_id: 1,
			points,
			last_recorded_reward_counter: RewardCounter::from_float(last_recorded_reward_counter),
			unbonding_eras: Default::default(),
		}
	}

	fn rew(
		last_recorded_reward_counter: u128,
		last_recorded_total_payouts: Balance,
		total_rewards_claimed: Balance,
	) -> RewardPool<Runtime> {
		RewardPool {
			last_recorded_reward_counter: last_recorded_reward_counter.into(),
			last_recorded_total_payouts,
			total_rewards_claimed,
			total_commission_claimed: 0,
			total_commission_pending: 0,
		}
	}

	#[test]
	fn claim_payout_works() {
		ExtBuilder::default()
			.add_members(vec![(40, 40), (50, 50)])
			.build_and_execute(|| {
				// Given each member currently has a free balance of
				Currency::set_balance(&10, 0);
				Currency::set_balance(&40, 0);
				Currency::set_balance(&50, 0);
				let ed = Currency::minimum_balance();

				// and the reward pool has earned 100 in rewards
				assert_eq!(Currency::free_balance(&default_reward_account()), ed);
				deposit_rewards(100);

				let _ = pool_events_since_last_call();

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 },]
				);
				// last recorded reward counter at the time of this member's payout is 1
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 1));
				// pool's 'last_recorded_reward_counter' and 'last_recorded_total_payouts' don't
				// really change unless if someone bonds/unbonds.
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 10));
				assert_eq!(Currency::free_balance(&10), 10);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 90);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del(40, 1));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 50));
				assert_eq!(Currency::free_balance(&40), 40);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 50);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del(50, 1));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 100));
				assert_eq!(Currency::free_balance(&50), 50);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed);

				// Given the reward pool has some new rewards
				deposit_rewards(50);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del_float(10, 1.5));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 105));
				assert_eq!(Currency::free_balance(&10), 10 + 5);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 45);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del_float(40, 1.5));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 125));
				assert_eq!(Currency::free_balance(&40), 40 + 20);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 25);

				// Given del 50 hasn't claimed and the reward pools has just earned 50
				deposit_rewards(50);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 75);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del_float(50, 2.0));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 175));
				assert_eq!(Currency::free_balance(&50), 50 + 50);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 25);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 2));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 180));
				assert_eq!(Currency::free_balance(&10), 15 + 5);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 20);

				// Given del 40 hasn't claimed and the reward pool has just earned 400
				deposit_rewards(400);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 420);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }]
				);

				// We expect a payout of 40
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del(10, 6));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 220));
				assert_eq!(Currency::free_balance(&10), 20 + 40);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 380);

				// Given del 40 + del 50 haven't claimed and the reward pool has earned 20
				deposit_rewards(20);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 400);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 2 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap(), del_float(10, 6.2));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 222));
				assert_eq!(Currency::free_balance(&10), 60 + 2);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 398);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(40)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 40, pool_id: 1, payout: 188 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(40).unwrap(), del_float(40, 6.2));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 410));
				assert_eq!(Currency::free_balance(&40), 60 + 188);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed + 210);

				// When
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(50)));

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 50, pool_id: 1, payout: 210 }]
				);
				assert_eq!(PoolMembers::<Runtime>::get(50).unwrap(), del_float(50, 6.2));
				assert_eq!(RewardPools::<Runtime>::get(1).unwrap(), rew(0, 0, 620));
				assert_eq!(Currency::free_balance(&50), 100 + 210);
				assert_eq!(Currency::free_balance(&default_reward_account()), ed);
			});
	}

	#[test]
	fn reward_payout_errors_if_a_member_is_fully_unbonding() {
		ExtBuilder::default().add_members(vec![(11, 11)]).build_and_execute(|| {
			// fully unbond the member.
			assert_ok!(fully_unbond_permissioned(11));

			assert_noop!(
				Pools::claim_payout(RuntimeOrigin::signed(11)),
				Error::<Runtime>::FullyUnbonding
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 11, pool_id: 1, bonded: 11, joined: true },
					Event::Unbonded { member: 11, pool_id: 1, points: 11, balance: 11, era: 3 }
				]
			);
		});
	}

	#[test]
	fn claim_payout_bounds_commission_above_global() {
		ExtBuilder::default().build_and_execute(|| {
			let (mut member, bonded_pool, mut reward_pool) =
				Pools::get_member_with_pools(&10).unwrap();

			// top up commission payee account to existential deposit
			let _ = Currency::set_balance(&2, 5);

			// Set a commission pool 1 to 75%, with a payee set to `2`
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				bonded_pool.id,
				Some((Perbill::from_percent(75), 2)),
			));

			// re-introduce the global maximum to 50% - 25% lower than the current commission of the
			// pool.
			GlobalMaxCommission::<Runtime>::set(Some(Perbill::from_percent(50)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(75), 2))
					}
				]
			);

			// The pool earns 10 points
			deposit_rewards(10);

			assert_ok!(Pools::do_reward_payout(
				&10,
				&mut member,
				&mut BondedPool::<Runtime>::get(1).unwrap(),
				&mut reward_pool
			));

			// commission applied is 50%, not 75%. Has been bounded by `GlobalMaxCommission`.
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 },]
			);
		})
	}

	#[test]
	fn do_reward_payout_works_with_a_pool_of_1() {
		let del = |last_recorded_reward_counter| del_float(10, last_recorded_reward_counter);

		ExtBuilder::default().build_and_execute(|| {
			let (mut member, mut bonded_pool, mut reward_pool) =
				Pools::get_member_with_pools(&10).unwrap();
			let ed = Currency::minimum_balance();

			let payout =
				Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
					.unwrap();

			// Then
			assert_eq!(payout, 0);
			assert_eq!(member, del(0.0));
			assert_eq!(reward_pool, rew(0, 0, 0));
			Pools::put_member_with_pools(
				&10,
				member.clone(),
				bonded_pool.clone(),
				reward_pool.clone(),
			);

			// Given the pool has earned some rewards for the first time
			deposit_rewards(5);

			// When
			let payout =
				Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
					.unwrap();

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 5 }
				]
			);
			assert_eq!(payout, 5);
			assert_eq!(reward_pool, rew(0, 0, 5));
			assert_eq!(member, del(0.5));
			Pools::put_member_with_pools(
				&10,
				member.clone(),
				bonded_pool.clone(),
				reward_pool.clone(),
			);

			// Given the pool has earned rewards again
			deposit_rewards(10);

			// When
			let payout =
				Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
					.unwrap();

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
			);
			assert_eq!(payout, 10);
			assert_eq!(reward_pool, rew(0, 0, 15));
			assert_eq!(member, del(1.5));
			Pools::put_member_with_pools(
				&10,
				member.clone(),
				bonded_pool.clone(),
				reward_pool.clone(),
			);

			// Given the pool has earned no new rewards
			Currency::set_balance(&default_reward_account(), ed);

			// When
			let payout =
				Pools::do_reward_payout(&10, &mut member, &mut bonded_pool, &mut reward_pool)
					.unwrap();

			// Then
			assert_eq!(pool_events_since_last_call(), vec![]);
			assert_eq!(payout, 0);
			assert_eq!(reward_pool, rew(0, 0, 15));
			assert_eq!(member, del(1.5));
			Pools::put_member_with_pools(
				&10,
				member.clone(),
				bonded_pool.clone(),
				reward_pool.clone(),
			);
		});
	}

	#[test]
	fn do_reward_payout_works_with_a_pool_of_3() {
		ExtBuilder::default()
			.add_members(vec![(40, 40), (50, 50)])
			.build_and_execute(|| {
				let mut bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
				let mut reward_pool = RewardPools::<Runtime>::get(1).unwrap();

				let mut del_10 = PoolMembers::<Runtime>::get(10).unwrap();
				let mut del_40 = PoolMembers::<Runtime>::get(40).unwrap();
				let mut del_50 = PoolMembers::<Runtime>::get(50).unwrap();

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
						Event::Bonded { member: 50, pool_id: 1, bonded: 50, joined: true }
					]
				);

				// Given we have a total of 100 points split among the members
				assert_eq!(del_50.points + del_40.points + del_10.points, 100);
				assert_eq!(bonded_pool.points, 100);

				// and the reward pool has earned 100 in rewards
				deposit_rewards(100);

				// When
				let payout =
					Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
				);
				assert_eq!(payout, 10);
				assert_eq!(del_10, del(10, 1));
				assert_eq!(reward_pool, rew(0, 0, 10));
				Pools::put_member_with_pools(
					&10,
					del_10.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 40, pool_id: 1, payout: 40 }]
				);
				assert_eq!(payout, 40);
				assert_eq!(del_40, del(40, 1));
				assert_eq!(reward_pool, rew(0, 0, 50));
				Pools::put_member_with_pools(
					&40,
					del_40.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
				);
				assert_eq!(payout, 50);
				assert_eq!(del_50, del(50, 1));
				assert_eq!(reward_pool, rew(0, 0, 100));
				Pools::put_member_with_pools(
					&50,
					del_50.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// Given the reward pool has some new rewards
				deposit_rewards(50);

				// When
				let payout =
					Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
				);
				assert_eq!(payout, 5);
				assert_eq!(del_10, del_float(10, 1.5));
				assert_eq!(reward_pool, rew(0, 0, 105));
				Pools::put_member_with_pools(
					&10,
					del_10.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 40, pool_id: 1, payout: 20 }]
				);
				assert_eq!(payout, 20);
				assert_eq!(del_40, del_float(40, 1.5));
				assert_eq!(reward_pool, rew(0, 0, 125));
				Pools::put_member_with_pools(
					&40,
					del_40.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// Given del_50 hasn't claimed and the reward pools has just earned 50
				deposit_rewards(50);

				// When
				let payout =
					Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 50, pool_id: 1, payout: 50 }]
				);
				assert_eq!(payout, 50);
				assert_eq!(del_50, del_float(50, 2.0));
				assert_eq!(reward_pool, rew(0, 0, 175));
				Pools::put_member_with_pools(
					&50,
					del_50.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 5 }]
				);
				assert_eq!(payout, 5);
				assert_eq!(del_10, del_float(10, 2.0));
				assert_eq!(reward_pool, rew(0, 0, 180));
				Pools::put_member_with_pools(
					&10,
					del_10.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// Given del_40 hasn't claimed and the reward pool has just earned 400
				deposit_rewards(400);

				// When
				let payout =
					Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 40 }]
				);
				assert_eq!(payout, 40);
				assert_eq!(del_10, del_float(10, 6.0));
				assert_eq!(reward_pool, rew(0, 0, 220));
				Pools::put_member_with_pools(
					&10,
					del_10.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// Given del_40 + del_50 haven't claimed and the reward pool has earned 20
				deposit_rewards(20);

				// When
				let payout =
					Pools::do_reward_payout(&10, &mut del_10, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(payout, 2);
				assert_eq!(del_10, del_float(10, 6.2));
				assert_eq!(reward_pool, rew(0, 0, 222));
				Pools::put_member_with_pools(
					&10,
					del_10.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&40, &mut del_40, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(payout, 188); // 20 (from the 50) + 160 (from the 400) + 8 (from the 20)
				assert_eq!(del_40, del_float(40, 6.2));
				assert_eq!(reward_pool, rew(0, 0, 410));
				Pools::put_member_with_pools(
					&40,
					del_40.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);

				// When
				let payout =
					Pools::do_reward_payout(&50, &mut del_50, &mut bonded_pool, &mut reward_pool)
						.unwrap();

				// Then
				assert_eq!(payout, 210); // 200 (from the 400) + 10 (from the 20)
				assert_eq!(del_50, del_float(50, 6.2));
				assert_eq!(reward_pool, rew(0, 0, 620));
				Pools::put_member_with_pools(
					&50,
					del_50.clone(),
					bonded_pool.clone(),
					reward_pool.clone(),
				);
			});
	}

	#[test]
	fn rewards_distribution_is_fair_basic() {
		ExtBuilder::default().build_and_execute(|| {
			// reward pool by 10.
			deposit_rewards(10);

			// 20 joins afterwards.
			Currency::set_balance(&20, Currency::minimum_balance() + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));

			// reward by another 20
			deposit_rewards(20);

			// 10 should claim 10 + 10, 20 should claim 20 / 2.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
				]
			);

			// any upcoming rewards are shared equally.
			deposit_rewards(20);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
				]
			);
		});
	}

	#[test]
	fn rewards_distribution_is_fair_basic_with_fractions() {
		// basically checks the case where the amount of rewards is less than the pool shares. for
		// this, we have to rely on fixed point arithmetic.
		ExtBuilder::default().build_and_execute(|| {
			deposit_rewards(3);

			Currency::set_balance(&20, Currency::minimum_balance() + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));

			deposit_rewards(6);

			// 10 should claim 3, 20 should claim 3 + 3.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 3 + 3 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
				]
			);

			// any upcoming rewards are shared equally.
			deposit_rewards(8);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 4 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 4 },
				]
			);

			// uneven upcoming rewards are shared equally, rounded down.
			deposit_rewards(7);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 3 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
				]
			);
		});
	}

	#[test]
	fn rewards_distribution_is_fair_3() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			deposit_rewards(30);

			Currency::set_balance(&20, ed + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));

			deposit_rewards(100);

			Currency::set_balance(&30, ed + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));

			deposit_rewards(60);

			// 10 should claim 10, 20 should claim nothing.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 30 + 100 / 2 + 60 / 3 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 100 / 2 + 60 / 3 },
					Event::PaidOut { member: 30, pool_id: 1, payout: 60 / 3 },
				]
			);

			// any upcoming rewards are shared equally.
			deposit_rewards(30);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 30, pool_id: 1, payout: 10 },
				]
			);
		});
	}

	#[test]
	fn pending_rewards_per_member_works() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			assert_eq!(Pools::api_pending_rewards(10), Some(0));
			deposit_rewards(30);
			assert_eq!(Pools::api_pending_rewards(10), Some(30));
			assert_eq!(Pools::api_pending_rewards(20), None);

			Currency::set_balance(&20, ed + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));

			assert_eq!(Pools::api_pending_rewards(10), Some(30));
			assert_eq!(Pools::api_pending_rewards(20), Some(0));

			deposit_rewards(100);

			assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
			assert_eq!(Pools::api_pending_rewards(20), Some(50));
			assert_eq!(Pools::api_pending_rewards(30), None);

			Currency::set_balance(&30, ed + 10);
			assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));

			assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50));
			assert_eq!(Pools::api_pending_rewards(20), Some(50));
			assert_eq!(Pools::api_pending_rewards(30), Some(0));

			deposit_rewards(60);

			assert_eq!(Pools::api_pending_rewards(10), Some(30 + 50 + 20));
			assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
			assert_eq!(Pools::api_pending_rewards(30), Some(20));

			// 10 should claim 10, 20 should claim nothing.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_eq!(Pools::api_pending_rewards(10), Some(0));
			assert_eq!(Pools::api_pending_rewards(20), Some(50 + 20));
			assert_eq!(Pools::api_pending_rewards(30), Some(20));

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_eq!(Pools::api_pending_rewards(10), Some(0));
			assert_eq!(Pools::api_pending_rewards(20), Some(0));
			assert_eq!(Pools::api_pending_rewards(30), Some(20));

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
			assert_eq!(Pools::api_pending_rewards(10), Some(0));
			assert_eq!(Pools::api_pending_rewards(20), Some(0));
			assert_eq!(Pools::api_pending_rewards(30), Some(0));
		});
	}

	#[test]
	fn rewards_distribution_is_fair_bond_extra() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			Currency::set_balance(&20, ed + 20);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
			Currency::set_balance(&30, ed + 20);
			assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));

			deposit_rewards(40);

			// everyone claims.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 20 },
					Event::PaidOut { member: 30, pool_id: 1, payout: 10 }
				]
			);

			// 30 now bumps itself to be like 20.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(30), BondExtra::FreeBalance(10)));

			// more rewards come in.
			deposit_rewards(100);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: false },
					Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 40 },
					Event::PaidOut { member: 30, pool_id: 1, payout: 40 }
				]
			);
		});
	}

	#[test]
	fn rewards_distribution_is_fair_unbond() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			Currency::set_balance(&20, ed + 20);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));

			deposit_rewards(30);

			// everyone claims.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 20 }
				]
			);

			// 20 unbonds to be equal to 10 (10 points each).
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));

			// more rewards come in.
			deposit_rewards(100);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 },
					Event::PaidOut { member: 10, pool_id: 1, payout: 50 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 50 },
				]
			);
		});
	}

	#[test]
	fn unclaimed_reward_is_safe() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			Currency::set_balance(&20, ed + 20);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
			Currency::set_balance(&30, ed + 20);
			assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10, 1));

			// 10 gets 10, 20 gets 20, 30 gets 10
			deposit_rewards(40);

			// some claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 20 }
				]
			);

			// 10 gets 20, 20 gets 40, 30 gets 20
			deposit_rewards(80);

			// some claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 40 }
				]
			);

			// 10 gets 20, 20 gets 40, 30 gets 20
			deposit_rewards(80);

			// some claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 20 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 40 }
				]
			);

			// now 30 claims all at once
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 30, pool_id: 1, payout: 10 + 20 + 20 }]
			);
		});
	}

	#[test]
	fn bond_extra_and_delayed_claim() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			Currency::set_balance(&20, ed + 200);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));

			// 10 gets 10, 20 gets 20, 30 gets 10
			deposit_rewards(30);

			// some claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 10 }
				]
			);

			// 20 has not claimed yet, more reward comes
			deposit_rewards(60);

			// and 20 bonds more -- they should not have more share of this reward.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::FreeBalance(10)));

			// everyone claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					// 20 + 40, which means the extra amount they bonded did not impact us.
					Event::PaidOut { member: 20, pool_id: 1, payout: 60 },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false },
					Event::PaidOut { member: 10, pool_id: 1, payout: 20 }
				]
			);

			// but in the next round of rewards, the extra10 they bonded has an impact.
			deposit_rewards(60);

			// everyone claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 15 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 45 }
				]
			);
		});
	}

	#[test]
	fn create_sets_recorded_data() {
		ExtBuilder::default().build_and_execute(|| {
			MaxPools::<Runtime>::set(None);
			// pool 10 has already been created.
			let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();

			assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
			assert_eq!(reward_pool_10.total_rewards_claimed, 0);
			assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());

			assert_eq!(member_10.last_recorded_reward_counter, 0.into());

			// transfer some reward to pool 1.
			deposit_rewards(60);

			// create pool 2
			Currency::set_balance(&20, 100);
			assert_ok!(Pools::create(RuntimeOrigin::signed(20), 10, 20, 20, 20));

			// has no impact -- initial
			let (member_20, _, reward_pool_20) = Pools::get_member_with_pools(&20).unwrap();

			assert_eq!(reward_pool_20.last_recorded_total_payouts, 0);
			assert_eq!(reward_pool_20.total_rewards_claimed, 0);
			assert_eq!(reward_pool_20.last_recorded_reward_counter, 0.into());

			assert_eq!(member_20.last_recorded_reward_counter, 0.into());

			// pre-fund the reward account of pool id 3 with some funds.
			Currency::set_balance(&Pools::create_reward_account(3), 10);

			// create pool 3
			Currency::set_balance(&30, 100);
			assert_ok!(Pools::create(RuntimeOrigin::signed(30), 10, 30, 30, 30));

			// reward counter is still the same.
			let (member_30, _, reward_pool_30) = Pools::get_member_with_pools(&30).unwrap();
			assert_eq!(
				Currency::free_balance(&Pools::create_reward_account(3)),
				10 + Currency::minimum_balance()
			);

			assert_eq!(reward_pool_30.last_recorded_total_payouts, 0);
			assert_eq!(reward_pool_30.total_rewards_claimed, 0);
			assert_eq!(reward_pool_30.last_recorded_reward_counter, 0.into());

			assert_eq!(member_30.last_recorded_reward_counter, 0.into());

			// and 30 can claim the reward now.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Created { depositor: 20, pool_id: 2 },
					Event::Bonded { member: 20, pool_id: 2, bonded: 10, joined: true },
					Event::Created { depositor: 30, pool_id: 3 },
					Event::Bonded { member: 30, pool_id: 3, bonded: 10, joined: true },
					Event::PaidOut { member: 30, pool_id: 3, payout: 10 }
				]
			);
		})
	}

	#[test]
	fn join_updates_recorded_data() {
		ExtBuilder::default().build_and_execute(|| {
			MaxPoolMembers::<Runtime>::set(None);
			MaxPoolMembersPerPool::<Runtime>::set(None);
			let join = |x, y| {
				Currency::set_balance(&x, y + Currency::minimum_balance());
				assert_ok!(Pools::join(RuntimeOrigin::signed(x), y, 1));
			};

			{
				let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();

				assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
				assert_eq!(reward_pool_10.total_rewards_claimed, 0);
				assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());

				assert_eq!(member_10.last_recorded_reward_counter, 0.into());
			}

			// someone joins without any rewards being issued.
			{
				join(20, 10);
				let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
				// reward counter is 0 both before..
				assert_eq!(member.last_recorded_reward_counter, 0.into());
				assert_eq!(reward_pool.last_recorded_total_payouts, 0);
				assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
			}

			// transfer some reward to pool 1.
			deposit_rewards(60);

			{
				join(30, 10);
				let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
				assert_eq!(reward_pool.last_recorded_total_payouts, 60);
				// explanation: we have a total of 20 points so far (excluding the 10 that just got
				// bonded), and 60 unclaimed rewards. each share is then roughly worth of 3 units of
				// rewards, thus reward counter is 3. member's reward counter is the same
				assert_eq!(member.last_recorded_reward_counter, 3.into());
				assert_eq!(reward_pool.last_recorded_reward_counter, 3.into());
			}

			// someone else joins
			{
				join(40, 10);
				let (member, _, reward_pool) = Pools::get_member_with_pools(&40).unwrap();
				// reward counter does not change since no rewards have came in.
				assert_eq!(member.last_recorded_reward_counter, 3.into());
				assert_eq!(reward_pool.last_recorded_reward_counter, 3.into());
				assert_eq!(reward_pool.last_recorded_total_payouts, 60);
			}

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 30, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 40, pool_id: 1, bonded: 10, joined: true }
				]
			);
		})
	}

	#[test]
	fn bond_extra_updates_recorded_data() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			MaxPoolMembers::<Runtime>::set(None);
			MaxPoolMembersPerPool::<Runtime>::set(None);

			// initial state of pool 1.
			{
				let (member_10, _, reward_pool_10) = Pools::get_member_with_pools(&10).unwrap();

				assert_eq!(reward_pool_10.last_recorded_total_payouts, 0);
				assert_eq!(reward_pool_10.total_rewards_claimed, 0);
				assert_eq!(reward_pool_10.last_recorded_reward_counter, 0.into());

				assert_eq!(member_10.last_recorded_reward_counter, 0.into());
			}

			Currency::set_balance(&10, 100);
			Currency::set_balance(&20, 100);

			// 10 bonds extra without any rewards.
			{
				assert_ok!(Pools::bond_extra(
					RuntimeOrigin::signed(10),
					BondExtra::FreeBalance(10)
				));
				let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
				assert_eq!(member.last_recorded_reward_counter, 0.into());
				assert_eq!(reward_pool.last_recorded_total_payouts, 0);
				assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
			}

			// 10 bonds extra again with some rewards. This reward should be split equally between
			// 10 and 20, as they both have equal points now.
			deposit_rewards(30);

			{
				assert_ok!(Pools::bond_extra(
					RuntimeOrigin::signed(10),
					BondExtra::FreeBalance(10)
				));
				let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
				// explanation: before bond_extra takes place, there is 40 points and 30 balance in
				// the system, RewardCounter is therefore 7.5
				assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75));
				assert_eq!(
					reward_pool.last_recorded_reward_counter,
					RewardCounter::from_float(0.75)
				);
				assert_eq!(reward_pool.last_recorded_total_payouts, 30);
			}

			// 20 bonds extra again, without further rewards.
			{
				assert_ok!(Pools::bond_extra(
					RuntimeOrigin::signed(20),
					BondExtra::FreeBalance(10)
				));
				let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
				assert_eq!(member.last_recorded_reward_counter, RewardCounter::from_float(0.75));
				assert_eq!(
					reward_pool.last_recorded_reward_counter,
					RewardCounter::from_float(0.75)
				);
				assert_eq!(reward_pool.last_recorded_total_payouts, 30);
			}

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
					Event::PaidOut { member: 10, pool_id: 1, payout: 15 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
					Event::PaidOut { member: 20, pool_id: 1, payout: 15 },
					Event::Bonded { member: 20, pool_id: 1, bonded: 10, joined: false }
				]
			);
		})
	}

	#[test]
	fn bond_extra_pending_rewards_works() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			MaxPoolMembers::<Runtime>::set(None);
			MaxPoolMembersPerPool::<Runtime>::set(None);

			// pool receives some rewards.
			deposit_rewards(30);
			System::reset_events();

			// 10 cashes it out, and bonds it.
			{
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
				// there is 30 points and 30 reward points in the system RC is 1.
				assert_eq!(member.last_recorded_reward_counter, 1.into());
				assert_eq!(reward_pool.total_rewards_claimed, 10);
				// these two are not updated -- only updated when the points change.
				assert_eq!(reward_pool.last_recorded_total_payouts, 0);
				assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());

				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10 }]
				);
			}

			// 20 re-bonds it.
			{
				assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards));
				let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();
				assert_eq!(member.last_recorded_reward_counter, 1.into());
				assert_eq!(reward_pool.total_rewards_claimed, 30);
				// since points change, these two are updated.
				assert_eq!(reward_pool.last_recorded_total_payouts, 30);
				assert_eq!(reward_pool.last_recorded_reward_counter, 1.into());

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::PaidOut { member: 20, pool_id: 1, payout: 20 },
						Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: false }
					]
				);
			}
		})
	}

	#[test]
	fn unbond_updates_recorded_data() {
		ExtBuilder::default()
			.add_members(vec![(20, 20), (30, 20)])
			.build_and_execute(|| {
				MaxPoolMembers::<Runtime>::set(None);
				MaxPoolMembersPerPool::<Runtime>::set(None);

				// initial state of pool 1.
				{
					let (member, _, reward_pool) = Pools::get_member_with_pools(&10).unwrap();

					assert_eq!(reward_pool.last_recorded_total_payouts, 0);
					assert_eq!(reward_pool.total_rewards_claimed, 0);
					assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());

					assert_eq!(member.last_recorded_reward_counter, 0.into());
				}

				// 20 unbonds without any rewards.
				{
					assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
					let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
					assert_eq!(member.last_recorded_reward_counter, 0.into());
					assert_eq!(reward_pool.last_recorded_total_payouts, 0);
					assert_eq!(reward_pool.last_recorded_reward_counter, 0.into());
				}

				// some rewards come in.
				deposit_rewards(30);

				// and 30 also unbonds half.
				{
					assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 10));
					let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
					// 30 reward in the system, and 40 points before this unbond to collect it,
					// RewardCounter is 3/4.
					assert_eq!(
						member.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
					assert_eq!(reward_pool.last_recorded_total_payouts, 30);
					assert_eq!(
						reward_pool.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
				}

				// 30 unbonds again, not change this time.
				{
					assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5));
					let (member, _, reward_pool) = Pools::get_member_with_pools(&30).unwrap();
					assert_eq!(
						member.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
					assert_eq!(reward_pool.last_recorded_total_payouts, 30);
					assert_eq!(
						reward_pool.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
				}

				// 20 unbonds again, not change this time, just collecting their reward.
				{
					assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
					let (member, _, reward_pool) = Pools::get_member_with_pools(&20).unwrap();
					assert_eq!(
						member.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
					assert_eq!(reward_pool.last_recorded_total_payouts, 30);
					assert_eq!(
						reward_pool.last_recorded_reward_counter,
						RewardCounter::from_float(0.75)
					);
				}

				// trigger 10's reward as well to see all of the payouts.
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
						Event::Bonded { member: 30, pool_id: 1, bonded: 20, joined: true },
						Event::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 3 },
						Event::PaidOut { member: 30, pool_id: 1, payout: 15 },
						Event::Unbonded { member: 30, pool_id: 1, balance: 10, points: 10, era: 3 },
						Event::Unbonded { member: 30, pool_id: 1, balance: 5, points: 5, era: 3 },
						Event::PaidOut { member: 20, pool_id: 1, payout: 7 },
						Event::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 3 },
						Event::PaidOut { member: 10, pool_id: 1, payout: 7 }
					]
				);
			})
	}

	#[test]
	fn rewards_are_rounded_down_depositor_collects_them() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			// initial balance of 10.

			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(
				Currency::free_balance(&default_reward_account()),
				Currency::minimum_balance()
			);

			// some rewards come in.
			deposit_rewards(40);

			// everyone claims
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			// some dust (1) remains in the reward account.
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 13 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 26 }
				]
			);

			// start dismantling the pool.
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
			assert_ok!(fully_unbond_permissioned(20));

			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
			assert_ok!(fully_unbond_permissioned(10));

			CurrentEra::set(6);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
					Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 },
					Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 },
					Event::MemberRemoved { pool_id: 1, member: 20 },
					Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 6 },
					Event::Withdrawn { member: 10, pool_id: 1, balance: 10, points: 10 },
					Event::MemberRemoved { pool_id: 1, member: 10 },
					Event::Destroyed { pool_id: 1 }
				]
			);

			assert!(!Metadata::<T>::contains_key(1));
			// original ed + ed put into reward account + reward + bond + dust.
			assert_eq!(Currency::free_balance(&10), 35 + 5 + 13 + 10 + 1);
		})
	}

	#[test]
	fn claim_payout_large_numbers() {
		let unit = 10u128.pow(12); // akin to KSM
		ExistentialDeposit::set(unit);
		StakingMinBond::set(unit * 1000);

		ExtBuilder::default()
			.max_members(Some(4))
			.max_members_per_pool(Some(4))
			.add_members(vec![(20, 1500 * unit), (21, 2500 * unit), (22, 5000 * unit)])
			.build_and_execute(|| {
				// some rewards come in.
				assert_eq!(Currency::free_balance(&default_reward_account()), unit);
				deposit_rewards(unit / 1000);

				// everyone claims
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(21)));
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(22)));

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded {
							member: 10,
							pool_id: 1,
							bonded: 1000000000000000,
							joined: true
						},
						Event::Bonded {
							member: 20,
							pool_id: 1,
							bonded: 1500000000000000,
							joined: true
						},
						Event::Bonded {
							member: 21,
							pool_id: 1,
							bonded: 2500000000000000,
							joined: true
						},
						Event::Bonded {
							member: 22,
							pool_id: 1,
							bonded: 5000000000000000,
							joined: true
						},
						Event::PaidOut { member: 10, pool_id: 1, payout: 100000000 },
						Event::PaidOut { member: 20, pool_id: 1, payout: 150000000 },
						Event::PaidOut { member: 21, pool_id: 1, payout: 250000000 },
						Event::PaidOut { member: 22, pool_id: 1, payout: 500000000 }
					]
				);
			})
	}

	#[test]
	fn claim_payout_other_works() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			Currency::set_balance(&default_reward_account(), 8);
			// ... of which only 3 are claimable to make sure the reward account does not die.
			let claimable_reward = 8 - ExistentialDeposit::get();
			// NOTE: easier to read if we use 3, so let's use the number instead of variable.
			assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");

			// given
			assert_eq!(Currency::free_balance(&10), 35);

			// when

			// NOTE: Claim permission of `PermissionlessWithdraw` allows payout claiming as default,
			// so a claim permission does not need to be set for non-pool members prior to claiming.
			assert_ok!(Pools::claim_payout_other(RuntimeOrigin::signed(80), 10));

			// then
			assert_eq!(Currency::free_balance(&10), 36);
			assert_eq!(Currency::free_balance(&default_reward_account()), 7);
		})
	}
}

mod unbond {
	use super::*;

	#[test]
	fn member_unbond_open() {
		// depositor in pool, pool state open
		//   - member unbond above limit
		//   - member unbonds to 0
		//   - member cannot unbond between within limit and 0
		ExtBuilder::default()
			.min_join_bond(10)
			.add_members(vec![(20, 20)])
			.build_and_execute(|| {
				assert_eq!(TotalValueLocked::<T>::get(), 30);
				// can unbond to above limit
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 15);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 5);

				// tvl remains unchanged.
				assert_eq!(TotalValueLocked::<T>::get(), 30);

				// cannot go to below 10:
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(20), 20, 10),
					Error::<T>::MinimumBondNotMet
				);

				// Make permissionless
				assert_ok!(Pools::set_claim_permission(
					RuntimeOrigin::signed(20),
					ClaimPermission::PermissionlessAll
				));

				// but can go to 0
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 15));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
				assert_eq!(
					ClaimPermissions::<Runtime>::get(20),
					ClaimPermission::PermissionlessAll
				);
			})
	}

	#[test]
	fn member_kicked() {
		// depositor in pool, pool state blocked
		//   - member cannot be kicked to above limit
		//   - member cannot be kicked between within limit and 0
		//   - member kicked to 0
		ExtBuilder::default()
			.min_join_bond(10)
			.add_members(vec![(20, 20)])
			.build_and_execute(|| {
				unsafe_set_state(1, PoolState::Blocked);
				let kicker = DEFAULT_ROLES.bouncer.unwrap();

				// cannot be kicked to above the limit.
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(kicker), 20, 5),
					Error::<T>::PartialUnbondNotAllowedPermissionlessly
				);

				// cannot go to below 10:
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(kicker), 20, 15),
					Error::<T>::PartialUnbondNotAllowedPermissionlessly
				);

				// but they themselves can do an unbond
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);

				// can be kicked to 0.
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(kicker), 20, 18));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
			})
	}

	#[test]
	fn member_unbond_destroying() {
		// depositor in pool, pool state destroying
		//   - member cannot be permissionlessly unbonded to above limit
		//   - member cannot be permissionlessly unbonded between within limit and 0
		//   - member permissionlessly unbonded to 0
		ExtBuilder::default()
			.min_join_bond(10)
			.add_members(vec![(20, 20)])
			.build_and_execute(|| {
				unsafe_set_state(1, PoolState::Destroying);
				let random = 123;

				// cannot be kicked to above the limit.
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(random), 20, 5),
					Error::<T>::PartialUnbondNotAllowedPermissionlessly
				);

				// cannot go to below 10:
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(random), 20, 15),
					Error::<T>::PartialUnbondNotAllowedPermissionlessly
				);

				// but they themselves can do an unbond
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 18);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 2);

				// but can go to 0
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(random), 20, 18));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);
			})
	}

	#[test]
	fn member_unbond_destroying_with_pending_rewards() {
		ExtBuilder::default()
			.min_join_bond(10)
			.add_members(vec![(20, 20)])
			.build_and_execute(|| {
				unsafe_set_state(1, PoolState::Destroying);
				let random = 123;

				// given the pool some pending rewards.
				assert_eq!(pending_rewards_for_delegator(20), 0);
				deposit_rewards(10);
				assert_eq!(pending_rewards_for_delegator(20), 6);

				// any random user can unbond 20 now.
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(random), 20, 20));
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().active_points(), 0);
				assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_points(), 20);

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
						Event::PaidOut { member: 20, pool_id: 1, payout: 6 },
						Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 3 }
					]
				);
			})
	}

	#[test]
	fn depositor_unbond_open() {
		// depositor in pool, pool state open
		//   - depositor  unbonds to above limit
		//   - depositor cannot unbond to below limit or 0
		ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
			// give the depositor some extra funds.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
			assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);

			// can unbond to above the limit.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);

			// cannot go to below 10:
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
				Error::<T>::MinimumBondNotMet
			);

			// cannot go to 0 either.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 15),
				Error::<T>::MinimumBondNotMet
			);
		})
	}

	#[test]
	fn depositor_kick() {
		// depositor in pool, pool state blocked
		//   - depositor can never be kicked.
		ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
			// give the depositor some extra funds.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
			assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);

			// set the stage
			unsafe_set_state(1, PoolState::Blocked);
			let kicker = DEFAULT_ROLES.bouncer.unwrap();

			// cannot be kicked to above limit.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(kicker), 10, 5),
				Error::<T>::PartialUnbondNotAllowedPermissionlessly
			);

			// or below the limit
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(kicker), 10, 15),
				Error::<T>::PartialUnbondNotAllowedPermissionlessly
			);

			// or 0.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(kicker), 10, 20),
				Error::<T>::DoesNotHavePermission
			);

			// they themselves cannot do it either
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 20),
				Error::<T>::MinimumBondNotMet
			);
		})
	}

	#[test]
	fn depositor_unbond_destroying_permissionless() {
		// depositor can never be permissionlessly unbonded.
		ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
			// give the depositor some extra funds.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
			assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);

			// set the stage
			unsafe_set_state(1, PoolState::Destroying);
			let random = 123;

			// cannot be kicked to above limit.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(random), 10, 5),
				Error::<T>::PartialUnbondNotAllowedPermissionlessly
			);

			// or below the limit
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(random), 10, 15),
				Error::<T>::PartialUnbondNotAllowedPermissionlessly
			);

			// or 0.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(random), 10, 20),
				Error::<T>::DoesNotHavePermission
			);

			// they themselves can do it in this case though.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
		})
	}

	#[test]
	fn depositor_unbond_destroying_not_last_member() {
		// deposit in pool, pool state destroying
		//   - depositor can never leave if there is another member in the pool.
		ExtBuilder::default()
			.min_join_bond(10)
			.add_members(vec![(20, 20)])
			.build_and_execute(|| {
				// give the depositor some extra funds.
				assert_ok!(Pools::bond_extra(
					RuntimeOrigin::signed(10),
					BondExtra::FreeBalance(10)
				));
				assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);

				// set the stage
				unsafe_set_state(1, PoolState::Destroying);

				// can go above the limit
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
				assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);

				// but not below the limit
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
					Error::<T>::MinimumBondNotMet
				);

				// and certainly not zero
				assert_noop!(
					Pools::unbond(RuntimeOrigin::signed(10), 10, 15),
					Error::<T>::MinimumBondNotMet
				);
			})
	}

	#[test]
	fn depositor_unbond_destroying_last_member() {
		// deposit in pool, pool state destroying
		//   - depositor can unbond to above limit always.
		//   - depositor cannot unbond to below limit if last.
		//   - depositor can unbond to 0 if last and destroying.
		ExtBuilder::default().min_join_bond(10).build_and_execute(|| {
			// give the depositor some extra funds.
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
			assert_eq!(PoolMembers::<T>::get(10).unwrap().points, 20);

			// set the stage
			unsafe_set_state(1, PoolState::Destroying);

			// can unbond to above the limit.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 15);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 5);

			// still cannot go to below limit
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
				Error::<T>::MinimumBondNotMet
			);

			// can go to 0 too.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 15));
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 0);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 20);
		})
	}

	#[test]
	fn unbond_of_1_works() {
		ExtBuilder::default().build_and_execute(|| {
			unsafe_set_state(1, PoolState::Destroying);
			assert_ok!(fully_unbond_permissioned(10));

			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
				unbonding_pools_with_era! { 3 => UnbondPool::<Runtime> { points: 10, balance: 10 }}
			);

			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap(),
				BondedPool {
					id: 1,
					inner: BondedPoolInner {
						commission: Commission::default(),
						member_counter: 1,
						points: 0,
						roles: DEFAULT_ROLES,
						state: PoolState::Destroying,
					}
				}
			);

			assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
		});
	}

	#[test]
	fn unbond_of_3_works() {
		ExtBuilder::default()
			.add_members(vec![(40, 40), (550, 550)])
			.build_and_execute(|| {
				let ed = Currency::minimum_balance();
				// Given a slash from 600 -> 500
				StakingMock::slash_by(1, 500);

				// and unclaimed rewards of 600.
				Currency::set_balance(&default_reward_account(), ed + 600);

				// When
				assert_ok!(fully_unbond_permissioned(40));

				// Then
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 3 => UnbondPool { points: 6, balance: 6 }}
				);
				assert_eq!(
					BondedPool::<Runtime>::get(1).unwrap(),
					BondedPool {
						id: 1,
						inner: BondedPoolInner {
							commission: Commission::default(),
							member_counter: 3,
							points: 560,
							roles: DEFAULT_ROLES,
							state: PoolState::Open,
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
						Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
						Event::PoolSlashed { pool_id: 1, balance: 100 },
						Event::PaidOut { member: 40, pool_id: 1, payout: 40 },
						Event::Unbonded { member: 40, pool_id: 1, balance: 6, points: 6, era: 3 }
					]
				);

				assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 94);
				assert_eq!(
					PoolMembers::<Runtime>::get(40).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 6)
				);
				assert_eq!(Currency::free_balance(&40), 40 + 40); // We claim rewards when unbonding

				// When
				unsafe_set_state(1, PoolState::Destroying);
				assert_ok!(fully_unbond_permissioned(550));

				// Then
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 3 => UnbondPool { points: 98, balance: 98 }}
				);
				assert_eq!(
					BondedPool::<Runtime>::get(1).unwrap(),
					BondedPool {
						id: 1,
						inner: BondedPoolInner {
							commission: Commission::default(),
							member_counter: 3,
							points: 10,
							roles: DEFAULT_ROLES,
							state: PoolState::Destroying,
						}
					}
				);
				assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 2);
				assert_eq!(
					PoolMembers::<Runtime>::get(550).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 92)
				);
				assert_eq!(Currency::free_balance(&550), 550 + 550);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::PaidOut { member: 550, pool_id: 1, payout: 550 },
						Event::Unbonded {
							member: 550,
							pool_id: 1,
							points: 92,
							balance: 92,
							era: 3
						}
					]
				);

				// When
				CurrentEra::set(3);
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 40, 0));
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 550, 0));
				assert_ok!(fully_unbond_permissioned(10));

				// Then
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 6 => UnbondPool { points: 2, balance: 2 }}
				);
				assert_eq!(
					BondedPool::<Runtime>::get(1).unwrap(),
					BondedPool {
						id: 1,
						inner: BondedPoolInner {
							commission: Commission::default(),
							member_counter: 1,
							points: 0,
							roles: DEFAULT_ROLES,
							state: PoolState::Destroying,
						}
					}
				);
				assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);

				assert_eq!(Currency::free_balance(&550), 550 + 550 + 92);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 40, pool_id: 1, points: 6, balance: 6 },
						Event::MemberRemoved { pool_id: 1, member: 40 },
						Event::Withdrawn { member: 550, pool_id: 1, points: 92, balance: 92 },
						Event::MemberRemoved { pool_id: 1, member: 550 },
						Event::PaidOut { member: 10, pool_id: 1, payout: 10 },
						Event::Unbonded { member: 10, pool_id: 1, points: 2, balance: 2, era: 6 }
					]
				);
			});
	}

	#[test]
	fn unbond_merges_older_pools() {
		ExtBuilder::default().with_check(1).build_and_execute(|| {
			// Given
			assert_eq!(StakingMock::bonding_duration(), 3);
			SubPoolsStorage::<Runtime>::insert(
				1,
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { balance: 10, points: 100 },
						1 + 3 => UnbondPool { balance: 20, points: 20 },
						2 + 3 => UnbondPool { balance: 101, points: 101}
					},
				},
			);
			unsafe_set_state(1, PoolState::Destroying);

			// When
			let current_era = 1 + TotalUnbondingPools::<Runtime>::get();
			CurrentEra::set(current_era);

			assert_ok!(fully_unbond_permissioned(10));

			// Then
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: UnbondPool { balance: 10 + 20, points: 100 + 20 },
					with_era: unbonding_pools_with_era! {
						2 + 3 => UnbondPool { balance: 101, points: 101},
						current_era + 3 => UnbondPool { balance: 10, points: 10 },
					},
				},
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 }
				]
			);
		});
	}

	#[test]
	fn unbond_kick_works() {
		// Kick: the pool is blocked and the caller is either the root or bouncer.
		ExtBuilder::default()
			.add_members(vec![(100, 100), (200, 200)])
			.build_and_execute(|| {
				// Given
				unsafe_set_state(1, PoolState::Blocked);
				let bonded_pool = BondedPool::<Runtime>::get(1).unwrap();
				assert_eq!(bonded_pool.roles.root.unwrap(), 900);
				assert_eq!(bonded_pool.roles.nominator.unwrap(), 901);
				assert_eq!(bonded_pool.roles.bouncer.unwrap(), 902);

				// When the nominator tries to kick, then its a noop
				assert_noop!(
					Pools::fully_unbond(RuntimeOrigin::signed(901), 100),
					Error::<Runtime>::NotKickerOrDestroying
				);

				// When the root kicks then its ok
				// Account with ID 100 is kicked.
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(900), 100));

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
						Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true },
						Event::Unbonded {
							member: 100,
							pool_id: 1,
							points: 100,
							balance: 100,
							era: 3
						},
					]
				);

				// When the bouncer kicks then its ok
				// Account with ID 200 is kicked.
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(902), 200));

				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Unbonded {
						member: 200,
						pool_id: 1,
						points: 200,
						balance: 200,
						era: 3
					}]
				);

				assert_eq!(
					BondedPool::<Runtime>::get(1).unwrap(),
					BondedPool {
						id: 1,
						inner: BondedPoolInner {
							commission: Commission::default(),
							member_counter: 3,
							points: 10, // Only 10 points because 200 + 100 was unbonded
							roles: DEFAULT_ROLES,
							state: PoolState::Blocked,
						}
					}
				);
				assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						no_era: Default::default(),
						with_era: unbonding_pools_with_era! {
							3 => UnbondPool { points: 100 + 200, balance: 100 + 200 }
						},
					}
				);
				assert_eq!(
					*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
					vec![(3, 100), (3, 200)],
				);
			});
	}

	#[test]
	fn unbond_permissionless_works() {
		// Scenarios where non-admin accounts can unbond others
		ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
			// Given the pool is blocked
			unsafe_set_state(1, PoolState::Blocked);

			// A permissionless unbond attempt errors
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(420), 100),
				Error::<Runtime>::NotKickerOrDestroying
			);

			// permissionless unbond must be full
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(420), 100, 80),
				Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
			);

			// Given the pool is destroying
			unsafe_set_state(1, PoolState::Destroying);

			// The depositor cannot be fully unbonded until they are the last member
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(10), 10),
				Error::<Runtime>::MinimumBondNotMet,
			);

			// Any account can unbond a member that is not the depositor
			assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(420), 100));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
					Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 }
				]
			);

			// still permissionless unbond must be full
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(420), 100, 80),
				Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
			);

			// Given the pool is blocked
			unsafe_set_state(1, PoolState::Blocked);

			// The depositor cannot be unbonded
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Given the pools is destroying
			unsafe_set_state(1, PoolState::Destroying);

			// The depositor cannot be unbonded yet.
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
				Error::<Runtime>::DoesNotHavePermission,
			);

			// but when everyone is unbonded it can..
			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 100, 0));

			// still permissionless unbond must be full.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(420), 10, 5),
				Error::<Runtime>::PartialUnbondNotAllowedPermissionlessly,
			);

			// depositor can never be unbonded permissionlessly .
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(420), 10),
				Error::<T>::DoesNotHavePermission
			);
			// but depositor itself can do it.
			assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10));

			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 0);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 + 3 => UnbondPool { points: 10, balance: 10 }
					}
				}
			);
			assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 0);
			assert_eq!(
				*UnbondingBalanceMap::get().get(&default_bonded_account()).unwrap(),
				vec![(6, 10)]
			);
		});
	}

	#[test]
	#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
	#[cfg_attr(not(debug_assertions), should_panic)]
	fn unbond_errors_correctly() {
		ExtBuilder::default().build_and_execute(|| {
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(11), 11),
				Error::<Runtime>::PoolMemberNotFound
			);

			// Add the member
			let member = PoolMember { pool_id: 2, points: 10, ..Default::default() };
			PoolMembers::<Runtime>::insert(11, member);

			let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11);
		});
	}

	#[test]
	#[cfg_attr(debug_assertions, should_panic(expected = "Defensive failure has been triggered!"))]
	#[cfg_attr(not(debug_assertions), should_panic)]
	fn unbond_panics_when_reward_pool_not_found() {
		ExtBuilder::default().build_and_execute(|| {
			let member = PoolMember { pool_id: 2, points: 10, ..Default::default() };
			PoolMembers::<Runtime>::insert(11, member);
			BondedPool::<Runtime> {
				id: 1,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 10,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			}
			.put();

			let _ = Pools::fully_unbond(RuntimeOrigin::signed(11), 11);
		});
	}

	#[test]
	fn partial_unbond_era_tracking() {
		ExtBuilder::default().ed(1).build_and_execute(|| {
			// to make the depositor capable of withdrawing.
			StakingMinBond::set(1);
			MinCreateBond::<T>::set(1);
			MinJoinBond::<T>::set(1);
			assert_eq!(Pools::depositor_min_bond(), 1);

			// given
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().pool_id, 1);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!()
			);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 10);
			assert!(SubPoolsStorage::<Runtime>::get(1).is_none());
			assert_eq!(CurrentEra::get(), 0);
			assert_eq!(BondingDuration::get(), 3);

			// so the depositor can leave, just keeps the test simpler.
			unsafe_set_state(1, PoolState::Destroying);

			// when: casual unbond
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));

			// then
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 9);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 1);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 9);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 3 }
				]
			);

			// when: casual further unbond, same era.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 5));

			// then
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 4);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 6);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 4);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 6)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 6, balance: 6 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 3 }]
			);

			// when: casual further unbond, next era.
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));

			// then
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 3);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 7);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 3);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 6, 4 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 6, balance: 6 },
						4 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }]
			);

			// when: unbonding more than our active: error
			assert_noop!(
				frame_support::storage::with_storage_layer(|| Pools::unbond(
					RuntimeOrigin::signed(10),
					10,
					5
				)),
				Error::<Runtime>::MinimumBondNotMet
			);
			// instead:
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));

			// then
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 0);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 10);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().points, 0);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 6, 4 => 4)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 6, balance: 6 },
						4 => UnbondPool { points: 4, balance: 4 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 4 }]
			);
		});
	}

	#[test]
	fn partial_unbond_max_chunks() {
		ExtBuilder::default().add_members(vec![(20, 20)]).ed(1).build_and_execute(|| {
			MaxUnbonding::set(2);

			// given
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3));
			assert_eq!(
				PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 2, 4 => 3)
			);

			// when
			CurrentEra::set(2);
			assert_noop!(
				frame_support::storage::with_storage_layer(|| Pools::unbond(
					RuntimeOrigin::signed(20),
					20,
					4
				)),
				Error::<Runtime>::MaxUnbondingLimit
			);

			// when
			MaxUnbonding::set(3);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 1));

			assert_eq!(
				PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 2, 4 => 3, 5 => 1)
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::Unbonded { member: 20, pool_id: 1, points: 2, balance: 2, era: 3 },
					Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 },
					Event::Unbonded { member: 20, pool_id: 1, points: 1, balance: 1, era: 5 }
				]
			);
		})
	}

	// depositor can unbond only up to `MinCreateBond`.
	#[test]
	fn depositor_permissioned_partial_unbond() {
		ExtBuilder::default().ed(1).build_and_execute(|| {
			// given
			StakingMinBond::set(5);
			assert_eq!(Pools::depositor_min_bond(), 5);

			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);

			// can unbond a bit..
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 7);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 3);

			// but not less than 2
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 6),
				Error::<Runtime>::MinimumBondNotMet
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Unbonded { member: 10, pool_id: 1, points: 3, balance: 3, era: 3 }
				]
			);
		});
	}

	#[test]
	fn depositor_permissioned_partial_unbond_slashed() {
		ExtBuilder::default().ed(1).build_and_execute(|| {
			// given
			assert_eq!(MinCreateBond::<Runtime>::get(), 2);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 10);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 0);

			// slash the default pool
			StakingMock::slash_by(1, 5);

			// cannot unbond even 7, because the value of shares is now less.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 7),
				Error::<Runtime>::MinimumBondNotMet
			);
		});
	}

	#[test]
	fn every_unbonding_triggers_payout() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			let initial_reward_account = Currency::free_balance(&default_reward_account());
			assert_eq!(initial_reward_account, Currency::minimum_balance());
			assert_eq!(initial_reward_account, 5);

			Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());

			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 2));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					// 2/3 of ed, which is 20's share.
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::PaidOut { member: 20, pool_id: 1, payout: 10 },
					Event::Unbonded { member: 20, pool_id: 1, balance: 2, points: 2, era: 3 }
				]
			);

			CurrentEra::set(1);
			Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());

			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 3));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					// 2/3 of ed, which is 20's share.
					Event::PaidOut { member: 20, pool_id: 1, payout: 6 },
					Event::Unbonded { member: 20, pool_id: 1, points: 3, balance: 3, era: 4 }
				]
			);

			CurrentEra::set(2);
			Currency::set_balance(&default_reward_account(), 4 * Currency::minimum_balance());

			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 20, pool_id: 1, payout: 3 },
					Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 }
				]
			);

			assert_eq!(
				PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 2, 4 => 3, 5 => 5)
			);
		});
	}
}

mod pool_withdraw_unbonded {
	use super::*;

	#[test]
	fn pool_withdraw_unbonded_works() {
		ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
			// Given 10 unbond'ed directly against the pool account

			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));

			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);

			// When
			CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
			assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));

			// Then their unbonding balance is no longer locked
			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
		});
	}
	#[test]
	fn pool_withdraw_unbonded_creates_tvl_diff() {
		ExtBuilder::default().add_members(vec![(20, 10)]).build_and_execute(|| {
			// Given 10 unbond'ed directly against the pool account
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));

			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(20));
			assert_eq!(Balances::free_balance(&default_bonded_account()), 20);
			assert_eq!(TotalValueLocked::<T>::get(), 20);

			// When
			CurrentEra::set(StakingMock::current_era() + StakingMock::bonding_duration() + 1);
			assert_ok!(Pools::pool_withdraw_unbonded(RuntimeOrigin::signed(10), 1, 0));
			assert_eq!(TotalValueLocked::<T>::get(), 15);

			let member_balance = PoolMembers::<T>::iter()
				.map(|(_, member)| member.total_balance())
				.reduce(|acc, total_balance| acc + total_balance)
				.unwrap_or_default();

			// Then their unbonding balance is no longer locked
			assert_eq!(StakingMock::active_stake(&default_bonded_account()), Ok(15));
			assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(15));
			assert_eq!(Currency::free_balance(&default_bonded_account()), 20);

			// The difference between TVL and member_balance is exactly the difference between
			// `total_stake` and the `free_balance`.
			// This relation is not guaranteed in the wild as arbitrary transfers towards
			// `free_balance` can be made to the pool that are not accounted for.
			let non_locked_balance = Balances::free_balance(&default_bonded_account()) -
				StakingMock::total_stake(&default_bonded_account()).unwrap();
			assert_eq!(member_balance, TotalValueLocked::<T>::get() + non_locked_balance);
		});
	}
}

mod withdraw_unbonded {
	use super::*;
	use sp_runtime::bounded_btree_map;

	#[test]
	fn withdraw_unbonded_works_against_slashed_no_era_sub_pool() {
		ExtBuilder::default()
			.add_members(vec![(40, 40), (550, 550)])
			.build_and_execute(|| {
				// reduce the noise a bit.
				let _ = balances_events_since_last_call();

				// Given
				assert_eq!(StakingMock::bonding_duration(), 3);
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(550), 550));
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(40), 40));
				assert_eq!(Currency::free_balance(&default_bonded_account()), 600);

				let mut current_era = 1;
				CurrentEra::set(current_era);

				let mut sub_pools = SubPoolsStorage::<Runtime>::get(1).unwrap();
				let unbond_pool = sub_pools.with_era.get_mut(&3).unwrap();
				// Sanity check
				assert_eq!(*unbond_pool, UnbondPool { points: 550 + 40, balance: 550 + 40 });
				assert_eq!(TotalValueLocked::<Runtime>::get(), 600);

				// Simulate a slash to the pool with_era(current_era), decreasing the balance by
				// half
				{
					unbond_pool.balance /= 2; // 295
					SubPoolsStorage::<Runtime>::insert(1, sub_pools);

					// Adjust the TVL for this non-api usage (direct sub-pool modification)
					TotalValueLocked::<Runtime>::mutate(|x| *x -= 295);

					// Update the equivalent of the unbonding chunks for the `StakingMock`
					let mut x = UnbondingBalanceMap::get();
					x.get_mut(&default_bonded_account())
						.unwrap()
						.get_mut(current_era as usize)
						.unwrap()
						.1 /= 2;
					UnbondingBalanceMap::set(&x);

					Currency::set_balance(
						&default_bonded_account(),
						Currency::free_balance(&default_bonded_account()) / 2, // 300
					);
					assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 10);
					StakingMock::slash_by(1, 5);
					assert_eq!(StakingMock::active_stake(&default_bonded_account()).unwrap(), 5);
				};

				// Advance the current_era to ensure all `with_era` pools will be merged into
				// `no_era` pool
				current_era += TotalUnbondingPools::<Runtime>::get();
				CurrentEra::set(current_era);

				// Simulate some other call to unbond that would merge `with_era` pools into
				// `no_era`
				let sub_pools =
					SubPoolsStorage::<Runtime>::get(1).unwrap().maybe_merge_pools(current_era);
				SubPoolsStorage::<Runtime>::insert(1, sub_pools);

				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						no_era: UnbondPool { points: 550 + 40, balance: 275 + 20 },
						with_era: Default::default()
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
						Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
						Event::Unbonded {
							member: 550,
							pool_id: 1,
							points: 550,
							balance: 550,
							era: 3
						},
						Event::Unbonded { member: 40, pool_id: 1, points: 40, balance: 40, era: 3 },
						Event::PoolSlashed { pool_id: 1, balance: 5 }
					]
				);
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Burned { who: default_bonded_account(), amount: 300 }]
				);

				// When
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));

				// Then
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().no_era,
					UnbondPool { points: 40, balance: 20 }
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 550 },
						Event::MemberRemoved { pool_id: 1, member: 550 }
					]
				);
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 }]
				);

				// When
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));

				// Then
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().no_era,
					UnbondPool { points: 0, balance: 0 }
				);
				assert!(!PoolMembers::<Runtime>::contains_key(40));
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 40 },
						Event::MemberRemoved { pool_id: 1, member: 40 }
					]
				);
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 }]
				);

				// now, finally, the depositor can take out its share.
				unsafe_set_state(1, PoolState::Destroying);
				assert_ok!(fully_unbond_permissioned(10));

				current_era += 3;
				CurrentEra::set(current_era);

				// when
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Unbonded { member: 10, pool_id: 1, balance: 5, points: 5, era: 9 },
						Event::Withdrawn { member: 10, pool_id: 1, balance: 5, points: 5 },
						Event::MemberRemoved { pool_id: 1, member: 10 },
						Event::Destroyed { pool_id: 1 }
					]
				);
				assert!(!Metadata::<T>::contains_key(1));
				assert_eq!(
					balances_events_since_last_call(),
					vec![
						BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
						BEvent::Thawed { who: default_reward_account(), amount: 5 },
						BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
					]
				);
			});
	}

	#[test]
	fn withdraw_unbonded_works_against_slashed_with_era_sub_pools() {
		ExtBuilder::default()
			.add_members(vec![(40, 40), (550, 550)])
			.build_and_execute(|| {
				let _ = balances_events_since_last_call();

				// Given
				// current bond is 600, we slash it all to 300.
				StakingMock::slash_by(1, 300);
				Currency::set_balance(&default_bonded_account(), 300);
				assert_eq!(StakingMock::total_stake(&default_bonded_account()), Ok(300));

				assert_ok!(fully_unbond_permissioned(40));
				assert_ok!(fully_unbond_permissioned(550));

				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2 + 40 / 2, balance: 550 / 2 + 40 / 2
					}}
				);

				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 40, pool_id: 1, bonded: 40, joined: true },
						Event::Bonded { member: 550, pool_id: 1, bonded: 550, joined: true },
						Event::PoolSlashed { pool_id: 1, balance: 300 },
						Event::Unbonded { member: 40, pool_id: 1, balance: 20, points: 20, era: 3 },
						Event::Unbonded {
							member: 550,
							pool_id: 1,
							balance: 275,
							points: 275,
							era: 3,
						}
					]
				);
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Burned { who: default_bonded_account(), amount: 300 },]
				);

				CurrentEra::set(StakingMock::bonding_duration());

				// When
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(40), 40, 0));

				// Then
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Transfer { from: default_bonded_account(), to: 40, amount: 20 },]
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 40, pool_id: 1, balance: 20, points: 20 },
						Event::MemberRemoved { pool_id: 1, member: 40 }
					]
				);

				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 3 => UnbondPool { points: 550 / 2, balance: 550 / 2 }}
				);

				// When
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(550), 550, 0));

				// Then
				assert_eq!(
					balances_events_since_last_call(),
					vec![BEvent::Transfer { from: default_bonded_account(), to: 550, amount: 275 },]
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 550, pool_id: 1, balance: 275, points: 275 },
						Event::MemberRemoved { pool_id: 1, member: 550 }
					]
				);
				assert!(SubPoolsStorage::<Runtime>::get(1).unwrap().with_era.is_empty());

				// now, finally, the depositor can take out its share.
				unsafe_set_state(1, PoolState::Destroying);
				assert_ok!(fully_unbond_permissioned(10));

				// because everyone else has left, the points
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
					unbonding_pools_with_era! { 6 => UnbondPool { points: 5, balance: 5 }}
				);

				CurrentEra::set(CurrentEra::get() + 3);

				// set metadata to check that it's being removed on dissolve
				assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
				assert!(Metadata::<T>::contains_key(1));

				// when
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

				// then
				assert_eq!(Currency::free_balance(&10), 10 + 35);
				assert_eq!(Currency::free_balance(&default_bonded_account()), 0);

				// in this test 10 also gets a fair share of the slash, because the slash was
				// applied to the bonded account.
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Unbonded { member: 10, pool_id: 1, points: 5, balance: 5, era: 6 },
						Event::Withdrawn { member: 10, pool_id: 1, points: 5, balance: 5 },
						Event::MemberRemoved { pool_id: 1, member: 10 },
						Event::Destroyed { pool_id: 1 }
					]
				);
				assert!(!Metadata::<T>::contains_key(1));
				assert_eq!(
					balances_events_since_last_call(),
					vec![
						BEvent::Transfer { from: default_bonded_account(), to: 10, amount: 5 },
						BEvent::Thawed { who: default_reward_account(), amount: 5 },
						BEvent::Transfer { from: default_reward_account(), to: 10, amount: 5 }
					]
				);
			});
	}

	#[test]
	fn withdraw_unbonded_handles_faulty_sub_pool_accounting() {
		ExtBuilder::default().build_and_execute(|| {
			// Given
			assert_eq!(Currency::minimum_balance(), 5);
			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(Currency::free_balance(&default_bonded_account()), 10);
			unsafe_set_state(1, PoolState::Destroying);
			assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(10), 10));

			// Simulate a slash that is not accounted for in the sub pools.
			Currency::set_balance(&default_bonded_account(), 5);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap().with_era,
				//------------------------------balance decrease is not account for
				unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10 } }
			);

			CurrentEra::set(3);

			// When
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			// Then
			assert_eq!(Currency::free_balance(&10), 10 + 35);
			assert_eq!(Currency::free_balance(&default_bonded_account()), 0);
		});
	}

	#[test]
	fn withdraw_unbonded_errors_correctly() {
		ExtBuilder::default().with_check(0).build_and_execute(|| {
			// Insert the sub-pool
			let sub_pools = SubPools {
				no_era: Default::default(),
				with_era: unbonding_pools_with_era! { 3 => UnbondPool { points: 10, balance: 10  }},
			};
			SubPoolsStorage::<Runtime>::insert(1, sub_pools.clone());

			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
				Error::<Runtime>::PoolMemberNotFound
			);

			let mut member = PoolMember { pool_id: 1, points: 10, ..Default::default() };
			PoolMembers::<Runtime>::insert(11, member.clone());

			// Simulate calling `unbond`
			member.unbonding_eras = member_unbonding_eras!(3 => 10);
			PoolMembers::<Runtime>::insert(11, member.clone());

			// We are still in the bonding duration
			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
				Error::<Runtime>::CannotWithdrawAny
			);

			// If we error the member does not get removed
			assert_eq!(PoolMembers::<Runtime>::get(11), Some(member));
			// and the sub pools do not get updated.
			assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), sub_pools)
		});
	}

	#[test]
	fn withdraw_unbonded_kick() {
		ExtBuilder::default()
			.add_members(vec![(100, 100), (200, 200)])
			.build_and_execute(|| {
				// Given
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100));
				assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(200), 200));
				assert_eq!(
					BondedPool::<Runtime>::get(1).unwrap(),
					BondedPool {
						id: 1,
						inner: BondedPoolInner {
							commission: Commission::default(),
							member_counter: 3,
							points: 10,
							roles: DEFAULT_ROLES,
							state: PoolState::Open,
						}
					}
				);
				CurrentEra::set(StakingMock::bonding_duration());

				// Cannot kick when pool is open
				assert_noop!(
					Pools::withdraw_unbonded(RuntimeOrigin::signed(902), 100, 0),
					Error::<Runtime>::NotKickerOrDestroying
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
						Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
						Event::Bonded { member: 200, pool_id: 1, bonded: 200, joined: true },
						Event::Unbonded {
							member: 100,
							pool_id: 1,
							points: 100,
							balance: 100,
							era: 3
						},
						Event::Unbonded {
							member: 200,
							pool_id: 1,
							points: 200,
							balance: 200,
							era: 3
						}
					]
				);

				// Given
				unsafe_set_state(1, PoolState::Blocked);

				// Cannot kick as a nominator
				assert_noop!(
					Pools::withdraw_unbonded(RuntimeOrigin::signed(901), 100, 0),
					Error::<Runtime>::NotKickerOrDestroying
				);

				// Can kick as root
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 100, 0));

				// Can kick as bouncer
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(900), 200, 0));

				assert_eq!(Currency::free_balance(&100), 100 + 100);
				assert_eq!(Currency::free_balance(&200), 200 + 200);
				assert!(!PoolMembers::<Runtime>::contains_key(100));
				assert!(!PoolMembers::<Runtime>::contains_key(200));
				assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 },
						Event::MemberRemoved { pool_id: 1, member: 100 },
						Event::Withdrawn { member: 200, pool_id: 1, points: 200, balance: 200 },
						Event::MemberRemoved { pool_id: 1, member: 200 }
					]
				);
			});
	}

	#[test]
	fn withdraw_unbonded_destroying_permissionless() {
		ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
			// Given
			assert_ok!(Pools::fully_unbond(RuntimeOrigin::signed(100), 100));
			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap(),
				BondedPool {
					id: 1,
					inner: BondedPoolInner {
						commission: Commission::default(),
						member_counter: 2,
						points: 10,
						roles: DEFAULT_ROLES,
						state: PoolState::Open,
					}
				}
			);
			CurrentEra::set(StakingMock::bonding_duration());
			assert_eq!(Currency::free_balance(&100), 100);

			// Cannot permissionlessly withdraw
			assert_noop!(
				Pools::fully_unbond(RuntimeOrigin::signed(420), 100),
				Error::<Runtime>::NotKickerOrDestroying
			);

			// Given
			unsafe_set_state(1, PoolState::Destroying);

			// Can permissionlessly withdraw a member that is not the depositor
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(420), 100, 0));

			assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default(),);
			assert_eq!(Currency::free_balance(&100), 100 + 100);
			assert!(!PoolMembers::<Runtime>::contains_key(100));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
					Event::Unbonded { member: 100, pool_id: 1, points: 100, balance: 100, era: 3 },
					Event::Withdrawn { member: 100, pool_id: 1, points: 100, balance: 100 },
					Event::MemberRemoved { pool_id: 1, member: 100 }
				]
			);
		});
	}

	#[test]
	fn partial_withdraw_unbonded_depositor() {
		ExtBuilder::default().ed(1).build_and_execute(|| {
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
			unsafe_set_state(1, PoolState::Destroying);

			// given
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 6));
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 1));
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 6, 4 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 6, balance: 6 },
						4 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().active_points(), 13);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().unbonding_points(), 7);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
					Event::Unbonded { member: 10, pool_id: 1, points: 6, balance: 6, era: 3 },
					Event::Unbonded { member: 10, pool_id: 1, points: 1, balance: 1, era: 4 }
				]
			);

			// when
			CurrentEra::set(2);
			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0),
				Error::<Runtime>::CannotWithdrawAny
			);

			// when
			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			// then
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(4 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						4 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Withdrawn { member: 10, pool_id: 1, points: 6, balance: 6 }]
			);

			// when
			CurrentEra::set(4);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			// then
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!()
			);
			assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Withdrawn { member: 10, pool_id: 1, points: 1, balance: 1 },]
			);

			// when repeating:
			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0),
				Error::<Runtime>::CannotWithdrawAny
			);
		});
	}

	#[test]
	fn partial_withdraw_unbonded_non_depositor() {
		ExtBuilder::default().add_members(vec![(11, 10)]).build_and_execute(|| {
			// given
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 6));
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(11), 11, 1));
			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 6, 4 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						3 => UnbondPool { points: 6, balance: 6 },
						4 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(PoolMembers::<Runtime>::get(11).unwrap().active_points(), 3);
			assert_eq!(PoolMembers::<Runtime>::get(11).unwrap().unbonding_points(), 7);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 11, pool_id: 1, bonded: 10, joined: true },
					Event::Unbonded { member: 11, pool_id: 1, points: 6, balance: 6, era: 3 },
					Event::Unbonded { member: 11, pool_id: 1, points: 1, balance: 1, era: 4 }
				]
			);

			// when
			CurrentEra::set(2);
			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
				Error::<Runtime>::CannotWithdrawAny
			);

			// when
			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0));

			// then
			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
				member_unbonding_eras!(4 => 1)
			);
			assert_eq!(
				SubPoolsStorage::<Runtime>::get(1).unwrap(),
				SubPools {
					no_era: Default::default(),
					with_era: unbonding_pools_with_era! {
						4 => UnbondPool { points: 1, balance: 1 }
					}
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Withdrawn { member: 11, pool_id: 1, points: 6, balance: 6 }]
			);

			// when
			CurrentEra::set(4);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0));

			// then
			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap().unbonding_eras,
				member_unbonding_eras!()
			);
			assert_eq!(SubPoolsStorage::<Runtime>::get(1).unwrap(), Default::default());
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Withdrawn { member: 11, pool_id: 1, points: 1, balance: 1 }]
			);

			// when repeating:
			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(11), 11, 0),
				Error::<Runtime>::CannotWithdrawAny
			);
		});
	}

	#[test]
	fn full_multi_step_withdrawing_non_depositor() {
		ExtBuilder::default().add_members(vec![(100, 100)]).build_and_execute(|| {
			assert_eq!(TotalValueLocked::<T>::get(), 110);
			// given
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 75));
			assert_eq!(
				PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 75)
			);

			// tvl unchanged.
			assert_eq!(TotalValueLocked::<T>::get(), 110);

			// progress one era and unbond the leftover.
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(100), 100, 25));
			assert_eq!(
				PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 75, 4 => 25)
			);

			assert_noop!(
				Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0),
				Error::<Runtime>::CannotWithdrawAny
			);
			// tvl unchanged.
			assert_eq!(TotalValueLocked::<T>::get(), 110);

			// now the 75 should be free.
			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 100, pool_id: 1, bonded: 100, joined: true },
					Event::Unbonded { member: 100, pool_id: 1, points: 75, balance: 75, era: 3 },
					Event::Unbonded { member: 100, pool_id: 1, points: 25, balance: 25, era: 4 },
					Event::Withdrawn { member: 100, pool_id: 1, points: 75, balance: 75 },
				]
			);
			assert_eq!(
				PoolMembers::<Runtime>::get(100).unwrap().unbonding_eras,
				member_unbonding_eras!(4 => 25)
			);
			// tvl updated
			assert_eq!(TotalValueLocked::<T>::get(), 35);

			// the 25 should be free now, and the member removed.
			CurrentEra::set(4);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(100), 100, 0));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Withdrawn { member: 100, pool_id: 1, points: 25, balance: 25 },
					Event::MemberRemoved { pool_id: 1, member: 100 }
				]
			);
		})
	}

	#[test]
	fn out_of_sync_unbonding_chunks() {
		// the unbonding_eras in pool member are always fixed to the era at which they are unlocked,
		// but the actual unbonding pools get pruned and might get combined in the no_era pool.
		// Pools are only merged when one unbonds, so we unbond a little bit on every era to
		// simulate this.
		ExtBuilder::default()
			.add_members(vec![(20, 100), (30, 100)])
			.build_and_execute(|| {
				System::reset_events();

				// when
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(30), 30, 5));

				// then member-local unbonding is pretty much in sync with the global pools.
				assert_eq!(
					PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 5)
				);
				assert_eq!(
					PoolMembers::<Runtime>::get(30).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 5)
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						no_era: Default::default(),
						with_era: unbonding_pools_with_era! {
							3 => UnbondPool { points: 10, balance: 10 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 3 },
						Event::Unbonded { member: 30, pool_id: 1, points: 5, balance: 5, era: 3 },
					]
				);

				// when
				CurrentEra::set(1);
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));

				// then still member-local unbonding is pretty much in sync with the global pools.
				assert_eq!(
					PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 5, 4 => 5)
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						no_era: Default::default(),
						with_era: unbonding_pools_with_era! {
							3 => UnbondPool { points: 10, balance: 10 },
							4 => UnbondPool { points: 5, balance: 5 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 4 }]
				);

				// when
				CurrentEra::set(2);
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));

				// then still member-local unbonding is pretty much in sync with the global pools.
				assert_eq!(
					PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5)
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						no_era: Default::default(),
						with_era: unbonding_pools_with_era! {
							3 => UnbondPool { points: 10, balance: 10 },
							4 => UnbondPool { points: 5, balance: 5 },
							5 => UnbondPool { points: 5, balance: 5 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 5 }]
				);

				// when
				CurrentEra::set(5);
				assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));

				// then
				assert_eq!(
					PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
					member_unbonding_eras!(3 => 5, 4 => 5, 5 => 5, 8 => 5)
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						// era 3 is merged into no_era.
						no_era: UnbondPool { points: 10, balance: 10 },
						with_era: unbonding_pools_with_era! {
							4 => UnbondPool { points: 5, balance: 5 },
							5 => UnbondPool { points: 5, balance: 5 },
							8 => UnbondPool { points: 5, balance: 5 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Unbonded { member: 20, pool_id: 1, points: 5, balance: 5, era: 8 }]
				);

				// now we start withdrawing unlocked bonds.

				// when
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
				// then
				assert_eq!(
					PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras,
					member_unbonding_eras!(8 => 5)
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						// era 3 is merged into no_era.
						no_era: UnbondPool { points: 5, balance: 5 },
						with_era: unbonding_pools_with_era! {
							8 => UnbondPool { points: 5, balance: 5 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Withdrawn { member: 20, pool_id: 1, points: 15, balance: 15 }]
				);

				// when
				assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(30), 30, 0));
				// then
				assert_eq!(
					PoolMembers::<Runtime>::get(30).unwrap().unbonding_eras,
					member_unbonding_eras!()
				);
				assert_eq!(
					SubPoolsStorage::<Runtime>::get(1).unwrap(),
					SubPools {
						// era 3 is merged into no_era.
						no_era: Default::default(),
						with_era: unbonding_pools_with_era! {
							8 => UnbondPool { points: 5, balance: 5 }
						}
					}
				);
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::Withdrawn { member: 30, pool_id: 1, points: 5, balance: 5 }]
				);
			})
	}

	#[test]
	fn full_multi_step_withdrawing_depositor() {
		ExtBuilder::default().ed(1).build_and_execute(|| {
			// depositor now has 20, they can unbond to 10.
			assert_eq!(Pools::depositor_min_bond(), 10);
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));

			// now they can.
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 7));

			// progress one era and unbond the leftover.
			CurrentEra::set(1);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 3));

			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(3 => 7, 4 => 3)
			);

			// they can't unbond to a value below 10 other than 0..
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 5),
				Error::<Runtime>::MinimumBondNotMet
			);

			// but not even full, because they pool is not yet destroying.
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 10),
				Error::<Runtime>::MinimumBondNotMet
			);

			// but now they can.
			unsafe_set_state(1, PoolState::Destroying);
			assert_noop!(
				Pools::unbond(RuntimeOrigin::signed(10), 10, 5),
				Error::<Runtime>::MinimumBondNotMet
			);
			assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));

			// now the 7 should be free.
			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false },
					Event::Unbonded { member: 10, pool_id: 1, balance: 7, points: 7, era: 3 },
					Event::Unbonded { member: 10, pool_id: 1, balance: 3, points: 3, era: 4 },
					Event::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
					Event::Withdrawn { member: 10, pool_id: 1, balance: 7, points: 7 }
				]
			);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap().unbonding_eras,
				member_unbonding_eras!(4 => 13)
			);

			// the 13 should be free now, and the member removed.
			CurrentEra::set(4);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Withdrawn { member: 10, pool_id: 1, points: 13, balance: 13 },
					Event::MemberRemoved { pool_id: 1, member: 10 },
					Event::Destroyed { pool_id: 1 },
				]
			);
			assert!(!Metadata::<T>::contains_key(1));
		})
	}

	#[test]
	fn withdraw_unbonded_removes_claim_permissions_on_leave() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			// Given
			CurrentEra::set(1);
			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);

			assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 20));
			assert_eq!(
				ClaimPermissions::<Runtime>::get(20),
				ClaimPermission::PermissionlessWithdraw
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::Unbonded { member: 20, pool_id: 1, balance: 20, points: 20, era: 4 },
				]
			);

			CurrentEra::set(5);

			// When
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Withdrawn { member: 20, pool_id: 1, balance: 20, points: 20 },
					Event::MemberRemoved { pool_id: 1, member: 20 }
				]
			);

			// Then
			assert_eq!(PoolMembers::<Runtime>::get(20), None);
			assert_eq!(ClaimPermissions::<Runtime>::contains_key(20), false);
		});
	}
}

mod create {
	use super::*;
	use frame_support::traits::fungible::InspectFreeze;

	#[test]
	fn create_works() {
		ExtBuilder::default().build_and_execute(|| {
			// next pool id is 2.
			let next_pool_stash = Pools::create_bonded_account(2);
			let ed = Currency::minimum_balance();

			assert_eq!(TotalValueLocked::<T>::get(), 10);
			assert!(!BondedPools::<Runtime>::contains_key(2));
			assert!(!RewardPools::<Runtime>::contains_key(2));
			assert!(!PoolMembers::<Runtime>::contains_key(11));
			assert_err!(StakingMock::active_stake(&next_pool_stash), "balance not found");

			Currency::set_balance(&11, StakingMock::minimum_nominator_bond() + ed);
			assert_ok!(Pools::create(
				RuntimeOrigin::signed(11),
				StakingMock::minimum_nominator_bond(),
				123,
				456,
				789
			));
			assert_eq!(TotalValueLocked::<T>::get(), 10 + StakingMock::minimum_nominator_bond());

			assert_eq!(Currency::free_balance(&11), 0);
			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap(),
				PoolMember {
					pool_id: 2,
					points: StakingMock::minimum_nominator_bond(),
					..Default::default()
				}
			);
			assert_eq!(
				BondedPool::<Runtime>::get(2).unwrap(),
				BondedPool {
					id: 2,
					inner: BondedPoolInner {
						commission: Commission::default(),
						points: StakingMock::minimum_nominator_bond(),
						member_counter: 1,
						roles: PoolRoles {
							depositor: 11,
							root: Some(123),
							nominator: Some(456),
							bouncer: Some(789)
						},
						state: PoolState::Open,
					}
				}
			);
			assert_eq!(
				StakingMock::active_stake(&next_pool_stash).unwrap(),
				StakingMock::minimum_nominator_bond()
			);
			assert_eq!(
				RewardPools::<Runtime>::get(2).unwrap(),
				RewardPool { ..Default::default() }
			);

			// make sure ED is frozen on pool creation.
			assert_eq!(
				Currency::balance_frozen(
					&FreezeReason::PoolMinBalance.into(),
					&default_reward_account()
				),
				Currency::minimum_balance()
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Created { depositor: 11, pool_id: 2 },
					Event::Bonded { member: 11, pool_id: 2, bonded: 10, joined: true }
				]
			);
		});
	}

	#[test]
	fn create_errors_correctly() {
		ExtBuilder::default().with_check(0).build_and_execute(|| {
			assert_noop!(
				Pools::create(RuntimeOrigin::signed(10), 420, 123, 456, 789),
				Error::<Runtime>::AccountBelongsToOtherPool
			);

			// Given
			assert_eq!(MinCreateBond::<Runtime>::get(), 2);
			assert_eq!(StakingMock::minimum_nominator_bond(), 10);

			// Then
			assert_noop!(
				Pools::create(RuntimeOrigin::signed(11), 9, 123, 456, 789),
				Error::<Runtime>::MinimumBondNotMet
			);

			// Given
			MinCreateBond::<Runtime>::put(20);

			// Then
			assert_noop!(
				Pools::create(RuntimeOrigin::signed(11), 19, 123, 456, 789),
				Error::<Runtime>::MinimumBondNotMet
			);

			// Given
			BondedPool::<Runtime> {
				id: 2,
				inner: BondedPoolInner {
					commission: Commission::default(),
					member_counter: 1,
					points: 10,
					roles: DEFAULT_ROLES,
					state: PoolState::Open,
				},
			}
			.put();
			assert_eq!(MaxPools::<Runtime>::get(), Some(2));
			assert_eq!(BondedPools::<Runtime>::count(), 2);

			// Then
			assert_noop!(
				Pools::create(RuntimeOrigin::signed(11), 20, 123, 456, 789),
				Error::<Runtime>::MaxPools
			);

			// Given
			assert_eq!(PoolMembers::<Runtime>::count(), 1);
			MaxPools::<Runtime>::put(3);
			MaxPoolMembers::<Runtime>::put(1);
			Currency::set_balance(&11, 5 + 20);

			// Then
			let create = RuntimeCall::Pools(Call::<Runtime>::create {
				amount: 20,
				root: 11,
				nominator: 11,
				bouncer: 11,
			});
			assert_noop!(
				create.dispatch(RuntimeOrigin::signed(11)),
				Error::<Runtime>::MaxPoolMembers
			);
		});
	}

	#[test]
	fn create_with_pool_id_works() {
		ExtBuilder::default().build_and_execute(|| {
			let ed = Currency::minimum_balance();

			Currency::set_balance(&11, StakingMock::minimum_nominator_bond() + ed);
			assert_ok!(Pools::create(
				RuntimeOrigin::signed(11),
				StakingMock::minimum_nominator_bond(),
				123,
				456,
				789
			));

			assert_eq!(Currency::free_balance(&11), 0);
			// delete the initial pool created, then pool_Id `1` will be free

			assert_noop!(
				Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 1),
				Error::<Runtime>::PoolIdInUse
			);

			assert_noop!(
				Pools::create_with_pool_id(RuntimeOrigin::signed(12), 20, 234, 654, 783, 3),
				Error::<Runtime>::InvalidPoolId
			);

			// start dismantling the pool.
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
			assert_ok!(fully_unbond_permissioned(10));

			CurrentEra::set(3);
			assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 10));

			assert_ok!(Pools::create_with_pool_id(RuntimeOrigin::signed(10), 20, 234, 654, 783, 1));
		});
	}
}

#[test]
fn set_claim_permission_works() {
	ExtBuilder::default().build_and_execute(|| {
		// Given
		Currency::set_balance(&11, ExistentialDeposit::get() + 2);
		assert!(!PoolMembers::<Runtime>::contains_key(11));

		// When
		assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));

		// Then
		assert_eq!(
			pool_events_since_last_call(),
			vec![
				Event::Created { depositor: 10, pool_id: 1 },
				Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
				Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
			]
		);

		// Make permissioned
		assert_eq!(ClaimPermissions::<Runtime>::get(11), ClaimPermission::PermissionlessWithdraw);
		assert_noop!(
			Pools::set_claim_permission(RuntimeOrigin::signed(12), ClaimPermission::Permissioned),
			Error::<T>::PoolMemberNotFound
		);
		assert_ok!(Pools::set_claim_permission(
			RuntimeOrigin::signed(11),
			ClaimPermission::Permissioned
		));

		// then
		assert_eq!(ClaimPermissions::<Runtime>::get(11), ClaimPermission::Permissioned);
	});
}

mod nominate {
	use super::*;

	#[test]
	fn nominate_works() {
		ExtBuilder::default().build_and_execute(|| {
			// Depositor can't nominate
			assert_noop!(
				Pools::nominate(RuntimeOrigin::signed(10), 1, vec![21]),
				Error::<Runtime>::NotNominator
			);

			// bouncer can't nominate
			assert_noop!(
				Pools::nominate(RuntimeOrigin::signed(902), 1, vec![21]),
				Error::<Runtime>::NotNominator
			);

			// if `depositor` stake is less than the `MinimumNominatorBond`, they can't nominate
			StakingMinBond::set(20);

			// Can't nominate if depositor's stake is less than the `MinimumNominatorBond`
			assert_noop!(
				Pools::nominate(RuntimeOrigin::signed(900), 1, vec![21]),
				Error::<Runtime>::MinimumBondNotMet
			);

			// restore `MinimumNominatorBond`
			StakingMinBond::set(10);

			// Root can nominate
			assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![21]));
			assert_eq!(Nominations::get().unwrap(), vec![21]);

			// Nominator can nominate
			assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31]));
			assert_eq!(Nominations::get().unwrap(), vec![31]);

			// Can't nominate for a pool that doesn't exist
			assert_noop!(
				Pools::nominate(RuntimeOrigin::signed(902), 123, vec![21]),
				Error::<Runtime>::PoolNotFound
			);
		});
	}
}

mod set_state {
	use super::*;

	#[test]
	fn set_state_works() {
		ExtBuilder::default().build_and_execute(|| {
			// Given
			assert_ok!(BondedPool::<Runtime>::get(1).unwrap().ok_to_be_open());

			// Only the root and bouncer can change the state when the pool is ok to be open.
			assert_noop!(
				Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Blocked),
				Error::<Runtime>::CanNotChangeState
			);
			assert_noop!(
				Pools::set_state(RuntimeOrigin::signed(901), 1, PoolState::Blocked),
				Error::<Runtime>::CanNotChangeState
			);

			// Root can change state
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::StateChanged { pool_id: 1, new_state: PoolState::Blocked }
				]
			);

			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Blocked);

			// bouncer can change state
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Destroying));
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);

			// If the pool is destroying, then no one can set state
			assert_noop!(
				Pools::set_state(RuntimeOrigin::signed(900), 1, PoolState::Blocked),
				Error::<Runtime>::CanNotChangeState
			);
			assert_noop!(
				Pools::set_state(RuntimeOrigin::signed(902), 1, PoolState::Blocked),
				Error::<Runtime>::CanNotChangeState
			);

			// If the pool is not ok to be open, then anyone can set it to destroying

			// Given
			unsafe_set_state(1, PoolState::Open);
			// slash the pool to the point that `max_points_to_balance` ratio is
			// surpassed. Making this pool destroyable by anyone.
			StakingMock::slash_by(1, 10);

			// When
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
			// Then
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);

			// Given
			Currency::set_balance(&default_bonded_account(), Balance::MAX / 10);
			unsafe_set_state(1, PoolState::Open);
			// When
			assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
			// Then
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap().state, PoolState::Destroying);

			// If the pool is not ok to be open, it cannot be permissionlessly set to a state that
			// isn't destroying
			unsafe_set_state(1, PoolState::Open);
			assert_noop!(
				Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Blocked),
				Error::<Runtime>::CanNotChangeState
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
					Event::PoolSlashed { pool_id: 1, balance: 0 },
					Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
					Event::StateChanged { pool_id: 1, new_state: PoolState::Destroying }
				]
			);
		});
	}
}

mod set_metadata {
	use super::*;

	#[test]
	fn set_metadata_works() {
		ExtBuilder::default().build_and_execute(|| {
			// Root can set metadata
			assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1]));
			assert_eq!(Metadata::<Runtime>::get(1), vec![1, 1]);

			// bouncer can set metadata
			assert_ok!(Pools::set_metadata(RuntimeOrigin::signed(902), 1, vec![2, 2]));
			assert_eq!(Metadata::<Runtime>::get(1), vec![2, 2]);

			// Depositor can't set metadata
			assert_noop!(
				Pools::set_metadata(RuntimeOrigin::signed(10), 1, vec![3, 3]),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Nominator can't set metadata
			assert_noop!(
				Pools::set_metadata(RuntimeOrigin::signed(901), 1, vec![3, 3]),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Metadata cannot be longer than `MaxMetadataLen`
			assert_noop!(
				Pools::set_metadata(RuntimeOrigin::signed(900), 1, vec![1, 1, 1]),
				Error::<Runtime>::MetadataExceedsMaxLen
			);
		});
	}
}

mod set_configs {
	use super::*;

	#[test]
	fn set_configs_works() {
		ExtBuilder::default().build_and_execute(|| {
			// only admin origin can set configs
			assert_noop!(
				Pools::set_configs(
					RuntimeOrigin::signed(20),
					ConfigOp::Set(1 as Balance),
					ConfigOp::Set(2 as Balance),
					ConfigOp::Set(3u32),
					ConfigOp::Set(4u32),
					ConfigOp::Set(5u32),
					ConfigOp::Set(Perbill::from_percent(6))
				),
				BadOrigin
			);

			// Setting works by Admin (42)
			assert_ok!(Pools::set_configs(
				RuntimeOrigin::signed(42),
				ConfigOp::Set(1 as Balance),
				ConfigOp::Set(2 as Balance),
				ConfigOp::Set(3u32),
				ConfigOp::Set(4u32),
				ConfigOp::Set(5u32),
				ConfigOp::Set(Perbill::from_percent(6))
			));
			assert_eq!(MinJoinBond::<Runtime>::get(), 1);
			assert_eq!(MinCreateBond::<Runtime>::get(), 2);
			assert_eq!(MaxPools::<Runtime>::get(), Some(3));
			assert_eq!(MaxPoolMembers::<Runtime>::get(), Some(4));
			assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), Some(5));
			assert_eq!(GlobalMaxCommission::<Runtime>::get(), Some(Perbill::from_percent(6)));

			// Noop does nothing
			assert_storage_noop!(assert_ok!(Pools::set_configs(
				RuntimeOrigin::signed(42),
				ConfigOp::Noop,
				ConfigOp::Noop,
				ConfigOp::Noop,
				ConfigOp::Noop,
				ConfigOp::Noop,
				ConfigOp::Noop,
			)));

			// Removing works
			assert_ok!(Pools::set_configs(
				RuntimeOrigin::signed(42),
				ConfigOp::Remove,
				ConfigOp::Remove,
				ConfigOp::Remove,
				ConfigOp::Remove,
				ConfigOp::Remove,
				ConfigOp::Remove,
			));
			assert_eq!(MinJoinBond::<Runtime>::get(), 0);
			assert_eq!(MinCreateBond::<Runtime>::get(), 0);
			assert_eq!(MaxPools::<Runtime>::get(), None);
			assert_eq!(MaxPoolMembers::<Runtime>::get(), None);
			assert_eq!(MaxPoolMembersPerPool::<Runtime>::get(), None);
			assert_eq!(GlobalMaxCommission::<Runtime>::get(), None);
		});
	}
}

mod bond_extra {
	use super::*;
	use crate::Event;

	#[test]
	fn bond_extra_from_free_balance_creator() {
		ExtBuilder::default().build_and_execute(|| {
			// 10 is the owner and a member in pool 1, give them some more funds.
			Currency::set_balance(&10, 100);

			// given
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
			assert_eq!(Currency::free_balance(&10), 100);

			// when
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));

			// then
			assert_eq!(Currency::free_balance(&10), 90);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 20);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 20);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: false }
				]
			);

			// when
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(20)));

			// then
			assert_eq!(Currency::free_balance(&10), 70);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 40);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 40);

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Bonded { member: 10, pool_id: 1, bonded: 20, joined: false }]
			);
		})
	}

	#[test]
	fn bond_extra_from_rewards_creator() {
		ExtBuilder::default().build_and_execute(|| {
			// put some money in the reward account, all of which will belong to 10 as the only
			// member of the pool.
			Currency::set_balance(&default_reward_account(), 7);
			// ... if which only 2 is claimable to make sure the reward account does not die.
			let claimable_reward = 7 - ExistentialDeposit::get();

			// given
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10);
			assert_eq!(Currency::free_balance(&10), 35);

			// when
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));

			// then
			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + claimable_reward);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 10 + claimable_reward);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: claimable_reward },
					Event::Bonded {
						member: 10,
						pool_id: 1,
						bonded: claimable_reward,
						joined: false
					}
				]
			);
		})
	}

	#[test]
	fn bond_extra_from_rewards_joiner() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			// put some money in the reward account, all of which will belong to 10 as the only
			// member of the pool.
			Currency::set_balance(&default_reward_account(), 8);
			// ... if which only 3 is claimable to make sure the reward account does not die.
			let claimable_reward = 8 - ExistentialDeposit::get();
			// NOTE: easier to read of we use 3, so let's use the number instead of variable.
			assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");

			// given
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);

			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(Currency::free_balance(&20), 20);
			assert_eq!(TotalValueLocked::<T>::get(), 30);

			// when
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::Rewards));
			assert_eq!(Currency::free_balance(&default_reward_account()), 7);

			// then
			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(TotalValueLocked::<T>::get(), 31);

			// 10's share of the reward is 1/3, since they gave 10/30 of the total shares.
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);

			// when
			assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(20), BondExtra::Rewards));

			// then
			assert_eq!(Currency::free_balance(&20), 20);
			assert_eq!(TotalValueLocked::<T>::get(), 33);

			// 20's share of the rewards is the other 2/3 of the rewards, since they have 20/30 of
			// the shares
			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20 + 2);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 3);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 1, joined: false },
					Event::PaidOut { member: 20, pool_id: 1, payout: 2 },
					Event::Bonded { member: 20, pool_id: 1, bonded: 2, joined: false }
				]
			);
		})
	}

	#[test]
	fn bond_extra_other() {
		ExtBuilder::default().add_members(vec![(20, 20)]).build_and_execute(|| {
			Currency::set_balance(&default_reward_account(), 8);
			// ... of which only 3 are claimable to make sure the reward account does not die.
			let claimable_reward = 8 - ExistentialDeposit::get();
			// NOTE: easier to read if we use 3, so let's use the number instead of variable.
			assert_eq!(claimable_reward, 3, "test is correct if rewards are divisible by 3");

			// given
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10);
			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 20);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30);
			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(Currency::free_balance(&20), 20);

			// Permissioned by default
			assert_noop!(
				Pools::bond_extra_other(RuntimeOrigin::signed(80), 20, BondExtra::Rewards),
				Error::<Runtime>::DoesNotHavePermission
			);

			assert_ok!(Pools::set_claim_permission(
				RuntimeOrigin::signed(10),
				ClaimPermission::PermissionlessCompound
			));
			assert_ok!(Pools::bond_extra_other(RuntimeOrigin::signed(50), 10, BondExtra::Rewards));
			assert_eq!(Currency::free_balance(&default_reward_account()), 7);

			// then
			assert_eq!(Currency::free_balance(&10), 35);
			assert_eq!(PoolMembers::<Runtime>::get(10).unwrap().points, 10 + 1);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 30 + 1);

			// when
			assert_noop!(
				Pools::bond_extra_other(RuntimeOrigin::signed(40), 40, BondExtra::Rewards),
				Error::<Runtime>::PoolMemberNotFound
			);

			// when
			assert_ok!(Pools::bond_extra_other(
				RuntimeOrigin::signed(20),
				20,
				BondExtra::FreeBalance(10)
			));

			// then
			assert_eq!(Currency::free_balance(&20), 12);
			assert_eq!(Currency::free_balance(&default_reward_account()), 5);
			assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 30);
			assert_eq!(BondedPools::<Runtime>::get(1).unwrap().points, 41);
		})
	}
}

mod update_roles {
	use super::*;

	#[test]
	fn update_roles_works() {
		ExtBuilder::default().build_and_execute(|| {
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().roles,
				PoolRoles {
					depositor: 10,
					root: Some(900),
					nominator: Some(901),
					bouncer: Some(902)
				},
			);

			// non-existent pools
			assert_noop!(
				Pools::update_roles(
					RuntimeOrigin::signed(1),
					2,
					ConfigOp::Set(5),
					ConfigOp::Set(6),
					ConfigOp::Set(7)
				),
				Error::<Runtime>::PoolNotFound,
			);

			// depositor cannot change roles.
			assert_noop!(
				Pools::update_roles(
					RuntimeOrigin::signed(1),
					1,
					ConfigOp::Set(5),
					ConfigOp::Set(6),
					ConfigOp::Set(7)
				),
				Error::<Runtime>::DoesNotHavePermission,
			);

			// nominator cannot change roles.
			assert_noop!(
				Pools::update_roles(
					RuntimeOrigin::signed(901),
					1,
					ConfigOp::Set(5),
					ConfigOp::Set(6),
					ConfigOp::Set(7)
				),
				Error::<Runtime>::DoesNotHavePermission,
			);
			// bouncer
			assert_noop!(
				Pools::update_roles(
					RuntimeOrigin::signed(902),
					1,
					ConfigOp::Set(5),
					ConfigOp::Set(6),
					ConfigOp::Set(7)
				),
				Error::<Runtime>::DoesNotHavePermission,
			);

			// but root can
			assert_ok!(Pools::update_roles(
				RuntimeOrigin::signed(900),
				1,
				ConfigOp::Set(5),
				ConfigOp::Set(6),
				ConfigOp::Set(7)
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::RolesUpdated { root: Some(5), bouncer: Some(7), nominator: Some(6) }
				]
			);
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().roles,
				PoolRoles { depositor: 10, root: Some(5), nominator: Some(6), bouncer: Some(7) },
			);

			// also root origin can
			assert_ok!(Pools::update_roles(
				RuntimeOrigin::root(),
				1,
				ConfigOp::Set(1),
				ConfigOp::Set(2),
				ConfigOp::Set(3)
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::RolesUpdated { root: Some(1), bouncer: Some(3), nominator: Some(2) }]
			);
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().roles,
				PoolRoles { depositor: 10, root: Some(1), nominator: Some(2), bouncer: Some(3) },
			);

			// Noop works
			assert_ok!(Pools::update_roles(
				RuntimeOrigin::root(),
				1,
				ConfigOp::Set(11),
				ConfigOp::Noop,
				ConfigOp::Noop
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::RolesUpdated { root: Some(11), bouncer: Some(3), nominator: Some(2) }]
			);

			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().roles,
				PoolRoles { depositor: 10, root: Some(11), nominator: Some(2), bouncer: Some(3) },
			);

			// Remove works
			assert_ok!(Pools::update_roles(
				RuntimeOrigin::root(),
				1,
				ConfigOp::Set(69),
				ConfigOp::Remove,
				ConfigOp::Remove
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::RolesUpdated { root: Some(69), bouncer: None, nominator: None }]
			);

			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().roles,
				PoolRoles { depositor: 10, root: Some(69), nominator: None, bouncer: None },
			);
		})
	}
}

mod reward_counter_precision {
	use super::*;

	const DOT: Balance = 10u128.pow(10u32);
	const POLKADOT_TOTAL_ISSUANCE_GENESIS: Balance = DOT * 10u128.pow(9u32);

	const fn inflation(years: u128) -> u128 {
		let mut i = 0;
		let mut start = POLKADOT_TOTAL_ISSUANCE_GENESIS;
		while i < years {
			start = start + start / 10;
			i += 1
		}
		start
	}

	fn default_pool_reward_counter() -> FixedU128 {
		let bonded_pool = BondedPools::<T>::get(1).unwrap();
		RewardPools::<Runtime>::get(1)
			.unwrap()
			.current_reward_counter(1, bonded_pool.points, bonded_pool.commission.current())
			.unwrap()
			.0
	}

	fn pending_rewards(of: AccountId) -> Option<BalanceOf<T>> {
		let member = PoolMembers::<T>::get(of).unwrap();
		assert_eq!(member.pool_id, 1);
		let rc = default_pool_reward_counter();
		member.pending_rewards(rc).ok()
	}

	#[test]
	fn smallest_claimable_reward() {
		// create a pool that has all of the polkadot issuance in 50 years.
		let pool_bond = inflation(50);
		ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded {
						member: 10,
						pool_id: 1,
						bonded: 1173908528796953165005,
						joined: true,
					}
				]
			);

			// the smallest reward that this pool can handle is
			let expected_smallest_reward = inflation(50) / 10u128.pow(18);

			// tad bit less. cannot be paid out.
			deposit_rewards(expected_smallest_reward - 1);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_eq!(pool_events_since_last_call(), vec![]);
			// revert it.

			remove_rewards(expected_smallest_reward - 1);

			// tad bit more. can be claimed.
			deposit_rewards(expected_smallest_reward + 1);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 1173 }]
			);
		})
	}

	#[test]
	fn massive_reward_in_small_pool() {
		let tiny_bond = 1000 * DOT;
		ExtBuilder::default().ed(DOT).min_bond(tiny_bond).build_and_execute(|| {
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10000000000000, joined: true }
				]
			);

			Currency::set_balance(&20, tiny_bond);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), tiny_bond / 2, 1));

			// Suddenly, add a shit ton of rewards.
			deposit_rewards(inflation(1));

			// now claim.
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Bonded { member: 20, pool_id: 1, bonded: 5000000000000, joined: true },
					Event::PaidOut { member: 10, pool_id: 1, payout: 7333333333333333333 },
					Event::PaidOut { member: 20, pool_id: 1, payout: 3666666666666666666 }
				]
			);
		})
	}

	#[test]
	fn reward_counter_calc_wont_fail_in_normal_polkadot_future() {
		// create a pool that has roughly half of the polkadot issuance in 10 years.
		let pool_bond = inflation(10) / 2;
		ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded {
						member: 10,
						pool_id: 1,
						bonded: 12_968_712_300_500_000_000,
						joined: true,
					}
				]
			);

			// in 10 years, the total claimed rewards are large values as well. assuming that a pool
			// is earning all of the inflation per year (which is really unrealistic, but worse
			// case), that will be:
			let pool_total_earnings_10_years = inflation(10) - POLKADOT_TOTAL_ISSUANCE_GENESIS;
			deposit_rewards(pool_total_earnings_10_years);

			// some whale now joins with the other half ot the total issuance. This will bloat all
			// the calculation regarding current reward counter.
			Currency::set_balance(&20, pool_bond * 2);
			assert_ok!(Pools::join(RuntimeOrigin::signed(20), pool_bond, 1));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::Bonded {
					member: 20,
					pool_id: 1,
					bonded: 12_968_712_300_500_000_000,
					joined: true
				}]
			);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));

			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 15937424600999999996 }]
			);

			// now let a small member join with 10 DOTs.
			Currency::set_balance(&30, 20 * DOT);
			assert_ok!(Pools::join(RuntimeOrigin::signed(30), 10 * DOT, 1));

			// and give a reasonably small reward to the pool.
			deposit_rewards(DOT);

			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(30)));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Bonded { member: 30, pool_id: 1, bonded: 100000000000, joined: true },
					// quite small, but working fine.
					Event::PaidOut { member: 30, pool_id: 1, payout: 38 }
				]
			);
		})
	}

	#[test]
	fn reward_counter_update_can_fail_if_pool_is_highly_slashed() {
		// create a pool that has roughly half of the polkadot issuance in 10 years.
		let pool_bond = inflation(10) / 2;
		ExtBuilder::default().ed(DOT).min_bond(pool_bond).build_and_execute(|| {
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded {
						member: 10,
						pool_id: 1,
						bonded: 12_968_712_300_500_000_000,
						joined: true,
					}
				]
			);

			// slash this pool by 99% of that.
			StakingMock::slash_by(1, pool_bond * 99 / 100);

			// some whale now joins with the other half ot the total issuance. This will trigger an
			// overflow. This test is actually a bit too lenient because all the reward counters are
			// set to zero. In other tests that we want to assert a scenario won't fail, we should
			// also set the reward counters to some large value.
			Currency::set_balance(&20, pool_bond * 2);
			assert_err!(
				Pools::join(RuntimeOrigin::signed(20), pool_bond, 1),
				Error::<T>::OverflowRisk
			);
		})
	}

	#[test]
	fn if_small_member_waits_long_enough_they_will_earn_rewards() {
		// create a pool that has a quarter of the current polkadot issuance
		ExtBuilder::default()
			.ed(DOT)
			.min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4)
			.build_and_execute(|| {
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded {
							member: 10,
							pool_id: 1,
							bonded: 2500000000000000000,
							joined: true,
						}
					]
				);

				// and have a tiny fish join the pool as well..
				Currency::set_balance(&20, 20 * DOT);
				assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));

				// earn some small rewards
				deposit_rewards(DOT / 1000);

				// no point in claiming for 20 (nonetheless, it should be harmless)
				assert!(pending_rewards(20).unwrap().is_zero());
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Bonded {
							member: 20,
							pool_id: 1,
							bonded: 100000000000,
							joined: true
						},
						Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 }
					]
				);

				// earn some small more, still nothing can be claimed for 20, but 10 claims their
				// share.
				deposit_rewards(DOT / 1000);
				assert!(pending_rewards(20).unwrap().is_zero());
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				assert_eq!(
					pool_events_since_last_call(),
					vec![Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 }]
				);

				// earn some more rewards, this time 20 can also claim.
				deposit_rewards(DOT / 1000);
				assert_eq!(pending_rewards(20).unwrap(), 1);
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::PaidOut { member: 10, pool_id: 1, payout: 10000000 },
						Event::PaidOut { member: 20, pool_id: 1, payout: 1 }
					]
				);
			});
	}

	#[test]
	fn zero_reward_claim_does_not_update_reward_counter() {
		// create a pool that has a quarter of the current polkadot issuance
		ExtBuilder::default()
			.ed(DOT)
			.min_bond(POLKADOT_TOTAL_ISSUANCE_GENESIS / 4)
			.build_and_execute(|| {
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Created { depositor: 10, pool_id: 1 },
						Event::Bonded {
							member: 10,
							pool_id: 1,
							bonded: 2500000000000000000,
							joined: true,
						}
					]
				);

				// and have a tiny fish join the pool as well..
				Currency::set_balance(&20, 20 * DOT);
				assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10 * DOT, 1));

				// earn some small rewards
				deposit_rewards(DOT / 1000);

				// if 20 claims now, their reward counter should stay the same, so that they have a
				// chance of claiming this if they let it accumulate. Also see
				// `if_small_member_waits_long_enough_they_will_earn_rewards`
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(20)));
				assert_eq!(
					pool_events_since_last_call(),
					vec![
						Event::Bonded {
							member: 20,
							pool_id: 1,
							bonded: 100000000000,
							joined: true
						},
						Event::PaidOut { member: 10, pool_id: 1, payout: 9999997 }
					]
				);

				let current_reward_counter = default_pool_reward_counter();
				// has been updated, because they actually claimed something.
				assert_eq!(
					PoolMembers::<T>::get(10).unwrap().last_recorded_reward_counter,
					current_reward_counter
				);
				// has not be updated, even though the claim transaction went through okay.
				assert_eq!(
					PoolMembers::<T>::get(20).unwrap().last_recorded_reward_counter,
					Default::default()
				);
			});
	}
}

mod commission {
	use super::*;

	#[test]
	fn set_commission_works() {
		ExtBuilder::default().build_and_execute(|| {
			let pool_id = 1;
			let root = 900;

			// Commission can be set by the `root` role.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				pool_id,
				Some((Perbill::from_percent(50), root))
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id },
					Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id,
						current: Some((Perbill::from_percent(50), root))
					},
				]
			);

			// Commission can be updated only, while keeping the same payee.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				1,
				Some((Perbill::from_percent(25), root))
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id,
					current: Some((Perbill::from_percent(25), root))
				},]
			);

			// Payee can be updated only, while keeping the same commission.

			// Given:
			let payee = 901;

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				pool_id,
				Some((Perbill::from_percent(25), payee))
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id: 1,
					current: Some((Perbill::from_percent(25), payee))
				},]
			);

			// Pool earns 80 points and a payout is triggered.

			// Given:
			deposit_rewards(80);
			assert_eq!(
				PoolMembers::<Runtime>::get(10).unwrap(),
				PoolMember::<Runtime> { pool_id, points: 10, ..Default::default() }
			);

			// When:
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id, payout: 60 }]
			);
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 20);

			// Pending pool commission can be claimed by the root role.

			// When:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(root), pool_id));

			// Then:
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionClaimed { pool_id: 1, commission: 20 }]
			);

			// Commission can be removed from the pool completely.

			// When:
			assert_ok!(Pools::set_commission(RuntimeOrigin::signed(root), pool_id, None));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated { pool_id, current: None },]
			);

			// Given a pool now has a reward counter history, additional rewards and payouts can be
			// made while maintaining a correct ledger of the reward pool. Pool earns 100 points,
			// payout is triggered.
			//
			// Note that the `total_commission_pending` will not be updated until `update_records`
			// is next called, which is not done in this test segment..

			// Given:
			deposit_rewards(100);

			// When:
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id, payout: 100 },]
			);
			assert_eq!(
				RewardPools::<Runtime>::get(pool_id).unwrap(),
				RewardPool {
					last_recorded_reward_counter: FixedU128::from_float(6.0),
					last_recorded_total_payouts: 80,
					total_rewards_claimed: 160,
					total_commission_pending: 0,
					total_commission_claimed: 20
				}
			);

			// When set commission is called again, update_records is called and
			// `total_commission_pending` is updated, based on the current reward counter and pool
			// balance.
			//
			// Note that commission is now 0%, so it should not come into play with subsequent
			// payouts.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				1,
				Some((Perbill::from_percent(10), root))
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id,
					current: Some((Perbill::from_percent(10), root))
				},]
			);
			assert_eq!(
				RewardPools::<Runtime>::get(pool_id).unwrap(),
				RewardPool {
					last_recorded_reward_counter: FixedU128::from_float(16.0),
					last_recorded_total_payouts: 180,
					total_rewards_claimed: 160,
					total_commission_pending: 0,
					total_commission_claimed: 20
				}
			);

			// Supplying a 0% commission along with a payee results in a `None` current value.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				pool_id,
				Some((Perbill::from_percent(0), root))
			));

			// Then:
			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: None,
					max: None,
					change_rate: None,
					throttle_from: Some(1),
					claim_permission: None,
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id,
					current: Some((Perbill::from_percent(0), root))
				},]
			);

			// The payee can be updated even when commission has reached maximum commission. Both
			// commission and max commission are set to 10% to test this.

			// Given:
			assert_ok!(Pools::set_commission_max(
				RuntimeOrigin::signed(root),
				pool_id,
				Perbill::from_percent(10)
			));
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				pool_id,
				Some((Perbill::from_percent(10), root))
			));

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				pool_id,
				Some((Perbill::from_percent(10), payee))
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolMaxCommissionUpdated {
						pool_id,
						max_commission: Perbill::from_percent(10)
					},
					Event::PoolCommissionUpdated {
						pool_id,
						current: Some((Perbill::from_percent(10), root))
					},
					Event::PoolCommissionUpdated {
						pool_id,
						current: Some((Perbill::from_percent(10), payee))
					}
				]
			);
		});
	}

	#[test]
	fn commission_reward_counter_works_one_member() {
		ExtBuilder::default().build_and_execute(|| {
			let pool_id = 1;
			let root = 900;
			let member = 10;

			// Set the pool commission to 10% to test commission shares. Pool is topped up 40 points
			// and `member` immediately claims their pending rewards. Reward pool should still have
			// 10% share.

			// Given:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				1,
				Some((Perbill::from_percent(10), root)),
			));
			deposit_rewards(40);

			// When:
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 4);

			// Set pool commission to 20% and repeat the same process.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(root),
				1,
				Some((Perbill::from_percent(20), root)),
			));

			// Then:
			assert_eq!(
				RewardPools::<Runtime>::get(pool_id).unwrap(),
				RewardPool {
					last_recorded_reward_counter: FixedU128::from_float(3.6),
					last_recorded_total_payouts: 40,
					total_rewards_claimed: 36,
					total_commission_pending: 4,
					total_commission_claimed: 0
				}
			);

			// The current reward counter should yield the correct pending rewards of zero.

			// Given:
			let (current_reward_counter, _) = RewardPools::<Runtime>::get(pool_id)
				.unwrap()
				.current_reward_counter(
					pool_id,
					BondedPools::<Runtime>::get(pool_id).unwrap().points,
					Perbill::from_percent(20),
				)
				.unwrap();

			// Then:
			assert_eq!(
				PoolMembers::<Runtime>::get(member)
					.unwrap()
					.pending_rewards(current_reward_counter)
					.unwrap(),
				0
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(10), 900))
					},
					Event::PaidOut { member: 10, pool_id: 1, payout: 36 },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(20), 900))
					}
				]
			);
		})
	}

	#[test]
	fn set_commission_handles_errors() {
		ExtBuilder::default().build_and_execute(|| {
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
				]
			);

			// Provided pool does not exist.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					9999,
					Some((Perbill::from_percent(1), 900)),
				),
				Error::<Runtime>::PoolNotFound
			);

			// Sender does not have permission to set commission.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(1),
					1,
					Some((Perbill::from_percent(5), 900)),
				),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Commission increases will be throttled if outside of change_rate allowance.
			// Commission is set to 5%.
			// Change rate is set to 1% max increase, 2 block delay.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(5), 900)),
			));
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 2_u64 }
			));
			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: Some((Perbill::from_percent(5), 900)),
					max: None,
					change_rate: Some(CommissionChangeRate {
						max_increase: Perbill::from_percent(1),
						min_delay: 2_u64
					}),
					throttle_from: Some(1_u64),
					claim_permission: None,
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(5), 900))
					},
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(1),
							min_delay: 2
						}
					}
				]
			);

			// Now try to increase commission to 10% (5% increase). This should be throttled.
			// Then:
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(10), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);

			run_blocks(2);

			// Increase commission by 1% and provide an initial payee. This should succeed and set
			// the `throttle_from` field.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(6), 900))
			));
			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: Some((Perbill::from_percent(6), 900)),
					max: None,
					change_rate: Some(CommissionChangeRate {
						max_increase: Perbill::from_percent(1),
						min_delay: 2_u64
					}),
					throttle_from: Some(3_u64),
					claim_permission: None,
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id: 1,
					current: Some((Perbill::from_percent(6), 900))
				},]
			);

			// Attempt to increase the commission an additional 1% (now 7%). This will fail as
			// `throttle_from` is now the current block. At least 2 blocks need to pass before we
			// can set commission again.

			// Then:
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(7), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);

			run_blocks(2);

			// Can now successfully increase the commission again, to 7%.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(7), 900)),
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id: 1,
					current: Some((Perbill::from_percent(7), 900))
				},]
			);

			run_blocks(2);

			// Now surpassed the `min_delay` threshold, but the `max_increase` threshold is
			// still at play. An attempted commission change now to 8% (+2% increase) should fail.

			// Then:
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(9), 900)),
				),
				Error::<Runtime>::CommissionChangeThrottled
			);

			// Now set a max commission to the current 5%. This will also update the current
			// commission to 5%.

			// When:
			assert_ok!(Pools::set_commission_max(
				RuntimeOrigin::signed(900),
				1,
				Perbill::from_percent(5)
			));
			assert_eq!(
				BondedPool::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: Some((Perbill::from_percent(5), 900)),
					max: Some(Perbill::from_percent(5)),
					change_rate: Some(CommissionChangeRate {
						max_increase: Perbill::from_percent(1),
						min_delay: 2
					}),
					throttle_from: Some(7),
					claim_permission: None,
				}
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(5), 900))
					},
					Event::PoolMaxCommissionUpdated {
						pool_id: 1,
						max_commission: Perbill::from_percent(5)
					}
				]
			);

			// Run 2 blocks into the future so we are eligible to update commission again.
			run_blocks(2);

			// Now attempt again to increase the commission by 1%, to 6%. This is within the change
			// rate allowance, but `max_commission` will now prevent us from going any higher.

			// Then:
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(6), 900)),
				),
				Error::<Runtime>::CommissionExceedsMaximum
			);
		});
	}

	#[test]
	fn set_commission_max_works_with_error_tests() {
		ExtBuilder::default().build_and_execute(|| {
			// Provided pool does not exist
			assert_noop!(
				Pools::set_commission_max(
					RuntimeOrigin::signed(900),
					9999,
					Perbill::from_percent(1)
				),
				Error::<Runtime>::PoolNotFound
			);
			// Sender does not have permission to set commission
			assert_noop!(
				Pools::set_commission_max(RuntimeOrigin::signed(1), 1, Perbill::from_percent(5)),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Cannot set max commission above GlobalMaxCommission
			assert_noop!(
				Pools::set_commission_max(
					RuntimeOrigin::signed(900),
					1,
					Perbill::from_percent(100)
				),
				Error::<Runtime>::CommissionExceedsGlobalMaximum
			);

			// Set a max commission commission pool 1 to 80%
			assert_ok!(Pools::set_commission_max(
				RuntimeOrigin::signed(900),
				1,
				Perbill::from_percent(80)
			));
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission.max,
				Some(Perbill::from_percent(80))
			);

			// We attempt to increase the max commission to 90%, but increasing is
			// disallowed due to pool's max commission.
			assert_noop!(
				Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Perbill::from_percent(90)),
				Error::<Runtime>::MaxCommissionRestricted
			);

			// We will now set a commission to 75% and then amend the max commission
			// to 50%. The max commission change should decrease the current
			// commission to 50%.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(75), 900))
			));
			assert_ok!(Pools::set_commission_max(
				RuntimeOrigin::signed(900),
				1,
				Perbill::from_percent(50)
			));
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: Some((Perbill::from_percent(50), 900)),
					max: Some(Perbill::from_percent(50)),
					change_rate: None,
					throttle_from: Some(1),
					claim_permission: None,
				}
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolMaxCommissionUpdated {
						pool_id: 1,
						max_commission: Perbill::from_percent(80)
					},
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(75), 900))
					},
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(50), 900))
					},
					Event::PoolMaxCommissionUpdated {
						pool_id: 1,
						max_commission: Perbill::from_percent(50)
					}
				]
			);
		});
	}

	#[test]
	fn set_commission_change_rate_works_with_errors() {
		ExtBuilder::default().build_and_execute(|| {
			// Provided pool does not exist
			assert_noop!(
				Pools::set_commission_change_rate(
					RuntimeOrigin::signed(900),
					9999,
					CommissionChangeRate {
						max_increase: Perbill::from_percent(5),
						min_delay: 1000_u64
					}
				),
				Error::<Runtime>::PoolNotFound
			);
			// Sender does not have permission to set commission
			assert_noop!(
				Pools::set_commission_change_rate(
					RuntimeOrigin::signed(1),
					1,
					CommissionChangeRate {
						max_increase: Perbill::from_percent(5),
						min_delay: 1000_u64
					}
				),
				Error::<Runtime>::DoesNotHavePermission
			);

			// Set a commission change rate for pool 1
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 10_u64 }
			));
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission.change_rate,
				Some(CommissionChangeRate {
					max_increase: Perbill::from_percent(5),
					min_delay: 10_u64
				})
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(5),
							min_delay: 10
						}
					},
				]
			);

			// We now try to half the min_delay - this will be disallowed. A greater delay between
			// commission changes is seen as more restrictive.
			assert_noop!(
				Pools::set_commission_change_rate(
					RuntimeOrigin::signed(900),
					1,
					CommissionChangeRate {
						max_increase: Perbill::from_percent(5),
						min_delay: 5_u64
					}
				),
				Error::<Runtime>::CommissionChangeRateNotAllowed
			);

			// We now try to increase the allowed max_increase - this will fail. A smaller allowed
			// commission change is seen as more restrictive.
			assert_noop!(
				Pools::set_commission_change_rate(
					RuntimeOrigin::signed(900),
					1,
					CommissionChangeRate {
						max_increase: Perbill::from_percent(10),
						min_delay: 10_u64
					}
				),
				Error::<Runtime>::CommissionChangeRateNotAllowed
			);

			// Successful more restrictive change of min_delay with the current max_increase
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(5), min_delay: 20_u64 }
			));

			// Successful more restrictive change of max_increase with the current min_delay
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(4), min_delay: 20_u64 }
			));

			// Successful more restrictive change of both max_increase and min_delay
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(3), min_delay: 30_u64 }
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(5),
							min_delay: 20
						}
					},
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(4),
							min_delay: 20
						}
					},
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(3),
							min_delay: 30
						}
					}
				]
			);
		});
	}

	#[test]
	fn change_rate_does_not_apply_to_decreasing_commission() {
		ExtBuilder::default().build_and_execute(|| {
			// set initial commission of the pool to 10%.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(10), 900))
			));

			// Set a commission change rate for pool 1, 1% every 10 blocks
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 10_u64 }
			));
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission.change_rate,
				Some(CommissionChangeRate {
					max_increase: Perbill::from_percent(1),
					min_delay: 10_u64
				})
			);

			// run `min_delay` blocks to allow a commission update.
			run_blocks(10_u64);

			// Test `max_increase`: attempt to decrease the commission by 5%. Should succeed.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(5), 900))
			));

			// Test `min_delay`: *immediately* attempt to decrease the commission by 2%. Should
			// succeed.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(3), 900))
			));

			// Attempt to *increase* the commission by 5%. Should fail.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(8), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);

			// Sanity check: the resulting pool Commission state.
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: Some((Perbill::from_percent(3), 900)),
					max: None,
					change_rate: Some(CommissionChangeRate {
						max_increase: Perbill::from_percent(1),
						min_delay: 10_u64
					}),
					throttle_from: Some(11),
					claim_permission: None,
				}
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(10), 900))
					},
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(1),
							min_delay: 10
						}
					},
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(5), 900))
					},
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(3), 900))
					}
				]
			);
		});
	}

	#[test]
	fn set_commission_max_to_zero_works() {
		ExtBuilder::default().build_and_execute(|| {
			// 0% max commission test.
			// set commission max 0%.
			assert_ok!(Pools::set_commission_max(RuntimeOrigin::signed(900), 1, Zero::zero()));

			// a max commission of 0% essentially freezes the current commission, even when None.
			// All commission update attempts will fail.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(1), 900))
				),
				Error::<Runtime>::CommissionExceedsMaximum
			);
		})
	}

	#[test]
	fn set_commission_change_rate_zero_max_increase_works() {
		ExtBuilder::default().build_and_execute(|| {
			// set commission change rate to 0% per 10 blocks
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 10_u64 }
			));

			// even though there is a min delay of 10 blocks, a max increase of 0% essentially
			// freezes the commission. All commission update attempts will fail.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(1), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);
		})
	}

	#[test]
	fn set_commission_change_rate_zero_min_delay_works() {
		ExtBuilder::default().build_and_execute(|| {
			// set commission change rate to 1% with a 0 block `min_delay`.
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(1), min_delay: 0_u64 }
			));
			assert_eq!(
				BondedPools::<Runtime>::get(1).unwrap().commission,
				Commission {
					current: None,
					max: None,
					change_rate: Some(CommissionChangeRate {
						max_increase: Perbill::from_percent(1),
						min_delay: 0
					}),
					throttle_from: Some(1),
					claim_permission: None,
				}
			);

			// since there is no min delay, we should be able to immediately set the commission.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				1,
				Some((Perbill::from_percent(1), 900))
			));

			// sanity check: increasing again to more than +1% will fail.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(3), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);
		})
	}

	#[test]
	fn set_commission_change_rate_zero_value_works() {
		ExtBuilder::default().build_and_execute(|| {
			// Check zero values play nice. 0 `min_delay` and 0% max_increase test.
			// set commission change rate to 0% per 0 blocks.
			assert_ok!(Pools::set_commission_change_rate(
				RuntimeOrigin::signed(900),
				1,
				CommissionChangeRate { max_increase: Perbill::from_percent(0), min_delay: 0_u64 }
			));

			// even though there is no min delay, a max increase of 0% essentially freezes the
			// commission. All commission update attempts will fail.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					1,
					Some((Perbill::from_percent(1), 900))
				),
				Error::<Runtime>::CommissionChangeThrottled
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionChangeRateUpdated {
						pool_id: 1,
						change_rate: CommissionChangeRate {
							max_increase: Perbill::from_percent(0),
							min_delay: 0_u64
						}
					}
				]
			);
		})
	}

	#[test]
	fn do_reward_payout_with_various_commissions() {
		ExtBuilder::default().build_and_execute(|| {
			// turn off GlobalMaxCommission for this test.
			GlobalMaxCommission::<Runtime>::set(None);
			let pool_id = 1;

			// top up commission payee account to existential deposit
			let _ = Currency::set_balance(&2, 5);

			// Set a commission pool 1 to 33%, with a payee set to `2`
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(33), 2)),
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(33), 2))
					},
				]
			);

			// The pool earns 10 points
			deposit_rewards(10);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 7 },]
			);

			// The pool earns 17 points
			deposit_rewards(17);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 11 },]
			);

			// The pool earns 50 points
			deposit_rewards(50);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 34 },]
			);

			// The pool earns 10439 points
			deposit_rewards(10439);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PaidOut { member: 10, pool_id: 1, payout: 6994 },]
			);

			// Set the commission to 100% and ensure the following payout to the pool member will
			// not happen.

			// When:
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(100), 2)),
			));

			// Given:
			deposit_rewards(200);
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id: 1,
					current: Some((Perbill::from_percent(100), 2))
				},]
			);
		})
	}

	#[test]
	fn commission_accumulates_on_multiple_rewards() {
		ExtBuilder::default().build_and_execute(|| {
			let pool_id = 1;

			// Given:

			// Set initial commission of pool 1 to 10%.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(10), 2)),
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(10), 2))
					},
				]
			);

			// When:

			// The pool earns 100 points
			deposit_rewards(100);

			// Change commission to 20%
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(20), 2)),
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![Event::PoolCommissionUpdated {
					pool_id: 1,
					current: Some((Perbill::from_percent(20), 2))
				},]
			);

			// The pool earns 100 points
			deposit_rewards(100);

			// Then:

			// Claim payout:
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Claim commission:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 90 + 80 },
					Event::PoolCommissionClaimed { pool_id: 1, commission: 30 }
				]
			);
		})
	}

	#[test]
	fn last_recorded_total_payouts_needs_commission() {
		ExtBuilder::default().build_and_execute(|| {
			let pool_id = 1;

			// Given:

			// Set initial commission of pool 1 to 10%.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(10), 2)),
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(10), 2))
					},
				]
			);

			// When:

			// The pool earns 100 points
			deposit_rewards(100);

			// Claim payout:
			assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(10)));

			// Claim commission:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));

			// Then:

			assert_eq!(
				RewardPools::<Runtime>::get(1).unwrap().last_recorded_total_payouts,
				90 + 10
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id: 1, payout: 90 },
					Event::PoolCommissionClaimed { pool_id: 1, commission: 10 }
				]
			);
		})
	}

	#[test]
	fn do_reward_payout_with_100_percent_commission() {
		ExtBuilder::default().build_and_execute(|| {
			// turn off GlobalMaxCommission for this test.
			GlobalMaxCommission::<Runtime>::set(None);

			let (mut member, bonded_pool, mut reward_pool) =
				Pools::get_member_with_pools(&10).unwrap();

			// top up commission payee account to existential deposit
			let _ = Currency::set_balance(&2, 5);

			// Set a commission pool 1 to 100%, with a payee set to `2`
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				bonded_pool.id,
				Some((Perbill::from_percent(100), 2)),
			));

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(100), 2))
					}
				]
			);

			// The pool earns 10 points
			deposit_rewards(10);

			// execute the payout
			assert_ok!(Pools::do_reward_payout(
				&10,
				&mut member,
				&mut BondedPool::<Runtime>::get(1).unwrap(),
				&mut reward_pool
			));
		})
	}

	#[test]
	fn global_max_caps_max_commission_payout() {
		ExtBuilder::default().build_and_execute(|| {
			// Note: GlobalMaxCommission is set at 90%.

			let (mut member, bonded_pool, mut reward_pool) =
				Pools::get_member_with_pools(&10).unwrap();

			// top up the commission payee account to existential deposit
			let _ = Currency::set_balance(&2, 5);

			// Set a commission pool 1 to 100% fails.
			assert_noop!(
				Pools::set_commission(
					RuntimeOrigin::signed(900),
					bonded_pool.id,
					Some((Perbill::from_percent(100), 2)),
				),
				Error::<Runtime>::CommissionExceedsGlobalMaximum
			);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
				]
			);

			// Set pool commission to 90% and then set global max commission to 80%.
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				bonded_pool.id,
				Some((Perbill::from_percent(90), 2)),
			));
			GlobalMaxCommission::<Runtime>::set(Some(Perbill::from_percent(80)));

			// The pool earns 10 points
			deposit_rewards(10);

			// execute the payout
			assert_ok!(Pools::do_reward_payout(
				&10,
				&mut member,
				&mut BondedPool::<Runtime>::get(1).unwrap(),
				&mut reward_pool
			));

			// Confirm the commission was only 8 points out of 10 points, and the payout was 2 out
			// of 10 points, reflecting the 80% global max commission.
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolCommissionUpdated {
						pool_id: 1,
						current: Some((Perbill::from_percent(90), 2))
					},
					Event::PaidOut { member: 10, pool_id: 1, payout: 2 },
				]
			);
		})
	}

	#[test]
	fn claim_commission_works() {
		ExtBuilder::default().build_and_execute(|| {
			/// Deposit rewards into the pool and claim payout. This will set up pending commission
			/// to be tested in various scenarios.
			fn deposit_rewards_and_claim_payout(caller: AccountId, points: u128) {
				deposit_rewards(points);
				assert_ok!(Pools::claim_payout(RuntimeOrigin::signed(caller)));
			}

			let pool_id = 1;

			let _ = Currency::set_balance(&900, 5);
			assert_ok!(Pools::set_commission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some((Perbill::from_percent(50), 900))
			));
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id },
					Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
					Event::PoolCommissionUpdated {
						pool_id,
						current: Some((Perbill::from_percent(50), 900))
					},
				]
			);

			// Given:
			deposit_rewards_and_claim_payout(10, 100);
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 50);

			// Pool does not exist
			assert_noop!(
				Pools::claim_commission(RuntimeOrigin::signed(900), 9999,),
				Error::<Runtime>::PoolNotFound
			);

			// Does not have permission.
			assert_noop!(
				Pools::claim_commission(RuntimeOrigin::signed(10), pool_id,),
				Error::<Runtime>::DoesNotHavePermission
			);

			// When:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));

			// Then:
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);

			// No more pending commission.
			assert_noop!(
				Pools::claim_commission(RuntimeOrigin::signed(900), pool_id,),
				Error::<Runtime>::NoPendingCommission
			);

			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id, payout: 50 },
					Event::PoolCommissionClaimed { pool_id: 1, commission: 50 }
				]
			);

			// The pool commission's claim_permission field is updated to `Permissionless` by the
			// root member, which means anyone can now claim commission for the pool.

			// Given:
			// Some random non-pool member to claim commission.
			let non_pool_member = 1001;
			let _ = Currency::set_balance(&non_pool_member, 5);

			// Set up pending commission.
			deposit_rewards_and_claim_payout(10, 100);
			assert_ok!(Pools::set_commission_claim_permission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some(CommissionClaimPermission::Permissionless)
			));

			// When:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(non_pool_member), pool_id));

			// Then:
			assert_eq!(RewardPool::<Runtime>::current_balance(pool_id), 0);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id, payout: 50 },
					Event::PoolCommissionClaimPermissionUpdated {
						pool_id: 1,
						permission: Some(CommissionClaimPermission::Permissionless)
					},
					Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
				]
			);

			// The pool commission's claim_permission is updated to an adhoc account by the root
			// member, which means now only that account (in addition to the root role) can claim
			// commission for the pool.

			// Given:
			// The account designated to claim commission.
			let designated_commission_claimer = 2001;
			let _ = Currency::set_balance(&designated_commission_claimer, 5);

			// Set up pending commission.
			deposit_rewards_and_claim_payout(10, 100);
			assert_ok!(Pools::set_commission_claim_permission(
				RuntimeOrigin::signed(900),
				pool_id,
				Some(CommissionClaimPermission::Account(designated_commission_claimer))
			));

			// When:
			// Previous claimer can no longer claim commission.
			assert_noop!(
				Pools::claim_commission(RuntimeOrigin::signed(1001), pool_id,),
				Error::<Runtime>::DoesNotHavePermission
			);
			// Designated claimer can claim commission.
			assert_ok!(Pools::claim_commission(
				RuntimeOrigin::signed(designated_commission_claimer),
				pool_id
			));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id, payout: 50 },
					Event::PoolCommissionClaimPermissionUpdated {
						pool_id: 1,
						permission: Some(CommissionClaimPermission::Account(2001))
					},
					Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
				]
			);

			// Even with an Account claim permission set, the `root` role of the pool can still
			// claim commission.

			// Given:
			deposit_rewards_and_claim_payout(10, 100);

			// When:
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id, payout: 50 },
					Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
				]
			);

			// The root role updates commission's claim_permission back to `None`, which results in
			// only the root member being able to claim commission for the pool.

			// Given:
			deposit_rewards_and_claim_payout(10, 100);

			// When:
			assert_ok!(Pools::set_commission_claim_permission(
				RuntimeOrigin::signed(900),
				pool_id,
				None
			));
			// Previous claimer can no longer claim commission.
			assert_noop!(
				Pools::claim_commission(
					RuntimeOrigin::signed(designated_commission_claimer),
					pool_id,
				),
				Error::<Runtime>::DoesNotHavePermission
			);
			// Root can claim commission.
			assert_ok!(Pools::claim_commission(RuntimeOrigin::signed(900), pool_id));

			// Then:
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PaidOut { member: 10, pool_id, payout: 50 },
					Event::PoolCommissionClaimPermissionUpdated { pool_id: 1, permission: None },
					Event::PoolCommissionClaimed { pool_id: 1, commission: 50 },
				]
			);
		})
	}

	#[test]
	fn set_commission_claim_permission_handles_errors() {
		ExtBuilder::default().build_and_execute(|| {
			let pool_id = 1;

			let _ = Currency::set_balance(&900, 5);
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id },
					Event::Bonded { member: 10, pool_id, bonded: 10, joined: true },
				]
			);

			// Cannot operate on a non-existing pool.
			assert_noop!(
				Pools::set_commission_claim_permission(
					RuntimeOrigin::signed(10),
					90,
					Some(CommissionClaimPermission::Permissionless)
				),
				Error::<Runtime>::PoolNotFound
			);

			// Only the root role can change the commission claim permission.
			assert_noop!(
				Pools::set_commission_claim_permission(
					RuntimeOrigin::signed(10),
					pool_id,
					Some(CommissionClaimPermission::Permissionless)
				),
				Error::<Runtime>::DoesNotHavePermission
			);
		})
	}
}
mod slash {
	use super::*;

	#[test]
	fn slash_no_subpool_is_tracked() {
		let bonded = |points, member_counter| BondedPool::<Runtime> {
			id: 1,
			inner: BondedPoolInner {
				commission: Commission::default(),
				member_counter,
				points,
				roles: DEFAULT_ROLES,
				state: PoolState::Open,
			},
		};
		ExtBuilder::default().with_check(0).build_and_execute(|| {
			// Given
			Currency::set_balance(&11, ExistentialDeposit::get() + 2);
			assert!(!PoolMembers::<Runtime>::contains_key(11));
			assert_eq!(TotalValueLocked::<T>::get(), 10);

			// When
			assert_ok!(Pools::join(RuntimeOrigin::signed(11), 2, 1));

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::Created { depositor: 10, pool_id: 1 },
					Event::Bonded { member: 10, pool_id: 1, bonded: 10, joined: true },
					Event::Bonded { member: 11, pool_id: 1, bonded: 2, joined: true },
				]
			);
			assert_eq!(TotalValueLocked::<T>::get(), 12);

			assert_eq!(
				PoolMembers::<Runtime>::get(11).unwrap(),
				PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
			);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12, 2));

			// Given
			// The bonded balance is slashed in half
			StakingMock::slash_by(1, 6);

			// And
			Currency::set_balance(&12, ExistentialDeposit::get() + 12);
			assert!(!PoolMembers::<Runtime>::contains_key(12));

			// When
			assert_ok!(Pools::join(RuntimeOrigin::signed(12), 12, 1));

			// Then
			assert_eq!(
				pool_events_since_last_call(),
				vec![
					Event::PoolSlashed { pool_id: 1, balance: 6 },
					Event::Bonded { member: 12, pool_id: 1, bonded: 12, joined: true }
				]
			);
			assert_eq!(TotalValueLocked::<T>::get(), 18);

			assert_eq!(
				PoolMembers::<Runtime>::get(12).unwrap(),
				PoolMember::<Runtime> { pool_id: 1, points: 24, ..Default::default() }
			);
			assert_eq!(BondedPool::<Runtime>::get(1).unwrap(), bonded(12 + 24, 3));
		});
	}
}

mod chill {
	use super::*;

	#[test]
	fn chill_works() {
		ExtBuilder::default().build_and_execute(|| {
			// only nominator or root can chill
			assert_noop!(
				Pools::chill(RuntimeOrigin::signed(10), 1),
				Error::<Runtime>::NotNominator
			);

			// root can chill and re-nominate
			assert_ok!(Pools::chill(RuntimeOrigin::signed(900), 1));
			assert_ok!(Pools::nominate(RuntimeOrigin::signed(900), 1, vec![31]));

			// nominator can chill and re-nominate
			assert_ok!(Pools::chill(RuntimeOrigin::signed(901), 1));
			assert_ok!(Pools::nominate(RuntimeOrigin::signed(901), 1, vec![31]));

			// if `depositor` stake is less than the `MinimumNominatorBond`, then this call
			// becomes permissionless;
			StakingMinBond::set(20);

			// any account can chill
			assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
		})
	}
}
