From 4d47b4431beaf12ed9ea45c1fb97ae87f0d145cd Mon Sep 17 00:00:00 2001
From: Ankan <10196091+Ank4n@users.noreply.github.com>
Date: Wed, 15 May 2024 14:00:24 +0200
Subject: [PATCH] Introduces: Delegated Staking Pallet (#3904)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This is the second PR in preparation for
https://github.com/paritytech/polkadot-sdk/issues/454.

## Also see
- **Precursor** https://github.com/paritytech/polkadot-sdk/pull/3889.
- **Follow up** https://github.com/paritytech/polkadot-sdk/pull/3905.

Overall changes are documented here (lot more visual 😍):
https://hackmd.io/@ak0n/454-np-governance

## Changes
### Delegation Interface
Provides delegation primitives for staking.

Introduces two new roles:
- Agent: These are accounts who receive delegation from other accounts
(delegators) and stakes on behalf of them. The funds are held in
delegator accounts.
- Delegator: Accounts who delegate their funds to an agent authorising
them to use it for staking.

Supports
- A way for delegators to add or withdraw delegation to an agent.
- A way for an agent to slash a delegator during a slashing event.

### Pallet Delegated Staking
- Implements `DelegationInterface`.
- Lazy slashing: Any slashes to an Agent is posted in a ledger but not
immediately slashed. The agent can call
`DelegationInterface::delegator_slash` to slash the member and clear the
corresponding slash from its ledger.
- Consumes `StakingInterface` to provide `CoreStaking` features. In
reality, this will be `pallet-staking`.
- Ensures bookkeeping for agent and delegator are correct but leaves the
management of reward and slash logic upto the consumer of this pallet.
- While it does not expose any calls yet, it is written with the intent
of exposing these primitives via extrinsics.

## TODO
- [x] Improve unit tests in the pallet.
- [x] Separate slash reward perbill for rewarding the slash reporters?
- [x] Review if we should add more events.

---------

Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com>
Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com>
Co-authored-by: georgepisaltu <52418509+georgepisaltu@users.noreply.github.com>
---
 Cargo.lock                                    |  22 +
 Cargo.toml                                    |   1 +
 prdoc/pr_3904.prdoc                           |  19 +
 substrate/frame/delegated-staking/Cargo.toml  |  69 ++
 .../frame/delegated-staking/src/impls.rs      | 149 ++++
 substrate/frame/delegated-staking/src/lib.rs  | 815 ++++++++++++++++++
 substrate/frame/delegated-staking/src/mock.rs | 308 +++++++
 .../frame/delegated-staking/src/tests.rs      | 685 +++++++++++++++
 .../frame/delegated-staking/src/types.rs      | 292 +++++++
 substrate/frame/staking/src/lib.rs            |   2 +-
 substrate/primitives/staking/src/lib.rs       | 119 +++
 11 files changed, 2480 insertions(+), 1 deletion(-)
 create mode 100644 prdoc/pr_3904.prdoc
 create mode 100644 substrate/frame/delegated-staking/Cargo.toml
 create mode 100644 substrate/frame/delegated-staking/src/impls.rs
 create mode 100644 substrate/frame/delegated-staking/src/lib.rs
 create mode 100644 substrate/frame/delegated-staking/src/mock.rs
 create mode 100644 substrate/frame/delegated-staking/src/tests.rs
 create mode 100644 substrate/frame/delegated-staking/src/types.rs

diff --git a/Cargo.lock b/Cargo.lock
index 7ff2da4f547..c91025ea34c 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -10123,6 +10123,28 @@ dependencies = [
  "sp-std 14.0.0",
 ]
 
+[[package]]
+name = "pallet-delegated-staking"
+version = "1.0.0"
+dependencies = [
+ "frame-election-provider-support",
+ "frame-support",
+ "frame-system",
+ "pallet-balances",
+ "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",
+ "substrate-test-utils",
+]
+
 [[package]]
 name = "pallet-democracy"
 version = "28.0.0"
diff --git a/Cargo.toml b/Cargo.toml
index 1440c2d497d..dcf410daa1f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -328,6 +328,7 @@ members = [
 	"substrate/frame/contracts/uapi",
 	"substrate/frame/conviction-voting",
 	"substrate/frame/core-fellowship",
+	"substrate/frame/delegated-staking",
 	"substrate/frame/democracy",
 	"substrate/frame/election-provider-multi-phase",
 	"substrate/frame/election-provider-multi-phase/test-staking-e2e",
diff --git a/prdoc/pr_3904.prdoc b/prdoc/pr_3904.prdoc
new file mode 100644
index 00000000000..694f9b44387
--- /dev/null
+++ b/prdoc/pr_3904.prdoc
@@ -0,0 +1,19 @@
+# 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: Introduce pallet-delegated-staking
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Adds a new pallet `delegated-staking` that allows delegators to delegate their funds to agents who can stake
+      these funds on behalf of them. This would be used by Nomination Pools to migrate into a delegation staking based
+      pool.
+
+crates:
+  - name: pallet-delegated-staking
+    bump: patch
+  - name: pallet-staking
+    bump: patch
+  - name: sp-staking
+    bump: minor
diff --git a/substrate/frame/delegated-staking/Cargo.toml b/substrate/frame/delegated-staking/Cargo.toml
new file mode 100644
index 00000000000..a9cbd758ed0
--- /dev/null
+++ b/substrate/frame/delegated-staking/Cargo.toml
@@ -0,0 +1,69 @@
+[package]
+name = "pallet-delegated-staking"
+version = "1.0.0"
+authors.workspace = true
+edition.workspace = true
+license = "Apache-2.0"
+homepage = "https://substrate.io"
+repository.workspace = true
+description = "FRAME delegated staking pallet"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
+frame-support = { path = "../support", default-features = false }
+frame-system = { path = "../system", default-features = false }
+scale-info = { version = "2.10.0", default-features = false, features = ["derive"] }
+sp-std = { path = "../../primitives/std", default-features = false }
+sp-runtime = { path = "../../primitives/runtime", default-features = false }
+sp-staking = { path = "../../primitives/staking", default-features = false }
+
+[dev-dependencies]
+sp-core = { path = "../../primitives/core" }
+sp-io = { path = "../../primitives/io" }
+substrate-test-utils = { path = "../../test-utils" }
+sp-tracing = { path = "../../primitives/tracing" }
+pallet-staking = { path = "../staking" }
+pallet-balances = { path = "../balances" }
+pallet-timestamp = { path = "../timestamp" }
+pallet-staking-reward-curve = { path = "../staking/reward-curve" }
+frame-election-provider-support = { path = "../election-provider-support", default-features = false }
+
+[features]
+default = ["std"]
+std = [
+	"codec/std",
+	"frame-election-provider-support/std",
+	"frame-support/std",
+	"frame-system/std",
+	"pallet-balances/std",
+	"pallet-staking/std",
+	"pallet-timestamp/std",
+	"scale-info/std",
+	"sp-core/std",
+	"sp-io/std",
+	"sp-runtime/std",
+	"sp-staking/std",
+	"sp-std/std",
+]
+runtime-benchmarks = [
+	"frame-election-provider-support/runtime-benchmarks",
+	"frame-support/runtime-benchmarks",
+	"frame-system/runtime-benchmarks",
+	"pallet-balances/runtime-benchmarks",
+	"pallet-staking/runtime-benchmarks",
+	"pallet-timestamp/runtime-benchmarks",
+	"sp-runtime/runtime-benchmarks",
+	"sp-staking/runtime-benchmarks",
+]
+try-runtime = [
+	"frame-election-provider-support/try-runtime",
+	"frame-support/try-runtime",
+	"frame-system/try-runtime",
+	"pallet-balances/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
new file mode 100644
index 00000000000..b1945b0ce37
--- /dev/null
+++ b/substrate/frame/delegated-staking/src/impls.rs
@@ -0,0 +1,149 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Implementations of public traits, namely [`DelegationInterface`] and [`OnStakingUpdate`].
+
+use super::*;
+use sp_staking::{DelegationInterface, DelegationMigrator, OnStakingUpdate};
+
+impl<T: Config> DelegationInterface for Pallet<T> {
+	type Balance = BalanceOf<T>;
+	type AccountId = T::AccountId;
+
+	/// Effective balance of the `Agent` account.
+	fn agent_balance(who: &Self::AccountId) -> Self::Balance {
+		Agent::<T>::get(who)
+			.map(|agent| agent.ledger.effective_balance())
+			.unwrap_or_default()
+	}
+
+	fn delegator_balance(delegator: &Self::AccountId) -> Self::Balance {
+		Delegation::<T>::get(delegator).map(|d| d.amount).unwrap_or_default()
+	}
+
+	/// Delegate funds to an `Agent`.
+	fn delegate(
+		who: &Self::AccountId,
+		agent: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: Self::Balance,
+	) -> DispatchResult {
+		Pallet::<T>::register_agent(
+			RawOrigin::Signed(agent.clone()).into(),
+			reward_account.clone(),
+		)?;
+
+		// Delegate the funds from who to the `Agent` account.
+		Pallet::<T>::delegate_to_agent(RawOrigin::Signed(who.clone()).into(), agent.clone(), amount)
+	}
+
+	/// Add more delegation to the `Agent` account.
+	fn delegate_extra(
+		who: &Self::AccountId,
+		agent: &Self::AccountId,
+		amount: Self::Balance,
+	) -> DispatchResult {
+		Pallet::<T>::delegate_to_agent(RawOrigin::Signed(who.clone()).into(), agent.clone(), amount)
+	}
+
+	/// Withdraw delegation of `delegator` to `Agent`.
+	///
+	/// If there are funds in `Agent` account that can be withdrawn, then those funds would be
+	/// unlocked/released in the delegator's account.
+	fn withdraw_delegation(
+		delegator: &Self::AccountId,
+		agent: &Self::AccountId,
+		amount: Self::Balance,
+		num_slashing_spans: u32,
+	) -> DispatchResult {
+		Pallet::<T>::release_delegation(
+			RawOrigin::Signed(agent.clone()).into(),
+			delegator.clone(),
+			amount,
+			num_slashing_spans,
+		)
+	}
+
+	/// Returns true if the `Agent` have any slash pending to be applied.
+	fn has_pending_slash(agent: &Self::AccountId) -> bool {
+		Agent::<T>::get(agent)
+			.map(|d| !d.ledger.pending_slash.is_zero())
+			.unwrap_or(false)
+	}
+
+	fn delegator_slash(
+		agent: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+		maybe_reporter: Option<Self::AccountId>,
+	) -> sp_runtime::DispatchResult {
+		Pallet::<T>::do_slash(agent.clone(), delegator.clone(), value, maybe_reporter)
+	}
+}
+
+impl<T: Config> DelegationMigrator for Pallet<T> {
+	type Balance = BalanceOf<T>;
+	type AccountId = T::AccountId;
+
+	fn migrate_nominator_to_agent(
+		agent: &Self::AccountId,
+		reward_account: &Self::AccountId,
+	) -> DispatchResult {
+		Pallet::<T>::migrate_to_agent(
+			RawOrigin::Signed(agent.clone()).into(),
+			reward_account.clone(),
+		)
+	}
+
+	fn migrate_delegation(
+		agent: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+	) -> DispatchResult {
+		Pallet::<T>::migrate_delegation(
+			RawOrigin::Signed(agent.clone()).into(),
+			delegator.clone(),
+			value,
+		)
+	}
+}
+
+impl<T: Config> OnStakingUpdate<T::AccountId, BalanceOf<T>> for Pallet<T> {
+	fn on_slash(
+		who: &T::AccountId,
+		_slashed_active: BalanceOf<T>,
+		_slashed_unlocking: &sp_std::collections::btree_map::BTreeMap<EraIndex, BalanceOf<T>>,
+		slashed_total: BalanceOf<T>,
+	) {
+		<Agents<T>>::mutate(who, |maybe_register| match maybe_register {
+			// if existing agent, register the slashed amount as pending slash.
+			Some(register) => register.pending_slash.saturating_accrue(slashed_total),
+			None => {
+				// nothing to do
+			},
+		});
+	}
+
+	fn on_withdraw(stash: &T::AccountId, amount: BalanceOf<T>) {
+		// if there is a withdraw to the agent, then add it to the unclaimed withdrawals.
+		let _ = Agent::<T>::get(stash)
+			// can't do anything if there is an overflow error. Just raise a defensive error.
+			.and_then(|agent| agent.add_unclaimed_withdraw(amount).defensive())
+			.map(|agent| agent.save());
+	}
+}
diff --git a/substrate/frame/delegated-staking/src/lib.rs b/substrate/frame/delegated-staking/src/lib.rs
new file mode 100644
index 00000000000..210f69d9c83
--- /dev/null
+++ b/substrate/frame/delegated-staking/src/lib.rs
@@ -0,0 +1,815 @@
+// 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.
+
+//! # Delegated Staking Pallet
+//!
+//! This pallet implements [`sp_staking::DelegationInterface`] that provides delegation
+//! functionality to `delegators` and `agents`. It is designed to be used in conjunction with
+//! [`StakingInterface`] and relies on [`Config::CoreStaking`] to provide primitive staking
+//! functions.
+//!
+//! Currently, it does not expose any dispatchable calls but is written with a vision to expose them
+//! in the future such that it can be utilised by any external account, off-chain entity or xcm
+//! `MultiLocation` such as a parachain or a smart contract.
+//!
+//! ## Key Terminologies
+//! - **Agent**: An account who accepts delegations from other accounts and act as an agent on their
+//!   behalf for staking these delegated funds. Also, sometimes referred as `Delegatee`.
+//! - **Delegator**: An account who delegates their funds to an `agent` and authorises them to use
+//!   it for staking.
+//! - **AgentLedger**: A data structure that holds important information about the `agent` such as
+//!   total delegations they have received, any slashes posted to them, etc.
+//! - **Delegation**: A data structure that stores the amount of funds delegated to an `agent` by a
+//!   `delegator`.
+//!
+//! ## Goals
+//!
+//! Direct nomination on the Staking pallet does not scale well. Nominations pools were created to
+//! address this by pooling delegator funds into one account and then staking it. This though had
+//! a very critical limitation that the funds were moved from delegator account to pool account
+//! and hence the delegator lost control over their funds for using it for other purposes such as
+//! governance. This pallet aims to solve this by extending the staking pallet to support a new
+//! primitive function: delegation of funds to an `agent` with the intent of staking. The agent can
+//! then stake the delegated funds to [`Config::CoreStaking`] on behalf of the delegators.
+//!
+//! ### Withdrawal Management
+//! Agent unbonding does not regulate ordering of consequent withdrawal for delegators. This is upto
+//! the consumer of this pallet to implement in what order unbondable funds from
+//! [`Config::CoreStaking`] can be withdrawn by the delegators.
+//!
+//! ### Reward and Slashing
+//! This pallet does not enforce any specific strategy for how rewards or slashes are applied. It
+//! is upto the `agent` account to decide how to apply the rewards and slashes.
+//!
+//! This importantly allows clients of this pallet to build their own strategies for reward/slashes.
+//! For example, an `agent` account can choose to first slash the reward pot before slashing the
+//! delegators. Or part of the reward can go to an insurance fund that can be used to cover any
+//! potential future slashes. The goal is to eventually allow foreign MultiLocations
+//! (smart contracts or pallets on another chain) to build their own pooled staking solutions
+//! similar to `NominationPools`.
+
+//! ## Core functions
+//!
+//! - Allow an account to receive delegations. See [`Pallet::register_agent`].
+//! - Delegate funds to an `agent` account. See [`Pallet::delegate_to_agent`].
+//! - Release delegated funds from an `agent` account to the `delegator`. See
+//!   [`Pallet::release_delegation`].
+//! - Migrate a `Nominator` account to an `agent` account. See [`Pallet::migrate_to_agent`].
+//!   Explained in more detail in the `Migration` section.
+//! - Migrate unclaimed delegated funds from `agent` to delegator. When a nominator migrates to an
+//! agent, the funds are held in a proxy account. This function allows the delegator to claim their
+//! share of the funds from the proxy account. See [`Pallet::migrate_delegation`].
+//!
+//! ## Lazy Slashing
+//! One of the reasons why direct nominators on staking pallet cannot scale well is because all
+//! nominators are slashed at the same time. This is expensive and needs to be bounded operation.
+//!
+//! This pallet implements a lazy slashing mechanism. Any slashes to the `agent` are posted in its
+//! `AgentLedger` as a pending slash. Since the actual amount is held in the multiple
+//! `delegator` accounts, this pallet has no way to know how to apply slash. It is the `agent`'s
+//! responsibility to apply slashes for each delegator, one at a time. Staking pallet ensures the
+//! pending slash never exceeds staked amount and would freeze further withdraws until all pending
+//! slashes are cleared.
+//!
+//! The user of this pallet can apply slash using
+//! [DelegationInterface::delegator_slash](sp_staking::DelegationInterface::delegator_slash).
+//!
+//! ## Migration from Nominator to Agent
+//! More details [here](https://hackmd.io/@ak0n/454-np-governance).
+//!
+//! ## Nomination Pool vs Delegation Staking
+//! This pallet is not a replacement for Nomination Pool but adds a new primitive in addition to
+//! staking pallet that can be used by Nomination Pool to support delegation based staking. It can
+//! be thought of as an extension to the Staking Pallet in relation to Nomination Pools.
+//! Technically, these changes could be made in one of those pallets as well but that would have
+//! meant significant refactoring and high chances of introducing a regression. With this approach,
+//! we can keep the existing pallets with minimal changes and introduce a new pallet that can be
+//! optionally used by Nomination Pool. The vision is to build this in a configurable way such that
+//! runtime can choose whether to use this pallet or not.
+//!
+//! With that said, following is the main difference between
+//! #### Nomination Pool without delegation support
+//!  1) transfer fund from delegator to pool account, and
+//!  2) stake from pool account as a direct nominator.
+//!
+//! #### Nomination Pool with delegation support
+//!  1) delegate fund from delegator to pool account, and
+//!  2) stake from pool account as an `Agent` account on the staking pallet.
+//!
+//! The difference being, in the second approach, the delegated funds will be locked in-place in
+//! user's account enabling them to participate in use cases that allows use of `held` funds such
+//! as participation in governance voting.
+//!
+//! Nomination pool still does all the heavy lifting around pool administration, reward
+//! distribution, lazy slashing and as such, is not meant to be replaced with this pallet.
+//!
+//! ## Limitations
+//! - Rewards can not be auto-compounded.
+//! - Slashes are lazy and hence there could be a period of time when an account can use funds for
+//!   operations such as voting in governance even though they should be slashed.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![deny(rustdoc::broken_intra_doc_links)]
+
+mod impls;
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+mod types;
+
+pub use pallet::*;
+
+use types::*;
+
+use frame_support::{
+	pallet_prelude::*,
+	traits::{
+		fungible::{
+			hold::{
+				Balanced as FunHoldBalanced, Inspect as FunHoldInspect, Mutate as FunHoldMutate,
+			},
+			Balanced, Inspect as FunInspect, Mutate as FunMutate,
+		},
+		tokens::{fungible::Credit, Fortitude, Precision, Preservation},
+		Defensive, DefensiveOption, Imbalance, OnUnbalanced,
+	},
+};
+use sp_runtime::{
+	traits::{AccountIdConversion, CheckedAdd, CheckedSub, Zero},
+	ArithmeticError, DispatchResult, Perbill, RuntimeDebug, Saturating,
+};
+use sp_staking::{EraIndex, StakingInterface, StakingUnchecked};
+use sp_std::{convert::TryInto, prelude::*};
+
+pub type BalanceOf<T> =
+	<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
+
+use frame_system::{ensure_signed, pallet_prelude::*, RawOrigin};
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(PhantomData<T>);
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// The overarching event type.
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+
+		/// Injected identifier for the pallet.
+		#[pallet::constant]
+		type PalletId: Get<frame_support::PalletId>;
+
+		/// Currency type.
+		type Currency: FunHoldMutate<Self::AccountId, Reason = Self::RuntimeHoldReason>
+			+ FunMutate<Self::AccountId>
+			+ FunHoldBalanced<Self::AccountId>;
+
+		/// Handler for the unbalanced reduction when slashing a delegator.
+		type OnSlash: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
+
+		/// Fraction of the slash that is rewarded to the caller of pending slash to the agent.
+		#[pallet::constant]
+		type SlashRewardFraction: Get<Perbill>;
+
+		/// Overarching hold reason.
+		type RuntimeHoldReason: From<HoldReason>;
+
+		/// Core staking implementation.
+		type CoreStaking: StakingUnchecked<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// The account cannot perform this operation.
+		NotAllowed,
+		/// An existing staker cannot perform this action.
+		AlreadyStaking,
+		/// Reward Destination cannot be same as `Agent` account.
+		InvalidRewardDestination,
+		/// Delegation conditions are not met.
+		///
+		/// Possible issues are
+		/// 1) Cannot delegate to self,
+		/// 2) Cannot delegate to multiple delegates.
+		InvalidDelegation,
+		/// The account does not have enough funds to perform the operation.
+		NotEnoughFunds,
+		/// Not an existing `Agent` account.
+		NotAgent,
+		/// Not a Delegator account.
+		NotDelegator,
+		/// Some corruption in internal state.
+		BadState,
+		/// Unapplied pending slash restricts operation on `Agent`.
+		UnappliedSlash,
+		/// `Agent` has no pending slash to be applied.
+		NothingToSlash,
+		/// Failed to withdraw amount from Core Staking.
+		WithdrawFailed,
+		/// Operation not supported by this pallet.
+		NotSupported,
+	}
+
+	/// A reason for placing a hold on funds.
+	#[pallet::composite_enum]
+	pub enum HoldReason {
+		/// Funds held for stake delegation to another account.
+		#[codec(index = 0)]
+		StakingDelegation,
+	}
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub (super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// Funds delegated by a delegator.
+		Delegated { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
+		/// Funds released to a delegator.
+		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> },
+	}
+
+	/// Map of Delegators to their `Delegation`.
+	///
+	/// Implementation note: We are not using a double map with `delegator` and `agent` account
+	/// as keys since we want to restrict delegators to delegate only to one account at a time.
+	#[pallet::storage]
+	pub(crate) type Delegators<T: Config> =
+		CountedStorageMap<_, Twox64Concat, T::AccountId, Delegation<T>, OptionQuery>;
+
+	/// Map of `Agent` to their `Ledger`.
+	#[pallet::storage]
+	pub(crate) type Agents<T: Config> =
+		CountedStorageMap<_, Twox64Concat, T::AccountId, AgentLedger<T>, OptionQuery>;
+
+	// This pallet is not currently written with the intention of exposing any calls. But the
+	// functions defined in the following impl block should act as a good reference for how the
+	// exposed calls would look like when exposed.
+	impl<T: Config> Pallet<T> {
+		/// Register an account to become a stake `Agent`. Sometimes also called a `Delegatee`.
+		///
+		/// Delegators can authorize `Agent`s to stake on their behalf by delegating their funds to
+		/// them. The `Agent` can then use the delegated funds to stake to [`Config::CoreStaking`].
+		///
+		/// An account that is directly staked to [`Config::CoreStaking`] cannot become an `Agent`.
+		/// However, they can migrate to become an agent using [`Self::migrate_to_agent`].
+		///
+		/// Implementation note: This function allows any account to become an agent. It is
+		/// important though that accounts that call [`StakingUnchecked::virtual_bond`] are keyless
+		/// accounts. This is not a problem for now since this is only used by other pallets in the
+		/// runtime which use keyless account as agents. If we later want to expose this as a
+		/// dispatchable call, we should derive a sub-account from the caller and use that as the
+		/// agent account.
+		pub fn register_agent(
+			origin: OriginFor<T>,
+			reward_account: T::AccountId,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+
+			// Existing `agent` cannot register again and a delegator cannot become an `agent`.
+			ensure!(!Self::is_agent(&who) && !Self::is_delegator(&who), Error::<T>::NotAllowed);
+
+			// They cannot be already a direct staker in the staking pallet.
+			ensure!(!Self::is_direct_staker(&who), Error::<T>::AlreadyStaking);
+
+			// Reward account cannot be same as `agent` account.
+			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
+
+			Self::do_register_agent(&who, &reward_account);
+			Ok(())
+		}
+
+		/// Migrate from a `Nominator` account to `Agent` account.
+		///
+		/// The origin needs to
+		/// - be a `Nominator` with [`Config::CoreStaking`],
+		/// - not already an `Agent`,
+		///
+		/// This function will create a proxy account to the agent called `proxy_delegator` and
+		/// transfer the directly staked amount by the agent to it. The `proxy_delegator` delegates
+		/// the funds to the origin making origin an `Agent` account. The real `delegator`
+		/// accounts of the origin can later migrate their funds using [Self::migrate_delegation] to
+		/// claim back their share of delegated funds from `proxy_delegator` to self.
+		///
+		/// Any free fund in the agent's account will be marked as unclaimed withdrawal.
+		pub fn migrate_to_agent(
+			origin: OriginFor<T>,
+			reward_account: T::AccountId,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			// ensure who is a staker in `CoreStaking` but not already an agent or a delegator.
+			ensure!(
+				Self::is_direct_staker(&who) && !Self::is_agent(&who) && !Self::is_delegator(&who),
+				Error::<T>::NotAllowed
+			);
+
+			// Reward account cannot be same as `agent` account.
+			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
+
+			Self::do_migrate_to_agent(&who, &reward_account)
+		}
+
+		/// Release previously delegated funds by delegator to origin.
+		///
+		/// Only agents can call this.
+		///
+		/// Tries to withdraw unbonded funds from `CoreStaking` if needed and release amount to
+		/// `delegator`.
+		pub fn release_delegation(
+			origin: OriginFor<T>,
+			delegator: T::AccountId,
+			amount: BalanceOf<T>,
+			num_slashing_spans: u32,
+		) -> DispatchResult {
+			let who = ensure_signed(origin)?;
+			Self::do_release(&who, &delegator, amount, num_slashing_spans)
+		}
+
+		/// Migrate delegated funds that are held in `proxy_delegator` to the claiming `delegator`'s
+		/// account. If successful, the specified funds will be moved and delegated from `delegator`
+		/// account to the agent.
+		///
+		/// This can be called by `agent` accounts that were previously a direct `Nominator` with
+		/// [`Config::CoreStaking`] and has some remaining unclaimed delegations.
+		///
+		/// Internally, it moves some delegations from `proxy_delegator` account to `delegator`
+		/// account and reapplying the holds.
+		pub fn migrate_delegation(
+			origin: OriginFor<T>,
+			delegator: T::AccountId,
+			amount: BalanceOf<T>,
+		) -> DispatchResult {
+			let agent = ensure_signed(origin)?;
+
+			// Ensure they have minimum delegation.
+			ensure!(amount >= T::Currency::minimum_balance(), Error::<T>::NotEnoughFunds);
+
+			// Ensure delegator is sane.
+			ensure!(!Self::is_agent(&delegator), Error::<T>::NotAllowed);
+			ensure!(!Self::is_delegator(&delegator), Error::<T>::NotAllowed);
+			ensure!(!Self::is_direct_staker(&delegator), Error::<T>::AlreadyStaking);
+
+			// ensure agent is sane.
+			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 balance_remaining = Self::held_balance_of(&proxy_delegator);
+			ensure!(balance_remaining >= amount, Error::<T>::NotEnoughFunds);
+
+			Self::do_migrate_delegation(&proxy_delegator, &delegator, amount)
+		}
+
+		/// Delegate given `amount` of tokens to an `Agent` account.
+		///
+		/// If `origin` is the first time delegator, we add them to state. If they are already
+		/// delegating, we increase the delegation.
+		///
+		/// Conditions:
+		/// - Delegators cannot delegate to more than one agent.
+		/// - The `agent` account should already be registered as such. See
+		///   [`Self::register_agent`].
+		pub fn delegate_to_agent(
+			origin: OriginFor<T>,
+			agent: T::AccountId,
+			amount: BalanceOf<T>,
+		) -> DispatchResult {
+			let delegator = ensure_signed(origin)?;
+
+			// ensure delegator is sane.
+			ensure!(
+				Delegation::<T>::can_delegate(&delegator, &agent),
+				Error::<T>::InvalidDelegation
+			);
+			ensure!(!Self::is_direct_staker(&delegator), Error::<T>::AlreadyStaking);
+
+			// ensure agent is sane.
+			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
+
+			// add to delegation.
+			Self::do_delegate(&delegator, &agent, amount)?;
+
+			// bond the newly delegated amount to `CoreStaking`.
+			Self::do_bond(&agent, amount)
+		}
+	}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
+		#[cfg(feature = "try-runtime")]
+		fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
+			Self::do_try_state()
+		}
+	}
+}
+
+impl<T: Config> Pallet<T> {
+	/// 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()))
+	}
+
+	/// Held balance of a delegator.
+	pub(crate) fn held_balance_of(who: &T::AccountId) -> BalanceOf<T> {
+		T::Currency::balance_on_hold(&HoldReason::StakingDelegation.into(), who)
+	}
+
+	/// Returns true if who is registered as an `Agent`.
+	fn is_agent(who: &T::AccountId) -> bool {
+		<Agents<T>>::contains_key(who)
+	}
+
+	/// Returns true if who is delegating to an `Agent` account.
+	fn is_delegator(who: &T::AccountId) -> bool {
+		<Delegators<T>>::contains_key(who)
+	}
+
+	/// Returns true if who is already staking on [`Config::CoreStaking`].
+	fn is_direct_staker(who: &T::AccountId) -> bool {
+		T::CoreStaking::status(who).is_ok()
+	}
+
+	/// Registers a new agent in the system.
+	fn do_register_agent(who: &T::AccountId, reward_account: &T::AccountId) {
+		AgentLedger::<T>::new(reward_account).update(who);
+
+		// Agent does not hold balance of its own but this pallet will provide for this to exist.
+		// This is expected to be a keyless account and not created by any user directly so safe.
+		// TODO: Someday if we allow anyone to be an agent, we should take a deposit for
+		// being a delegator.
+		frame_system::Pallet::<T>::inc_providers(who);
+	}
+
+	/// Migrate existing staker account `who` to an `Agent` account.
+	fn do_migrate_to_agent(who: &T::AccountId, reward_account: &T::AccountId) -> DispatchResult {
+		Self::do_register_agent(who, reward_account);
+
+		// 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());
+
+		// Keep proxy delegator alive until all funds are migrated.
+		frame_system::Pallet::<T>::inc_providers(&proxy_delegator);
+
+		// Get current stake
+		let stake = T::CoreStaking::stake(who)?;
+
+		// release funds from core staking.
+		T::CoreStaking::migrate_to_virtual_staker(who);
+
+		// transfer just released staked amount plus any free amount.
+		let amount_to_transfer =
+			T::Currency::reducible_balance(who, Preservation::Expendable, Fortitude::Polite);
+
+		// This should never fail but if it does, it indicates bad state and we abort.
+		T::Currency::transfer(who, &proxy_delegator, amount_to_transfer, Preservation::Expendable)?;
+
+		T::CoreStaking::update_payee(who, reward_account)?;
+		// delegate all transferred funds back to agent.
+		Self::do_delegate(&proxy_delegator, who, amount_to_transfer)?;
+
+		// if the transferred/delegated amount was greater than the stake, mark the extra as
+		// unclaimed withdrawal.
+		let unclaimed_withdraws = amount_to_transfer
+			.checked_sub(&stake.total)
+			.defensive_ok_or(ArithmeticError::Underflow)?;
+
+		if !unclaimed_withdraws.is_zero() {
+			let mut ledger = AgentLedger::<T>::get(who).ok_or(Error::<T>::NotAgent)?;
+			ledger.unclaimed_withdrawals = ledger
+				.unclaimed_withdrawals
+				.checked_add(&unclaimed_withdraws)
+				.defensive_ok_or(ArithmeticError::Overflow)?;
+			ledger.update(who);
+		}
+
+		Ok(())
+	}
+
+	/// Bond `amount` to `agent_acc` in [`Config::CoreStaking`].
+	fn do_bond(agent_acc: &T::AccountId, amount: BalanceOf<T>) -> DispatchResult {
+		let agent = Agent::<T>::get(agent_acc)?;
+
+		let available_to_bond = agent.available_to_bond();
+		defensive_assert!(amount == available_to_bond, "not expected value to bond");
+
+		if agent.is_bonded() {
+			T::CoreStaking::bond_extra(&agent.key, amount)
+		} else {
+			T::CoreStaking::virtual_bond(&agent.key, amount, agent.reward_account())
+		}
+	}
+
+	/// Delegate `amount` from `delegator` to `agent`.
+	fn do_delegate(
+		delegator: &T::AccountId,
+		agent: &T::AccountId,
+		amount: BalanceOf<T>,
+	) -> DispatchResult {
+		let mut ledger = AgentLedger::<T>::get(agent).ok_or(Error::<T>::NotAgent)?;
+		// try to hold the funds.
+		T::Currency::hold(&HoldReason::StakingDelegation.into(), delegator, amount)?;
+
+		let new_delegation_amount =
+			if let Some(existing_delegation) = Delegation::<T>::get(delegator) {
+				ensure!(&existing_delegation.agent == agent, Error::<T>::InvalidDelegation);
+				existing_delegation
+					.amount
+					.checked_add(&amount)
+					.ok_or(ArithmeticError::Overflow)?
+			} else {
+				amount
+			};
+
+		Delegation::<T>::new(agent, new_delegation_amount).update_or_kill(delegator);
+		ledger.total_delegated =
+			ledger.total_delegated.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
+		ledger.update(agent);
+
+		Self::deposit_event(Event::<T>::Delegated {
+			agent: agent.clone(),
+			delegator: delegator.clone(),
+			amount,
+		});
+
+		Ok(())
+	}
+
+	/// Release `amount` of delegated funds from `agent` to `delegator`.
+	fn do_release(
+		who: &T::AccountId,
+		delegator: &T::AccountId,
+		amount: BalanceOf<T>,
+		num_slashing_spans: u32,
+	) -> DispatchResult {
+		let mut agent = Agent::<T>::get(who)?;
+		let mut delegation = Delegation::<T>::get(delegator).ok_or(Error::<T>::NotDelegator)?;
+
+		// make sure delegation to be released is sound.
+		ensure!(&delegation.agent == who, Error::<T>::NotAgent);
+		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
+
+		// if we do not already have enough funds to be claimed, try withdraw some more.
+		// keep track if we killed the staker in the process.
+		let stash_killed = if agent.ledger.unclaimed_withdrawals < amount {
+			// withdraw account.
+			let killed = T::CoreStaking::withdraw_unbonded(who.clone(), num_slashing_spans)
+				.map_err(|_| Error::<T>::WithdrawFailed)?;
+			// reload agent from storage since withdrawal might have changed the state.
+			agent = agent.refresh()?;
+			Some(killed)
+		} else {
+			None
+		};
+
+		// if we still do not have enough funds to release, abort.
+		ensure!(agent.ledger.unclaimed_withdrawals >= amount, Error::<T>::NotEnoughFunds);
+
+		// Claim withdraw from agent. Kill agent if no delegation left.
+		// TODO: Ideally if there is a register, there should be an unregister that should
+		// clean up the agent. Can be improved in future.
+		if agent.remove_unclaimed_withdraw(amount)?.update_or_kill()? {
+			match stash_killed {
+				Some(killed) => {
+					// this implies we did a `CoreStaking::withdraw` before release. Ensure
+					// we killed the staker as well.
+					ensure!(killed, Error::<T>::BadState);
+				},
+				None => {
+					// We did not do a `CoreStaking::withdraw` before release. Ensure staker is
+					// already killed in `CoreStaking`.
+					ensure!(T::CoreStaking::status(who).is_err(), Error::<T>::BadState);
+				},
+			}
+
+			// Remove provider reference for `who`.
+			let _ = frame_system::Pallet::<T>::dec_providers(who).defensive();
+		}
+
+		// book keep delegation
+		delegation.amount = delegation
+			.amount
+			.checked_sub(&amount)
+			.defensive_ok_or(ArithmeticError::Overflow)?;
+
+		// remove delegator if nothing delegated anymore
+		delegation.update_or_kill(delegator);
+
+		let released = T::Currency::release(
+			&HoldReason::StakingDelegation.into(),
+			delegator,
+			amount,
+			Precision::BestEffort,
+		)?;
+
+		defensive_assert!(released == amount, "hold should have been released fully");
+
+		Self::deposit_event(Event::<T>::Released {
+			agent: who.clone(),
+			delegator: delegator.clone(),
+			amount,
+		});
+
+		Ok(())
+	}
+
+	/// Migrates delegation of `amount` from `source` account to `destination` account.
+	fn do_migrate_delegation(
+		source_delegator: &T::AccountId,
+		destination_delegator: &T::AccountId,
+		amount: BalanceOf<T>,
+	) -> DispatchResult {
+		let mut source_delegation =
+			Delegators::<T>::get(source_delegator).defensive_ok_or(Error::<T>::BadState)?;
+
+		// some checks that must have already been checked before.
+		ensure!(source_delegation.amount >= amount, Error::<T>::NotEnoughFunds);
+		debug_assert!(
+			!Self::is_delegator(destination_delegator) && !Self::is_agent(destination_delegator)
+		);
+
+		// update delegations
+		Delegation::<T>::new(&source_delegation.agent, amount)
+			.update_or_kill(destination_delegator);
+
+		source_delegation.amount = source_delegation
+			.amount
+			.checked_sub(&amount)
+			.defensive_ok_or(Error::<T>::BadState)?;
+
+		source_delegation.update_or_kill(source_delegator);
+
+		// release funds from source
+		let released = T::Currency::release(
+			&HoldReason::StakingDelegation.into(),
+			source_delegator,
+			amount,
+			Precision::BestEffort,
+		)?;
+
+		defensive_assert!(released == amount, "hold should have been released fully");
+
+		// transfer the released amount to `destination_delegator`.
+		let post_balance = T::Currency::transfer(
+			source_delegator,
+			destination_delegator,
+			amount,
+			Preservation::Expendable,
+		)
+		.map_err(|_| Error::<T>::BadState)?;
+
+		// if balance is zero, clear provider for source (proxy) delegator.
+		if post_balance == Zero::zero() {
+			let _ = frame_system::Pallet::<T>::dec_providers(source_delegator).defensive();
+		}
+
+		// hold the funds again in the new delegator account.
+		T::Currency::hold(&HoldReason::StakingDelegation.into(), destination_delegator, amount)?;
+
+		Ok(())
+	}
+
+	/// Take slash `amount` from agent's `pending_slash`counter and apply it to `delegator` account.
+	pub fn do_slash(
+		agent_acc: T::AccountId,
+		delegator: T::AccountId,
+		amount: BalanceOf<T>,
+		maybe_reporter: Option<T::AccountId>,
+	) -> DispatchResult {
+		let agent = Agent::<T>::get(&agent_acc)?;
+		// ensure there is something to slash
+		ensure!(agent.ledger.pending_slash > Zero::zero(), Error::<T>::NothingToSlash);
+
+		let mut delegation = <Delegators<T>>::get(&delegator).ok_or(Error::<T>::NotDelegator)?;
+		ensure!(delegation.agent == agent_acc, Error::<T>::NotAgent);
+		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
+
+		// slash delegator
+		let (mut credit, missing) =
+			T::Currency::slash(&HoldReason::StakingDelegation.into(), &delegator, amount);
+
+		defensive_assert!(missing.is_zero(), "slash should have been fully applied");
+
+		let actual_slash = credit.peek();
+
+		// remove the applied slashed amount from agent.
+		agent.remove_slash(actual_slash).save();
+		delegation.amount =
+			delegation.amount.checked_sub(&actual_slash).ok_or(ArithmeticError::Overflow)?;
+		delegation.update_or_kill(&delegator);
+
+		if let Some(reporter) = maybe_reporter {
+			let reward_payout: BalanceOf<T> = T::SlashRewardFraction::get() * actual_slash;
+			let (reporter_reward, rest) = credit.split(reward_payout);
+
+			// credit is the amount that we provide to `T::OnSlash`.
+			credit = rest;
+
+			// reward reporter or drop it.
+			let _ = T::Currency::resolve(&reporter, reporter_reward);
+		}
+
+		T::OnSlash::on_unbalanced(credit);
+
+		Self::deposit_event(Event::<T>::Slashed { agent: agent_acc, delegator, amount });
+
+		Ok(())
+	}
+
+	/// Total balance that is available for stake. Includes already staked amount.
+	#[cfg(test)]
+	pub(crate) fn stakeable_balance(who: &T::AccountId) -> BalanceOf<T> {
+		Agent::<T>::get(who)
+			.map(|agent| agent.ledger.stakeable_balance())
+			.unwrap_or_default()
+	}
+}
+
+#[cfg(any(test, feature = "try-runtime"))]
+use sp_std::collections::btree_map::BTreeMap;
+
+#[cfg(any(test, feature = "try-runtime"))]
+impl<T: Config> Pallet<T> {
+	pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
+		// build map to avoid reading storage multiple times.
+		let delegation_map = Delegators::<T>::iter().collect::<BTreeMap<_, _>>();
+		let ledger_map = Agents::<T>::iter().collect::<BTreeMap<_, _>>();
+
+		Self::check_delegates(ledger_map.clone())?;
+		Self::check_delegators(delegation_map, ledger_map)?;
+
+		Ok(())
+	}
+
+	fn check_delegates(
+		ledgers: BTreeMap<T::AccountId, AgentLedger<T>>,
+	) -> Result<(), sp_runtime::TryRuntimeError> {
+		for (agent, ledger) in ledgers {
+			ensure!(
+				matches!(
+					T::CoreStaking::status(&agent).expect("agent should be bonded"),
+					sp_staking::StakerStatus::Nominator(_) | sp_staking::StakerStatus::Idle
+				),
+				"agent should be bonded and not validator"
+			);
+
+			ensure!(
+				ledger.stakeable_balance() >=
+					T::CoreStaking::total_stake(&agent)
+						.expect("agent should exist as a nominator"),
+				"Cannot stake more than balance"
+			);
+		}
+
+		Ok(())
+	}
+
+	fn check_delegators(
+		delegations: BTreeMap<T::AccountId, Delegation<T>>,
+		ledger: BTreeMap<T::AccountId, AgentLedger<T>>,
+	) -> Result<(), sp_runtime::TryRuntimeError> {
+		let mut delegation_aggregation = BTreeMap::<T::AccountId, BalanceOf<T>>::new();
+		for (delegator, delegation) in delegations.iter() {
+			ensure!(
+				T::CoreStaking::status(delegator).is_err(),
+				"delegator should not be directly staked"
+			);
+			ensure!(!Self::is_agent(delegator), "delegator cannot be an agent");
+
+			delegation_aggregation
+				.entry(delegation.agent.clone())
+				.and_modify(|e| *e += delegation.amount)
+				.or_insert(delegation.amount);
+		}
+
+		for (agent, total_delegated) in delegation_aggregation {
+			ensure!(!Self::is_delegator(&agent), "agent cannot be delegator");
+
+			let ledger = ledger.get(&agent).expect("ledger should exist");
+			ensure!(
+				ledger.total_delegated == total_delegated,
+				"ledger total delegated should match delegations"
+			);
+		}
+
+		Ok(())
+	}
+}
diff --git a/substrate/frame/delegated-staking/src/mock.rs b/substrate/frame/delegated-staking/src/mock.rs
new file mode 100644
index 00000000000..21a9fe6b227
--- /dev/null
+++ b/substrate/frame/delegated-staking/src/mock.rs
@@ -0,0 +1,308 @@
+// 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::{self as delegated_staking, types::Agent};
+use frame_support::{
+	assert_ok, derive_impl,
+	pallet_prelude::*,
+	parameter_types,
+	traits::{ConstU64, Currency},
+	PalletId,
+};
+
+use sp_runtime::{traits::IdentityLookup, BuildStorage, Perbill};
+
+use frame_election_provider_support::{
+	bounds::{ElectionBounds, ElectionBoundsBuilder},
+	onchain, SequentialPhragmen,
+};
+use frame_support::dispatch::RawOrigin;
+use pallet_staking::{ActiveEra, ActiveEraInfo, CurrentEra};
+use sp_staking::{Stake, StakingInterface};
+
+pub type T = Runtime;
+type Block = frame_system::mocking::MockBlock<Runtime>;
+pub type AccountId = u128;
+
+pub const GENESIS_VALIDATOR: AccountId = 1;
+pub const GENESIS_NOMINATOR_ONE: AccountId = 101;
+pub const GENESIS_NOMINATOR_TWO: AccountId = 102;
+
+#[derive_impl(frame_system::config_preludes::TestDefaultConfig as frame_system::DefaultConfig)]
+impl frame_system::Config for Runtime {
+	type Block = Block;
+	type AccountData = pallet_balances::AccountData<Balance>;
+	type AccountId = AccountId;
+	type Lookup = IdentityLookup<Self::AccountId>;
+}
+
+impl pallet_timestamp::Config for Runtime {
+	type Moment = u64;
+	type OnTimestampSet = ();
+	type MinimumPeriod = ConstU64<5>;
+	type WeightInfo = ();
+}
+
+pub type Balance = u128;
+
+parameter_types! {
+	pub static ExistentialDeposit: Balance = 1;
+}
+impl pallet_balances::Config for Runtime {
+	type MaxLocks = ConstU32<128>;
+	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 = 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;
+	pub static ElectionsBoundsOnChain: ElectionBounds = ElectionBoundsBuilder::default().build();
+}
+pub struct OnChainSeqPhragmen;
+impl onchain::Config for OnChainSeqPhragmen {
+	type System = Runtime;
+	type Solver = SequentialPhragmen<Balance, sp_runtime::Perbill>;
+	type DataProvider = Staking;
+	type WeightInfo = ();
+	type MaxWinners = ConstU32<100>;
+	type Bounds = ElectionsBoundsOnChain;
+}
+
+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 = ConstU32<1>;
+	type SlashDeferDuration = ();
+	type AdminOrigin = frame_system::EnsureRoot<Self::AccountId>;
+	type BondingDuration = BondingDuration;
+	type SessionInterface = ();
+	type EraPayout = pallet_staking::ConvertCurve<RewardCurve>;
+	type NextNewSession = ();
+	type HistoryDepth = ConstU32<84>;
+	type MaxExposurePageSize = ConstU32<64>;
+	type ElectionProvider = onchain::OnChainExecution<OnChainSeqPhragmen>;
+	type GenesisElectionProvider = Self::ElectionProvider;
+	type VoterList = pallet_staking::UseNominatorsAndValidatorsMap<Self>;
+	type TargetList = pallet_staking::UseValidatorsMap<Self>;
+	type NominationsQuota = pallet_staking::FixedNominationsQuota<16>;
+	type MaxUnlockingChunks = ConstU32<10>;
+	type MaxControllersInDeprecationBatch = ConstU32<100>;
+	type EventListeners = DelegatedStaking;
+	type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
+	type WeightInfo = ();
+	type DisablingStrategy = pallet_staking::UpToLimitDisablingStrategy;
+}
+
+parameter_types! {
+	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
+	pub const SlashRewardFraction: Perbill = Perbill::from_percent(10);
+}
+impl 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;
+}
+
+parameter_types! {
+	pub static MaxUnbonding: u32 = 8;
+}
+
+frame_support::construct_runtime!(
+	pub enum Runtime {
+		System: frame_system,
+		Timestamp: pallet_timestamp,
+		Balances: pallet_balances,
+		Staking: pallet_staking,
+		DelegatedStaking: delegated_staking,
+	}
+);
+
+#[derive(Default)]
+pub struct ExtBuilder {}
+
+impl ExtBuilder {
+	fn build(self) -> sp_io::TestExternalities {
+		sp_tracing::try_init_simple();
+		let mut storage =
+			frame_system::GenesisConfig::<Runtime>::default().build_storage().unwrap();
+
+		let _ = pallet_balances::GenesisConfig::<T> {
+			balances: vec![
+				(GENESIS_VALIDATOR, 10000),
+				(GENESIS_NOMINATOR_ONE, 1000),
+				(GENESIS_NOMINATOR_TWO, 2000),
+			],
+		}
+		.assimilate_storage(&mut storage);
+
+		let stakers = vec![
+			(
+				GENESIS_VALIDATOR,
+				GENESIS_VALIDATOR,
+				1000,
+				sp_staking::StakerStatus::<AccountId>::Validator,
+			),
+			(
+				GENESIS_NOMINATOR_ONE,
+				GENESIS_NOMINATOR_ONE,
+				100,
+				sp_staking::StakerStatus::<AccountId>::Nominator(vec![1]),
+			),
+			(
+				GENESIS_NOMINATOR_TWO,
+				GENESIS_NOMINATOR_TWO,
+				200,
+				sp_staking::StakerStatus::<AccountId>::Nominator(vec![1]),
+			),
+		];
+
+		let _ = pallet_staking::GenesisConfig::<T> {
+			stakers: stakers.clone(),
+			// ideal validator count
+			validator_count: 2,
+			minimum_validator_count: 1,
+			invulnerables: vec![],
+			slash_reward_fraction: Perbill::from_percent(10),
+			min_nominator_bond: ExistentialDeposit::get(),
+			min_validator_bond: ExistentialDeposit::get(),
+			..Default::default()
+		}
+		.assimilate_storage(&mut storage);
+
+		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 era for staking.
+			start_era(0);
+		});
+
+		ext
+	}
+	pub fn build_and_execute(self, test: impl FnOnce()) {
+		sp_tracing::try_init_simple();
+		let mut ext = self.build();
+		ext.execute_with(test);
+		ext.execute_with(|| {
+			#[cfg(feature = "try-runtime")]
+			<AllPalletsWithSystem as frame_support::traits::TryState<u64>>::try_state(
+				frame_system::Pallet::<Runtime>::block_number(),
+				frame_support::traits::TryStateSelect::All,
+			)
+			.unwrap();
+			#[cfg(not(feature = "try-runtime"))]
+			DelegatedStaking::do_try_state().unwrap();
+		});
+	}
+}
+
+/// fund and return who.
+pub(crate) fn fund(who: &AccountId, amount: Balance) {
+	let _ = Balances::deposit_creating(who, amount);
+}
+
+/// Sets up delegation for passed delegators, returns total delegated amount.
+///
+/// `delegate_amount` is incremented by the amount `increment` starting with `base_delegate_amount`
+/// from lower index to higher index of delegators.
+pub(crate) fn setup_delegation_stake(
+	agent: AccountId,
+	reward_acc: AccountId,
+	delegators: Vec<AccountId>,
+	base_delegate_amount: Balance,
+	increment: Balance,
+) -> Balance {
+	fund(&agent, 100);
+	assert_ok!(DelegatedStaking::register_agent(RawOrigin::Signed(agent).into(), reward_acc));
+	let mut delegated_amount: Balance = 0;
+	for (index, delegator) in delegators.iter().enumerate() {
+		let amount_to_delegate = base_delegate_amount + increment * index as Balance;
+		delegated_amount += amount_to_delegate;
+
+		fund(delegator, amount_to_delegate + ExistentialDeposit::get());
+		assert_ok!(DelegatedStaking::delegate_to_agent(
+			RawOrigin::Signed(*delegator).into(),
+			agent,
+			amount_to_delegate
+		));
+	}
+
+	// sanity checks
+	assert_eq!(DelegatedStaking::stakeable_balance(&agent), delegated_amount);
+	assert_eq!(Agent::<T>::get(&agent).unwrap().available_to_bond(), 0);
+
+	delegated_amount
+}
+
+pub(crate) fn start_era(era: sp_staking::EraIndex) {
+	CurrentEra::<T>::set(Some(era));
+	ActiveEra::<T>::set(Some(ActiveEraInfo { index: era, start: None }));
+}
+
+pub(crate) fn eq_stake(who: AccountId, total: Balance, active: Balance) -> bool {
+	Staking::stake(&who).unwrap() == Stake { total, active } &&
+		get_agent(&who).ledger.stakeable_balance() == total
+}
+
+pub(crate) fn get_agent(agent: &AccountId) -> Agent<T> {
+	Agent::<T>::get(agent).expect("delegate should exist")
+}
+
+parameter_types! {
+	static ObservedEventsDelegatedStaking: usize = 0;
+}
+
+#[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();
+	ObservedEventsDelegatedStaking::set(events.len());
+	events.into_iter().skip(already_seen).collect()
+}
diff --git a/substrate/frame/delegated-staking/src/tests.rs b/substrate/frame/delegated-staking/src/tests.rs
new file mode 100644
index 00000000000..1f36f655beb
--- /dev/null
+++ b/substrate/frame/delegated-staking/src/tests.rs
@@ -0,0 +1,685 @@
+// 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.
+
+//! Tests for pallet-delegated-staking.
+
+use super::*;
+use crate::mock::*;
+use frame_support::{assert_noop, assert_ok, traits::fungible::InspectHold};
+use pallet_staking::Error as StakingError;
+use sp_staking::DelegationInterface;
+
+#[test]
+fn create_an_agent_with_first_delegator() {
+	ExtBuilder::default().build_and_execute(|| {
+		let agent: AccountId = 200;
+		let reward_account: AccountId = 201;
+		let delegator: AccountId = 202;
+
+		// set intention to accept delegation.
+		fund(&agent, 1000);
+		assert_ok!(DelegatedStaking::register_agent(
+			RawOrigin::Signed(agent).into(),
+			reward_account
+		));
+
+		// delegate to this account
+		fund(&delegator, 1000);
+		assert_ok!(DelegatedStaking::delegate_to_agent(
+			RawOrigin::Signed(delegator).into(),
+			agent,
+			100
+		));
+
+		// verify
+		assert!(DelegatedStaking::is_agent(&agent));
+		assert_eq!(DelegatedStaking::stakeable_balance(&agent), 100);
+		assert_eq!(
+			Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
+			100
+		);
+		assert_eq!(DelegatedStaking::held_balance_of(&delegator), 100);
+	});
+}
+
+#[test]
+fn cannot_become_agent() {
+	ExtBuilder::default().build_and_execute(|| {
+		// cannot set reward account same as agent account
+		assert_noop!(
+			DelegatedStaking::register_agent(RawOrigin::Signed(100).into(), 100),
+			Error::<T>::InvalidRewardDestination
+		);
+
+		// an existing validator cannot become agent
+		assert_noop!(
+			DelegatedStaking::register_agent(
+				RawOrigin::Signed(mock::GENESIS_VALIDATOR).into(),
+				100
+			),
+			Error::<T>::AlreadyStaking
+		);
+
+		// an existing direct staker to `CoreStaking` cannot become an agent.
+		assert_noop!(
+			DelegatedStaking::register_agent(
+				RawOrigin::Signed(mock::GENESIS_NOMINATOR_ONE).into(),
+				100
+			),
+			Error::<T>::AlreadyStaking
+		);
+		assert_noop!(
+			DelegatedStaking::register_agent(
+				RawOrigin::Signed(mock::GENESIS_NOMINATOR_TWO).into(),
+				100
+			),
+			Error::<T>::AlreadyStaking
+		);
+	});
+}
+
+#[test]
+fn create_multiple_delegators() {
+	ExtBuilder::default().build_and_execute(|| {
+		let agent: AccountId = 200;
+		let reward_account: AccountId = 201;
+
+		// stakeable balance is 0 for non agent
+		fund(&agent, 1000);
+		assert!(!DelegatedStaking::is_agent(&agent));
+		assert_eq!(DelegatedStaking::stakeable_balance(&agent), 0);
+
+		// set intention to accept delegation.
+		assert_ok!(DelegatedStaking::register_agent(
+			RawOrigin::Signed(agent).into(),
+			reward_account
+		));
+
+		// create 100 delegators
+		for i in 202..302 {
+			fund(&i, 100 + ExistentialDeposit::get());
+			assert_ok!(DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(i).into(),
+				agent,
+				100
+			));
+			// Balance of 100 held on delegator account for delegating to the agent.
+			assert_eq!(Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &i), 100);
+		}
+
+		// verify
+		assert!(DelegatedStaking::is_agent(&agent));
+		assert_eq!(DelegatedStaking::stakeable_balance(&agent), 100 * 100);
+	});
+}
+
+#[test]
+fn agent_restrictions() {
+	// Similar to creating a nomination pool
+	ExtBuilder::default().build_and_execute(|| {
+		let agent_one = 200;
+		let delegator_one = 210;
+		fund(&agent_one, 100);
+		assert_ok!(DelegatedStaking::register_agent(
+			RawOrigin::Signed(agent_one).into(),
+			agent_one + 1
+		));
+		fund(&delegator_one, 200);
+		assert_ok!(DelegatedStaking::delegate_to_agent(
+			RawOrigin::Signed(delegator_one).into(),
+			agent_one,
+			100
+		));
+
+		let agent_two = 300;
+		let delegator_two = 310;
+		fund(&agent_two, 100);
+		assert_ok!(DelegatedStaking::register_agent(
+			RawOrigin::Signed(agent_two).into(),
+			agent_two + 1
+		));
+		fund(&delegator_two, 200);
+		assert_ok!(DelegatedStaking::delegate_to_agent(
+			RawOrigin::Signed(delegator_two).into(),
+			agent_two,
+			100
+		));
+
+		// agent one tries to delegate to agent 2
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(RawOrigin::Signed(agent_one).into(), agent_two, 10),
+			Error::<T>::InvalidDelegation
+		);
+
+		// agent one tries to delegate to a delegator
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(agent_one).into(),
+				delegator_one,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(agent_one).into(),
+				delegator_two,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+
+		// delegator one tries to delegate to agent 2 as well (it already delegates to agent
+		// 1)
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(delegator_one).into(),
+				agent_two,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+
+		// cannot delegate to non agents.
+		let non_agent = 201;
+		// give it some funds
+		fund(&non_agent, 200);
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(delegator_one).into(),
+				non_agent,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+
+		// cannot delegate to a delegator
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(delegator_one).into(),
+				delegator_two,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+
+		// delegator cannot delegate to self
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(delegator_one).into(),
+				delegator_one,
+				10
+			),
+			Error::<T>::InvalidDelegation
+		);
+
+		// agent cannot delegate to self
+		assert_noop!(
+			DelegatedStaking::delegate_to_agent(RawOrigin::Signed(agent_one).into(), agent_one, 10),
+			Error::<T>::InvalidDelegation
+		);
+	});
+}
+
+#[test]
+fn apply_pending_slash() {
+	ExtBuilder::default().build_and_execute(|| {
+		start_era(1);
+		let agent: AccountId = 200;
+		let reward_acc: AccountId = 201;
+		let delegators: Vec<AccountId> = (301..=350).collect();
+		let reporter: AccountId = 400;
+
+		let total_staked = setup_delegation_stake(agent, reward_acc, delegators.clone(), 10, 10);
+
+		start_era(4);
+		// slash half of the stake
+		pallet_staking::slashing::do_slash::<T>(
+			&agent,
+			total_staked / 2,
+			&mut Default::default(),
+			&mut Default::default(),
+			3,
+		);
+
+		// agent cannot slash an account that is not its delegator.
+		setup_delegation_stake(210, 211, (351..=352).collect(), 100, 0);
+		assert_noop!(
+			<DelegatedStaking as DelegationInterface>::delegator_slash(&agent, &351, 1, Some(400)),
+			Error::<T>::NotAgent
+		);
+		// or a non delegator account
+		fund(&353, 100);
+		assert_noop!(
+			<DelegatedStaking as DelegationInterface>::delegator_slash(&agent, &353, 1, Some(400)),
+			Error::<T>::NotDelegator
+		);
+
+		// ensure bookkept pending slash is correct.
+		assert_eq!(get_agent(&agent).ledger.pending_slash, total_staked / 2);
+		let mut old_reporter_balance = Balances::free_balance(reporter);
+
+		// lets apply the pending slash on delegators.
+		for i in delegators {
+			// balance before slash
+			let initial_pending_slash = get_agent(&agent).ledger.pending_slash;
+			assert!(initial_pending_slash > 0);
+			let unslashed_balance = DelegatedStaking::held_balance_of(&i);
+			let slash = unslashed_balance / 2;
+			// slash half of delegator's delegation.
+			assert_ok!(<DelegatedStaking as DelegationInterface>::delegator_slash(
+				&agent,
+				&i,
+				slash,
+				Some(400)
+			));
+
+			// balance after slash.
+			assert_eq!(DelegatedStaking::held_balance_of(&i), unslashed_balance - slash);
+			// pending slash is reduced by the amount slashed.
+			assert_eq!(get_agent(&agent).ledger.pending_slash, initial_pending_slash - slash);
+			// reporter get 10% of the slash amount.
+			assert_eq!(
+				Balances::free_balance(reporter) - old_reporter_balance,
+				<Staking as StakingInterface>::slash_reward_fraction() * slash,
+			);
+			// update old balance
+			old_reporter_balance = Balances::free_balance(reporter);
+		}
+
+		// nothing to slash anymore
+		assert_eq!(get_agent(&agent).ledger.pending_slash, 0);
+
+		// cannot slash anymore
+		assert_noop!(
+			<DelegatedStaking as DelegationInterface>::delegator_slash(&agent, &350, 1, None),
+			Error::<T>::NothingToSlash
+		);
+	});
+}
+
+/// Integration tests with pallet-staking.
+mod staking_integration {
+	use super::*;
+	use pallet_staking::RewardDestination;
+	use sp_staking::Stake;
+
+	#[test]
+	fn bond() {
+		ExtBuilder::default().build_and_execute(|| {
+			let agent: AccountId = 99;
+			let reward_acc: AccountId = 100;
+			assert_eq!(Staking::status(&agent), Err(StakingError::<T>::NotStash.into()));
+
+			// set intention to become an agent
+			fund(&agent, 100);
+			assert_ok!(DelegatedStaking::register_agent(
+				RawOrigin::Signed(agent).into(),
+				reward_acc
+			));
+			assert_eq!(DelegatedStaking::stakeable_balance(&agent), 0);
+
+			let mut delegated_balance: Balance = 0;
+
+			// set some delegations
+			for delegator in 200..250 {
+				fund(&delegator, 200);
+				assert_ok!(DelegatedStaking::delegate_to_agent(
+					RawOrigin::Signed(delegator).into(),
+					agent,
+					100
+				));
+				delegated_balance += 100;
+				assert_eq!(
+					Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
+					100
+				);
+				assert_eq!(DelegatedStaking::delegator_balance(&delegator), 100);
+
+				let agent_obj = get_agent(&agent);
+				assert_eq!(agent_obj.ledger.stakeable_balance(), delegated_balance);
+				assert_eq!(agent_obj.available_to_bond(), 0);
+				assert_eq!(agent_obj.bonded_stake(), delegated_balance);
+			}
+
+			assert_eq!(Staking::stake(&agent).unwrap(), Stake { total: 50 * 100, active: 50 * 100 })
+		});
+	}
+
+	#[test]
+	fn withdraw_test() {
+		ExtBuilder::default().build_and_execute(|| {
+			// initial era
+			start_era(1);
+			let agent: AccountId = 200;
+			let reward_acc: AccountId = 201;
+			let delegators: Vec<AccountId> = (301..=350).collect();
+			let total_staked =
+				setup_delegation_stake(agent, reward_acc, delegators.clone(), 10, 10);
+
+			// lets go to a new era
+			start_era(2);
+
+			assert!(eq_stake(agent, total_staked, total_staked));
+			// Withdrawing without unbonding would fail.
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 301, 50, 0),
+				Error::<T>::NotEnoughFunds
+			);
+
+			// 305 wants to unbond 50 in era 2, withdrawable in era 5.
+			assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 50));
+
+			// 310 wants to unbond 100 in era 3, withdrawable in era 6.
+			start_era(3);
+			assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 100));
+
+			// 320 wants to unbond 200 in era 4, withdrawable in era 7.
+			start_era(4);
+			assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 200));
+
+			// active stake is now reduced..
+			let expected_active = total_staked - (50 + 100 + 200);
+			assert!(eq_stake(agent, total_staked, expected_active));
+
+			// nothing to withdraw at era 4
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 50, 0),
+				Error::<T>::NotEnoughFunds
+			);
+
+			assert_eq!(get_agent(&agent).available_to_bond(), 0);
+			// full amount is still delegated
+			assert_eq!(get_agent(&agent).ledger.effective_balance(), total_staked);
+
+			start_era(5);
+			// at era 5, 50 tokens are withdrawable, cannot withdraw more.
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 51, 0),
+				Error::<T>::NotEnoughFunds
+			);
+			// less is possible
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				305,
+				30,
+				0
+			));
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				305,
+				20,
+				0
+			));
+
+			// Lets go to future era where everything is unbonded. Withdrawable amount: 100 + 200
+			start_era(7);
+			// 305 has no more amount delegated so it cannot withdraw.
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 305, 5, 0),
+				Error::<T>::NotDelegator
+			);
+			// 309 is an active delegator but has total delegation of 90, so it cannot withdraw more
+			// than that.
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 309, 91, 0),
+				Error::<T>::NotEnoughFunds
+			);
+			// 310 cannot withdraw more than delegated funds.
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 310, 101, 0),
+				Error::<T>::NotEnoughFunds
+			);
+			// but can withdraw all its delegation amount.
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				310,
+				100,
+				0
+			));
+			// 320 can withdraw all its delegation amount.
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				320,
+				200,
+				0
+			));
+
+			// cannot withdraw anything more..
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 301, 1, 0),
+				Error::<T>::NotEnoughFunds
+			);
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 350, 1, 0),
+				Error::<T>::NotEnoughFunds
+			);
+		});
+	}
+
+	#[test]
+	fn withdraw_happens_with_unbonded_balance_first() {
+		ExtBuilder::default().build_and_execute(|| {
+			start_era(1);
+			let agent = 200;
+			setup_delegation_stake(agent, 201, (300..350).collect(), 100, 0);
+
+			// verify withdraw not possible yet
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 300, 100, 0),
+				Error::<T>::NotEnoughFunds
+			);
+
+			// fill up unlocking chunks in core staking.
+			// 10 is the max chunks
+			for i in 2..=11 {
+				start_era(i);
+				assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 10));
+				// no withdrawals from core staking yet.
+				assert_eq!(get_agent(&agent).ledger.unclaimed_withdrawals, 0);
+			}
+
+			// another unbond would trigger withdrawal
+			start_era(12);
+			assert_ok!(Staking::unbond(RawOrigin::Signed(agent).into(), 10));
+
+			// 8 previous unbonds would be withdrawn as they were already unlocked. Unlocking period
+			// is 3 eras.
+			assert_eq!(get_agent(&agent).ledger.unclaimed_withdrawals, 8 * 10);
+
+			// release some delegation now.
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				300,
+				40,
+				0
+			));
+			assert_eq!(get_agent(&agent).ledger.unclaimed_withdrawals, 80 - 40);
+
+			// cannot release more than available
+			assert_noop!(
+				DelegatedStaking::release_delegation(RawOrigin::Signed(agent).into(), 300, 50, 0),
+				Error::<T>::NotEnoughFunds
+			);
+			assert_ok!(DelegatedStaking::release_delegation(
+				RawOrigin::Signed(agent).into(),
+				300,
+				40,
+				0
+			));
+
+			assert_eq!(DelegatedStaking::held_balance_of(&300), 100 - 80);
+		});
+	}
+
+	#[test]
+	fn reward_destination_restrictions() {
+		ExtBuilder::default().build_and_execute(|| {
+			// give some funds to 200
+			fund(&200, 1000);
+			let balance_200 = Balances::free_balance(200);
+
+			// `Agent` account cannot be reward destination
+			assert_noop!(
+				DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 200),
+				Error::<T>::InvalidRewardDestination
+			);
+
+			// different reward account works
+			assert_ok!(DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 201));
+			// add some delegations to it
+			fund(&300, 1000);
+			assert_ok!(DelegatedStaking::delegate_to_agent(
+				RawOrigin::Signed(300).into(),
+				200,
+				100
+			));
+
+			// update_payee to self fails.
+			assert_noop!(
+				<Staking as StakingInterface>::update_payee(&200, &200),
+				StakingError::<T>::RewardDestinationRestricted
+			);
+
+			// passing correct reward destination works
+			assert_ok!(<Staking as StakingInterface>::update_payee(&200, &201));
+
+			// amount is staked correctly
+			assert!(eq_stake(200, 100, 100));
+			assert_eq!(get_agent(&200).available_to_bond(), 0);
+			assert_eq!(get_agent(&200).ledger.effective_balance(), 100);
+
+			// free balance of delegate is untouched
+			assert_eq!(Balances::free_balance(200), balance_200);
+		});
+	}
+
+	#[test]
+	fn agent_restrictions() {
+		ExtBuilder::default().build_and_execute(|| {
+			setup_delegation_stake(200, 201, (202..203).collect(), 100, 0);
+
+			// Registering again is noop
+			assert_noop!(
+				DelegatedStaking::register_agent(RawOrigin::Signed(200).into(), 201),
+				Error::<T>::NotAllowed
+			);
+			// a delegator cannot become delegate
+			assert_noop!(
+				DelegatedStaking::register_agent(RawOrigin::Signed(202).into(), 203),
+				Error::<T>::NotAllowed
+			);
+			// existing staker cannot become a delegate
+			assert_noop!(
+				DelegatedStaking::register_agent(
+					RawOrigin::Signed(GENESIS_NOMINATOR_ONE).into(),
+					201
+				),
+				Error::<T>::AlreadyStaking
+			);
+			assert_noop!(
+				DelegatedStaking::register_agent(RawOrigin::Signed(GENESIS_VALIDATOR).into(), 201),
+				Error::<T>::AlreadyStaking
+			);
+		});
+	}
+
+	#[test]
+	fn migration_works() {
+		ExtBuilder::default().build_and_execute(|| {
+			// add a nominator
+			let staked_amount = 4000;
+			let agent_amount = 5000;
+			fund(&200, agent_amount);
+
+			assert_ok!(Staking::bond(
+				RuntimeOrigin::signed(200),
+				staked_amount,
+				RewardDestination::Account(201)
+			));
+			assert_ok!(Staking::nominate(RuntimeOrigin::signed(200), vec![GENESIS_VALIDATOR],));
+			let init_stake = Staking::stake(&200).unwrap();
+
+			// scenario: 200 is a pool account, and the stake comes from its 4 delegators (300..304)
+			// in equal parts. lets try to migrate this nominator into delegate based stake.
+
+			// all balance currently is in 200
+			assert_eq!(Balances::free_balance(200), agent_amount);
+
+			// 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);
+
+			assert_ok!(DelegatedStaking::migrate_to_agent(RawOrigin::Signed(200).into(), 201));
+
+			// verify all went well
+			let mut expected_proxy_delegated_amount = agent_amount;
+			assert_eq!(
+				Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &proxy_delegator),
+				expected_proxy_delegated_amount
+			);
+			// stake amount is transferred from delegate to proxy delegator account.
+			assert_eq!(Balances::free_balance(200), 0);
+			assert_eq!(Staking::stake(&200).unwrap(), init_stake);
+			assert_eq!(get_agent(&200).ledger.effective_balance(), agent_amount);
+			assert_eq!(get_agent(&200).available_to_bond(), 0);
+			assert_eq!(get_agent(&200).ledger.unclaimed_withdrawals, agent_amount - staked_amount);
+
+			// now lets migrate the delegators
+			let delegator_share = agent_amount / 4;
+			for delegator in 300..304 {
+				assert_eq!(Balances::free_balance(delegator), 0);
+				// fund them with ED
+				fund(&delegator, ExistentialDeposit::get());
+				// migrate 1/4th amount into each delegator
+				assert_ok!(DelegatedStaking::migrate_delegation(
+					RawOrigin::Signed(200).into(),
+					delegator,
+					delegator_share
+				));
+				assert_eq!(
+					Balances::balance_on_hold(&HoldReason::StakingDelegation.into(), &delegator),
+					delegator_share
+				);
+				expected_proxy_delegated_amount -= delegator_share;
+				assert_eq!(
+					Balances::balance_on_hold(
+						&HoldReason::StakingDelegation.into(),
+						&proxy_delegator
+					),
+					expected_proxy_delegated_amount
+				);
+
+				// delegate stake is unchanged.
+				assert_eq!(Staking::stake(&200).unwrap(), init_stake);
+				assert_eq!(get_agent(&200).ledger.effective_balance(), agent_amount);
+				assert_eq!(get_agent(&200).available_to_bond(), 0);
+				assert_eq!(
+					get_agent(&200).ledger.unclaimed_withdrawals,
+					agent_amount - staked_amount
+				);
+			}
+
+			// cannot use migrate delegator anymore
+			assert_noop!(
+				DelegatedStaking::migrate_delegation(RawOrigin::Signed(200).into(), 305, 1),
+				Error::<T>::NotEnoughFunds
+			);
+		});
+	}
+}
diff --git a/substrate/frame/delegated-staking/src/types.rs b/substrate/frame/delegated-staking/src/types.rs
new file mode 100644
index 00000000000..0bfc23281df
--- /dev/null
+++ b/substrate/frame/delegated-staking/src/types.rs
@@ -0,0 +1,292 @@
+// This file is part of Substrate.
+
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
+
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+//! Basic types used in delegated staking.
+
+use super::*;
+use frame_support::traits::DefensiveSaturating;
+
+/// The type of pot account being created.
+#[derive(Encode, Decode)]
+pub(crate) enum AccountType {
+	/// A proxy delegator account created for a nominator who migrated to an `Agent` account.
+	///
+	/// Funds for unmigrated `delegator` accounts of the `Agent` are kept here.
+	ProxyDelegator,
+}
+
+/// Information about delegation of a `delegator`.
+#[derive(Default, Encode, Clone, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+#[scale_info(skip_type_params(T))]
+pub struct Delegation<T: Config> {
+	/// The target of delegation.
+	pub agent: T::AccountId,
+	/// The amount delegated.
+	pub amount: BalanceOf<T>,
+}
+
+impl<T: Config> Delegation<T> {
+	/// Get delegation of a `delegator`.
+	pub(crate) fn get(delegator: &T::AccountId) -> Option<Self> {
+		<Delegators<T>>::get(delegator)
+	}
+
+	/// Create and return a new delegation instance.
+	pub(crate) fn new(agent: &T::AccountId, amount: BalanceOf<T>) -> Self {
+		Delegation { agent: agent.clone(), amount }
+	}
+
+	/// Ensure the delegator is either a new delegator or they are adding more delegation to the
+	/// existing agent.
+	///
+	/// Delegators are prevented from delegating to multiple agents at the same time.
+	pub(crate) fn can_delegate(delegator: &T::AccountId, agent: &T::AccountId) -> bool {
+		Delegation::<T>::get(delegator)
+			.map(|delegation| delegation.agent == *agent)
+			.unwrap_or(
+				// all good if it is a new delegator except it should not be an existing agent.
+				!<Agents<T>>::contains_key(delegator),
+			)
+	}
+
+	/// Save self to storage. If the delegation amount is zero, remove the delegation.
+	pub(crate) fn update_or_kill(self, key: &T::AccountId) {
+		// Clean up if no delegation left.
+		if self.amount == Zero::zero() {
+			<Delegators<T>>::remove(key);
+			return
+		}
+
+		<Delegators<T>>::insert(key, self)
+	}
+}
+
+/// Ledger of all delegations to an `Agent`.
+///
+/// This keeps track of the active balance of the `Agent` that is made up from the funds that
+/// are currently delegated to this `Agent`. It also tracks the pending slashes yet to be
+/// applied among other things.
+#[derive(Default, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
+#[scale_info(skip_type_params(T))]
+pub struct AgentLedger<T: Config> {
+	/// Where the reward should be paid out.
+	pub payee: T::AccountId,
+	/// Sum of all delegated funds to this `Agent`.
+	#[codec(compact)]
+	pub total_delegated: BalanceOf<T>,
+	/// Funds that are withdrawn from core staking but not released to delegator/s. It is a subset
+	/// of `total_delegated` and can never be greater than it.
+	///
+	/// We need this register to ensure that the `Agent` does not bond funds from delegated
+	/// funds that are withdrawn and should be claimed by delegators.
+	#[codec(compact)]
+	pub unclaimed_withdrawals: BalanceOf<T>,
+	/// Slashes that are not yet applied. This affects the effective balance of the `Agent`.
+	#[codec(compact)]
+	pub pending_slash: BalanceOf<T>,
+}
+
+impl<T: Config> AgentLedger<T> {
+	/// Create a new instance of `AgentLedger`.
+	pub(crate) fn new(reward_destination: &T::AccountId) -> Self {
+		AgentLedger {
+			payee: reward_destination.clone(),
+			total_delegated: Zero::zero(),
+			unclaimed_withdrawals: Zero::zero(),
+			pending_slash: Zero::zero(),
+		}
+	}
+
+	/// Get `AgentLedger` from storage.
+	pub(crate) fn get(key: &T::AccountId) -> Option<Self> {
+		<Agents<T>>::get(key)
+	}
+
+	/// Save self to storage with the given key.
+	pub(crate) fn update(self, key: &T::AccountId) {
+		<Agents<T>>::insert(key, self)
+	}
+
+	/// Effective total balance of the `Agent`.
+	///
+	/// This takes into account any slashes reported to `Agent` but unapplied.
+	pub(crate) fn effective_balance(&self) -> BalanceOf<T> {
+		defensive_assert!(
+			self.total_delegated >= self.pending_slash,
+			"slash cannot be higher than actual balance of delegator"
+		);
+
+		// pending slash needs to be burned and cannot be used for stake.
+		self.total_delegated.saturating_sub(self.pending_slash)
+	}
+
+	/// Agent balance that can be staked/bonded in [`T::CoreStaking`].
+	pub(crate) fn stakeable_balance(&self) -> BalanceOf<T> {
+		self.effective_balance().saturating_sub(self.unclaimed_withdrawals)
+	}
+}
+
+/// Wrapper around `AgentLedger` to provide some helper functions to mutate the ledger.
+#[derive(Clone)]
+pub struct Agent<T: Config> {
+	/// storage key
+	pub key: T::AccountId,
+	/// storage value
+	pub ledger: AgentLedger<T>,
+}
+
+impl<T: Config> Agent<T> {
+	/// Get `Agent` from storage if it exists or return an error.
+	pub(crate) fn get(agent: &T::AccountId) -> Result<Agent<T>, DispatchError> {
+		let ledger = AgentLedger::<T>::get(agent).ok_or(Error::<T>::NotAgent)?;
+		Ok(Agent { key: agent.clone(), ledger })
+	}
+
+	/// Remove funds that are withdrawn from [Config::CoreStaking] but not claimed by a delegator.
+	///
+	/// Checked decrease of delegation amount from `total_delegated` and `unclaimed_withdrawals`
+	/// registers. Consumes self and returns a new instance of self if success.
+	pub(crate) fn remove_unclaimed_withdraw(
+		self,
+		amount: BalanceOf<T>,
+	) -> Result<Self, DispatchError> {
+		let new_total_delegated = self
+			.ledger
+			.total_delegated
+			.checked_sub(&amount)
+			.defensive_ok_or(ArithmeticError::Overflow)?;
+		let new_unclaimed_withdrawals = self
+			.ledger
+			.unclaimed_withdrawals
+			.checked_sub(&amount)
+			.defensive_ok_or(ArithmeticError::Overflow)?;
+
+		Ok(Agent {
+			ledger: AgentLedger {
+				total_delegated: new_total_delegated,
+				unclaimed_withdrawals: new_unclaimed_withdrawals,
+				..self.ledger
+			},
+			..self
+		})
+	}
+
+	/// Add funds that are withdrawn from [Config::CoreStaking] to be claimed by delegators later.
+	pub(crate) fn add_unclaimed_withdraw(
+		self,
+		amount: BalanceOf<T>,
+	) -> Result<Self, DispatchError> {
+		let new_unclaimed_withdrawals = self
+			.ledger
+			.unclaimed_withdrawals
+			.checked_add(&amount)
+			.defensive_ok_or(ArithmeticError::Overflow)?;
+
+		Ok(Agent {
+			ledger: AgentLedger { unclaimed_withdrawals: new_unclaimed_withdrawals, ..self.ledger },
+			..self
+		})
+	}
+
+	/// Amount that is delegated but not bonded yet.
+	///
+	/// This importantly does not include `unclaimed_withdrawals` as those should not be bonded
+	/// again unless explicitly requested.
+	pub(crate) fn available_to_bond(&self) -> BalanceOf<T> {
+		let bonded_stake = self.bonded_stake();
+		let stakeable = self.ledger.stakeable_balance();
+
+		defensive_assert!(
+			stakeable >= bonded_stake,
+			"cannot be bonded with more than total amount delegated to agent"
+		);
+
+		stakeable.saturating_sub(bonded_stake)
+	}
+
+	/// Remove slashes from the `AgentLedger`.
+	pub(crate) fn remove_slash(self, amount: BalanceOf<T>) -> Self {
+		let pending_slash = self.ledger.pending_slash.defensive_saturating_sub(amount);
+		let total_delegated = self.ledger.total_delegated.defensive_saturating_sub(amount);
+
+		Agent { ledger: AgentLedger { pending_slash, total_delegated, ..self.ledger }, ..self }
+	}
+
+	/// Get the total stake of agent bonded in [`Config::CoreStaking`].
+	pub(crate) fn bonded_stake(&self) -> BalanceOf<T> {
+		T::CoreStaking::total_stake(&self.key).unwrap_or(Zero::zero())
+	}
+
+	/// Returns true if the agent is bonded in [`Config::CoreStaking`].
+	pub(crate) fn is_bonded(&self) -> bool {
+		T::CoreStaking::stake(&self.key).is_ok()
+	}
+
+	/// Returns the reward account registered by the agent.
+	pub(crate) fn reward_account(&self) -> &T::AccountId {
+		&self.ledger.payee
+	}
+
+	/// Save self to storage.
+	pub(crate) fn save(self) {
+		let key = self.key;
+		self.ledger.update(&key)
+	}
+
+	/// Save self and remove if no delegation left.
+	///
+	/// Returns:
+	/// - true if agent killed.
+	/// - error if the delegate is in an unexpected state.
+	pub(crate) fn update_or_kill(self) -> Result<bool, DispatchError> {
+		let key = self.key;
+		// see if delegate can be killed
+		if self.ledger.total_delegated == Zero::zero() {
+			ensure!(
+				self.ledger.unclaimed_withdrawals == Zero::zero() &&
+					self.ledger.pending_slash == Zero::zero(),
+				Error::<T>::BadState
+			);
+			<Agents<T>>::remove(key);
+			return Ok(true)
+		}
+		self.ledger.update(&key);
+		Ok(false)
+	}
+
+	/// Reloads self from storage.
+	pub(crate) fn refresh(self) -> Result<Agent<T>, DispatchError> {
+		Self::get(&self.key)
+	}
+
+	/// Balance of `Agent` that is not bonded.
+	///
+	/// 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();
+
+		let net_balance = self.ledger.effective_balance();
+
+		assert!(net_balance >= bonded_stake, "cannot be bonded with more than the agent balance");
+
+		net_balance.saturating_sub(bonded_stake)
+	}
+}
diff --git a/substrate/frame/staking/src/lib.rs b/substrate/frame/staking/src/lib.rs
index 692e62acfdf..4f91fd6dff2 100644
--- a/substrate/frame/staking/src/lib.rs
+++ b/substrate/frame/staking/src/lib.rs
@@ -376,7 +376,7 @@ pub struct ActiveEraInfo {
 	///
 	/// Start can be none if start hasn't been set for the era yet,
 	/// Start is set on the first on_finalize of the era to guarantee usage of `Time`.
-	start: Option<u64>,
+	pub start: Option<u64>,
 }
 
 /// Reward points of an era. Used to split era total payout between validators.
diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs
index ad6cc6e2f4f..c7045508cea 100644
--- a/substrate/primitives/staking/src/lib.rs
+++ b/substrate/primitives/staking/src/lib.rs
@@ -456,4 +456,123 @@ pub struct PagedExposureMetadata<Balance: HasCompact + codec::MaxEncodedLen> {
 	pub page_count: Page,
 }
 
+/// Trait to provide delegation functionality for stakers.
+///
+/// Introduces two new terms to the staking system:
+/// - `Delegator`: An account that delegates funds to an `Agent`.
+/// - `Agent`: An account that receives delegated funds from `Delegators`. It can then use these
+/// funds to participate in the staking system. It can never use its own funds to stake. They
+/// (virtually bond)[`StakingUnchecked::virtual_bond`] into the staking system and can also be
+/// termed as `Virtual Nominators`.
+///
+/// The `Agent` is responsible for managing rewards and slashing for all the `Delegators` that
+/// have delegated funds to it.
+pub trait DelegationInterface {
+	/// Balance type used by the staking system.
+	type Balance: Sub<Output = Self::Balance>
+		+ Ord
+		+ PartialEq
+		+ Default
+		+ Copy
+		+ MaxEncodedLen
+		+ FullCodec
+		+ TypeInfo
+		+ Saturating;
+
+	/// AccountId type used by the staking system.
+	type AccountId: Clone + core::fmt::Debug;
+
+	/// Effective balance of the `Agent` account.
+	///
+	/// This takes into account any pending slashes to `Agent`.
+	fn agent_balance(agent: &Self::AccountId) -> Self::Balance;
+
+	/// Returns the total amount of funds delegated by a `delegator`.
+	fn delegator_balance(delegator: &Self::AccountId) -> Self::Balance;
+
+	/// Delegate funds to `Agent`.
+	///
+	/// Only used for the initial delegation. Use [`Self::delegate_extra`] to add more delegation.
+	fn delegate(
+		delegator: &Self::AccountId,
+		agent: &Self::AccountId,
+		reward_account: &Self::AccountId,
+		amount: Self::Balance,
+	) -> DispatchResult;
+
+	/// Add more delegation to the `Agent`.
+	///
+	/// If this is the first delegation, use [`Self::delegate`] instead.
+	fn delegate_extra(
+		delegator: &Self::AccountId,
+		agent: &Self::AccountId,
+		amount: Self::Balance,
+	) -> DispatchResult;
+
+	/// Withdraw or revoke delegation to `Agent`.
+	///
+	/// If there are `Agent` funds upto `amount` available to withdraw, then those funds would
+	/// be released to the `delegator`
+	fn withdraw_delegation(
+		delegator: &Self::AccountId,
+		agent: &Self::AccountId,
+		amount: Self::Balance,
+		num_slashing_spans: u32,
+	) -> DispatchResult;
+
+	/// Returns true if there are pending slashes posted to the `Agent` account.
+	///
+	/// Slashes to `Agent` account are not immediate and are applied lazily. Since `Agent`
+	/// has an unbounded number of delegators, immediate slashing is not possible.
+	fn has_pending_slash(agent: &Self::AccountId) -> bool;
+
+	/// Apply a pending slash to an `Agent` by slashing `value` from `delegator`.
+	///
+	/// A reporter may be provided (if one exists) in order for the implementor to reward them,
+	/// if applicable.
+	fn delegator_slash(
+		agent: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+		maybe_reporter: Option<Self::AccountId>,
+	) -> DispatchResult;
+}
+
+/// Trait to provide functionality for direct stakers to migrate to delegation agents.
+/// See [`DelegationInterface`] for more details on delegation.
+pub trait DelegationMigrator {
+	/// Balance type used by the staking system.
+	type Balance: Sub<Output = Self::Balance>
+		+ Ord
+		+ PartialEq
+		+ Default
+		+ Copy
+		+ MaxEncodedLen
+		+ FullCodec
+		+ TypeInfo
+		+ Saturating;
+
+	/// AccountId type used by the staking system.
+	type AccountId: Clone + core::fmt::Debug;
+
+	/// Migrate an existing `Nominator` to `Agent` account.
+	///
+	/// The implementation should ensure the `Nominator` account funds are moved to an escrow
+	/// from which `Agents` can later release funds to its `Delegators`.
+	fn migrate_nominator_to_agent(
+		agent: &Self::AccountId,
+		reward_account: &Self::AccountId,
+	) -> DispatchResult;
+
+	/// Migrate `value` of delegation to `delegator` from a migrating agent.
+	///
+	/// When a direct `Nominator` migrates to `Agent`, the funds are kept in escrow. This function
+	/// allows the `Agent` to release the funds to the `delegator`.
+	fn migrate_delegation(
+		agent: &Self::AccountId,
+		delegator: &Self::AccountId,
+		value: Self::Balance,
+	) -> DispatchResult;
+}
+
 sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);
-- 
GitLab