referrerpolicy=no-referrer-when-downgrade

pallet_delegated_staking/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Delegated Staking Pallet
19//!
20//! This pallet implements [`sp_staking::DelegationInterface`] that provides delegation
21//! functionality to `delegators` and `agents`. It is designed to be used in conjunction with
22//! [`StakingInterface`] and relies on [`Config::CoreStaking`] to provide primitive staking
23//! functions.
24//!
25//! Currently, it does not expose any dispatchable calls but is written with a vision to expose them
26//! in the future such that it can be utilised by any external account, off-chain entity or xcm
27//! `MultiLocation` such as a parachain or a smart contract.
28//!
29//! ## Key Terminologies
30//! - **Agent**: An account who accepts delegations from other accounts and act as an agent on their
31//!   behalf for staking these delegated funds. Also, sometimes referred as `Delegatee`.
32//! - **Delegator**: An account who delegates their funds to an `agent` and authorises them to use
33//!   it for staking.
34//! - **AgentLedger**: A data structure that holds important information about the `agent` such as
35//!   total delegations they have received, any slashes posted to them, etc.
36//! - **Delegation**: A data structure that stores the amount of funds delegated to an `agent` by a
37//!   `delegator`.
38//!
39//! ## Goals
40//!
41//! Direct nomination on the Staking pallet does not scale well. Nominations pools were created to
42//! address this by pooling delegator funds into one account and then staking it. This though had
43//! a very critical limitation that the funds were moved from delegator account to pool account
44//! and hence the delegator lost control over their funds for using it for other purposes such as
45//! governance. This pallet aims to solve this by extending the staking pallet to support a new
46//! primitive function: delegation of funds to an `agent` with the intent of staking. The agent can
47//! then stake the delegated funds to [`Config::CoreStaking`] on behalf of the delegators.
48//!
49//! ### Withdrawal Management
50//! Agent unbonding does not regulate ordering of consequent withdrawal for delegators. This is upto
51//! the consumer of this pallet to implement in what order unbondable funds from
52//! [`Config::CoreStaking`] can be withdrawn by the delegators.
53//!
54//! ### Reward and Slashing
55//! This pallet does not enforce any specific strategy for how rewards or slashes are applied. It
56//! is upto the `agent` account to decide how to apply the rewards and slashes.
57//!
58//! This importantly allows clients of this pallet to build their own strategies for reward/slashes.
59//! For example, an `agent` account can choose to first slash the reward pot before slashing the
60//! delegators. Or part of the reward can go to an insurance fund that can be used to cover any
61//! potential future slashes. The goal is to eventually allow foreign MultiLocations
62//! (smart contracts or pallets on another chain) to build their own pooled staking solutions
63//! similar to `NominationPools`.
64
65//! ## Core functions
66//!
67//! - Allow an account to receive delegations. See [`Pallet::register_agent`].
68//! - Delegate funds to an `agent` account. See [`Pallet::delegate_to_agent`].
69//! - Release delegated funds from an `agent` account to the `delegator`. See
70//!   [`Pallet::release_delegation`].
71//! - Migrate a `Nominator` account to an `agent` account. See [`Pallet::migrate_to_agent`].
72//!   Explained in more detail in the `Migration` section.
73//! - Migrate unclaimed delegated funds from `agent` to delegator. When a nominator migrates to an
74//!   agent, the funds are held in a proxy account. This function allows the delegator to claim
75//!   their share of the funds from the proxy account. See [`Pallet::migrate_delegation`].
76//!
77//! ## Lazy Slashing
78//! One of the reasons why direct nominators on staking pallet cannot scale well is because all
79//! nominators are slashed at the same time. This is expensive and needs to be bounded operation.
80//!
81//! This pallet implements a lazy slashing mechanism. Any slashes to the `agent` are posted in its
82//! `AgentLedger` as a pending slash. Since the actual amount is held in the multiple
83//! `delegator` accounts, this pallet has no way to know how to apply slash. It is the `agent`'s
84//! responsibility to apply slashes for each delegator, one at a time. Staking pallet ensures the
85//! pending slash never exceeds staked amount and would freeze further withdraws until all pending
86//! slashes are cleared.
87//!
88//! The user of this pallet can apply slash using
89//! [DelegationInterface::delegator_slash](sp_staking::DelegationInterface::delegator_slash).
90//!
91//! ## Migration from Nominator to Agent
92//! More details [here](https://hackmd.io/@ak0n/454-np-governance).
93//!
94//! ## Nomination Pool vs Delegation Staking
95//! This pallet is not a replacement for Nomination Pool but adds a new primitive in addition to
96//! staking pallet that can be used by Nomination Pool to support delegation based staking. It can
97//! be thought of as an extension to the Staking Pallet in relation to Nomination Pools.
98//! Technically, these changes could be made in one of those pallets as well but that would have
99//! meant significant refactoring and high chances of introducing a regression. With this approach,
100//! we can keep the existing pallets with minimal changes and introduce a new pallet that can be
101//! optionally used by Nomination Pool. The vision is to build this in a configurable way such that
102//! runtime can choose whether to use this pallet or not.
103//!
104//! With that said, following is the main difference between
105//! #### Nomination Pool without delegation support
106//!  1) transfer fund from delegator to pool account, and
107//!  2) stake from pool account as a direct nominator.
108//!
109//! #### Nomination Pool with delegation support
110//!  1) delegate fund from delegator to pool account, and
111//!  2) stake from pool account as an `Agent` account on the staking pallet.
112//!
113//! The difference being, in the second approach, the delegated funds will be locked in-place in
114//! user's account enabling them to participate in use cases that allows use of `held` funds such
115//! as participation in governance voting.
116//!
117//! Nomination pool still does all the heavy lifting around pool administration, reward
118//! distribution, lazy slashing and as such, is not meant to be replaced with this pallet.
119//!
120//! ## Limitations
121//! - Rewards can not be auto-compounded.
122//! - Slashes are lazy and hence there could be a period of time when an account can use funds for
123//!   operations such as voting in governance even though they should be slashed.
124
125#![cfg_attr(not(feature = "std"), no_std)]
126#![deny(rustdoc::broken_intra_doc_links)]
127
128mod impls;
129pub mod migration;
130#[cfg(test)]
131mod mock;
132#[cfg(test)]
133mod tests;
134pub mod types;
135
136extern crate alloc;
137
138pub use pallet::*;
139
140use types::*;
141
142use core::convert::TryInto;
143use frame_support::{
144	pallet_prelude::*,
145	traits::{
146		fungible::{
147			hold::{
148				Balanced as FunHoldBalanced, Inspect as FunHoldInspect, Mutate as FunHoldMutate,
149			},
150			Balanced, Inspect as FunInspect, Mutate as FunMutate,
151		},
152		tokens::{fungible::Credit, Fortitude, Precision, Preservation, Restriction},
153		Defensive, DefensiveOption, Imbalance, OnUnbalanced,
154	},
155};
156use sp_io::hashing::blake2_256;
157use sp_runtime::{
158	traits::{CheckedAdd, CheckedSub, TrailingZeroInput, Zero},
159	ArithmeticError, DispatchResult, Perbill, RuntimeDebug, Saturating,
160};
161use sp_staking::{Agent, Delegator, EraIndex, StakingInterface, StakingUnchecked};
162
163/// The log target of this pallet.
164pub const LOG_TARGET: &str = "runtime::delegated-staking";
165// syntactic sugar for logging.
166#[macro_export]
167macro_rules! log {
168	($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
169		log::$level!(
170			target: $crate::LOG_TARGET,
171			concat!("[{:?}] ๐ŸŠโ€โ™‚๏ธ ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
172		)
173	};
174}
175pub type BalanceOf<T> =
176	<<T as Config>::Currency as FunInspect<<T as frame_system::Config>::AccountId>>::Balance;
177
178use frame_system::{ensure_signed, pallet_prelude::*, RawOrigin};
179
180#[frame_support::pallet]
181pub mod pallet {
182	use super::*;
183
184	/// The in-code storage version.
185	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
186	#[pallet::pallet]
187	#[pallet::storage_version(STORAGE_VERSION)]
188	pub struct Pallet<T>(PhantomData<T>);
189
190	#[pallet::config]
191	pub trait Config: frame_system::Config {
192		/// The overarching event type.
193		#[allow(deprecated)]
194		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
195
196		/// Injected identifier for the pallet.
197		#[pallet::constant]
198		type PalletId: Get<frame_support::PalletId>;
199
200		/// Currency type.
201		type Currency: FunHoldMutate<Self::AccountId, Reason = Self::RuntimeHoldReason>
202			+ FunMutate<Self::AccountId>
203			+ FunHoldBalanced<Self::AccountId>;
204
205		/// Handler for the unbalanced reduction when slashing a delegator.
206		type OnSlash: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
207
208		/// Fraction of the slash that is rewarded to the caller of pending slash to the agent.
209		#[pallet::constant]
210		type SlashRewardFraction: Get<Perbill>;
211
212		/// Overarching hold reason.
213		type RuntimeHoldReason: From<HoldReason>;
214
215		/// Core staking implementation.
216		type CoreStaking: StakingUnchecked<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
217	}
218
219	#[pallet::error]
220	pub enum Error<T> {
221		/// The account cannot perform this operation.
222		NotAllowed,
223		/// An existing staker cannot perform this action.
224		AlreadyStaking,
225		/// Reward Destination cannot be same as `Agent` account.
226		InvalidRewardDestination,
227		/// Delegation conditions are not met.
228		///
229		/// Possible issues are
230		/// 1) Cannot delegate to self,
231		/// 2) Cannot delegate to multiple delegates.
232		InvalidDelegation,
233		/// The account does not have enough funds to perform the operation.
234		NotEnoughFunds,
235		/// Not an existing `Agent` account.
236		NotAgent,
237		/// Not a Delegator account.
238		NotDelegator,
239		/// Some corruption in internal state.
240		BadState,
241		/// Unapplied pending slash restricts operation on `Agent`.
242		UnappliedSlash,
243		/// `Agent` has no pending slash to be applied.
244		NothingToSlash,
245		/// Failed to withdraw amount from Core Staking.
246		WithdrawFailed,
247		/// Operation not supported by this pallet.
248		NotSupported,
249	}
250
251	/// A reason for placing a hold on funds.
252	#[pallet::composite_enum]
253	pub enum HoldReason {
254		/// Funds held for stake delegation to another account.
255		#[codec(index = 0)]
256		StakingDelegation,
257	}
258
259	#[pallet::event]
260	#[pallet::generate_deposit(pub (super) fn deposit_event)]
261	pub enum Event<T: Config> {
262		/// Funds delegated by a delegator.
263		Delegated { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
264		/// Funds released to a delegator.
265		Released { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
266		/// Funds slashed from a delegator.
267		Slashed { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
268		/// Unclaimed delegation funds migrated to delegator.
269		MigratedDelegation { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
270	}
271
272	/// Map of Delegators to their `Delegation`.
273	///
274	/// Implementation note: We are not using a double map with `delegator` and `agent` account
275	/// as keys since we want to restrict delegators to delegate only to one account at a time.
276	#[pallet::storage]
277	pub type Delegators<T: Config> =
278		CountedStorageMap<_, Twox64Concat, T::AccountId, Delegation<T>, OptionQuery>;
279
280	/// Map of `Agent` to their `Ledger`.
281	#[pallet::storage]
282	pub type Agents<T: Config> =
283		CountedStorageMap<_, Twox64Concat, T::AccountId, AgentLedger<T>, OptionQuery>;
284
285	// This pallet is not currently written with the intention of exposing any calls. But the
286	// functions defined in the following impl block should act as a good reference for how the
287	// exposed calls would look like when exposed.
288	impl<T: Config> Pallet<T> {
289		/// Register an account to become a stake `Agent`. Sometimes also called a `Delegatee`.
290		///
291		/// Delegators can authorize `Agent`s to stake on their behalf by delegating their funds to
292		/// them. The `Agent` can then use the delegated funds to stake to [`Config::CoreStaking`].
293		///
294		/// An account that is directly staked to [`Config::CoreStaking`] cannot become an `Agent`.
295		/// However, they can migrate to become an agent using [`Self::migrate_to_agent`].
296		///
297		/// Implementation note: This function allows any account to become an agent. It is
298		/// important though that accounts that call [`StakingUnchecked::virtual_bond`] are keyless
299		/// accounts. This is not a problem for now since this is only used by other pallets in the
300		/// runtime which use keyless account as agents. If we later want to expose this as a
301		/// dispatchable call, we should derive a sub-account from the caller and use that as the
302		/// agent account.
303		pub fn register_agent(
304			origin: OriginFor<T>,
305			reward_account: T::AccountId,
306		) -> DispatchResult {
307			let who = ensure_signed(origin)?;
308
309			// Existing `agent` cannot register again and a delegator cannot become an `agent`.
310			ensure!(!Self::is_agent(&who) && !Self::is_delegator(&who), Error::<T>::NotAllowed);
311
312			// Reward account cannot be same as `agent` account.
313			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
314
315			Self::do_register_agent(&who, &reward_account);
316			Ok(())
317		}
318
319		/// Remove an account from being an `Agent`.
320		///
321		/// This can only be called if the agent has no delegated funds, no pending slashes and no
322		/// unclaimed withdrawals.
323		pub fn remove_agent(origin: OriginFor<T>) -> DispatchResult {
324			let who = ensure_signed(origin)?;
325			let ledger = AgentLedger::<T>::get(&who).ok_or(Error::<T>::NotAgent)?;
326
327			ensure!(
328				ledger.total_delegated == Zero::zero() &&
329					ledger.pending_slash == Zero::zero() &&
330					ledger.unclaimed_withdrawals == Zero::zero(),
331				Error::<T>::NotAllowed
332			);
333
334			AgentLedger::<T>::remove(&who);
335			Ok(())
336		}
337
338		/// Migrate from a `Nominator` account to `Agent` account.
339		///
340		/// The origin needs to
341		/// - be a `Nominator` with [`Config::CoreStaking`],
342		/// - not already an `Agent`,
343		///
344		/// This function will create a proxy account to the agent called `proxy_delegator` and
345		/// transfer the directly staked amount by the agent to it. The `proxy_delegator` delegates
346		/// the funds to the origin making origin an `Agent` account. The real `delegator`
347		/// accounts of the origin can later migrate their funds using [Self::migrate_delegation] to
348		/// claim back their share of delegated funds from `proxy_delegator` to self.
349		///
350		/// Any free fund in the agent's account will be marked as unclaimed withdrawal.
351		pub fn migrate_to_agent(
352			origin: OriginFor<T>,
353			reward_account: T::AccountId,
354		) -> DispatchResult {
355			let who = ensure_signed(origin)?;
356			// ensure who is a staker in `CoreStaking` but not already an agent or a delegator.
357			ensure!(
358				Self::is_direct_staker(&who) && !Self::is_agent(&who) && !Self::is_delegator(&who),
359				Error::<T>::NotAllowed
360			);
361
362			// Reward account cannot be same as `agent` account.
363			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
364
365			Self::do_migrate_to_agent(&who, &reward_account)
366		}
367
368		/// Release previously delegated funds by delegator to origin.
369		///
370		/// Only agents can call this.
371		///
372		/// Tries to withdraw unbonded funds from `CoreStaking` if needed and release amount to
373		/// `delegator`.
374		pub fn release_delegation(
375			origin: OriginFor<T>,
376			delegator: T::AccountId,
377			amount: BalanceOf<T>,
378			num_slashing_spans: u32,
379		) -> DispatchResult {
380			let who = ensure_signed(origin)?;
381			Self::do_release(
382				Agent::from(who),
383				Delegator::from(delegator),
384				amount,
385				num_slashing_spans,
386			)
387		}
388
389		/// Migrate delegated funds that are held in `proxy_delegator` to the claiming `delegator`'s
390		/// account. If successful, the specified funds will be moved and delegated from `delegator`
391		/// account to the agent.
392		///
393		/// This can be called by `agent` accounts that were previously a direct `Nominator` with
394		/// [`Config::CoreStaking`] and has some remaining unclaimed delegations.
395		///
396		/// Internally, it moves some delegations from `proxy_delegator` account to `delegator`
397		/// account and reapplying the holds.
398		pub fn migrate_delegation(
399			origin: OriginFor<T>,
400			delegator: T::AccountId,
401			amount: BalanceOf<T>,
402		) -> DispatchResult {
403			let agent = ensure_signed(origin)?;
404
405			// Ensure delegator is sane.
406			ensure!(!Self::is_agent(&delegator), Error::<T>::NotAllowed);
407			ensure!(!Self::is_delegator(&delegator), Error::<T>::NotAllowed);
408
409			// ensure agent is sane.
410			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
411
412			// and has enough delegated balance to migrate.
413			let proxy_delegator = Self::generate_proxy_delegator(Agent::from(agent));
414			let balance_remaining = Self::held_balance_of(proxy_delegator.clone());
415			ensure!(balance_remaining >= amount, Error::<T>::NotEnoughFunds);
416
417			Self::do_migrate_delegation(proxy_delegator, Delegator::from(delegator), amount)
418		}
419
420		/// Delegate given `amount` of tokens to an `Agent` account.
421		///
422		/// If `origin` is the first time delegator, we add them to state. If they are already
423		/// delegating, we increase the delegation.
424		///
425		/// Conditions:
426		/// - Delegators cannot delegate to more than one agent.
427		/// - The `agent` account should already be registered as such. See
428		///   [`Self::register_agent`].
429		pub fn delegate_to_agent(
430			origin: OriginFor<T>,
431			agent: T::AccountId,
432			amount: BalanceOf<T>,
433		) -> DispatchResult {
434			let delegator = ensure_signed(origin)?;
435
436			// ensure delegator is sane.
437			ensure!(
438				Delegation::<T>::can_delegate(&delegator, &agent),
439				Error::<T>::InvalidDelegation
440			);
441
442			// ensure agent is sane.
443			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
444
445			// add to delegation.
446			Self::do_delegate(Delegator::from(delegator), Agent::from(agent.clone()), amount)?;
447
448			// bond the newly delegated amount to `CoreStaking`.
449			Self::do_bond(Agent::from(agent), amount)
450		}
451	}
452
453	#[pallet::hooks]
454	impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
455		#[cfg(feature = "try-runtime")]
456		fn try_state(_n: BlockNumberFor<T>) -> Result<(), sp_runtime::TryRuntimeError> {
457			Self::do_try_state()
458		}
459	}
460}
461
462impl<T: Config> Pallet<T> {
463	/// Derive an account from the migrating agent account where the unclaimed delegation funds
464	/// are held.
465	pub fn generate_proxy_delegator(agent: Agent<T::AccountId>) -> Delegator<T::AccountId> {
466		Delegator::from(Self::sub_account(AccountType::ProxyDelegator, agent.get()))
467	}
468
469	/// Derive a (keyless) pot account from the given agent account and account type.
470	fn sub_account(account_type: AccountType, acc: T::AccountId) -> T::AccountId {
471		let entropy = (T::PalletId::get(), acc, account_type).using_encoded(blake2_256);
472		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
473			.expect("infinite length input; no invalid inputs for type; qed")
474	}
475
476	/// Held balance of a delegator.
477	pub(crate) fn held_balance_of(who: Delegator<T::AccountId>) -> BalanceOf<T> {
478		T::Currency::balance_on_hold(&HoldReason::StakingDelegation.into(), &who.get())
479	}
480
481	/// Returns true if who is registered as an `Agent`.
482	fn is_agent(who: &T::AccountId) -> bool {
483		<Agents<T>>::contains_key(who)
484	}
485
486	/// Returns true if who is delegating to an `Agent` account.
487	fn is_delegator(who: &T::AccountId) -> bool {
488		<Delegators<T>>::contains_key(who)
489	}
490
491	/// Returns true if who is already staking on [`Config::CoreStaking`].
492	fn is_direct_staker(who: &T::AccountId) -> bool {
493		T::CoreStaking::status(who).is_ok()
494	}
495
496	/// Registers a new agent in the system.
497	fn do_register_agent(who: &T::AccountId, reward_account: &T::AccountId) {
498		// TODO: Consider taking a deposit for being an agent.
499		AgentLedger::<T>::new(reward_account).update(who);
500	}
501
502	/// Migrate existing staker account `who` to an `Agent` account.
503	fn do_migrate_to_agent(who: &T::AccountId, reward_account: &T::AccountId) -> DispatchResult {
504		Self::do_register_agent(who, reward_account);
505
506		// We create a proxy delegator that will keep all the delegation funds until funds are
507		// transferred to actual delegator.
508		let proxy_delegator = Self::generate_proxy_delegator(Agent::from(who.clone()));
509
510		// Get current stake
511		let stake = T::CoreStaking::stake(who)?;
512
513		// release funds from core staking.
514		T::CoreStaking::migrate_to_virtual_staker(who)?;
515
516		// transfer just released staked amount plus any free amount.
517		let amount_to_transfer =
518			T::Currency::reducible_balance(who, Preservation::Expendable, Fortitude::Polite);
519
520		// This should never fail but if it does, it indicates bad state and we abort.
521		T::Currency::transfer(
522			who,
523			&proxy_delegator.clone().get(),
524			amount_to_transfer,
525			Preservation::Expendable,
526		)?;
527
528		T::CoreStaking::set_payee(who, reward_account)?;
529		// delegate all transferred funds back to agent.
530		Self::do_delegate(proxy_delegator, Agent::from(who.clone()), amount_to_transfer)?;
531		// if the transferred/delegated amount was greater than the stake, mark the extra as
532		// unclaimed withdrawal.
533		let unclaimed_withdraws = amount_to_transfer
534			.checked_sub(&stake.total)
535			.defensive_ok_or(ArithmeticError::Underflow)?;
536
537		if !unclaimed_withdraws.is_zero() {
538			let mut ledger = AgentLedger::<T>::get(who).ok_or(Error::<T>::NotAgent)?;
539			ledger.unclaimed_withdrawals = ledger
540				.unclaimed_withdrawals
541				.checked_add(&unclaimed_withdraws)
542				.defensive_ok_or(ArithmeticError::Overflow)?;
543			ledger.update(who);
544		}
545
546		Ok(())
547	}
548
549	/// Bond `amount` to `agent_acc` in [`Config::CoreStaking`].
550	fn do_bond(agent_acc: Agent<T::AccountId>, amount: BalanceOf<T>) -> DispatchResult {
551		let agent_ledger = AgentLedgerOuter::<T>::get(&agent_acc.get())?;
552
553		let available_to_bond = agent_ledger.available_to_bond();
554		defensive_assert!(amount == available_to_bond, "not expected value to bond");
555
556		if agent_ledger.is_bonded() {
557			T::CoreStaking::bond_extra(&agent_ledger.key, amount)
558		} else {
559			T::CoreStaking::virtual_bond(&agent_ledger.key, amount, agent_ledger.reward_account())
560		}
561	}
562
563	/// Delegate `amount` from `delegator` to `agent`.
564	fn do_delegate(
565		delegator: Delegator<T::AccountId>,
566		agent: Agent<T::AccountId>,
567		amount: BalanceOf<T>,
568	) -> DispatchResult {
569		// get inner type
570		let agent = agent.get();
571		let delegator = delegator.get();
572
573		let mut ledger = AgentLedger::<T>::get(&agent).ok_or(Error::<T>::NotAgent)?;
574
575		if let Some(mut existing_delegation) = Delegation::<T>::get(&delegator) {
576			ensure!(existing_delegation.agent == agent, Error::<T>::InvalidDelegation);
577			// update amount and return the updated delegation.
578			existing_delegation.amount = existing_delegation
579				.amount
580				.checked_add(&amount)
581				.ok_or(ArithmeticError::Overflow)?;
582			existing_delegation
583		} else {
584			Delegation::<T>::new(&agent, amount)
585		}
586		.update(&delegator);
587
588		// try to hold the funds.
589		T::Currency::hold(&HoldReason::StakingDelegation.into(), &delegator, amount)?;
590
591		ledger.total_delegated =
592			ledger.total_delegated.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
593		ledger.update(&agent);
594
595		Self::deposit_event(Event::<T>::Delegated { agent, delegator, amount });
596
597		Ok(())
598	}
599
600	/// Release `amount` of delegated funds from `agent` to `delegator`.
601	fn do_release(
602		who: Agent<T::AccountId>,
603		delegator: Delegator<T::AccountId>,
604		amount: BalanceOf<T>,
605		num_slashing_spans: u32,
606	) -> DispatchResult {
607		// get inner type
608		let agent = who.get();
609		let delegator = delegator.get();
610
611		let mut agent_ledger = AgentLedgerOuter::<T>::get(&agent)?;
612		let mut delegation = Delegation::<T>::get(&delegator).ok_or(Error::<T>::NotDelegator)?;
613
614		// make sure delegation to be released is sound.
615		ensure!(delegation.agent == agent, Error::<T>::NotAgent);
616		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
617
618		// if we do not already have enough funds to be claimed, try to withdraw some more.
619		if agent_ledger.ledger.unclaimed_withdrawals < amount {
620			// withdraw account.
621			T::CoreStaking::withdraw_unbonded(agent.clone(), num_slashing_spans)
622				.map_err(|_| Error::<T>::WithdrawFailed)?;
623			// reload agent from storage since withdrawal might have changed the state.
624			agent_ledger = agent_ledger.reload()?;
625		}
626
627		// if we still do not have enough funds to release, abort.
628		ensure!(agent_ledger.ledger.unclaimed_withdrawals >= amount, Error::<T>::NotEnoughFunds);
629		agent_ledger.remove_unclaimed_withdraw(amount)?.update();
630
631		delegation.amount = delegation
632			.amount
633			.checked_sub(&amount)
634			.defensive_ok_or(ArithmeticError::Overflow)?;
635
636		let released = T::Currency::release(
637			&HoldReason::StakingDelegation.into(),
638			&delegator,
639			amount,
640			Precision::BestEffort,
641		)?;
642
643		defensive_assert!(released == amount, "hold should have been released fully");
644
645		// update delegation.
646		delegation.update(&delegator);
647
648		Self::deposit_event(Event::<T>::Released { agent, delegator, amount });
649
650		Ok(())
651	}
652
653	/// Migrates delegation of `amount` from `source` account to `destination` account.
654	fn do_migrate_delegation(
655		source_delegator: Delegator<T::AccountId>,
656		destination_delegator: Delegator<T::AccountId>,
657		amount: BalanceOf<T>,
658	) -> DispatchResult {
659		// get inner type
660		let source_delegator = source_delegator.get();
661		let destination_delegator = destination_delegator.get();
662
663		let mut source_delegation =
664			Delegators::<T>::get(&source_delegator).defensive_ok_or(Error::<T>::BadState)?;
665
666		// ensure source has enough funds to migrate.
667		ensure!(source_delegation.amount >= amount, Error::<T>::NotEnoughFunds);
668		debug_assert!(
669			!Self::is_delegator(&destination_delegator) && !Self::is_agent(&destination_delegator)
670		);
671
672		let agent = source_delegation.agent.clone();
673		// create a new delegation for destination delegator.
674		Delegation::<T>::new(&agent, amount).update(&destination_delegator);
675
676		source_delegation.amount = source_delegation
677			.amount
678			.checked_sub(&amount)
679			.defensive_ok_or(Error::<T>::BadState)?;
680
681		// transfer the held amount in `source_delegator` to `destination_delegator`.
682		T::Currency::transfer_on_hold(
683			&HoldReason::StakingDelegation.into(),
684			&source_delegator,
685			&destination_delegator,
686			amount,
687			Precision::Exact,
688			Restriction::OnHold,
689			Fortitude::Polite,
690		)?;
691
692		// update source delegation.
693		source_delegation.update(&source_delegator);
694
695		Self::deposit_event(Event::<T>::MigratedDelegation {
696			agent,
697			delegator: destination_delegator,
698			amount,
699		});
700
701		Ok(())
702	}
703
704	/// Take slash `amount` from agent's `pending_slash`counter and apply it to `delegator` account.
705	pub fn do_slash(
706		agent: Agent<T::AccountId>,
707		delegator: Delegator<T::AccountId>,
708		amount: BalanceOf<T>,
709		maybe_reporter: Option<T::AccountId>,
710	) -> DispatchResult {
711		// get inner type
712		let agent = agent.get();
713		let delegator = delegator.get();
714
715		let agent_ledger = AgentLedgerOuter::<T>::get(&agent)?;
716		// ensure there is something to slash
717		ensure!(agent_ledger.ledger.pending_slash > Zero::zero(), Error::<T>::NothingToSlash);
718
719		let mut delegation = <Delegators<T>>::get(&delegator).ok_or(Error::<T>::NotDelegator)?;
720		ensure!(delegation.agent == agent.clone(), Error::<T>::NotAgent);
721		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
722
723		// slash delegator
724		let (mut credit, missing) =
725			T::Currency::slash(&HoldReason::StakingDelegation.into(), &delegator, amount);
726
727		defensive_assert!(missing.is_zero(), "slash should have been fully applied");
728
729		let actual_slash = credit.peek();
730
731		// remove the applied slashed amount from agent.
732		agent_ledger.remove_slash(actual_slash).save();
733		delegation.amount =
734			delegation.amount.checked_sub(&actual_slash).ok_or(ArithmeticError::Overflow)?;
735		delegation.update(&delegator);
736
737		if let Some(reporter) = maybe_reporter {
738			let reward_payout: BalanceOf<T> = T::SlashRewardFraction::get() * actual_slash;
739			let (reporter_reward, rest) = credit.split(reward_payout);
740
741			// credit is the amount that we provide to `T::OnSlash`.
742			credit = rest;
743
744			// reward reporter or drop it.
745			let _ = T::Currency::resolve(&reporter, reporter_reward);
746		}
747
748		T::OnSlash::on_unbalanced(credit);
749
750		Self::deposit_event(Event::<T>::Slashed { agent, delegator, amount });
751
752		Ok(())
753	}
754
755	/// Total balance that is available for stake. Includes already staked amount.
756	#[cfg(test)]
757	pub(crate) fn stakeable_balance(who: Agent<T::AccountId>) -> BalanceOf<T> {
758		AgentLedgerOuter::<T>::get(&who.get())
759			.map(|agent| agent.ledger.stakeable_balance())
760			.unwrap_or_default()
761	}
762}
763
764#[cfg(any(test, feature = "try-runtime"))]
765use alloc::collections::btree_map::BTreeMap;
766
767#[cfg(any(test, feature = "try-runtime"))]
768impl<T: Config> Pallet<T> {
769	pub(crate) fn do_try_state() -> Result<(), sp_runtime::TryRuntimeError> {
770		// build map to avoid reading storage multiple times.
771		let delegation_map = Delegators::<T>::iter().collect::<BTreeMap<_, _>>();
772		let ledger_map = Agents::<T>::iter().collect::<BTreeMap<_, _>>();
773
774		Self::check_delegates(ledger_map.clone())?;
775		Self::check_delegators(delegation_map, ledger_map)?;
776
777		Ok(())
778	}
779
780	fn check_delegates(
781		ledgers: BTreeMap<T::AccountId, AgentLedger<T>>,
782	) -> Result<(), sp_runtime::TryRuntimeError> {
783		for (agent, ledger) in ledgers {
784			let staked_value = ledger.stakeable_balance();
785
786			if !staked_value.is_zero() {
787				ensure!(
788					matches!(
789						T::CoreStaking::status(&agent).expect("agent should be bonded"),
790						sp_staking::StakerStatus::Nominator(_) | sp_staking::StakerStatus::Idle
791					),
792					"agent should be bonded and not validator"
793				);
794			}
795
796			ensure!(
797				ledger.stakeable_balance() >=
798					T::CoreStaking::total_stake(&agent).unwrap_or_default(),
799				"Cannot stake more than balance"
800			);
801		}
802
803		Ok(())
804	}
805
806	fn check_delegators(
807		delegations: BTreeMap<T::AccountId, Delegation<T>>,
808		ledger: BTreeMap<T::AccountId, AgentLedger<T>>,
809	) -> Result<(), sp_runtime::TryRuntimeError> {
810		let mut delegation_aggregation = BTreeMap::<T::AccountId, BalanceOf<T>>::new();
811		for (delegator, delegation) in delegations.iter() {
812			ensure!(!Self::is_agent(delegator), "delegator cannot be an agent");
813
814			delegation_aggregation
815				.entry(delegation.agent.clone())
816				.and_modify(|e| *e += delegation.amount)
817				.or_insert(delegation.amount);
818		}
819
820		for (agent, total_delegated) in delegation_aggregation {
821			ensure!(!Self::is_delegator(&agent), "agent cannot be delegator");
822
823			let ledger = ledger.get(&agent).expect("ledger should exist");
824			ensure!(
825				ledger.total_delegated == total_delegated,
826				"ledger total delegated should match delegations"
827			);
828		}
829
830		Ok(())
831	}
832}