Skip to main content

pezpallet_delegated_staking/
lib.rs

1// This file is part of Bizinikiwi.
2
3// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
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 Pezpallet
19//!
20//! This pezpallet implements [`pezsp_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 teyrchain 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 pezpallet does not scale well. Nominations pools were created
42//! to 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 pezpallet aims to solve this by extending the staking pezpallet to support a
46//! new primitive function: delegation of funds to an `agent` with the intent of staking. The agent
47//! can 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 pezpallet to implement in what order unbondable funds from
52//! [`Config::CoreStaking`] can be withdrawn by the delegators.
53//!
54//! ### Reward and Slashing
55//! This pezpallet 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 pezpallet to build their own strategies for
59//! reward/slashes. For example, an `agent` account can choose to first slash the reward pot before
60//! slashing the delegators. Or part of the reward can go to an insurance fund that can be used to
61//! cover any 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 [`Pezpallet::register_agent`].
68//! - Delegate funds to an `agent` account. See [`Pezpallet::delegate_to_agent`].
69//! - Release delegated funds from an `agent` account to the `delegator`. See
70//!   [`Pezpallet::release_delegation`].
71//! - Migrate a `Nominator` account to an `agent` account. See [`Pezpallet::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 [`Pezpallet::migrate_delegation`].
76//!
77//! ## Lazy Slashing
78//! One of the reasons why direct nominators on staking pezpallet 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 pezpallet implements a lazy slashing mechanism. Any slashes to the `agent` are posted in
82//! its `AgentLedger` as a pending slash. Since the actual amount is held in the multiple
83//! `delegator` accounts, this pezpallet 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 pezpallet 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 pezpallet can apply slash using
89//! [DelegationInterface::delegator_slash](pezsp_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 pezpallet is not a replacement for Nomination Pool but adds a new primitive in addition to
96//! staking pezpallet that can be used by Nomination Pool to support delegation based staking. It
97//! can be thought of as an extension to the Staking Pezpallet 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 pezpallet 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 pezpallet 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 pezpallet.
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 pezpallet.
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 pezpallet::*;
139
140use types::*;
141
142use core::convert::TryInto;
143use pezframe_support::{
144	pezpallet_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 pezsp_io::hashing::blake2_256;
157use pezsp_runtime::{
158	traits::{CheckedAdd, CheckedSub, TrailingZeroInput, Zero},
159	ArithmeticError, DispatchResult, Perbill, RuntimeDebug, Saturating,
160};
161use pezsp_staking::{Agent, Delegator, EraIndex, StakingInterface, StakingUnchecked};
162
163/// The log target of this pezpallet.
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), <pezframe_system::Pezpallet<T>>::block_number() $(, $values)*
172		)
173	};
174}
175pub type BalanceOf<T> =
176	<<T as Config>::Currency as FunInspect<<T as pezframe_system::Config>::AccountId>>::Balance;
177
178use pezframe_system::{ensure_signed, pezpallet_prelude::*, RawOrigin};
179
180#[pezframe_support::pezpallet]
181pub mod pezpallet {
182	use super::*;
183
184	/// The in-code storage version.
185	const STORAGE_VERSION: StorageVersion = StorageVersion::new(0);
186	#[pezpallet::pezpallet]
187	#[pezpallet::storage_version(STORAGE_VERSION)]
188	pub struct Pezpallet<T>(PhantomData<T>);
189
190	#[pezpallet::config]
191	pub trait Config: pezframe_system::Config {
192		/// The overarching event type.
193		#[allow(deprecated)]
194		type RuntimeEvent: From<Event<Self>>
195			+ IsType<<Self as pezframe_system::Config>::RuntimeEvent>;
196
197		/// Injected identifier for the pezpallet.
198		#[pezpallet::constant]
199		type PalletId: Get<pezframe_support::PalletId>;
200
201		/// Currency type.
202		type Currency: FunHoldMutate<Self::AccountId, Reason = Self::RuntimeHoldReason>
203			+ FunMutate<Self::AccountId>
204			+ FunHoldBalanced<Self::AccountId>;
205
206		/// Handler for the unbalanced reduction when slashing a delegator.
207		type OnSlash: OnUnbalanced<Credit<Self::AccountId, Self::Currency>>;
208
209		/// Fraction of the slash that is rewarded to the caller of pending slash to the agent.
210		#[pezpallet::constant]
211		type SlashRewardFraction: Get<Perbill>;
212
213		/// Overarching hold reason.
214		type RuntimeHoldReason: From<HoldReason>;
215
216		/// Core staking implementation.
217		type CoreStaking: StakingUnchecked<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
218	}
219
220	#[pezpallet::error]
221	pub enum Error<T> {
222		/// The account cannot perform this operation.
223		NotAllowed,
224		/// An existing staker cannot perform this action.
225		AlreadyStaking,
226		/// Reward Destination cannot be same as `Agent` account.
227		InvalidRewardDestination,
228		/// Delegation conditions are not met.
229		///
230		/// Possible issues are
231		/// 1) Cannot delegate to self,
232		/// 2) Cannot delegate to multiple delegates.
233		InvalidDelegation,
234		/// The account does not have enough funds to perform the operation.
235		NotEnoughFunds,
236		/// Not an existing `Agent` account.
237		NotAgent,
238		/// Not a Delegator account.
239		NotDelegator,
240		/// Some corruption in internal state.
241		BadState,
242		/// Unapplied pending slash restricts operation on `Agent`.
243		UnappliedSlash,
244		/// `Agent` has no pending slash to be applied.
245		NothingToSlash,
246		/// Failed to withdraw amount from Core Staking.
247		WithdrawFailed,
248		/// Operation not supported by this pezpallet.
249		NotSupported,
250	}
251
252	/// A reason for placing a hold on funds.
253	#[pezpallet::composite_enum]
254	pub enum HoldReason {
255		/// Funds held for stake delegation to another account.
256		#[codec(index = 0)]
257		StakingDelegation,
258	}
259
260	#[pezpallet::event]
261	#[pezpallet::generate_deposit(pub (super) fn deposit_event)]
262	pub enum Event<T: Config> {
263		/// Funds delegated by a delegator.
264		Delegated { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
265		/// Funds released to a delegator.
266		Released { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
267		/// Funds slashed from a delegator.
268		Slashed { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
269		/// Unclaimed delegation funds migrated to delegator.
270		MigratedDelegation { agent: T::AccountId, delegator: T::AccountId, amount: BalanceOf<T> },
271	}
272
273	/// Map of Delegators to their `Delegation`.
274	///
275	/// Implementation note: We are not using a double map with `delegator` and `agent` account
276	/// as keys since we want to restrict delegators to delegate only to one account at a time.
277	#[pezpallet::storage]
278	pub type Delegators<T: Config> =
279		CountedStorageMap<_, Twox64Concat, T::AccountId, Delegation<T>, OptionQuery>;
280
281	/// Map of `Agent` to their `Ledger`.
282	#[pezpallet::storage]
283	pub type Agents<T: Config> =
284		CountedStorageMap<_, Twox64Concat, T::AccountId, AgentLedger<T>, OptionQuery>;
285
286	// This pezpallet is not currently written with the intention of exposing any calls. But the
287	// functions defined in the following impl block should act as a good reference for how the
288	// exposed calls would look like when exposed.
289	impl<T: Config> Pezpallet<T> {
290		/// Register an account to become a stake `Agent`. Sometimes also called a `Delegatee`.
291		///
292		/// Delegators can authorize `Agent`s to stake on their behalf by delegating their funds to
293		/// them. The `Agent` can then use the delegated funds to stake to [`Config::CoreStaking`].
294		///
295		/// An account that is directly staked to [`Config::CoreStaking`] cannot become an `Agent`.
296		/// However, they can migrate to become an agent using [`Self::migrate_to_agent`].
297		///
298		/// Implementation note: This function allows any account to become an agent. It is
299		/// important though that accounts that call [`StakingUnchecked::virtual_bond`] are keyless
300		/// accounts. This is not a problem for now since this is only used by other pallets in the
301		/// runtime which use keyless account as agents. If we later want to expose this as a
302		/// dispatchable call, we should derive a sub-account from the caller and use that as the
303		/// agent account.
304		pub fn register_agent(
305			origin: OriginFor<T>,
306			reward_account: T::AccountId,
307		) -> DispatchResult {
308			let who = ensure_signed(origin)?;
309
310			// Existing `agent` cannot register again and a delegator cannot become an `agent`.
311			ensure!(!Self::is_agent(&who) && !Self::is_delegator(&who), Error::<T>::NotAllowed);
312
313			// Reward account cannot be same as `agent` account.
314			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
315
316			Self::do_register_agent(&who, &reward_account);
317			Ok(())
318		}
319
320		/// Remove an account from being an `Agent`.
321		///
322		/// This can only be called if the agent has no delegated funds, no pending slashes and no
323		/// unclaimed withdrawals.
324		pub fn remove_agent(origin: OriginFor<T>) -> DispatchResult {
325			let who = ensure_signed(origin)?;
326			let ledger = AgentLedger::<T>::get(&who).ok_or(Error::<T>::NotAgent)?;
327
328			ensure!(
329				ledger.total_delegated == Zero::zero()
330					&& ledger.pending_slash == Zero::zero()
331					&& ledger.unclaimed_withdrawals == Zero::zero(),
332				Error::<T>::NotAllowed
333			);
334
335			AgentLedger::<T>::remove(&who);
336			Ok(())
337		}
338
339		/// Migrate from a `Nominator` account to `Agent` account.
340		///
341		/// The origin needs to
342		/// - be a `Nominator` with [`Config::CoreStaking`],
343		/// - not already an `Agent`,
344		///
345		/// This function will create a proxy account to the agent called `proxy_delegator` and
346		/// transfer the directly staked amount by the agent to it. The `proxy_delegator` delegates
347		/// the funds to the origin making origin an `Agent` account. The real `delegator`
348		/// accounts of the origin can later migrate their funds using [Self::migrate_delegation] to
349		/// claim back their share of delegated funds from `proxy_delegator` to self.
350		///
351		/// Any free fund in the agent's account will be marked as unclaimed withdrawal.
352		pub fn migrate_to_agent(
353			origin: OriginFor<T>,
354			reward_account: T::AccountId,
355		) -> DispatchResult {
356			let who = ensure_signed(origin)?;
357			// ensure who is a staker in `CoreStaking` but not already an agent or a delegator.
358			ensure!(
359				Self::is_direct_staker(&who) && !Self::is_agent(&who) && !Self::is_delegator(&who),
360				Error::<T>::NotAllowed
361			);
362
363			// Reward account cannot be same as `agent` account.
364			ensure!(reward_account != who, Error::<T>::InvalidRewardDestination);
365
366			Self::do_migrate_to_agent(&who, &reward_account)
367		}
368
369		/// Release previously delegated funds by delegator to origin.
370		///
371		/// Only agents can call this.
372		///
373		/// Tries to withdraw unbonded funds from `CoreStaking` if needed and release amount to
374		/// `delegator`.
375		pub fn release_delegation(
376			origin: OriginFor<T>,
377			delegator: T::AccountId,
378			amount: BalanceOf<T>,
379			num_slashing_spans: u32,
380		) -> DispatchResult {
381			let who = ensure_signed(origin)?;
382			Self::do_release(
383				Agent::from(who),
384				Delegator::from(delegator),
385				amount,
386				num_slashing_spans,
387			)
388		}
389
390		/// Migrate delegated funds that are held in `proxy_delegator` to the claiming `delegator`'s
391		/// account. If successful, the specified funds will be moved and delegated from `delegator`
392		/// account to the agent.
393		///
394		/// This can be called by `agent` accounts that were previously a direct `Nominator` with
395		/// [`Config::CoreStaking`] and has some remaining unclaimed delegations.
396		///
397		/// Internally, it moves some delegations from `proxy_delegator` account to `delegator`
398		/// account and reapplying the holds.
399		pub fn migrate_delegation(
400			origin: OriginFor<T>,
401			delegator: T::AccountId,
402			amount: BalanceOf<T>,
403		) -> DispatchResult {
404			let agent = ensure_signed(origin)?;
405
406			// Ensure delegator is sane.
407			ensure!(!Self::is_agent(&delegator), Error::<T>::NotAllowed);
408			ensure!(!Self::is_delegator(&delegator), Error::<T>::NotAllowed);
409
410			// ensure agent is sane.
411			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
412
413			// and has enough delegated balance to migrate.
414			let proxy_delegator = Self::generate_proxy_delegator(Agent::from(agent));
415			let balance_remaining = Self::held_balance_of(proxy_delegator.clone());
416			ensure!(balance_remaining >= amount, Error::<T>::NotEnoughFunds);
417
418			Self::do_migrate_delegation(proxy_delegator, Delegator::from(delegator), amount)
419		}
420
421		/// Delegate given `amount` of tokens to an `Agent` account.
422		///
423		/// If `origin` is the first time delegator, we add them to state. If they are already
424		/// delegating, we increase the delegation.
425		///
426		/// Conditions:
427		/// - Delegators cannot delegate to more than one agent.
428		/// - The `agent` account should already be registered as such. See
429		///   [`Self::register_agent`].
430		pub fn delegate_to_agent(
431			origin: OriginFor<T>,
432			agent: T::AccountId,
433			amount: BalanceOf<T>,
434		) -> DispatchResult {
435			let delegator = ensure_signed(origin)?;
436
437			// ensure delegator is sane.
438			ensure!(
439				Delegation::<T>::can_delegate(&delegator, &agent),
440				Error::<T>::InvalidDelegation
441			);
442
443			// ensure agent is sane.
444			ensure!(Self::is_agent(&agent), Error::<T>::NotAgent);
445
446			// add to delegation.
447			Self::do_delegate(Delegator::from(delegator), Agent::from(agent.clone()), amount)?;
448
449			// bond the newly delegated amount to `CoreStaking`.
450			Self::do_bond(Agent::from(agent), amount)
451		}
452	}
453
454	#[pezpallet::hooks]
455	impl<T: Config> Hooks<BlockNumberFor<T>> for Pezpallet<T> {
456		#[cfg(feature = "try-runtime")]
457		fn try_state(_n: BlockNumberFor<T>) -> Result<(), pezsp_runtime::TryRuntimeError> {
458			Self::do_try_state()
459		}
460	}
461}
462
463impl<T: Config> Pezpallet<T> {
464	/// Derive an account from the migrating agent account where the unclaimed delegation funds
465	/// are held.
466	pub fn generate_proxy_delegator(agent: Agent<T::AccountId>) -> Delegator<T::AccountId> {
467		Delegator::from(Self::sub_account(AccountType::ProxyDelegator, agent.get()))
468	}
469
470	/// Derive a (keyless) pot account from the given agent account and account type.
471	fn sub_account(account_type: AccountType, acc: T::AccountId) -> T::AccountId {
472		let entropy = (T::PalletId::get(), acc, account_type).using_encoded(blake2_256);
473		Decode::decode(&mut TrailingZeroInput::new(entropy.as_ref()))
474			.expect("infinite length input; no invalid inputs for type; qed")
475	}
476
477	/// Held balance of a delegator.
478	pub(crate) fn held_balance_of(who: Delegator<T::AccountId>) -> BalanceOf<T> {
479		T::Currency::balance_on_hold(&HoldReason::StakingDelegation.into(), &who.get())
480	}
481
482	/// Returns true if who is registered as an `Agent`.
483	fn is_agent(who: &T::AccountId) -> bool {
484		<Agents<T>>::contains_key(who)
485	}
486
487	/// Returns true if who is delegating to an `Agent` account.
488	fn is_delegator(who: &T::AccountId) -> bool {
489		<Delegators<T>>::contains_key(who)
490	}
491
492	/// Returns true if who is already staking on [`Config::CoreStaking`].
493	fn is_direct_staker(who: &T::AccountId) -> bool {
494		T::CoreStaking::status(who).is_ok()
495	}
496
497	/// Registers a new agent in the system.
498	fn do_register_agent(who: &T::AccountId, reward_account: &T::AccountId) {
499		// TODO: Consider taking a deposit for being an agent.
500		AgentLedger::<T>::new(reward_account).update(who);
501	}
502
503	/// Migrate existing staker account `who` to an `Agent` account.
504	fn do_migrate_to_agent(who: &T::AccountId, reward_account: &T::AccountId) -> DispatchResult {
505		Self::do_register_agent(who, reward_account);
506
507		// We create a proxy delegator that will keep all the delegation funds until funds are
508		// transferred to actual delegator.
509		let proxy_delegator = Self::generate_proxy_delegator(Agent::from(who.clone()));
510
511		// Get current stake
512		let stake = T::CoreStaking::stake(who)?;
513
514		// release funds from core staking.
515		T::CoreStaking::migrate_to_virtual_staker(who)?;
516
517		// transfer just released staked amount plus any free amount.
518		let amount_to_transfer =
519			T::Currency::reducible_balance(who, Preservation::Expendable, Fortitude::Polite);
520
521		// This should never fail but if it does, it indicates bad state and we abort.
522		T::Currency::transfer(
523			who,
524			&proxy_delegator.clone().get(),
525			amount_to_transfer,
526			Preservation::Expendable,
527		)?;
528
529		T::CoreStaking::set_payee(who, reward_account)?;
530		// delegate all transferred funds back to agent.
531		Self::do_delegate(proxy_delegator, Agent::from(who.clone()), amount_to_transfer)?;
532		// if the transferred/delegated amount was greater than the stake, mark the extra as
533		// unclaimed withdrawal.
534		let unclaimed_withdraws = amount_to_transfer
535			.checked_sub(&stake.total)
536			.defensive_ok_or(ArithmeticError::Underflow)?;
537
538		if !unclaimed_withdraws.is_zero() {
539			let mut ledger = AgentLedger::<T>::get(who).ok_or(Error::<T>::NotAgent)?;
540			ledger.unclaimed_withdrawals = ledger
541				.unclaimed_withdrawals
542				.checked_add(&unclaimed_withdraws)
543				.defensive_ok_or(ArithmeticError::Overflow)?;
544			ledger.update(who);
545		}
546
547		Ok(())
548	}
549
550	/// Bond `amount` to `agent_acc` in [`Config::CoreStaking`].
551	fn do_bond(agent_acc: Agent<T::AccountId>, amount: BalanceOf<T>) -> DispatchResult {
552		let agent_ledger = AgentLedgerOuter::<T>::get(&agent_acc.get())?;
553
554		let available_to_bond = agent_ledger.available_to_bond();
555		defensive_assert!(amount == available_to_bond, "not expected value to bond");
556
557		if agent_ledger.is_bonded() {
558			T::CoreStaking::bond_extra(&agent_ledger.key, amount)
559		} else {
560			T::CoreStaking::virtual_bond(&agent_ledger.key, amount, agent_ledger.reward_account())
561		}
562	}
563
564	/// Delegate `amount` from `delegator` to `agent`.
565	fn do_delegate(
566		delegator: Delegator<T::AccountId>,
567		agent: Agent<T::AccountId>,
568		amount: BalanceOf<T>,
569	) -> DispatchResult {
570		// get inner type
571		let agent = agent.get();
572		let delegator = delegator.get();
573
574		let mut ledger = AgentLedger::<T>::get(&agent).ok_or(Error::<T>::NotAgent)?;
575
576		if let Some(mut existing_delegation) = Delegation::<T>::get(&delegator) {
577			ensure!(existing_delegation.agent == agent, Error::<T>::InvalidDelegation);
578			// update amount and return the updated delegation.
579			existing_delegation.amount = existing_delegation
580				.amount
581				.checked_add(&amount)
582				.ok_or(ArithmeticError::Overflow)?;
583			existing_delegation
584		} else {
585			Delegation::<T>::new(&agent, amount)
586		}
587		.update(&delegator);
588
589		// try to hold the funds.
590		T::Currency::hold(&HoldReason::StakingDelegation.into(), &delegator, amount)?;
591
592		ledger.total_delegated =
593			ledger.total_delegated.checked_add(&amount).ok_or(ArithmeticError::Overflow)?;
594		ledger.update(&agent);
595
596		Self::deposit_event(Event::<T>::Delegated { agent, delegator, amount });
597
598		Ok(())
599	}
600
601	/// Release `amount` of delegated funds from `agent` to `delegator`.
602	fn do_release(
603		who: Agent<T::AccountId>,
604		delegator: Delegator<T::AccountId>,
605		amount: BalanceOf<T>,
606		num_slashing_spans: u32,
607	) -> DispatchResult {
608		// get inner type
609		let agent = who.get();
610		let delegator = delegator.get();
611
612		let mut agent_ledger = AgentLedgerOuter::<T>::get(&agent)?;
613		let mut delegation = Delegation::<T>::get(&delegator).ok_or(Error::<T>::NotDelegator)?;
614
615		// make sure delegation to be released is sound.
616		ensure!(delegation.agent == agent, Error::<T>::NotAgent);
617		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
618
619		// if we do not already have enough funds to be claimed, try to withdraw some more.
620		if agent_ledger.ledger.unclaimed_withdrawals < amount {
621			// withdraw account.
622			T::CoreStaking::withdraw_unbonded(agent.clone(), num_slashing_spans)
623				.map_err(|_| Error::<T>::WithdrawFailed)?;
624			// reload agent from storage since withdrawal might have changed the state.
625			agent_ledger = agent_ledger.reload()?;
626		}
627
628		// if we still do not have enough funds to release, abort.
629		ensure!(agent_ledger.ledger.unclaimed_withdrawals >= amount, Error::<T>::NotEnoughFunds);
630		agent_ledger.remove_unclaimed_withdraw(amount)?.update();
631
632		delegation.amount = delegation
633			.amount
634			.checked_sub(&amount)
635			.defensive_ok_or(ArithmeticError::Overflow)?;
636
637		let released = T::Currency::release(
638			&HoldReason::StakingDelegation.into(),
639			&delegator,
640			amount,
641			Precision::BestEffort,
642		)?;
643
644		defensive_assert!(released == amount, "hold should have been released fully");
645
646		// update delegation.
647		delegation.update(&delegator);
648
649		Self::deposit_event(Event::<T>::Released { agent, delegator, amount });
650
651		Ok(())
652	}
653
654	/// Migrates delegation of `amount` from `source` account to `destination` account.
655	fn do_migrate_delegation(
656		source_delegator: Delegator<T::AccountId>,
657		destination_delegator: Delegator<T::AccountId>,
658		amount: BalanceOf<T>,
659	) -> DispatchResult {
660		// get inner type
661		let source_delegator = source_delegator.get();
662		let destination_delegator = destination_delegator.get();
663
664		let mut source_delegation =
665			Delegators::<T>::get(&source_delegator).defensive_ok_or(Error::<T>::BadState)?;
666
667		// ensure source has enough funds to migrate.
668		ensure!(source_delegation.amount >= amount, Error::<T>::NotEnoughFunds);
669		debug_assert!(
670			!Self::is_delegator(&destination_delegator) && !Self::is_agent(&destination_delegator)
671		);
672
673		let agent = source_delegation.agent.clone();
674		// create a new delegation for destination delegator.
675		Delegation::<T>::new(&agent, amount).update(&destination_delegator);
676
677		source_delegation.amount = source_delegation
678			.amount
679			.checked_sub(&amount)
680			.defensive_ok_or(Error::<T>::BadState)?;
681
682		// transfer the held amount in `source_delegator` to `destination_delegator`.
683		T::Currency::transfer_on_hold(
684			&HoldReason::StakingDelegation.into(),
685			&source_delegator,
686			&destination_delegator,
687			amount,
688			Precision::Exact,
689			Restriction::OnHold,
690			Fortitude::Polite,
691		)?;
692
693		// update source delegation.
694		source_delegation.update(&source_delegator);
695
696		Self::deposit_event(Event::<T>::MigratedDelegation {
697			agent,
698			delegator: destination_delegator,
699			amount,
700		});
701
702		Ok(())
703	}
704
705	/// Take slash `amount` from agent's `pending_slash`counter and apply it to `delegator` account.
706	pub fn do_slash(
707		agent: Agent<T::AccountId>,
708		delegator: Delegator<T::AccountId>,
709		amount: BalanceOf<T>,
710		maybe_reporter: Option<T::AccountId>,
711	) -> DispatchResult {
712		// get inner type
713		let agent = agent.get();
714		let delegator = delegator.get();
715
716		let agent_ledger = AgentLedgerOuter::<T>::get(&agent)?;
717		// ensure there is something to slash
718		ensure!(agent_ledger.ledger.pending_slash > Zero::zero(), Error::<T>::NothingToSlash);
719
720		let mut delegation = <Delegators<T>>::get(&delegator).ok_or(Error::<T>::NotDelegator)?;
721		ensure!(delegation.agent == agent.clone(), Error::<T>::NotAgent);
722		ensure!(delegation.amount >= amount, Error::<T>::NotEnoughFunds);
723
724		// slash delegator
725		let (mut credit, missing) =
726			T::Currency::slash(&HoldReason::StakingDelegation.into(), &delegator, amount);
727
728		defensive_assert!(missing.is_zero(), "slash should have been fully applied");
729
730		let actual_slash = credit.peek();
731
732		// remove the applied slashed amount from agent.
733		agent_ledger.remove_slash(actual_slash).save();
734		delegation.amount =
735			delegation.amount.checked_sub(&actual_slash).ok_or(ArithmeticError::Overflow)?;
736		delegation.update(&delegator);
737
738		if let Some(reporter) = maybe_reporter {
739			let reward_payout: BalanceOf<T> = T::SlashRewardFraction::get() * actual_slash;
740			let (reporter_reward, rest) = credit.split(reward_payout);
741
742			// credit is the amount that we provide to `T::OnSlash`.
743			credit = rest;
744
745			// reward reporter or drop it.
746			let _ = T::Currency::resolve(&reporter, reporter_reward);
747		}
748
749		T::OnSlash::on_unbalanced(credit);
750
751		Self::deposit_event(Event::<T>::Slashed { agent, delegator, amount });
752
753		Ok(())
754	}
755
756	/// Total balance that is available for stake. Includes already staked amount.
757	#[cfg(test)]
758	pub(crate) fn stakeable_balance(who: Agent<T::AccountId>) -> BalanceOf<T> {
759		AgentLedgerOuter::<T>::get(&who.get())
760			.map(|agent| agent.ledger.stakeable_balance())
761			.unwrap_or_default()
762	}
763}
764
765#[cfg(any(test, feature = "try-runtime"))]
766use alloc::collections::btree_map::BTreeMap;
767
768#[cfg(any(test, feature = "try-runtime"))]
769impl<T: Config> Pezpallet<T> {
770	pub(crate) fn do_try_state() -> Result<(), pezsp_runtime::TryRuntimeError> {
771		// build map to avoid reading storage multiple times.
772		let delegation_map = Delegators::<T>::iter().collect::<BTreeMap<_, _>>();
773		let ledger_map = Agents::<T>::iter().collect::<BTreeMap<_, _>>();
774
775		Self::check_delegates(ledger_map.clone())?;
776		Self::check_delegators(delegation_map, ledger_map)?;
777
778		Ok(())
779	}
780
781	fn check_delegates(
782		ledgers: BTreeMap<T::AccountId, AgentLedger<T>>,
783	) -> Result<(), pezsp_runtime::TryRuntimeError> {
784		for (agent, ledger) in ledgers {
785			let staked_value = ledger.stakeable_balance();
786
787			if !staked_value.is_zero() {
788				ensure!(
789					matches!(
790						T::CoreStaking::status(&agent).expect("agent should be bonded"),
791						pezsp_staking::StakerStatus::Nominator(_)
792							| pezsp_staking::StakerStatus::Idle
793					),
794					"agent should be bonded and not validator"
795				);
796			}
797
798			ensure!(
799				ledger.stakeable_balance()
800					>= T::CoreStaking::total_stake(&agent).unwrap_or_default(),
801				"Cannot stake more than balance"
802			);
803		}
804
805		Ok(())
806	}
807
808	fn check_delegators(
809		delegations: BTreeMap<T::AccountId, Delegation<T>>,
810		ledger: BTreeMap<T::AccountId, AgentLedger<T>>,
811	) -> Result<(), pezsp_runtime::TryRuntimeError> {
812		let mut delegation_aggregation = BTreeMap::<T::AccountId, BalanceOf<T>>::new();
813		for (delegator, delegation) in delegations.iter() {
814			ensure!(!Self::is_agent(delegator), "delegator cannot be an agent");
815
816			delegation_aggregation
817				.entry(delegation.agent.clone())
818				.and_modify(|e| *e += delegation.amount)
819				.or_insert(delegation.amount);
820		}
821
822		for (agent, total_delegated) in delegation_aggregation {
823			ensure!(!Self::is_delegator(&agent), "agent cannot be delegator");
824
825			let ledger = ledger.get(&agent).expect("ledger should exist");
826			ensure!(
827				ledger.total_delegated == total_delegated,
828				"ledger total delegated should match delegations"
829			);
830		}
831
832		Ok(())
833	}
834}