diff --git a/Cargo.lock b/Cargo.lock
index f8126787531425ed9baf5839d1d5a31a0b294cb8..e3a72ca23d8833957d90d4d9a9fa1347886e409b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10171,6 +10171,7 @@ dependencies = [
  "frame-support",
  "frame-system",
  "pallet-balances",
+ "pallet-nomination-pools",
  "pallet-staking",
  "pallet-staking-reward-curve",
  "pallet-timestamp",
@@ -10844,6 +10845,7 @@ dependencies = [
  "frame-system",
  "pallet-bags-list",
  "pallet-balances",
+ "pallet-delegated-staking",
  "pallet-nomination-pools",
  "pallet-staking",
  "pallet-staking-reward-curve",
@@ -10884,7 +10886,32 @@ dependencies = [
 ]
 
 [[package]]
-name = "pallet-nomination-pools-test-staking"
+name = "pallet-nomination-pools-test-delegate-stake"
+version = "1.0.0"
+dependencies = [
+ "frame-election-provider-support",
+ "frame-support",
+ "frame-system",
+ "log",
+ "pallet-bags-list",
+ "pallet-balances",
+ "pallet-delegated-staking",
+ "pallet-nomination-pools",
+ "pallet-staking",
+ "pallet-staking-reward-curve",
+ "pallet-timestamp",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-staking",
+ "sp-std 14.0.0",
+ "sp-tracing 16.0.0",
+]
+
+[[package]]
+name = "pallet-nomination-pools-test-transfer-stake"
 version = "1.0.0"
 dependencies = [
  "frame-election-provider-support",
@@ -22848,6 +22875,7 @@ dependencies = [
  "pallet-beefy-mmr",
  "pallet-collective",
  "pallet-conviction-voting",
+ "pallet-delegated-staking",
  "pallet-democracy",
  "pallet-election-provider-multi-phase",
  "pallet-election-provider-support-benchmarking",
diff --git a/Cargo.toml b/Cargo.toml
index 239e1f5de996fe11119e89a191fb9e646476f5d2..54fa44d6654d6d2cfe88e5a8af1f0c2c85f36fc7 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -374,7 +374,8 @@ members = [
 	"substrate/frame/nomination-pools/benchmarking",
 	"substrate/frame/nomination-pools/fuzzer",
 	"substrate/frame/nomination-pools/runtime-api",
-	"substrate/frame/nomination-pools/test-staking",
+	"substrate/frame/nomination-pools/test-delegate-stake",
+	"substrate/frame/nomination-pools/test-transfer-stake",
 	"substrate/frame/offences",
 	"substrate/frame/offences/benchmarking",
 	"substrate/frame/paged-list",
diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml
index 01e9dd1527f22882a144254935ee9217940bc01b..56623272be82e2fd355550b63777477f267ac7c6 100644
--- a/polkadot/runtime/westend/Cargo.toml
+++ b/polkadot/runtime/westend/Cargo.toml
@@ -83,6 +83,7 @@ pallet-society = { path = "../../../substrate/frame/society", default-features =
 pallet-staking = { path = "../../../substrate/frame/staking", default-features = false }
 pallet-staking-reward-curve = { package = "pallet-staking-reward-curve", path = "../../../substrate/frame/staking/reward-curve" }
 pallet-staking-runtime-api = { path = "../../../substrate/frame/staking/runtime-api", default-features = false }
+pallet-delegated-staking = { path = "../../../substrate/frame/delegated-staking", default-features = false }
 pallet-state-trie-migration = { path = "../../../substrate/frame/state-trie-migration", default-features = false }
 pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false }
 pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-features = false }
@@ -161,6 +162,7 @@ std = [
 	"pallet-beefy/std",
 	"pallet-collective/std",
 	"pallet-conviction-voting/std",
+	"pallet-delegated-staking/std",
 	"pallet-democracy/std",
 	"pallet-election-provider-multi-phase/std",
 	"pallet-election-provider-support-benchmarking?/std",
@@ -244,6 +246,7 @@ runtime-benchmarks = [
 	"pallet-balances/runtime-benchmarks",
 	"pallet-collective/runtime-benchmarks",
 	"pallet-conviction-voting/runtime-benchmarks",
+	"pallet-delegated-staking/runtime-benchmarks",
 	"pallet-democracy/runtime-benchmarks",
 	"pallet-election-provider-multi-phase/runtime-benchmarks",
 	"pallet-election-provider-support-benchmarking/runtime-benchmarks",
@@ -304,6 +307,7 @@ try-runtime = [
 	"pallet-beefy/try-runtime",
 	"pallet-collective/try-runtime",
 	"pallet-conviction-voting/try-runtime",
+	"pallet-delegated-staking/try-runtime",
 	"pallet-democracy/try-runtime",
 	"pallet-election-provider-multi-phase/try-runtime",
 	"pallet-elections-phragmen/try-runtime",
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index cfe0dde0da13d086d92cd578bf4eceb9d7a6cd3c..4bf132d82c9634609cc4f9c4c3a2790ca4bf36f7 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -647,7 +647,7 @@ impl pallet_staking::Config for Runtime {
 	type HistoryDepth = frame_support::traits::ConstU32<84>;
 	type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
 	type BenchmarkingConfig = runtime_common::StakingBenchmarkingConfig;
-	type EventListeners = NominationPools;
+	type EventListeners = (NominationPools, DelegatedStaking);
 	type WeightInfo = weights::pallet_staking::WeightInfo<Runtime>;
 	type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
 }
@@ -1360,7 +1360,8 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = Staking;
+	type StakeAdapter =
+		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
 	type PostUnbondingPoolsWindow = ConstU32<4>;
 	type MaxMetadataLen = ConstU32<256>;
 	// we use the same number of allowed unlocking chunks as with staking.
@@ -1370,6 +1371,21 @@ impl pallet_nomination_pools::Config for Runtime {
 	type AdminOrigin = EitherOf<EnsureRoot<AccountId>, StakingAdmin>;
 }
 
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
+}
+
+impl pallet_delegated_staking::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = DelegatedStakingPalletId;
+	type Currency = Balances;
+	type OnSlash = ();
+	type SlashRewardFraction = SlashRewardFraction;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type CoreStaking = Staking;
+}
+
 impl pallet_root_testing::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 }
@@ -1518,6 +1534,10 @@ mod runtime {
 	#[runtime::pallet_index(37)]
 	pub type Treasury = pallet_treasury;
 
+	// Staking extension for delegation
+	#[runtime::pallet_index(38)]
+	pub type DelegatedStaking = pallet_delegated_staking;
+
 	// Parachains pallets. Start indices at 40 to leave room.
 	#[runtime::pallet_index(41)]
 	pub type ParachainsOrigin = parachains_origin;
@@ -1621,11 +1641,12 @@ pub type SignedExtra = (
 	frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
 );
 
-pub struct NominationPoolsMigrationV4OldPallet;
-impl Get<Perbill> for NominationPoolsMigrationV4OldPallet {
-	fn get() -> Perbill {
-		Perbill::from_percent(100)
-	}
+parameter_types! {
+	// This is the max pools that will be migrated in the runtime upgrade. Westend has more pools
+	// than this, but we want to emulate some non migrated pools. In prod runtimes, if weight is not
+	// a concern, it is recommended to set to (existing pools + 10) to also account for any new
+	// pools getting created before the migration is actually executed.
+	pub const MaxPoolsToMigrate: u32 = 250;
 }
 
 /// All migrations that will run on the next runtime upgrade.
@@ -1658,7 +1679,15 @@ pub mod migrations {
 	}
 
 	/// Unreleased migrations. Add new ones here:
-	pub type Unreleased = (pallet_staking::migrations::v15::MigrateV14ToV15<Runtime>,);
+	pub type Unreleased = (
+		// Migrate NominationPools to `DelegateStake` adapter. This is unversioned upgrade and
+		// should not be applied yet in Kusama/Polkadot.
+		pallet_nomination_pools::migration::unversioned::DelegationStakeMigration<
+			Runtime,
+			MaxPoolsToMigrate,
+		>,
+		pallet_staking::migrations::v15::MigrateV14ToV15<Runtime>,
+	);
 }
 
 /// Unchecked extrinsic type as expected by this runtime.
diff --git a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs
index 6aa5ddd1ec8fb5251ac6bbd5059938e4bad93b22..35eef199fb7a974f7665a83c09318ee06b37fea2 100644
--- a/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs
+++ b/polkadot/runtime/westend/src/weights/pallet_nomination_pools.rs
@@ -16,10 +16,10 @@
 
 //! Autogenerated weights for `pallet_nomination_pools`
 //!
-//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-11-24, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2024-04-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-yprdrvc7-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-dcu62vjg-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024
 
 // Executed Command:
@@ -54,7 +54,7 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::BondedPools` (r:1 w:1)
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
-	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Storage: `Staking::Bonded` (r:2 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
@@ -62,7 +62,7 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0)
 	/// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:2 w:1)
+	/// Storage: `System::Account` (r:1 w:0)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::MaxPoolMembersPerPool` (r:1 w:0)
 	/// Proof: `NominationPools::MaxPoolMembersPerPool` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -70,10 +70,16 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -82,13 +88,13 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn join() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3355`
+		//  Measured:  `3606`
 		//  Estimated: `8877`
-		// Minimum execution time: 173_707_000 picoseconds.
-		Weight::from_parts(179_920_000, 0)
+		// Minimum execution time: 204_877_000 picoseconds.
+		Weight::from_parts(210_389_000, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
-			.saturating_add(T::DbWeight::get().reads(20))
-			.saturating_add(T::DbWeight::get().writes(13))
+			.saturating_add(T::DbWeight::get().reads(24))
+			.saturating_add(T::DbWeight::get().writes(15))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
@@ -98,16 +104,20 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0)
 	/// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:3 w:2)
+	/// Storage: `System::Account` (r:2 w:1)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
-	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Storage: `Staking::Bonded` (r:2 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -116,13 +126,13 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_transfer() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3365`
+		//  Measured:  `3762`
 		//  Estimated: `8877`
-		// Minimum execution time: 174_414_000 picoseconds.
-		Weight::from_parts(178_068_000, 0)
+		// Minimum execution time: 203_362_000 picoseconds.
+		Weight::from_parts(209_899_000, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
-			.saturating_add(T::DbWeight::get().reads(17))
-			.saturating_add(T::DbWeight::get().writes(13))
+			.saturating_add(T::DbWeight::get().reads(20))
+			.saturating_add(T::DbWeight::get().writes(14))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
 	/// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`)
@@ -134,16 +144,20 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0)
 	/// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:3 w:3)
+	/// Storage: `System::Account` (r:2 w:2)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
-	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Storage: `Staking::Bonded` (r:2 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:2 w:2)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -152,13 +166,13 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3312`
-		//  Estimated: `8799`
-		// Minimum execution time: 198_864_000 picoseconds.
-		Weight::from_parts(203_783_000, 0)
-			.saturating_add(Weight::from_parts(0, 8799))
-			.saturating_add(T::DbWeight::get().reads(17))
-			.saturating_add(T::DbWeight::get().writes(13))
+		//  Measured:  `3709`
+		//  Estimated: `6248`
+		// Minimum execution time: 230_686_000 picoseconds.
+		Weight::from_parts(237_502_000, 0)
+			.saturating_add(Weight::from_parts(0, 6248))
+			.saturating_add(T::DbWeight::get().reads(20))
+			.saturating_add(T::DbWeight::get().writes(14))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
 	/// Proof: `NominationPools::ClaimPermissions` (`max_values`: None, `max_size`: Some(41), added: 2516, mode: `MaxEncodedLen`)
@@ -176,8 +190,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `1138`
 		//  Estimated: `4182`
-		// Minimum execution time: 70_250_000 picoseconds.
-		Weight::from_parts(72_231_000, 0)
+		// Minimum execution time: 70_821_000 picoseconds.
+		Weight::from_parts(72_356_000, 0)
 			.saturating_add(Weight::from_parts(0, 4182))
 			.saturating_add(T::DbWeight::get().reads(6))
 			.saturating_add(T::DbWeight::get().writes(4))
@@ -194,7 +208,7 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::GlobalMaxCommission` (r:1 w:0)
 	/// Proof: `NominationPools::GlobalMaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:2 w:1)
+	/// Storage: `System::Account` (r:1 w:0)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -202,10 +216,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::MinNominatorBond` (r:1 w:0)
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListNodes` (r:3 w:3)
 	/// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`)
 	/// Storage: `VoterList::ListBags` (r:2 w:2)
@@ -216,13 +228,13 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3545`
+		//  Measured:  `3341`
 		//  Estimated: `8877`
-		// Minimum execution time: 155_853_000 picoseconds.
-		Weight::from_parts(161_032_000, 0)
+		// Minimum execution time: 156_714_000 picoseconds.
+		Weight::from_parts(158_305_000, 0)
 			.saturating_add(Weight::from_parts(0, 8877))
-			.saturating_add(T::DbWeight::get().reads(20))
-			.saturating_add(T::DbWeight::get().writes(13))
+			.saturating_add(T::DbWeight::get().reads(18))
+			.saturating_add(T::DbWeight::get().writes(11))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
@@ -232,23 +244,25 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0)
+	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::TotalValueLocked` (r:1 w:1)
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
 	/// The range of component `s` is `[0, 100]`.
 	fn pool_withdraw_unbonded(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1744`
-		//  Estimated: `4764`
-		// Minimum execution time: 62_933_000 picoseconds.
-		Weight::from_parts(65_847_171, 0)
-			.saturating_add(Weight::from_parts(0, 4764))
-			// Standard Error: 1_476
-			.saturating_add(Weight::from_parts(59_648, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(7))
+		//  Measured:  `1767`
+		//  Estimated: `4556`
+		// Minimum execution time: 56_836_000 picoseconds.
+		Weight::from_parts(59_738_398, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
+			// Standard Error: 1_478
+			.saturating_add(Weight::from_parts(60_085, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(8))
 			.saturating_add(T::DbWeight::get().writes(3))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -259,18 +273,24 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1)
 	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:1 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:1 w:0)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:1 w:1)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0)
+	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::TotalValueLocked` (r:1 w:1)
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::ClaimPermissions` (r:0 w:1)
@@ -278,15 +298,15 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2134`
-		//  Estimated: `4764`
-		// Minimum execution time: 123_641_000 picoseconds.
-		Weight::from_parts(127_222_589, 0)
-			.saturating_add(Weight::from_parts(0, 4764))
-			// Standard Error: 2_493
-			.saturating_add(Weight::from_parts(83_361, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(11))
-			.saturating_add(T::DbWeight::get().writes(9))
+		//  Measured:  `2405`
+		//  Estimated: `4556`
+		// Minimum execution time: 136_737_000 picoseconds.
+		Weight::from_parts(141_757_658, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
+			// Standard Error: 2_609
+			.saturating_add(Weight::from_parts(84_538, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(14))
+			.saturating_add(T::DbWeight::get().writes(11))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
@@ -296,28 +316,38 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:1)
 	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:0)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
-	/// Storage: `Balances::Locks` (r:2 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:2 w:1)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:2 w:2)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:0)
 	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:1)
+	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::TotalValueLocked` (r:1 w:1)
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:1)
-	/// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForReversePoolIdLookup` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForReversePoolIdLookup` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::RewardPools` (r:1 w:1)
@@ -326,6 +356,10 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::CounterForRewardPools` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForSubPoolsStorage` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Freezes` (r:1 w:1)
+	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:0)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::Metadata` (r:1 w:1)
 	/// Proof: `NominationPools::Metadata` (`max_values`: None, `max_size`: Some(270), added: 2745, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForBondedPools` (r:1 w:1)
@@ -337,13 +371,13 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(_s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2453`
-		//  Estimated: `8538`
-		// Minimum execution time: 219_469_000 picoseconds.
-		Weight::from_parts(227_526_000, 0)
-			.saturating_add(Weight::from_parts(0, 8538))
-			.saturating_add(T::DbWeight::get().reads(24))
-			.saturating_add(T::DbWeight::get().writes(20))
+		//  Measured:  `2809`
+		//  Estimated: `6274`
+		// Minimum execution time: 241_043_000 picoseconds.
+		Weight::from_parts(250_578_253, 0)
+			.saturating_add(Weight::from_parts(0, 6274))
+			.saturating_add(T::DbWeight::get().reads(29))
+			.saturating_add(T::DbWeight::get().writes(26))
 	}
 	/// Storage: `NominationPools::LastPoolId` (r:1 w:1)
 	/// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -365,16 +399,30 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:2 w:1)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `System::Account` (r:2 w:2)
 	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
-	/// Storage: `Staking::Bonded` (r:1 w:1)
-	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Locks` (r:2 w:1)
-	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::Freezes` (r:2 w:1)
-	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::TotalValueLocked` (r:1 w:1)
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Freezes` (r:1 w:1)
+	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:0)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::RewardPools` (r:1 w:1)
 	/// Proof: `NominationPools::RewardPools` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForRewardPools` (r:1 w:1)
@@ -391,22 +439,28 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn create() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1102`
-		//  Estimated: `8538`
-		// Minimum execution time: 166_466_000 picoseconds.
-		Weight::from_parts(171_425_000, 0)
-			.saturating_add(Weight::from_parts(0, 8538))
-			.saturating_add(T::DbWeight::get().reads(23))
-			.saturating_add(T::DbWeight::get().writes(17))
+		//  Measured:  `1168`
+		//  Estimated: `6196`
+		// Minimum execution time: 180_902_000 picoseconds.
+		Weight::from_parts(187_769_000, 0)
+			.saturating_add(Weight::from_parts(0, 6196))
+			.saturating_add(T::DbWeight::get().reads(31))
+			.saturating_add(T::DbWeight::get().writes(23))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:0)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::MinNominatorBond` (r:1 w:0)
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::MinCreateBond` (r:1 w:0)
+	/// Proof: `NominationPools::MinCreateBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::MinJoinBond` (r:1 w:0)
+	/// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
 	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::MaxNominatorsCount` (r:1 w:0)
@@ -426,14 +480,14 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1738`
+		//  Measured:  `1921`
 		//  Estimated: `4556 + n * (2520 ±0)`
-		// Minimum execution time: 59_650_000 picoseconds.
-		Weight::from_parts(60_620_077, 0)
+		// Minimum execution time: 78_369_000 picoseconds.
+		Weight::from_parts(79_277_958, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
-			// Standard Error: 7_316
-			.saturating_add(Weight::from_parts(1_467_406, 0).saturating_mul(n.into()))
-			.saturating_add(T::DbWeight::get().reads(12))
+			// Standard Error: 8_343
+			.saturating_add(Weight::from_parts(1_493_255, 0).saturating_mul(n.into()))
+			.saturating_add(T::DbWeight::get().reads(15))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(5))
 			.saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into()))
@@ -446,10 +500,10 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_state() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1363`
+		//  Measured:  `1406`
 		//  Estimated: `4556`
-		// Minimum execution time: 31_170_000 picoseconds.
-		Weight::from_parts(32_217_000, 0)
+		// Minimum execution time: 32_631_000 picoseconds.
+		Weight::from_parts(33_356_000, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -465,11 +519,11 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `498`
 		//  Estimated: `3735`
-		// Minimum execution time: 12_603_000 picoseconds.
-		Weight::from_parts(13_241_702, 0)
+		// Minimum execution time: 12_514_000 picoseconds.
+		Weight::from_parts(13_232_732, 0)
 			.saturating_add(Weight::from_parts(0, 3735))
-			// Standard Error: 116
-			.saturating_add(Weight::from_parts(1_428, 0).saturating_mul(n.into()))
+			// Standard Error: 150
+			.saturating_add(Weight::from_parts(2_371, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(2))
 	}
@@ -489,8 +543,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 3_608_000 picoseconds.
-		Weight::from_parts(3_801_000, 0)
+		// Minimum execution time: 3_107_000 picoseconds.
+		Weight::from_parts(3_255_000, 0)
 			.saturating_add(Weight::from_parts(0, 0))
 			.saturating_add(T::DbWeight::get().writes(6))
 	}
@@ -500,18 +554,22 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `498`
 		//  Estimated: `3719`
-		// Minimum execution time: 16_053_000 picoseconds.
-		Weight::from_parts(16_473_000, 0)
+		// Minimum execution time: 16_568_000 picoseconds.
+		Weight::from_parts(17_019_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
 	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:0)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:0)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::MinNominatorBond` (r:1 w:0)
+	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Validators` (r:1 w:0)
 	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Nominators` (r:1 w:1)
@@ -526,12 +584,12 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1901`
+		//  Measured:  `2138`
 		//  Estimated: `4556`
-		// Minimum execution time: 57_251_000 picoseconds.
-		Weight::from_parts(59_390_000, 0)
+		// Minimum execution time: 73_717_000 picoseconds.
+		Weight::from_parts(77_030_000, 0)
 			.saturating_add(Weight::from_parts(0, 4556))
-			.saturating_add(T::DbWeight::get().reads(9))
+			.saturating_add(T::DbWeight::get().reads(11))
 			.saturating_add(T::DbWeight::get().writes(5))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:1)
@@ -546,8 +604,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `770`
 		//  Estimated: `3719`
-		// Minimum execution time: 29_888_000 picoseconds.
-		Weight::from_parts(31_056_000, 0)
+		// Minimum execution time: 30_770_000 picoseconds.
+		Weight::from_parts(31_556_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(4))
 			.saturating_add(T::DbWeight::get().writes(2))
@@ -560,8 +618,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `538`
 		//  Estimated: `3719`
-		// Minimum execution time: 15_769_000 picoseconds.
-		Weight::from_parts(16_579_000, 0)
+		// Minimum execution time: 16_257_000 picoseconds.
+		Weight::from_parts(16_891_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -572,8 +630,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `498`
 		//  Estimated: `3719`
-		// Minimum execution time: 15_385_000 picoseconds.
-		Weight::from_parts(16_402_000, 0)
+		// Minimum execution time: 16_548_000 picoseconds.
+		Weight::from_parts(18_252_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -584,8 +642,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `498`
 		//  Estimated: `3719`
-		// Minimum execution time: 14_965_000 picoseconds.
-		Weight::from_parts(15_548_000, 0)
+		// Minimum execution time: 16_085_000 picoseconds.
+		Weight::from_parts(17_218_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -598,8 +656,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `508`
 		//  Estimated: `4182`
-		// Minimum execution time: 13_549_000 picoseconds.
-		Weight::from_parts(14_307_000, 0)
+		// Minimum execution time: 13_648_000 picoseconds.
+		Weight::from_parts(13_990_000, 0)
 			.saturating_add(Weight::from_parts(0, 4182))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -616,8 +674,8 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 		// Proof Size summary in bytes:
 		//  Measured:  `968`
 		//  Estimated: `3719`
-		// Minimum execution time: 60_153_000 picoseconds.
-		Weight::from_parts(61_369_000, 0)
+		// Minimum execution time: 60_321_000 picoseconds.
+		Weight::from_parts(61_512_000, 0)
 			.saturating_add(Weight::from_parts(0, 3719))
 			.saturating_add(T::DbWeight::get().reads(4))
 			.saturating_add(T::DbWeight::get().writes(2))
@@ -632,12 +690,135 @@ impl<T: frame_system::Config> pallet_nomination_pools::WeightInfo for WeightInfo
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	fn adjust_pool_deposit() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `867`
+		//  Measured:  `876`
 		//  Estimated: `4764`
-		// Minimum execution time: 64_985_000 picoseconds.
-		Weight::from_parts(66_616_000, 0)
+		// Minimum execution time: 65_609_000 picoseconds.
+		Weight::from_parts(67_320_000, 0)
 			.saturating_add(Weight::from_parts(0, 4764))
 			.saturating_add(T::DbWeight::get().reads(4))
 			.saturating_add(T::DbWeight::get().writes(2))
 	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
+	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0)
+	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn apply_slash() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `3328`
+		//  Estimated: `4556`
+		// Minimum execution time: 99_605_000 picoseconds.
+		Weight::from_parts(101_986_000, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
+			.saturating_add(T::DbWeight::get().reads(9))
+			.saturating_add(T::DbWeight::get().writes(4))
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:0)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:1 w:0)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
+	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0)
+	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`)
+	fn apply_slash_fail() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `3070`
+		//  Estimated: `4556`
+		// Minimum execution time: 58_103_000 picoseconds.
+		Weight::from_parts(59_680_000, 0)
+			.saturating_add(Weight::from_parts(0, 4556))
+			.saturating_add(T::DbWeight::get().reads(7))
+	}
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Validators` (r:1 w:0)
+	/// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Nominators` (r:1 w:0)
+	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:2 w:1)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForAgents` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForAgents` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Locks` (r:1 w:1)
+	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Freezes` (r:1 w:0)
+	/// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(67), added: 2542, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::CounterForVirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::CounterForVirtualStakers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:1 w:1)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Payee` (r:0 w:1)
+	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
+	fn pool_migrate() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1359`
+		//  Estimated: `6196`
+		// Minimum execution time: 144_098_000 picoseconds.
+		Weight::from_parts(146_590_000, 0)
+			.saturating_add(Weight::from_parts(0, 6196))
+			.saturating_add(T::DbWeight::get().reads(16))
+			.saturating_add(T::DbWeight::get().writes(11))
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(717), added: 3192, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
+	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:2 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0)
+	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(261), added: 2736, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::MinJoinBond` (r:1 w:0)
+	/// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Delegators` (r:2 w:2)
+	/// Proof: `DelegatedStaking::Delegators` (`max_values`: None, `max_size`: Some(88), added: 2563, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::Agents` (r:2 w:0)
+	/// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`)
+	/// Storage: `Balances::Holds` (r:2 w:2)
+	/// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`)
+	/// Storage: `DelegatedStaking::CounterForDelegators` (r:1 w:1)
+	/// Proof: `DelegatedStaking::CounterForDelegators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:1 w:1)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	fn migrate_delegation() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `2275`
+		//  Estimated: `6180`
+		// Minimum execution time: 148_594_000 picoseconds.
+		Weight::from_parts(152_119_000, 0)
+			.saturating_add(Weight::from_parts(0, 6180))
+			.saturating_add(T::DbWeight::get().reads(15))
+			.saturating_add(T::DbWeight::get().writes(6))
+	}
 }
diff --git a/prdoc/pr_3905.prdoc b/prdoc/pr_3905.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..d1c03650c9b2365d8f33345fbcef98b4f3885a51
--- /dev/null
+++ b/prdoc/pr_3905.prdoc
@@ -0,0 +1,25 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: Allows Nomination Pool to use different staking strategies including a new DelegateStake strategy.
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      This PR introduces a new staking strategy called `DelegateStake`. This strategy allows the nomination pool to
+      delegate its stake to a validator, that is, funds are locked in user account itself instead of being transferred
+      to the pool account. Includes migration of pools to this strategy for Westend.
+
+crates:
+    - name: pallet-nomination-pools
+      bump: major
+    - name: pallet-nomination-pools-benchmarking
+      bump: major
+    - name: sp-staking
+      bump: patch
+    - name: pallet-staking
+      bump: patch
+    - name: pallet-delegated-staking
+      bump: patch
+    - name: westend-runtime
+      bump: major
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 5067085e8eddabe75732a048b55e7bfc591c1780..617088ffe1ff2bfec888da55746f6e8003c769d6 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -910,7 +910,7 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = Staking;
+	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
 	type PostUnbondingPoolsWindow = PostUnbondPoolsWindow;
 	type MaxMetadataLen = ConstU32<256>;
 	type MaxUnbonding = ConstU32<8>;
diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml
index 4a4898827110105c4b05f81841494e19c14411f4..3b122dc2e26c3c9d0f56f07427b037bd1cd388c0 100644
--- a/substrate/frame/delegated-staking/Cargo.toml
+++ b/substrate/frame/delegated-staking/Cargo.toml
@@ -26,6 +26,7 @@ sp-io = { path = "../../primitives/io" }
 substrate-test-utils = { path = "../../test-utils" }
 sp-tracing = { path = "../../primitives/tracing" }
 pallet-staking = { path = "../staking" }
+pallet-nomination-pools = { path = "../nomination-pools" }
 pallet-balances = { path = "../balances" }
 pallet-timestamp = { path = "../timestamp" }
 pallet-staking-reward-curve = { path = "../staking/reward-curve" }
@@ -39,6 +40,7 @@ std = [
 	"frame-support/std",
 	"frame-system/std",
 	"pallet-balances/std",
+	"pallet-nomination-pools/std",
 	"pallet-staking/std",
 	"pallet-timestamp/std",
 	"scale-info/std",
@@ -53,6 +55,7 @@ runtime-benchmarks = [
 	"frame-support/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
+	"pallet-nomination-pools/runtime-benchmarks",
 	"pallet-staking/runtime-benchmarks",
 	"pallet-timestamp/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
@@ -63,6 +66,7 @@ try-runtime = [
 	"frame-support/try-runtime",
 	"frame-system/try-runtime",
 	"pallet-balances/try-runtime",
+	"pallet-nomination-pools/try-runtime",
 	"pallet-staking/try-runtime",
 	"pallet-timestamp/try-runtime",
 	"sp-runtime/try-runtime",
diff --git a/substrate/frame/delegated-staking/src/impls.rs b/substrate/frame/delegated-staking/src/impls.rs
index b1945b0ce3769affb7537d9b575adb85890304e6..032f6120642852f04867861c053a97bddc942ac9 100644
--- a/substrate/frame/delegated-staking/src/impls.rs
+++ b/substrate/frame/delegated-staking/src/impls.rs
@@ -109,7 +109,6 @@ impl<T: Config> DelegationMigrator for Pallet<T> {
 			reward_account.clone(),
 		)
 	}
-
 	fn migrate_delegation(
 		agent: &Self::AccountId,
 		delegator: &Self::AccountId,
@@ -121,6 +120,24 @@ impl<T: Config> DelegationMigrator for Pallet<T> {
 			value,
 		)
 	}
+
+	/// Only used for testing.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn drop_agent(agent: &T::AccountId) {
+		<Agents<T>>::remove(agent);
+		<Delegators<T>>::iter()
+			.filter(|(_, delegation)| delegation.agent == *agent)
+			.for_each(|(delegator, _)| {
+				let _ = T::Currency::release_all(
+					&HoldReason::StakingDelegation.into(),
+					&delegator,
+					Precision::BestEffort,
+				);
+				<Delegators<T>>::remove(&delegator);
+			});
+
+		T::CoreStaking::migrate_to_direct_staker(agent);
+	}
 }
 
 impl<T: Config> OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs
