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