referrerpolicy=no-referrer-when-downgrade

pallet_delegated_staking/
types.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//! Basic types used in delegated staking.
19
20use super::*;
21use frame_support::traits::DefensiveSaturating;
22
23/// The type of pot account being created.
24#[derive(Encode, Decode)]
25pub(crate) enum AccountType {
26	/// A proxy delegator account created for a nominator who migrated to an `Agent` account.
27	///
28	/// Funds for unmigrated `delegator` accounts of the `Agent` are kept here.
29	ProxyDelegator,
30}
31
32/// Information about delegation of a `delegator`.
33#[derive(Default, Encode, Clone, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
34#[scale_info(skip_type_params(T))]
35pub struct Delegation<T: Config> {
36	/// The target of delegation.
37	pub agent: T::AccountId,
38	/// The amount delegated.
39	pub amount: BalanceOf<T>,
40}
41
42impl<T: Config> Delegation<T> {
43	/// Get delegation of a `delegator`.
44	pub(crate) fn get(delegator: &T::AccountId) -> Option<Self> {
45		<Delegators<T>>::get(delegator)
46	}
47
48	/// Create and return a new delegation instance.
49	pub(crate) fn new(agent: &T::AccountId, amount: BalanceOf<T>) -> Self {
50		Delegation { agent: agent.clone(), amount }
51	}
52
53	/// Ensure the delegator is either a new delegator or they are adding more delegation to the
54	/// existing agent.
55	///
56	/// Delegators are prevented from delegating to multiple agents at the same time.
57	pub(crate) fn can_delegate(delegator: &T::AccountId, agent: &T::AccountId) -> bool {
58		Delegation::<T>::get(delegator)
59			.map(|delegation| delegation.agent == *agent)
60			.unwrap_or(
61				// all good if it is a new delegator except it should not be an existing agent.
62				!<Agents<T>>::contains_key(delegator),
63			)
64	}
65
66	/// Save self to storage.
67	///
68	/// If the delegation amount is zero, remove the delegation. Also adds and removes provider
69	/// reference as needed.
70	pub(crate) fn update(self, key: &T::AccountId) {
71		if <Delegators<T>>::contains_key(key) {
72			// Clean up if no delegation left.
73			if self.amount == Zero::zero() {
74				<Delegators<T>>::remove(key);
75				// Remove provider if no delegation left.
76				let _ = frame_system::Pallet::<T>::dec_providers(key).defensive();
77				return
78			}
79		} else {
80			// this is a new delegation. Provide for this account.
81			frame_system::Pallet::<T>::inc_providers(key);
82		}
83
84		<Delegators<T>>::insert(key, self);
85	}
86}
87
88/// Ledger of all delegations to an `Agent`.
89///
90/// This keeps track of the active balance of the `Agent` that is made up from the funds that
91/// are currently delegated to this `Agent`. It also tracks the pending slashes yet to be
92/// applied among other things.
93#[derive(Default, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
94#[scale_info(skip_type_params(T))]
95pub struct AgentLedger<T: Config> {
96	/// Where the reward should be paid out.
97	pub payee: T::AccountId,
98	/// Sum of all delegated funds to this `Agent`.
99	#[codec(compact)]
100	pub total_delegated: BalanceOf<T>,
101	/// Funds that are withdrawn from core staking but not released to delegator/s. It is a subset
102	/// of `total_delegated` and can never be greater than it.
103	///
104	/// We need this register to ensure that the `Agent` does not bond funds from delegated
105	/// funds that are withdrawn and should be claimed by delegators.
106	#[codec(compact)]
107	pub unclaimed_withdrawals: BalanceOf<T>,
108	/// Slashes that are not yet applied. This affects the effective balance of the `Agent`.
109	#[codec(compact)]
110	pub pending_slash: BalanceOf<T>,
111}
112
113impl<T: Config> AgentLedger<T> {
114	/// Create a new instance of `AgentLedger`.
115	pub(crate) fn new(reward_destination: &T::AccountId) -> Self {
116		AgentLedger {
117			payee: reward_destination.clone(),
118			total_delegated: Zero::zero(),
119			unclaimed_withdrawals: Zero::zero(),
120			pending_slash: Zero::zero(),
121		}
122	}
123
124	/// Get `AgentLedger` from storage.
125	pub(crate) fn get(key: &T::AccountId) -> Option<Self> {
126		<Agents<T>>::get(key)
127	}
128
129	/// Save self to storage with the given key.
130	///
131	/// Increments provider count if this is a new agent.
132	pub(crate) fn update(self, key: &T::AccountId) {
133		<Agents<T>>::insert(key, self)
134	}
135
136	/// Remove self from storage.
137	pub(crate) fn remove(key: &T::AccountId) {
138		debug_assert!(<Agents<T>>::contains_key(key), "Agent should exist in storage");
139		<Agents<T>>::remove(key);
140	}
141
142	/// Effective total balance of the `Agent`.
143	///
144	/// This takes into account any slashes reported to `Agent` but unapplied.
145	pub(crate) fn effective_balance(&self) -> BalanceOf<T> {
146		defensive_assert!(
147			self.total_delegated >= self.pending_slash,
148			"slash cannot be higher than actual balance of delegator"
149		);
150
151		// pending slash needs to be burned and cannot be used for stake.
152		self.total_delegated.saturating_sub(self.pending_slash)
153	}
154
155	/// Agent balance that can be staked/bonded in [`T::CoreStaking`].
156	pub(crate) fn stakeable_balance(&self) -> BalanceOf<T> {
157		self.effective_balance().saturating_sub(self.unclaimed_withdrawals)
158	}
159}
160
161/// Wrapper around `AgentLedger` to provide some helper functions to mutate the ledger.
162#[derive(Clone)]
163pub struct AgentLedgerOuter<T: Config> {
164	/// storage key
165	pub key: T::AccountId,
166	/// storage value
167	pub ledger: AgentLedger<T>,
168}
169
170impl<T: Config> AgentLedgerOuter<T> {
171	/// Get `Agent` from storage if it exists or return an error.
172	pub(crate) fn get(agent: &T::AccountId) -> Result<AgentLedgerOuter<T>, DispatchError> {
173		let ledger = AgentLedger::<T>::get(agent).ok_or(Error::<T>::NotAgent)?;
174		Ok(AgentLedgerOuter { key: agent.clone(), ledger })
175	}
176
177	/// Remove funds that are withdrawn from [Config::CoreStaking] but not claimed by a delegator.
178	///
179	/// Checked decrease of delegation amount from `total_delegated` and `unclaimed_withdrawals`
180	/// registers. Consumes self and returns a new instance of self if success.
181	pub(crate) fn remove_unclaimed_withdraw(
182		self,
183		amount: BalanceOf<T>,
184	) -> Result<Self, DispatchError> {
185		let new_total_delegated = self
186			.ledger
187			.total_delegated
188			.checked_sub(&amount)
189			.defensive_ok_or(ArithmeticError::Overflow)?;
190		let new_unclaimed_withdrawals = self
191			.ledger
192			.unclaimed_withdrawals
193			.checked_sub(&amount)
194			.defensive_ok_or(ArithmeticError::Overflow)?;
195
196		Ok(AgentLedgerOuter {
197			ledger: AgentLedger {
198				total_delegated: new_total_delegated,
199				unclaimed_withdrawals: new_unclaimed_withdrawals,
200				..self.ledger
201			},
202			..self
203		})
204	}
205
206	/// Add funds that are withdrawn from [Config::CoreStaking] to be claimed by delegators later.
207	pub(crate) fn add_unclaimed_withdraw(
208		self,
209		amount: BalanceOf<T>,
210	) -> Result<Self, DispatchError> {
211		let new_unclaimed_withdrawals = self
212			.ledger
213			.unclaimed_withdrawals
214			.checked_add(&amount)
215			.defensive_ok_or(ArithmeticError::Overflow)?;
216
217		Ok(AgentLedgerOuter {
218			ledger: AgentLedger { unclaimed_withdrawals: new_unclaimed_withdrawals, ..self.ledger },
219			..self
220		})
221	}
222
223	/// Amount that is delegated but not bonded yet.
224	///
225	/// This importantly does not include `unclaimed_withdrawals` as those should not be bonded
226	/// again unless explicitly requested.
227	pub(crate) fn available_to_bond(&self) -> BalanceOf<T> {
228		let bonded_stake = self.bonded_stake();
229		let stakeable = self.ledger.stakeable_balance();
230
231		defensive_assert!(
232			stakeable >= bonded_stake,
233			"cannot be bonded with more than total amount delegated to agent"
234		);
235
236		stakeable.saturating_sub(bonded_stake)
237	}
238
239	/// Remove slashes from the `AgentLedger`.
240	pub(crate) fn remove_slash(self, amount: BalanceOf<T>) -> Self {
241		let pending_slash = self.ledger.pending_slash.defensive_saturating_sub(amount);
242		let total_delegated = self.ledger.total_delegated.defensive_saturating_sub(amount);
243
244		AgentLedgerOuter {
245			ledger: AgentLedger { pending_slash, total_delegated, ..self.ledger },
246			..self
247		}
248	}
249
250	/// Get the total stake of agent bonded in [`Config::CoreStaking`].
251	pub(crate) fn bonded_stake(&self) -> BalanceOf<T> {
252		T::CoreStaking::total_stake(&self.key).unwrap_or(Zero::zero())
253	}
254
255	/// Returns true if the agent is bonded in [`Config::CoreStaking`].
256	pub(crate) fn is_bonded(&self) -> bool {
257		T::CoreStaking::stake(&self.key).is_ok()
258	}
259
260	/// Returns the reward account registered by the agent.
261	pub(crate) fn reward_account(&self) -> &T::AccountId {
262		&self.ledger.payee
263	}
264
265	/// Save self to storage.
266	pub(crate) fn save(self) {
267		let key = self.key;
268		self.ledger.update(&key)
269	}
270
271	/// Update agent ledger.
272	pub(crate) fn update(self) {
273		let key = self.key;
274		self.ledger.update(&key);
275	}
276
277	/// Reloads self from storage.
278	pub(crate) fn reload(self) -> Result<AgentLedgerOuter<T>, DispatchError> {
279		Self::get(&self.key)
280	}
281
282	/// Balance of `Agent` that is not bonded.
283	///
284	/// This is similar to [Self::available_to_bond] except it also includes `unclaimed_withdrawals`
285	/// of `Agent`.
286	#[cfg(test)]
287	pub(crate) fn total_unbonded(&self) -> BalanceOf<T> {
288		let bonded_stake = self.bonded_stake();
289
290		let net_balance = self.ledger.effective_balance();
291
292		assert!(net_balance >= bonded_stake, "cannot be bonded with more than the agent balance");
293
294		net_balance.saturating_sub(bonded_stake)
295	}
296}