index 210f69d9c839d5b977506c177a11f425c26e87f6..8581a4a981fe46af53d1b0708160bc6c69a95ec9 100644
--- a/substrate/frame/delegated-staking/src/lib.rs
+++ b/substrate/frame/delegated-staking/src/lib.rs
@@ -165,7 +165,10 @@ use frame_system::{ensure_signed, pallet_prelude::*, RawOrigin};
 pub mod pallet {
 	use super::*;
 
+	/// The in-code storage version.
+	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
 	#[pallet::pallet]
+	#[pallet::storage_version(STORAGE_VERSION)]
 	pub struct Pallet<T>(PhantomData<T>);
 
 	#[pallet::config]
@@ -245,6 +248,8 @@ pub mod pallet {
 		Released { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
 		/// Funds slashed from a delegator.
 		Slashed { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
+		/// Unclaimed delegation funds migrated to delegator.
+		MigratedDelegation { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
 	}
 
 	/// Map of Delegators to their `Delegation`.
@@ -371,7 +376,7 @@ pub mod pallet {
 			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
 
 			// and has enough delegated balance to migrate.
-			let proxy_delegator = Self::sub_account(AccountType::ProxyDelegator, agent);
+			let proxy_delegator = Self::generate_proxy_delegator(agent);
 			let balance_remaining = Self::held_balance_of(&proxy_delegator);
 			ensure!(balance_remaining >= amount, Error::<T>::NotEnoughFunds);
 
@@ -422,6 +427,12 @@ pub mod pallet {
 }
 
 impl<T: Config> Pallet<T> {
+	/// Derive an account from the migrating agent account where the unclaimed delegation funds
+	/// are held.
+	pub fn generate_proxy_delegator(agent: T::AccountId) -> T::AccountId {
+		Self::sub_account(AccountType::ProxyDelegator, agent)
+	}
+
 	/// Derive a (keyless) pot account from the given agent account and account type.
 	pub(crate) fn sub_account(account_type: AccountType, agent: T::AccountId) -> T::AccountId {
 		T::PalletId::get().into_sub_account_truncating((account_type, agent.clone()))
@@ -464,7 +475,7 @@ impl<T: Config> Pallet<T> {
 
 		// We create a proxy delegator that will keep all the delegation funds until funds are
 		// transferred to actual delegator.
-		let proxy_delegator = Self::sub_account(AccountType::ProxyDelegator, who.clone());
+		let proxy_delegator = Self::generate_proxy_delegator(who.clone());
 
 		// Keep proxy delegator alive until all funds are migrated.
 		frame_system::Pallet::<T>::inc_providers(&proxy_delegator);
@@ -646,9 +657,9 @@ impl<T: Config> Pallet<T> {
 			!Self::is_delegator(destination_delegator) && !Self::is_agent(destination_delegator)
 		);
 
+		let agent = source_delegation.agent.clone();
 		// update delegations
-		Delegation::<T>::new(&source_delegation.agent, amount)
-			.update_or_kill(destination_delegator);
+		Delegation::<T>::new(&agent, amount).update_or_kill(destination_delegator);
 
 		source_delegation.amount = source_delegation
 			.amount
@@ -684,6 +695,12 @@ impl<T: Config> Pallet<T> {
 		// hold the funds again in the new delegator account.
 		T::Currency::hold(&HoldReason::StakingDelegation.into(), destination_delegator, amount)?;
 
+		Self::deposit_event(Event::<T>::MigratedDelegation {
+			agent,
+			delegator: destination_delegator.clone(),
+			amount,
+		});
+
 		Ok(())
 	}
 
diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs
index 21a9fe6b227009abe3b31c9fa6a4bbdc81e831d7..b9eaffb970e13ab69853dc946ee3431370d892c9 100644
--- a/substrate/frame/delegated-staking/src/mock.rs
+++ b/substrate/frame/delegated-staking/src/mock.rs
@@ -32,6 +32,8 @@ use frame_election_provider_support::{
 };
 use frame_support::dispatch::RawOrigin;
 use pallet_staking::{ActiveEra, ActiveEraInfo, CurrentEra};
+use sp_core::U256;
+use sp_runtime::traits::Convert;
 use sp_staking::{Stake, StakingInterface};
 
 pub type T = Runtime;
@@ -129,7 +131,7 @@ impl pallet_staking::Config for Runtime {
 	type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
 	type MaxUnlockingChunks = ConstU32<10>;
 	type MaxControllersInDeprecationBatch = ConstU32<100>;
-	type EventListeners = DelegatedStaking;
+	type EventListeners = (Pools, DelegatedStaking);
 	type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
 	type WeightInfo = ();
 	type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
@@ -149,8 +151,39 @@ impl delegated_staking::Config for Runtime {
 	type CoreStaking = Staking;
 }
 
+pub struct BalanceToU256;
+impl Convert<Balance, U256> for BalanceToU256 {
+	fn convert(n: Balance) -> U256 {
+		n.into()
+	}
+}
+pub struct U256ToBalance;
+impl Convert<U256, Balance> for U256ToBalance {
+	fn convert(n: U256) -> Balance {
+		n.try_into().unwrap()
+	}
+}
+
 parameter_types! {
 	pub static MaxUnbonding: u32 = 8;
+	pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
+}
+impl pallet_nomination_pools::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = ();
+	type Currency = Balances;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type RewardCounter = sp_runtime::FixedU128;
+	type BalanceToU256 = BalanceToU256;
+	type U256ToBalance = U256ToBalance;
+	type PostUnbondingPoolsWindow = ConstU32<2>;
+	type PalletId = PoolsPalletId;
+	type MaxMetadataLen = ConstU32<256>;
+	type MaxUnbonding = MaxUnbonding;
+	type MaxPointsToBalance = frame_support::traits::ConstU8<10>;
+	type StakeAdapter =
+		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
+	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
 }
 
 frame_support::construct_runtime!(
@@ -159,6 +192,7 @@ frame_support::construct_runtime!(
 		Timestamp: pallet_timestamp,
 		Balances: pallet_balances,
 		Staking: pallet_staking,
+		Pools: pallet_nomination_pools,
 		DelegatedStaking: delegated_staking,
 	}
 );
@@ -297,9 +331,16 @@ pub(crate) fn get_agent(agent: &AccountId) -> Agent<T> {
 
 parameter_types! {
 	static ObservedEventsDelegatedStaking: usize = 0;
+	static ObservedEventsPools: usize = 0;
+}
+
+pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
+	let events = System::read_events_for_pallet::<pallet_nomination_pools::Event<Runtime>>();
+	let already_seen = ObservedEventsPools::get();
+	ObservedEventsPools::set(events.len());
+	events.into_iter().skip(already_seen).collect()
 }
 
-#[allow(unused)]
 pub(crate) fn events_since_last_call() -> Vec<crate::Event<Runtime>> {
 	let events = System::read_events_for_pallet::<crate::Event<Runtime>>();
 	let already_seen = ObservedEventsDelegatedStaking::get();
diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs
index 1f36f655beb865f138a87fb4a9546ea1ad97a3ae..6b68726b274cb0447fd26c11e4c2e6ad282f8e85 100644
--- a/substrate/frame/delegated-staking/src/tests.rs
+++ b/substrate/frame/delegated-staking/src/tests.rs
@@ -20,8 +20,9 @@
 use super::*;
 use crate::mock::*;
 use frame_support::{assert_noop, assert_ok, traits::fungible::InspectHold};
+use pallet_nomination_pools::{Error as PoolsError, Event as PoolsEvent};
 use pallet_staking::Error as StakingError;
-use sp_staking::DelegationInterface;
+use sp_staking::{DelegationInterface, StakerStatus};
 
 #[test]
 fn create_an_agent_with_first_delegator() {
@@ -623,7 +624,7 @@ mod staking_integration {
 			// to migrate, nominator needs to set an account as a proxy delegator where staked funds
 			// will be moved and delegated back to this old nominator account. This should be funded
 			// with at least ED.
-			let proxy_delegator = DelegatedStaking::sub_account(AccountType::ProxyDelegator, 200);
+			let proxy_delegator = DelegatedStaking::generate_proxy_delegator(200);
 
 			assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(200).into(), 201));
 
@@ -683,3 +684,501 @@ mod staking_integration {
 		});
 	}
 }
+
+mod pool_integration {
+	use super::*;
+	use pallet_nomination_pools::{BondExtra, BondedPools, PoolState};
+
+	#[test]
+	fn create_pool_test() {
+		ExtBuilder::default().build_and_execute(|| {
+			let creator: AccountId = 100;
+			fund(&creator, 500);
+			let delegate_amount = 200;
+
+			// nothing held initially
+			assert_eq!(DelegatedStaking::held_balance_of(&creator), 0);
+
+			// create pool
+			assert_ok!(Pools::create(
+				RawOrigin::Signed(creator).into(),
+				delegate_amount,
+				creator,
+				creator,
+				creator
+			));
+
+			// correct amount is locked in depositor's account.
+			assert_eq!(DelegatedStaking::held_balance_of(&creator), delegate_amount);
+
+			let pool_account = Pools::generate_bonded_account(1);
+			let agent = get_agent(&pool_account);
+
+			// verify state
+			assert_eq!(agent.ledger.effective_balance(), delegate_amount);
+			assert_eq!(agent.available_to_bond(), 0);
+			assert_eq!(agent.total_unbonded(), 0);
+		});
+	}
+
+	#[test]
+	fn join_pool() {
+		ExtBuilder::default().build_and_execute(|| {
+			// create a pool
+			let pool_id = create_pool(100, 200);
+			// keep track of staked amount.
+			let mut staked_amount: Balance = 200;
+
+			// fund delegator
+			let delegator: AccountId = 300;
+			fund(&delegator, 500);
+			// nothing held initially
+			assert_eq!(DelegatedStaking::held_balance_of(&delegator), 0);
+
+			// delegator joins pool
+			assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), 100, pool_id));
+			staked_amount += 100;
+
+			// correct amount is locked in depositor's account.
+			assert_eq!(DelegatedStaking::held_balance_of(&delegator), 100);
+
+			// delegator is not actively exposed to core staking.
+			assert_eq!(Staking::status(&delegator), Err(StakingError::<T>::NotStash.into()));
+
+			let pool_agent = get_agent(&Pools::generate_bonded_account(1));
+			// verify state
+			assert_eq!(pool_agent.ledger.effective_balance(), staked_amount);
+			assert_eq!(pool_agent.bonded_stake(), staked_amount);
+			assert_eq!(pool_agent.available_to_bond(), 0);
+			assert_eq!(pool_agent.total_unbonded(), 0);
+
+			// cannot reap agent in staking.
+			assert_noop!(
+				Staking::reap_stash(RuntimeOrigin::signed(100), pool_agent.key, 0),
+				StakingError::<T>::VirtualStakerNotAllowed
+			);
+
+			// let a bunch of delegators join this pool
+			for i in 301..350 {
+				fund(&i, 500);
+				assert_ok!(Pools::join(RawOrigin::Signed(i).into(), 100 + i, pool_id));
+				staked_amount += 100 + i;
+				assert_eq!(DelegatedStaking::held_balance_of(&i), 100 + i);
+			}
+
+			let pool_agent = pool_agent.refresh().unwrap();
+			assert_eq!(pool_agent.ledger.effective_balance(), staked_amount);
+			assert_eq!(pool_agent.bonded_stake(), staked_amount);
+			assert_eq!(pool_agent.available_to_bond(), 0);
+			assert_eq!(pool_agent.total_unbonded(), 0);
+		});
+	}
+
+	#[test]
+	fn bond_extra_to_pool() {
+		ExtBuilder::default().build_and_execute(|| {
+			let pool_id = create_pool(100, 200);
+			add_delegators_to_pool(pool_id, (300..310).collect(), 100);
+			let mut staked_amount = 200 + 100 * 10;
+			assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount);
+
+			// bond extra to pool
+			for i in 300..310 {
+				assert_ok!(Pools::bond_extra(
+					RawOrigin::Signed(i).into(),
+					BondExtra::FreeBalance(50)
+				));
+				staked_amount += 50;
+				assert_eq!(get_pool_agent(pool_id).bonded_stake(), staked_amount);
+			}
+		});
+	}
+
+	#[test]
+	fn claim_pool_rewards() {
+		ExtBuilder::default().build_and_execute(|| {
+			let creator = 100;
+			let creator_stake = 1000;
+			let pool_id = create_pool(creator, creator_stake);
+			add_delegators_to_pool(pool_id, (300..310).collect(), 100);
+			add_delegators_to_pool(pool_id, (310..320).collect(), 200);
+			let total_staked = creator_stake + 100 * 10 + 200 * 10;
+
+			// give some rewards
+			let reward_acc = Pools::generate_reward_account(pool_id);
+			let reward_amount = 1000;
+			fund(&reward_acc, reward_amount);
+
+			// claim rewards
+			for i in 300..320 {
+				let pre_balance = Balances::free_balance(i);
+				let delegator_staked_balance = DelegatedStaking::held_balance_of(&i);
+				// payout reward
+				assert_ok!(Pools::claim_payout(RawOrigin::Signed(i).into()));
+
+				let reward = Balances::free_balance(i) - pre_balance;
+				assert_eq!(reward, delegator_staked_balance * reward_amount / total_staked);
+			}
+
+			// payout creator
+			let pre_balance = Balances::free_balance(creator);
+			assert_ok!(Pools::claim_payout(RawOrigin::Signed(creator).into()));
+			// verify they are paid out correctly
+			let reward = Balances::free_balance(creator) - pre_balance;
+			assert_eq!(reward, creator_stake * reward_amount / total_staked);
+
+			// reward account should only have left minimum balance after paying out everyone.
+			assert_eq!(Balances::free_balance(reward_acc), ExistentialDeposit::get());
+		});
+	}
+
+	#[test]
+	fn withdraw_from_pool() {
+		ExtBuilder::default().build_and_execute(|| {
+			// initial era
+			start_era(1);
+
+			let pool_id = create_pool(100, 1000);
+			let bond_amount = 200;
+			add_delegators_to_pool(pool_id, (300..310).collect(), bond_amount);
+			let total_staked = 1000 + bond_amount * 10;
+			let pool_acc = Pools::generate_bonded_account(pool_id);
+
+			start_era(2);
+			// nothing to release yet.
+			assert_noop!(
+				Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0),
+				PoolsError::<T>::SubPoolsNotFound
+			);
+
+			// 301 wants to unbond 50 in era 2, withdrawable in era 5.
+			assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, 50));
+
+			// 302 wants to unbond 100 in era 3, withdrawable in era 6.
+			start_era(3);
+			assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, 100));
+
+			// 303 wants to unbond 200 in era 4, withdrawable in era 7.
+			start_era(4);
+			assert_ok!(Pools::unbond(RawOrigin::Signed(303).into(), 303, 200));
+
+			// active stake is now reduced..
+			let expected_active = total_staked - (50 + 100 + 200);
+			assert!(eq_stake(pool_acc, total_staked, expected_active));
+
+			// nothing to withdraw at era 4
+			for i in 301..310 {
+				assert_noop!(
+					Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0),
+					PoolsError::<T>::CannotWithdrawAny
+				);
+			}
+
+			assert!(eq_stake(pool_acc, total_staked, expected_active));
+
+			start_era(5);
+			// at era 5, 301 can withdraw.
+
+			System::reset_events();
+			let held_301 = DelegatedStaking::held_balance_of(&301);
+			let free_301 = Balances::free_balance(301);
+
+			assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(301).into(), 301, 0));
+			assert_eq!(
+				events_since_last_call(),
+				vec![Event::Released { agent: pool_acc, delegator: 301, amount: 50 }]
+			);
+			assert_eq!(
+				pool_events_since_last_call(),
+				vec![PoolsEvent::Withdrawn { member: 301, pool_id, balance: 50, points: 50 }]
+			);
+			assert_eq!(DelegatedStaking::held_balance_of(&301), held_301 - 50);
+			assert_eq!(Balances::free_balance(301), free_301 + 50);
+
+			start_era(7);
+			// era 7 both delegators can withdraw
+			assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(302).into(), 302, 0));
+			assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(303).into(), 303, 0));
+
+			assert_eq!(
+				events_since_last_call(),
+				vec![
+					Event::Released { agent: pool_acc, delegator: 302, amount: 100 },
+					Event::Released { agent: pool_acc, delegator: 303, amount: 200 },
+				]
+			);
+			assert_eq!(
+				pool_events_since_last_call(),
+				vec![
+					PoolsEvent::Withdrawn { member: 302, pool_id, balance: 100, points: 100 },
+					PoolsEvent::Withdrawn { member: 303, pool_id, balance: 200, points: 200 },
+					PoolsEvent::MemberRemoved { pool_id: 1, member: 303 },
+				]
+			);
+
+			// 303 is killed
+			assert!(!Delegators::<T>::contains_key(303));
+		});
+	}
+
+	#[test]
+	fn pool_withdraw_unbonded() {
+		ExtBuilder::default().build_and_execute(|| {
+			// initial era
+			start_era(1);
+			let pool_id = create_pool(100, 1000);
+			add_delegators_to_pool(pool_id, (300..310).collect(), 200);
+
+			start_era(2);
+			// 1000 tokens to be unbonded in era 5.
+			for i in 300..310 {
+				assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 100));
+			}
+
+			start_era(3);
+			// 500 tokens to be unbonded in era 6.
+			for i in 300..310 {
+				assert_ok!(Pools::unbond(RawOrigin::Signed(i).into(), i, 50));
+			}
+
+			start_era(5);
+			// withdraw pool should withdraw 1000 tokens
+			assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
+			assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000);
+
+			start_era(6);
+			// should withdraw 500 more
+			assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
+			assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1000 + 500);
+
+			start_era(7);
+			// Nothing to withdraw, still at 1500.
+			assert_ok!(Pools::pool_withdraw_unbonded(RawOrigin::Signed(100).into(), pool_id, 0));
+			assert_eq!(get_pool_agent(pool_id).total_unbonded(), 1500);
+		});
+	}
+
+	#[test]
+	fn update_nominations() {
+		ExtBuilder::default().build_and_execute(|| {
+			start_era(1);
+			// can't nominate for non-existent pool
+			assert_noop!(
+				Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![99]),
+				PoolsError::<T>::PoolNotFound
+			);
+
+			let pool_id = create_pool(100, 1000);
+			let pool_acc = Pools::generate_bonded_account(pool_id);
+			assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![20, 21, 22]));
+			assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![20, 21, 22])));
+
+			start_era(3);
+			assert_ok!(Pools::nominate(RawOrigin::Signed(100).into(), 1, vec![18, 19, 22]));
+			assert!(Staking::status(&pool_acc) == Ok(StakerStatus::Nominator(vec![18, 19, 22])));
+		});
+	}
+
+	#[test]
+	fn destroy_pool() {
+		ExtBuilder::default().build_and_execute(|| {
+			start_era(1);
+			let creator = 100;
+			let creator_stake = 1000;
+			let pool_id = create_pool(creator, creator_stake);
+			add_delegators_to_pool(pool_id, (300..310).collect(), 200);
+
+			start_era(3);
+			// lets destroy the pool
+			assert_ok!(Pools::set_state(
+				RawOrigin::Signed(creator).into(),
+				pool_id,
+				PoolState::Destroying
+			));
+			assert_ok!(Pools::chill(RawOrigin::Signed(creator).into(), pool_id));
+
+			// unbond all members by the creator/admin
+			for i in 300..310 {
+				assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), i, 200));
+			}
+
+			start_era(6);
+			// withdraw all members by the creator/admin
+			for i in 300..310 {
+				assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), i, 0));
+			}
+
+			// unbond creator
+			assert_ok!(Pools::unbond(RawOrigin::Signed(creator).into(), creator, creator_stake));
+
+			start_era(9);
+			System::reset_events();
+			// Withdraw self
+			assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(creator).into(), creator, 0));
+			assert_eq!(
+				pool_events_since_last_call(),
+				vec![
+					PoolsEvent::Withdrawn {
+						member: creator,
+						pool_id,
+						balance: creator_stake,
+						points: creator_stake,
+					},
+					PoolsEvent::MemberRemoved { pool_id, member: creator },
+					PoolsEvent::Destroyed { pool_id },
+				]
+			);
+
+			// Make sure all data is cleaned up.
+			assert!(!Agents::<T>::contains_key(Pools::generate_bonded_account(pool_id)));
+			assert!(!System::account_exists(&Pools::generate_bonded_account(pool_id)));
+			assert!(!Delegators::<T>::contains_key(creator));
+			for i in 300..310 {
+				assert!(!Delegators::<T>::contains_key(i));
+			}
+		});
+	}
+
+	#[test]
+	fn pool_partially_slashed() {
+		ExtBuilder::default().build_and_execute(|| {
+			start_era(1);
+			let creator = 100;
+			let creator_stake = 500;
+			let pool_id = create_pool(creator, creator_stake);
+			let delegator_stake = 100;
+			add_delegators_to_pool(pool_id, (300..306).collect(), delegator_stake);
+			let pool_acc = Pools::generate_bonded_account(pool_id);
+
+			let total_staked = creator_stake + delegator_stake * 6;
+			assert_eq!(Staking::stake(&pool_acc).unwrap().total, total_staked);
+
+			// lets unbond a delegator each in next eras (2, 3, 4).
+			start_era(2);
+			assert_ok!(Pools::unbond(RawOrigin::Signed(300).into(), 300, delegator_stake));
+
+			start_era(3);
+			assert_ok!(Pools::unbond(RawOrigin::Signed(301).into(), 301, delegator_stake));
+
+			start_era(4);
+			assert_ok!(Pools::unbond(RawOrigin::Signed(302).into(), 302, delegator_stake));
+			System::reset_events();
+
+			// slash the pool at era 3
+			assert_eq!(
+				BondedPools::<T>::get(1).unwrap().points,
+				creator_stake + delegator_stake * 6 - delegator_stake * 3
+			);
+			pallet_staking::slashing::do_slash::<T>(
+				&pool_acc,
+				500,
+				&mut Default::default(),
+				&mut Default::default(),
+				3,
+			);
+
+			assert_eq!(
+				pool_events_since_last_call(),
+				vec![
+					// 300 did not get slashed as all as it unbonded in an era before slash.
+					// 301 got slashed 50% of 100 = 50.
+					PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 6, balance: 50 },
+					// 302 got slashed 50% of 100 = 50.
+					PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 7, balance: 50 },
+					// Rest of the pool slashed 50% of 800 = 400.
+					PoolsEvent::PoolSlashed { pool_id: 1, balance: 400 },
+				]
+			);
+
+			// slash is lazy and balance is still locked in user's accounts.
+			assert_eq!(DelegatedStaking::held_balance_of(&creator), creator_stake);
+			for i in 300..306 {
+				assert_eq!(DelegatedStaking::held_balance_of(&i), delegator_stake);
+			}
+			assert_eq!(
+				get_pool_agent(pool_id).ledger.effective_balance(),
+				Staking::total_stake(&pool_acc).unwrap()
+			);
+
+			// pending slash is book kept.
+			assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500);
+
+			// go in some distant future era.
+			start_era(10);
+			System::reset_events();
+
+			// 300 is not slashed and can withdraw all balance.
+			assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(300).into(), 300, 1));
+			assert_eq!(
+				events_since_last_call(),
+				vec![Event::Released { agent: pool_acc, delegator: 300, amount: 100 }]
+			);
+			assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 500);
+
+			// withdraw the other two delegators (301 and 302) who were unbonding.
+			for i in 301..=302 {
+				let pre_balance = Balances::free_balance(i);
+				let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash;
+				assert_ok!(Pools::withdraw_unbonded(RawOrigin::Signed(i).into(), i, 0));
+				assert_eq!(
+					events_since_last_call(),
+					vec![
+						Event::Slashed { agent: pool_acc, delegator: i, amount: 50 },
+						Event::Released { agent: pool_acc, delegator: i, amount: 50 },
+					]
+				);
+				assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50);
+				assert_eq!(DelegatedStaking::held_balance_of(&i), 0);
+				assert_eq!(Balances::free_balance(i) - pre_balance, 50);
+			}
+
+			// let's update all the slash
+			let slash_reporter = 99;
+			// give our reporter some balance.
+			fund(&slash_reporter, 100);
+
+			for i in 303..306 {
+				let pre_pending_slash = get_pool_agent(pool_id).ledger.pending_slash;
+				assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), i));
+
+				// each member is slashed 50% of 100 = 50.
+				assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, pre_pending_slash - 50);
+				// left with 50.
+				assert_eq!(DelegatedStaking::held_balance_of(&i), 50);
+			}
+			// reporter is paid SlashRewardFraction of the slash, i.e. 10% of 50 = 5
+			assert_eq!(Balances::free_balance(slash_reporter), 100 + 5 * 3);
+			// slash creator
+			assert_ok!(Pools::apply_slash(RawOrigin::Signed(slash_reporter).into(), creator));
+			// all slash should be applied now.
+			assert_eq!(get_pool_agent(pool_id).ledger.pending_slash, 0);
+			// for creator, 50% of stake should be slashed (250), 10% of which should go to reporter
+			// (25).
+			assert_eq!(Balances::free_balance(slash_reporter), 115 + 25);
+		});
+	}
+
+	fn create_pool(creator: AccountId, amount: Balance) -> u32 {
+		fund(&creator, amount * 2);
+		assert_ok!(Pools::create(
+			RawOrigin::Signed(creator).into(),
+			amount,
+			creator,
+			creator,
+			creator
+		));
+
+		pallet_nomination_pools::LastPoolId::<T>::get()
+	}
+
+	fn add_delegators_to_pool(pool_id: u32, delegators: Vec<AccountId>, amount: Balance) {
+		for delegator in delegators {
+			fund(&delegator, amount * 2);
+			assert_ok!(Pools::join(RawOrigin::Signed(delegator).into(), amount, pool_id));
+		}
+	}
+
+	fn get_pool_agent(pool_id: u32) -> Agent<T> {
+		get_agent(&Pools::generate_bonded_account(pool_id))
+	}
+}
diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs
index 0bfc23281dfe6eb3648418610a1184babdd3605d..958d81c294aa914858601f51a6adaf5cc978d5da 100644
--- a/substrate/frame/delegated-staking/src/types.rs
+++ b/substrate/frame/delegated-staking/src/types.rs
@@ -279,7 +279,6 @@ impl<T: Config> Agent<T> {
 	/// This is similar to [Self::available_to_bond] except it also includes `unclaimed_withdrawals`
 	/// of `Agent`.
 	#[cfg(test)]
-	#[allow(unused)]
 	pub(crate) fn total_unbonded(&self) -> BalanceOf<T> {
 		let bonded_stake = self.bonded_stake();
 
diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
index c00bb66ea13044f8b11aa781e30896e03225aea8..2b1f1335c6fe888c3ff756b00327b74d5ba9f279 100644
--- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
+++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs
@@ -322,7 +322,7 @@ fn automatic_unbonding_pools() {
 		let init_free_balance_2 = Balances::free_balance(2);
 		let init_free_balance_3 = Balances::free_balance(3);
 
-		let pool_bonded_account = Pools::create_bonded_account(1);
+		let pool_bonded_account = Pools::generate_bonded_account(1);
 
 		// creates a pool with 5 bonded, owned by 1.
 		assert_ok!(Pools::create(RuntimeOrigin::signed(1), 5, 1, 1, 1));
diff --git a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
index 8f1775a7e5951ad1c9271f486a782b18fe5da900..a9512bef2d50befdde41bc9dc5c48720bfdc4240 100644
--- a/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
+++ b/substrate/frame/election-provider-multi-phase/test-staking-e2e/src/mock.rs
@@ -274,7 +274,7 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = sp_runtime::FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = Staking;
+	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
 	type PostUnbondingPoolsWindow = ConstU32<2>;
 	type PalletId = PoolsPalletId;
 	type MaxMetadataLen = ConstU32<256>;
diff --git a/substrate/frame/nomination-pools/benchmarking/Cargo.toml b/substrate/frame/nomination-pools/benchmarking/Cargo.toml
index 3186bce5164cc9fa57c2601a1219b5aadfca4e50..3f9463a9c429b93da7cf5945a6ebd2ac46077196 100644
--- a/substrate/frame/nomination-pools/benchmarking/Cargo.toml
+++ b/substrate/frame/nomination-pools/benchmarking/Cargo.toml
@@ -27,6 +27,7 @@ frame-support = { path = "../../support", default-features = false }
 frame-system = { path = "../../system", default-features = false }
 pallet-bags-list = { path = "../../bags-list", default-features = false }
 pallet-staking = { path = "../../staking", default-features = false }
+pallet-delegated-staking = { path = "../../delegated-staking", default-features = false }
 pallet-nomination-pools = { path = "..", default-features = false }
 
 # Substrate Primitives
@@ -53,6 +54,7 @@ std = [
 	"frame-system/std",
 	"pallet-bags-list/std",
 	"pallet-balances/std",
+	"pallet-delegated-staking/std",
 	"pallet-nomination-pools/std",
 	"pallet-staking/std",
 	"pallet-timestamp/std",
@@ -72,6 +74,7 @@ runtime-benchmarks = [
 	"frame-system/runtime-benchmarks",
 	"pallet-bags-list/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
+	"pallet-delegated-staking/runtime-benchmarks",
 	"pallet-nomination-pools/runtime-benchmarks",
 	"pallet-staking/runtime-benchmarks",
 	"pallet-timestamp/runtime-benchmarks",
diff --git a/substrate/frame/nomination-pools/benchmarking/src/inner.rs b/substrate/frame/nomination-pools/benchmarking/src/inner.rs
index 277060e7f640f962bd112a6f337339ec1cf2559e..43de0fddb8b5fd574f26464d8332507ff96a1575 100644
--- a/substrate/frame/nomination-pools/benchmarking/src/inner.rs
+++ b/substrate/frame/nomination-pools/benchmarking/src/inner.rs
@@ -23,22 +23,24 @@ use frame_support::{
 	assert_ok, ensure,
 	traits::{
 		fungible::{Inspect, Mutate, Unbalanced},
-		Get,
+		tokens::Preservation,
+		Get, Imbalance,
 	},
 };
 use frame_system::RawOrigin as RuntimeOrigin;
 use pallet_nomination_pools::{
+	adapter::{StakeStrategy, StakeStrategyType},
 	BalanceOf, BondExtra, BondedPoolInner, BondedPools, ClaimPermission, ClaimPermissions,
 	Commission, CommissionChangeRate, CommissionClaimPermission, ConfigOp, GlobalMaxCommission,
 	MaxPoolMembers, MaxPoolMembersPerPool, MaxPools, Metadata, MinCreateBond, MinJoinBond,
-	Pallet as Pools, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
+	Pallet as Pools, PoolId, PoolMembers, PoolRoles, PoolState, RewardPools, SubPoolsStorage,
 };
 use pallet_staking::MaxNominationsOf;
 use sp_runtime::{
 	traits::{Bounded, StaticLookup, Zero},
 	Perbill,
 };
-use sp_staking::{EraIndex, StakingInterface};
+use sp_staking::EraIndex;
 use sp_std::{vec, vec::Vec};
 // `frame_benchmarking::benchmarks!` macro needs this
 use pallet_nomination_pools::Call;
@@ -101,18 +103,46 @@ fn create_pool_account<T: pallet_nomination_pools::Config>(
 
 	let pool_account = pallet_nomination_pools::BondedPools::<T>::iter()
 		.find(|(_, bonded_pool)| bonded_pool.roles.depositor == pool_creator)
-		.map(|(pool_id, _)| Pools::<T>::create_bonded_account(pool_id))
+		.map(|(pool_id, _)| Pools::<T>::generate_bonded_account(pool_id))
 		.expect("pool_creator created a pool above");
 
 	(pool_creator, pool_account)
 }
 
+fn migrate_to_transfer_stake<T: Config>(pool_id: PoolId) {
+	if T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer {
+		// should already be in the correct strategy
+		return;
+	}
+	let pool_acc = Pools::<T>::generate_bonded_account(pool_id);
+	// drop the agent and its associated delegators .
+	T::StakeAdapter::remove_as_agent(&pool_acc);
+
+	// tranfer funds from all members to the pool account.
+	PoolMembers::<T>::iter()
+		.filter(|(_, member)| member.pool_id == pool_id)
+		.for_each(|(member_acc, member)| {
+			let member_balance = member.total_balance();
+			<T as pallet_nomination_pools::Config>::Currency::transfer(
+				&member_acc,
+				&pool_acc,
+				member_balance,
+				Preservation::Preserve,
+			)
+			.expect("member should have enough balance to transfer");
+		});
+}
+
 fn vote_to_balance<T: pallet_nomination_pools::Config>(
 	vote: u64,
 ) -> Result<BalanceOf<T>, &'static str> {
 	vote.try_into().map_err(|_| "could not convert u64 to Balance")
 }
 
+fn is_transfer_stake_strategy<T: pallet_nomination_pools::Config>() -> bool {
+	T::StakeAdapter::strategy_type() == StakeStrategyType::Transfer
+}
+
 #[allow(unused)]
 struct ListScenario<T: pallet_nomination_pools::Config> {
 	/// Stash/Controller that is expected to be moved.
@@ -151,7 +181,7 @@ impl<T: Config> ListScenario<T> {
 		let (pool_creator1, pool_origin1) =
 			create_pool_account::<T>(USER_SEED + 1, origin_weight, Some(Perbill::from_percent(50)));
 
-		T::Staking::nominate(
+		T::StakeAdapter::nominate(
 			&pool_origin1,
 			// NOTE: these don't really need to be validators.
 			vec![account("random_validator", 0, USER_SEED)],
@@ -160,7 +190,7 @@ impl<T: Config> ListScenario<T> {
 		let (_, pool_origin2) =
 			create_pool_account::<T>(USER_SEED + 2, origin_weight, Some(Perbill::from_percent(50)));
 
-		T::Staking::nominate(
+		T::StakeAdapter::nominate(
 			&pool_origin2,
 			vec![account("random_validator", 0, USER_SEED)].clone(),
 		)?;
@@ -178,7 +208,7 @@ impl<T: Config> ListScenario<T> {
 		let (_, pool_dest1) =
 			create_pool_account::<T>(USER_SEED + 3, dest_weight, Some(Perbill::from_percent(50)));
 
-		T::Staking::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
+		T::StakeAdapter::nominate(&pool_dest1, vec![account("random_validator", 0, USER_SEED)])?;
 
 		let weight_of = pallet_staking::Pallet::<T>::weight_of_fn();
 		assert_eq!(vote_to_balance::<T>(weight_of(&pool_origin1)).unwrap(), origin_weight);
@@ -204,11 +234,12 @@ impl<T: Config> ListScenario<T> {
 		self.origin1_member = Some(joiner.clone());
 		CurrencyOf::<T>::set_balance(&joiner, amount * 2u32.into());
 
-		let original_bonded = T::Staking::active_stake(&self.origin1).unwrap();
+		let original_bonded = T::StakeAdapter::active_stake(&self.origin1);
 
 		// Unbond `amount` from the underlying pool account so when the member joins
 		// we will maintain `current_bonded`.
-		T::Staking::unbond(&self.origin1, amount).expect("the pool was created in `Self::new`.");
+		T::StakeAdapter::unbond(&self.origin1, amount)
+			.expect("the pool was created in `Self::new`.");
 
 		// Account pool points for the unbonded balance.
 		BondedPools::<T>::mutate(&1, |maybe_pool| {
@@ -231,13 +262,20 @@ impl<T: Config> ListScenario<T> {
 }
 
 frame_benchmarking::benchmarks! {
+	where_clause {
+		where
+			T: pallet_staking::Config,
+			pallet_staking::BalanceOf<T>: From<u128>,
+			BalanceOf<T>: Into<u128>,
+	}
+
 	join {
 		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
 
 		// setup the worst case list scenario.
 		let scenario = ListScenario::<T>::new(origin_weight, true)?;
 		assert_eq!(
-			T::Staking::active_stake(&scenario.origin1).unwrap(),
+			T::StakeAdapter::active_stake(&scenario.origin1),
 			origin_weight
 		);
 
@@ -252,7 +290,7 @@ frame_benchmarking::benchmarks! {
 	verify {
 		assert_eq!(CurrencyOf::<T>::balance(&joiner), joiner_free - max_additional);
 		assert_eq!(
-			T::Staking::active_stake(&scenario.origin1).unwrap(),
+			T::StakeAdapter::active_stake(&scenario.origin1),
 			scenario.dest_weight
 		);
 	}
@@ -267,7 +305,7 @@ frame_benchmarking::benchmarks! {
 	}: bond_extra(RuntimeOrigin::Signed(scenario.creator1.clone()), BondExtra::FreeBalance(extra))
 	verify {
 		assert!(
-			T::Staking::active_stake(&scenario.origin1).unwrap() >=
+			T::StakeAdapter::active_stake(&scenario.origin1) >=
 			scenario.dest_weight
 		);
 	}
@@ -283,7 +321,7 @@ frame_benchmarking::benchmarks! {
 		let _ = Pools::<T>::set_claim_permission(RuntimeOrigin::Signed(scenario.creator1.clone()).into(), ClaimPermission::PermissionlessAll);
 
 		// transfer exactly `extra` to the depositor of the src pool (1),
-		let reward_account1 = Pools::<T>::create_reward_account(1);
+		let reward_account1 = Pools::<T>::generate_reward_account(1);
 		assert!(extra >= CurrencyOf::<T>::minimum_balance());
 		let _ = CurrencyOf::<T>::mint_into(&reward_account1, extra);
 
@@ -291,7 +329,7 @@ frame_benchmarking::benchmarks! {
 	verify {
 		 // commission of 50% deducted here.
 		assert!(
-			T::Staking::active_stake(&scenario.origin1).unwrap() >=
+			T::StakeAdapter::active_stake(&scenario.origin1) >=
 			scenario.dest_weight / 2u32.into()
 		);
 	}
@@ -302,7 +340,7 @@ frame_benchmarking::benchmarks! {
 		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
 		let ed = CurrencyOf::<T>::minimum_balance();
 		let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
-		let reward_account = Pools::<T>::create_reward_account(1);
+		let reward_account = Pools::<T>::generate_reward_account(1);
 
 		// Send funds to the reward account of the pool
 		CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
@@ -345,7 +383,7 @@ frame_benchmarking::benchmarks! {
 		whitelist_account!(member_id);
 	}: _(RuntimeOrigin::Signed(member_id.clone()), member_id_lookup, all_points)
 	verify {
-		let bonded_after = T::Staking::active_stake(&scenario.origin1).unwrap();
+		let bonded_after = T::StakeAdapter::active_stake(&scenario.origin1);
 		// We at least went down to the destination bag
 		assert!(bonded_after <= scenario.dest_weight);
 		let member = PoolMembers::<T>::get(
@@ -354,7 +392,7 @@ frame_benchmarking::benchmarks! {
 		.unwrap();
 		assert_eq!(
 			member.unbonding_eras.keys().cloned().collect::<Vec<_>>(),
-			vec![0 + T::Staking::bonding_duration()]
+			vec![0 + T::StakeAdapter::bonding_duration()]
 		);
 		assert_eq!(
 			member.unbonding_eras.values().cloned().collect::<Vec<_>>(),
@@ -376,7 +414,7 @@ frame_benchmarking::benchmarks! {
 
 		// Sanity check join worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			min_create_bond + min_join_bond
 		);
 		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
@@ -386,7 +424,7 @@ frame_benchmarking::benchmarks! {
 
 		// Sanity check that unbond worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			min_create_bond
 		);
 		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
@@ -419,7 +457,7 @@ frame_benchmarking::benchmarks! {
 
 		// Sanity check join worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			min_create_bond + min_join_bond
 		);
 		assert_eq!(CurrencyOf::<T>::balance(&joiner), min_join_bond);
@@ -430,7 +468,7 @@ frame_benchmarking::benchmarks! {
 
 		// Sanity check that unbond worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			min_create_bond
 		);
 		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
@@ -470,17 +508,17 @@ frame_benchmarking::benchmarks! {
 		// here to ensure the complete flow for destroying a pool works - the reward pool account
 		// should never exist by time the depositor withdraws so we test that it gets cleaned
 		// up when unbonding.
-		let reward_account = Pools::<T>::create_reward_account(1);
+		let reward_account = Pools::<T>::generate_reward_account(1);
 		assert!(frame_system::Account::<T>::contains_key(&reward_account));
 		Pools::<T>::fully_unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor.clone()).unwrap();
 
 		// Sanity check that unbond worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			Zero::zero()
 		);
 		assert_eq!(
-			CurrencyOf::<T>::balance(&pool_account),
+			T::StakeAdapter::total_balance(&pool_account),
 			min_create_bond
 		);
 		assert_eq!(pallet_staking::Ledger::<T>::get(&pool_account).unwrap().unlocking.len(), 1);
@@ -522,8 +560,8 @@ frame_benchmarking::benchmarks! {
 		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
 
 		// Give the depositor some balance to bond
-		CurrencyOf::<T>::set_balance(&depositor, min_create_bond * 2u32.into());
-
+		// it needs to transfer min balance to reward account as well so give additional min balance.
+		CurrencyOf::<T>::set_balance(&depositor, min_create_bond + CurrencyOf::<T>::minimum_balance() * 2u32.into());
 		// Make sure no Pools exist at a pre-condition for our verify checks
 		assert_eq!(RewardPools::<T>::count(), 0);
 		assert_eq!(BondedPools::<T>::count(), 0);
@@ -556,8 +594,8 @@ frame_benchmarking::benchmarks! {
 			}
 		);
 		assert_eq!(
-			T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
-			Ok(min_create_bond)
+			T::StakeAdapter::active_stake(&Pools::<T>::generate_bonded_account(1)),
+			min_create_bond
 		);
 	}
 
@@ -596,8 +634,8 @@ frame_benchmarking::benchmarks! {
 			}
 		);
 		assert_eq!(
-			T::Staking::active_stake(&Pools::<T>::create_bonded_account(1)),
-			Ok(min_create_bond)
+			T::StakeAdapter::active_stake(&Pools::<T>::generate_bonded_account(1)),
+			min_create_bond
 		);
 	}
 
@@ -681,13 +719,13 @@ frame_benchmarking::benchmarks! {
 			.map(|i| account("stash", USER_SEED, i))
 			.collect();
 
-		assert_ok!(T::Staking::nominate(&pool_account, validators));
-		assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());
+		assert_ok!(T::StakeAdapter::nominate(&pool_account, validators));
+		assert!(T::StakeAdapter::nominations(&Pools::<T>::generate_bonded_account(1)).is_some());
 
 		whitelist_account!(depositor);
 	}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
 	verify {
-		assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
+		assert!(T::StakeAdapter::nominations(&Pools::<T>::generate_bonded_account(1)).is_none());
 	}
 
 	set_commission {
@@ -786,7 +824,7 @@ frame_benchmarking::benchmarks! {
 
 		// Sanity check join worked
 		assert_eq!(
-			T::Staking::active_stake(&pool_account).unwrap(),
+			T::StakeAdapter::active_stake(&pool_account),
 			min_create_bond + min_join_bond
 		);
 	}:_(RuntimeOrigin::Signed(joiner.clone()), ClaimPermission::Permissioned)
@@ -800,7 +838,7 @@ frame_benchmarking::benchmarks! {
 		let origin_weight = Pools::<T>::depositor_min_bond() * 2u32.into();
 		let ed = CurrencyOf::<T>::minimum_balance();
 		let (depositor, pool_account) = create_pool_account::<T>(0, origin_weight, Some(commission));
-		let reward_account = Pools::<T>::create_reward_account(1);
+		let reward_account = Pools::<T>::generate_reward_account(1);
 		CurrencyOf::<T>::set_balance(&reward_account, ed + origin_weight);
 
 		// member claims a payout to make some commission available.
@@ -829,7 +867,7 @@ frame_benchmarking::benchmarks! {
 		let (depositor, _) = create_pool_account::<T>(0, Pools::<T>::depositor_min_bond() * 2u32.into(), None);
 
 		// Remove ed freeze to create a scenario where the ed deposit needs to be adjusted.
-		let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::create_reward_account(1));
+		let _ = Pools::<T>::unfreeze_pool_deposit(&Pools::<T>::generate_reward_account(1));
 		assert!(&Pools::<T>::check_ed_imbalance().is_err());
 
 		whitelist_account!(depositor);
@@ -838,6 +876,147 @@ frame_benchmarking::benchmarks! {
 		assert!(&Pools::<T>::check_ed_imbalance().is_ok());
 	}
 
+	apply_slash {
+		// Note: With older `TransferStake` strategy, slashing is greedy and apply_slash should
+		// always fail.
+
+		// We want to fill member's unbonding pools. So let's bond with big enough amount.
+		let deposit_amount = Pools::<T>::depositor_min_bond() * T::MaxUnbonding::get().into() * 4u32.into();
+		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
+		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
+
+		// verify user balance in the pool.
+		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
+		// verify delegated balance.
+		assert!(is_transfer_stake_strategy::<T>() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount);
+
+		// ugly type conversion between balances of pallet staking and pools (which really are same
+		// type). Maybe there is a better way?
+		let slash_amount: u128 = deposit_amount.into()/2;
+
+		// slash pool by half
+		pallet_staking::slashing::do_slash::<T>(
+			&pool_account,
+			slash_amount.into(),
+			&mut pallet_staking::BalanceOf::<T>::zero(),
+			&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
+			EraIndex::zero()
+		);
+
+		// verify user balance is slashed in the pool.
+		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount/2u32.into());
+		// verify delegated balance are not yet slashed.
+		assert!(is_transfer_stake_strategy::<T>() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount);
+
+		// Fill member's sub pools for the worst case.
+		for i in 1..(T::MaxUnbonding::get() + 1) {
+			pallet_staking::CurrentEra::<T>::put(i);
+			assert!(Pools::<T>::unbond(RuntimeOrigin::Signed(depositor.clone()).into(), depositor_lookup.clone(), Pools::<T>::depositor_min_bond()).is_ok());
+		}
+
+		pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 2);
+
+		let slash_reporter = create_funded_user_with_balance::<T>("slasher", 0, CurrencyOf::<T>::minimum_balance());
+		whitelist_account!(depositor);
+	}:
+	{
+		let res = Pools::<T>::apply_slash(RuntimeOrigin::Signed(slash_reporter.clone()).into(), depositor_lookup.clone());
+		// for transfer stake strategy, apply slash would error, otherwise success.
+		assert!(is_transfer_stake_strategy::<T>() ^ res.is_ok());
+	}
+	verify {
+		// verify balances are correct and slash applied.
+		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount/2u32.into());
+		assert!(is_transfer_stake_strategy::<T>() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount/2u32.into());
+	}
+
+	apply_slash_fail {
+		// Bench the scenario where pool has some unapplied slash but the member does not have any
+		// slash to be applied.
+		let deposit_amount = Pools::<T>::depositor_min_bond() * 10u32.into();
+		// Create pool.
+		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
+
+		// slash pool by half
+		let slash_amount: u128 = deposit_amount.into()/2;
+		pallet_staking::slashing::do_slash::<T>(
+			&pool_account,
+			slash_amount.into(),
+			&mut pallet_staking::BalanceOf::<T>::zero(),
+			&mut pallet_staking::NegativeImbalanceOf::<T>::zero(),
+			EraIndex::zero()
+		);
+
+		pallet_staking::CurrentEra::<T>::put(1);
+
+		// new member joins the pool who should not be affected by slash.
+		let min_join_bond = MinJoinBond::<T>::get().max(CurrencyOf::<T>::minimum_balance());
+		let join_amount = min_join_bond * T::MaxUnbonding::get().into() * 2u32.into();
+		let joiner = create_funded_user_with_balance::<T>("joiner", 0, join_amount * 2u32.into());
+		let joiner_lookup = T::Lookup::unlookup(joiner.clone());
+		assert!(Pools::<T>::join(RuntimeOrigin::Signed(joiner.clone()).into(), join_amount, 1).is_ok());
+
+		// Fill member's sub pools for the worst case.
+		for i in 0..T::MaxUnbonding::get() {
+			pallet_staking::CurrentEra::<T>::put(i + 2); // +2 because we already set the current era to 1.
+			assert!(Pools::<T>::unbond(RuntimeOrigin::Signed(joiner.clone()).into(), joiner_lookup.clone(), min_join_bond).is_ok());
+		}
+
+		pallet_staking::CurrentEra::<T>::put(T::MaxUnbonding::get() + 3);
+		whitelist_account!(joiner);
+
+	}: {
+		// Since the StakeAdapter can be different based on the runtime config, the errors could be different as well.
+		assert!(Pools::<T>::apply_slash(RuntimeOrigin::Signed(joiner.clone()).into(), joiner_lookup.clone()).is_err());
+	}
+
+
+	pool_migrate {
+		// create a pool.
+		let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
+		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
+
+		// migrate pool to transfer stake.
+		let _ = migrate_to_transfer_stake::<T>(1);
+	}: {
+		// Try migrate to `DelegateStake`. Would succeed only if `DelegateStake` strategy is used.
+		let res = Pools::<T>::migrate_pool_to_delegate_stake(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into());
+		assert!(is_transfer_stake_strategy::<T>() ^ res.is_ok());
+	}
+	verify {
+		// this queries agent balance if `DelegateStake` strategy.
+		assert!(T::StakeAdapter::total_balance(&pool_account) == deposit_amount);
+	}
+
+	migrate_delegation {
+		// create a pool.
+		let deposit_amount = Pools::<T>::depositor_min_bond() * 2u32.into();
+		let (depositor, pool_account) = create_pool_account::<T>(0, deposit_amount, None);
+		let depositor_lookup = T::Lookup::unlookup(depositor.clone());
+
+		// migrate pool to transfer stake.
+		let _ = migrate_to_transfer_stake::<T>(1);
+
+		// Now migrate pool to delegate stake keeping delegators unmigrated.
+		let migration_res = Pools::<T>::migrate_pool_to_delegate_stake(RuntimeOrigin::Signed(depositor.clone()).into(), 1u32.into());
+		assert!(is_transfer_stake_strategy::<T>() ^ migration_res.is_ok());
+
+		// verify balances that we will check again later.
+		assert!(T::StakeAdapter::member_delegation_balance(&depositor) == Zero::zero());
+		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
+
+		whitelist_account!(depositor);
+	}: {
+		let res = Pools::<T>::migrate_delegation(RuntimeOrigin::Signed(depositor.clone()).into(), depositor_lookup.clone());
+		// for transfer stake strategy, apply slash would error, otherwise success.
+		assert!(is_transfer_stake_strategy::<T>() ^ res.is_ok());
+	}
+	verify {
+		// verify balances once more.
+		assert!(is_transfer_stake_strategy::<T>() || T::StakeAdapter::member_delegation_balance(&depositor) == deposit_amount);
+		assert_eq!(PoolMembers::<T>::get(&depositor).unwrap().total_balance(), deposit_amount);
+	}
+
 	impl_benchmark_test_suite!(
 		Pallet,
 		crate::mock::new_test_ext(),
diff --git a/substrate/frame/nomination-pools/benchmarking/src/lib.rs b/substrate/frame/nomination-pools/benchmarking/src/lib.rs
index 45e8f1f27e99a52b5900aa5e18c5439d9d7baa5c..910cdf2e3dff6353d90a2ba138c4731b0b020a0f 100644
--- a/substrate/frame/nomination-pools/benchmarking/src/lib.rs
+++ b/substrate/frame/nomination-pools/benchmarking/src/lib.rs
@@ -18,6 +18,7 @@
 //! Benchmarks for the nomination pools coupled with the staking and bags list pallets.
 
 #![cfg_attr(not(feature = "std"), no_std)]
+#![recursion_limit = "256"]
 
 #[cfg(feature = "runtime-benchmarks")]
 pub mod inner;
diff --git a/substrate/frame/nomination-pools/benchmarking/src/mock.rs b/substrate/frame/nomination-pools/benchmarking/src/mock.rs
index 2752d53a6b9f3364c234d9144a6c40b10b449a5b..def98b4d2945e20c6c3475383e8e526f19b8110d 100644
--- a/substrate/frame/nomination-pools/benchmarking/src/mock.rs
+++ b/substrate/frame/nomination-pools/benchmarking/src/mock.rs
@@ -77,7 +77,7 @@ impl pallet_balances::Config for Runtime {
 	type WeightInfo = ();
 	type FreezeIdentifier = RuntimeFreezeReason;
 	type MaxFreezes = ConstU32<1>;
-	type RuntimeHoldReason = ();
+	type RuntimeHoldReason = RuntimeHoldReason;
 	type RuntimeFreezeReason = ();
 }
 
@@ -120,7 +120,7 @@ impl pallet_staking::Config for Runtime {
 	type MaxControllersInDeprecationBatch = ConstU32<100>;
 	type MaxUnlockingChunks = ConstU32<32>;
 	type HistoryDepth = ConstU32<84>;
-	type EventListeners = Pools;
+	type EventListeners = (Pools, DelegatedStaking);
 	type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
 	type WeightInfo = ();
 	type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
@@ -166,7 +166,8 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = Staking;
+	type StakeAdapter =
+		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
 	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
 	type MaxMetadataLen = ConstU32<256>;
 	type MaxUnbonding = ConstU32<8>;
@@ -175,6 +176,20 @@ impl pallet_nomination_pools::Config for Runtime {
 	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
 }
 
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
+}
+impl pallet_delegated_staking::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = DelegatedStakingPalletId;
+	type Currency = Balances;
+	type OnSlash = ();
+	type SlashRewardFraction = SlashRewardFraction;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type CoreStaking = Staking;
+}
+
 impl crate::Config for Runtime {}
 
 type Block = frame_system::mocking::MockBlock<Runtime>;
@@ -187,6 +202,7 @@ frame_support::construct_runtime!(
 		Staking: pallet_staking,
 		VoterList: pallet_bags_list::<Instance1>,
 		Pools: pallet_nomination_pools,
+		DelegatedStaking: pallet_delegated_staking,
 	}
 );
 
diff --git a/substrate/frame/nomination-pools/fuzzer/src/call.rs b/substrate/frame/nomination-pools/fuzzer/src/call.rs
index 027fb2b69138c64f8903f99737187b7cd65c12c2..9e10d87da6750e4171d2736eb6fb7dcbc7324640 100644
--- a/substrate/frame/nomination-pools/fuzzer/src/call.rs
+++ b/substrate/frame/nomination-pools/fuzzer/src/call.rs
@@ -306,7 +306,7 @@ fn main() {
 					BondedPools::<T>::iter().for_each(|(id, _)| {
 						let amount = random_ed_multiple(&mut rng);
 						let _ =
-							Balances::deposit_creating(&Pools::create_reward_account(id), amount);
+							Balances::deposit_creating(&Pools::generate_reward_account(id), amount);
 						// if we just paid out the reward agent, let's calculate how much we expect
 						// our reward agent to have earned.
 						if reward_agent.pool_id.map_or(false, |mid| mid == id) {
diff --git a/substrate/frame/nomination-pools/src/adapter.rs b/substrate/frame/nomination-pools/src/adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..caf4671191d8896a17c130b5e6ffeafc42a19c20
--- /dev/null
+++ b/substrate/frame/nomination-pools/src/adapter.rs
@@ -0,0 +1,389 @@
+// 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 crate::*;
+use sp_staking::{DelegationInterface, DelegationMigrator};
+
+/// Types of stake strategies.
+///
+/// Useful for determining current staking strategy of a runtime and enforce integrity tests.
+#[derive(Encode, Decode, MaxEncodedLen, TypeInfo, RuntimeDebugNoBound, PartialEq)]
+pub enum StakeStrategyType {
+	/// Member funds are transferred to pool account and staked.
+	///
+	/// This is the older staking strategy used by pools. For a new runtime, it is recommended to
+	/// use [`StakeStrategyType::Delegate`] strategy instead.
+	Transfer,
+	/// Member funds are delegated to pool account and staked.
+	Delegate,
+}
+
+/// An adapter trait that can support multiple staking strategies.
+///
+/// Depending on which staking strategy we want to use, the staking logic can be slightly
+/// different. Refer the two possible strategies currently: [`TransferStake`] and
+/// [`DelegateStake`] for more detail.
+pub trait StakeStrategy {
+	type Balance: frame_support::traits::tokens::Balance;
+	type AccountId: Clone + sp_std::fmt::Debug;
+	type CoreStaking: StakingInterface<Balance = Self::Balance, AccountId = Self::AccountId>;
+
+	/// The type of staking strategy of the current adapter.
+	fn strategy_type() -> StakeStrategyType;
+
+	/// See [`StakingInterface::bonding_duration`].
+	fn bonding_duration() -> EraIndex {
+		Self::CoreStaking::bonding_duration()
+	}
+
+	/// See [`StakingInterface::current_era`].
+	fn current_era() -> EraIndex {
+		Self::CoreStaking::current_era()
+	}
+
+	/// See [`StakingInterface::minimum_nominator_bond`].
+	fn minimum_nominator_bond() -> Self::Balance {
+		Self::CoreStaking::minimum_nominator_bond()
+	}
+
+	/// Balance that can be transferred from pool account to member.
+	///
+	/// This is part of the pool balance that is not actively staked. That is, tokens that are
+	/// in unbonding period or unbonded.
+	fn transferable_balance(pool_account: &Self::AccountId) -> Self::Balance;
+
+	/// Total balance of the pool including amount that is actively staked.
+	fn total_balance(pool_account: &Self::AccountId) -> Self::Balance;
+
+	/// Amount of tokens delegated by the member.
+	fn member_delegation_balance(member_account: &Self::AccountId) -> Self::Balance;
+
+	/// See [`StakingInterface::active_stake`].
+	fn active_stake(pool_account: &Self::AccountId) -> Self::Balance {
+		Self::CoreStaking::active_stake(pool_account).unwrap_or_default()
+	}
+
+	/// See [`StakingInterface::total_stake`].
+	fn total_stake(pool_account: &Self::AccountId) -> Self::Balance {
+		Self::CoreStaking::total_stake(pool_account).unwrap_or_default()
+	}
+
+	/// Which strategy the pool account is using.
+	///
+	/// This can be different from the [`Self::strategy_type`] of the adapter if the pool has not
+	/// migrated to the new strategy yet.
+	fn pool_strategy(pool_account: &Self::AccountId) -> StakeStrategyType {
+		match Self::CoreStaking::is_virtual_staker(pool_account) {
+			true => StakeStrategyType::Delegate,
+			false => StakeStrategyType::Transfer,
+		}
+	}
+
+	/// See [`StakingInterface::nominate`].
+	fn nominate(
+		pool_account: &Self::AccountId,
+		validators: Vec<Self::AccountId>,
+	) -> DispatchResult {
+		Self::CoreStaking::nominate(pool_account, validators)
+	}
+
+	/// See [`StakingInterface::chill`].
+	fn chill(pool_account: &Self::AccountId) -> DispatchResult {
+		Self::CoreStaking::chill(pool_account)
+	}
+
+	/// Pledge `amount` towards `pool_account` and update the pool bond. Also see
+	/// [`StakingInterface::bond`].
+	fn pledge_bond(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: Self::Balance,
+		bond_type: BondType,
+	) -> DispatchResult;
+
+	/// See [`StakingInterface::unbond`].
+	fn unbond(pool_account: &Self::AccountId, amount: Self::Balance) -> DispatchResult {
+		Self::CoreStaking::unbond(pool_account, amount)
+	}
+
+	/// See [`StakingInterface::withdraw_unbonded`].
+	fn withdraw_unbonded(
+		pool_account: &Self::AccountId,
+		num_slashing_spans: u32,
+	) -> Result<bool, DispatchError> {
+		Self::CoreStaking::withdraw_unbonded(pool_account.clone(), num_slashing_spans)
+	}
+
+	/// Withdraw funds from pool account to member account.
+	fn member_withdraw(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		amount: Self::Balance,
+		num_slashing_spans: u32,
+	) -> DispatchResult;
+
+	/// Check if there is any pending slash for the pool.
+	fn has_pending_slash(pool_account: &Self::AccountId) -> bool;
+
+	/// Slash the member account with `amount` against pending slashes for the pool.
+	fn member_slash(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		amount: Self::Balance,
+		maybe_reporter: Option<Self::AccountId>,
+	) -> DispatchResult;
+
+	/// Migrate pool account from being a direct nominator to a delegated agent.
+	///
+	/// This is useful for migrating a pool account from [`StakeStrategyType::Transfer`] to
+	/// [`StakeStrategyType::Delegate`].
+	fn migrate_nominator_to_agent(
+		pool_account: &Self::AccountId,
+		reward_account: &Self::AccountId,
+	) -> DispatchResult;
+
+	/// Migrate member balance from pool account to member account.
+	///
+	/// This is useful for a pool account that migrated from [`StakeStrategyType::Transfer`] to
+	/// [`StakeStrategyType::Delegate`]. Its members can then migrate their delegated balance
+	/// back to their account.
+	///
+	/// Internally, the member funds that are locked in the pool account are transferred back and
+	/// locked in the member account.
+	fn migrate_delegation(
+		pool: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+	) -> DispatchResult;
+
+	/// List of validators nominated by the pool account.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn nominations(pool_account: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
+		Self::CoreStaking::nominations(pool_account)
+	}
+
+	/// Remove the pool account as agent.
+	///
+	/// Useful for migrating pool account from a delegated agent to a direct nominator. Only used
+	/// in tests and benchmarks.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn remove_as_agent(_pool: &Self::AccountId) {
+		// noop by default
+	}
+}
+
+/// A staking strategy implementation that supports transfer based staking.
+///
+/// In order to stake, this adapter transfers the funds from the member/delegator account to the
+/// pool account and stakes through the pool account on `Staking`.
+///
+/// This is the older Staking strategy used by pools. To switch to the newer [`DelegateStake`]
+/// strategy in an existing runtime, storage migration is required. See
+/// [`migration::unversioned::DelegationStakeMigration`]. For new runtimes, it is highly recommended
+/// to use the [`DelegateStake`] strategy.
+pub struct TransferStake<T: Config, Staking: StakingInterface>(PhantomData<(T, Staking)>);
+
+impl<T: Config, Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>>
+	StakeStrategy for TransferStake<T, Staking>
+{
+	type Balance = BalanceOf<T>;
+	type AccountId = T::AccountId;
+	type CoreStaking = Staking;
+
+	fn strategy_type() -> StakeStrategyType {
+		StakeStrategyType::Transfer
+	}
+
+	fn transferable_balance(pool_account: &Self::AccountId) -> BalanceOf<T> {
+		T::Currency::balance(pool_account).saturating_sub(Self::active_stake(pool_account))
+	}
+
+	fn total_balance(pool_account: &Self::AccountId) -> BalanceOf<T> {
+		T::Currency::total_balance(pool_account)
+	}
+
+	fn member_delegation_balance(_member_account: &T::AccountId) -> Staking::Balance {
+		// for transfer stake, delegation balance is always zero.
+		Zero::zero()
+	}
+
+	fn pledge_bond(
+		who: &T::AccountId,
+		pool_account: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: BalanceOf<T>,
+		bond_type: BondType,
+	) -> DispatchResult {
+		match bond_type {
+			BondType::Create => {
+				// first bond
+				T::Currency::transfer(who, pool_account, amount, Preservation::Expendable)?;
+				Staking::bond(pool_account, amount, &reward_account)
+			},
+			BondType::Extra => {
+				// additional bond
+				T::Currency::transfer(who, pool_account, amount, Preservation::Preserve)?;
+				Staking::bond_extra(pool_account, amount)
+			},
+		}
+	}
+
+	fn member_withdraw(
+		who: &T::AccountId,
+		pool_account: &Self::AccountId,
+		amount: BalanceOf<T>,
+		_num_slashing_spans: u32,
+	) -> DispatchResult {
+		T::Currency::transfer(pool_account, &who, amount, Preservation::Expendable)?;
+
+		Ok(())
+	}
+
+	fn has_pending_slash(_: &Self::AccountId) -> bool {
+		// for transfer stake strategy, slashing is greedy and never deferred.
+		false
+	}
+
+	fn member_slash(
+		_who: &T::AccountId,
+		_pool: &Self::AccountId,
+		_amount: Staking::Balance,
+		_maybe_reporter: Option<T::AccountId>,
+	) -> DispatchResult {
+		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
+	}
+
+	fn migrate_nominator_to_agent(
+		_pool: &Self::AccountId,
+		_reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
+	}
+
+	fn migrate_delegation(
+		_pool: &Self::AccountId,
+		_delegator: &Self::AccountId,
+		_value: Self::Balance,
+	) -> DispatchResult {
+		Err(Error::<T>::Defensive(DefensiveError::DelegationUnsupported).into())
+	}
+}
+
+/// A staking strategy implementation that supports delegation based staking.
+///
+/// In this approach, first the funds are delegated from delegator to the pool account and later
+/// staked with `Staking`. The advantage of this approach is that the funds are held in the
+/// user account itself and not in the pool account.
+///
+/// This is the newer staking strategy used by pools. Once switched to this and migrated, ideally
+/// the `TransferStake` strategy should not be used. Or a separate migration would be required for
+/// it which is not provided by this pallet.
+///
+/// Use [`migration::unversioned::DelegationStakeMigration`] to migrate to this strategy.
+pub struct DelegateStake<T: Config, Staking: StakingInterface, Delegation: DelegationInterface>(
+	PhantomData<(T, Staking, Delegation)>,
+);
+
+impl<
+		T: Config,
+		Staking: StakingInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>,
+		Delegation: DelegationInterface<Balance = BalanceOf<T>, AccountId = T::AccountId>
+			+ DelegationMigrator<Balance = BalanceOf<T>, AccountId = T::AccountId>,
+	> StakeStrategy for DelegateStake<T, Staking, Delegation>
+{
+	type Balance = BalanceOf<T>;
+	type AccountId = T::AccountId;
+	type CoreStaking = Staking;
+
+	fn strategy_type() -> StakeStrategyType {
+		StakeStrategyType::Delegate
+	}
+
+	fn transferable_balance(pool_account: &Self::AccountId) -> BalanceOf<T> {
+		Delegation::agent_balance(pool_account).saturating_sub(Self::active_stake(pool_account))
+	}
+
+	fn total_balance(pool_account: &Self::AccountId) -> BalanceOf<T> {
+		Delegation::agent_balance(pool_account)
+	}
+
+	fn member_delegation_balance(member_account: &T::AccountId) -> BalanceOf<T> {
+		Delegation::delegator_balance(member_account)
+	}
+
+	fn pledge_bond(
+		who: &T::AccountId,
+		pool_account: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: BalanceOf<T>,
+		bond_type: BondType,
+	) -> DispatchResult {
+		match bond_type {
+			BondType::Create => {
+				// first delegation
+				Delegation::delegate(who, pool_account, reward_account, amount)
+			},
+			BondType::Extra => {
+				// additional delegation
+				Delegation::delegate_extra(who, pool_account, amount)
+			},
+		}
+	}
+
+	fn member_withdraw(
+		who: &T::AccountId,
+		pool_account: &Self::AccountId,
+		amount: BalanceOf<T>,
+		num_slashing_spans: u32,
+	) -> DispatchResult {
+		Delegation::withdraw_delegation(&who, pool_account, amount, num_slashing_spans)
+	}
+
+	fn has_pending_slash(pool_account: &Self::AccountId) -> bool {
+		Delegation::has_pending_slash(pool_account)
+	}
+
+	fn member_slash(
+		who: &T::AccountId,
+		pool_account: &Self::AccountId,
+		amount: BalanceOf<T>,
+		maybe_reporter: Option<T::AccountId>,
+	) -> DispatchResult {
+		Delegation::delegator_slash(pool_account, who, amount, maybe_reporter)
+	}
+
+	fn migrate_nominator_to_agent(
+		pool: &Self::AccountId,
+		reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		Delegation::migrate_nominator_to_agent(pool, reward_account)
+	}
+
+	fn migrate_delegation(
+		pool: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+	) -> DispatchResult {
+		Delegation::migrate_delegation(pool, delegator, value)
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn remove_as_agent(pool: &Self::AccountId) {
+		Delegation::drop_agent(pool)
+	}
+}
diff --git a/substrate/frame/nomination-pools/src/lib.rs b/substrate/frame/nomination-pools/src/lib.rs
index 95d23f2280a7e57e76a491c62536174d23f204af..816334c1a084072c7f953b366d0b114316a82d33 100644
--- a/substrate/frame/nomination-pools/src/lib.rs
+++ b/substrate/frame/nomination-pools/src/lib.rs
@@ -351,6 +351,7 @@
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
+use adapter::StakeStrategy;
 use codec::Codec;
 use frame_support::{
 	defensive, defensive_assert, ensure,
@@ -397,6 +398,7 @@ pub mod mock;
 #[cfg(test)]
 mod tests;
 
+pub mod adapter;
 pub mod migration;
 pub mod weights;
 
@@ -425,11 +427,11 @@ pub enum ConfigOp<T: Codec + Debug> {
 }
 
 /// The type of bonding that can happen to a pool.
-enum BondType {
+pub enum BondType {
 	/// Someone is bonding into the pool upon creation.
 	Create,
 	/// Someone is adding more funds later to this pool.
-	Later,
+	Extra,
 }
 
 /// How to increase the bond of a member.
@@ -549,9 +551,19 @@ impl<T: Config> PoolMember<T> {
 
 	/// Total balance of the member, both active and unbonding.
 	/// Doesn't mutate state.
-	#[cfg(any(feature = "try-runtime", feature = "fuzzing", test, debug_assertions))]
-	fn total_balance(&self) -> BalanceOf<T> {
-		let pool = BondedPool::<T>::get(self.pool_id).unwrap();
+	///
+	/// Worst case, iterates over [`TotalUnbondingPools`] member unbonding pools to calculate member
+	/// balance.
+	pub fn total_balance(&self) -> BalanceOf<T> {
+		let pool = match BondedPool::<T>::get(self.pool_id) {
+			Some(pool) => pool,
+			None => {
+				// this internal function is always called with a valid pool id.
+				defensive!("pool should exist; qed");
+				return Zero::zero();
+			},
+		};
+
 		let active_balance = pool.points_to_balance(self.active_points());
 
 		let sub_pools = match SubPoolsStorage::<T>::get(self.pool_id) {
@@ -973,12 +985,12 @@ impl<T: Config> BondedPool<T> {
 
 	/// Get the bonded account id of this pool.
 	fn bonded_account(&self) -> T::AccountId {
-		Pallet::<T>::create_bonded_account(self.id)
+		Pallet::<T>::generate_bonded_account(self.id)
 	}
 
 	/// Get the reward account id of this pool.
 	fn reward_account(&self) -> T::AccountId {
-		Pallet::<T>::create_reward_account(self.id)
+		Pallet::<T>::generate_reward_account(self.id)
 	}
 
 	/// Consume self and put into storage.
@@ -995,8 +1007,7 @@ impl<T: Config> BondedPool<T> {
 	///
 	/// This is often used for bonding and issuing new funds into the pool.
 	fn balance_to_point(&self, new_funds: BalanceOf<T>) -> BalanceOf<T> {
-		let bonded_balance =
-			T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero());
+		let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account());
 		Pallet::<T>::balance_to_point(bonded_balance, self.points, new_funds)
 	}
 
@@ -1004,8 +1015,7 @@ impl<T: Config> BondedPool<T> {
 	///
 	/// This is often used for unbonding.
 	fn points_to_balance(&self, points: BalanceOf<T>) -> BalanceOf<T> {
-		let bonded_balance =
-			T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero());
+		let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account());
 		Pallet::<T>::point_to_balance(bonded_balance, self.points, points)
 	}
 
@@ -1052,18 +1062,6 @@ impl<T: Config> BondedPool<T> {
 		self
 	}
 
-	/// The pools balance that is transferable provided it is expendable by staking pallet.
-	fn transferable_balance(&self) -> BalanceOf<T> {
-		let account = self.bonded_account();
-		// Note on why we can't use `Currency::reducible_balance`: Since pooled account has a
-		// provider (staking pallet), the account can not be set expendable by
-		// `pallet-nomination-pool`. This means reducible balance always returns balance preserving
-		// ED in the account. What we want though is transferable balance given the account can be
-		// dusted.
-		T::Currency::balance(&account)
-			.saturating_sub(T::Staking::active_stake(&account).unwrap_or_default())
-	}
-
 	fn is_root(&self, who: &T::AccountId) -> bool {
 		self.roles.root.as_ref().map_or(false, |root| root == who)
 	}
@@ -1127,8 +1125,7 @@ impl<T: Config> BondedPool<T> {
 	fn ok_to_be_open(&self) -> Result<(), DispatchError> {
 		ensure!(!self.is_destroying(), Error::<T>::CanNotChangeState);
 
-		let bonded_balance =
-			T::Staking::active_stake(&self.bonded_account()).unwrap_or(Zero::zero());
+		let bonded_balance = T::StakeAdapter::active_stake(&self.bonded_account());
 		ensure!(!bonded_balance.is_zero(), Error::<T>::OverflowRisk);
 
 		let points_to_balance_ratio_floor = self
@@ -1257,28 +1254,17 @@ impl<T: Config> BondedPool<T> {
 		amount: BalanceOf<T>,
 		ty: BondType,
 	) -> Result<BalanceOf<T>, DispatchError> {
-		// Cache the value
-		let bonded_account = self.bonded_account();
-		T::Currency::transfer(
-			who,
-			&bonded_account,
-			amount,
-			match ty {
-				BondType::Create => Preservation::Expendable,
-				BondType::Later => Preservation::Preserve,
-			},
-		)?;
 		// We must calculate the points issued *before* we bond who's funds, else points:balance
 		// ratio will be wrong.
 		let points_issued = self.issue(amount);
 
-		match ty {
-			BondType::Create => T::Staking::bond(&bonded_account, amount, &self.reward_account())?,
-			// The pool should always be created in such a way its in a state to bond extra, but if
-			// the active balance is slashed below the minimum bonded or the account cannot be
-			// found, we exit early.
-			BondType::Later => T::Staking::bond_extra(&bonded_account, amount)?,
-		}
+		T::StakeAdapter::pledge_bond(
+			who,
+			&self.bonded_account(),
+			&self.reward_account(),
+			amount,
+			ty,
+		)?;
 		TotalValueLocked::<T>::mutate(|tvl| {
 			tvl.saturating_accrue(amount);
 		});
@@ -1456,7 +1442,7 @@ impl<T: Config> RewardPool<T> {
 	/// This is sum of all the rewards that are claimable by pool members.
 	fn current_balance(id: PoolId) -> BalanceOf<T> {
 		T::Currency::reducible_balance(
-			&Pallet::<T>::create_reward_account(id),
+			&Pallet::<T>::generate_reward_account(id),
 			Preservation::Expendable,
 			Fortitude::Polite,
 		)
@@ -1569,7 +1555,7 @@ impl<T: Config> Get<u32> for TotalUnbondingPools<T> {
 		// NOTE: this may be dangerous in the scenario bonding_duration gets decreased because
 		// we would no longer be able to decode `BoundedBTreeMap::<EraIndex, UnbondPool<T>,
 		// TotalUnbondingPools<T>>`, which uses `TotalUnbondingPools` as the bound
-		T::Staking::bonding_duration() + T::PostUnbondingPoolsWindow::get()
+		T::StakeAdapter::bonding_duration() + T::PostUnbondingPoolsWindow::get()
 	}
 }
 
@@ -1646,7 +1632,9 @@ pub mod pallet {
 		type U256ToBalance: Convert<U256, BalanceOf<Self>>;
 
 		/// The interface for nominating.
-		type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
+		///
+		/// Note: Switching to a new [`StakeStrategy`] might require a migration of the storage.
+		type StakeAdapter: StakeStrategy<AccountId = Self::AccountId, Balance = BalanceOf<Self>>;
 
 		/// The amount of eras a `SubPools::with_era` pool can exist before it gets merged into the
 		/// `SubPools::no_era` pool. In other words, this is the amount of eras a member will be
@@ -1950,6 +1938,16 @@ pub mod pallet {
 		BondExtraRestricted,
 		/// No imbalance in the ED deposit for the pool.
 		NothingToAdjust,
+		/// No slash pending that can be applied to the member.
+		NothingToSlash,
+		/// No delegation to migrate.
+		NoDelegationToMigrate,
+		/// The pool has already migrated to enable delegation.
+		PoolAlreadyMigrated,
+		/// The pool has not migrated yet to enable delegation.
+		PoolNotMigrated,
+		/// This call is not allowed in the current state of the pallet.
+		NotSupported,
 	}
 
 	#[derive(Encode, Decode, PartialEq, TypeInfo, PalletError, RuntimeDebug)]
@@ -1965,6 +1963,10 @@ pub mod pallet {
 		/// The bonded account should only be killed by the staking system when the depositor is
 		/// withdrawing
 		BondedStashKilledPrematurely,
+		/// The delegation feature is unsupported.
+		DelegationUnsupported,
+		/// Unable to slash to the member of the pool.
+		SlashNotApplied,
 	}
 
 	impl<T> From<DefensiveError> for Error<T> {
@@ -2019,7 +2021,7 @@ pub mod pallet {
 			)?;
 
 			bonded_pool.try_inc_members()?;
-			let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Later)?;
+			let points_issued = bonded_pool.try_bond_funds(&who, amount, BondType::Extra)?;
 
 			PoolMembers::insert(
 				who.clone(),
@@ -2141,12 +2143,12 @@ pub mod pallet {
 				&mut reward_pool,
 			)?;
 
-			let current_era = T::Staking::current_era();
-			let unbond_era = T::Staking::bonding_duration().saturating_add(current_era);
+			let current_era = T::StakeAdapter::current_era();
+			let unbond_era = T::StakeAdapter::bonding_duration().saturating_add(current_era);
 
 			// Unbond in the actual underlying nominator.
 			let unbonding_balance = bonded_pool.dissolve(unbonding_points);
-			T::Staking::unbond(&bonded_pool.bonded_account(), unbonding_balance)?;
+			T::StakeAdapter::unbond(&bonded_pool.bonded_account(), unbonding_balance)?;
 
 			// Note that we lazily create the unbonding pools here if they don't already exist
 			let mut sub_pools = SubPoolsStorage::<T>::get(member.pool_id)
@@ -2209,7 +2211,7 @@ pub mod pallet {
 			// For now we only allow a pool to withdraw unbonded if its not destroying. If the pool
 			// is destroying then `withdraw_unbonded` can be used.
 			ensure!(pool.state != PoolState::Destroying, Error::<T>::NotDestroying);
-			T::Staking::withdraw_unbonded(pool.bonded_account(), num_slashing_spans)?;
+			T::StakeAdapter::withdraw_unbonded(&pool.bonded_account(), num_slashing_spans)?;
 
 			Ok(())
 		}
@@ -2232,7 +2234,10 @@ pub mod pallet {
 		///
 		/// # Note
 		///
-		/// If the target is the depositor, the pool will be destroyed.
+		/// - If the target is the depositor, the pool will be destroyed.
+		/// - If the pool has any pending slash, we also try to slash the member before letting them
+		/// withdraw. This calculation adds some weight overhead and is only defensive. In reality,
+		/// pool slashes must have been already applied via permissionless [`Call::apply_slash`].
 		#[pallet::call_index(5)]
 		#[pallet::weight(
 			T::WeightInfo::withdraw_unbonded_kill(*num_slashing_spans)
@@ -2246,13 +2251,30 @@ pub mod pallet {
 			let member_account = T::Lookup::lookup(member_account)?;
 			let mut member =
 				PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
-			let current_era = T::Staking::current_era();
+			let current_era = T::StakeAdapter::current_era();
 
 			let bonded_pool = BondedPool::<T>::get(member.pool_id)
 				.defensive_ok_or::<Error<T>>(DefensiveError::PoolNotFound.into())?;
 			let mut sub_pools =
 				SubPoolsStorage::<T>::get(member.pool_id).ok_or(Error::<T>::SubPoolsNotFound)?;
 
+			let slash_weight =
+				// apply slash if any before withdraw.
+				match Self::do_apply_slash(&member_account, None) {
+					Ok(_) => T::WeightInfo::apply_slash(),
+					Err(e) => {
+						let no_pending_slash: DispatchResult = Err(Error::<T>::NothingToSlash.into());
+						// This is an expected error. We add appropriate fees and continue withdrawal.
+						if Err(e) == no_pending_slash {
+							T::WeightInfo::apply_slash_fail()
+						} else {
+							// defensive: if we can't apply slash for some reason, we abort.
+							return Err(Error::<T>::Defensive(DefensiveError::SlashNotApplied).into());
+						}
+					}
+
+				};
+
 			bonded_pool.ok_to_withdraw_unbonded_with(&caller, &member_account)?;
 			let pool_account = bonded_pool.bonded_account();
 
@@ -2261,9 +2283,11 @@ pub mod pallet {
 			ensure!(!withdrawn_points.is_empty(), Error::<T>::CannotWithdrawAny);
 
 			// Before calculating the `balance_to_unbond`, we call withdraw unbonded to ensure the
-			// `transferrable_balance` is correct.
-			let stash_killed =
-				T::Staking::withdraw_unbonded(pool_account.clone(), num_slashing_spans)?;
+			// `transferable_balance` is correct.
+			let stash_killed = T::StakeAdapter::withdraw_unbonded(
+				&bonded_pool.bonded_account(),
+				num_slashing_spans,
+			)?;
 
 			// defensive-only: the depositor puts enough funds into the stash so that it will only
 			// be destroyed when they are leaving.
@@ -2310,15 +2334,16 @@ pub mod pallet {
 				// don't exist. This check is also defensive in cases where the unbond pool does not
 				// update its balance (e.g. a bug in the slashing hook.) We gracefully proceed in
 				// order to ensure members can leave the pool and it can be destroyed.
-				.min(bonded_pool.transferable_balance());
+				.min(T::StakeAdapter::transferable_balance(&bonded_pool.bonded_account()));
 
-			T::Currency::transfer(
-				&bonded_pool.bonded_account(),
+			// this can fail if the pool uses `DelegateStake` strategy and the member delegation
+			// is not claimed yet. See `Call::migrate_delegation()`.
+			T::StakeAdapter::member_withdraw(
 				&member_account,
+				&bonded_pool.bonded_account(),
 				balance_to_unbond,
-				Preservation::Expendable,
-			)
-			.defensive()?;
+				num_slashing_spans,
+			)?;
 
 			Self::deposit_event(Event::<T>::Withdrawn {
 				member: member_account.clone(),
@@ -2340,20 +2365,20 @@ pub mod pallet {
 
 				if member_account == bonded_pool.roles.depositor {
 					Pallet::<T>::dissolve_pool(bonded_pool);
-					None
+					Weight::default()
 				} else {
 					bonded_pool.dec_members().put();
 					SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
-					Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans))
+					T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
 				}
 			} else {
 				// we certainly don't need to delete any pools, because no one is being removed.
 				SubPoolsStorage::<T>::insert(member.pool_id, sub_pools);
 				PoolMembers::<T>::insert(&member_account, member);
-				Some(T::WeightInfo::withdraw_unbonded_update(num_slashing_spans))
+				T::WeightInfo::withdraw_unbonded_update(num_slashing_spans)
 			};
 
-			Ok(post_info_weight.into())
+			Ok(Some(post_info_weight.saturating_add(slash_weight)).into())
 		}
 
 		/// Create a new delegation pool.
@@ -2448,7 +2473,7 @@ pub mod pallet {
 				Error::<T>::MinimumBondNotMet
 			);
 
-			T::Staking::nominate(&bonded_pool.bonded_account(), validators)
+			T::StakeAdapter::nominate(&bonded_pool.bonded_account(), validators)
 		}
 
 		/// Set a new state for the pool.
@@ -2636,12 +2661,12 @@ pub mod pallet {
 				.active_points();
 
 			if bonded_pool.points_to_balance(depositor_points) >=
-				T::Staking::minimum_nominator_bond()
+				T::StakeAdapter::minimum_nominator_bond()
 			{
 				ensure!(bonded_pool.can_nominate(&who), Error::<T>::NotNominator);
 			}
 
-			T::Staking::chill(&bonded_pool.bonded_account())
+			T::StakeAdapter::chill(&bonded_pool.bonded_account())
 		}
 
 		/// `origin` bonds funds from `extra` for some pool member `member` into their respective
@@ -2838,6 +2863,119 @@ pub mod pallet {
 
 			Ok(())
 		}
+
+		/// Apply a pending slash on a member.
+		///
+		/// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type:
+		/// [`adapter::StakeStrategyType::Delegate`].
+		///
+		/// This call can be dispatched permissionlessly (i.e. by any account). If the member has
+		/// slash to be applied, caller may be rewarded with the part of the slash.
+		#[pallet::call_index(23)]
+		#[pallet::weight(T::WeightInfo::apply_slash())]
+		pub fn apply_slash(
+			origin: OriginFor<T>,
+			member_account: AccountIdLookupOf<T>,
+		) -> DispatchResultWithPostInfo {
+			ensure!(
+				T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
+				Error::<T>::NotSupported
+			);
+
+			let who = ensure_signed(origin)?;
+			let member_account = T::Lookup::lookup(member_account)?;
+			Self::do_apply_slash(&member_account, Some(who))?;
+
+			// If successful, refund the fees.
+			Ok(Pays::No.into())
+		}
+
+		/// Migrates delegated funds from the pool account to the `member_account`.
+		///
+		/// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type:
+		/// [`adapter::StakeStrategyType::Delegate`].
+		///
+		/// This is a permission-less call and refunds any fee if claim is successful.
+		///
+		/// If the pool has migrated to delegation based staking, the staked tokens of pool members
+		/// can be moved and held in their own account. See [`adapter::DelegateStake`]
+		#[pallet::call_index(24)]
+		#[pallet::weight(T::WeightInfo::migrate_delegation())]
+		pub fn migrate_delegation(
+			origin: OriginFor<T>,
+			member_account: AccountIdLookupOf<T>,
+		) -> DispatchResultWithPostInfo {
+			let _caller = ensure_signed(origin)?;
+
+			ensure!(
+				T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
+				Error::<T>::NotSupported
+			);
+
+			let member_account = T::Lookup::lookup(member_account)?;
+			let member =
+				PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
+
+			// ensure pool is migrated.
+			ensure!(
+				T::StakeAdapter::pool_strategy(&Self::generate_bonded_account(member.pool_id)) ==
+					adapter::StakeStrategyType::Delegate,
+				Error::<T>::PoolNotMigrated
+			);
+
+			let pool_contribution = member.total_balance();
+			ensure!(pool_contribution >= MinJoinBond::<T>::get(), Error::<T>::MinimumBondNotMet);
+			// the member must have some contribution to be migrated.
+			ensure!(pool_contribution > Zero::zero(), Error::<T>::NoDelegationToMigrate);
+
+			let delegation = T::StakeAdapter::member_delegation_balance(&member_account);
+			// delegation can be claimed only once.
+			ensure!(delegation == Zero::zero(), Error::<T>::NoDelegationToMigrate);
+
+			let diff = pool_contribution.defensive_saturating_sub(delegation);
+			T::StakeAdapter::migrate_delegation(
+				&Pallet::<T>::generate_bonded_account(member.pool_id),
+				&member_account,
+				diff,
+			)?;
+
+			// if successful, we refund the fee.
+			Ok(Pays::No.into())
+		}
+
+		/// Migrate pool from [`adapter::StakeStrategyType::Transfer`] to
+		/// [`adapter::StakeStrategyType::Delegate`].
+		///
+		/// Fails unless [`crate::pallet::Config::StakeAdapter`] is of strategy type:
+		/// [`adapter::StakeStrategyType::Delegate`].
+		///
+		/// This call can be dispatched permissionlessly, and refunds any fee if successful.
+		///
+		/// If the pool has already migrated to delegation based staking, this call will fail.
+		#[pallet::call_index(25)]
+		#[pallet::weight(T::WeightInfo::pool_migrate())]
+		pub fn migrate_pool_to_delegate_stake(
+			origin: OriginFor<T>,
+			pool_id: PoolId,
+		) -> DispatchResultWithPostInfo {
+			// gate this call to be called only if `DelegateStake` strategy is used.
+			ensure!(
+				T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
+				Error::<T>::NotSupported
+			);
+
+			let _caller = ensure_signed(origin)?;
+			// ensure pool exists.
+			let bonded_pool = BondedPool::<T>::get(pool_id).ok_or(Error::<T>::PoolNotFound)?;
+			ensure!(
+				T::StakeAdapter::pool_strategy(&bonded_pool.bonded_account()) ==
+					adapter::StakeStrategyType::Transfer,
+				Error::<T>::PoolAlreadyMigrated
+			);
+
+			Self::migrate_to_delegate_stake(pool_id)?;
+			Ok(Pays::No.into())
+		}
 	}
 
 	#[pallet::hooks]
@@ -2853,7 +2991,7 @@ pub mod pallet {
 				"Minimum points to balance ratio must be greater than 0"
 			);
 			assert!(
-				T::Staking::bonding_duration() < TotalUnbondingPools::<T>::get(),
+				T::StakeAdapter::bonding_duration() < TotalUnbondingPools::<T>::get(),
 				"There must be more unbonding pools then the bonding duration /
 				so a slash can be applied to relevant unbonding pools. (We assume /
 				the bonding duration > slash deffer duration.",
@@ -2871,7 +3009,7 @@ impl<T: Config> Pallet<T> {
 	/// It is essentially `max { MinNominatorBond, MinCreateBond, MinJoinBond }`, where the former
 	/// is coming from the staking pallet and the latter two are configured in this pallet.
 	pub fn depositor_min_bond() -> BalanceOf<T> {
-		T::Staking::minimum_nominator_bond()
+		T::StakeAdapter::minimum_nominator_bond()
 			.max(MinCreateBond::<T>::get())
 			.max(MinJoinBond::<T>::get())
 			.max(T::Currency::minimum_balance())
@@ -2907,7 +3045,7 @@ impl<T: Config> Pallet<T> {
 			"bonded account of dissolving pool should have no consumers"
 		);
 		defensive_assert!(
-			T::Staking::total_stake(&bonded_account).unwrap_or_default() == Zero::zero(),
+			T::StakeAdapter::total_stake(&bonded_pool.bonded_account()) == Zero::zero(),
 			"dissolving pool should not have any stake in the staking pallet"
 		);
 
@@ -2930,11 +3068,12 @@ impl<T: Config> Pallet<T> {
 			"could not transfer all amount to depositor while dissolving pool"
 		);
 		defensive_assert!(
-			T::Currency::total_balance(&bonded_pool.bonded_account()) == Zero::zero(),
+			T::StakeAdapter::total_balance(&bonded_pool.bonded_account()) == Zero::zero(),
 			"dissolving pool should not have any balance"
 		);
 		// NOTE: Defensively force set balance to zero.
 		T::Currency::set_balance(&reward_account, Zero::zero());
+		// With `DelegateStake` strategy, this won't do anything.
 		T::Currency::set_balance(&bonded_pool.bonded_account(), Zero::zero());
 
 		Self::deposit_event(Event::<T>::Destroyed { pool_id: bonded_pool.id });
@@ -2945,12 +3084,19 @@ impl<T: Config> Pallet<T> {
 	}
 
 	/// Create the main, bonded account of a pool with the given id.
-	pub fn create_bonded_account(id: PoolId) -> T::AccountId {
+	pub fn generate_bonded_account(id: PoolId) -> T::AccountId {
 		T::PalletId::get().into_sub_account_truncating((AccountType::Bonded, id))
 	}
 
+	fn migrate_to_delegate_stake(id: PoolId) -> DispatchResult {
+		T::StakeAdapter::migrate_nominator_to_agent(
+			&Self::generate_bonded_account(id),
+			&Self::generate_reward_account(id),
+		)
+	}
+
 	/// Create the reward account of a pool with the given id.
-	pub fn create_reward_account(id: PoolId) -> T::AccountId {
+	pub fn generate_reward_account(id: PoolId) -> T::AccountId {
 		// NOTE: in order to have a distinction in the test account id type (u128), we put
 		// account_type first so it does not get truncated out.
 		T::PalletId::get().into_sub_account_truncating((AccountType::Reward, id))
@@ -3194,9 +3340,9 @@ impl<T: Config> Pallet<T> {
 
 		let (points_issued, bonded) = match extra {
 			BondExtra::FreeBalance(amount) =>
-				(bonded_pool.try_bond_funds(&member_account, amount, BondType::Later)?, amount),
+				(bonded_pool.try_bond_funds(&member_account, amount, BondType::Extra)?, amount),
 			BondExtra::Rewards =>
-				(bonded_pool.try_bond_funds(&member_account, claimed, BondType::Later)?, claimed),
+				(bonded_pool.try_bond_funds(&member_account, claimed, BondType::Extra)?, claimed),
 		};
 
 		bonded_pool.ok_to_be_open()?;
@@ -3317,6 +3463,36 @@ impl<T: Config> Pallet<T> {
 		Ok(())
 	}
 
+	/// Slash member against the pending slash for the pool.
+	fn do_apply_slash(
+		member_account: &T::AccountId,
+		reporter: Option<T::AccountId>,
+	) -> DispatchResult {
+		// calculate points to be slashed.
+		let member =
+			PoolMembers::<T>::get(&member_account).ok_or(Error::<T>::PoolMemberNotFound)?;
+
+		let pool_account = Pallet::<T>::generate_bonded_account(member.pool_id);
+		ensure!(T::StakeAdapter::has_pending_slash(&pool_account), Error::<T>::NothingToSlash);
+
+		let unslashed_balance = T::StakeAdapter::member_delegation_balance(&member_account);
+		let slashed_balance = member.total_balance();
+		defensive_assert!(
+			unslashed_balance >= slashed_balance,
+			"unslashed balance should always be greater or equal to the slashed"
+		);
+
+		// if nothing to slash, return error.
+		ensure!(unslashed_balance > slashed_balance, Error::<T>::NothingToSlash);
+
+		T::StakeAdapter::member_slash(
+			&member_account,
+			&pool_account,
+			unslashed_balance.defensive_saturating_sub(slashed_balance),
+			reporter,
+		)
+	}
+
 	/// Apply freeze on reward account to restrict it from going below ED.
 	pub(crate) fn freeze_pool_deposit(reward_acc: &T::AccountId) -> DispatchResult {
 		T::Currency::set_freeze(
@@ -3395,7 +3571,7 @@ impl<T: Config> Pallet<T> {
 		);
 
 		for id in reward_pools {
-			let account = Self::create_reward_account(id);
+			let account = Self::generate_reward_account(id);
 			if T::Currency::reducible_balance(&account, Preservation::Expendable, Fortitude::Polite) <
 				T::Currency::minimum_balance()
 			{
@@ -3480,8 +3656,7 @@ impl<T: Config> Pallet<T> {
 				pool is being destroyed and the depositor is the last member",
 			);
 
-			expected_tvl +=
-				T::Staking::total_stake(&bonded_pool.bonded_account()).unwrap_or_default();
+			expected_tvl += T::StakeAdapter::total_stake(&bonded_pool.bonded_account());
 
 			Ok(())
 		})?;
@@ -3506,19 +3681,28 @@ impl<T: Config> Pallet<T> {
 		}
 
 		for (pool_id, _pool) in BondedPools::<T>::iter() {
-			let pool_account = Pallet::<T>::create_bonded_account(pool_id);
+			let pool_account = Pallet::<T>::generate_bonded_account(pool_id);
 			let subs = SubPoolsStorage::<T>::get(pool_id).unwrap_or_default();
 
 			let sum_unbonding_balance = subs.sum_unbonding_balance();
-			let bonded_balance = T::Staking::active_stake(&pool_account).unwrap_or_default();
-			let total_balance = T::Currency::total_balance(&pool_account);
+			let bonded_balance = T::StakeAdapter::active_stake(&pool_account);
+			let total_balance = T::StakeAdapter::total_balance(&pool_account);
+
+			// At the time when StakeAdapter is changed but migration is not yet done, the new
+			// adapter would return zero balance (as it is not an agent yet). We handle that by
+			// falling back to reading actual balance of the pool account.
+			let pool_balance = if total_balance.is_zero() {
+				T::Currency::total_balance(&pool_account)
+			} else {
+				total_balance
+			};
 
 			assert!(
-				total_balance >= bonded_balance + sum_unbonding_balance,
-				"faulty pool: {:?} / {:?}, total_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
+				pool_balance >= bonded_balance + sum_unbonding_balance,
+				"faulty pool: {:?} / {:?}, pool_balance {:?} >= bonded_balance {:?} + sum_unbonding_balance {:?}",
 				pool_id,
 				_pool,
-				total_balance,
+				pool_balance,
 				bonded_balance,
 				sum_unbonding_balance
 			);
@@ -3544,7 +3728,7 @@ impl<T: Config> Pallet<T> {
 	pub fn check_ed_imbalance() -> Result<(), DispatchError> {
 		let mut failed: u32 = 0;
 		BondedPools::<T>::iter_keys().for_each(|id| {
-			let reward_acc = Self::create_reward_account(id);
+			let reward_acc = Self::generate_reward_account(id);
 			let frozen_balance =
 				T::Currency::balance_frozen(&FreezeReason::PoolMinBalance.into(), &reward_acc);
 
@@ -3615,7 +3799,7 @@ impl<T: Config> Pallet<T> {
 	pub fn api_balance_to_points(pool_id: PoolId, new_funds: BalanceOf<T>) -> BalanceOf<T> {
 		if let Some(pool) = BondedPool::<T>::get(pool_id) {
 			let bonded_balance =
-				T::Staking::active_stake(&pool.bonded_account()).unwrap_or(Zero::zero());
+				T::StakeAdapter::active_stake(&Self::generate_bonded_account(pool_id));
 			Pallet::<T>::balance_to_point(bonded_balance, pool.points, new_funds)
 		} else {
 			Zero::zero()
diff --git a/substrate/frame/nomination-pools/src/migration.rs b/substrate/frame/nomination-pools/src/migration.rs
index 796b310862afcb8b897d4dafd66395e33e1970fb..a3989559dfbb01a0f13f53944e34f578c4d87cfd 100644
--- a/substrate/frame/nomination-pools/src/migration.rs
+++ b/substrate/frame/nomination-pools/src/migration.rs
@@ -107,6 +107,137 @@ pub mod unversioned {
 			Ok(())
 		}
 	}
+
+	/// Migrate existing pools from [`adapter::StakeStrategyType::Transfer`] to
+	/// [`adapter::StakeStrategyType::Delegate`].
+	///
+	/// Note: This only migrates the pools, the members are not migrated. They can use the
+	/// permission-less [`Pallet::migrate_delegation()`] to migrate their funds.
+	///
+	/// This migration does not break any existing pool storage item, does not need to happen in any
+	/// sequence and hence can be applied unversioned on a production runtime.
+	///
+	/// Takes `MaxPools` as type parameter to limit the number of pools that should be migrated in a
+	/// single block. It should be set such that migration weight does not exceed the block weight
+	/// limit. If all pools can be safely migrated, it is good to keep this number a little higher
+	/// than the actual number of pools to handle any extra pools created while the migration is
+	/// proposed, and before it is executed.
+	///
+	/// If there are pools that fail to migrate or did not fit in the bounds, the remaining pools
+	/// can be migrated via the permission-less extrinsic [`Call::migrate_pool_to_delegate_stake`].
+	pub struct DelegationStakeMigration<T, MaxPools>(sp_std::marker::PhantomData<(T, MaxPools)>);
+
+	impl<T: Config, MaxPools: Get<u32>> OnRuntimeUpgrade for DelegationStakeMigration<T, MaxPools> {
+		fn on_runtime_upgrade() -> Weight {
+			let mut count: u32 = 0;
+
+			BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).for_each(|id| {
+				let pool_acc = Pallet::<T>::generate_bonded_account(id);
+
+				// only migrate if the pool is in Transfer Strategy.
+				if T::StakeAdapter::pool_strategy(&pool_acc) == adapter::StakeStrategyType::Transfer
+				{
+					let _ = Pallet::<T>::migrate_to_delegate_stake(id).map_err(|err| {
+						log!(
+							warn,
+							"failed to migrate pool {:?} to delegate stake strategy with err: {:?}",
+							id,
+							err
+						)
+					});
+					count.saturating_inc();
+				}
+			});
+
+			log!(info, "migrated {:?} pools to delegate stake strategy", count);
+
+			// reads: (bonded pool key + current pool strategy) * MaxPools (worst case)
+			T::DbWeight::get()
+				.reads_writes(2, 0)
+				.saturating_mul(MaxPools::get() as u64)
+				// migration weight: `pool_migrate` weight * count
+				.saturating_add(T::WeightInfo::pool_migrate().saturating_mul(count.into()))
+		}
+
+		#[cfg(feature = "try-runtime")]
+		fn pre_upgrade() -> Result<Vec<u8>, TryRuntimeError> {
+			// ensure stake adapter is correct.
+			ensure!(
+				T::StakeAdapter::strategy_type() == adapter::StakeStrategyType::Delegate,
+				"Current strategy is not `Delegate"
+			);
+
+			if BondedPools::<T>::count() > MaxPools::get() {
+				// we log a warning if the number of pools exceeds the bound.
+				log!(
+					warn,
+					"Number of pools {} exceeds the maximum bound {}. This would leave some pools unmigrated.", BondedPools::<T>::count(), MaxPools::get()
+				);
+			}
+
+			let mut pool_balances: Vec<BalanceOf<T>> = Vec::new();
+			BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).for_each(|id| {
+				let pool_account = Pallet::<T>::generate_bonded_account(id);
+				let current_strategy = T::StakeAdapter::pool_strategy(&pool_account);
+
+				// we ensure migration is idempotent.
+				let pool_balance = if current_strategy == adapter::StakeStrategyType::Transfer {
+					T::Currency::total_balance(&pool_account)
+				} else {
+					T::StakeAdapter::total_balance(&pool_account)
+				};
+
+				pool_balances.push(pool_balance);
+			});
+
+			Ok(pool_balances.encode())
+		}
+
+		#[cfg(feature = "try-runtime")]
+		fn post_upgrade(data: Vec<u8>) -> Result<(), TryRuntimeError> {
+			let expected_pool_balances: Vec<BalanceOf<T>> = Decode::decode(&mut &data[..]).unwrap();
+
+			for (index, id) in
+				BondedPools::<T>::iter_keys().take(MaxPools::get() as usize).enumerate()
+			{
+				let pool_account = Pallet::<T>::generate_bonded_account(id);
+				if T::StakeAdapter::pool_strategy(&pool_account) ==
+					adapter::StakeStrategyType::Transfer
+				{
+					log!(error, "Pool {} failed to migrate", id,);
+					return Err(TryRuntimeError::Other("Pool failed to migrate"));
+				}
+
+				let actual_balance = T::StakeAdapter::total_balance(&pool_account);
+				let expected_balance = expected_pool_balances.get(index).unwrap();
+
+				if actual_balance != *expected_balance {
+					log!(
+						error,
+						"Pool {} balance mismatch. Expected: {:?}, Actual: {:?}",
+						id,
+						expected_balance,
+						actual_balance
+					);
+					return Err(TryRuntimeError::Other("Pool balance mismatch"));
+				}
+
+				// account balance should be zero.
+				let pool_account_balance = T::Currency::total_balance(&pool_account);
+				if pool_account_balance != Zero::zero() {
+					log!(
+						error,
+						"Pool account balance was expected to be zero. Pool: {}, Balance: {:?}",
+						id,
+						pool_account_balance
+					);
+					return Err(TryRuntimeError::Other("Pool account balance not migrated"));
+				}
+			}
+
+			Ok(())
+		}
+	}
 }
 
 pub mod v8 {
@@ -201,7 +332,7 @@ pub(crate) mod v7 {
 	impl<T: Config> V7BondedPool<T> {
 		#[allow(dead_code)]
 		fn bonded_account(&self) -> T::AccountId {
-			Pallet::<T>::create_bonded_account(self.id)
+			Pallet::<T>::generate_bonded_account(self.id)
 		}
 	}
 
@@ -275,7 +406,7 @@ mod v6 {
 
 	impl<T: Config> MigrateToV6<T> {
 		fn freeze_ed(pool_id: PoolId) -> Result<(), ()> {
-			let reward_acc = Pallet::<T>::create_reward_account(pool_id);
+			let reward_acc = Pallet::<T>::generate_reward_account(pool_id);
 			Pallet::<T>::freeze_pool_deposit(&reward_acc).map_err(|e| {
 				log!(error, "Failed to freeze ED for pool {} with error: {:?}", pool_id, e);
 				()
@@ -760,7 +891,7 @@ pub mod v2 {
 					};
 
 					let accumulated_reward = RewardPool::<T>::current_balance(id);
-					let reward_account = Pallet::<T>::create_reward_account(id);
+					let reward_account = Pallet::<T>::generate_reward_account(id);
 					let mut sum_paid_out = BalanceOf::<T>::zero();
 
 					members
@@ -882,7 +1013,7 @@ pub mod v2 {
 			// all reward accounts must have more than ED.
 			RewardPools::<T>::iter().try_for_each(|(id, _)| -> Result<(), TryRuntimeError> {
 				ensure!(
-					<T::Currency as frame_support::traits::fungible::Inspect<T::AccountId>>::balance(&Pallet::<T>::create_reward_account(id)) >=
+					<T::Currency as frame_support::traits::fungible::Inspect<T::AccountId>>::balance(&Pallet::<T>::generate_reward_account(id)) >=
 						T::Currency::minimum_balance(),
 					"Reward accounts must have greater balance than ED."
 				);
@@ -1022,11 +1153,8 @@ mod helpers {
 	use super::*;
 
 	pub(crate) fn calculate_tvl_by_total_stake<T: Config>() -> BalanceOf<T> {
-		BondedPools::<T>::iter()
-			.map(|(id, inner)| {
-				T::Staking::total_stake(&BondedPool { id, inner: inner.clone() }.bonded_account())
-					.unwrap_or_default()
-			})
+		BondedPools::<T>::iter_keys()
+			.map(|id| T::StakeAdapter::total_stake(&Pallet::<T>::generate_bonded_account(id)))
 			.reduce(|acc, total_balance| acc + total_balance)
 			.unwrap_or_default()
 	}
diff --git a/substrate/frame/nomination-pools/src/mock.rs b/substrate/frame/nomination-pools/src/mock.rs
index e34719a7b80e87cef65cfc5dd638ee4ca5573803..b659c975a8395c0d8afb486909da3695a28ba102 100644
--- a/substrate/frame/nomination-pools/src/mock.rs
+++ b/substrate/frame/nomination-pools/src/mock.rs
@@ -36,12 +36,12 @@ pub type Currency = <T as Config>::Currency;
 
 // Ext builder creates a pool with id 1.
 pub fn default_bonded_account() -> AccountId {
-	Pools::create_bonded_account(1)
+	Pools::generate_bonded_account(1)
 }
 
 // Ext builder creates a pool with id 1.
 pub fn default_reward_account() -> AccountId {
-	Pools::create_reward_account(1)
+	Pools::generate_reward_account(1)
 }
 
 parameter_types! {
@@ -71,7 +71,7 @@ impl StakingMock {
 	/// Does not modify any [`SubPools`] of the pool as [`Default::default`] is passed for
 	/// `slashed_unlocking`.
 	pub fn slash_by(pool_id: PoolId, amount: Balance) {
-		let acc = Pools::create_bonded_account(pool_id);
+		let acc = Pools::generate_bonded_account(pool_id);
 		let bonded = BondedBalanceMap::get();
 		let pre_total = bonded.get(&acc).unwrap();
 		Self::set_bonded_balance(acc, pre_total - amount);
@@ -111,6 +111,10 @@ impl sp_staking::StakingInterface for StakingMock {
 			.ok_or(DispatchError::Other("NotStash"))
 	}
 
+	fn is_virtual_staker(_who: &Self::AccountId) -> bool {
+		false
+	}
+
 	fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
 		let mut x = BondedBalanceMap::get();
 		x.get_mut(who).map(|v| *v += extra);
@@ -314,7 +318,7 @@ impl pools::Config for Runtime {
 	type RewardCounter = RewardCounter;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = StakingMock;
+	type StakeAdapter = adapter::TransferStake<Self, StakingMock>;
 	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
 	type PalletId = PoolsPalletId;
 	type MaxMetadataLen = MaxMetadataLen;
diff --git a/substrate/frame/nomination-pools/src/tests.rs b/substrate/frame/nomination-pools/src/tests.rs
index 535e7537469ecd77a8eb261c895b4d5fd264ecfb..8fc339c695bde447f950a946f6001392c496f333 100644
--- a/substrate/frame/nomination-pools/src/tests.rs
+++ b/substrate/frame/nomination-pools/src/tests.rs
@@ -95,8 +95,8 @@ fn test_setup_works() {
 			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);
+		let bonded_account = Pools::generate_bonded_account(last_pool);
+		let reward_account = Pools::generate_reward_account(last_pool);
 
 		// the bonded_account should be bonded by the depositor's funds.
 		assert_eq!(StakingMock::active_stake(&bonded_account).unwrap(), 10);
@@ -728,7 +728,7 @@ mod join {
 			);
 
 			// Force the pools bonded balance to 0, simulating a 100% slash
-			StakingMock::set_bonded_balance(Pools::create_bonded_account(1), 0);
+			StakingMock::set_bonded_balance(Pools::generate_bonded_account(1), 0);
 			assert_noop!(
 				Pools::join(RuntimeOrigin::signed(11), 420, 1),
 				Error::<Runtime>::OverflowRisk
@@ -755,7 +755,7 @@ mod join {
 				<<Runtime as Config>::MaxPointsToBalance as Get<u8>>::get().into();
 
 			StakingMock::set_bonded_balance(
-				Pools::create_bonded_account(123),
+				Pools::generate_bonded_account(123),
 				max_points_to_balance,
 			);
 			assert_noop!(
@@ -764,7 +764,7 @@ mod join {
 			);
 
 			StakingMock::set_bonded_balance(
-				Pools::create_bonded_account(123),
+				Pools::generate_bonded_account(123),
 				Balance::MAX / max_points_to_balance,
 			);
 			// Balance needs to be gt Balance::MAX / `MaxPointsToBalance`
@@ -773,7 +773,10 @@ mod join {
 				TokenError::FundsUnavailable,
 			);
 
-			StakingMock::set_bonded_balance(Pools::create_bonded_account(1), max_points_to_balance);
+			StakingMock::set_bonded_balance(
+				Pools::generate_bonded_account(1),
+				max_points_to_balance,
+			);
 
 			// Cannot join a pool that isn't open
 			unsafe_set_state(123, PoolState::Blocked);
@@ -804,7 +807,7 @@ mod join {
 	#[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);
+			StakingMock::set_bonded_balance(Pools::generate_bonded_account(123), 100);
 			BondedPool::<Runtime> {
 				id: 123,
 				inner: BondedPoolInner {
@@ -1979,7 +1982,7 @@ mod claim_payout {
 			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);
+			Currency::set_balance(&Pools::generate_reward_account(3), 10);
 
 			// create pool 3
 			Currency::set_balance(&30, 100);
@@ -1988,7 +1991,7 @@ mod claim_payout {
 			// 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)),
+				Currency::free_balance(&Pools::generate_reward_account(3)),
 				10 + Currency::minimum_balance()
 			);
 
@@ -4631,7 +4634,7 @@ mod withdraw_unbonded {
 			// pool is destroyed.
 			assert!(!Metadata::<T>::contains_key(1));
 			// ensure the pool account is reaped.
-			assert!(!frame_system::Account::<T>::contains_key(&Pools::create_bonded_account(1)));
+			assert!(!frame_system::Account::<T>::contains_key(&Pools::generate_bonded_account(1)));
 		})
 	}
 
@@ -4639,7 +4642,7 @@ mod withdraw_unbonded {
 	fn destroy_works_with_erroneous_extra_consumer() {
 		ExtBuilder::default().ed(1).build_and_execute(|| {
 			// 10 is the depositor for pool 1, with min join bond 10.
-			let pool_one = Pools::create_bonded_account(1);
+			let pool_one = Pools::generate_bonded_account(1);
 
 			// set pool to destroying.
 			unsafe_set_state(1, PoolState::Destroying);
@@ -4690,7 +4693,7 @@ mod create {
 	fn create_works() {
 		ExtBuilder::default().build_and_execute(|| {
 			// next pool id is 2.
-			let next_pool_stash = Pools::create_bonded_account(2);
+			let next_pool_stash = Pools::generate_bonded_account(2);
 			let ed = Currency::minimum_balance();
 
 			assert_eq!(TotalValueLocked::<T>::get(), 10);
@@ -5011,6 +5014,13 @@ mod set_state {
 			// surpassed. Making this pool destroyable by anyone.
 			StakingMock::slash_by(1, 10);
 
+			// in mock we are using transfer stake which implies slash is greedy. Extrinsic to
+			// apply pending slash should fail.
+			assert_noop!(
+				Pools::apply_slash(RuntimeOrigin::signed(11), 10),
+				Error::<Runtime>::NotSupported
+			);
+
 			// When
 			assert_ok!(Pools::set_state(RuntimeOrigin::signed(11), 1, PoolState::Destroying));
 			// Then
@@ -7473,3 +7483,61 @@ mod chill {
 		})
 	}
 }
+
+// the test mock is using `TransferStake` and so `DelegateStake` is not tested here. Extrinsics
+// meant for `DelegateStake` should be gated.
+//
+// `DelegateStake` tests are in `pallet-nomination-pools-test-delegate-stake`. Since we support both
+// strategies currently, we keep these tests as it is but in future we may remove `TransferStake`
+// completely.
+mod delegate_stake {
+	use super::*;
+	#[test]
+	fn delegation_specific_calls_are_gated() {
+		ExtBuilder::default().with_check(0).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 },
+				]
+			);
+
+			assert_eq!(
+				PoolMembers::<Runtime>::get(11).unwrap(),
+				PoolMember::<Runtime> { pool_id: 1, points: 2, ..Default::default() }
+			);
+
+			// ensure pool 1 cannot be migrated.
+			assert_noop!(
+				Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1),
+				Error::<Runtime>::NotSupported
+			);
+
+			// members cannot be migrated either.
+			assert_noop!(
+				Pools::migrate_delegation(RuntimeOrigin::signed(10), 11),
+				Error::<Runtime>::NotSupported
+			);
+
+			// Given
+			// The bonded balance is slashed in half
+			StakingMock::slash_by(1, 6);
+
+			// since slash is greedy with `TransferStake`, `apply_slash` should not work either.
+			assert_noop!(
+				Pools::apply_slash(RuntimeOrigin::signed(10), 11),
+				Error::<Runtime>::NotSupported
+			);
+		});
+	}
+}
diff --git a/substrate/frame/nomination-pools/src/weights.rs b/substrate/frame/nomination-pools/src/weights.rs
index 57ea8dc388f6869c8f4f2be761816cced9758e19..21711a499b623fad15198b265b99476545f6b2b5 100644
--- a/substrate/frame/nomination-pools/src/weights.rs
+++ b/substrate/frame/nomination-pools/src/weights.rs
@@ -18,27 +18,25 @@
 //! Autogenerated weights for `pallet_nomination_pools`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
-//! DATE: 2024-04-09, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2024-04-25, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-anb7yjbi-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
+//! HOSTNAME: `runner-dcu62vjg-project-674-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
 //! WASM-EXECUTION: `Compiled`, CHAIN: `Some("dev")`, DB CACHE: `1024`
 
 // Executed Command:
-// ./target/production/substrate-node
+// target/production/substrate-node
 // benchmark
 // pallet
-// --chain=dev
 // --steps=50
 // --repeat=20
-// --pallet=pallet_nomination_pools
-// --no-storage-info
-// --no-median-slopes
-// --no-min-squares
 // --extrinsic=*
 // --wasm-execution=compiled
 // --heap-pages=4096
-// --output=./substrate/frame/nomination-pools/src/weights.rs
+// --json-file=/builds/parity/mirrors/polkadot-sdk/.git/.artifacts/bench.json
+// --pallet=pallet_nomination_pools
+// --chain=dev
 // --header=./substrate/HEADER-APACHE2
+// --output=./substrate/frame/nomination-pools/src/weights.rs
 // --template=./substrate/.maintain/frame-weight-template.hbs
 
 #![cfg_attr(rustfmt, rustfmt_skip)]
@@ -73,6 +71,10 @@ pub trait WeightInfo {
 	fn set_claim_permission() -> Weight;
 	fn claim_commission() -> Weight;
 	fn adjust_pool_deposit() -> Weight;
+	fn apply_slash() -> Weight;
+	fn apply_slash_fail() -> Weight;
+	fn pool_migrate() -> Weight;
+	fn migrate_delegation() -> Weight;
 }
 
 /// Weights for `pallet_nomination_pools` using the Substrate node and recommended hardware.
@@ -100,6 +102,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -112,11 +116,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn join() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3425`
+		//  Measured:  `3458`
 		//  Estimated: `8877`
-		// Minimum execution time: 201_783_000 picoseconds.
-		Weight::from_parts(206_014_000, 8877)
-			.saturating_add(T::DbWeight::get().reads(20_u64))
+		// Minimum execution time: 195_962_000 picoseconds.
+		Weight::from_parts(201_682_000, 8877)
+			.saturating_add(T::DbWeight::get().reads(21_u64))
 			.saturating_add(T::DbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -133,6 +137,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -145,11 +151,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_transfer() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3435`
+		//  Measured:  `3468`
 		//  Estimated: `8877`
-		// Minimum execution time: 204_124_000 picoseconds.
-		Weight::from_parts(207_910_000, 8877)
-			.saturating_add(T::DbWeight::get().reads(17_u64))
+		// Minimum execution time: 197_466_000 picoseconds.
+		Weight::from_parts(201_356_000, 8877)
+			.saturating_add(T::DbWeight::get().reads(18_u64))
 			.saturating_add(T::DbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
@@ -168,6 +174,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -180,11 +188,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3500`
+		//  Measured:  `3533`
 		//  Estimated: `8877`
-		// Minimum execution time: 240_342_000 picoseconds.
-		Weight::from_parts(245_735_000, 8877)
-			.saturating_add(T::DbWeight::get().reads(18_u64))
+		// Minimum execution time: 232_623_000 picoseconds.
+		Weight::from_parts(236_970_000, 8877)
+			.saturating_add(T::DbWeight::get().reads(19_u64))
 			.saturating_add(T::DbWeight::get().writes(14_u64))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
@@ -203,8 +211,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `1172`
 		//  Estimated: `3719`
-		// Minimum execution time: 81_054_000 picoseconds.
-		Weight::from_parts(83_324_000, 3719)
+		// Minimum execution time: 77_992_000 picoseconds.
+		Weight::from_parts(79_927_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(6_u64))
 			.saturating_add(T::DbWeight::get().writes(4_u64))
 	}
@@ -228,6 +236,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::MinNominatorBond` (r:1 w:0)
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -242,11 +252,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3622`
+		//  Measured:  `3655`
 		//  Estimated: `27847`
-		// Minimum execution time: 188_835_000 picoseconds.
-		Weight::from_parts(192_565_000, 27847)
-			.saturating_add(T::DbWeight::get().reads(20_u64))
+		// Minimum execution time: 182_368_000 picoseconds.
+		Weight::from_parts(185_387_000, 27847)
+			.saturating_add(T::DbWeight::get().reads(21_u64))
 			.saturating_add(T::DbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
@@ -257,6 +267,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -268,13 +280,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn pool_withdraw_unbonded(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1848`
+		//  Measured:  `1881`
 		//  Estimated: `4764`
-		// Minimum execution time: 73_556_000 picoseconds.
-		Weight::from_parts(76_075_881, 4764)
-			// Standard Error: 1_419
-			.saturating_add(Weight::from_parts(54_476, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(8_u64))
+		// Minimum execution time: 72_179_000 picoseconds.
+		Weight::from_parts(75_031_092, 4764)
+			// Standard Error: 1_487
+			.saturating_add(Weight::from_parts(56_741, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(9_u64))
 			.saturating_add(T::DbWeight::get().writes(3_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -289,6 +301,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -306,13 +320,13 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2238`
+		//  Measured:  `2271`
 		//  Estimated: `27847`
-		// Minimum execution time: 144_177_000 picoseconds.
-		Weight::from_parts(148_686_524, 27847)
-			// Standard Error: 2_475
-			.saturating_add(Weight::from_parts(77_460, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(12_u64))
+		// Minimum execution time: 137_277_000 picoseconds.
+		Weight::from_parts(143_537_793, 27847)
+			// Standard Error: 3_049
+			.saturating_add(Weight::from_parts(71_178, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(13_u64))
 			.saturating_add(T::DbWeight::get().writes(9_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -329,6 +343,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:0)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:2 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:2 w:1)
@@ -364,14 +380,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2525`
+		//  Measured:  `2558`
 		//  Estimated: `27847`
-		// Minimum execution time: 255_957_000 picoseconds.
-		Weight::from_parts(264_206_788, 27847)
-			// Standard Error: 4_229
-			.saturating_add(Weight::from_parts(3_064, 0).saturating_mul(s.into()))
-			.saturating_add(T::DbWeight::get().reads(24_u64))
-			.saturating_add(T::DbWeight::get().writes(20_u64))
+		// Minimum execution time: 242_522_000 picoseconds.
+		Weight::from_parts(250_740_608, 27847)
+			// Standard Error: 4_517
+			.saturating_add(Weight::from_parts(13_231, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(25_u64))
+			.saturating_add(T::DbWeight::get().writes(21_u64))
 	}
 	/// Storage: `NominationPools::LastPoolId` (r:1 w:1)
 	/// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -393,12 +409,14 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:2 w:2)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:2 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:2 w:1)
@@ -419,11 +437,11 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn create() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1284`
+		//  Measured:  `1317`
 		//  Estimated: `8538`
-		// Minimum execution time: 193_527_000 picoseconds.
-		Weight::from_parts(197_140_000, 8538)
-			.saturating_add(T::DbWeight::get().reads(24_u64))
+		// Minimum execution time: 182_740_000 picoseconds.
+		Weight::from_parts(188_820_000, 8538)
+			.saturating_add(T::DbWeight::get().reads(25_u64))
 			.saturating_add(T::DbWeight::get().writes(17_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
@@ -459,12 +477,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1976`
+		//  Measured:  `2009`
 		//  Estimated: `4556 + n * (2520 ±0)`
-		// Minimum execution time: 86_054_000 picoseconds.
-		Weight::from_parts(88_743_932, 4556)
-			// Standard Error: 12_699
-			.saturating_add(Weight::from_parts(1_829_097, 0).saturating_mul(n.into()))
+		// Minimum execution time: 83_649_000 picoseconds.
+		Weight::from_parts(85_754_306, 4556)
+			// Standard Error: 12_757
+			.saturating_add(Weight::from_parts(1_616_356, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(15_u64))
 			.saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
@@ -478,10 +496,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_state() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1434`
+		//  Measured:  `1467`
 		//  Estimated: `4556`
-		// Minimum execution time: 34_544_000 picoseconds.
-		Weight::from_parts(35_910_000, 4556)
+		// Minimum execution time: 34_594_000 picoseconds.
+		Weight::from_parts(36_173_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -496,10 +514,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3735`
-		// Minimum execution time: 14_111_000 picoseconds.
-		Weight::from_parts(15_204_218, 3735)
-			// Standard Error: 226
-			.saturating_add(Weight::from_parts(1_291, 0).saturating_mul(n.into()))
+		// Minimum execution time: 13_945_000 picoseconds.
+		Weight::from_parts(14_764_062, 3735)
+			// Standard Error: 127
+			.saturating_add(Weight::from_parts(1_406, 0).saturating_mul(n.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -519,8 +537,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_524_000 picoseconds.
-		Weight::from_parts(4_882_000, 0)
+		// Minimum execution time: 4_523_000 picoseconds.
+		Weight::from_parts(4_727_000, 0)
 			.saturating_add(T::DbWeight::get().writes(6_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:1)
@@ -529,8 +547,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_975_000 picoseconds.
-		Weight::from_parts(18_549_000, 3719)
+		// Minimum execution time: 17_124_000 picoseconds.
+		Weight::from_parts(17_718_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -558,10 +576,10 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2143`
+		//  Measured:  `2176`
 		//  Estimated: `4556`
-		// Minimum execution time: 81_574_000 picoseconds.
-		Weight::from_parts(83_519_000, 4556)
+		// Minimum execution time: 78_293_000 picoseconds.
+		Weight::from_parts(81_177_000, 4556)
 			.saturating_add(T::DbWeight::get().reads(11_u64))
 			.saturating_add(T::DbWeight::get().writes(5_u64))
 	}
@@ -577,8 +595,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `804`
 		//  Estimated: `3719`
-		// Minimum execution time: 35_015_000 picoseconds.
-		Weight::from_parts(36_159_000, 3719)
+		// Minimum execution time: 33_105_000 picoseconds.
+		Weight::from_parts(34_106_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -590,8 +608,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `572`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_775_000 picoseconds.
-		Weight::from_parts(18_358_000, 3719)
+		// Minimum execution time: 16_710_000 picoseconds.
+		Weight::from_parts(17_269_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -601,8 +619,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 16_997_000 picoseconds.
-		Weight::from_parts(18_041_000, 3719)
+		// Minimum execution time: 16_557_000 picoseconds.
+		Weight::from_parts(17_431_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -612,8 +630,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_000_000 picoseconds.
-		Weight::from_parts(17_807_000, 3719)
+		// Minimum execution time: 16_723_000 picoseconds.
+		Weight::from_parts(17_155_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -625,8 +643,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `542`
 		//  Estimated: `3702`
-		// Minimum execution time: 14_803_000 picoseconds.
-		Weight::from_parts(15_401_000, 3702)
+		// Minimum execution time: 14_667_000 picoseconds.
+		Weight::from_parts(15_242_000, 3702)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -642,8 +660,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `1002`
 		//  Estimated: `3719`
-		// Minimum execution time: 69_759_000 picoseconds.
-		Weight::from_parts(71_985_000, 3719)
+		// Minimum execution time: 64_219_000 picoseconds.
+		Weight::from_parts(66_718_000, 3719)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -659,11 +677,58 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `901`
 		//  Estimated: `4764`
-		// Minimum execution time: 73_829_000 picoseconds.
-		Weight::from_parts(75_966_000, 4764)
+		// Minimum execution time: 70_284_000 picoseconds.
+		Weight::from_parts(71_375_000, 4764)
 			.saturating_add(T::DbWeight::get().reads(4_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	fn apply_slash() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `694`
+		//  Estimated: `3702`
+		// Minimum execution time: 13_403_000 picoseconds.
+		Weight::from_parts(14_064_000, 3702)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	fn apply_slash_fail() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `732`
+		//  Estimated: `3702`
+		// Minimum execution time: 14_419_000 picoseconds.
+		Weight::from_parts(15_004_000, 3702)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+	}
+	fn pool_migrate() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 759_000 picoseconds.
+		Weight::from_parts(819_000, 0)
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
+	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0)
+	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(24382), added: 26857, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::MinJoinBond` (r:1 w:0)
+	/// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	fn migrate_delegation() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1648`
+		//  Estimated: `27847`
+		// Minimum execution time: 36_192_000 picoseconds.
+		Weight::from_parts(37_038_000, 27847)
+			.saturating_add(T::DbWeight::get().reads(6_u64))
+	}
 }
 
 // For backwards compatibility and tests.
@@ -690,6 +755,8 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -702,11 +769,11 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn join() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3425`
+		//  Measured:  `3458`
 		//  Estimated: `8877`
-		// Minimum execution time: 201_783_000 picoseconds.
-		Weight::from_parts(206_014_000, 8877)
-			.saturating_add(RocksDbWeight::get().reads(20_u64))
+		// Minimum execution time: 195_962_000 picoseconds.
+		Weight::from_parts(201_682_000, 8877)
+			.saturating_add(RocksDbWeight::get().reads(21_u64))
 			.saturating_add(RocksDbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -723,6 +790,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -735,11 +804,11 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_transfer() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3435`
+		//  Measured:  `3468`
 		//  Estimated: `8877`
-		// Minimum execution time: 204_124_000 picoseconds.
-		Weight::from_parts(207_910_000, 8877)
-			.saturating_add(RocksDbWeight::get().reads(17_u64))
+		// Minimum execution time: 197_466_000 picoseconds.
+		Weight::from_parts(201_356_000, 8877)
+			.saturating_add(RocksDbWeight::get().reads(18_u64))
 			.saturating_add(RocksDbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
@@ -758,6 +827,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -770,11 +841,11 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::TotalValueLocked` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
 	fn bond_extra_other() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3500`
+		//  Measured:  `3533`
 		//  Estimated: `8877`
-		// Minimum execution time: 240_342_000 picoseconds.
-		Weight::from_parts(245_735_000, 8877)
-			.saturating_add(RocksDbWeight::get().reads(18_u64))
+		// Minimum execution time: 232_623_000 picoseconds.
+		Weight::from_parts(236_970_000, 8877)
+			.saturating_add(RocksDbWeight::get().reads(19_u64))
 			.saturating_add(RocksDbWeight::get().writes(14_u64))
 	}
 	/// Storage: `NominationPools::ClaimPermissions` (r:1 w:0)
@@ -793,8 +864,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `1172`
 		//  Estimated: `3719`
-		// Minimum execution time: 81_054_000 picoseconds.
-		Weight::from_parts(83_324_000, 3719)
+		// Minimum execution time: 77_992_000 picoseconds.
+		Weight::from_parts(79_927_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(6_u64))
 			.saturating_add(RocksDbWeight::get().writes(4_u64))
 	}
@@ -818,6 +889,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::MinNominatorBond` (r:1 w:0)
 	/// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -832,11 +905,11 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::CounterForSubPoolsStorage` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn unbond() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `3622`
+		//  Measured:  `3655`
 		//  Estimated: `27847`
-		// Minimum execution time: 188_835_000 picoseconds.
-		Weight::from_parts(192_565_000, 27847)
-			.saturating_add(RocksDbWeight::get().reads(20_u64))
+		// Minimum execution time: 182_368_000 picoseconds.
+		Weight::from_parts(185_387_000, 27847)
+			.saturating_add(RocksDbWeight::get().reads(21_u64))
 			.saturating_add(RocksDbWeight::get().writes(13_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
@@ -847,6 +920,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::CurrentEra` (r:1 w:0)
 	/// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -858,13 +933,13 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[0, 100]`.
 	fn pool_withdraw_unbonded(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1848`
+		//  Measured:  `1881`
 		//  Estimated: `4764`
-		// Minimum execution time: 73_556_000 picoseconds.
-		Weight::from_parts(76_075_881, 4764)
-			// Standard Error: 1_419
-			.saturating_add(Weight::from_parts(54_476, 0).saturating_mul(s.into()))
-			.saturating_add(RocksDbWeight::get().reads(8_u64))
+		// Minimum execution time: 72_179_000 picoseconds.
+		Weight::from_parts(75_031_092, 4764)
+			// Standard Error: 1_487
+			.saturating_add(Weight::from_parts(56_741, 0).saturating_mul(s.into()))
+			.saturating_add(RocksDbWeight::get().reads(9_u64))
 			.saturating_add(RocksDbWeight::get().writes(3_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -879,6 +954,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:1 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:1 w:0)
@@ -896,13 +973,13 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_update(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2238`
+		//  Measured:  `2271`
 		//  Estimated: `27847`
-		// Minimum execution time: 144_177_000 picoseconds.
-		Weight::from_parts(148_686_524, 27847)
-			// Standard Error: 2_475
-			.saturating_add(Weight::from_parts(77_460, 0).saturating_mul(s.into()))
-			.saturating_add(RocksDbWeight::get().reads(12_u64))
+		// Minimum execution time: 137_277_000 picoseconds.
+		Weight::from_parts(143_537_793, 27847)
+			// Standard Error: 3_049
+			.saturating_add(Weight::from_parts(71_178, 0).saturating_mul(s.into()))
+			.saturating_add(RocksDbWeight::get().reads(13_u64))
 			.saturating_add(RocksDbWeight::get().writes(9_u64))
 	}
 	/// Storage: `NominationPools::PoolMembers` (r:1 w:1)
@@ -919,6 +996,8 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::SlashingSpans` (r:1 w:0)
 	/// Proof: `Staking::SlashingSpans` (`max_values`: None, `max_size`: None, mode: `Measured`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:1)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:2 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:2 w:1)
@@ -954,14 +1033,14 @@ impl WeightInfo for () {
 	/// The range of component `s` is `[0, 100]`.
 	fn withdraw_unbonded_kill(s: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2525`
+		//  Measured:  `2558`
 		//  Estimated: `27847`
-		// Minimum execution time: 255_957_000 picoseconds.
-		Weight::from_parts(264_206_788, 27847)
-			// Standard Error: 4_229
-			.saturating_add(Weight::from_parts(3_064, 0).saturating_mul(s.into()))
-			.saturating_add(RocksDbWeight::get().reads(24_u64))
-			.saturating_add(RocksDbWeight::get().writes(20_u64))
+		// Minimum execution time: 242_522_000 picoseconds.
+		Weight::from_parts(250_740_608, 27847)
+			// Standard Error: 4_517
+			.saturating_add(Weight::from_parts(13_231, 0).saturating_mul(s.into()))
+			.saturating_add(RocksDbWeight::get().reads(25_u64))
+			.saturating_add(RocksDbWeight::get().writes(21_u64))
 	}
 	/// Storage: `NominationPools::LastPoolId` (r:1 w:1)
 	/// Proof: `NominationPools::LastPoolId` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
@@ -983,12 +1062,14 @@ impl WeightInfo for () {
 	/// Proof: `NominationPools::MaxPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	/// Storage: `NominationPools::CounterForPoolMembers` (r:1 w:1)
 	/// Proof: `NominationPools::CounterForPoolMembers` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:2 w:2)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Bonded` (r:1 w:1)
 	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `System::Account` (r:2 w:2)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
 	/// Storage: `Staking::Ledger` (r:1 w:1)
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::VirtualStakers` (r:1 w:0)
+	/// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Locks` (r:2 w:1)
 	/// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`)
 	/// Storage: `Balances::Freezes` (r:2 w:1)
@@ -1009,11 +1090,11 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`)
 	fn create() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1284`
+		//  Measured:  `1317`
 		//  Estimated: `8538`
-		// Minimum execution time: 193_527_000 picoseconds.
-		Weight::from_parts(197_140_000, 8538)
-			.saturating_add(RocksDbWeight::get().reads(24_u64))
+		// Minimum execution time: 182_740_000 picoseconds.
+		Weight::from_parts(188_820_000, 8538)
+			.saturating_add(RocksDbWeight::get().reads(25_u64))
 			.saturating_add(RocksDbWeight::get().writes(17_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
@@ -1049,12 +1130,12 @@ impl WeightInfo for () {
 	/// The range of component `n` is `[1, 16]`.
 	fn nominate(n: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1976`
+		//  Measured:  `2009`
 		//  Estimated: `4556 + n * (2520 ±0)`
-		// Minimum execution time: 86_054_000 picoseconds.
-		Weight::from_parts(88_743_932, 4556)
-			// Standard Error: 12_699
-			.saturating_add(Weight::from_parts(1_829_097, 0).saturating_mul(n.into()))
+		// Minimum execution time: 83_649_000 picoseconds.
+		Weight::from_parts(85_754_306, 4556)
+			// Standard Error: 12_757
+			.saturating_add(Weight::from_parts(1_616_356, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(15_u64))
 			.saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into())))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
@@ -1068,10 +1149,10 @@ impl WeightInfo for () {
 	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
 	fn set_state() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `1434`
+		//  Measured:  `1467`
 		//  Estimated: `4556`
-		// Minimum execution time: 34_544_000 picoseconds.
-		Weight::from_parts(35_910_000, 4556)
+		// Minimum execution time: 34_594_000 picoseconds.
+		Weight::from_parts(36_173_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1086,10 +1167,10 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3735`
-		// Minimum execution time: 14_111_000 picoseconds.
-		Weight::from_parts(15_204_218, 3735)
-			// Standard Error: 226
-			.saturating_add(Weight::from_parts(1_291, 0).saturating_mul(n.into()))
+		// Minimum execution time: 13_945_000 picoseconds.
+		Weight::from_parts(14_764_062, 3735)
+			// Standard Error: 127
+			.saturating_add(Weight::from_parts(1_406, 0).saturating_mul(n.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -1109,8 +1190,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `0`
 		//  Estimated: `0`
-		// Minimum execution time: 4_524_000 picoseconds.
-		Weight::from_parts(4_882_000, 0)
+		// Minimum execution time: 4_523_000 picoseconds.
+		Weight::from_parts(4_727_000, 0)
 			.saturating_add(RocksDbWeight::get().writes(6_u64))
 	}
 	/// Storage: `NominationPools::BondedPools` (r:1 w:1)
@@ -1119,8 +1200,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_975_000 picoseconds.
-		Weight::from_parts(18_549_000, 3719)
+		// Minimum execution time: 17_124_000 picoseconds.
+		Weight::from_parts(17_718_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1148,10 +1229,10 @@ impl WeightInfo for () {
 	/// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
 	fn chill() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `2143`
+		//  Measured:  `2176`
 		//  Estimated: `4556`
-		// Minimum execution time: 81_574_000 picoseconds.
-		Weight::from_parts(83_519_000, 4556)
+		// Minimum execution time: 78_293_000 picoseconds.
+		Weight::from_parts(81_177_000, 4556)
 			.saturating_add(RocksDbWeight::get().reads(11_u64))
 			.saturating_add(RocksDbWeight::get().writes(5_u64))
 	}
@@ -1167,8 +1248,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `804`
 		//  Estimated: `3719`
-		// Minimum execution time: 35_015_000 picoseconds.
-		Weight::from_parts(36_159_000, 3719)
+		// Minimum execution time: 33_105_000 picoseconds.
+		Weight::from_parts(34_106_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -1180,8 +1261,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `572`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_775_000 picoseconds.
-		Weight::from_parts(18_358_000, 3719)
+		// Minimum execution time: 16_710_000 picoseconds.
+		Weight::from_parts(17_269_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1191,8 +1272,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 16_997_000 picoseconds.
-		Weight::from_parts(18_041_000, 3719)
+		// Minimum execution time: 16_557_000 picoseconds.
+		Weight::from_parts(17_431_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1202,8 +1283,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `532`
 		//  Estimated: `3719`
-		// Minimum execution time: 17_000_000 picoseconds.
-		Weight::from_parts(17_807_000, 3719)
+		// Minimum execution time: 16_723_000 picoseconds.
+		Weight::from_parts(17_155_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1215,8 +1296,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `542`
 		//  Estimated: `3702`
-		// Minimum execution time: 14_803_000 picoseconds.
-		Weight::from_parts(15_401_000, 3702)
+		// Minimum execution time: 14_667_000 picoseconds.
+		Weight::from_parts(15_242_000, 3702)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -1232,8 +1313,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `1002`
 		//  Estimated: `3719`
-		// Minimum execution time: 69_759_000 picoseconds.
-		Weight::from_parts(71_985_000, 3719)
+		// Minimum execution time: 64_219_000 picoseconds.
+		Weight::from_parts(66_718_000, 3719)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -1249,9 +1330,56 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `901`
 		//  Estimated: `4764`
-		// Minimum execution time: 73_829_000 picoseconds.
-		Weight::from_parts(75_966_000, 4764)
+		// Minimum execution time: 70_284_000 picoseconds.
+		Weight::from_parts(71_375_000, 4764)
 			.saturating_add(RocksDbWeight::get().reads(4_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	fn apply_slash() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `694`
+		//  Estimated: `3702`
+		// Minimum execution time: 13_403_000 picoseconds.
+		Weight::from_parts(14_064_000, 3702)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	fn apply_slash_fail() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `732`
+		//  Estimated: `3702`
+		// Minimum execution time: 14_419_000 picoseconds.
+		Weight::from_parts(15_004_000, 3702)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+	}
+	fn pool_migrate() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `0`
+		//  Estimated: `0`
+		// Minimum execution time: 759_000 picoseconds.
+		Weight::from_parts(819_000, 0)
+	}
+	/// Storage: `NominationPools::PoolMembers` (r:1 w:0)
+	/// Proof: `NominationPools::PoolMembers` (`max_values`: None, `max_size`: Some(237), added: 2712, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::BondedPools` (r:1 w:0)
+	/// Proof: `NominationPools::BondedPools` (`max_values`: None, `max_size`: Some(254), added: 2729, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Bonded` (r:1 w:0)
+	/// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `Staking::Ledger` (r:1 w:0)
+	/// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(1091), added: 3566, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::SubPoolsStorage` (r:1 w:0)
+	/// Proof: `NominationPools::SubPoolsStorage` (`max_values`: None, `max_size`: Some(24382), added: 26857, mode: `MaxEncodedLen`)
+	/// Storage: `NominationPools::MinJoinBond` (r:1 w:0)
+	/// Proof: `NominationPools::MinJoinBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
+	fn migrate_delegation() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1648`
+		//  Estimated: `27847`
+		// Minimum execution time: 36_192_000 picoseconds.
+		Weight::from_parts(37_038_000, 27847)
+			.saturating_add(RocksDbWeight::get().reads(6_u64))
+	}
 }
diff --git a/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ea8eb20696931f8c45edc6f67e4d5af49412eccd
--- /dev/null
+++ b/substrate/frame/nomination-pools/test-delegate-stake/Cargo.toml
@@ -0,0 +1,41 @@
+[package]
+name = "pallet-nomination-pools-test-delegate-stake"
+version = "1.0.0"
+authors.workspace = true
+edition.workspace = true
+license = "Apache-2.0"
+homepage = "https://substrate.io"
+repository.workspace = true
+description = "FRAME nomination pools pallet tests with the staking pallet"
+publish = false
+
+[lints]
+workspace = true
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dev-dependencies]
+codec = { package = "parity-scale-codec", version = "3.6.12", features = ["derive"] }
+scale-info = { version = "2.11.1", features = ["derive"] }
+
+sp-runtime = { path = "../../../primitives/runtime" }
+sp-io = { path = "../../../primitives/io" }
+sp-std = { path = "../../../primitives/std" }
+sp-staking = { path = "../../../primitives/staking" }
+sp-core = { path = "../../../primitives/core" }
+
+frame-system = { path = "../../system" }
+frame-support = { path = "../../support" }
+frame-election-provider-support = { path = "../../election-provider-support" }
+
+pallet-timestamp = { path = "../../timestamp" }
+pallet-balances = { path = "../../balances" }
+pallet-staking = { path = "../../staking" }
+pallet-delegated-staking = { path = "../../delegated-staking" }
+pallet-bags-list = { path = "../../bags-list" }
+pallet-staking-reward-curve = { path = "../../staking/reward-curve" }
+pallet-nomination-pools = { path = ".." }
+
+sp-tracing = { path = "../../../primitives/tracing" }
+log = { workspace = true, default-features = true }
diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d3235760ed23b06d0e7f0beb5530a3f77c5aee61
--- /dev/null
+++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs
@@ -0,0 +1,1158 @@
+// 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.
+
+#![cfg(test)]
+
+mod mock;
+
+use frame_support::{
+	assert_noop, assert_ok,
+	traits::{fungible::InspectHold, Currency},
+};
+use mock::*;
+use pallet_nomination_pools::{
+	BondExtra, BondedPools, Error as PoolsError, Event as PoolsEvent, LastPoolId, PoolMember,
+	PoolMembers, PoolState,
+};
+use pallet_staking::{
+	CurrentEra, Error as StakingError, Event as StakingEvent, Payee, RewardDestination,
+};
+
+use pallet_delegated_staking::{Error as DelegatedStakingError, Event as DelegatedStakingEvent};
+
+use sp_runtime::{bounded_btree_map, traits::Zero};
+
+#[test]
+fn pool_lifecycle_e2e() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(Balances::minimum_balance(), 5);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
+		assert_eq!(LastPoolId::<Runtime>::get(), 1);
+
+		// have the pool nominate.
+		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
+			]
+		);
+
+		// have two members join
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
+		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
+				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
+			]
+		);
+
+		// pool goes into destroying
+		assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
+
+		// depositor cannot unbond yet.
+		assert_noop!(
+			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
+			PoolsError::<Runtime>::MinimumBondNotMet,
+		);
+
+		// now the members want to unbond.
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
+
+		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
+		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
+		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
+		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
+				PoolsEvent::Unbonded { member: 20, pool_id: 1, points: 10, balance: 10, era: 3 },
+				PoolsEvent::Unbonded { member: 21, pool_id: 1, points: 10, balance: 10, era: 3 },
+			]
+		);
+
+		// depositor cannot still unbond
+		assert_noop!(
+			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
+			PoolsError::<Runtime>::MinimumBondNotMet,
+		);
+
+		for e in 1..BondingDuration::get() {
+			CurrentEra::<Runtime>::set(Some(e));
+			assert_noop!(
+				Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0),
+				PoolsError::<Runtime>::CannotWithdrawAny
+			);
+		}
+
+		// members are now unlocked.
+		CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
+
+		// depositor cannot still unbond
+		assert_noop!(
+			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
+			PoolsError::<Runtime>::MinimumBondNotMet,
+		);
+
+		// but members can now withdraw.
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
+		assert!(PoolMembers::<Runtime>::get(20).is_none());
+		assert!(PoolMembers::<Runtime>::get(21).is_none());
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Withdrawn { member: 20, pool_id: 1, points: 10, balance: 10 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
+				PoolsEvent::Withdrawn { member: 21, pool_id: 1, points: 10, balance: 10 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 21 },
+			]
+		);
+
+		// as soon as all members have left, the depositor can try to unbond, but since the
+		// min-nominator intention is set, they must chill first.
+		assert_noop!(
+			Pools::unbond(RuntimeOrigin::signed(10), 10, 50),
+			pallet_staking::Error::<Runtime>::InsufficientBond
+		);
+
+		assert_ok!(Pools::chill(RuntimeOrigin::signed(10), 1));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 50));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Chilled { stash: POOL1_BONDED },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 50 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 50, balance: 50, era: 6 }]
+		);
+
+		// waiting another bonding duration:
+		CurrentEra::<Runtime>::set(Some(BondingDuration::get() * 2));
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 1));
+
+		// pools is fully destroyed now.
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 50 },]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Withdrawn { member: 10, pool_id: 1, points: 50, balance: 50 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
+				PoolsEvent::Destroyed { pool_id: 1 }
+			]
+		);
+	})
+}
+
+#[test]
+fn pool_chill_e2e() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(Balances::minimum_balance(), 5);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
+		assert_eq!(LastPoolId::<Runtime>::get(), 1);
+
+		// have the pool nominate.
+		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
+			]
+		);
+
+		// have two members join
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
+		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
+				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
+			]
+		);
+
+		// in case depositor does not have more than `MinNominatorBond` staked, we can end up in
+		// situation where a member unbonding would cause pool balance to drop below
+		// `MinNominatorBond` and hence not allowed. This can happen if the `MinNominatorBond` is
+		// increased after the pool is created.
+		assert_ok!(Staking::set_staking_configs(
+			RuntimeOrigin::root(),
+			pallet_staking::ConfigOp::Set(55), // minimum nominator bond
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+		));
+
+		// members can unbond as long as total stake of the pool is above min nominator bond
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10),);
+		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().unbonding_eras.len(), 1);
+		assert_eq!(PoolMembers::<Runtime>::get(20).unwrap().points, 0);
+
+		// this member cannot unbond since it will cause `pool stake < MinNominatorBond`
+		assert_noop!(
+			Pools::unbond(RuntimeOrigin::signed(21), 21, 10),
+			StakingError::<Runtime>::InsufficientBond,
+		);
+
+		// members can call `chill` permissionlessly now
+		assert_ok!(Pools::chill(RuntimeOrigin::signed(20), 1));
+
+		// now another member can unbond.
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
+		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().unbonding_eras.len(), 1);
+		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().points, 0);
+
+		// nominator can not resume nomination until depositor have enough stake
+		assert_noop!(
+			Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
+			PoolsError::<Runtime>::MinimumBondNotMet,
+		);
+
+		// other members joining pool does not affect the depositor's ability to resume nomination
+		assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1));
+
+		assert_noop!(
+			Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]),
+			PoolsError::<Runtime>::MinimumBondNotMet,
+		);
+
+		// depositor can bond extra stake
+		assert_ok!(Pools::bond_extra(RuntimeOrigin::signed(10), BondExtra::FreeBalance(10)));
+
+		// `chill` can not be called permissionlessly anymore
+		assert_noop!(
+			Pools::chill(RuntimeOrigin::signed(20), 1),
+			PoolsError::<Runtime>::NotNominator,
+		);
+
+		// now nominator can resume nomination
+		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
+
+		// skip to make the unbonding period end.
+		CurrentEra::<Runtime>::set(Some(BondingDuration::get()));
+
+		// members can now withdraw.
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Chilled { stash: POOL1_BONDED },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // other member bonding
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 }, // depositor bond extra
+				StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 20 },
+			]
+		);
+	})
+}
+
+#[test]
+fn pool_slash_e2e() {
+	new_test_ext().execute_with(|| {
+		ExistentialDeposit::set(1);
+		assert_eq!(Balances::minimum_balance(), 1);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
+		assert_eq!(LastPoolId::<Runtime>::get(), 1);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
+			]
+		);
+
+		assert_eq!(
+			Payee::<Runtime>::get(POOL1_BONDED),
+			Some(RewardDestination::Account(POOL1_REWARD))
+		);
+
+		// have two members join
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 20, 1));
+		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 20, 1));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 20 }
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 20, joined: true },
+				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 20, joined: true },
+			]
+		);
+
+		// now let's progress a bit.
+		CurrentEra::<Runtime>::set(Some(1));
+
+		// 20 / 80 of the total funds are unlocked, and safe from any further slash.
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 4 },
+				PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 4 }
+			]
+		);
+
+		CurrentEra::<Runtime>::set(Some(2));
+
+		// note: depositor cannot fully unbond at this point.
+		// these funds will still get slashed.
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 10));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 10));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Unbonded { member: 10, pool_id: 1, balance: 10, points: 10, era: 5 },
+				PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 10, points: 10, era: 5 },
+				PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 5 },
+			]
+		);
+
+		// At this point, 20 are safe from slash, 30 are unlocking but vulnerable to slash, and and
+		// another 30 are active and vulnerable to slash. Let's slash half of them.
+		pallet_staking::slashing::do_slash::<Runtime>(
+			&POOL1_BONDED,
+			30,
+			&mut Default::default(),
+			&mut Default::default(),
+			2, // slash era 2, affects chunks at era 5 onwards.
+		);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				// 30 has been slashed to 15 (15 slash)
+				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 5, balance: 15 },
+				// 30 has been slashed to 15 (15 slash)
+				PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
+			]
+		);
+
+		CurrentEra::<Runtime>::set(Some(3));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
+
+		assert_eq!(
+			PoolMembers::<Runtime>::get(21).unwrap(),
+			PoolMember {
+				pool_id: 1,
+				points: 0,
+				last_recorded_reward_counter: Zero::zero(),
+				// the 10 points unlocked just now correspond to 5 points in the unbond pool.
+				unbonding_eras: bounded_btree_map!(5 => 10, 6 => 5)
+			}
+		);
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 5, points: 5, era: 6 }]
+		);
+
+		// now we start withdrawing. we do it all at once, at era 6 where 20 and 21 are fully free.
+		CurrentEra::<Runtime>::set(Some(6));
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 0));
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 0));
+
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				// 20 had unbonded 10 safely, and 10 got slashed by half.
+				PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 10 + 5, points: 20 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 20 },
+				// 21 unbonded all of it after the slash
+				PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 5 + 5, points: 15 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 21 }
+			]
+		);
+		assert_eq!(
+			staking_events_since_last_call(),
+			// a 10 (un-slashed) + 10/2 (slashed) balance from 10 has also been unlocked
+			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 15 + 10 + 15 }]
+		);
+
+		// now, finally, we can unbond the depositor further than their current limit.
+		assert_ok!(Pools::set_state(RuntimeOrigin::signed(10), 1, PoolState::Destroying));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(10), 10, 20));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::StateChanged { pool_id: 1, new_state: PoolState::Destroying },
+				PoolsEvent::Unbonded { member: 10, pool_id: 1, points: 10, balance: 10, era: 9 }
+			]
+		);
+
+		CurrentEra::<Runtime>::set(Some(9));
+		assert_eq!(
+			PoolMembers::<Runtime>::get(10).unwrap(),
+			PoolMember {
+				pool_id: 1,
+				points: 0,
+				last_recorded_reward_counter: Zero::zero(),
+				unbonding_eras: bounded_btree_map!(4 => 10, 5 => 10, 9 => 10)
+			}
+		);
+		// withdraw the depositor, they should lose 12 balance in total due to slash.
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(10), 10, 0));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Withdrawn { member: 10, pool_id: 1, balance: 10 + 15, points: 30 },
+				PoolsEvent::MemberRemoved { pool_id: 1, member: 10 },
+				PoolsEvent::Destroyed { pool_id: 1 }
+			]
+		);
+	});
+}
+
+#[test]
+fn pool_slash_proportional() {
+	// a typical example where 3 pool members unbond in era 99, 100, and 101, and a slash that
+	// happened in era 100 should only affect the latter two.
+	new_test_ext().execute_with(|| {
+		ExistentialDeposit::set(1);
+		BondingDuration::set(28);
+		assert_eq!(Balances::minimum_balance(), 1);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
+		assert_eq!(LastPoolId::<T>::get(), 1);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
+		);
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![DelegatedStakingEvent::Delegated {
+				agent: POOL1_BONDED,
+				delegator: 10,
+				amount: 40
+			}]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
+			]
+		);
+
+		// have two members join
+		let bond = 20;
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
+		assert_ok!(Pools::join(RuntimeOrigin::signed(21), bond, 1));
+		assert_ok!(Pools::join(RuntimeOrigin::signed(22), bond, 1));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond },
+			]
+		);
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![
+				DelegatedStakingEvent::Delegated {
+					agent: POOL1_BONDED,
+					delegator: 20,
+					amount: bond
+				},
+				DelegatedStakingEvent::Delegated {
+					agent: POOL1_BONDED,
+					delegator: 21,
+					amount: bond
+				},
+				DelegatedStakingEvent::Delegated {
+					agent: POOL1_BONDED,
+					delegator: 22,
+					amount: bond
+				}
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true },
+				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: bond, joined: true },
+				PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: bond, joined: true },
+			]
+		);
+
+		// now let's progress a lot.
+		CurrentEra::<T>::set(Some(99));
+
+		// and unbond
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded {
+				member: 20,
+				pool_id: 1,
+				balance: bond,
+				points: bond,
+				era: 127
+			}]
+		);
+
+		CurrentEra::<T>::set(Some(100));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, bond));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded {
+				member: 21,
+				pool_id: 1,
+				balance: bond,
+				points: bond,
+				era: 128
+			}]
+		);
+
+		CurrentEra::<T>::set(Some(101));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, bond));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond },]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded {
+				member: 22,
+				pool_id: 1,
+				balance: bond,
+				points: bond,
+				era: 129
+			}]
+		);
+
+		// Apply a slash that happened in era 100. This is typically applied with a delay.
+		// Of the total 100, 50 is slashed.
+		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
+		pallet_staking::slashing::do_slash::<Runtime>(
+			&POOL1_BONDED,
+			50,
+			&mut Default::default(),
+			&mut Default::default(),
+			100,
+		);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				// This era got slashed 12.5, which rounded up to 13.
+				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 128, balance: 7 },
+				// This era got slashed 12 instead of 12.5 because an earlier chunk got 0.5 more
+				// slashed, and 12 is all the remaining slash
+				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 129, balance: 8 },
+				// Bonded pool got slashed for 25, remaining 15 in it.
+				PoolsEvent::PoolSlashed { pool_id: 1, balance: 15 }
+			]
+		);
+
+		// 21's balance in the pool is slashed.
+		assert_eq!(PoolMembers::<Runtime>::get(21).unwrap().total_balance(), 7);
+		// But their actual balance is still unslashed.
+		assert_eq!(Balances::total_balance_on_hold(&21), bond);
+		// apply slash permissionlessly.
+		assert_ok!(Pools::apply_slash(RuntimeOrigin::signed(10), 21));
+		// member balance is slashed.
+		assert_eq!(Balances::total_balance_on_hold(&21), 7);
+
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![DelegatedStakingEvent::Slashed {
+				agent: POOL1_BONDED,
+				delegator: 21,
+				amount: bond - 7
+			}]
+		);
+
+		// 22 balance isn't slashed yet as well.
+		assert_eq!(PoolMembers::<Runtime>::get(22).unwrap().total_balance(), 8);
+		assert_eq!(Balances::total_balance_on_hold(&22), bond);
+
+		// they try to withdraw. This should slash them.
+		CurrentEra::<T>::set(Some(129));
+		let pre_balance = Balances::free_balance(&22);
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 0));
+		// all balance should be released.
+		assert_eq!(Balances::total_balance_on_hold(&22), 0);
+		assert_eq!(Balances::free_balance(&22), pre_balance + 8);
+
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![
+				DelegatedStakingEvent::Slashed {
+					agent: POOL1_BONDED,
+					delegator: 22,
+					amount: bond - 8
+				},
+				DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 22, amount: 8 },
+			]
+		);
+	});
+}
+
+#[test]
+fn pool_slash_non_proportional_only_bonded_pool() {
+	// A typical example where a pool member unbonds in era 99, and they can get away with a slash
+	// that happened in era 100, as long as the pool has enough active bond to cover the slash. If
+	// everything else in the slashing/staking system works, this should always be the case.
+	// Nonetheless, `ledger.slash` has been written such that it will slash greedily from any chunk
+	// if it runs out of chunks that it thinks should be affected by the slash.
+	new_test_ext().execute_with(|| {
+		ExistentialDeposit::set(1);
+		BondingDuration::set(28);
+		assert_eq!(Balances::minimum_balance(), 1);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
+			]
+		);
+
+		// have two members join
+		let bond = 20;
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
+		);
+
+		// progress and unbond.
+		CurrentEra::<T>::set(Some(99));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded {
+				member: 20,
+				pool_id: 1,
+				balance: bond,
+				points: bond,
+				era: 127
+			}]
+		);
+
+		// slash for 30. This will be deducted only from the bonded pool.
+		CurrentEra::<T>::set(Some(100));
+		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
+		pallet_staking::slashing::do_slash::<Runtime>(
+			&POOL1_BONDED,
+			30,
+			&mut Default::default(),
+			&mut Default::default(),
+			100,
+		);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 30 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::PoolSlashed { pool_id: 1, balance: 10 }]
+		);
+	});
+}
+
+#[test]
+fn pool_slash_non_proportional_bonded_pool_and_chunks() {
+	// An uncommon example where even though some funds are unlocked such that they should not be
+	// affected by a slash, we still slash out of them. This should not happen at all. If a
+	// nomination has unbonded, from the next era onwards, their exposure will drop, so if an era
+	// happens in that era, then their share of that slash should naturally be less, such that only
+	// their active ledger stake is enough to compensate it.
+	new_test_ext().execute_with(|| {
+		ExistentialDeposit::set(1);
+		BondingDuration::set(28);
+		assert_eq!(Balances::minimum_balance(), 1);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool, we know this has id 1.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 40, 10, 10, 10));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 40 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 40, joined: true },
+			]
+		);
+
+		// have two members join
+		let bond = 20;
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), bond, 1));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: bond }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: bond, joined: true }]
+		);
+
+		// progress and unbond.
+		CurrentEra::<T>::set(Some(99));
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, bond));
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Unbonded { stash: POOL1_BONDED, amount: bond }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![PoolsEvent::Unbonded {
+				member: 20,
+				pool_id: 1,
+				balance: bond,
+				points: bond,
+				era: 127
+			}]
+		);
+
+		// slash 50. This will be deducted only from the bonded pool and one of the unbonding pools.
+		CurrentEra::<T>::set(Some(100));
+		assert_eq!(BondedPools::<T>::get(1).unwrap().points, 40);
+		pallet_staking::slashing::do_slash::<Runtime>(
+			&POOL1_BONDED,
+			50,
+			&mut Default::default(),
+			&mut Default::default(),
+			100,
+		);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Slashed { staker: POOL1_BONDED, amount: 50 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				// out of 20, 10 was taken.
+				PoolsEvent::UnbondingPoolSlashed { pool_id: 1, era: 127, balance: 10 },
+				// out of 40, all was taken.
+				PoolsEvent::PoolSlashed { pool_id: 1, balance: 0 }
+			]
+		);
+	});
+}
+#[test]
+fn pool_migration_e2e() {
+	new_test_ext().execute_with(|| {
+		LegacyAdapter::set(true);
+		assert_eq!(Balances::minimum_balance(), 5);
+		assert_eq!(Staking::current_era(), None);
+
+		// create the pool with TransferStake strategy.
+		assert_ok!(Pools::create(RuntimeOrigin::signed(10), 50, 10, 10, 10));
+		assert_eq!(LastPoolId::<Runtime>::get(), 1);
+
+		// have the pool nominate.
+		assert_ok!(Pools::nominate(RuntimeOrigin::signed(10), 1, vec![1, 2, 3]));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![StakingEvent::Bonded { stash: POOL1_BONDED, amount: 50 }]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Created { depositor: 10, pool_id: 1 },
+				PoolsEvent::Bonded { member: 10, pool_id: 1, bonded: 50, joined: true },
+			]
+		);
+
+		// have three members join
+		let pre_20 = Balances::free_balance(20);
+		assert_ok!(Pools::join(RuntimeOrigin::signed(20), 10, 1));
+		let pre_21 = Balances::free_balance(21);
+		assert_ok!(Pools::join(RuntimeOrigin::signed(21), 10, 1));
+		let pre_22 = Balances::free_balance(22);
+		assert_ok!(Pools::join(RuntimeOrigin::signed(22), 10, 1));
+
+		// verify members balance is moved to pool.
+		assert_eq!(Balances::free_balance(20), pre_20 - 10);
+		assert_eq!(Balances::free_balance(21), pre_21 - 10);
+		assert_eq!(Balances::free_balance(22), pre_22 - 10);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Bonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Bonded { member: 20, pool_id: 1, bonded: 10, joined: true },
+				PoolsEvent::Bonded { member: 21, pool_id: 1, bonded: 10, joined: true },
+				PoolsEvent::Bonded { member: 22, pool_id: 1, bonded: 10, joined: true },
+			]
+		);
+
+		CurrentEra::<Runtime>::set(Some(2));
+		// 20 is partially unbonding
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(20), 20, 5));
+
+		CurrentEra::<Runtime>::set(Some(3));
+		// 21 is fully unbonding
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(21), 21, 10));
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 },
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 10 },
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Unbonded { member: 20, pool_id: 1, balance: 5, points: 5, era: 5 },
+				PoolsEvent::Unbonded { member: 21, pool_id: 1, balance: 10, points: 10, era: 6 },
+			]
+		);
+
+		// with `TransferStake`, we can't migrate.
+		assert_noop!(
+			Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1),
+			PoolsError::<Runtime>::NotSupported
+		);
+
+		// we reset the adapter to `DelegateStake`.
+		LegacyAdapter::set(false);
+
+		// cannot migrate the member delegation unless pool is migrated first.
+		assert_noop!(
+			Pools::migrate_delegation(RuntimeOrigin::signed(10), 20),
+			PoolsError::<Runtime>::PoolNotMigrated
+		);
+
+		// migrate the pool.
+		assert_ok!(Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1));
+
+		// migrate again does not work.
+		assert_noop!(
+			Pools::migrate_pool_to_delegate_stake(RuntimeOrigin::signed(10), 1),
+			PoolsError::<Runtime>::PoolAlreadyMigrated
+		);
+
+		// unclaimed delegations to the pool are stored in this account.
+		let proxy_delegator_1 = DelegatedStaking::generate_proxy_delegator(POOL1_BONDED);
+
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![DelegatedStakingEvent::Delegated {
+				agent: POOL1_BONDED,
+				delegator: proxy_delegator_1,
+				amount: 50 + 10 * 3
+			}]
+		);
+
+		// move to era 5 when 20 can withdraw unbonded funds.
+		CurrentEra::<Runtime>::set(Some(5));
+		// Unbond works even without claiming delegation. Lets unbond 22.
+		assert_ok!(Pools::unbond(RuntimeOrigin::signed(22), 22, 5));
+
+		// withdraw fails for 20 before claiming delegation
+		assert_noop!(
+			Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 10),
+			DelegatedStakingError::<Runtime>::NotDelegator
+		);
+
+		let pre_claim_balance_20 = Balances::total_balance(&20);
+		assert_eq!(Balances::total_balance_on_hold(&20), 0);
+
+		// migrate delegation for 20. This is permissionless and can be called by anyone.
+		assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 20));
+
+		// tokens moved to 20's account and held there.
+		assert_eq!(Balances::total_balance(&20), pre_claim_balance_20 + 10);
+		assert_eq!(Balances::total_balance_on_hold(&20), 10);
+
+		// withdraw works now
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(20), 20, 5));
+
+		// balance unlocked in 20's account
+		assert_eq!(Balances::total_balance_on_hold(&20), 5);
+		assert_eq!(Balances::total_balance(&20), pre_claim_balance_20 + 10);
+
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Unbonded { stash: POOL1_BONDED, amount: 5 },
+				StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 }
+			]
+		);
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Unbonded { member: 22, pool_id: 1, balance: 5, points: 5, era: 8 },
+				PoolsEvent::Withdrawn { member: 20, pool_id: 1, balance: 5, points: 5 },
+			]
+		);
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![
+				DelegatedStakingEvent::MigratedDelegation {
+					agent: POOL1_BONDED,
+					delegator: 20,
+					amount: 10
+				},
+				DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 20, amount: 5 }
+			]
+		);
+
+		// MIGRATE 21
+		let pre_migrate_balance_21 = Balances::total_balance(&21);
+		assert_eq!(Balances::total_balance_on_hold(&21), 0);
+
+		// migrate delegation for 21.
+		assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 21));
+
+		// tokens moved to 21's account and held there.
+		assert_eq!(Balances::total_balance(&21), pre_migrate_balance_21 + 10);
+		assert_eq!(Balances::total_balance_on_hold(&21), 10);
+
+		// withdraw fails since 21 only unbonds at era 6.
+		assert_noop!(
+			Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 10),
+			PoolsError::<Runtime>::CannotWithdrawAny
+		);
+
+		// go to era when 21 can unbond
+		CurrentEra::<Runtime>::set(Some(6));
+
+		// withdraw works now
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(21), 21, 10));
+
+		// all balance unlocked in 21's account
+		assert_eq!(Balances::total_balance_on_hold(&21), 0);
+		assert_eq!(Balances::total_balance(&21), pre_migrate_balance_21 + 10);
+
+		// MIGRATE 22
+		let pre_migrate_balance_22 = Balances::total_balance(&22);
+		assert_eq!(Balances::total_balance_on_hold(&22), 0);
+
+		// migrate delegation for 22.
+		assert_ok!(Pools::migrate_delegation(RuntimeOrigin::signed(10), 22));
+
+		// tokens moved to 22's account and held there.
+		assert_eq!(Balances::total_balance(&22), pre_migrate_balance_22 + 10);
+		assert_eq!(Balances::total_balance_on_hold(&22), 10);
+
+		// withdraw fails since 22 only unbonds at era 8.
+		assert_noop!(
+			Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 5),
+			PoolsError::<Runtime>::CannotWithdrawAny
+		);
+
+		// go to era when 22 can unbond
+		CurrentEra::<Runtime>::set(Some(10));
+
+		// withdraw works now
+		assert_ok!(Pools::withdraw_unbonded(RuntimeOrigin::signed(22), 22, 10));
+
+		// balance of 5 unlocked in 22's account
+		assert_eq!(Balances::total_balance_on_hold(&22), 10 - 5);
+
+		// assert events for 21 and 22.
+		assert_eq!(
+			staking_events_since_last_call(),
+			vec![
+				StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 10 },
+				StakingEvent::Withdrawn { stash: POOL1_BONDED, amount: 5 }
+			]
+		);
+
+		assert_eq!(
+			pool_events_since_last_call(),
+			vec![
+				PoolsEvent::Withdrawn { member: 21, pool_id: 1, balance: 10, points: 10 },
+				// 21 was fully unbonding and removed from pool.
+				PoolsEvent::MemberRemoved { member: 21, pool_id: 1 },
+				PoolsEvent::Withdrawn { member: 22, pool_id: 1, balance: 5, points: 5 },
+			]
+		);
+		assert_eq!(
+			delegated_staking_events_since_last_call(),
+			vec![
+				DelegatedStakingEvent::MigratedDelegation {
+					agent: POOL1_BONDED,
+					delegator: 21,
+					amount: 10
+				},
+				DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 21, amount: 10 },
+				DelegatedStakingEvent::MigratedDelegation {
+					agent: POOL1_BONDED,
+					delegator: 22,
+					amount: 10
+				},
+				DelegatedStakingEvent::Released { agent: POOL1_BONDED, delegator: 22, amount: 5 }
+			]
+		);
+	})
+}
diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1c0a0166fd9a9d25bac8252106a35383c11a1f9c
--- /dev/null
+++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs
@@ -0,0 +1,406 @@
+// 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 frame_election_provider_support::VoteWeight;
+use frame_support::{
+	assert_ok, derive_impl,
+	pallet_prelude::*,
+	parameter_types,
+	traits::{ConstU64, ConstU8},
+	PalletId,
+};
+use frame_system::EnsureRoot;
+use pallet_nomination_pools::{adapter::StakeStrategyType, BondType};
+use sp_runtime::{
+	traits::{Convert, IdentityLookup},
+	BuildStorage, FixedU128, Perbill,
+};
+
+type AccountId = u128;
+type Nonce = u32;
+type BlockNumber = u64;
+type Balance = u128;
+
+pub(crate) type T = Runtime;
+
+pub(crate) const POOL1_BONDED: AccountId = 20318131474730217858575332831085u128;
+pub(crate) const POOL1_REWARD: AccountId = 20397359637244482196168876781421u128;
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig)]
+impl frame_system::Config for Runtime {
+	type BaseCallFilter = frame_support::traits::Everything;
+	type BlockWeights = ();
+	type BlockLength = ();
+	type DbWeight = ();
+	type RuntimeOrigin = RuntimeOrigin;
+	type Nonce = Nonce;
+	type RuntimeCall = RuntimeCall;
+	type Hash = sp_core::H256;
+	type Hashing = sp_runtime::traits::BlakeTwo256;
+	type AccountId = AccountId;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Block = Block;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ();
+	type Version = ();
+	type PalletInfo = PalletInfo;
+	type AccountData = pallet_balances::AccountData<Balance>;
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+	type SystemWeightInfo = ();
+	type SS58Prefix = ();
+	type OnSetCode = ();
+	type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+impl pallet_timestamp::Config for Runtime {
+	type Moment = u64;
+	type OnTimestampSet = ();
+	type MinimumPeriod = ConstU64<5>;
+	type WeightInfo = ();
+}
+
+parameter_types! {
+	pub static ExistentialDeposit: Balance = 5;
+}
+
+impl pallet_balances::Config for Runtime {
+	type MaxLocks = ();
+	type MaxReserves = ();
+	type ReserveIdentifier = [u8; 8];
+	type Balance = Balance;
+	type RuntimeEvent = RuntimeEvent;
+	type DustRemoval = ();
+	type ExistentialDeposit = ExistentialDeposit;
+	type AccountStore = System;
+	type WeightInfo = ();
+	type FreezeIdentifier = RuntimeFreezeReason;
+	type MaxFreezes = ConstU32<1>;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type RuntimeFreezeReason = ();
+}
+
+pallet_staking_reward_curve::build! {
+	const I_NPOS: sp_runtime::curve::PiecewiseLinear<'static> = curve!(
+		min_inflation: 0_025_000,
+		max_inflation: 0_100_000,
+		ideal_stake: 0_500_000,
+		falloff: 0_050_000,
+		max_piece_count: 40,
+		test_precision: 0_005_000,
+	);
+}
+
+parameter_types! {
+	pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS;
+	pub static BondingDuration: u32 = 3;
+}
+
+impl pallet_staking::Config for Runtime {
+	type Currency = Balances;
+	type CurrencyBalance = Balance;
+	type UnixTime = pallet_timestamp::Pallet<Self>;
+	type CurrencyToVote = ();
+	type RewardRemainder = ();
+	type RuntimeEvent = RuntimeEvent;
+	type Slash = ();
+	type Reward = ();
+	type SessionsPerEra = ();
+	type SlashDeferDuration = ();
+	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
+	type BondingDuration = BondingDuration;
+	type SessionInterface = ();
+	type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
+	type NextNewSession = ();
+	type MaxExposurePageSize = ConstU32<64>;
+	type ElectionProvider =
+		frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>;
+	type GenesisElectionProvider = Self::ElectionProvider;
+	type VoterList = VoterList;
+	type TargetList = pallet_staking::UseValidatorsMap<Self>;
+	type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
+	type MaxUnlockingChunks = ConstU32<32>;
+	type MaxControllersInDeprecationBatch = ConstU32<100>;
+	type HistoryDepth = ConstU32<84>;
+	type EventListeners = (Pools, DelegatedStaking);
+	type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
+	type WeightInfo = ();
+	type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
+}
+
+parameter_types! {
+	pub static BagThresholds: &'static [VoteWeight] = &[10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000];
+}
+
+type VoterBagsListInstance = pallet_bags_list::Instance1;
+impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = ();
+	type BagThresholds = BagThresholds;
+	type ScoreProvider = Staking;
+	type Score = VoteWeight;
+}
+
+pub struct BalanceToU256;
+impl Convert<Balance, sp_core::U256> for BalanceToU256 {
+	fn convert(n: Balance) -> sp_core::U256 {
+		n.into()
+	}
+}
+
+pub struct U256ToBalance;
+impl Convert<sp_core::U256, Balance> for U256ToBalance {
+	fn convert(n: sp_core::U256) -> Balance {
+		n.try_into().unwrap()
+	}
+}
+
+parameter_types! {
+	pub const PostUnbondingPoolsWindow: u32 = 10;
+	pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
+	pub static LegacyAdapter: bool = false;
+}
+
+pub struct MockAdapter;
+type DelegateStake =
+	pallet_nomination_pools::adapter::DelegateStake<Runtime, Staking, DelegatedStaking>;
+type TransferStake = pallet_nomination_pools::adapter::TransferStake<Runtime, Staking>;
+impl pallet_nomination_pools::adapter::StakeStrategy for MockAdapter {
+	type Balance = Balance;
+	type AccountId = AccountId;
+	type CoreStaking = Staking;
+
+	fn strategy_type() -> StakeStrategyType {
+		if LegacyAdapter::get() {
+			return TransferStake::strategy_type()
+		}
+		DelegateStake::strategy_type()
+	}
+	fn transferable_balance(pool_account: &Self::AccountId) -> Self::Balance {
+		if LegacyAdapter::get() {
+			return TransferStake::transferable_balance(pool_account)
+		}
+		DelegateStake::transferable_balance(pool_account)
+	}
+
+	fn total_balance(pool_account: &Self::AccountId) -> Self::Balance {
+		if LegacyAdapter::get() {
+			return TransferStake::total_balance(pool_account)
+		}
+		DelegateStake::total_balance(pool_account)
+	}
+
+	fn member_delegation_balance(member_account: &Self::AccountId) -> Self::Balance {
+		if LegacyAdapter::get() {
+			return TransferStake::member_delegation_balance(member_account)
+		}
+		DelegateStake::member_delegation_balance(member_account)
+	}
+
+	fn pledge_bond(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: Self::Balance,
+		bond_type: BondType,
+	) -> DispatchResult {
+		if LegacyAdapter::get() {
+			return TransferStake::pledge_bond(who, pool_account, reward_account, amount, bond_type)
+		}
+		DelegateStake::pledge_bond(who, pool_account, reward_account, amount, bond_type)
+	}
+
+	fn member_withdraw(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		amount: Self::Balance,
+		num_slashing_spans: u32,
+	) -> DispatchResult {
+		if LegacyAdapter::get() {
+			return TransferStake::member_withdraw(who, pool_account, amount, num_slashing_spans)
+		}
+		DelegateStake::member_withdraw(who, pool_account, amount, num_slashing_spans)
+	}
+
+	fn has_pending_slash(pool_account: &Self::AccountId) -> bool {
+		if LegacyAdapter::get() {
+			return TransferStake::has_pending_slash(pool_account)
+		}
+		DelegateStake::has_pending_slash(pool_account)
+	}
+
+	fn member_slash(
+		who: &Self::AccountId,
+		pool_account: &Self::AccountId,
+		amount: Self::Balance,
+		maybe_reporter: Option<Self::AccountId>,
+	) -> DispatchResult {
+		if LegacyAdapter::get() {
+			return TransferStake::member_slash(who, pool_account, amount, maybe_reporter)
+		}
+		DelegateStake::member_slash(who, pool_account, amount, maybe_reporter)
+	}
+
+	fn migrate_nominator_to_agent(
+		agent: &Self::AccountId,
+		reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		if LegacyAdapter::get() {
+			return TransferStake::migrate_nominator_to_agent(agent, reward_account)
+		}
+		DelegateStake::migrate_nominator_to_agent(agent, reward_account)
+	}
+
+	fn migrate_delegation(
+		agent: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+	) -> DispatchResult {
+		if LegacyAdapter::get() {
+			return TransferStake::migrate_delegation(agent, delegator, value)
+		}
+		DelegateStake::migrate_delegation(agent, delegator, value)
+	}
+}
+impl pallet_nomination_pools::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type WeightInfo = ();
+	type Currency = Balances;
+	type RuntimeFreezeReason = RuntimeFreezeReason;
+	type RewardCounter = FixedU128;
+	type BalanceToU256 = BalanceToU256;
+	type U256ToBalance = U256ToBalance;
+	type StakeAdapter = MockAdapter;
+	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
+	type MaxMetadataLen = ConstU32<256>;
+	type MaxUnbonding = ConstU32<8>;
+	type MaxPointsToBalance = ConstU8<10>;
+	type PalletId = PoolsPalletId;
+	type AdminOrigin = EnsureRoot<AccountId>;
+}
+
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
+}
+impl pallet_delegated_staking::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type PalletId = DelegatedStakingPalletId;
+	type Currency = Balances;
+	type OnSlash = ();
+	type SlashRewardFraction = SlashRewardFraction;
+	type RuntimeHoldReason = RuntimeHoldReason;
+	type CoreStaking = Staking;
+}
+type Block = frame_system::mocking::MockBlock<Runtime>;
+
+frame_support::construct_runtime!(
+	pub enum Runtime {
+		System: frame_system,
+		Timestamp: pallet_timestamp,
+		Balances: pallet_balances,
+		Staking: pallet_staking,
+		VoterList: pallet_bags_list::<Instance1>,
+		Pools: pallet_nomination_pools,
+		DelegatedStaking: pallet_delegated_staking,
+	}
+);
+
+pub fn new_test_ext() -> sp_io::TestExternalities {
+	sp_tracing::try_init_simple();
+	let mut storage = frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
+	let _ = pallet_nomination_pools::GenesisConfig::<Runtime> {
+		min_join_bond: 2,
+		min_create_bond: 2,
+		max_pools: Some(3),
+		max_members_per_pool: Some(5),
+		max_members: Some(3 * 5),
+		global_max_commission: Some(Perbill::from_percent(90)),
+	}
+	.assimilate_storage(&mut storage)
+	.unwrap();
+
+	let _ = pallet_balances::GenesisConfig::<Runtime> {
+		balances: vec![(10, 100), (20, 100), (21, 100), (22, 100)],
+	}
+	.assimilate_storage(&mut storage)
+	.unwrap();
+
+	let mut ext = sp_io::TestExternalities::from(storage);
+
+	ext.execute_with(|| {
+		// for events to be deposited.
+		frame_system::Pallet::<Runtime>::set_block_number(1);
+
+		// set some limit for nominations.
+		assert_ok!(Staking::set_staking_configs(
+			RuntimeOrigin::root(),
+			pallet_staking::ConfigOp::Set(10), // minimum nominator bond
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+			pallet_staking::ConfigOp::Noop,
+		));
+	});
+
+	ext
+}
+
+parameter_types! {
+	static ObservedEventsPools: usize = 0;
+	static ObservedEventsStaking: usize = 0;
+	static ObservedEventsBalances: usize = 0;
+	static ObservedEventsDelegatedStaking: usize = 0;
+}
+
+pub(crate) fn pool_events_since_last_call() -> Vec<pallet_nomination_pools::Event<Runtime>> {
+	let events = System::events()
+		.into_iter()
+		.map(|r| r.event)
+		.filter_map(|e| if let RuntimeEvent::Pools(inner) = e { Some(inner) } else { None })
+		.collect::<Vec<_>>();
+	let already_seen = ObservedEventsPools::get();
+	ObservedEventsPools::set(events.len());
+	events.into_iter().skip(already_seen).collect()
+}
+
+pub(crate) fn staking_events_since_last_call() -> Vec<pallet_staking::Event<Runtime>> {
+	let events = System::events()
+		.into_iter()
+		.map(|r| r.event)
+		.filter_map(|e| if let RuntimeEvent::Staking(inner) = e { Some(inner) } else { None })
+		.collect::<Vec<_>>();
+	let already_seen = ObservedEventsStaking::get();
+	ObservedEventsStaking::set(events.len());
+	events.into_iter().skip(already_seen).collect()
+}
+
+pub(crate) fn delegated_staking_events_since_last_call(
+) -> Vec<pallet_delegated_staking::Event<Runtime>> {
+	let events = System::events()
+		.into_iter()
+		.map(|r| r.event)
+		.filter_map(
+			|e| if let RuntimeEvent::DelegatedStaking(inner) = e { Some(inner) } else { None },
+		)
+		.collect::<Vec<_>>();
+	let already_seen = ObservedEventsDelegatedStaking::get();
+	ObservedEventsDelegatedStaking::set(events.len());
+	events.into_iter().skip(already_seen).collect()
+}
diff --git a/substrate/frame/nomination-pools/test-staking/Cargo.toml b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml
similarity index 96%
rename from substrate/frame/nomination-pools/test-staking/Cargo.toml
rename to substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml
index ada52db6de53180093eafc47a140a4b229ced685..5f9bc9af3a214eb3d82da11786c93bf462565a26 100644
--- a/substrate/frame/nomination-pools/test-staking/Cargo.toml
+++ b/substrate/frame/nomination-pools/test-transfer-stake/Cargo.toml
@@ -1,5 +1,5 @@
 [package]
-name = "pallet-nomination-pools-test-staking"
+name = "pallet-nomination-pools-test-transfer-stake"
 version = "1.0.0"
 authors.workspace = true
 edition.workspace = true
diff --git a/substrate/frame/nomination-pools/test-staking/src/lib.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs
similarity index 100%
rename from substrate/frame/nomination-pools/test-staking/src/lib.rs
rename to substrate/frame/nomination-pools/test-transfer-stake/src/lib.rs
diff --git a/substrate/frame/nomination-pools/test-staking/src/mock.rs b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs
similarity index 98%
rename from substrate/frame/nomination-pools/test-staking/src/mock.rs
rename to substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs
index 93a05ddfae990108c7277c2448ff1470ae11d2ae..0970570453b469138774eeb783914990deb0938b 100644
--- a/substrate/frame/nomination-pools/test-staking/src/mock.rs
+++ b/substrate/frame/nomination-pools/test-transfer-stake/src/mock.rs
@@ -180,7 +180,7 @@ impl pallet_nomination_pools::Config for Runtime {
 	type RewardCounter = FixedU128;
 	type BalanceToU256 = BalanceToU256;
 	type U256ToBalance = U256ToBalance;
-	type Staking = Staking;
+	type StakeAdapter = pallet_nomination_pools::adapter::TransferStake<Self, Staking>;
 	type PostUnbondingPoolsWindow = PostUnbondingPoolsWindow;
 	type MaxMetadataLen = ConstU32<256>;
 	type MaxUnbonding = ConstU32<8>;
diff --git a/substrate/frame/staking/src/ledger.rs b/substrate/frame/staking/src/ledger.rs
index 67a86b86226cfb1aa5da4c8bca794f315f348009..294918376d82ce5a8473a5353cea0dc5cdd1ba50 100644
--- a/substrate/frame/staking/src/ledger.rs
+++ b/substrate/frame/staking/src/ledger.rs
@@ -35,7 +35,7 @@ use frame_support::{
 	defensive, ensure,
 	traits::{Defensive, LockableCurrency},
 };
-use sp_staking::StakingAccount;
+use sp_staking::{StakingAccount, StakingInterface};
 use sp_std::prelude::*;
 
 use crate::{
diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs
index 4f91fd6dff220d7fbf2b768a3b9f9a4d3e8c29ab..053ecdef2b00b7bb900443153f63e64f51d89252 100644
--- a/substrate/frame/staking/src/lib.rs
+++ b/substrate/frame/staking/src/lib.rs
@@ -361,7 +361,7 @@ pub type BalanceOf<T> = <T as Config>::CurrencyBalance;
 type PositiveImbalanceOf<T> = <<T as Config>::Currency as Currency<
 	<T as frame_system::Config>::AccountId,
 >>::PositiveImbalance;
-type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
+pub type NegativeImbalanceOf<T> = <<T as Config>::Currency as Currency<
 	<T as frame_system::Config>::AccountId,
 >>::NegativeImbalance;
 
diff --git a/substrate/frame/staking/src/pallet/impls.rs b/substrate/frame/staking/src/pallet/impls.rs
index 52361c6ccdc13653e155df8e1535ec6a16cbe65a..90374451a3a5266362699e6fc55e957a552ba157 100644
--- a/substrate/frame/staking/src/pallet/impls.rs
+++ b/substrate/frame/staking/src/pallet/impls.rs
@@ -1161,11 +1161,6 @@ impl<T: Config> Pallet<T> {
 	) -> Exposure<T::AccountId, BalanceOf<T>> {
 		EraInfo::<T>::get_full_exposure(era, account)
 	}
-
-	/// Whether `who` is a virtual staker whose funds are managed by another pallet.
-	pub(crate) fn is_virtual_staker(who: &T::AccountId) -> bool {
-		VirtualStakers::<T>::contains_key(who)
-	}
 }
 
 impl<T: Config> Pallet<T> {
@@ -1885,6 +1880,11 @@ impl<T: Config> StakingInterface for Pallet<T> {
 		}
 	}
 
+	/// Whether `who` is a virtual staker whose funds are managed by another pallet.
+	fn is_virtual_staker(who: &T::AccountId) -> bool {
+		VirtualStakers::<T>::contains_key(who)
+	}
+
 	fn slash_reward_fraction() -> Perbill {
 		SlashRewardFraction::<T>::get()
 	}
diff --git a/substrate/frame/staking/src/pallet/mod.rs b/substrate/frame/staking/src/pallet/mod.rs
index f82266c03903ceb09703324ba8e79a75d8a0fa16..284a801a0f050eb79672feb2e1167e5d14f033a5 100644
--- a/substrate/frame/staking/src/pallet/mod.rs
+++ b/substrate/frame/staking/src/pallet/mod.rs
@@ -39,6 +39,7 @@ use sp_runtime::{
 use sp_staking::{
 	EraIndex, Page, SessionIndex,
 	StakingAccount::{self, Controller, Stash},
+	StakingInterface,
 };
 use sp_std::prelude::*;
 
diff --git a/substrate/frame/staking/src/slashing.rs b/substrate/frame/staking/src/slashing.rs
index f831f625957d4c495fce3b239e68faed737cb3ab..1fe608cd3358bb8d7ab113ee96d720df4998c759 100644
--- a/substrate/frame/staking/src/slashing.rs
+++ b/substrate/frame/staking/src/slashing.rs
@@ -64,7 +64,7 @@ use sp_runtime::{
 	traits::{Saturating, Zero},
 	DispatchResult, RuntimeDebug,
 };
-use sp_staking::EraIndex;
+use sp_staking::{EraIndex, StakingInterface};
 use sp_std::vec::Vec;
 
 /// The proportion of the slashing reward to be paid out on the first slashing detection.
diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs
index c7045508cea3ff789e8a8910170f97dcca82c6d0..28a61cd4331392d8682dc5491926318d250ebf82 100644
--- a/substrate/primitives/staking/src/lib.rs
+++ b/substrate/primitives/staking/src/lib.rs
@@ -285,6 +285,13 @@ pub trait StakingInterface {
 		Self::status(who).map(|s| matches!(s, StakerStatus::Validator)).unwrap_or(false)
 	}
 
+	/// Checks whether the staker is a virtual account.
+	///
+	/// A virtual staker is an account whose locks are not managed by the [`StakingInterface`]
+	/// implementation but by an external pallet. See [`StakingUnchecked::virtual_bond`] for more
+	/// details.
+	fn is_virtual_staker(who: &Self::AccountId) -> bool;
+
 	/// Get the nominations of a stash, if they are a nominator, `None` otherwise.
 	fn nominations(who: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
 		match Self::status(who) {
@@ -573,6 +580,12 @@ pub trait DelegationMigrator {
 		delegator: &Self::AccountId,
 		value: Self::Balance,
 	) -> DispatchResult;
+
+	/// Drop the `Agent` account and its associated delegators.
+	///
+	/// Also removed from [`StakingUnchecked`] as a Virtual Staker. Useful for testing.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn drop_agent(agent: &Self::AccountId);
 }
 
 sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);