referrerpolicy=no-referrer-when-downgrade
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
// 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. Also adds and removes provider
	/// reference as needed.
	pub(crate) fn update(self, key: &T::AccountId) {
		if <Delegators<T>>::contains_key(key) {
			// Clean up if no delegation left.
			if self.amount == Zero::zero() {
				<Delegators<T>>::remove(key);
				// Remove provider if no delegation left.
				let _ = frame_system::Pallet::<T>::dec_providers(key).defensive();
				return
			}
		} else {
			// this is a new delegation. Provide for this account.
			frame_system::Pallet::<T>::inc_providers(key);
		}

		<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.
	///
	/// Increments provider count if this is a new agent.
	pub(crate) fn update(self, key: &T::AccountId) {
		if !<Agents<T>>::contains_key(key) {
			// This is a new agent. Provide for this account.
			frame_system::Pallet::<T>::inc_providers(key);
		}
		<Agents<T>>::insert(key, self)
	}

	/// Remove self from storage.
	pub(crate) fn remove(key: &T::AccountId) {
		debug_assert!(<Agents<T>>::contains_key(key), "Agent should exist in storage");
		<Agents<T>>::remove(key);
		// Remove provider reference.
		let _ = frame_system::Pallet::<T>::dec_providers(key).defensive();
	}

	/// 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 AgentLedgerOuter<T: Config> {
	/// storage key
	pub key: T::AccountId,
	/// storage value
	pub ledger: AgentLedger<T>,
}

impl<T: Config> AgentLedgerOuter<T> {
	/// Get `Agent` from storage if it exists or return an error.
	pub(crate) fn get(agent: &T::AccountId) -> Result<AgentLedgerOuter<T>, DispatchError> {
		let ledger = AgentLedger::<T>::get(agent).ok_or(Error::<T>::NotAgent)?;
		Ok(AgentLedgerOuter { 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(AgentLedgerOuter {
			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(AgentLedgerOuter {
			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);

		AgentLedgerOuter {
			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)
	}

	/// Update agent ledger.
	pub(crate) fn update(self) {
		let key = self.key;
		self.ledger.update(&key);
	}

	/// Reloads self from storage.
	pub(crate) fn reload(self) -> Result<AgentLedgerOuter<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)]
	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)
	}
}