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