From 0c41dd36310670b19cff1bcb39c6a7c84f2df1ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 12 Jul 2023 23:15:06 +0200 Subject: [PATCH 01/47] Refactors StakingLedger to own module --- frame/staking/src/ledger.rs | 255 +++++++++++++++++++++++++++++++ frame/staking/src/lib.rs | 256 +------------------------------- frame/staking/src/mock.rs | 5 +- frame/staking/src/pallet/mod.rs | 2 +- frame/staking/src/tests.rs | 2 +- 5 files changed, 268 insertions(+), 252 deletions(-) create mode 100644 frame/staking/src/ledger.rs diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs new file mode 100644 index 000000000000..76e13f7f16e6 --- /dev/null +++ b/frame/staking/src/ledger.rs @@ -0,0 +1,255 @@ +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + traits::{Defensive, Get}, + BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, +}; +use scale_info::TypeInfo; +use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; +use sp_staking::{EraIndex, OnStakingUpdate}; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; + +use crate::{log, BalanceOf, Config, UnlockChunk}; + +/// The ledger of a (bonded) stash. +#[derive( + PartialEqNoBound, + EqNoBound, + CloneNoBound, + Encode, + Decode, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { + /// The stash account whose balance is actually locked and at stake. + pub stash: T::AccountId, + /// The total amount of the stash's balance that we are currently accounting for. + /// It's just `active` plus all the `unlocking` balances. + #[codec(compact)] + pub total: BalanceOf, + /// The total amount of the stash's balance that will be at stake in any forthcoming + /// rounds. + #[codec(compact)] + pub active: BalanceOf, + /// Any balance that is becoming free, which may eventually be transferred out of the stash + /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first + /// in, first out queue where the new (higher value) eras get pushed on the back. + pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, + /// List of eras for which the stakers behind a validator have claimed rewards. Only updated + /// for validators. + pub claimed_rewards: BoundedVec, +} + +impl StakingLedger { + /// Initializes the default object using the given `validator`. + pub fn default_from(stash: T::AccountId) -> Self { + Self { + stash, + total: Zero::zero(), + active: Zero::zero(), + unlocking: Default::default(), + claimed_rewards: Default::default(), + } + } + + /// Remove entries from `unlocking` that are sufficiently old and reduce the + /// total by the sum of their balances. + pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + let mut total = self.total; + let unlocking: BoundedVec<_, _> = self + .unlocking + .into_iter() + .filter(|chunk| { + if chunk.era > current_era { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }) + .collect::>() + .try_into() + .expect( + "filtering items from a bounded vec always leaves length less than bounds. qed", + ); + + Self { + stash: self.stash, + total, + active: self.active, + unlocking, + claimed_rewards: self.claimed_rewards, + } + } + + /// Re-bond funds that were scheduled for unlocking. + /// + /// Returns the updated ledger, and the amount actually rebonded. + pub(crate) fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); + + while let Some(last) = self.unlocking.last_mut() { + if unlocking_balance + last.value <= value { + unlocking_balance += last.value; + self.active += last.value; + self.unlocking.pop(); + } else { + let diff = value - unlocking_balance; + + unlocking_balance += diff; + self.active += diff; + last.value -= diff; + } + + if unlocking_balance >= value { + break + } + } + + (self, unlocking_balance) + } + + /// Slash the staker for a given amount of balance. + /// + /// This implements a proportional slashing system, whereby we set our preference to slash as + /// such: + /// + /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + + /// bonding_duration` and onwards, the slash is divided equally between the active ledger and + /// the unlocking chunks. + /// - If no such chunks exist, then only the active balance is slashed. + /// + /// Note that the above is only a *preference*. If for any reason the active ledger, with or + /// without some portion of the unlocking chunks that are more justified to be slashed are not + /// enough, then the slashing will continue and will consume as much of the active and unlocking + /// chunks as needed. + /// + /// This will never slash more than the given amount. If any of the chunks become dusted, the + /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually + /// slashed. + /// + /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. + /// + /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was + /// applied. + pub(crate) fn slash( + &mut self, + slash_amount: BalanceOf, + minimum_balance: BalanceOf, + slash_era: EraIndex, + ) -> BalanceOf { + if slash_amount.is_zero() { + return Zero::zero() + } + + use sp_runtime::PerThing as _; + let mut remaining_slash = slash_amount; + let pre_slash_total = self.total; + + // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` + // (assuming 28 is the bonding duration) onwards should be slashed. + let slashable_chunks_start = slash_era + T::BondingDuration::get(); + + // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we + // slash first the active chunk, and then `slash_chunks_priority`. + let (maybe_proportional, slash_chunks_priority) = { + if let Some(first_slashable_index) = + self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) + { + // If there exists a chunk who's after the first_slashable_start, then this is a + // proportional slash, because we want to slash active and these chunks + // proportionally. + + // The indices of the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = first_slashable_index..self.unlocking.len(); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get(i).defensive() { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + let affected_balance = self.active.saturating_add(unbonding_affected_balance); + let ratio = Perquintill::from_rational_with_rounding( + slash_amount, + affected_balance, + Rounding::Up, + ) + .unwrap_or_else(|_| Perquintill::one()); + ( + Some(ratio), + affected_indices.chain((0..first_slashable_index).rev()).collect::>(), + ) + } else { + // We just slash from the last chunk to the most recent one, if need be. + (None, (0..self.unlocking.len()).rev().collect::>()) + } + }; + + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + log!( + debug, + "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", + slash_amount, + slash_era, + self, + slash_chunks_priority, + maybe_proportional, + ); + + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + let mut slash_from_target = if let Some(ratio) = maybe_proportional { + ratio.mul_ceil(*target) + } else { + *slash_remaining + } + // this is the total that that the slash target has. We can't slash more than + // this anyhow! + .min(*target) + // this is the total amount that we would have wanted to slash + // non-proportionally, a proportional slash should never exceed this either! + .min(*slash_remaining); + + // slash out from *target exactly `slash_from_target`. + *target = *target - slash_from_target; + if *target < minimum_balance { + // Slash the rest of the target if it's dust. This might cause the last chunk to be + // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big + // deal. + slash_from_target = + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } + + self.total = self.total.saturating_sub(slash_from_target); + *slash_remaining = slash_remaining.saturating_sub(slash_from_target); + }; + + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + for i in slash_chunks_priority { + if remaining_slash.is_zero() { + break + } + + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); + } else { + break + } + } + + // clean unlocking chunks that are set to zero. + self.unlocking.retain(|c| !c.value.is_zero()); + + T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); + pre_slash_total.saturating_sub(self.total) + } +} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 99622a64f8dc..9666188d287d 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -293,6 +293,7 @@ pub(crate) mod mock; mod tests; pub mod inflation; +pub mod ledger; pub mod migrations; pub mod slashing; pub mod weights; @@ -301,20 +302,21 @@ mod pallet; use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{Currency, Defensive, Get}, + traits::{Currency, Get}, weights::Weight, - BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; +use ledger::StakingLedger; use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, - traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero}, - Perbill, Perquintill, Rounding, RuntimeDebug, + traits::{AtLeast32BitUnsigned, Convert, StaticLookup, Zero}, + Perbill, RuntimeDebug, }; pub use sp_staking::StakerStatus; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - EraIndex, OnStakingUpdate, SessionIndex, + EraIndex, SessionIndex, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; @@ -427,250 +429,6 @@ pub struct UnlockChunk { era: EraIndex, } -/// The ledger of a (bonded) stash. -#[derive( - PartialEqNoBound, - EqNoBound, - CloneNoBound, - Encode, - Decode, - RuntimeDebugNoBound, - TypeInfo, - MaxEncodedLen, -)] -#[scale_info(skip_type_params(T))] -pub struct StakingLedger { - /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, - /// The total amount of the stash's balance that we are currently accounting for. - /// It's just `active` plus all the `unlocking` balances. - #[codec(compact)] - pub total: BalanceOf, - /// The total amount of the stash's balance that will be at stake in any forthcoming - /// rounds. - #[codec(compact)] - pub active: BalanceOf, - /// Any balance that is becoming free, which may eventually be transferred out of the stash - /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first - /// in, first out queue where the new (higher value) eras get pushed on the back. - pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, - /// List of eras for which the stakers behind a validator have claimed rewards. Only updated - /// for validators. - pub claimed_rewards: BoundedVec, -} - -impl StakingLedger { - /// Initializes the default object using the given `validator`. - pub fn default_from(stash: T::AccountId) -> Self { - Self { - stash, - total: Zero::zero(), - active: Zero::zero(), - unlocking: Default::default(), - claimed_rewards: Default::default(), - } - } - - /// Remove entries from `unlocking` that are sufficiently old and reduce the - /// total by the sum of their balances. - fn consolidate_unlocked(self, current_era: EraIndex) -> Self { - let mut total = self.total; - let unlocking: BoundedVec<_, _> = self - .unlocking - .into_iter() - .filter(|chunk| { - if chunk.era > current_era { - true - } else { - total = total.saturating_sub(chunk.value); - false - } - }) - .collect::>() - .try_into() - .expect( - "filtering items from a bounded vec always leaves length less than bounds. qed", - ); - - Self { - stash: self.stash, - total, - active: self.active, - unlocking, - claimed_rewards: self.claimed_rewards, - } - } - - /// Re-bond funds that were scheduled for unlocking. - /// - /// Returns the updated ledger, and the amount actually rebonded. - fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { - let mut unlocking_balance = BalanceOf::::zero(); - - while let Some(last) = self.unlocking.last_mut() { - if unlocking_balance + last.value <= value { - unlocking_balance += last.value; - self.active += last.value; - self.unlocking.pop(); - } else { - let diff = value - unlocking_balance; - - unlocking_balance += diff; - self.active += diff; - last.value -= diff; - } - - if unlocking_balance >= value { - break - } - } - - (self, unlocking_balance) - } - - /// Slash the staker for a given amount of balance. - /// - /// This implements a proportional slashing system, whereby we set our preference to slash as - /// such: - /// - /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + - /// bonding_duration` and onwards, the slash is divided equally between the active ledger and - /// the unlocking chunks. - /// - If no such chunks exist, then only the active balance is slashed. - /// - /// Note that the above is only a *preference*. If for any reason the active ledger, with or - /// without some portion of the unlocking chunks that are more justified to be slashed are not - /// enough, then the slashing will continue and will consume as much of the active and unlocking - /// chunks as needed. - /// - /// This will never slash more than the given amount. If any of the chunks become dusted, the - /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually - /// slashed. - /// - /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. - /// - /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was - /// applied. - pub fn slash( - &mut self, - slash_amount: BalanceOf, - minimum_balance: BalanceOf, - slash_era: EraIndex, - ) -> BalanceOf { - if slash_amount.is_zero() { - return Zero::zero() - } - - use sp_runtime::PerThing as _; - let mut remaining_slash = slash_amount; - let pre_slash_total = self.total; - - // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` - // (assuming 28 is the bonding duration) onwards should be slashed. - let slashable_chunks_start = slash_era + T::BondingDuration::get(); - - // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we - // slash first the active chunk, and then `slash_chunks_priority`. - let (maybe_proportional, slash_chunks_priority) = { - if let Some(first_slashable_index) = - self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) - { - // If there exists a chunk who's after the first_slashable_start, then this is a - // proportional slash, because we want to slash active and these chunks - // proportionally. - - // The indices of the first chunk after the slash up through the most recent chunk. - // (The most recent chunk is at greatest from this era) - let affected_indices = first_slashable_index..self.unlocking.len(); - let unbonding_affected_balance = - affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { - if let Some(chunk) = self.unlocking.get(i).defensive() { - sum.saturating_add(chunk.value) - } else { - sum - } - }); - let affected_balance = self.active.saturating_add(unbonding_affected_balance); - let ratio = Perquintill::from_rational_with_rounding( - slash_amount, - affected_balance, - Rounding::Up, - ) - .unwrap_or_else(|_| Perquintill::one()); - ( - Some(ratio), - affected_indices.chain((0..first_slashable_index).rev()).collect::>(), - ) - } else { - // We just slash from the last chunk to the most recent one, if need be. - (None, (0..self.unlocking.len()).rev().collect::>()) - } - }; - - // Helper to update `target` and the ledgers total after accounting for slashing `target`. - log!( - debug, - "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", - slash_amount, - slash_era, - self, - slash_chunks_priority, - maybe_proportional, - ); - - let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let mut slash_from_target = if let Some(ratio) = maybe_proportional { - ratio.mul_ceil(*target) - } else { - *slash_remaining - } - // this is the total that that the slash target has. We can't slash more than - // this anyhow! - .min(*target) - // this is the total amount that we would have wanted to slash - // non-proportionally, a proportional slash should never exceed this either! - .min(*slash_remaining); - - // slash out from *target exactly `slash_from_target`. - *target = *target - slash_from_target; - if *target < minimum_balance { - // Slash the rest of the target if it's dust. This might cause the last chunk to be - // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big - // deal. - slash_from_target = - sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) - } - - self.total = self.total.saturating_sub(slash_from_target); - *slash_remaining = slash_remaining.saturating_sub(slash_from_target); - }; - - // If this is *not* a proportional slash, the active will always wiped to 0. - slash_out_of(&mut self.active, &mut remaining_slash); - - let mut slashed_unlocking = BTreeMap::<_, _>::new(); - for i in slash_chunks_priority { - if remaining_slash.is_zero() { - break - } - - if let Some(chunk) = self.unlocking.get_mut(i).defensive() { - slash_out_of(&mut chunk.value, &mut remaining_slash); - // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.era, chunk.value); - } else { - break - } - } - - // clean unlocking chunks that are set to zero. - self.unlocking.retain(|c| !c.value.is_zero()); - - T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_slash_total.saturating_sub(self.total) - } -} - /// A record of the nominations made by a specific account. #[derive( PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 2a5edd6e00e5..fddb04125c93 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -36,7 +36,10 @@ use sp_runtime::{ traits::{IdentityLookup, Zero}, BuildStorage, }; -use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; +use sp_staking::{ + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + OnStakingUpdate, +}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 41b8d4b415e1..5618f36d976c 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -50,7 +50,7 @@ use crate::{ ValidatorPrefs, }; -const STAKING_ID: LockIdentifier = *b"staking "; +pub(crate) const STAKING_ID: LockIdentifier = *b"staking "; // The speculative number of spans are used as an input of the weight annotation of // [`Call::unbond`], as the post dipatch weight may depend on the number of slashing span on the // account which is not provided as an input. The value set should be conservative but sensible. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index e3ee4cd1a8e9..e6cbaafb9a26 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -30,7 +30,7 @@ use pallet_balances::Error as BalancesError; use sp_runtime::{ assert_eq_error_rate, traits::{BadOrigin, Dispatchable}, - Perbill, Percent, Rounding, TokenError, + Perbill, Percent, Perquintill, Rounding, TokenError, }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, From cc12800cd50b6e724287f7e6e8bf360d49e0e374 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 13 Jul 2023 23:20:56 +0200 Subject: [PATCH 02/47] Refactors set_lock and remove_lock calls into StakingLedger methods; Removes Staking::ledger getter; All mutating and reading calls for the staking ledger go through StakingLedger --- frame/staking/src/ledger.rs | 84 +++++++- frame/staking/src/lib.rs | 5 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 57 +++--- frame/staking/src/pallet/mod.rs | 70 +++---- frame/staking/src/slashing.rs | 8 +- frame/staking/src/tests.rs | 305 ++++++++++++++---------------- 7 files changed, 300 insertions(+), 231 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 76e13f7f16e6..e2453388c22d 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -1,6 +1,6 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ - traits::{Defensive, Get}, + traits::{Defensive, Get, LockableCurrency, WithdrawReasons}, BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; use scale_info::TypeInfo; @@ -8,7 +8,7 @@ use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; use sp_staking::{EraIndex, OnStakingUpdate}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -use crate::{log, BalanceOf, Config, UnlockChunk}; +use crate::{log, BalanceOf, Config, Ledger, UnlockChunk, STAKING_ID}; /// The ledger of a (bonded) stash. #[derive( @@ -40,6 +40,12 @@ pub struct StakingLedger { /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. pub claimed_rewards: BoundedVec, + /// The controller associated with this ledger's stash. + /// + /// This is not stored on-chain, and is only bundled when the ledger is read from storage. + /// Use [`controller`] function to get the controller associated with the ledger. + #[codec(skip)] + controller: Option, } impl StakingLedger { @@ -51,12 +57,57 @@ impl StakingLedger { active: Zero::zero(), unlocking: Default::default(), claimed_rewards: Default::default(), + controller: Default::default(), } } + // only used for new ledgers, so controller == stash. + pub fn new( + stash: T::AccountId, + total_stake: BalanceOf, + active_stake: BalanceOf, + unlocking: BoundedVec>, T::MaxUnlockingChunks>, + claimed_rewards: BoundedVec, + ) -> Self { + Self { + stash: stash.clone(), + total: total_stake, + active: active_stake, + unlocking, + claimed_rewards, + // controllers are deprecated and map 1-1 to stashes. + controller: Some(stash), + } + } + + pub(crate) fn controller(&self) -> T::AccountId { + self.controller.clone().expect("TODO, handle this edge case better?") + } + + pub(crate) fn storage_get(controller: &T::AccountId) -> Option { + >::get(&controller).map(|mut l| { + l.controller = Some(controller.clone()); + l + }) + } + + pub(crate) fn storage_put(&self) { + T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); + Ledger::::insert(&self.controller(), &self); + } + + pub(crate) fn storage_remove(self) { + T::Currency::remove_lock(STAKING_ID, &self.stash); + Ledger::::remove(self.controller()); + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. - pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + pub(crate) fn consolidate_unlocked( + self, + current_era: EraIndex, + controller: &T::AccountId, + ) -> Self { let mut total = self.total; let unlocking: BoundedVec<_, _> = self .unlocking @@ -81,6 +132,7 @@ impl StakingLedger { active: self.active, unlocking, claimed_rewards: self.claimed_rewards, + controller: Some(controller.clone()), } } @@ -253,3 +305,29 @@ impl StakingLedger { pre_slash_total.saturating_sub(self.total) } } + +#[cfg(test)] +#[derive(frame_support::DebugNoBound, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] +pub struct StakingLedgerInspect { + pub stash: T::AccountId, + #[codec(compact)] + pub total: BalanceOf, + #[codec(compact)] + pub active: BalanceOf, + pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, + pub claimed_rewards: BoundedVec, +} + +#[cfg(test)] +impl PartialEq> for StakingLedger { + fn eq(&self, other: &StakingLedgerInspect) -> bool { + self.stash == other.stash && + self.total == other.total && + self.active == other.active && + self.unlocking == other.unlocking && + self.claimed_rewards == other.claimed_rewards + } +} + +#[cfg(test)] +impl codec::EncodeLike> for StakingLedgerInspect {} diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 9666188d287d..10149ed97111 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -302,7 +302,7 @@ mod pallet; use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{Currency, Get}, + traits::{Currency, Get, LockIdentifier}, weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -323,6 +323,7 @@ pub use weights::WeightInfo; pub use pallet::{pallet::*, UseNominatorsAndValidatorsMap, UseValidatorsMap}; +pub(crate) const STAKING_ID: LockIdentifier = *b"staking "; pub(crate) const LOG_TARGET: &str = "runtime::staking"; // syntactic sugar for logging. @@ -644,7 +645,7 @@ pub struct StashOf(sp_std::marker::PhantomData); impl Convert> for StashOf { fn convert(controller: T::AccountId) -> Option { - >::ledger(&controller).map(|l| l.stash) + StakingLedger::::storage_get(&controller).map(|l| l.stash) } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index fddb04125c93..c36c2eacb139 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -77,7 +77,7 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { } pub fn is_disabled(controller: AccountId) -> bool { - let stash = Staking::ledger(&controller).unwrap().stash; + let stash = StakingLedger::::storage_get(&controller).unwrap().stash; let validator_index = match Session::validators().iter().position(|v| *v == stash) { Some(index) => index as u32, None => return false, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 5166608a97aa..dc7fe6c1fcb7 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -26,8 +26,8 @@ use frame_support::{ dispatch::WithPostDispatchInfo, pallet_prelude::*, traits::{ - Currency, Defensive, DefensiveResult, EstimateNextNewSession, Get, Imbalance, - LockableCurrency, OnUnbalanced, TryCollect, UnixTime, WithdrawReasons, + Currency, Defensive, DefensiveResult, EstimateNextNewSession, Get, Imbalance, OnUnbalanced, + TryCollect, UnixTime, }, weights::Weight, }; @@ -50,7 +50,7 @@ use crate::{ SessionInterface, StakingLedger, ValidatorPrefs, }; -use super::{pallet::*, STAKING_ID}; +use super::pallet::*; #[cfg(feature = "try-runtime")] use frame_support::ensure; @@ -69,7 +69,10 @@ impl Pallet { /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. - Self::bonded(stash).and_then(Self::ledger).map(|l| l.active).unwrap_or_default() + Self::bonded(stash) + .and_then(|s| StakingLedger::::storage_get(&s)) + .map(|l| l.active) + .unwrap_or_default() } /// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`]. @@ -103,10 +106,11 @@ impl Pallet { controller: &T::AccountId, num_slashing_spans: u32, ) -> Result { - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { - ledger = ledger.consolidate_unlocked(current_era) + ledger = ledger.consolidate_unlocked(current_era, controller) } let used_weight = @@ -115,13 +119,11 @@ impl Pallet { // portion to fall below existential deposit + will have no more unlocking chunks // left. We can now safely remove all staking-related information. Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) } else { // This was the consequence of a partial unbond. just update the ledger and move on. - Self::update_ledger(&controller, &ledger); + ledger.storage_put(); // This is only an update, so we use less overall weight. T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) @@ -164,7 +166,8 @@ impl Pallet { let controller = Self::bonded(&validator_stash).ok_or_else(|| { Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let mut ledger = >::get(&controller).ok_or(Error::::NotController)?; + let mut ledger = + >::storage_get(&controller).ok_or(Error::::NotController)?; ledger .claimed_rewards @@ -272,14 +275,6 @@ impl Pallet { Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) } - /// Update the ledger for a controller. - /// - /// This will also update the stash lock. - pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { - T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); - >::insert(controller, ledger); - } - /// Chill a stash account. pub(crate) fn chill_stash(stash: &T::AccountId) { let chilled_as_validator = Self::do_remove_validator(stash); @@ -298,12 +293,13 @@ impl Pallet { .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), RewardDestination::Staked => Self::bonded(stash) - .and_then(|c| Self::ledger(&c).map(|l| (c, l))) - .and_then(|(controller, mut l)| { - l.active += amount; - l.total += amount; + .and_then(|c| StakingLedger::::storage_get(&c).map(|l| (c, l))) + .and_then(|(_, mut ledger)| { + ledger.active += amount; + ledger.total += amount; let r = T::Currency::deposit_into_existing(stash, amount).ok(); - Self::update_ledger(&controller, &l); + ledger.storage_put(); + r }), RewardDestination::Account(dest_account) => @@ -662,9 +658,12 @@ impl Pallet { slashing::clear_stash_metadata::(stash, num_slashing_spans)?; >::remove(stash); - >::remove(&controller); - >::remove(stash); + StakingLedger::::storage_get(&controller).map_or_else( + || log!(debug, "ledger not found in storage at `kill_stash`, unexpected."), + |ledger| ledger.storage_remove(), + ); + Self::do_remove_validator(stash); Self::do_remove_nominator(stash); @@ -1403,7 +1402,7 @@ impl ScoreProvider for Pallet { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); - let mut ledger = match Self::ledger(who) { + let mut ledger = match StakingLedger::::storage_get(who) { None => StakingLedger::default_from(who.clone()), Some(l) => l, }; @@ -1596,7 +1595,7 @@ impl StakingInterface for Pallet { } fn stash_by_ctrl(controller: &Self::AccountId) -> Result { - Self::ledger(controller) + StakingLedger::::storage_get(controller) .map(|l| l.stash) .ok_or(Error::::NotController.into()) } @@ -1617,7 +1616,7 @@ impl StakingInterface for Pallet { fn stake(who: &Self::AccountId) -> Result>, DispatchError> { Self::bonded(who) - .and_then(|c| Self::ledger(c)) + .and_then(|c| StakingLedger::::storage_get(&c)) .map(|l| Stake { total: l.total, active: l.active }) .ok_or(Error::::NotStash.into()) } @@ -1826,7 +1825,7 @@ impl Pallet { fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = Self::ledger(ctrl.clone()).ok_or("Not a controller.")?; + let ledger = StakingLedger::::storage_get(&ctrl).ok_or("Not a controller.")?; let real_total: BalanceOf = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); ensure!(real_total == ledger.total, "ledger.total corrupt"); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 5618f36d976c..834f79b2299e 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -25,8 +25,7 @@ use frame_support::{ pallet_prelude::*, traits::{ Currency, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, - EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, - UnixTime, + EstimateNextNewSession, Get, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, weights::Weight, BoundedVec, @@ -50,7 +49,6 @@ use crate::{ ValidatorPrefs, }; -pub(crate) const STAKING_ID: LockIdentifier = *b"staking "; // The speculative number of spans are used as an input of the weight annotation of // [`Call::unbond`], as the post dipatch weight may depend on the number of slashing span on the // account which is not provided as an input. The value set should be conservative but sensible. @@ -319,7 +317,6 @@ pub mod pallet { /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] - #[pallet::getter(fn ledger)] pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; /// Where the reward payment should be made. Keyed by stash. @@ -866,19 +863,19 @@ pub mod pallet { let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); - let item = StakingLedger { - stash: stash.clone(), - total: value, - active: value, - unlocking: Default::default(), - claimed_rewards: (last_reward_era..current_era) + let ledger: StakingLedger = StakingLedger::::new( + stash.clone(), + value, + value, + Default::default(), + (last_reward_era..current_era) .try_collect() // Since last_reward_era is calculated as `current_era - // HistoryDepth`, following bound is always expected to be // satisfied. .defensive_map_err(|_| Error::::BoundNotMet)?, - }; - Self::update_ledger(&controller_to_be_deprecated, &item); + ); + ledger.storage_put(); Ok(()) } @@ -905,7 +902,8 @@ pub mod pallet { let stash = ensure_signed(origin)?; let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let stash_balance = T::Currency::free_balance(&stash); if let Some(extra) = stash_balance.checked_sub(&ledger.total) { @@ -919,7 +917,7 @@ pub mod pallet { ); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); + ledger.storage_put(); // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { let _ = @@ -959,7 +957,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let unlocking = Self::ledger(&controller) + let unlocking = StakingLedger::::storage_get(&controller) .map(|l| l.unlocking.len()) .ok_or(Error::::NotController)?; @@ -977,7 +975,8 @@ pub mod pallet { // we need to fetch the ledger again because it may have been mutated in the call // to `Self::do_withdraw_unbonded` above. - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let mut value = value.min(ledger.active); ensure!( @@ -1020,7 +1019,7 @@ pub mod pallet { .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); + ledger.storage_put(); // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&ledger.stash) { @@ -1085,7 +1084,8 @@ pub mod pallet { pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1131,7 +1131,8 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1195,7 +1196,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; Self::chill_stash(&ledger.stash); Ok(()) } @@ -1219,7 +1221,8 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; >::insert(stash, payee); Ok(()) @@ -1402,11 +1405,9 @@ pub mod pallet { ) -> DispatchResult { ensure_root(origin)?; - // Remove all staking-related information. + // Remove all staking-related information and lock. Self::kill_stash(&stash, num_slashing_spans)?; - // Remove the lock. - T::Currency::remove_lock(STAKING_ID, &stash); Ok(()) } @@ -1495,7 +1496,8 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; @@ -1509,7 +1511,7 @@ pub mod pallet { }); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - Self::update_ledger(&controller, &ledger); + ledger.storage_put(); if T::VoterList::contains(&ledger.stash) { let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) .defensive(); @@ -1549,13 +1551,15 @@ pub mod pallet { let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) - .map(|l| l.total) - .unwrap_or_default() < ed; + StakingLedger::::storage_get( + &Self::bonded(stash.clone()).ok_or(Error::::NotStash)?, + ) + .map(|l| l.total) + .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); + // Remove all staking-related information and lock. Self::kill_stash(&stash, num_slashing_spans)?; - T::Currency::remove_lock(STAKING_ID, &stash); Ok(Pays::No.into()) } @@ -1575,7 +1579,8 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; for nom_stash in who @@ -1684,7 +1689,8 @@ pub mod pallet { pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. let caller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; let stash = ledger.stash; // In order for one user to chill another user, the following conditions must be met: diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index bb02da73f6e5..a4bd10367ead 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -51,8 +51,8 @@ use crate::{ BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, - OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, - ValidatorSlashInEra, + OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, StakingLedger, + UnappliedSlash, ValidatorSlashInEra, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ @@ -602,7 +602,7 @@ pub fn do_slash( Some(c) => c, }; - let mut ledger = match >::ledger(&controller) { + let mut ledger = match StakingLedger::::storage_get(&controller) { Some(ledger) => ledger, None => return, // nothing to do. }; @@ -618,7 +618,7 @@ pub fn do_slash( *reward_payout = reward_payout.saturating_sub(missing); } - >::update_ledger(&controller, &ledger); + ledger.storage_put(); // trigger the event >::deposit_event(super::Event::::Slashed { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index e6cbaafb9a26..2d69cb3a4fbd 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,6 +18,7 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; +use crate::ledger::StakingLedgerInspect; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, @@ -148,8 +149,8 @@ fn basic_setup_works() { // Account 11 controls its own stash, which is 100 * balance_factor units assert_eq!( - Staking::ledger(&11).unwrap(), - StakingLedger { + Ledger::get(&11).unwrap(), + StakingLedgerInspect:: { stash: 11, total: 1000, active: 1000, @@ -159,17 +160,17 @@ fn basic_setup_works() { ); // Account 21 controls its own stash, which is 200 * balance_factor units assert_eq!( - Staking::ledger(&21), - Some(StakingLedger { + Ledger::get(&21).unwrap(), + StakingLedgerInspect:: { stash: 21, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Account 1 does not control any stash - assert_eq!(Staking::ledger(&1), None); + assert_eq!(Ledger::get(&1), None); // ValidatorPrefs are default assert_eq_uvec!( @@ -182,14 +183,14 @@ fn basic_setup_works() { ); assert_eq!( - Staking::ledger(101), - Some(StakingLedger { + Ledger::get(101).unwrap(), + StakingLedgerInspect { stash: 101, total: 500, active: 500, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); @@ -457,14 +458,14 @@ fn staking_should_work() { // Note: the stashed value of 4 is still lock assert_eq!( - Staking::ledger(&3), - Some(StakingLedger { + Staking::ledger(&3).unwrap(), + StakingLedgerInspect { stash: 3, total: 1500, active: 1500, unlocking: Default::default(), claimed_rewards: bounded_vec![0], - }) + } ); // e.g. it cannot reserve more than 500 that it has free from the total 2000 assert_noop!(Balances::reserve(&3, 501), BalancesError::::LiquidityRestrictions); @@ -1044,14 +1045,14 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000); // Check how much is at stake assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Compute total payout now for whole duration as other parameter won't change @@ -1067,14 +1068,14 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); // Check that amount at stake increased accordingly assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, unlocking: Default::default(), claimed_rewards: bounded_vec![0], - }) + } ); // Change RewardDestination to Stash @@ -1093,14 +1094,14 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, unlocking: Default::default(), claimed_rewards: bounded_vec![0, 1], - }) + } ); // Change RewardDestination to Controller @@ -1122,14 +1123,14 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 23150 + total_payout_2); // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, unlocking: Default::default(), claimed_rewards: bounded_vec![0, 1, 2], - }) + } ); }); } @@ -1182,14 +1183,14 @@ fn bond_extra_works() { assert_eq!(Staking::bonded(&11), Some(11)); // Check how much is at stake assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Give account 11 some large free balance greater than total @@ -1199,28 +1200,28 @@ fn bond_extra_works() { assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); // There should be 100 more `total` and `active` in the ledger assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Call the bond_extra function with a large number, should handle it assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), Balance::max_value())); // The full amount of the funds should now be in the total and active assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000000, active: 1000000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); }); } @@ -1251,14 +1252,14 @@ fn bond_extra_and_withdraw_unbonded_works() { // Initial state of 11 assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); assert_eq!( Staking::eras_stakers(active_era(), 11), @@ -1269,14 +1270,14 @@ fn bond_extra_and_withdraw_unbonded_works() { Staking::bond_extra(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Exposure is a snapshot! only updated after the next era update. assert_ne!( @@ -1290,14 +1291,14 @@ fn bond_extra_and_withdraw_unbonded_works() { // ledger should be the same. assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Exposure is now updated. assert_eq!( @@ -1308,27 +1309,27 @@ fn bond_extra_and_withdraw_unbonded_works() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 1000).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 100, unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: bounded_vec![], - }), + }, ); // Attempting to free the balances now will fail. 2 eras need to pass. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 100, unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: bounded_vec![], - }), + }, ); // trigger next era. @@ -1337,14 +1338,14 @@ fn bond_extra_and_withdraw_unbonded_works() { // nothing yet assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000 + 100, active: 100, unlocking: bounded_vec![UnlockChunk { value: 1000, era: 2 + 3 }], claimed_rewards: bounded_vec![], - }), + }, ); // trigger next era. @@ -1353,14 +1354,14 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); // Now the value is free and the staking ledger is updated. assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 100, active: 100, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }), + }, ); }) } @@ -1456,14 +1457,14 @@ fn rebond_works() { // Initial state of 11 assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(2); @@ -1475,66 +1476,66 @@ fn rebond_works() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 100, unlocking: bounded_vec![UnlockChunk { value: 900, era: 2 + 3 }], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond all the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 100, unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond part of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 600, unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond the remainder of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Unbond parts of the funds in stash. @@ -1542,27 +1543,27 @@ fn rebond_works() { Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 100, unlocking: bounded_vec![UnlockChunk { value: 900, era: 5 }], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond part of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 600, unlocking: bounded_vec![UnlockChunk { value: 400, era: 5 }], claimed_rewards: bounded_vec![], - }) + } ); }) } @@ -1582,14 +1583,14 @@ fn rebond_is_fifo() { // Initial state of 10 assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(2); @@ -1597,14 +1598,14 @@ fn rebond_is_fifo() { // Unbond some of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 600, unlocking: bounded_vec![UnlockChunk { value: 400, era: 2 + 3 }], claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(3); @@ -1612,8 +1613,8 @@ fn rebond_is_fifo() { // Unbond more of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 300, @@ -1622,7 +1623,7 @@ fn rebond_is_fifo() { UnlockChunk { value: 300, era: 3 + 3 }, ], claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(4); @@ -1630,8 +1631,8 @@ fn rebond_is_fifo() { // Unbond yet more of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 200).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 100, @@ -1641,14 +1642,14 @@ fn rebond_is_fifo() { UnlockChunk { value: 200, era: 4 + 3 }, ], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond half of the unbonding funds. Staking::rebond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 500, @@ -1657,7 +1658,7 @@ fn rebond_is_fifo() { UnlockChunk { value: 100, era: 3 + 3 }, ], claimed_rewards: bounded_vec![], - }) + } ); }) } @@ -1679,27 +1680,27 @@ fn rebond_emits_right_value_in_event() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 100, unlocking: bounded_vec![UnlockChunk { value: 900, era: 1 + 3 }], claimed_rewards: bounded_vec![], - }) + } ); // Re-bond less than the total Staking::rebond(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 200, unlocking: bounded_vec![UnlockChunk { value: 800, era: 1 + 3 }], claimed_rewards: bounded_vec![], - }) + } ); // Event emitted should be correct assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 100 }); @@ -1707,14 +1708,14 @@ fn rebond_emits_right_value_in_event() { // Re-bond way more than available Staking::rebond(RuntimeOrigin::signed(11), 100_000).unwrap(); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); // Event emitted should be correct, only 800 assert_eq!(*staking_events().last().unwrap(), Event::Bonded { stash: 11, amount: 800 }); @@ -1744,7 +1745,7 @@ fn reward_to_stake_works() { ErasStakers::::insert(0, 21, Exposure { total: 69, own: 69, others: vec![] }); >::insert( &20, - StakingLedger { + StakingLedgerInspect { stash: 21, total: 69, active: 69, @@ -1805,13 +1806,7 @@ fn reap_stash_works() { // instead. Ledger::::insert( 11, - StakingLedger { - stash: 11, - total: 5, - active: 5, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }, + StakingLedger::::new(11, 5, 5, bounded_vec![], bounded_vec![]), ); // reap-able @@ -1934,14 +1929,14 @@ fn bond_with_no_staked_value() { // unbonding even 1 will cause all to be unbonded. assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); assert_eq!( - Staking::ledger(1), - Some(StakingLedger { + Staking::ledger(1).unwrap(), + StakingLedgerInspect { stash: 1, active: 0, total: 5, unlocking: bounded_vec![UnlockChunk { value: 5, era: 3 }], claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(1); @@ -3029,7 +3024,7 @@ fn staker_cannot_bail_deferred_slash() { assert_eq!( Ledger::::get(101).unwrap(), - StakingLedger { + StakingLedgerInspect { active: 0, total: 500, stash: 101, @@ -3769,14 +3764,14 @@ fn test_payout_stakers() { // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![1] - }) + } ); for i in 3..16 { @@ -3800,14 +3795,14 @@ fn test_payout_stakers() { // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: (1..=14).collect::>().try_into().unwrap() - }) + } ); let last_era = 99; @@ -3833,14 +3828,14 @@ fn test_payout_stakers() { expected_last_reward_era )); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![expected_start_reward_era, expected_last_reward_era] - }) + } ); // Out of order claims works. @@ -3848,8 +3843,8 @@ fn test_payout_stakers() { assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 23)); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 42)); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, @@ -3861,7 +3856,7 @@ fn test_payout_stakers() { 69, expected_last_reward_era ] - }) + } ); }); } @@ -4060,26 +4055,26 @@ fn bond_during_era_correctly_populates_claimed_rewards() { // Era = None bond_validator(9, 1000); assert_eq!( - Staking::ledger(&9), - Some(StakingLedger { + Staking::ledger(&9).unwrap(), + StakingLedgerInspect { stash: 9, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: bounded_vec![], - }) + } ); mock::start_active_era(5); bond_validator(11, 1000); assert_eq!( - Staking::ledger(&11), - Some(StakingLedger { + Staking::ledger(&11).unwrap(), + StakingLedgerInspect { stash: 11, total: 1000, active: 1000, unlocking: Default::default(), claimed_rewards: (0..5).collect::>().try_into().unwrap(), - }) + } ); // make sure only era upto history depth is stored @@ -4088,8 +4083,8 @@ fn bond_during_era_correctly_populates_claimed_rewards() { mock::start_active_era(current_era); bond_validator(13, 1000); assert_eq!( - Staking::ledger(&13), - Some(StakingLedger { + Staking::ledger(&13).unwrap(), + StakingLedgerInspect { stash: 13, total: 1000, active: 1000, @@ -4098,7 +4093,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { .collect::>() .try_into() .unwrap(), - }) + } ); }); } @@ -4344,7 +4339,7 @@ fn cannot_rebond_to_lower_than_ed() { // initial stuff. assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: 11 * 1000, active: 11 * 1000, @@ -4358,7 +4353,7 @@ fn cannot_rebond_to_lower_than_ed() { assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: 11 * 1000, active: 0, @@ -4384,7 +4379,7 @@ fn cannot_bond_extra_to_lower_than_ed() { // initial stuff. assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: 11 * 1000, active: 11 * 1000, @@ -4398,7 +4393,7 @@ fn cannot_bond_extra_to_lower_than_ed() { assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: 11 * 1000, active: 0, @@ -4425,7 +4420,7 @@ fn do_not_die_when_active_is_ed() { // given assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: 1000 * ed, active: 1000 * ed, @@ -4442,7 +4437,7 @@ fn do_not_die_when_active_is_ed() { // then assert_eq!( Staking::ledger(&21).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 21, total: ed, active: ed, @@ -5238,16 +5233,12 @@ fn force_apply_min_commission_works() { #[test] fn proportional_slash_stop_slashing_if_remaining_zero() { let c = |era, value| UnlockChunk:: { era, value }; - // Given - let mut ledger = StakingLedger:: { - stash: 123, - total: 40, - active: 20, - // we have some chunks, but they are not affected. - unlocking: bounded_vec![c(1, 10), c(2, 10)], - claimed_rewards: bounded_vec![], - }; + // we have some chunks, but they are not affected. + let unlocking = bounded_vec![c(1, 10), c(2, 10)]; + + // Given + let mut ledger = StakingLedger::::new(123, 40, 20, unlocking, bounded_vec![]); assert_eq!(BondingDuration::get(), 3); // should not slash more than the amount requested, by accidentally slashing the first chunk. @@ -5258,13 +5249,7 @@ fn proportional_slash_stop_slashing_if_remaining_zero() { fn proportional_ledger_slash_works() { let c = |era, value| UnlockChunk:: { era, value }; // Given - let mut ledger = StakingLedger:: { - stash: 123, - total: 10, - active: 10, - unlocking: bounded_vec![], - claimed_rewards: bounded_vec![], - }; + let mut ledger = StakingLedger::::new(123, 10, 10, bounded_vec![], bounded_vec![]); assert_eq!(BondingDuration::get(), 3); // When we slash a ledger with no unlocking chunks @@ -5504,7 +5489,7 @@ fn pre_bonding_era_cannot_be_claimed() { (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( Staking::ledger(&3).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 3, total: 1500, active: 1500, @@ -5570,7 +5555,7 @@ fn reducing_history_depth_abrupt() { (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( Staking::ledger(&3).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 3, total: 1500, active: 1500, @@ -5609,7 +5594,7 @@ fn reducing_history_depth_abrupt() { (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( Staking::ledger(&5).unwrap(), - StakingLedger { + StakingLedgerInspect { stash: 5, total: 1200, active: 1200, From 9ab1ba2a12fc0e8be899a5e4e752ae980e30907f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 14 Jul 2023 17:11:05 +0200 Subject: [PATCH 03/47] re-exposes Pallet::::ledger which calls into StakingLedger methods --- frame/staking/src/lib.rs | 4 ++-- frame/staking/src/pallet/impls.rs | 24 ++++++++++--------- frame/staking/src/pallet/mod.rs | 37 +++++++++++------------------- frame/staking/src/slashing.rs | 6 ++--- frame/staking/src/tests.rs | 38 +++++++++++++++---------------- 5 files changed, 50 insertions(+), 59 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 10149ed97111..70da1091f3a0 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -159,7 +159,7 @@ //! ``` //! use pallet_staking::{self as staking}; //! -//! #[frame_support::pallet] +//! #[frame_support::pallet(dev_mode)] //! pub mod pallet { //! use super::*; //! use frame_support::pallet_prelude::*; @@ -645,7 +645,7 @@ pub struct StashOf(sp_std::marker::PhantomData); impl Convert> for StashOf { fn convert(controller: T::AccountId) -> Option { - StakingLedger::::storage_get(&controller).map(|l| l.stash) + Pallet::::ledger(&controller).map(|l| l.stash) } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index dc7fe6c1fcb7..798cad6d3e34 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -66,11 +66,15 @@ use sp_runtime::TryRuntimeError; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { + pub fn ledger(controller: &T::AccountId) -> Option> { + StakingLedger::::storage_get(controller) + } + /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. Self::bonded(stash) - .and_then(|s| StakingLedger::::storage_get(&s)) + .and_then(|s| Self::ledger(&s)) .map(|l| l.active) .unwrap_or_default() } @@ -106,8 +110,7 @@ impl Pallet { controller: &T::AccountId, num_slashing_spans: u32, ) -> Result { - let mut ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { ledger = ledger.consolidate_unlocked(current_era, controller) @@ -166,8 +169,7 @@ impl Pallet { let controller = Self::bonded(&validator_stash).ok_or_else(|| { Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let mut ledger = - >::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; ledger .claimed_rewards @@ -293,7 +295,7 @@ impl Pallet { .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), RewardDestination::Staked => Self::bonded(stash) - .and_then(|c| StakingLedger::::storage_get(&c).map(|l| (c, l))) + .and_then(|c| Self::ledger(&c).map(|l| (c, l))) .and_then(|(_, mut ledger)| { ledger.active += amount; ledger.total += amount; @@ -659,7 +661,7 @@ impl Pallet { >::remove(stash); >::remove(stash); - StakingLedger::::storage_get(&controller).map_or_else( + Self::ledger(&controller).map_or_else( || log!(debug, "ledger not found in storage at `kill_stash`, unexpected."), |ledger| ledger.storage_remove(), ); @@ -1402,7 +1404,7 @@ impl ScoreProvider for Pallet { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); - let mut ledger = match StakingLedger::::storage_get(who) { + let mut ledger = match Self::ledger(who) { None => StakingLedger::default_from(who.clone()), Some(l) => l, }; @@ -1595,7 +1597,7 @@ impl StakingInterface for Pallet { } fn stash_by_ctrl(controller: &Self::AccountId) -> Result { - StakingLedger::::storage_get(controller) + Self::ledger(controller) .map(|l| l.stash) .ok_or(Error::::NotController.into()) } @@ -1616,7 +1618,7 @@ impl StakingInterface for Pallet { fn stake(who: &Self::AccountId) -> Result>, DispatchError> { Self::bonded(who) - .and_then(|c| StakingLedger::::storage_get(&c)) + .and_then(|c| Self::ledger(&c)) .map(|l| Stake { total: l.total, active: l.active }) .ok_or(Error::::NotStash.into()) } @@ -1825,7 +1827,7 @@ impl Pallet { fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = StakingLedger::::storage_get(&ctrl).ok_or("Not a controller.")?; + let ledger = Self::ledger(&ctrl).ok_or("Not a controller.")?; let real_total: BalanceOf = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); ensure!(real_total == ledger.total, "ledger.total corrupt"); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 834f79b2299e..a9d7648737e3 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -902,8 +902,7 @@ pub mod pallet { let stash = ensure_signed(origin)?; let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - let mut ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash_balance = T::Currency::free_balance(&stash); if let Some(extra) = stash_balance.checked_sub(&ledger.total) { @@ -957,7 +956,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let unlocking = StakingLedger::::storage_get(&controller) + let unlocking = Self::ledger(&controller) .map(|l| l.unlocking.len()) .ok_or(Error::::NotController)?; @@ -975,8 +974,7 @@ pub mod pallet { // we need to fetch the ledger again because it may have been mutated in the call // to `Self::do_withdraw_unbonded` above. - let mut ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let mut value = value.min(ledger.active); ensure!( @@ -1084,8 +1082,7 @@ pub mod pallet { pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1131,8 +1128,7 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1196,8 +1192,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; Self::chill_stash(&ledger.stash); Ok(()) } @@ -1221,8 +1216,7 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; >::insert(stash, payee); Ok(()) @@ -1496,8 +1490,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; @@ -1551,11 +1544,9 @@ pub mod pallet { let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || - StakingLedger::::storage_get( - &Self::bonded(stash.clone()).ok_or(Error::::NotStash)?, - ) - .map(|l| l.total) - .unwrap_or_default() < ed; + Self::ledger(&Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + .map(|l| l.total) + .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); // Remove all staking-related information and lock. @@ -1579,8 +1570,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash = &ledger.stash; for nom_stash in who @@ -1689,8 +1679,7 @@ pub mod pallet { pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. let caller = ensure_signed(origin)?; - let ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash = ledger.stash; // In order for one user to chill another user, the following conditions must be met: diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index a4bd10367ead..d3d078b871ea 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -51,8 +51,8 @@ use crate::{ BalanceOf, Config, Error, Exposure, NegativeImbalanceOf, NominatorSlashInEra, - OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, StakingLedger, - UnappliedSlash, ValidatorSlashInEra, + OffendingValidators, Pallet, Perbill, SessionInterface, SpanSlash, UnappliedSlash, + ValidatorSlashInEra, }; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ @@ -602,7 +602,7 @@ pub fn do_slash( Some(c) => c, }; - let mut ledger = match StakingLedger::::storage_get(&controller) { + let mut ledger = match Pallet::::ledger(&controller) { Some(ledger) => ledger, None => return, // nothing to do. }; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 2d69cb3a4fbd..1a9f30e134bb 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -170,7 +170,7 @@ fn basic_setup_works() { } ); // Account 1 does not control any stash - assert_eq!(Ledger::get(&1), None); + assert_eq!(Staking::ledger(&1), None); // ValidatorPrefs are default assert_eq_uvec!( @@ -183,7 +183,7 @@ fn basic_setup_works() { ); assert_eq!( - Ledger::get(101).unwrap(), + Staking::ledger(&101).unwrap(), StakingLedgerInspect { stash: 101, total: 500, @@ -715,9 +715,9 @@ fn nominators_also_get_slashed_pro_rata() { assert_eq!(initial_exposure.others.first().unwrap().who, 101); // staked values; - let nominator_stake = Staking::ledger(101).unwrap().active; + let nominator_stake = Staking::ledger(&101).unwrap().active; let nominator_balance = balances(&101).0; - let validator_stake = Staking::ledger(11).unwrap().active; + let validator_stake = Staking::ledger(&11).unwrap().active; let validator_balance = balances(&11).0; let exposed_stake = initial_exposure.total; let exposed_validator = initial_exposure.own; @@ -730,8 +730,8 @@ fn nominators_also_get_slashed_pro_rata() { ); // both stakes must have been decreased. - assert!(Staking::ledger(101).unwrap().active < nominator_stake); - assert!(Staking::ledger(11).unwrap().active < validator_stake); + assert!(Staking::ledger(&101).unwrap().active < nominator_stake); + assert!(Staking::ledger(&11).unwrap().active < validator_stake); let slash_amount = slash_percent * exposed_stake; let validator_share = @@ -744,8 +744,8 @@ fn nominators_also_get_slashed_pro_rata() { assert!(nominator_share > 0); // both stakes must have been decreased pro-rata. - assert_eq!(Staking::ledger(101).unwrap().active, nominator_stake - nominator_share); - assert_eq!(Staking::ledger(11).unwrap().active, validator_stake - validator_share); + assert_eq!(Staking::ledger(&101).unwrap().active, nominator_stake - nominator_share); + assert_eq!(Staking::ledger(&11).unwrap().active, validator_stake - validator_share); assert_eq!( balances(&101).0, // free balance nominator_balance - nominator_share, @@ -1929,7 +1929,7 @@ fn bond_with_no_staked_value() { // unbonding even 1 will cause all to be unbonded. assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); assert_eq!( - Staking::ledger(1).unwrap(), + Staking::ledger(&1).unwrap(), StakingLedgerInspect { stash: 1, active: 0, @@ -1944,14 +1944,14 @@ fn bond_with_no_staked_value() { // not yet removed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1).is_some()); + assert!(Staking::ledger(&1).is_some()); assert_eq!(Balances::locks(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1).is_none()); + assert!(Staking::ledger(&1).is_none()); assert_eq!(Balances::locks(&1).len(), 0); }); } @@ -2036,7 +2036,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(&v).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2088,7 +2088,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(v).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(&v).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2974,7 +2974,7 @@ fn retroactive_deferred_slashes_one_before() { mock::start_active_era(4); - assert_eq!(Staking::ledger(11).unwrap().total, 1000); + assert_eq!(Staking::ledger(&11).unwrap().total, 1000); // slash happens after the next line. mock::start_active_era(5); @@ -2989,9 +2989,9 @@ fn retroactive_deferred_slashes_one_before() { )); // their ledger has already been slashed. - assert_eq!(Staking::ledger(11).unwrap().total, 900); + assert_eq!(Staking::ledger(&11).unwrap().total, 900); assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000)); - assert_eq!(Staking::ledger(11).unwrap().total, 900); + assert_eq!(Staking::ledger(&11).unwrap().total, 900); }) } @@ -5617,7 +5617,7 @@ fn reducing_max_unlocking_chunks_abrupt() { MaxUnlockingChunks::set(2); start_active_era(10); assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 300, RewardDestination::Staked)); - assert!(matches!(Staking::ledger(3), Some(_))); + assert!(matches!(Staking::ledger(&3), Some(_))); // when staker unbonds assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20)); @@ -5626,7 +5626,7 @@ fn reducing_max_unlocking_chunks_abrupt() { // => 10 + 3 = 13 let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; - assert!(matches!(Staking::ledger(3), + assert!(matches!(Staking::ledger(&3), Some(StakingLedger { unlocking, .. @@ -5638,7 +5638,7 @@ fn reducing_max_unlocking_chunks_abrupt() { // then another unlock chunk is added let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; - assert!(matches!(Staking::ledger(3), + assert!(matches!(Staking::ledger(&3), Some(StakingLedger { unlocking, .. From 78f4ff688f427de03a777d714471e0f0780ac37a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 14 Jul 2023 17:48:24 +0200 Subject: [PATCH 04/47] Fixes benchmarks --- frame/staking/src/benchmarking.rs | 16 ++++----- frame/staking/src/ledger.rs | 16 ++++----- frame/staking/src/pallet/impls.rs | 58 +++++++++++++++---------------- 3 files changed, 43 insertions(+), 47 deletions(-) diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 8d6eb7459074..828601445e26 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -30,7 +30,7 @@ use frame_support::{ }; use sp_runtime::{ traits::{Bounded, One, StaticLookup, TrailingZeroInput, Zero}, - Perbill, Percent, + Perbill, Percent, Saturating, }; use sp_staking::{currency_to_vote::CurrencyToVote, SessionIndex}; use sp_std::prelude::*; @@ -685,13 +685,13 @@ benchmarks! { let stash = scenario.origin_stash1; add_slashing_spans::(&stash, s); - let l = StakingLedger { - stash: stash.clone(), - active: T::Currency::minimum_balance() - One::one(), - total: T::Currency::minimum_balance() - One::one(), - unlocking: Default::default(), - claimed_rewards: Default::default(), - }; + let l = StakingLedger::::new( + stash.clone(), + T::Currency::minimum_balance() - One::one(), + T::Currency::minimum_balance() - One::one(), + Default::default(), + Default::default(), + ); Ledger::::insert(&controller, l); assert!(Bonded::::contains_key(&stash)); diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index e2453388c22d..ae78e26448be 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -49,7 +49,7 @@ pub struct StakingLedger { } impl StakingLedger { - /// Initializes the default object using the given `validator`. + #[cfg(feature = "runtime-benchmarks")] pub fn default_from(stash: T::AccountId) -> Self { Self { stash, @@ -61,18 +61,18 @@ impl StakingLedger { } } - // only used for new ledgers, so controller == stash. + /// only used for new ledgers, so controller == stash. pub fn new( stash: T::AccountId, - total_stake: BalanceOf, active_stake: BalanceOf, + total_stake: BalanceOf, unlocking: BoundedVec>, T::MaxUnlockingChunks>, claimed_rewards: BoundedVec, ) -> Self { Self { stash: stash.clone(), - total: total_stake, active: active_stake, + total: total_stake, unlocking, claimed_rewards, // controllers are deprecated and map 1-1 to stashes. @@ -103,11 +103,7 @@ impl StakingLedger { /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. - pub(crate) fn consolidate_unlocked( - self, - current_era: EraIndex, - controller: &T::AccountId, - ) -> Self { + pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { let mut total = self.total; let unlocking: BoundedVec<_, _> = self .unlocking @@ -132,7 +128,7 @@ impl StakingLedger { active: self.active, unlocking, claimed_rewards: self.claimed_rewards, - controller: Some(controller.clone()), + controller: self.controller, } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 798cad6d3e34..51054ad3f341 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -113,7 +113,7 @@ impl Pallet { let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { - ledger = ledger.consolidate_unlocked(current_era, controller) + ledger = ledger.consolidate_unlocked(current_era) } let used_weight = @@ -1068,13 +1068,13 @@ impl ElectionDataProvider for Pallet { >::insert(voter.clone(), voter.clone()); >::insert( voter.clone(), - StakingLedger { - stash: voter.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, + StakingLedger::::new( + voter.clone(), + stake, + stake, + Default::default(), + Default::default(), + ), ); Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false }); @@ -1086,13 +1086,13 @@ impl ElectionDataProvider for Pallet { >::insert(target.clone(), target.clone()); >::insert( target.clone(), - StakingLedger { - stash: target.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, + StakingLedger::::new( + target.clone(), + stake, + stake, + Default::default(), + Default::default(), + ), ); Self::do_add_validator( &target, @@ -1127,13 +1127,13 @@ impl ElectionDataProvider for Pallet { >::insert(v.clone(), v.clone()); >::insert( v.clone(), - StakingLedger { - stash: v.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, + StakingLedger::::new( + v.clone(), + stake, + stake, + Default::default(), + Default::default(), + ), ); Self::do_add_validator( &v, @@ -1148,13 +1148,13 @@ impl ElectionDataProvider for Pallet { >::insert(v.clone(), v.clone()); >::insert( v.clone(), - StakingLedger { - stash: v.clone(), - active: stake, - total: stake, - unlocking: Default::default(), - claimed_rewards: Default::default(), - }, + StakingLedger::::new( + v.clone(), + stake, + stake, + Default::default(), + Default::default(), + ), ); Self::do_add_nominator( &v, From 8c4d2b0851c4c80ea4f7bc14b7594d3c6475dec5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 17 Jul 2023 11:12:04 +0200 Subject: [PATCH 05/47] Improves docs --- frame/staking/src/ledger.rs | 47 ++++++++++++++++++++++++++++++- frame/staking/src/lib.rs | 2 +- frame/staking/src/pallet/impls.rs | 5 ++-- frame/staking/src/pallet/mod.rs | 8 ++++-- 4 files changed, 56 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index ae78e26448be..9f67852a067f 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -11,6 +11,19 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use crate::{log, BalanceOf, Config, Ledger, UnlockChunk, STAKING_ID}; /// The ledger of a (bonded) stash. +/// +/// Note: All the reads and mutations to the `Ledger` storage type *MUST* be performed through the +/// methods exposed by this struct to ensure data and staking lock consistency, namely: +/// +/// - [`StakingLedger::storage_get`]: queries the [`Ledger`] storage type and enriches the returned +/// object with the controller account, which is used in posterior mutating calls to both +/// [`StakingLedger::storage_put`] and [`StakingLedger::storage_remove`]. +/// - [`StakingLedger::storage_put`]: inserts/updates a staking ledger entry in [`Ledger`] and +/// updates the staking locks accordingly. +/// - [`StakingLedger::storage_remove`]: removes a staking ledger entry in [`Ledger`] and updates +/// the staking locks accordingly. +/// - [`StakingLedger::storage_exists`]: checks if an account has staking ledger entry in +/// [`Ledger`]. #[derive( PartialEqNoBound, EqNoBound, @@ -61,7 +74,13 @@ impl StakingLedger { } } - /// only used for new ledgers, so controller == stash. + /// Returns a new instance of a staking ledger. + /// + /// The `Ledger` storage is not mutated. In order to do that [`fn storage_put`] must be called + /// on the returned staking ledger. + /// + /// Note: as the controller accounts are being deprecated, the stash account is the same as the + /// controller account. pub fn new( stash: T::AccountId, active_stake: BalanceOf, @@ -80,10 +99,16 @@ impl StakingLedger { } } + /// Returns the controller account of the staking ledger. pub(crate) fn controller(&self) -> T::AccountId { self.controller.clone().expect("TODO, handle this edge case better?") } + /// Returns the staking ledger entry stored in [`Ledger`] storage. Returns `None` if the entry + /// does not exist for the give controller. + /// + /// Note: To ensure consistency, all the [`Ledger`] storage queries should be made through this + /// helper function. pub(crate) fn storage_get(controller: &T::AccountId) -> Option { >::get(&controller).map(|mut l| { l.controller = Some(controller.clone()); @@ -91,16 +116,36 @@ impl StakingLedger { }) } + /// Inserts/updates a staking ledger account. + /// + /// The staking locks od the stash account are updated accordingly. + /// + /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through + /// this helper function. pub(crate) fn storage_put(&self) { T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); Ledger::::insert(&self.controller(), &self); } + /// Removes a staking ledger account. + /// + /// The staking locks od the stash account are updated accordingly. + /// + /// Note: To ensure lock consistency, all the [`Ledger`] storage removes should be made through + /// this helper function. pub(crate) fn storage_remove(self) { T::Currency::remove_lock(STAKING_ID, &self.stash); Ledger::::remove(self.controller()); } + /// Checks if a staking ledger with a given controller account exists. + /// + /// Note: this is only a wrapper around `Ledger::contains_key` to ensure that, as a rule, + /// developers never call into [`Ledger`] directly. + pub(crate) fn storage_exists(controller: &T::AccountId) -> bool { + >::contains_key(controller) + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 70da1091f3a0..cf13514516a4 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -91,7 +91,7 @@ //! #### Nomination //! //! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on -//! a set of validators to be elected. Once interest in nomination is stated by an account, it +//! a set of validators to be elected. Once interest in nomination is stated by an account, it //! takes effect at the next election round. The funds in the nominator's stash account indicate the //! _weight_ of its vote. Both the rewards and any punishment that a validator earns are shared //! between the validator and its nominators. This rule incentivizes the nominators to NOT vote for diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 51054ad3f341..3de36c829d2b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -169,7 +169,8 @@ impl Pallet { let controller = Self::bonded(&validator_stash).ok_or_else(|| { Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; ledger .claimed_rewards @@ -192,7 +193,7 @@ impl Pallet { // Input data seems good, no errors allowed after this point - >::insert(&controller, &ledger); + ledger.storage_put(); // Get Era reward points. It has TOTAL and INDIVIDUAL // Find the fraction of the era reward that belongs to the validator diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index a9d7648737e3..6a474bf4f979 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -316,6 +316,9 @@ pub mod pallet { pub type MinCommission = StorageValue<_, Perbill, ValueQuery>; /// Map from all (unlocked) "controller" accounts to the info regarding the staking. + /// + /// Note: All the reads and mutations to this storage *MUST* be done through the methods exposed + /// by [`StakingLedger`] to ensure data and lock consistency. #[pallet::storage] pub type Ledger = StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>; @@ -840,7 +843,7 @@ pub mod pallet { return Err(Error::::AlreadyBonded.into()) } - if >::contains_key(&controller_to_be_deprecated) { + if StakingLedger::::storage_exists(&controller_to_be_deprecated) { return Err(Error::::AlreadyPaired.into()) } @@ -1242,11 +1245,12 @@ pub mod pallet { let stash = ensure_signed(origin)?; let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - if >::contains_key(&stash) { + if StakingLedger::::storage_exists(&stash) { return Err(Error::::AlreadyPaired.into()) } if old_controller != stash { >::insert(&stash, &stash); + // the ledger is accessed directly since this is temporary passive migration logic. if let Some(l) = >::take(&old_controller) { >::insert(&stash, l); } From 2f34a49620b686f4bc3318948f15b0d3d57f334c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 17 Jul 2023 11:22:32 +0200 Subject: [PATCH 06/47] Fixes rust docs --- frame/staking/src/ledger.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 9f67852a067f..b348912507a2 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -15,15 +15,14 @@ use crate::{log, BalanceOf, Config, Ledger, UnlockChunk, STAKING_ID}; /// Note: All the reads and mutations to the `Ledger` storage type *MUST* be performed through the /// methods exposed by this struct to ensure data and staking lock consistency, namely: /// -/// - [`StakingLedger::storage_get`]: queries the [`Ledger`] storage type and enriches the returned +/// - `StakingLedger::storage_get`: queries the [`Ledger`] storage type and enriches the returned /// object with the controller account, which is used in posterior mutating calls to both -/// [`StakingLedger::storage_put`] and [`StakingLedger::storage_remove`]. -/// - [`StakingLedger::storage_put`]: inserts/updates a staking ledger entry in [`Ledger`] and +/// `StakingLedger::storage_put` and [`StakingLedger::storage_remove`]. +/// - `StakingLedger::storage_put`: inserts/updates a staking ledger entry in [`Ledger`] and /// updates the staking locks accordingly. -/// - [`StakingLedger::storage_remove`]: removes a staking ledger entry in [`Ledger`] and updates +/// - `StakingLedger::storage_remove`: removes a staking ledger entry in [`Ledger`] and updates /// the staking locks accordingly. -/// - [`StakingLedger::storage_exists`]: checks if an account has staking ledger entry in -/// [`Ledger`]. +/// - `StakingLedger::storage_exists`: checks if an account has staking ledger entry in [`Ledger`]. #[derive( PartialEqNoBound, EqNoBound, From 73822160754bae861c54730737360083ecd6e8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 17 Jul 2023 11:28:05 +0200 Subject: [PATCH 07/47] Improves expect message for controller --- frame/staking/src/ledger.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index b348912507a2..4f7e645b9729 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -99,8 +99,10 @@ impl StakingLedger { } /// Returns the controller account of the staking ledger. - pub(crate) fn controller(&self) -> T::AccountId { - self.controller.clone().expect("TODO, handle this edge case better?") + fn controller(&self) -> T::AccountId { + self.controller + .clone() + .expect("staking ledger instance was fetched through `storage_get`, qed.") } /// Returns the staking ledger entry stored in [`Ledger`] storage. Returns `None` if the entry From acb71b3d5a03c7dc2c31c42028c0c1119792ce13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 18 Jul 2023 16:53:02 +0200 Subject: [PATCH 08/47] Encapsulates Bond mutations and reads into StakingLedger --- frame/staking/src/ledger.rs | 92 ++++++++++++++++--------------- frame/staking/src/lib.rs | 2 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 53 +++++++++++------- frame/staking/src/pallet/mod.rs | 84 +++++++++++++++++----------- frame/staking/src/slashing.rs | 3 +- frame/staking/src/tests.rs | 2 +- 7 files changed, 140 insertions(+), 98 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 4f7e645b9729..2f3c7844ee25 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -8,21 +8,14 @@ use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; use sp_staking::{EraIndex, OnStakingUpdate}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -use crate::{log, BalanceOf, Config, Ledger, UnlockChunk, STAKING_ID}; +use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ID}; /// The ledger of a (bonded) stash. /// /// Note: All the reads and mutations to the `Ledger` storage type *MUST* be performed through the /// methods exposed by this struct to ensure data and staking lock consistency, namely: /// -/// - `StakingLedger::storage_get`: queries the [`Ledger`] storage type and enriches the returned -/// object with the controller account, which is used in posterior mutating calls to both -/// `StakingLedger::storage_put` and [`StakingLedger::storage_remove`]. -/// - `StakingLedger::storage_put`: inserts/updates a staking ledger entry in [`Ledger`] and -/// updates the staking locks accordingly. -/// - `StakingLedger::storage_remove`: removes a staking ledger entry in [`Ledger`] and updates -/// the staking locks accordingly. -/// - `StakingLedger::storage_exists`: checks if an account has staking ledger entry in [`Ledger`]. +/// TODO: improve/fix comments #[derive( PartialEqNoBound, EqNoBound, @@ -60,6 +53,12 @@ pub struct StakingLedger { controller: Option, } +// TODO: docs +pub(crate) enum StakingLedgerStatus { + BondedNotPaired, + Paired(StakingLedger), +} + impl StakingLedger { #[cfg(feature = "runtime-benchmarks")] pub fn default_from(stash: T::AccountId) -> Self { @@ -98,23 +97,47 @@ impl StakingLedger { } } - /// Returns the controller account of the staking ledger. - fn controller(&self) -> T::AccountId { - self.controller - .clone() - .expect("staking ledger instance was fetched through `storage_get`, qed.") + pub(crate) fn controller_of(stash: &T::AccountId) -> Option { + >::get(stash) } - /// Returns the staking ledger entry stored in [`Ledger`] storage. Returns `None` if the entry - /// does not exist for the give controller. + /// Returns the controller account of the staking ledger. /// - /// Note: To ensure consistency, all the [`Ledger`] storage queries should be made through this - /// helper function. - pub(crate) fn storage_get(controller: &T::AccountId) -> Option { - >::get(&controller).map(|mut l| { - l.controller = Some(controller.clone()); - l - }) + /// Note: Fallback into querying the `Bonded` storage with the ledger stash if the controller + /// is not set, which most likely means that self was fetched directly from `Ledger` instead of + /// through the methods exposed in `StakingLedger`. If ledger does not exist, return `None`. + pub(crate) fn controller(&self) -> Option { + self.controller.clone().or_else(|| Self::controller_of(&self.stash)) + } + + /// Removes all data related to a staking ledger and its bond. + pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { + let controller = >::get(stash).ok_or(Error::::NotStash)?; + + // TODO: we can return an error here now instead of silently failing, do it? + >::get(&controller).map_or_else( + || log!(debug, "ledger not found in storage, unexpected"), + |ledger| { + T::Currency::remove_lock(STAKING_ID, &ledger.stash); + Ledger::::remove(controller); + }, + ); + + Ok(()) + } + + pub(crate) fn get(stash: &T::AccountId) -> Option> { + if let Some(controller) = >::get(stash) { + match >::get(&controller).map(|mut l| { + l.controller = Some(controller.clone()); + l + }) { + Some(ledger) => Some(StakingLedgerStatus::Paired(ledger)), + None => Some(StakingLedgerStatus::BondedNotPaired), + } + } else { + None + } } /// Inserts/updates a staking ledger account. @@ -123,28 +146,11 @@ impl StakingLedger { /// /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through /// this helper function. - pub(crate) fn storage_put(&self) { + pub(crate) fn mutate(&self) -> Result<(), Error> { T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); - Ledger::::insert(&self.controller(), &self); - } - - /// Removes a staking ledger account. - /// - /// The staking locks od the stash account are updated accordingly. - /// - /// Note: To ensure lock consistency, all the [`Ledger`] storage removes should be made through - /// this helper function. - pub(crate) fn storage_remove(self) { - T::Currency::remove_lock(STAKING_ID, &self.stash); - Ledger::::remove(self.controller()); - } + Ledger::::insert(&self.controller().ok_or(Error::::NotController)?, &self); - /// Checks if a staking ledger with a given controller account exists. - /// - /// Note: this is only a wrapper around `Ledger::contains_key` to ensure that, as a rule, - /// developers never call into [`Ledger`] directly. - pub(crate) fn storage_exists(controller: &T::AccountId) -> bool { - >::contains_key(controller) + Ok(()) } /// Remove entries from `unlocking` that are sufficiently old and reduce the diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index cf13514516a4..c5f884238ffd 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -306,7 +306,7 @@ use frame_support::{ weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; -use ledger::StakingLedger; +use ledger::{StakingLedger, StakingLedgerStatus}; use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index c36c2eacb139..4592ead3824f 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -77,7 +77,7 @@ impl sp_runtime::BoundToRuntimeAppPublic for OtherSessionHandler { } pub fn is_disabled(controller: AccountId) -> bool { - let stash = StakingLedger::::storage_get(&controller).unwrap().stash; + let stash = Ledger::::get(&controller).unwrap().stash; let validator_index = match Session::validators().iter().position(|v| *v == stash) { Some(index) => index as u32, None => return false, diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 3de36c829d2b..f51215361649 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -45,9 +45,9 @@ use sp_staking::{ use sp_std::prelude::*; use crate::{ - log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, - Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination, - SessionInterface, StakingLedger, ValidatorPrefs, + ledger::StakingLedgerStatus, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, + EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, MaxWinnersOf, Nominations, + PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, }; use super::pallet::*; @@ -66,8 +66,16 @@ use sp_runtime::TryRuntimeError; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { - pub fn ledger(controller: &T::AccountId) -> Option> { - StakingLedger::::storage_get(controller) + pub fn ledger(stash: &T::AccountId) -> Option> { + match StakingLedger::::get(stash) { + None => None, + Some(StakingLedgerStatus::BondedNotPaired) => None, + Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger), + } + } + + pub fn bonded(stash: &T::AccountId) -> Option { + StakingLedger::::controller_of(&stash) } /// The total balance that can be slashed from a stash account as of right now. @@ -126,7 +134,7 @@ impl Pallet { T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) } else { // This was the consequence of a partial unbond. just update the ledger and move on. - ledger.storage_put(); + ledger.mutate()?; // This is only an update, so we use less overall weight. T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) @@ -166,11 +174,18 @@ impl Pallet { .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let controller = Self::bonded(&validator_stash).ok_or_else(|| { - Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - })?; - let mut ledger = - StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = match StakingLedger::::get(&validator_stash) { + None => + Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController.into()), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; + + //let controller = Self::bonded(&validator_stash).ok_or_else(|| { + // Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) + //})?; + //let mut ledger = + // StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; ledger .claimed_rewards @@ -193,7 +208,7 @@ impl Pallet { // Input data seems good, no errors allowed after this point - ledger.storage_put(); + ledger.mutate()?; // Get Era reward points. It has TOTAL and INDIVIDUAL // Find the fraction of the era reward that belongs to the validator @@ -301,7 +316,9 @@ impl Pallet { ledger.active += amount; ledger.total += amount; let r = T::Currency::deposit_into_existing(stash, amount).ok(); - ledger.storage_put(); + + // TODO: handle error here. + let _ = ledger.mutate(); r }), @@ -656,16 +673,12 @@ impl Pallet { /// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance. /// - through `reap_stash()` if the balance has fallen to zero (through slashing). pub(crate) fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { - let controller = >::get(stash).ok_or(Error::::NotStash)?; - slashing::clear_stash_metadata::(stash, num_slashing_spans)?; + // removes controller from `Bonded` and staking ledger from `Ledger`. + StakingLedger::::kill(stash)?; >::remove(stash); >::remove(stash); - Self::ledger(&controller).map_or_else( - || log!(debug, "ledger not found in storage at `kill_stash`, unexpected."), - |ledger| ledger.storage_remove(), - ); Self::do_remove_validator(stash); Self::do_remove_nominator(stash); @@ -1646,7 +1659,7 @@ impl StakingInterface for Pallet { who: Self::AccountId, num_slashing_spans: u32, ) -> Result { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + let ctrl = Self::bonded(&who).ok_or(Error::::NotStash)?; Self::withdraw_unbonded(RawOrigin::Signed(ctrl.clone()).into(), num_slashing_spans) .map(|_| !Ledger::::contains_key(&ctrl)) .map_err(|with_post| with_post.error) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 6a474bf4f979..529ed1656ec3 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -45,8 +45,8 @@ pub use impls::*; use crate::{ slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, - RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, - ValidatorPrefs, + RewardDestination, SessionInterface, StakingLedger, StakingLedgerStatus, UnappliedSlash, + UnlockChunk, ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -294,7 +294,6 @@ pub mod pallet { /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] - #[pallet::getter(fn bonded)] pub type Bonded = StorageMap<_, Twox64Concat, T::AccountId, T::AccountId>; /// The minimum active bond to become and maintain the role of a nominator. @@ -837,15 +836,12 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let stash = ensure_signed(origin)?; - let controller_to_be_deprecated = stash.clone(); - if >::contains_key(&stash) { - return Err(Error::::AlreadyBonded.into()) - } - - if StakingLedger::::storage_exists(&controller_to_be_deprecated) { - return Err(Error::::AlreadyPaired.into()) - } + match StakingLedger::::get(&stash) { + None => Ok(()), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::AlreadyBonded), + Some(StakingLedgerStatus::Paired(_)) => Err(Error::::AlreadyPaired), + }?; // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { @@ -856,6 +852,8 @@ pub mod pallet { // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. + + // TODO: insert stash<>stash to Bonded; StakingLedger::bond(&AccountId, &AccountId) >::insert(&stash, &stash); >::insert(&stash, payee); @@ -878,7 +876,7 @@ pub mod pallet { // satisfied. .defensive_map_err(|_| Error::::BoundNotMet)?, ); - ledger.storage_put(); + ledger.mutate()?; Ok(()) } @@ -904,8 +902,14 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = match StakingLedger::::get(&stash) { + None => Err(Error::::NotStash), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; + + //let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; + //let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let stash_balance = T::Currency::free_balance(&stash); if let Some(extra) = stash_balance.checked_sub(&ledger.total) { @@ -919,7 +923,7 @@ pub mod pallet { ); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.storage_put(); + ledger.mutate()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { let _ = @@ -1020,7 +1024,7 @@ pub mod pallet { .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.storage_put(); + ledger.mutate()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&ledger.stash) { @@ -1131,7 +1135,11 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + // TODO: refactor get ledger from controller to StakingLedger. + // Maybe wrap the AccountId with input to StakingLedger::get with an enum that could be + // either a stash or controller. + let ledger = >::get(controller).ok_or(Error::::NotController)?; + ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1195,7 +1203,11 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::chill())] pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + + // TODO: refactor get ledger from controller to StakingLedger. + // Maybe wrap the AccountId with input to StakingLedger::get with an enum that could be + // either a stash or controller. + let ledger = >::get(controller).ok_or(Error::::NotController)?; Self::chill_stash(&ledger.stash); Ok(()) } @@ -1243,19 +1255,29 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_controller())] pub fn set_controller(origin: OriginFor) -> DispatchResult { let stash = ensure_signed(origin)?; - let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - if StakingLedger::::storage_exists(&stash) { - return Err(Error::::AlreadyPaired.into()) - } - if old_controller != stash { - >::insert(&stash, &stash); - // the ledger is accessed directly since this is temporary passive migration logic. - if let Some(l) = >::take(&old_controller) { - >::insert(&stash, l); - } + // the bonded map and ledger are mutated directly as this logic is related to a + // (temporary) passive migration. + match StakingLedger::::get(&stash) { + None => Err(Error::::NotStash.into()), + Some(StakingLedgerStatus::BondedNotPaired) => { + // update bond only. + >::insert(&stash, &stash); + Ok(()) + }, + Some(StakingLedgerStatus::Paired(ledger)) => { + let controller = ledger.controller().ok_or(Error::::NotController)?; // correct error to throw here? + if controller == stash { + // stash is already its own controller. + return Err(Error::::AlreadyPaired.into()) + } + // update bond and ledger. + >::remove(controller); + >::insert(&stash, &stash); + >::insert(&stash, ledger); + Ok(()) + }, } - Ok(()) } /// Sets the ideal number of validators. @@ -1508,7 +1530,7 @@ pub mod pallet { }); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.storage_put(); + ledger.mutate()?; if T::VoterList::contains(&ledger.stash) { let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) .defensive(); @@ -1548,7 +1570,7 @@ pub mod pallet { let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(&Self::bonded(stash.clone()).ok_or(Error::::NotStash)?) + Self::ledger(&Self::bonded(&stash.clone()).ok_or(Error::::NotStash)?) .map(|l| l.total) .unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index d3d078b871ea..12be24824ecc 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -618,7 +618,8 @@ pub fn do_slash( *reward_payout = reward_payout.saturating_sub(missing); } - ledger.storage_put(); + // TODO: handle error here + let _ = ledger.mutate(); // trigger the event >::deposit_event(super::Event::::Slashed { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 1a9f30e134bb..52eb3ad01825 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -263,7 +263,7 @@ fn change_controller_works() { #[test] fn change_controller_already_paired_once_stash() { ExtBuilder::default().build_and_execute(|| { - // 10 and 11 are bonded as controller and stash respectively. + // 11 and 11 are bonded as controller and stash respectively. assert_eq!(Staking::bonded(&11), Some(11)); // 11 is initially a validator. From f4b1062e010b115f2411ceebe5b3d3bc5610d7c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 18 Jul 2023 21:57:36 +0200 Subject: [PATCH 09/47] Refactors leftover calls to Bonded and Ledger under StakingLedger --- frame/staking/src/ledger.rs | 32 +++++++++++++++++--------------- frame/staking/src/pallet/mod.rs | 15 +++++++-------- 2 files changed, 24 insertions(+), 23 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 2f3c7844ee25..15c0cb12a20e 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -110,27 +110,17 @@ impl StakingLedger { self.controller.clone().or_else(|| Self::controller_of(&self.stash)) } - /// Removes all data related to a staking ledger and its bond. - pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { - let controller = >::get(stash).ok_or(Error::::NotStash)?; - - // TODO: we can return an error here now instead of silently failing, do it? - >::get(&controller).map_or_else( - || log!(debug, "ledger not found in storage, unexpected"), - |ledger| { - T::Currency::remove_lock(STAKING_ID, &ledger.stash); - Ledger::::remove(controller); - }, - ); + pub(crate) fn bond(&self) -> Result<(), Error> { + >::insert(&self.stash, &self.stash); Ok(()) } pub(crate) fn get(stash: &T::AccountId) -> Option> { if let Some(controller) = >::get(stash) { - match >::get(&controller).map(|mut l| { - l.controller = Some(controller.clone()); - l + match >::get(&controller).map(|mut ledger| { + ledger.controller = Some(controller.clone()); + ledger }) { Some(ledger) => Some(StakingLedgerStatus::Paired(ledger)), None => Some(StakingLedgerStatus::BondedNotPaired), @@ -153,6 +143,18 @@ impl StakingLedger { Ok(()) } + /// Removes all data related to a staking ledger and its bond. + pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { + let controller = >::get(stash).ok_or(Error::::NotStash)?; + + >::get(&controller).ok_or(Error::::NotController).map(|ledger| { + T::Currency::remove_lock(STAKING_ID, &ledger.stash); + Ledger::::remove(controller); + + Ok(()) + })? + } + /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 529ed1656ec3..92c2bb7a7c03 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -850,13 +850,6 @@ pub mod pallet { frame_system::Pallet::::inc_consumers(&stash).map_err(|_| Error::::BadState)?; - // You're auto-bonded forever, here. We might improve this by only bonding when - // you actually validate/nominate and remove once you unbond __everything__. - - // TODO: insert stash<>stash to Bonded; StakingLedger::bond(&AccountId, &AccountId) - >::insert(&stash, &stash); - >::insert(&stash, payee); - let current_era = CurrentEra::::get().unwrap_or(0); let history_depth = T::HistoryDepth::get(); let last_reward_era = current_era.saturating_sub(history_depth); @@ -877,6 +870,12 @@ pub mod pallet { .defensive_map_err(|_| Error::::BoundNotMet)?, ); ledger.mutate()?; + + // You're auto-bonded forever, here. We might improve this by only bonding when + // you actually validate/nominate and remove once you unbond __everything__. + ledger.bond()?; + >::insert(&stash, payee); + Ok(()) } @@ -1256,7 +1255,7 @@ pub mod pallet { pub fn set_controller(origin: OriginFor) -> DispatchResult { let stash = ensure_signed(origin)?; - // the bonded map and ledger are mutated directly as this logic is related to a + // the bonded map and ledger are mutated directly as this extrinsic is related to a // (temporary) passive migration. match StakingLedger::::get(&stash) { None => Err(Error::::NotStash.into()), From f1942b6601ac3663c70efeea668a63fc6b01ea8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 19 Jul 2023 09:43:00 +0200 Subject: [PATCH 10/47] Further refactors --- frame/staking/src/ledger.rs | 73 ++++++++++++++++++++----------- frame/staking/src/pallet/impls.rs | 31 +++++++------ frame/staking/src/pallet/mod.rs | 29 ++++++------ frame/staking/src/slashing.rs | 2 +- primitives/staking/src/lib.rs | 9 ++++ 5 files changed, 88 insertions(+), 56 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 15c0cb12a20e..5b3ffc6f2afc 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -5,7 +5,7 @@ use frame_support::{ }; use scale_info::TypeInfo; use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; -use sp_staking::{EraIndex, OnStakingUpdate}; +use sp_staking::{EraIndex, OnStakingUpdate, StakingAccount}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ID}; @@ -97,37 +97,15 @@ impl StakingLedger { } } - pub(crate) fn controller_of(stash: &T::AccountId) -> Option { - >::get(stash) - } - /// Returns the controller account of the staking ledger. /// /// Note: Fallback into querying the `Bonded` storage with the ledger stash if the controller /// is not set, which most likely means that self was fetched directly from `Ledger` instead of /// through the methods exposed in `StakingLedger`. If ledger does not exist, return `None`. pub(crate) fn controller(&self) -> Option { - self.controller.clone().or_else(|| Self::controller_of(&self.stash)) - } - - pub(crate) fn bond(&self) -> Result<(), Error> { - >::insert(&self.stash, &self.stash); - - Ok(()) - } - - pub(crate) fn get(stash: &T::AccountId) -> Option> { - if let Some(controller) = >::get(stash) { - match >::get(&controller).map(|mut ledger| { - ledger.controller = Some(controller.clone()); - ledger - }) { - Some(ledger) => Some(StakingLedgerStatus::Paired(ledger)), - None => Some(StakingLedgerStatus::BondedNotPaired), - } - } else { - None - } + self.controller + .clone() + .or_else(|| Self::paired_account(StakingAccount::Stash(self.stash.clone()))) } /// Inserts/updates a staking ledger account. @@ -143,6 +121,49 @@ impl StakingLedger { Ok(()) } + // TODO: add tests! + /// Returns the paired account, if any. + /// + /// A "pair" refers to the tuple (stash, controller). If the input is the + /// [`StakingAccount::Stash`], its pair account will be of type [`StakingAccount::Controller`] + /// and vice-versa. + /// + /// This method is meant to abstract from the runtime development the difference between stash + /// and controller. This will be deprecated once the controller is fully deprecated as well. + pub(crate) fn paired_account(account: StakingAccount) -> Option { + match account { + StakingAccount::Stash(stash) => >::get(stash), + StakingAccount::Controller(controller) => + >::get(&controller).map(|ledger| ledger.stash), + } + } + + pub(crate) fn bond(&self) -> Result<(), Error> { + >::insert(&self.stash, &self.stash); + + Ok(()) + } + + pub(crate) fn get(account: StakingAccount) -> Option> { + let controller = if let Some(controller) = match account { + StakingAccount::Stash(stash) => >::get(stash), + StakingAccount::Controller(controller) => Some(controller), + } { + controller + } else { + return None + }; + + match >::get(&controller).map(|mut ledger| { + ledger.controller = Some(controller.clone()); + ledger + }) { + Some(ledger) => Some(StakingLedgerStatus::Paired(ledger)), + None => Some(StakingLedgerStatus::BondedNotPaired), + } + } + + /// Removes all data related to a staking ledger and its bond. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { let controller = >::get(stash).ok_or(Error::::NotStash)?; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index f51215361649..600d4a73d657 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -40,7 +40,7 @@ use sp_runtime::{ use sp_staking::{ currency_to_vote::CurrencyToVote, offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, Stake, StakingInterface, + EraIndex, SessionIndex, Stake, StakingAccount, StakingInterface, }; use sp_std::prelude::*; @@ -66,16 +66,18 @@ use sp_runtime::TryRuntimeError; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { + /// Fetches the ledger associated with a stash account, if any. pub fn ledger(stash: &T::AccountId) -> Option> { - match StakingLedger::::get(stash) { + match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => None, Some(StakingLedgerStatus::BondedNotPaired) => None, Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger), } } + /// Fetches the controller bonded to a stash account, if any. pub fn bonded(stash: &T::AccountId) -> Option { - StakingLedger::::controller_of(&stash) + StakingLedger::::paired_account(StakingAccount::Stash(stash.clone())) } /// The total balance that can be slashed from a stash account as of right now. @@ -174,18 +176,14 @@ impl Pallet { .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let mut ledger = match StakingLedger::::get(&validator_stash) { - None => - Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController.into()), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), - }?; - - //let controller = Self::bonded(&validator_stash).ok_or_else(|| { - // Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) - //})?; - //let mut ledger = - // StakingLedger::::storage_get(&controller).ok_or(Error::::NotController)?; + let mut ledger = + match StakingLedger::::get(StakingAccount::Stash(validator_stash.clone())) { + None => + Err(Error::::NotStash + .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController.into()), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; ledger .claimed_rewards @@ -317,7 +315,8 @@ impl Pallet { ledger.total += amount; let r = T::Currency::deposit_into_existing(stash, amount).ok(); - // TODO: handle error here. + // calling `fn Self::ledger` ensures that the returned ledger exists in storage, + // qed. let _ = ledger.mutate(); r diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 92c2bb7a7c03..86fcfcf5985b 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -35,7 +35,7 @@ use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, }; -use sp_staking::{EraIndex, SessionIndex}; +use sp_staking::{EraIndex, SessionIndex, StakingAccount}; use sp_std::prelude::*; mod impls; @@ -837,7 +837,7 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - match StakingLedger::::get(&stash) { + match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Ok(()), Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::AlreadyBonded), Some(StakingLedgerStatus::Paired(_)) => Err(Error::::AlreadyPaired), @@ -901,7 +901,7 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - let mut ledger = match StakingLedger::::get(&stash) { + let mut ledger = match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Err(Error::::NotStash), Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), @@ -1134,10 +1134,11 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - // TODO: refactor get ledger from controller to StakingLedger. - // Maybe wrap the AccountId with input to StakingLedger::get with an enum that could be - // either a stash or controller. - let ledger = >::get(controller).ok_or(Error::::NotController)?; + let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { + None => Err(Error::::NotStash), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1203,10 +1204,12 @@ pub mod pallet { pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; - // TODO: refactor get ledger from controller to StakingLedger. - // Maybe wrap the AccountId with input to StakingLedger::get with an enum that could be - // either a stash or controller. - let ledger = >::get(controller).ok_or(Error::::NotController)?; + let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { + None => Err(Error::::NotStash), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; + Self::chill_stash(&ledger.stash); Ok(()) } @@ -1257,7 +1260,7 @@ pub mod pallet { // the bonded map and ledger are mutated directly as this extrinsic is related to a // (temporary) passive migration. - match StakingLedger::::get(&stash) { + match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Err(Error::::NotStash.into()), Some(StakingLedgerStatus::BondedNotPaired) => { // update bond only. @@ -1265,7 +1268,7 @@ pub mod pallet { Ok(()) }, Some(StakingLedgerStatus::Paired(ledger)) => { - let controller = ledger.controller().ok_or(Error::::NotController)?; // correct error to throw here? + let controller = ledger.controller().ok_or(Error::::NotController)?; // TODO: correct error to throw here? if controller == stash { // stash is already its own controller. return Err(Error::::AlreadyPaired.into()) diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 12be24824ecc..189db50add64 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -618,7 +618,7 @@ pub fn do_slash( *reward_payout = reward_payout.saturating_sub(missing); } - // TODO: handle error here + // calling `fn Pallet::ledger` ensures that the returned ledger exists in storage, qed. let _ = ledger.mutate(); // trigger the event diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 1621af164b37..4cd5a21ac9ae 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -37,6 +37,15 @@ pub type SessionIndex = u32; /// Counter for the number of eras that have passed. pub type EraIndex = u32; +/// Representation of a staking account, which may be a stash or controller account. +/// +/// Note: once the controller is completely deprecated, this enum can also be deprecated in favor of +/// the stash account. +pub enum StakingAccount { + Stash(AccountId), + Controller(AccountId), +} + /// Representation of the status of a staker. #[derive(RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone))] From f01d21400550a3687d03758f898c84f6beffc0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 20 Jul 2023 10:38:58 +0200 Subject: [PATCH 11/47] Refactors staking ledger API; Adds documentation --- frame/staking/src/ledger.rs | 72 ++++++++++++++++++------------- frame/staking/src/lib.rs | 7 ++- frame/staking/src/pallet/impls.rs | 62 ++++++++++++++------------ frame/staking/src/pallet/mod.rs | 23 +++++----- frame/staking/src/slashing.rs | 2 +- frame/staking/src/tests.rs | 1 + 6 files changed, 94 insertions(+), 73 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 5b3ffc6f2afc..3617d9216977 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -12,10 +12,8 @@ use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ /// The ledger of a (bonded) stash. /// -/// Note: All the reads and mutations to the `Ledger` storage type *MUST* be performed through the -/// methods exposed by this struct to ensure data and staking lock consistency, namely: -/// -/// TODO: improve/fix comments +/// Note: All the reads and mutations to the `Ledger` and `Bonded` storage items *MUST* be performed +/// through the methods exposed by this struct to ensure data and staking lock consistency. #[derive( PartialEqNoBound, EqNoBound, @@ -53,9 +51,12 @@ pub struct StakingLedger { controller: Option, } -// TODO: docs +/// Represents the status of a ledger. pub(crate) enum StakingLedgerStatus { + /// A bond exists but the staking ledger is not stored in the `Ledger` storage. BondedNotPaired, + /// The controller of the ledger is bonded and the staking ledger is initialized in storage. It + /// wraps the staking ledger itself. Paired(StakingLedger), } @@ -74,8 +75,8 @@ impl StakingLedger { /// Returns a new instance of a staking ledger. /// - /// The `Ledger` storage is not mutated. In order to do that [`fn storage_put`] must be called - /// on the returned staking ledger. + /// The `Ledger` storage is not mutated. In order to do that, [`update`] must be called on the + /// returned staking ledger. /// /// Note: as the controller accounts are being deprecated, the stash account is the same as the /// controller account. @@ -97,11 +98,28 @@ impl StakingLedger { } } - /// Returns the controller account of the staking ledger. + /// Returns the paired account, if any. /// - /// Note: Fallback into querying the `Bonded` storage with the ledger stash if the controller - /// is not set, which most likely means that self was fetched directly from `Ledger` instead of - /// through the methods exposed in `StakingLedger`. If ledger does not exist, return `None`. + /// A "pair" refers to the tuple (stash, controller). If the input is a + /// [`StakingAccount::Stash`] variant, its pair account will be of type + /// [`StakingAccount::Controller`] and vice-versa. + /// + /// This method is meant to abstract from the runtime development the difference between stash + /// and controller. This will be deprecated once the controller is fully deprecated as well. + pub(crate) fn paired_account(account: StakingAccount) -> Option { + match account { + StakingAccount::Stash(stash) => >::get(stash), + StakingAccount::Controller(controller) => + >::get(&controller).map(|ledger| ledger.stash), + } + } + + /// Returns the controller account of a staking ledger. + /// + /// Note: it will fallback into querying the `Bonded` storage with the ledger stash if the + /// controller is not set in `self`, which most likely means that self was fetched directly from + /// `Ledger` instead of through the methods exposed in `StakingLedger`. If the ledger does not + /// exist in storage, it returns `None`. pub(crate) fn controller(&self) -> Option { self.controller .clone() @@ -110,40 +128,32 @@ impl StakingLedger { /// Inserts/updates a staking ledger account. /// - /// The staking locks od the stash account are updated accordingly. + /// The staking locks of the stash account are updated accordingly. /// /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through /// this helper function. - pub(crate) fn mutate(&self) -> Result<(), Error> { + pub(crate) fn update(&self) -> Result<(), Error> { T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); Ledger::::insert(&self.controller().ok_or(Error::::NotController)?, &self); Ok(()) } - // TODO: add tests! - /// Returns the paired account, if any. + /// Helper to bond a new stash. /// - /// A "pair" refers to the tuple (stash, controller). If the input is the - /// [`StakingAccount::Stash`], its pair account will be of type [`StakingAccount::Controller`] - /// and vice-versa. - /// - /// This method is meant to abstract from the runtime development the difference between stash - /// and controller. This will be deprecated once the controller is fully deprecated as well. - pub(crate) fn paired_account(account: StakingAccount) -> Option { - match account { - StakingAccount::Stash(stash) => >::get(stash), - StakingAccount::Controller(controller) => - >::get(&controller).map(|ledger| ledger.stash), - } - } - + /// Note: as the controller accounts are being deprecated, the stash account is the same as the + /// controller account. pub(crate) fn bond(&self) -> Result<(), Error> { >::insert(&self.stash, &self.stash); Ok(()) } + /// Returns a staking ledger wrapped in a [`StakingLedgerStatus`], if it exists. + /// + /// This getter can be called with either a controller or stash account, provided that the + /// account is properly wrapped in the respective [`StakingAccount`] variant. This is meant to + /// abstract the concept of controller/stash accounts to the caller. pub(crate) fn get(account: StakingAccount) -> Option> { let controller = if let Some(controller) = match account { StakingAccount::Stash(stash) => >::get(stash), @@ -163,8 +173,8 @@ impl StakingLedger { } } - - /// Removes all data related to a staking ledger and its bond. + /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] + /// storage items and updates the stash staking lock. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { let controller = >::get(stash).ok_or(Error::::NotStash)?; diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index c5f884238ffd..9c33514f9f76 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -316,7 +316,7 @@ use sp_runtime::{ pub use sp_staking::StakerStatus; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - EraIndex, SessionIndex, + EraIndex, SessionIndex, StakingAccount, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; @@ -645,7 +645,10 @@ pub struct StashOf(sp_std::marker::PhantomData); impl Convert> for StashOf { fn convert(controller: T::AccountId) -> Option { - Pallet::::ledger(&controller).map(|l| l.stash) + match StakingLedger::::get(StakingAccount::Controller(controller)) { + Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger.stash), + _ => None, + } } } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 600d4a73d657..16244866cc6c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -66,9 +66,9 @@ use sp_runtime::TryRuntimeError; const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { - /// Fetches the ledger associated with a stash account, if any. - pub fn ledger(stash: &T::AccountId) -> Option> { - match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { + /// Fetches the ledger associated with a controller or stash account, if any. + pub fn ledger(controller: &T::AccountId) -> Option> { + match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { None => None, Some(StakingLedgerStatus::BondedNotPaired) => None, Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger), @@ -83,6 +83,7 @@ impl Pallet { /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. + // TODO_ Self::bonded(stash) .and_then(|s| Self::ledger(&s)) .map(|l| l.active) @@ -120,6 +121,7 @@ impl Pallet { controller: &T::AccountId, num_slashing_spans: u32, ) -> Result { + // TODO_: stash, not controller let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { @@ -131,12 +133,12 @@ impl Pallet { // This account must have called `unbond()` with some value that caused the active // portion to fall below existential deposit + will have no more unlocking chunks // left. We can now safely remove all staking-related information. - Self::kill_stash(&stash, num_slashing_spans)?; + Self::kill_stash(&ledger.stash, num_slashing_spans)?; T::WeightInfo::withdraw_unbonded_kill(num_slashing_spans) } else { // This was the consequence of a partial unbond. just update the ledger and move on. - ledger.mutate()?; + ledger.update()?; // This is only an update, so we use less overall weight. T::WeightInfo::withdraw_unbonded_update(num_slashing_spans) @@ -206,7 +208,7 @@ impl Pallet { // Input data seems good, no errors allowed after this point - ledger.mutate()?; + ledger.update()?; // Get Era reward points. It has TOTAL and INDIVIDUAL // Find the fraction of the era reward that belongs to the validator @@ -270,6 +272,7 @@ impl Pallet { // Lets now calculate how this is split to the nominators. // Reward only the clipped exposures. Note this is not necessarily sorted. + for nominator in exposure.others.iter() { let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total); @@ -308,19 +311,17 @@ impl Pallet { RewardDestination::Controller => Self::bonded(stash) .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), - RewardDestination::Staked => Self::bonded(stash) - .and_then(|c| Self::ledger(&c).map(|l| (c, l))) - .and_then(|(_, mut ledger)| { - ledger.active += amount; - ledger.total += amount; - let r = T::Currency::deposit_into_existing(stash, amount).ok(); - - // calling `fn Self::ledger` ensures that the returned ledger exists in storage, - // qed. - let _ = ledger.mutate(); - - r - }), + RewardDestination::Staked => Self::ledger(&stash).and_then(|mut ledger| { + ledger.active += amount; + ledger.total += amount; + let r = T::Currency::deposit_into_existing(stash, amount).ok(); + + // calling `fn Self::ledger` ensures that the returned ledger exists in storage, + // qed. + let _ = ledger.update(); + + r + }), RewardDestination::Account(dest_account) => Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, @@ -672,17 +673,17 @@ impl Pallet { /// - after a `withdraw_unbonded()` call that frees all of a stash's bonded balance. /// - through `reap_stash()` if the balance has fallen to zero (through slashing). pub(crate) fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { - slashing::clear_stash_metadata::(stash, num_slashing_spans)?; + slashing::clear_stash_metadata::(&stash, num_slashing_spans)?; // removes controller from `Bonded` and staking ledger from `Ledger`. - StakingLedger::::kill(stash)?; - >::remove(stash); - >::remove(stash); + StakingLedger::::kill(&stash)?; + >::remove(&stash); + >::remove(&stash); - Self::do_remove_validator(stash); - Self::do_remove_nominator(stash); + Self::do_remove_validator(&stash); + Self::do_remove_nominator(&stash); - frame_system::Pallet::::dec_consumers(stash); + frame_system::Pallet::::dec_consumers(&stash); Ok(()) } @@ -1021,7 +1022,6 @@ impl ElectionDataProvider for Pallet { // This can never fail -- if `maybe_max_len` is `Some(_)` we handle it. let voters = Self::get_npos_voters(maybe_max_len); debug_assert!(maybe_max_len.map_or(true, |max| voters.len() <= max)); - Ok(voters) } @@ -1840,7 +1840,13 @@ impl Pallet { fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = Self::ledger(&ctrl).ok_or("Not a controller.")?; + let ledger = match StakingLedger::::get(StakingAccount::Controller(ctrl.clone())) + .ok_or("Not a controller")? + { + StakingLedgerStatus::Paired(l) => Ok(l), + _ => Err("Not a controller"), + }?; + let real_total: BalanceOf = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); ensure!(real_total == ledger.total, "ledger.total corrupt"); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 86fcfcf5985b..b4aee78dd28d 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -839,8 +839,8 @@ pub mod pallet { match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Ok(()), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::AlreadyBonded), - Some(StakingLedgerStatus::Paired(_)) => Err(Error::::AlreadyPaired), + Some(StakingLedgerStatus::BondedNotPaired) | + Some(StakingLedgerStatus::Paired(_)) => Err(Error::::AlreadyBonded), }?; // Reject a bond which is considered to be _dust_. @@ -869,7 +869,7 @@ pub mod pallet { // satisfied. .defensive_map_err(|_| Error::::BoundNotMet)?, ); - ledger.mutate()?; + ledger.update()?; // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. @@ -922,7 +922,7 @@ pub mod pallet { ); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.mutate()?; + ledger.update()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { let _ = @@ -1023,7 +1023,7 @@ pub mod pallet { .map_err(|_| Error::::NoMoreChunks)?; }; // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.mutate()?; + ledger.update()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&ledger.stash) { @@ -1134,11 +1134,12 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { - None => Err(Error::::NotStash), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), - }?; + let ledger = + match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { + None => Err(Error::::NotStash), + Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + }?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1532,7 +1533,7 @@ pub mod pallet { }); // NOTE: ledger must be updated prior to calling `Self::weight_of`. - ledger.mutate()?; + ledger.update()?; if T::VoterList::contains(&ledger.stash) { let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) .defensive(); diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 189db50add64..b0e1538dbe22 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -619,7 +619,7 @@ pub fn do_slash( } // calling `fn Pallet::ledger` ensures that the returned ledger exists in storage, qed. - let _ = ledger.mutate(); + let _ = ledger.update(); // trigger the event >::deposit_event(super::Event::::Slashed { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 52eb3ad01825..f4e1ccb282cf 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -4182,6 +4182,7 @@ fn payout_creates_controller() { false, ) .unwrap(); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(controller), vec![11])); // kill controller From b8a785dde96182459c1609724a1ede445e7ef10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 20 Jul 2023 11:10:33 +0200 Subject: [PATCH 12/47] Pallet::ledger now accepts a StakingAccount which can be a controller or stash account --- frame/staking/src/pallet/impls.rs | 47 +++++----- frame/staking/src/pallet/mod.rs | 25 +++--- frame/staking/src/slashing.rs | 9 +- frame/staking/src/tests.rs | 138 +++++++++++++++--------------- primitives/staking/src/lib.rs | 6 ++ 5 files changed, 114 insertions(+), 111 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 16244866cc6c..98ebc2fefdea 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -40,7 +40,9 @@ use sp_runtime::{ use sp_staking::{ currency_to_vote::CurrencyToVote, offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, Stake, StakingAccount, StakingInterface, + EraIndex, SessionIndex, Stake, + StakingAccount::{self, Controller, Stash}, + StakingInterface, }; use sp_std::prelude::*; @@ -67,8 +69,8 @@ const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { /// Fetches the ledger associated with a controller or stash account, if any. - pub fn ledger(controller: &T::AccountId) -> Option> { - match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { + pub fn ledger(account: StakingAccount) -> Option> { + match StakingLedger::::get(account) { None => None, Some(StakingLedgerStatus::BondedNotPaired) => None, Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger), @@ -77,17 +79,13 @@ impl Pallet { /// Fetches the controller bonded to a stash account, if any. pub fn bonded(stash: &T::AccountId) -> Option { - StakingLedger::::paired_account(StakingAccount::Stash(stash.clone())) + StakingLedger::::paired_account(Stash(stash.clone())) } /// The total balance that can be slashed from a stash account as of right now. pub fn slashable_balance_of(stash: &T::AccountId) -> BalanceOf { // Weight note: consider making the stake accessible through stash. - // TODO_ - Self::bonded(stash) - .and_then(|s| Self::ledger(&s)) - .map(|l| l.active) - .unwrap_or_default() + Self::ledger(Stash(stash.clone())).map(|l| l.active).unwrap_or_default() } /// Internal impl of [`Self::slashable_balance_of`] that returns [`VoteWeight`]. @@ -122,7 +120,8 @@ impl Pallet { num_slashing_spans: u32, ) -> Result { // TODO_: stash, not controller - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + Self::ledger(Controller(controller.clone())).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { ledger = ledger.consolidate_unlocked(current_era) @@ -311,17 +310,18 @@ impl Pallet { RewardDestination::Controller => Self::bonded(stash) .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), - RewardDestination::Staked => Self::ledger(&stash).and_then(|mut ledger| { - ledger.active += amount; - ledger.total += amount; - let r = T::Currency::deposit_into_existing(stash, amount).ok(); - - // calling `fn Self::ledger` ensures that the returned ledger exists in storage, - // qed. - let _ = ledger.update(); - - r - }), + RewardDestination::Staked => + Self::ledger(Stash(stash.clone())).and_then(|mut ledger| { + ledger.active += amount; + ledger.total += amount; + let r = T::Currency::deposit_into_existing(stash, amount).ok(); + + // calling `fn Self::ledger` ensures that the returned ledger exists in storage, + // qed. + let _ = ledger.update(); + + r + }), RewardDestination::Account(dest_account) => Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, @@ -1610,7 +1610,7 @@ impl StakingInterface for Pallet { } fn stash_by_ctrl(controller: &Self::AccountId) -> Result { - Self::ledger(controller) + Self::ledger(Controller(controller.clone())) .map(|l| l.stash) .ok_or(Error::::NotController.into()) } @@ -1630,8 +1630,7 @@ impl StakingInterface for Pallet { } fn stake(who: &Self::AccountId) -> Result>, DispatchError> { - Self::bonded(who) - .and_then(|c| Self::ledger(&c)) + Self::ledger(Stash(who.clone())) .map(|l| Stake { total: l.total, active: l.active }) .ok_or(Error::::NotStash.into()) } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index b4aee78dd28d..9a042ef31a46 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -35,7 +35,10 @@ use sp_runtime::{ traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero}, ArithmeticError, Perbill, Percent, }; -use sp_staking::{EraIndex, SessionIndex, StakingAccount}; +use sp_staking::{ + EraIndex, SessionIndex, + StakingAccount::{self, Controller, Stash}, +}; use sp_std::prelude::*; mod impls; @@ -962,7 +965,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let unlocking = Self::ledger(&controller) + let unlocking = Self::ledger(Controller(controller.clone())) .map(|l| l.unlocking.len()) .ok_or(Error::::NotController)?; @@ -980,7 +983,8 @@ pub mod pallet { // we need to fetch the ledger again because it may have been mutated in the call // to `Self::do_withdraw_unbonded` above. - let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let mut ledger = + Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; let mut value = value.min(ledger.active); ensure!( @@ -1088,7 +1092,7 @@ pub mod pallet { pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1234,7 +1238,7 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; let stash = &ledger.stash; >::insert(stash, payee); Ok(()) @@ -1519,7 +1523,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; @@ -1573,9 +1577,7 @@ pub mod pallet { let ed = T::Currency::minimum_balance(); let reapable = T::Currency::total_balance(&stash) < ed || - Self::ledger(&Self::bonded(&stash.clone()).ok_or(Error::::NotStash)?) - .map(|l| l.total) - .unwrap_or_default() < ed; + Self::ledger(Stash(stash.clone())).map(|l| l.total).unwrap_or_default() < ed; ensure!(reapable, Error::::FundedTarget); // Remove all staking-related information and lock. @@ -1599,7 +1601,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; let stash = &ledger.stash; for nom_stash in who @@ -1708,7 +1710,8 @@ pub mod pallet { pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. let caller = ensure_signed(origin)?; - let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; + let ledger = + Self::ledger(Controller(controller.clone())).ok_or(Error::::NotController)?; let stash = ledger.stash; // In order for one user to chill another user, the following conditions must be met: diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index b0e1538dbe22..c145eae14c06 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -57,7 +57,7 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Defensive, Get, Imbalance, OnUnbalanced}, + traits::{Currency, Get, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -597,12 +597,7 @@ pub fn do_slash( slashed_imbalance: &mut NegativeImbalanceOf, slash_era: EraIndex, ) { - let controller = match >::bonded(stash).defensive() { - None => return, - Some(c) => c, - }; - - let mut ledger = match Pallet::::ledger(&controller) { + let mut ledger = match Pallet::::ledger(sp_staking::StakingAccount::Stash(stash.clone())) { Some(ledger) => ledger, None => return, // nothing to do. }; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index f4e1ccb282cf..9bb012b4578b 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -170,7 +170,7 @@ fn basic_setup_works() { } ); // Account 1 does not control any stash - assert_eq!(Staking::ledger(&1), None); + assert_eq!(Staking::ledger(1.into()), None); // ValidatorPrefs are default assert_eq_uvec!( @@ -183,7 +183,7 @@ fn basic_setup_works() { ); assert_eq!( - Staking::ledger(&101).unwrap(), + Staking::ledger(101.into()).unwrap(), StakingLedgerInspect { stash: 101, total: 500, @@ -458,7 +458,7 @@ fn staking_should_work() { // Note: the stashed value of 4 is still lock assert_eq!( - Staking::ledger(&3).unwrap(), + Staking::ledger(3.into()).unwrap(), StakingLedgerInspect { stash: 3, total: 1500, @@ -715,9 +715,9 @@ fn nominators_also_get_slashed_pro_rata() { assert_eq!(initial_exposure.others.first().unwrap().who, 101); // staked values; - let nominator_stake = Staking::ledger(&101).unwrap().active; + let nominator_stake = Staking::ledger(101.into()).unwrap().active; let nominator_balance = balances(&101).0; - let validator_stake = Staking::ledger(&11).unwrap().active; + let validator_stake = Staking::ledger(11.into()).unwrap().active; let validator_balance = balances(&11).0; let exposed_stake = initial_exposure.total; let exposed_validator = initial_exposure.own; @@ -730,8 +730,8 @@ fn nominators_also_get_slashed_pro_rata() { ); // both stakes must have been decreased. - assert!(Staking::ledger(&101).unwrap().active < nominator_stake); - assert!(Staking::ledger(&11).unwrap().active < validator_stake); + assert!(Staking::ledger(101.into()).unwrap().active < nominator_stake); + assert!(Staking::ledger(11.into()).unwrap().active < validator_stake); let slash_amount = slash_percent * exposed_stake; let validator_share = @@ -744,8 +744,8 @@ fn nominators_also_get_slashed_pro_rata() { assert!(nominator_share > 0); // both stakes must have been decreased pro-rata. - assert_eq!(Staking::ledger(&101).unwrap().active, nominator_stake - nominator_share); - assert_eq!(Staking::ledger(&11).unwrap().active, validator_stake - validator_share); + assert_eq!(Staking::ledger(101.into()).unwrap().active, nominator_stake - nominator_share); + assert_eq!(Staking::ledger(11.into()).unwrap().active, validator_stake - validator_share); assert_eq!( balances(&101).0, // free balance nominator_balance - nominator_share, @@ -1045,7 +1045,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000); // Check how much is at stake assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1068,7 +1068,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); // Check that amount at stake increased accordingly assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, @@ -1094,7 +1094,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, @@ -1123,7 +1123,7 @@ fn reward_destination_works() { assert_eq!(Balances::free_balance(11), 23150 + total_payout_2); // Check that amount at stake is NOT increased assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + total_payout_0, @@ -1183,7 +1183,7 @@ fn bond_extra_works() { assert_eq!(Staking::bonded(&11), Some(11)); // Check how much is at stake assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1200,7 +1200,7 @@ fn bond_extra_works() { assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), 100)); // There should be 100 more `total` and `active` in the ledger assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1214,7 +1214,7 @@ fn bond_extra_works() { assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(11), Balance::max_value())); // The full amount of the funds should now be in the total and active assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000000, @@ -1252,7 +1252,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // Initial state of 11 assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1270,7 +1270,7 @@ fn bond_extra_and_withdraw_unbonded_works() { Staking::bond_extra(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1291,7 +1291,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // ledger should be the same. assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1309,7 +1309,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 1000).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1322,7 +1322,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // Attempting to free the balances now will fail. 2 eras need to pass. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1338,7 +1338,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // nothing yet assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000 + 100, @@ -1354,7 +1354,7 @@ fn bond_extra_and_withdraw_unbonded_works() { assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); // Now the value is free and the staking ledger is updated. assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 100, @@ -1388,7 +1388,7 @@ fn many_unbond_calls_should_work() { // `BondingDuration` == 3). assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1)); assert_eq!( - Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), + Staking::ledger(11.into()).map(|l| l.unlocking.len()).unwrap(), <::MaxUnlockingChunks as Get>::get() as usize ); @@ -1403,7 +1403,7 @@ fn many_unbond_calls_should_work() { // only slots within last `BondingDuration` are filled. assert_eq!( - Staking::ledger(&11).map(|l| l.unlocking.len()).unwrap(), + Staking::ledger(11.into()).map(|l| l.unlocking.len()).unwrap(), <::BondingDuration>::get() as usize ); }) @@ -1457,7 +1457,7 @@ fn rebond_works() { // Initial state of 11 assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1476,7 +1476,7 @@ fn rebond_works() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1489,7 +1489,7 @@ fn rebond_works() { // Re-bond all the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1502,7 +1502,7 @@ fn rebond_works() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1515,7 +1515,7 @@ fn rebond_works() { // Re-bond part of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1528,7 +1528,7 @@ fn rebond_works() { // Re-bond the remainder of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1543,7 +1543,7 @@ fn rebond_works() { Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1556,7 +1556,7 @@ fn rebond_works() { // Re-bond part of the funds unbonded. Staking::rebond(RuntimeOrigin::signed(11), 500).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1583,7 +1583,7 @@ fn rebond_is_fifo() { // Initial state of 10 assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1598,7 +1598,7 @@ fn rebond_is_fifo() { // Unbond some of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1613,7 +1613,7 @@ fn rebond_is_fifo() { // Unbond more of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 300).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1631,7 +1631,7 @@ fn rebond_is_fifo() { // Unbond yet more of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 200).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1648,7 +1648,7 @@ fn rebond_is_fifo() { // Re-bond half of the unbonding funds. Staking::rebond(RuntimeOrigin::signed(11), 400).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1680,7 +1680,7 @@ fn rebond_emits_right_value_in_event() { // Unbond almost all of the funds in stash. Staking::unbond(RuntimeOrigin::signed(11), 900).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1693,7 +1693,7 @@ fn rebond_emits_right_value_in_event() { // Re-bond less than the total Staking::rebond(RuntimeOrigin::signed(11), 100).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1708,7 +1708,7 @@ fn rebond_emits_right_value_in_event() { // Re-bond way more than available Staking::rebond(RuntimeOrigin::signed(11), 100_000).unwrap(); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -1929,7 +1929,7 @@ fn bond_with_no_staked_value() { // unbonding even 1 will cause all to be unbonded. assert_ok!(Staking::unbond(RuntimeOrigin::signed(1), 1)); assert_eq!( - Staking::ledger(&1).unwrap(), + Staking::ledger(1.into()).unwrap(), StakingLedgerInspect { stash: 1, active: 0, @@ -1944,14 +1944,14 @@ fn bond_with_no_staked_value() { // not yet removed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(&1).is_some()); + assert!(Staking::ledger(1.into()).is_some()); assert_eq!(Balances::locks(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(&1).is_none()); + assert!(Staking::ledger(1.into()).is_none()); assert_eq!(Balances::locks(&1).len(), 0); }); } @@ -2036,7 +2036,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(&v).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(v.into()).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2088,7 +2088,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { // ensure all have equal stake. assert_eq!( >::iter() - .map(|(v, _)| (v, Staking::ledger(&v).unwrap().total)) + .map(|(v, _)| (v, Staking::ledger(v.into()).unwrap().total)) .collect::>(), vec![(31, 1000), (21, 1000), (11, 1000)], ); @@ -2974,7 +2974,7 @@ fn retroactive_deferred_slashes_one_before() { mock::start_active_era(4); - assert_eq!(Staking::ledger(&11).unwrap().total, 1000); + assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000); // slash happens after the next line. mock::start_active_era(5); @@ -2989,9 +2989,9 @@ fn retroactive_deferred_slashes_one_before() { )); // their ledger has already been slashed. - assert_eq!(Staking::ledger(&11).unwrap().total, 900); + assert_eq!(Staking::ledger(11.into()).unwrap().total, 900); assert_ok!(Staking::unbond(RuntimeOrigin::signed(11), 1000)); - assert_eq!(Staking::ledger(&11).unwrap().total, 900); + assert_eq!(Staking::ledger(11.into()).unwrap().total, 900); }) } @@ -3764,7 +3764,7 @@ fn test_payout_stakers() { // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -3795,7 +3795,7 @@ fn test_payout_stakers() { // We track rewards in `claimed_rewards` vec assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -3828,7 +3828,7 @@ fn test_payout_stakers() { expected_last_reward_era )); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -3843,7 +3843,7 @@ fn test_payout_stakers() { assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 23)); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 42)); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -4055,7 +4055,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { // Era = None bond_validator(9, 1000); assert_eq!( - Staking::ledger(&9).unwrap(), + Staking::ledger(9.into()).unwrap(), StakingLedgerInspect { stash: 9, total: 1000, @@ -4067,7 +4067,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { mock::start_active_era(5); bond_validator(11, 1000); assert_eq!( - Staking::ledger(&11).unwrap(), + Staking::ledger(11.into()).unwrap(), StakingLedgerInspect { stash: 11, total: 1000, @@ -4083,7 +4083,7 @@ fn bond_during_era_correctly_populates_claimed_rewards() { mock::start_active_era(current_era); bond_validator(13, 1000); assert_eq!( - Staking::ledger(&13).unwrap(), + Staking::ledger(13.into()).unwrap(), StakingLedgerInspect { stash: 13, total: 1000, @@ -4339,7 +4339,7 @@ fn cannot_rebond_to_lower_than_ed() { .build_and_execute(|| { // initial stuff. assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: 11 * 1000, @@ -4353,7 +4353,7 @@ fn cannot_rebond_to_lower_than_ed() { assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: 11 * 1000, @@ -4379,7 +4379,7 @@ fn cannot_bond_extra_to_lower_than_ed() { .build_and_execute(|| { // initial stuff. assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: 11 * 1000, @@ -4393,7 +4393,7 @@ fn cannot_bond_extra_to_lower_than_ed() { assert_ok!(Staking::chill(RuntimeOrigin::signed(21))); assert_ok!(Staking::unbond(RuntimeOrigin::signed(21), 11 * 1000)); assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: 11 * 1000, @@ -4420,7 +4420,7 @@ fn do_not_die_when_active_is_ed() { .build_and_execute(|| { // given assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: 1000 * ed, @@ -4437,7 +4437,7 @@ fn do_not_die_when_active_is_ed() { // then assert_eq!( - Staking::ledger(&21).unwrap(), + Staking::ledger(21.into()).unwrap(), StakingLedgerInspect { stash: 21, total: ed, @@ -5489,7 +5489,7 @@ fn pre_bonding_era_cannot_be_claimed() { let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&3).unwrap(), + Staking::ledger(3.into()).unwrap(), StakingLedgerInspect { stash: 3, total: 1500, @@ -5517,7 +5517,7 @@ fn pre_bonding_era_cannot_be_claimed() { // decoding will fail now since Staking Ledger is in corrupt state HistoryDepth::set(history_depth - 1); - assert_eq!(Staking::ledger(&4), None); + assert_eq!(Staking::ledger(4.into()), None); // make sure stakers still cannot claim rewards that they are not meant to assert_noop!( @@ -5555,7 +5555,7 @@ fn reducing_history_depth_abrupt() { let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&3).unwrap(), + Staking::ledger(3.into()).unwrap(), StakingLedgerInspect { stash: 3, total: 1500, @@ -5594,7 +5594,7 @@ fn reducing_history_depth_abrupt() { let claimed_rewards: BoundedVec<_, _> = (start_reward_era..=last_reward_era).collect::>().try_into().unwrap(); assert_eq!( - Staking::ledger(&5).unwrap(), + Staking::ledger(5.into()).unwrap(), StakingLedgerInspect { stash: 5, total: 1200, @@ -5618,7 +5618,7 @@ fn reducing_max_unlocking_chunks_abrupt() { MaxUnlockingChunks::set(2); start_active_era(10); assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 300, RewardDestination::Staked)); - assert!(matches!(Staking::ledger(&3), Some(_))); + assert!(matches!(Staking::ledger(3.into()), Some(_))); // when staker unbonds assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20)); @@ -5627,7 +5627,7 @@ fn reducing_max_unlocking_chunks_abrupt() { // => 10 + 3 = 13 let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; - assert!(matches!(Staking::ledger(&3), + assert!(matches!(Staking::ledger(3.into()), Some(StakingLedger { unlocking, .. @@ -5639,7 +5639,7 @@ fn reducing_max_unlocking_chunks_abrupt() { // then another unlock chunk is added let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; - assert!(matches!(Staking::ledger(&3), + assert!(matches!(Staking::ledger(3.into()), Some(StakingLedger { unlocking, .. diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 4cd5a21ac9ae..fb20887caf8a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -46,6 +46,12 @@ pub enum StakingAccount { Controller(AccountId), } +impl From for StakingAccount { + fn from(account: AccountId) -> Self { + StakingAccount::Stash(account) + } +} + /// Representation of the status of a staker. #[derive(RuntimeDebug, TypeInfo)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone))] From be0c5829606fe9c1b2a9f82ddd3a20e07a4017a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 20 Jul 2023 12:20:16 +0200 Subject: [PATCH 13/47] nits --- frame/staking/src/pallet/impls.rs | 1 - frame/staking/src/pallet/mod.rs | 12 ++++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 98ebc2fefdea..d9c774e1516e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -119,7 +119,6 @@ impl Pallet { controller: &T::AccountId, num_slashing_spans: u32, ) -> Result { - // TODO_: stash, not controller let mut ledger = Self::ledger(Controller(controller.clone())).ok_or(Error::::NotController)?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 9a042ef31a46..ea42af8289d6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -910,9 +910,6 @@ pub mod pallet { Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), }?; - //let controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; - //let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - let stash_balance = T::Currency::free_balance(&stash); if let Some(extra) = stash_balance.checked_sub(&ledger.total) { let extra = extra.min(max_additional); @@ -1140,8 +1137,8 @@ pub mod pallet { let ledger = match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { - None => Err(Error::::NotStash), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + None | Some(StakingLedgerStatus::BondedNotPaired) => + Err(Error::::NotController), Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), }?; @@ -1210,8 +1207,7 @@ pub mod pallet { let controller = ensure_signed(origin)?; let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { - None => Err(Error::::NotStash), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), + None | Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), }?; @@ -1273,7 +1269,7 @@ pub mod pallet { Ok(()) }, Some(StakingLedgerStatus::Paired(ledger)) => { - let controller = ledger.controller().ok_or(Error::::NotController)?; // TODO: correct error to throw here? + let controller = ledger.controller().ok_or(Error::::NotController)?; if controller == stash { // stash is already its own controller. return Err(Error::::AlreadyPaired.into()) From cfc4f2b51370ba6eefcfcd3176333800534105e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Wed, 26 Jul 2023 09:51:02 +0200 Subject: [PATCH 14/47] Simplifies logic and removes the StakingLedgerStatus; Adds ledger tests --- frame/staking/src/ledger.rs | 71 +++++++++++----------- frame/staking/src/lib.rs | 4 +- frame/staking/src/mock.rs | 10 ++++ frame/staking/src/pallet/impls.rs | 38 ++++++------ frame/staking/src/pallet/mod.rs | 26 +++------ frame/staking/src/tests.rs | 97 +++++++++++++++++++++++++++++++ primitives/staking/src/lib.rs | 1 + 7 files changed, 173 insertions(+), 74 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 3617d9216977..c8a4418af669 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -51,17 +51,8 @@ pub struct StakingLedger { controller: Option, } -/// Represents the status of a ledger. -pub(crate) enum StakingLedgerStatus { - /// A bond exists but the staking ledger is not stored in the `Ledger` storage. - BondedNotPaired, - /// The controller of the ledger is bonded and the staking ledger is initialized in storage. It - /// wraps the staking ledger itself. - Paired(StakingLedger), -} - impl StakingLedger { - #[cfg(feature = "runtime-benchmarks")] + #[cfg(any(test, runtime_benchmarks))] pub fn default_from(stash: T::AccountId) -> Self { Self { stash, @@ -114,6 +105,42 @@ impl StakingLedger { } } + /// Returns whether a given account is bonded. + pub(crate) fn is_bonded(account: StakingAccount) -> bool { + match account { + StakingAccount::Stash(stash) => >::get(stash).is_some(), + StakingAccount::Controller(controller) => >::get(controller).is_some(), + } + } + + /// Returns a staking ledger, if it is bonded and it exists. + /// + /// This getter can be called with either a controller or stash account, provided that the + /// account is properly wrapped in the respective [`StakingAccount`] variant. This is meant to + /// abstract the concept of controller/stash accounts to the caller. + pub(crate) fn get(account: StakingAccount) -> Option> { + let controller = if let Some(controller) = match account { + StakingAccount::Stash(stash) => >::get(stash), + StakingAccount::Controller(controller) => Some(controller), + } { + controller + } else { + return None + }; + + match >::get(&controller).map(|mut ledger| { + ledger.controller = Some(controller.clone()); + ledger + }) { + Some(ledger) => Some(ledger), + None => { + // this should not happen. + log!(debug, "staking account is bonded but ledger does not exist, unexpected."); + None + }, + } + } + /// Returns the controller account of a staking ledger. /// /// Note: it will fallback into querying the `Bonded` storage with the ledger stash if the @@ -149,30 +176,6 @@ impl StakingLedger { Ok(()) } - /// Returns a staking ledger wrapped in a [`StakingLedgerStatus`], if it exists. - /// - /// This getter can be called with either a controller or stash account, provided that the - /// account is properly wrapped in the respective [`StakingAccount`] variant. This is meant to - /// abstract the concept of controller/stash accounts to the caller. - pub(crate) fn get(account: StakingAccount) -> Option> { - let controller = if let Some(controller) = match account { - StakingAccount::Stash(stash) => >::get(stash), - StakingAccount::Controller(controller) => Some(controller), - } { - controller - } else { - return None - }; - - match >::get(&controller).map(|mut ledger| { - ledger.controller = Some(controller.clone()); - ledger - }) { - Some(ledger) => Some(StakingLedgerStatus::Paired(ledger)), - None => Some(StakingLedgerStatus::BondedNotPaired), - } - } - /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 9c33514f9f76..aab8167bede6 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -306,7 +306,7 @@ use frame_support::{ weights::Weight, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; -use ledger::{StakingLedger, StakingLedgerStatus}; +use ledger::StakingLedger; use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, @@ -646,7 +646,7 @@ pub struct StashOf(sp_std::marker::PhantomData); impl Convert> for StashOf { fn convert(controller: T::AccountId) -> Option { match StakingLedger::::get(StakingAccount::Controller(controller)) { - Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger.stash), + Some(ledger) => Some(ledger.stash), _ => None, } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 4592ead3824f..d3acf16d96a9 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -763,6 +763,16 @@ pub(crate) fn make_all_reward_payment(era: EraIndex) { } } +pub(crate) fn bond_controller_stash(controller: AccountId, stash: AccountId) -> Result<(), String> { + >::get(&stash).map_or(Ok(()), |_| Err("stash already bonded"))?; + >::get(&controller).map_or(Ok(()), |_| Err("controller already bonded"))?; + + >::insert(stash, controller); + >::insert(controller, StakingLedger::::default_from(stash)); + + Ok(()) +} + #[macro_export] macro_rules! assert_session_era { ($session:expr, $era:expr) => { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index d9c774e1516e..21b8ffcee445 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -47,9 +47,9 @@ use sp_staking::{ use sp_std::prelude::*; use crate::{ - ledger::StakingLedgerStatus, log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, - EraPayout, Exposure, ExposureOf, Forcing, IndividualExposure, MaxWinnersOf, Nominations, - PositiveImbalanceOf, RewardDestination, SessionInterface, StakingLedger, ValidatorPrefs, + log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraPayout, Exposure, ExposureOf, + Forcing, IndividualExposure, MaxWinnersOf, Nominations, PositiveImbalanceOf, RewardDestination, + SessionInterface, StakingLedger, ValidatorPrefs, }; use super::pallet::*; @@ -70,10 +70,10 @@ const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { /// Fetches the ledger associated with a controller or stash account, if any. pub fn ledger(account: StakingAccount) -> Option> { - match StakingLedger::::get(account) { - None => None, - Some(StakingLedgerStatus::BondedNotPaired) => None, - Some(StakingLedgerStatus::Paired(ledger)) => Some(ledger), + if let Some(ledger) = StakingLedger::::get(account) { + Some(ledger) + } else { + None } } @@ -176,14 +176,14 @@ impl Pallet { .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; - let mut ledger = - match StakingLedger::::get(StakingAccount::Stash(validator_stash.clone())) { - None => - Err(Error::::NotStash - .with_weight(T::WeightInfo::payout_stakers_alive_staked(0))), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController.into()), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), - }?; + let account = StakingAccount::Stash(validator_stash.clone()); + let mut ledger = StakingLedger::::get(account.clone()).ok_or_else(|| { + if StakingLedger::::is_bonded(account) { + Error::::NotController.into() + } else { + Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) + } + })?; ledger .claimed_rewards @@ -1838,12 +1838,8 @@ impl Pallet { fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = match StakingLedger::::get(StakingAccount::Controller(ctrl.clone())) - .ok_or("Not a controller")? - { - StakingLedgerStatus::Paired(l) => Ok(l), - _ => Err("Not a controller"), - }?; + let ledger = StakingLedger::::get(StakingAccount::Controller(ctrl.clone())) + .ok_or("Not a controller")?; let real_total: BalanceOf = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index ea42af8289d6..e9200c30f8a6 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -48,8 +48,8 @@ pub use impls::*; use crate::{ slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, - RewardDestination, SessionInterface, StakingLedger, StakingLedgerStatus, UnappliedSlash, - UnlockChunk, ValidatorPrefs, + RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, + ValidatorPrefs, }; // The speculative number of spans are used as an input of the weight annotation of @@ -842,8 +842,7 @@ pub mod pallet { match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Ok(()), - Some(StakingLedgerStatus::BondedNotPaired) | - Some(StakingLedgerStatus::Paired(_)) => Err(Error::::AlreadyBonded), + Some(_) => Err(Error::::AlreadyBonded), }?; // Reject a bond which is considered to be _dust_. @@ -906,8 +905,7 @@ pub mod pallet { let mut ledger = match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Err(Error::::NotStash), - Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + Some(ledger) => Ok(ledger), }?; let stash_balance = T::Currency::free_balance(&stash); @@ -1137,9 +1135,8 @@ pub mod pallet { let ledger = match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { - None | Some(StakingLedgerStatus::BondedNotPaired) => - Err(Error::::NotController), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + Some(ledger) => Ok(ledger), + None => Err(Error::::NotController), }?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); @@ -1207,8 +1204,8 @@ pub mod pallet { let controller = ensure_signed(origin)?; let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { - None | Some(StakingLedgerStatus::BondedNotPaired) => Err(Error::::NotController), - Some(StakingLedgerStatus::Paired(ledger)) => Ok(ledger), + Some(ledger) => Ok(ledger), + None => Err(Error::::NotController), }?; Self::chill_stash(&ledger.stash); @@ -1263,12 +1260,7 @@ pub mod pallet { // (temporary) passive migration. match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { None => Err(Error::::NotStash.into()), - Some(StakingLedgerStatus::BondedNotPaired) => { - // update bond only. - >::insert(&stash, &stash); - Ok(()) - }, - Some(StakingLedgerStatus::Paired(ledger)) => { + Some(ledger) => { let controller = ledger.controller().ok_or(Error::::NotController)?; if controller == stash { // stash is already its own controller. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 9bb012b4578b..2f083f2ac9e5 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5828,3 +5828,100 @@ mod staking_interface { }) } } + +mod ledger { + use super::*; + + #[test] + fn paired_account_works() { + ExtBuilder::default().build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(10), + 100, + RewardDestination::Controller + )); + + assert_eq!(>::get(&10), Some(10)); + assert_eq!( + StakingLedger::::paired_account(StakingAccount::Controller(10)), + Some(10) + ); + assert_eq!(StakingLedger::::paired_account(StakingAccount::Stash(10)), Some(10)); + + assert_eq!(>::get(&42), None); + assert_eq!(StakingLedger::::paired_account(StakingAccount::Controller(42)), None); + assert_eq!(StakingLedger::::paired_account(StakingAccount::Stash(42)), None); + + // bond manually stash with different controller. This is deprecated but the migration + // has not been complete yet (controller: 100, stash: 200) + assert_ok!(bond_controller_stash(100, 200)); + assert_eq!(>::get(&200), Some(100)); + assert_eq!( + StakingLedger::::paired_account(StakingAccount::Controller(100)), + Some(200) + ); + assert_eq!( + StakingLedger::::paired_account(StakingAccount::Stash(200)), + Some(100) + ); + }) + } + + #[test] + fn get_ledger_works() { + ExtBuilder::default().build_and_execute(|| { + // stash does not exist + assert_eq!(StakingLedger::::get(StakingAccount::Stash(42)), None); + + // bonded and paired + assert_eq!(>::get(&11), Some(11)); + + match StakingLedger::::get(StakingAccount::Stash(11)) { + Some(ledger) => { + assert_eq!(ledger.controller(), Some(11)); + assert_eq!(ledger.stash, 11); + }, + None => panic!("staking ledger must exist"), + }; + + // bond manually stash with different controller. This is deprecated but the migration + // has not been complete yet (controller: 100, stash: 200) + assert_ok!(bond_controller_stash(100, 200)); + assert_eq!(>::get(&200), Some(100)); + + match StakingLedger::::get(StakingAccount::Stash(200)) { + Some(ledger) => { + assert_eq!(ledger.controller(), Some(100)); + assert_eq!(ledger.stash, 200); + }, + None => panic!("staking ledger must exist"), + }; + + match StakingLedger::::get(StakingAccount::Controller(100)) { + Some(ledger) => { + assert_eq!(ledger.controller(), Some(100)); + assert_eq!(ledger.stash, 200); + }, + None => panic!("staking ledger must exist"), + }; + }) + } + + #[test] + fn is_bonded_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(!StakingLedger::::is_bonded(StakingAccount::Stash(42))); + assert!(!StakingLedger::::is_bonded(StakingAccount::Controller(42))); + + // adds entry to Bonded without Ledger pair (should not happen). + >::insert(42, 42); + assert!(!StakingLedger::::is_bonded(StakingAccount::Controller(42))); + + assert_eq!(>::get(&11), Some(11)); + assert!(StakingLedger::::is_bonded(StakingAccount::Stash(11))); + assert!(StakingLedger::::is_bonded(StakingAccount::Controller(11))); + + >::remove(42); // ensures try-state checks pass. + }) + } +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index fb20887caf8a..c2078e17c917 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -41,6 +41,7 @@ pub type EraIndex = u32; /// /// Note: once the controller is completely deprecated, this enum can also be deprecated in favor of /// the stash account. +#[derive(Copy, Clone, Debug)] pub enum StakingAccount { Stash(AccountId), Controller(AccountId), From eb3e731fb57bd0c35ceef934c94393f86f8a1419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Fri, 28 Jul 2023 23:55:59 +0200 Subject: [PATCH 15/47] Removes ledger.bond; fixes benchmarks --- frame/session/benchmarking/src/lib.rs | 2 +- frame/staking/src/ledger.rs | 21 +++++++++------------ frame/staking/src/pallet/impls.rs | 2 +- frame/staking/src/pallet/mod.rs | 3 +-- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index a7e326fb27ac..b4052a0bd42f 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -136,7 +136,7 @@ fn check_membership_proof_setup( use rand::{RngCore, SeedableRng}; let validator = T::Lookup::lookup(who).unwrap(); - let controller = pallet_staking::Pallet::::bonded(validator).unwrap(); + let controller = pallet_staking::Pallet::::bonded(&validator).unwrap(); let keys = { let mut keys = [0u8; 128]; diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index c8a4418af669..61687b5d8d75 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -52,7 +52,7 @@ pub struct StakingLedger { } impl StakingLedger { - #[cfg(any(test, runtime_benchmarks))] + #[cfg(any(feature = "runtime-benchmarks", test))] pub fn default_from(stash: T::AccountId) -> Self { Self { stash, @@ -155,27 +155,24 @@ impl StakingLedger { /// Inserts/updates a staking ledger account. /// - /// The staking locks of the stash account are updated accordingly. + /// Bonds the ledger if it was not bonded yet, signaling that this is a new ledger. The staking + /// locks of the stash account are updated accordingly. /// /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through /// this helper function. pub(crate) fn update(&self) -> Result<(), Error> { + if >::get(&self.stash).is_none() { + // not bonded yet, new ledger. Note: controllers are deprecated, stash is the + // controller. + >::insert(&self.stash, &self.stash); + } + T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); Ledger::::insert(&self.controller().ok_or(Error::::NotController)?, &self); Ok(()) } - /// Helper to bond a new stash. - /// - /// Note: as the controller accounts are being deprecated, the stash account is the same as the - /// controller account. - pub(crate) fn bond(&self) -> Result<(), Error> { - >::insert(&self.stash, &self.stash); - - Ok(()) - } - /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 21b8ffcee445..99bbbe654778 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1416,7 +1416,7 @@ impl ScoreProvider for Pallet { // this will clearly results in an inconsistent state, but it should not matter for a // benchmark. let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); - let mut ledger = match Self::ledger(who) { + let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) { None => StakingLedger::default_from(who.clone()), Some(l) => l, }; diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index e9200c30f8a6..23b277e69f23 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -871,11 +871,10 @@ pub mod pallet { // satisfied. .defensive_map_err(|_| Error::::BoundNotMet)?, ); - ledger.update()?; // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. - ledger.bond()?; + ledger.update()?; >::insert(&stash, payee); Ok(()) From df90343e863e42b0de4926185c3b81f258ad9e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 29 Jul 2023 16:53:36 +0200 Subject: [PATCH 16/47] Adds ledger.bond again as syntatic sugar for ledger.update --- frame/staking/src/ledger.rs | 12 ++++++++++++ frame/staking/src/pallet/mod.rs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 61687b5d8d75..3ac206457ce4 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -173,6 +173,18 @@ impl StakingLedger { Ok(()) } + // Bonds a ledger. + // + // This method is just syntatic sugar of [`Self::update`] with a check that returns an error if + // the ledger has is already bonded to ensure that the method behaves as expected. + pub(crate) fn bond(&self) -> Result<(), Error> { + if >::get(&self.stash).is_some() { + Err(Error::::AlreadyBonded) + } else { + self.update() + } + } + /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 23b277e69f23..64b0f8ce7beb 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -874,7 +874,7 @@ pub mod pallet { // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. - ledger.update()?; + ledger.bond()?; >::insert(&stash, payee); Ok(()) From f091facb36a520d4b8960a5d8b3b375fd2421c22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 29 Jul 2023 17:34:44 +0200 Subject: [PATCH 17/47] Adds tests for ledger.bond --- frame/staking/src/pallet/mod.rs | 1 + frame/staking/src/tests.rs | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 64b0f8ce7beb..125a5e7b20c3 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -702,6 +702,7 @@ pub mod pallet { ForceEra { mode: Forcing }, } + #[derive(PartialEq)] #[pallet::error] pub enum Error { /// Not a controller account. diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 2f083f2ac9e5..9a246f5ca7bc 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5907,6 +5907,27 @@ mod ledger { }) } + #[test] + fn bond_works() { + ExtBuilder::default().build_and_execute(|| { + assert!(!StakingLedger::::is_bonded(StakingAccount::Stash(42))); + assert!(>::get(&42).is_none()); + + let mut ledger: StakingLedger = StakingLedger::default_from(42); + + assert_ok!(ledger.bond()); + assert!(StakingLedger::::is_bonded(StakingAccount::Stash(42))); + assert!(>::get(&42).is_some()); + + // cannot bond again. + assert_noop!(ledger.bond(), Error::::AlreadyBonded); + + // once bonded, update works as expected. + ledger.claimed_rewards = bounded_vec![1]; + assert_ok!(ledger.update()); + }) + } + #[test] fn is_bonded_works() { ExtBuilder::default().build_and_execute(|| { From 7fefceff0c954427babd3554213b3044f10a5c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 29 Jul 2023 17:46:41 +0200 Subject: [PATCH 18/47] Update frame/staking/src/pallet/impls.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- frame/staking/src/pallet/impls.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 99bbbe654778..3d08cd856cc5 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -70,11 +70,7 @@ const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { /// Fetches the ledger associated with a controller or stash account, if any. pub fn ledger(account: StakingAccount) -> Option> { - if let Some(ledger) = StakingLedger::::get(account) { - Some(ledger) - } else { - None - } + StakingLedger::::get(account) } /// Fetches the controller bonded to a stash account, if any. From 9cb56ccccb5021c26d2f048096a13e30d1c5854a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sat, 29 Jul 2023 17:48:38 +0200 Subject: [PATCH 19/47] Update frame/staking/src/ledger.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 3ac206457ce4..e242e034f8f2 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -155,7 +155,7 @@ impl StakingLedger { /// Inserts/updates a staking ledger account. /// - /// Bonds the ledger if it was not bonded yet, signaling that this is a new ledger. The staking + /// Bonds the ledger if it is not bonded yet, signalling that this is a new ledger. The staking /// locks of the stash account are updated accordingly. /// /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through From 2acb8351ba282048c70fc3ad637eedb138be6b31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Sun, 30 Jul 2023 10:47:46 +0200 Subject: [PATCH 20/47] Update frame/staking/src/lib.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- frame/staking/src/lib.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index aab8167bede6..6467e8838a1e 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -645,10 +645,7 @@ pub struct StashOf(sp_std::marker::PhantomData); impl Convert> for StashOf { fn convert(controller: T::AccountId) -> Option { - match StakingLedger::::get(StakingAccount::Controller(controller)) { - Some(ledger) => Some(ledger.stash), - _ => None, - } + StakingLedger::::paired_account(StakingAccount::Controller(controller)) } } From 1198297bcd09c2fc30ad48764b55a4732074d103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 09:31:51 +0200 Subject: [PATCH 21/47] Refactors staking ledger get to result; other nits --- frame/staking/src/ledger.rs | 35 +++++++--------- frame/staking/src/pallet/impls.rs | 37 ++++++++--------- frame/staking/src/pallet/mod.rs | 68 ++++++++++++------------------- frame/staking/src/slashing.rs | 11 ++--- frame/staking/src/tests.rs | 33 ++++++++------- 5 files changed, 82 insertions(+), 102 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index e242e034f8f2..09282dd53335 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -113,32 +113,27 @@ impl StakingLedger { } } - /// Returns a staking ledger, if it is bonded and it exists. + /// Returns a staking ledger, if it is bonded and it exists in storage. /// /// This getter can be called with either a controller or stash account, provided that the /// account is properly wrapped in the respective [`StakingAccount`] variant. This is meant to /// abstract the concept of controller/stash accounts to the caller. - pub(crate) fn get(account: StakingAccount) -> Option> { - let controller = if let Some(controller) = match account { - StakingAccount::Stash(stash) => >::get(stash), - StakingAccount::Controller(controller) => Some(controller), - } { - controller - } else { - return None - }; - - match >::get(&controller).map(|mut ledger| { - ledger.controller = Some(controller.clone()); - ledger - }) { - Some(ledger) => Some(ledger), - None => { + pub(crate) fn get(account: StakingAccount) -> Result, Error> { + let controller = match account { + StakingAccount::Stash(stash) => >::get(stash).ok_or(Error::::NotStash), + StakingAccount::Controller(controller) => Ok(controller), + }?; + + >::get(&controller) + .map(|mut ledger| { + ledger.controller = Some(controller.clone()); + ledger + }) + .ok_or_else(|| { // this should not happen. log!(debug, "staking account is bonded but ledger does not exist, unexpected."); - None - }, - } + Error::::NotController + }) } /// Returns the controller account of a staking ledger. diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 3d08cd856cc5..63a3bd0a8dea 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -69,7 +69,7 @@ const NPOS_MAX_ITERATIONS_COEFFICIENT: u32 = 2; impl Pallet { /// Fetches the ledger associated with a controller or stash account, if any. - pub fn ledger(account: StakingAccount) -> Option> { + pub fn ledger(account: StakingAccount) -> Result, Error> { StakingLedger::::get(account) } @@ -115,8 +115,7 @@ impl Pallet { controller: &T::AccountId, num_slashing_spans: u32, ) -> Result { - let mut ledger = - Self::ledger(Controller(controller.clone())).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(Controller(controller.clone()))?; let (stash, old_total) = (ledger.stash.clone(), ledger.total); if let Some(current_era) = Self::current_era() { ledger = ledger.consolidate_unlocked(current_era) @@ -173,11 +172,11 @@ impl Pallet { })?; let account = StakingAccount::Stash(validator_stash.clone()); - let mut ledger = StakingLedger::::get(account.clone()).ok_or_else(|| { + let mut ledger = Self::ledger(account.clone()).or_else(|_| { if StakingLedger::::is_bonded(account) { - Error::::NotController.into() + Err(Error::::NotController.into()) } else { - Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) + Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))) } })?; @@ -306,17 +305,19 @@ impl Pallet { .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), RewardDestination::Staked => - Self::ledger(Stash(stash.clone())).and_then(|mut ledger| { + if let Some(mut ledger) = Self::ledger(Stash(stash.clone())).ok() { ledger.active += amount; ledger.total += amount; let r = T::Currency::deposit_into_existing(stash, amount).ok(); - // calling `fn Self::ledger` ensures that the returned ledger exists in storage, - // qed. - let _ = ledger.update(); + let _ = ledger + .update() + .defensive_proof("ledger fetched from storage, so it exists; qed."); r - }), + } else { + None + }, RewardDestination::Account(dest_account) => Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, @@ -409,8 +410,7 @@ impl Pallet { } /// Start a new era. It does: - /// - /// * Increment `active_era.index`, + // * Increment `active_era.index`, /// * reset `active_era.start`, /// * update `BondedEras` and apply slashes. fn start_era(start_session: SessionIndex) { @@ -1413,8 +1413,8 @@ impl ScoreProvider for Pallet { // benchmark. let active: BalanceOf = weight.try_into().map_err(|_| ()).unwrap(); let mut ledger = match Self::ledger(StakingAccount::Stash(who.clone())) { - None => StakingLedger::default_from(who.clone()), - Some(l) => l, + Ok(l) => l, + Err(_) => StakingLedger::default_from(who.clone()), }; ledger.active = active; @@ -1607,7 +1607,7 @@ impl StakingInterface for Pallet { fn stash_by_ctrl(controller: &Self::AccountId) -> Result { Self::ledger(Controller(controller.clone())) .map(|l| l.stash) - .ok_or(Error::::NotController.into()) + .map_err(|e| e.into()) } fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { @@ -1627,7 +1627,7 @@ impl StakingInterface for Pallet { fn stake(who: &Self::AccountId) -> Result>, DispatchError> { Self::ledger(Stash(who.clone())) .map(|l| Stake { total: l.total, active: l.active }) - .ok_or(Error::::NotStash.into()) + .map_err(|e| e.into()) } fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { @@ -1834,8 +1834,7 @@ impl Pallet { fn ensure_ledger_consistent(ctrl: T::AccountId) -> Result<(), TryRuntimeError> { // ensures ledger.total == ledger.active + sum(ledger.unlocking). - let ledger = StakingLedger::::get(StakingAccount::Controller(ctrl.clone())) - .ok_or("Not a controller")?; + let ledger = Self::ledger(StakingAccount::Controller(ctrl.clone()))?; let real_total: BalanceOf = ledger.unlocking.iter().fold(ledger.active, |a, c| a + c.value); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 125a5e7b20c3..1e85a93399dd 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -841,10 +841,8 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { - None => Ok(()), - Some(_) => Err(Error::::AlreadyBonded), - }?; + Self::ledger(StakingAccount::Stash(stash.clone())) + .map_or(Ok(()), |_| Err(Error::::AlreadyBonded))?; // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { @@ -903,10 +901,7 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - let mut ledger = match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { - None => Err(Error::::NotStash), - Some(ledger) => Ok(ledger), - }?; + let mut ledger = Self::ledger(StakingAccount::Stash(stash.clone()))?; let stash_balance = T::Currency::free_balance(&stash); if let Some(extra) = stash_balance.checked_sub(&ledger.total) { @@ -960,9 +955,8 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let unlocking = Self::ledger(Controller(controller.clone())) - .map(|l| l.unlocking.len()) - .ok_or(Error::::NotController)?; + let unlocking = + Self::ledger(Controller(controller.clone())).map(|l| l.unlocking.len())?; // if there are no unlocking chunks available, try to withdraw chunks older than // `BondingDuration` to proceed with the unbonding. @@ -978,8 +972,7 @@ pub mod pallet { // we need to fetch the ledger again because it may have been mutated in the call // to `Self::do_withdraw_unbonded` above. - let mut ledger = - Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; + let mut ledger = Self::ledger(Controller(controller))?; let mut value = value.min(ledger.active); ensure!( @@ -1087,7 +1080,7 @@ pub mod pallet { pub fn validate(origin: OriginFor, prefs: ValidatorPrefs) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller))?; ensure!(ledger.active >= MinValidatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1133,11 +1126,7 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = - match StakingLedger::::get(StakingAccount::Controller(controller.clone())) { - Some(ledger) => Ok(ledger), - None => Err(Error::::NotController), - }?; + let ledger = Self::ledger(StakingAccount::Controller(controller.clone()))?; ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; @@ -1203,10 +1192,7 @@ pub mod pallet { pub fn chill(origin: OriginFor) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = match StakingLedger::::get(StakingAccount::Controller(controller)) { - Some(ledger) => Ok(ledger), - None => Err(Error::::NotController), - }?; + let ledger = Self::ledger(StakingAccount::Controller(controller))?; Self::chill_stash(&ledger.stash); Ok(()) @@ -1231,7 +1217,7 @@ pub mod pallet { payee: RewardDestination, ) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller))?; let stash = &ledger.stash; >::insert(stash, payee); Ok(()) @@ -1258,21 +1244,18 @@ pub mod pallet { // the bonded map and ledger are mutated directly as this extrinsic is related to a // (temporary) passive migration. - match StakingLedger::::get(StakingAccount::Stash(stash.clone())) { - None => Err(Error::::NotStash.into()), - Some(ledger) => { - let controller = ledger.controller().ok_or(Error::::NotController)?; - if controller == stash { - // stash is already its own controller. - return Err(Error::::AlreadyPaired.into()) - } - // update bond and ledger. - >::remove(controller); - >::insert(&stash, &stash); - >::insert(&stash, ledger); - Ok(()) - }, - } + Self::ledger(StakingAccount::Stash(stash.clone())).map(|ledger| { + let controller = ledger.controller().ok_or(Error::::NotController)?; + if controller == stash { + // stash is already its own controller. + return Err(Error::::AlreadyPaired.into()) + } + // update bond and ledger. + >::remove(controller); + >::insert(&stash, &stash); + >::insert(&stash, ledger); + Ok(()) + })? } /// Sets the ideal number of validators. @@ -1511,7 +1494,7 @@ pub mod pallet { #[pallet::compact] value: BalanceOf, ) -> DispatchResultWithPostInfo { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller))?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); let initial_unlocking = ledger.unlocking.len() as u32; @@ -1589,7 +1572,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::kick(who.len() as u32))] pub fn kick(origin: OriginFor, who: Vec>) -> DispatchResult { let controller = ensure_signed(origin)?; - let ledger = Self::ledger(Controller(controller)).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller))?; let stash = &ledger.stash; for nom_stash in who @@ -1698,8 +1681,7 @@ pub mod pallet { pub fn chill_other(origin: OriginFor, controller: T::AccountId) -> DispatchResult { // Anyone can call this function. let caller = ensure_signed(origin)?; - let ledger = - Self::ledger(Controller(controller.clone())).ok_or(Error::::NotController)?; + let ledger = Self::ledger(Controller(controller.clone()))?; let stash = ledger.stash; // In order for one user to chill another user, the following conditions must be met: diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index c145eae14c06..34929c00b00a 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -57,7 +57,7 @@ use crate::{ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Get, Imbalance, OnUnbalanced}, + traits::{Currency, Defensive, Get, Imbalance, OnUnbalanced}, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -598,8 +598,8 @@ pub fn do_slash( slash_era: EraIndex, ) { let mut ledger = match Pallet::::ledger(sp_staking::StakingAccount::Stash(stash.clone())) { - Some(ledger) => ledger, - None => return, // nothing to do. + Ok(ledger) => ledger, + Err(_) => return, // nothing to do. }; let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); @@ -613,8 +613,9 @@ pub fn do_slash( *reward_payout = reward_payout.saturating_sub(missing); } - // calling `fn Pallet::ledger` ensures that the returned ledger exists in storage, qed. - let _ = ledger.update(); + let _ = ledger + .update() + .defensive_proof("ledger fetched from storage so it exists in storage; qed."); // trigger the event >::deposit_event(super::Event::::Slashed { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 9a246f5ca7bc..264e03694131 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -21,7 +21,7 @@ use super::{ConfigOp, Event, *}; use crate::ledger::StakingLedgerInspect; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_noop, assert_ok, assert_storage_noop, bounded_vec, + assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{Currency, Get, ReservableCurrency}, @@ -170,7 +170,7 @@ fn basic_setup_works() { } ); // Account 1 does not control any stash - assert_eq!(Staking::ledger(1.into()), None); + assert_err!(Staking::ledger(1.into()), Error::::NotStash); // ValidatorPrefs are default assert_eq_uvec!( @@ -1944,14 +1944,14 @@ fn bond_with_no_staked_value() { // not yet removed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1.into()).is_some()); + assert!(Staking::ledger(1.into()).is_ok()); assert_eq!(Balances::locks(&1)[0].amount, 5); mock::start_active_era(3); // poof. Account 1 is removed from the staking system. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(1), 0)); - assert!(Staking::ledger(1.into()).is_none()); + assert!(Staking::ledger(1.into()).is_err()); assert_eq!(Balances::locks(&1).len(), 0); }); } @@ -5517,7 +5517,7 @@ fn pre_bonding_era_cannot_be_claimed() { // decoding will fail now since Staking Ledger is in corrupt state HistoryDepth::set(history_depth - 1); - assert_eq!(Staking::ledger(4.into()), None); + assert!(Staking::ledger(4.into()).is_err()); // make sure stakers still cannot claim rewards that they are not meant to assert_noop!( @@ -5618,7 +5618,7 @@ fn reducing_max_unlocking_chunks_abrupt() { MaxUnlockingChunks::set(2); start_active_era(10); assert_ok!(Staking::bond(RuntimeOrigin::signed(3), 300, RewardDestination::Staked)); - assert!(matches!(Staking::ledger(3.into()), Some(_))); + assert!(matches!(Staking::ledger(3.into()), Ok(_))); // when staker unbonds assert_ok!(Staking::unbond(RuntimeOrigin::signed(3), 20)); @@ -5628,7 +5628,7 @@ fn reducing_max_unlocking_chunks_abrupt() { let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20 as Balance, era: 13 as EraIndex }]; assert!(matches!(Staking::ledger(3.into()), - Some(StakingLedger { + Ok(StakingLedger { unlocking, .. }) if unlocking==expected_unlocking)); @@ -5640,7 +5640,7 @@ fn reducing_max_unlocking_chunks_abrupt() { let expected_unlocking: BoundedVec, MaxUnlockingChunks> = bounded_vec![UnlockChunk { value: 20, era: 13 }, UnlockChunk { value: 50, era: 14 }]; assert!(matches!(Staking::ledger(3.into()), - Some(StakingLedger { + Ok(StakingLedger { unlocking, .. }) if unlocking==expected_unlocking)); @@ -5871,17 +5871,20 @@ mod ledger { fn get_ledger_works() { ExtBuilder::default().build_and_execute(|| { // stash does not exist - assert_eq!(StakingLedger::::get(StakingAccount::Stash(42)), None); + assert_noop!( + StakingLedger::::get(StakingAccount::Stash(42)), + Error::::NotStash + ); // bonded and paired assert_eq!(>::get(&11), Some(11)); match StakingLedger::::get(StakingAccount::Stash(11)) { - Some(ledger) => { + Ok(ledger) => { assert_eq!(ledger.controller(), Some(11)); assert_eq!(ledger.stash, 11); }, - None => panic!("staking ledger must exist"), + Err(_) => panic!("staking ledger must exist"), }; // bond manually stash with different controller. This is deprecated but the migration @@ -5890,19 +5893,19 @@ mod ledger { assert_eq!(>::get(&200), Some(100)); match StakingLedger::::get(StakingAccount::Stash(200)) { - Some(ledger) => { + Ok(ledger) => { assert_eq!(ledger.controller(), Some(100)); assert_eq!(ledger.stash, 200); }, - None => panic!("staking ledger must exist"), + Err(_) => panic!("staking ledger must exist"), }; match StakingLedger::::get(StakingAccount::Controller(100)) { - Some(ledger) => { + Ok(ledger) => { assert_eq!(ledger.controller(), Some(100)); assert_eq!(ledger.stash, 200); }, - None => panic!("staking ledger must exist"), + Err(_) => panic!("staking ledger must exist"), }; }) } From 283ab0b202494eb59e22988d3cc6c6d3e397bd7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 09:36:46 +0200 Subject: [PATCH 22/47] nit --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 09282dd53335..13e2310df964 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -131,7 +131,7 @@ impl StakingLedger { }) .ok_or_else(|| { // this should not happen. - log!(debug, "staking account is bonded but ledger does not exist, unexpected."); + log!(debug, "staking account is bonded but ledger does not exist or it is in a bad state, unexpected."); Error::::NotController }) } From 9e59fccd5f96459cc405947fcbfb0c6a87e50cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 14:19:16 +0200 Subject: [PATCH 23/47] Update frame/staking/src/ledger.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 13e2310df964..719afc9c71f9 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -170,7 +170,7 @@ impl StakingLedger { // Bonds a ledger. // - // This method is just syntatic sugar of [`Self::update`] with a check that returns an error if + // This method is just syntactic sugar of [`Self::update`] with a check that returns an error if // the ledger has is already bonded to ensure that the method behaves as expected. pub(crate) fn bond(&self) -> Result<(), Error> { if >::get(&self.stash).is_some() { From c56fa7e65f206da0565d1332f7e18bb73f01156c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 14:22:44 +0200 Subject: [PATCH 24/47] Update frame/staking/src/pallet/impls.rs Co-authored-by: Ankan <10196091+Ank4n@users.noreply.github.com> --- frame/staking/src/pallet/impls.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 63a3bd0a8dea..3d0dd809f9a4 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -172,12 +172,8 @@ impl Pallet { })?; let account = StakingAccount::Stash(validator_stash.clone()); - let mut ledger = Self::ledger(account.clone()).or_else(|_| { - if StakingLedger::::is_bonded(account) { - Err(Error::::NotController.into()) - } else { - Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))) - } + let mut ledger = Self::ledger(account.clone()) + .or_else(|e| Err(e.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))))?; })?; ledger From 2e0f050406d531de1144e34e9e3e0b62048656d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 14:53:20 +0200 Subject: [PATCH 25/47] Simplifies code and adds some comment nits --- frame/staking/src/ledger.rs | 16 +++++++--------- frame/staking/src/pallet/impls.rs | 19 +++++++++++-------- frame/staking/src/pallet/mod.rs | 5 ++++- 3 files changed, 22 insertions(+), 18 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 719afc9c71f9..9d6509cbd5a7 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -108,8 +108,8 @@ impl StakingLedger { /// Returns whether a given account is bonded. pub(crate) fn is_bonded(account: StakingAccount) -> bool { match account { - StakingAccount::Stash(stash) => >::get(stash).is_some(), - StakingAccount::Controller(controller) => >::get(controller).is_some(), + StakingAccount::Stash(stash) => >::contains_key(stash), + StakingAccount::Controller(controller) => >::contains_key(controller), } } @@ -129,11 +129,7 @@ impl StakingLedger { ledger.controller = Some(controller.clone()); ledger }) - .ok_or_else(|| { - // this should not happen. - log!(debug, "staking account is bonded but ledger does not exist or it is in a bad state, unexpected."); - Error::::NotController - }) + .ok_or_else(|| Error::::NotController) } /// Returns the controller account of a staking ledger. @@ -156,7 +152,7 @@ impl StakingLedger { /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through /// this helper function. pub(crate) fn update(&self) -> Result<(), Error> { - if >::get(&self.stash).is_none() { + if !>::contains_key(&self.stash) { // not bonded yet, new ledger. Note: controllers are deprecated, stash is the // controller. >::insert(&self.stash, &self.stash); @@ -173,7 +169,7 @@ impl StakingLedger { // This method is just syntactic sugar of [`Self::update`] with a check that returns an error if // the ledger has is already bonded to ensure that the method behaves as expected. pub(crate) fn bond(&self) -> Result<(), Error> { - if >::get(&self.stash).is_some() { + if >::contains_key(&self.stash) { Err(Error::::AlreadyBonded) } else { self.update() @@ -394,6 +390,8 @@ impl StakingLedger { } } +// This structs makes it easy to write tests to compare staking ledgers fetched from storage. This +// is required because the controller field is not stored in storage and it is private. #[cfg(test)] #[derive(frame_support::DebugNoBound, Clone, Encode, Decode, TypeInfo, MaxEncodedLen)] pub struct StakingLedgerInspect { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 3d0dd809f9a4..ccb3528b51b7 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -172,8 +172,12 @@ impl Pallet { })?; let account = StakingAccount::Stash(validator_stash.clone()); - let mut ledger = Self::ledger(account.clone()) - .or_else(|e| Err(e.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))))?; + let mut ledger = Self::ledger(account.clone()).or_else(|_| { + if StakingLedger::::is_bonded(account) { + Err(Error::::NotController.into()) + } else { + Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))) + } })?; ledger @@ -300,8 +304,8 @@ impl Pallet { RewardDestination::Controller => Self::bonded(stash) .map(|controller| T::Currency::deposit_creating(&controller, amount)), RewardDestination::Stash => T::Currency::deposit_into_existing(stash, amount).ok(), - RewardDestination::Staked => - if let Some(mut ledger) = Self::ledger(Stash(stash.clone())).ok() { + RewardDestination::Staked => Self::ledger(Stash(stash.clone())) + .and_then(|mut ledger| { ledger.active += amount; ledger.total += amount; let r = T::Currency::deposit_into_existing(stash, amount).ok(); @@ -310,10 +314,9 @@ impl Pallet { .update() .defensive_proof("ledger fetched from storage, so it exists; qed."); - r - } else { - None - }, + Ok(r) + }) + .unwrap_or_default(), RewardDestination::Account(dest_account) => Some(T::Currency::deposit_creating(&dest_account, amount)), RewardDestination::None => None, diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 1e85a93399dd..5f69de2364f1 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1245,7 +1245,10 @@ pub mod pallet { // the bonded map and ledger are mutated directly as this extrinsic is related to a // (temporary) passive migration. Self::ledger(StakingAccount::Stash(stash.clone())).map(|ledger| { - let controller = ledger.controller().ok_or(Error::::NotController)?; + let controller = ledger.controller() + .defensive_proof("ledger was fetched used the StakingInterface, so controller field must exist; qed.") + .ok_or(Error::::NotController)?; + if controller == stash { // stash is already its own controller. return Err(Error::::AlreadyPaired.into()) From 7bfe9cdaebf2837a0b105d75624d9f756e04f3ad Mon Sep 17 00:00:00 2001 From: command-bot Date: Mon, 31 Jul 2023 13:02:22 +0000 Subject: [PATCH 26/47] ".git/.scripts/commands/fmt/fmt.sh" --- primitives/staking/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index c2078e17c917..aeca30a133b9 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -48,9 +48,9 @@ pub enum StakingAccount { } impl From for StakingAccount { - fn from(account: AccountId) -> Self { - StakingAccount::Stash(account) - } + fn from(account: AccountId) -> Self { + StakingAccount::Stash(account) + } } /// Representation of the status of a staker. From 2cd73bc54fb9ae9036720d74092c200a81a0ed0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 15:08:01 +0200 Subject: [PATCH 27/47] restart CI jobs From d5c7ee3fffaca74aa789226b38bc6e8cdac8cc13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 17:55:33 +0200 Subject: [PATCH 28/47] Fixes EPM e2e tests --- .../test-staking-e2e/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs index e40bac3e9fc4..1d3f4712b1d6 100644 --- a/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs +++ b/frame/election-provider-multi-phase/test-staking-e2e/src/lib.rs @@ -338,7 +338,7 @@ fn ledger_consistency_active_balance_below_ed() { ExtBuilder::default().staking(StakingExtBuilder::default()).build_offchainify(); ext.execute_with(|| { - assert_eq!(Staking::ledger(&11).unwrap().active, 1000); + assert_eq!(Staking::ledger(11.into()).unwrap().active, 1000); // unbonding total of active stake fails because the active ledger balance would fall // below the `MinNominatorBond`. @@ -356,13 +356,13 @@ fn ledger_consistency_active_balance_below_ed() { // the active balance of the ledger entry is 0, while total balance is 1000 until // `withdraw_unbonded` is called. - assert_eq!(Staking::ledger(&11).unwrap().active, 0); - assert_eq!(Staking::ledger(&11).unwrap().total, 1000); + assert_eq!(Staking::ledger(11.into()).unwrap().active, 0); + assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000); // trying to withdraw the unbonded balance won't work yet because not enough bonding // eras have passed. assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); - assert_eq!(Staking::ledger(&11).unwrap().total, 1000); + assert_eq!(Staking::ledger(11.into()).unwrap().total, 1000); // tries to reap stash after chilling, which fails since the stash total balance is // above ED. @@ -384,6 +384,6 @@ fn ledger_consistency_active_balance_below_ed() { pool_state, ); assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(11), 0)); - assert_eq!(Staking::ledger(&11), None); + assert!(Staking::ledger(11.into()).is_err()); }); } From 10fa2b3480940585cabd7e9bbc69455636af9fb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 31 Jul 2023 18:17:24 +0200 Subject: [PATCH 29/47] Fixes rust docs warning --- frame/staking/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 9d6509cbd5a7..d7973b49508b 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -66,8 +66,8 @@ impl StakingLedger { /// Returns a new instance of a staking ledger. /// - /// The `Ledger` storage is not mutated. In order to do that, [`update`] must be called on the - /// returned staking ledger. + /// The `Ledger` storage is not mutated. In order to store, `fn update` must be called on + /// the returned staking ledger. /// /// Note: as the controller accounts are being deprecated, the stash account is the same as the /// controller account. From fe27f2c1626b9271bd014dd2810148077e7b62f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:00:20 +0200 Subject: [PATCH 30/47] Update frame/staking/src/ledger.rs Co-authored-by: Liam Aharon --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index d7973b49508b..e1092ab9ece7 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -12,7 +12,7 @@ use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ /// The ledger of a (bonded) stash. /// -/// Note: All the reads and mutations to the `Ledger` and `Bonded` storage items *MUST* be performed +/// Note: All the reads and mutations to the [`Ledger`] and [`Bonded`] storage items *MUST* be performed /// through the methods exposed by this struct to ensure data and staking lock consistency. #[derive( PartialEqNoBound, From 1929868faef924067ae7fdb315f9b9cc2b681538 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:00:41 +0200 Subject: [PATCH 31/47] Update frame/staking/src/ledger.rs Co-authored-by: Ross Bulat --- frame/staking/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index e1092ab9ece7..18e113f290cd 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -166,8 +166,8 @@ impl StakingLedger { // Bonds a ledger. // - // This method is just syntactic sugar of [`Self::update`] with a check that returns an error if - // the ledger has is already bonded to ensure that the method behaves as expected. + // This method is just syntactic sugar of [`Self::update`], with an additional check that returns an error if + // the ledger is already bonded. This ensures the method behaves as expected. pub(crate) fn bond(&self) -> Result<(), Error> { if >::contains_key(&self.stash) { Err(Error::::AlreadyBonded) From fceeaa9aea9630ed4e4b8d3bf752be9c47fd68de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:01:09 +0200 Subject: [PATCH 32/47] Update frame/staking/src/ledger.rs Co-authored-by: Liam Aharon --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 18e113f290cd..db907577419b 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -66,7 +66,7 @@ impl StakingLedger { /// Returns a new instance of a staking ledger. /// - /// The `Ledger` storage is not mutated. In order to store, `fn update` must be called on + /// The [`Ledger`] storage is not mutated. In order to store, [`StakingLedger::update`] must be called on /// the returned staking ledger. /// /// Note: as the controller accounts are being deprecated, the stash account is the same as the From 55a1598295fc12ea1f0ec47d81c728405273018c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:01:16 +0200 Subject: [PATCH 33/47] Update frame/staking/src/ledger.rs Co-authored-by: Liam Aharon --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index db907577419b..a1ac1becbedc 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -84,7 +84,7 @@ impl StakingLedger { total: total_stake, unlocking, claimed_rewards, - // controllers are deprecated and map 1-1 to stashes. + // controllers are deprecated and mapped 1-1 to stashes. controller: Some(stash), } } From 47ff62ec8440c60ba5c269a31180464f9f7589e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:01:29 +0200 Subject: [PATCH 34/47] Update frame/staking/src/pallet/impls.rs Co-authored-by: Liam Aharon --- frame/staking/src/pallet/impls.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 1bfa50259004..a68b53b1ad16 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -265,7 +265,6 @@ impl Pallet { // Lets now calculate how this is split to the nominators. // Reward only the clipped exposures. Note this is not necessarily sorted. - for nominator in exposure.others.iter() { let nominator_exposure_part = Perbill::from_rational(nominator.value, exposure.total); From c9850b1244571556cf3c647feb28a1bc0bc84e74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:01:46 +0200 Subject: [PATCH 35/47] Update frame/staking/src/pallet/impls.rs Co-authored-by: Liam Aharon --- frame/staking/src/pallet/impls.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a68b53b1ad16..fd3ccd2a7b92 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -408,7 +408,7 @@ impl Pallet { } /// Start a new era. It does: - // * Increment `active_era.index`, + /// * Increment `active_era.index`, /// * reset `active_era.start`, /// * update `BondedEras` and apply slashes. fn start_era(start_session: SessionIndex) { From 778bc00858b13b6a79b66bfc6a1540809bfc99fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:24:38 +0200 Subject: [PATCH 36/47] Adds license and mod rust docs to ledger --- frame/staking/src/ledger.rs | 46 ++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index a1ac1becbedc..a31a6a72aed7 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -1,3 +1,35 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! A Ledger implementation for stakers. +//! +//! A [`StakingLedger`] encapsulates all the state and logic related to the stake of bonded +//! stakers, namely: +//! * [`Bonded`]: mutates and reads the state of the controller <> stash bond map (to be deprecated +//! soon); +//! * [`Ledger`]: mutates and reads the state of all the stakers. The [`Ledger`] storage item stores +//! instances of [`StakingLedger`] keyed by the staker's controller account and should be mutated +//! and read through the [`StakingLedger`] API; +//! * Staking locks: mutates the locks for staking. +//! +//! NOTE: All the storage operations related to the staking ledger (both reads and writes) *MUST* be +//! performed through the methods exposed by the [`StakingLedger`] implementation in order to ensure +//! state consistency. + use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::{Defensive, Get, LockableCurrency, WithdrawReasons}, @@ -12,8 +44,9 @@ use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ /// The ledger of a (bonded) stash. /// -/// Note: All the reads and mutations to the [`Ledger`] and [`Bonded`] storage items *MUST* be performed -/// through the methods exposed by this struct to ensure data and staking lock consistency. +/// Note: All the reads and mutations to the [`Ledger`] and [`Bonded`] storage items *MUST* be +/// performed through the methods exposed by this struct to ensure data and staking lock +/// consistency. #[derive( PartialEqNoBound, EqNoBound, @@ -66,8 +99,8 @@ impl StakingLedger { /// Returns a new instance of a staking ledger. /// - /// The [`Ledger`] storage is not mutated. In order to store, [`StakingLedger::update`] must be called on - /// the returned staking ledger. + /// The [`Ledger`] storage is not mutated. In order to store, `StakingLedger::update` must be + /// called on the returned staking ledger. /// /// Note: as the controller accounts are being deprecated, the stash account is the same as the /// controller account. @@ -166,8 +199,9 @@ impl StakingLedger { // Bonds a ledger. // - // This method is just syntactic sugar of [`Self::update`], with an additional check that returns an error if - // the ledger is already bonded. This ensures the method behaves as expected. + // This method is just syntactic sugar of [`Self::update`], with an additional check that + // returns an error if the ledger is already bonded. This ensures the method behaves as + // expected. pub(crate) fn bond(&self) -> Result<(), Error> { if >::contains_key(&self.stash) { Err(Error::::AlreadyBonded) From f7888844b0938c8efe20f3dac95878db6e37eacf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:35:51 +0200 Subject: [PATCH 37/47] Update frame/staking/src/ledger.rs Co-authored-by: Liam Aharon --- frame/staking/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index a31a6a72aed7..16504bf67b6c 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -167,9 +167,9 @@ impl StakingLedger { /// Returns the controller account of a staking ledger. /// - /// Note: it will fallback into querying the `Bonded` storage with the ledger stash if the + /// Note: it will fallback into querying the [`Bonded`] storage with the ledger stash if the /// controller is not set in `self`, which most likely means that self was fetched directly from - /// `Ledger` instead of through the methods exposed in `StakingLedger`. If the ledger does not + /// [`Ledger`] instead of through the methods exposed in [`StakingLedger`]. If the ledger does not /// exist in storage, it returns `None`. pub(crate) fn controller(&self) -> Option { self.controller From 5309a867b6ea83a56a2f2bf84043d033db8eaa74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Tue, 1 Aug 2023 10:36:41 +0200 Subject: [PATCH 38/47] Update frame/staking/src/ledger.rs Co-authored-by: Liam Aharon --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 16504bf67b6c..404df72c4f5e 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -150,7 +150,7 @@ impl StakingLedger { /// /// This getter can be called with either a controller or stash account, provided that the /// account is properly wrapped in the respective [`StakingAccount`] variant. This is meant to - /// abstract the concept of controller/stash accounts to the caller. + /// abstract the concept of controller/stash accounts from the caller. pub(crate) fn get(account: StakingAccount) -> Result, Error> { let controller = match account { StakingAccount::Stash(stash) => >::get(stash).ok_or(Error::::NotStash), From 90c2187698df9475a186a63d5fcd930cbf458335 Mon Sep 17 00:00:00 2001 From: command-bot <> Date: Tue, 1 Aug 2023 09:55:10 +0000 Subject: [PATCH 39/47] ".git/.scripts/commands/fmt/fmt.sh" --- frame/staking/src/ledger.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 404df72c4f5e..61cc8a579b9d 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -169,8 +169,8 @@ impl StakingLedger { /// /// Note: it will fallback into querying the [`Bonded`] storage with the ledger stash if the /// controller is not set in `self`, which most likely means that self was fetched directly from - /// [`Ledger`] instead of through the methods exposed in [`StakingLedger`]. If the ledger does not - /// exist in storage, it returns `None`. + /// [`Ledger`] instead of through the methods exposed in [`StakingLedger`]. If the ledger does + /// not exist in storage, it returns `None`. pub(crate) fn controller(&self) -> Option { self.controller .clone() From ed99af2c2ef54a46c5d676550faead2c46f73077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 7 Aug 2023 16:03:17 +0200 Subject: [PATCH 40/47] addresses PR comments pt1 --- frame/staking/src/ledger.rs | 71 +++++++++++++++++++++++++------ frame/staking/src/pallet/impls.rs | 11 +++-- frame/staking/src/pallet/mod.rs | 12 +++--- frame/staking/src/slashing.rs | 9 ++-- frame/staking/src/tests.rs | 14 +++--- primitives/staking/src/lib.rs | 5 ++- 6 files changed, 86 insertions(+), 36 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 61cc8a579b9d..927a342ce9ba 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -24,6 +24,7 @@ //! * [`Ledger`]: mutates and reads the state of all the stakers. The [`Ledger`] storage item stores //! instances of [`StakingLedger`] keyed by the staker's controller account and should be mutated //! and read through the [`StakingLedger`] API; +//! * [`Payee`]: mutates and reads the reward destination preferences for a bonded stash. //! * Staking locks: mutates the locks for staking. //! //! NOTE: All the storage operations related to the staking ledger (both reads and writes) *MUST* be @@ -32,6 +33,7 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ + defensive, traits::{Defensive, Get, LockableCurrency, WithdrawReasons}, BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; @@ -40,13 +42,16 @@ use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; use sp_staking::{EraIndex, OnStakingUpdate, StakingAccount}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; -use crate::{log, BalanceOf, Bonded, Config, Error, Ledger, UnlockChunk, STAKING_ID}; +use crate::{ + log, BalanceOf, Bonded, Config, Error, Ledger, Payee, RewardDestination, UnlockChunk, + STAKING_ID, +}; /// The ledger of a (bonded) stash. /// -/// Note: All the reads and mutations to the [`Ledger`] and [`Bonded`] storage items *MUST* be -/// performed through the methods exposed by this struct to ensure data and staking lock -/// consistency. +/// Note: All the reads and mutations to the [`Ledger`], [`Bonded`] and [`Payee`] storage items +/// *MUST* be performed through the methods exposed by this struct, to ensure the consistency of +/// ledger's data and corresponding staking lock #[derive( PartialEqNoBound, EqNoBound, @@ -165,6 +170,27 @@ impl StakingLedger { .ok_or_else(|| Error::::NotController) } + /// Returns the reward destination of a staking ledger, stored in [`Payee`]. + /// + /// Note: if the stash is not bonded and/or does not have an entry in [`Payee`], it returns the + /// default reward destination. + pub(crate) fn reward_destination( + account: StakingAccount, + ) -> RewardDestination { + let stash = match account { + StakingAccount::Stash(stash) => Some(stash), + StakingAccount::Controller(controller) => + Self::paired_account(StakingAccount::Controller(controller)), + }; + + if let Some(stash) = stash { + >::get(stash) + } else { + defensive!("fetched reward destination from unbonded stash {}", stash); + RewardDestination::default() + } + } + /// Returns the controller account of a staking ledger. /// /// Note: it will fallback into querying the [`Bonded`] storage with the ledger stash if the @@ -186,30 +212,44 @@ impl StakingLedger { /// this helper function. pub(crate) fn update(&self) -> Result<(), Error> { if !>::contains_key(&self.stash) { - // not bonded yet, new ledger. Note: controllers are deprecated, stash is the - // controller. - >::insert(&self.stash, &self.stash); + return Err(Error::::NotStash) } T::Currency::set_lock(STAKING_ID, &self.stash, self.total, WithdrawReasons::all()); - Ledger::::insert(&self.controller().ok_or(Error::::NotController)?, &self); + Ledger::::insert( + &self.controller().ok_or_else(|| { + defensive!("update called on a ledger that is not bonded."); + Error::::NotController + })?, + &self, + ); Ok(()) } - // Bonds a ledger. - // - // This method is just syntactic sugar of [`Self::update`], with an additional check that - // returns an error if the ledger is already bonded. This ensures the method behaves as - // expected. - pub(crate) fn bond(&self) -> Result<(), Error> { + /// Bonds a ledger. + /// + /// It sets the reward preferences for the bonded stash. + pub(crate) fn bond(&self, payee: RewardDestination) -> Result<(), Error> { if >::contains_key(&self.stash) { Err(Error::::AlreadyBonded) } else { + >::insert(&self.stash, payee); + >::insert(&self.stash, &self.stash); self.update() } } + /// Sets the ledger Payee. + pub(crate) fn set_payee(self, payee: RewardDestination) -> Result<(), Error> { + if !>::contains_key(&self.stash) { + Err(Error::::NotStash) + } else { + >::insert(&self.stash, payee); + Ok(()) + } + } + /// Clears all data related to a staking ledger and its bond in both [`Ledger`] and [`Bonded`] /// storage items and updates the stash staking lock. pub(crate) fn kill(stash: &T::AccountId) -> Result<(), Error> { @@ -219,6 +259,9 @@ impl StakingLedger { T::Currency::remove_lock(STAKING_ID, &ledger.stash); Ledger::::remove(controller); + >::remove(&stash); + >::remove(&stash); + Ok(()) })? } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index fd3ccd2a7b92..63e6acdfdf32 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -73,6 +73,10 @@ impl Pallet { StakingLedger::::get(account) } + pub fn payee(account: StakingAccount) -> RewardDestination { + StakingLedger::::reward_destination(account) + } + /// Fetches the controller bonded to a stash account, if any. pub fn bonded(stash: &T::AccountId) -> Option { StakingLedger::::paired_account(Stash(stash.clone())) @@ -298,7 +302,7 @@ impl Pallet { /// Actually make a payment to a staker. This uses the currency's reward function /// to pay the right payee for the given staker account. fn make_payout(stash: &T::AccountId, amount: BalanceOf) -> Option> { - let dest = Self::payee(stash); + let dest = Self::payee(StakingAccount::Stash(stash.clone())); match dest { RewardDestination::Controller => Self::bonded(stash) .map(|controller| T::Currency::deposit_creating(&controller, amount)), @@ -668,10 +672,9 @@ impl Pallet { pub(crate) fn kill_stash(stash: &T::AccountId, num_slashing_spans: u32) -> DispatchResult { slashing::clear_stash_metadata::(&stash, num_slashing_spans)?; - // removes controller from `Bonded` and staking ledger from `Ledger`. + // removes controller from `Bonded` and staking ledger from `Ledger`, as well as reward + // setting of the stash in `Payee`. StakingLedger::::kill(&stash)?; - >::remove(&stash); - >::remove(&stash); Self::do_remove_validator(&stash); Self::do_remove_nominator(&stash); diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index ea9b897f4e94..567e0b639d1d 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -328,7 +328,6 @@ pub mod pallet { /// /// TWOX-NOTE: SAFE since `AccountId` is a secure hash. #[pallet::storage] - #[pallet::getter(fn payee)] pub type Payee = StorageMap<_, Twox64Concat, T::AccountId, RewardDestination, ValueQuery>; @@ -854,7 +853,7 @@ pub mod pallet { let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded { stash: stash.clone(), amount: value }); - let ledger: StakingLedger = StakingLedger::::new( + let ledger = StakingLedger::::new( stash.clone(), value, value, @@ -869,8 +868,7 @@ pub mod pallet { // You're auto-bonded forever, here. We might improve this by only bonding when // you actually validate/nominate and remove once you unbond __everything__. - ledger.bond()?; - >::insert(&stash, payee); + ledger.bond(payee)?; Ok(()) } @@ -1214,8 +1212,10 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; let ledger = Self::ledger(Controller(controller))?; - let stash = &ledger.stash; - >::insert(stash, payee); + let _ = ledger + .set_payee(payee) + .defensive_proof("ledger was retrieved from storage, thus its bonded; qed."); + Ok(()) } diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 34929c00b00a..0d84d503733e 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -597,10 +597,11 @@ pub fn do_slash( slashed_imbalance: &mut NegativeImbalanceOf, slash_era: EraIndex, ) { - let mut ledger = match Pallet::::ledger(sp_staking::StakingAccount::Stash(stash.clone())) { - Ok(ledger) => ledger, - Err(_) => return, // nothing to do. - }; + let mut ledger = + match Pallet::::ledger(sp_staking::StakingAccount::Stash(stash.clone())).defensive() { + Ok(ledger) => ledger, + Err(_) => return, // nothing to do. + }; let value = ledger.slash(value, T::Currency::minimum_balance(), slash_era); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index e6558c3c8fa1..bcc2705e7cbc 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +//173 This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 @@ -1063,7 +1063,7 @@ fn reward_destination_works() { mock::make_all_reward_payment(0); // Check that RewardDestination is Staked (default) - assert_eq!(Staking::payee(&11), RewardDestination::Staked); + assert_eq!(Staking::payee(11.into()), RewardDestination::Staked); // Check that reward went to the stash account of validator assert_eq!(Balances::free_balance(11), 1000 + total_payout_0); // Check that amount at stake increased accordingly @@ -1089,7 +1089,7 @@ fn reward_destination_works() { mock::make_all_reward_payment(1); // Check that RewardDestination is Stash - assert_eq!(Staking::payee(&11), RewardDestination::Stash); + assert_eq!(Staking::payee(11.into()), RewardDestination::Stash); // Check that reward went to the stash account assert_eq!(Balances::free_balance(11), 1000 + total_payout_0 + total_payout_1); // Check that amount at stake is NOT increased @@ -1118,7 +1118,7 @@ fn reward_destination_works() { mock::make_all_reward_payment(2); // Check that RewardDestination is Controller - assert_eq!(Staking::payee(&11), RewardDestination::Controller); + assert_eq!(Staking::payee(11.into()), RewardDestination::Controller); // Check that reward went to the controller account assert_eq!(Balances::free_balance(11), 23150 + total_payout_2); // Check that amount at stake is NOT increased @@ -5981,13 +5981,15 @@ mod ledger { assert!(>::get(&42).is_none()); let mut ledger: StakingLedger = StakingLedger::default_from(42); + let reward_dest = RewardDestination::Account(10); - assert_ok!(ledger.bond()); + assert_ok!(ledger.bond(reward_dest)); assert!(StakingLedger::::is_bonded(StakingAccount::Stash(42))); assert!(>::get(&42).is_some()); + assert_eq!(>::get(&42), reward_dest); // cannot bond again. - assert_noop!(ledger.bond(), Error::::AlreadyBonded); + assert_noop!(ledger.bond(reward_dest), Error::::AlreadyBonded); // once bonded, update works as expected. ledger.claimed_rewards = bounded_vec![1]; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index aeca30a133b9..37176979edab 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -40,13 +40,14 @@ pub type EraIndex = u32; /// Representation of a staking account, which may be a stash or controller account. /// /// Note: once the controller is completely deprecated, this enum can also be deprecated in favor of -/// the stash account. -#[derive(Copy, Clone, Debug)] +/// the stash account. Tracking issue: . +#[derive(Clone, Debug)] pub enum StakingAccount { Stash(AccountId), Controller(AccountId), } +#[cfg(not(feature = "no-std"))] impl From for StakingAccount { fn from(account: AccountId) -> Self { StakingAccount::Stash(account) From ad9b5acd915a6b84a42ee15a8321ea8ae51a287f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 7 Aug 2023 16:03:43 +0200 Subject: [PATCH 41/47] Update frame/staking/src/ledger.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/staking/src/ledger.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 927a342ce9ba..848761354f1e 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -18,7 +18,7 @@ //! A Ledger implementation for stakers. //! //! A [`StakingLedger`] encapsulates all the state and logic related to the stake of bonded -//! stakers, namely: +//! stakers, namely, it handles the following storage items: //! * [`Bonded`]: mutates and reads the state of the controller <> stash bond map (to be deprecated //! soon); //! * [`Ledger`]: mutates and reads the state of all the stakers. The [`Ledger`] storage item stores From e1093e51fa8cd12dd503b8fb21927b7983737298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 7 Aug 2023 17:42:25 +0200 Subject: [PATCH 42/47] addresses PR comments pt2 --- frame/staking/src/pallet/impls.rs | 3 +-- frame/staking/src/pallet/mod.rs | 6 +++--- frame/staking/src/tests.rs | 11 ++++------- primitives/staking/src/lib.rs | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 63e6acdfdf32..bf44d9deaa05 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1686,8 +1686,7 @@ impl StakingInterface for Pallet { fn status( who: &Self::AccountId, ) -> Result, DispatchError> { - let is_bonded = Self::bonded(who).is_some(); - if !is_bonded { + if !StakingLedger::::is_bonded(StakingAccount::Stash(who.clone())) { return Err(Error::::NotStash.into()) } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 567e0b639d1d..9571d1999e3a 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -701,7 +701,6 @@ pub mod pallet { ForceEra { mode: Forcing }, } - #[derive(PartialEq)] #[pallet::error] pub enum Error { /// Not a controller account. @@ -836,8 +835,9 @@ pub mod pallet { ) -> DispatchResult { let stash = ensure_signed(origin)?; - Self::ledger(StakingAccount::Stash(stash.clone())) - .map_or(Ok(()), |_| Err(Error::::AlreadyBonded))?; + if StakingLedger::::is_bonded(StakingAccount::Stash(stash.clone())) { + return Err(Error::::AlreadyBonded.into()) + } // Reject a bond which is considered to be _dust_. if value < T::Currency::minimum_balance() { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bcc2705e7cbc..0737c6729855 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -21,7 +21,7 @@ use super::{ConfigOp, Event, *}; use crate::ledger::StakingLedgerInspect; use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ - assert_err, assert_noop, assert_ok, assert_storage_noop, bounded_vec, + assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, pallet_prelude::*, traits::{Currency, Get, ReservableCurrency}, @@ -170,7 +170,7 @@ fn basic_setup_works() { } ); // Account 1 does not control any stash - assert_err!(Staking::ledger(1.into()), Error::::NotStash); + assert!(Staking::ledger(1.into()).is_err()); // ValidatorPrefs are default assert_eq_uvec!( @@ -5935,10 +5935,7 @@ mod ledger { fn get_ledger_works() { ExtBuilder::default().build_and_execute(|| { // stash does not exist - assert_noop!( - StakingLedger::::get(StakingAccount::Stash(42)), - Error::::NotStash - ); + assert!(StakingLedger::::get(StakingAccount::Stash(42)).is_err()); // bonded and paired assert_eq!(>::get(&11), Some(11)); @@ -5989,7 +5986,7 @@ mod ledger { assert_eq!(>::get(&42), reward_dest); // cannot bond again. - assert_noop!(ledger.bond(reward_dest), Error::::AlreadyBonded); + assert!(ledger.bond(reward_dest).is_err()); // once bonded, update works as expected. ledger.claimed_rewards = bounded_vec![1]; diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 37176979edab..d412cd032591 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -47,7 +47,7 @@ pub enum StakingAccount { Controller(AccountId), } -#[cfg(not(feature = "no-std"))] +#[cfg(feature = "std")] impl From for StakingAccount { fn from(account: AccountId) -> Self { StakingAccount::Stash(account) From 17abac817540a7734a3dd5e99c1e6b0a62867d97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Mon, 7 Aug 2023 18:31:18 +0200 Subject: [PATCH 43/47] improvements pt3. ledger consume itself on update --- frame/staking/src/ledger.rs | 4 ++-- frame/staking/src/pallet/impls.rs | 24 ++++++++++-------------- frame/staking/src/pallet/mod.rs | 25 +++++++++++++------------ frame/staking/src/tests.rs | 4 ++-- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 848761354f1e..10630d8d4d8b 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -210,7 +210,7 @@ impl StakingLedger { /// /// Note: To ensure lock consistency, all the [`Ledger`] storage updates should be made through /// this helper function. - pub(crate) fn update(&self) -> Result<(), Error> { + pub(crate) fn update(self) -> Result<(), Error> { if !>::contains_key(&self.stash) { return Err(Error::::NotStash) } @@ -230,7 +230,7 @@ impl StakingLedger { /// Bonds a ledger. /// /// It sets the reward preferences for the bonded stash. - pub(crate) fn bond(&self, payee: RewardDestination) -> Result<(), Error> { + pub(crate) fn bond(self, payee: RewardDestination) -> Result<(), Error> { if >::contains_key(&self.stash) { Err(Error::::AlreadyBonded) } else { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index bf44d9deaa05..e057ef19ce2e 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -124,6 +124,7 @@ impl Pallet { if let Some(current_era) = Self::current_era() { ledger = ledger.consolidate_unlocked(current_era) } + let new_total = ledger.total; let used_weight = if ledger.unlocking.is_empty() && ledger.active < T::Currency::minimum_balance() { @@ -143,9 +144,9 @@ impl Pallet { // `old_total` should never be less than the new total because // `consolidate_unlocked` strictly subtracts balance. - if ledger.total < old_total { + if new_total < old_total { // Already checked that this won't overflow by entry condition. - let value = old_total - ledger.total; + let value = old_total - new_total; Self::deposit_event(Event::::Withdrawn { stash, amount: value }); } @@ -183,6 +184,7 @@ impl Pallet { Err(Error::::NotStash.with_weight(T::WeightInfo::payout_stakers_alive_staked(0))) } })?; + let stash = ledger.stash.clone(); ledger .claimed_rewards @@ -201,7 +203,7 @@ impl Pallet { .defensive_map_err(|_| Error::::BoundNotMet)?, } - let exposure = >::get(&era, &ledger.stash); + let exposure = >::get(&era, &stash); // Input data seems good, no errors allowed after this point @@ -216,11 +218,8 @@ impl Pallet { let era_reward_points = >::get(&era); let total_reward_points = era_reward_points.total; - let validator_reward_points = era_reward_points - .individual - .get(&ledger.stash) - .copied() - .unwrap_or_else(Zero::zero); + let validator_reward_points = + era_reward_points.individual.get(&stash).copied().unwrap_or_else(Zero::zero); // Nothing to do if they have no reward points. if validator_reward_points.is_zero() { @@ -247,18 +246,15 @@ impl Pallet { Self::deposit_event(Event::::PayoutStarted { era_index: era, - validator_stash: ledger.stash.clone(), + validator_stash: stash.clone(), }); let mut total_imbalance = PositiveImbalanceOf::::zero(); // We can now make total validator payout: if let Some(imbalance) = - Self::make_payout(&ledger.stash, validator_staking_payout + validator_commission_payout) + Self::make_payout(&stash, validator_staking_payout + validator_commission_payout) { - Self::deposit_event(Event::::Rewarded { - stash: ledger.stash, - amount: imbalance.peek(), - }); + Self::deposit_event(Event::::Rewarded { stash, amount: imbalance.peek() }); total_imbalance.subsume(imbalance); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 9571d1999e3a..7a7f76b0d0d7 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -912,8 +912,7 @@ pub mod pallet { ledger.update()?; // update this staker in the sorted list, if they exist in it. if T::VoterList::contains(&stash) { - let _ = - T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); + let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); } Self::deposit_event(Event::::Bonded { stash, amount: extra }); @@ -968,6 +967,7 @@ pub mod pallet { // to `Self::do_withdraw_unbonded` above. let mut ledger = Self::ledger(Controller(controller))?; let mut value = value.min(ledger.active); + let stash = ledger.stash.clone(); ensure!( ledger.unlocking.len() < T::MaxUnlockingChunks::get() as usize, @@ -983,9 +983,9 @@ pub mod pallet { ledger.active = Zero::zero(); } - let min_active_bond = if Nominators::::contains_key(&ledger.stash) { + let min_active_bond = if Nominators::::contains_key(&stash) { MinNominatorBond::::get() - } else if Validators::::contains_key(&ledger.stash) { + } else if Validators::::contains_key(&stash) { MinValidatorBond::::get() } else { Zero::zero() @@ -1012,12 +1012,11 @@ pub mod pallet { ledger.update()?; // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); + if T::VoterList::contains(&stash) { + let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); } - Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); + Self::deposit_event(Event::::Unbonded { stash, amount: value }); } let actual_weight = if let Some(withdraw_weight) = maybe_withdraw_weight { @@ -1506,16 +1505,18 @@ pub mod pallet { amount: rebonded_value, }); + let stash = ledger.stash.clone(); + let final_unlocking = ledger.unlocking.len(); + // NOTE: ledger must be updated prior to calling `Self::weight_of`. ledger.update()?; - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); + if T::VoterList::contains(&stash) { + let _ = T::VoterList::on_update(&stash, Self::weight_of(&stash)).defensive(); } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) - .saturating_sub(ledger.unlocking.len() as u32); + .saturating_sub(final_unlocking as u32); Ok(Some(T::WeightInfo::rebond(removed_chunks)).into()) } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 0737c6729855..b3ff57091e48 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5980,13 +5980,13 @@ mod ledger { let mut ledger: StakingLedger = StakingLedger::default_from(42); let reward_dest = RewardDestination::Account(10); - assert_ok!(ledger.bond(reward_dest)); + assert_ok!(ledger.clone().bond(reward_dest)); assert!(StakingLedger::::is_bonded(StakingAccount::Stash(42))); assert!(>::get(&42).is_some()); assert_eq!(>::get(&42), reward_dest); // cannot bond again. - assert!(ledger.bond(reward_dest).is_err()); + assert!(ledger.clone().bond(reward_dest).is_err()); // once bonded, update works as expected. ledger.claimed_rewards = bounded_vec![1]; From c26b8e5a496bd495edffbb661f6c0b261c0726fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 10 Aug 2023 21:41:19 +0200 Subject: [PATCH 44/47] Moves back StakingLedger main impl to src/lib.rs to improve diff --- frame/staking/src/ledger.rs | 260 ++---------------------------------- frame/staking/src/lib.rs | 257 ++++++++++++++++++++++++++++++++++- frame/staking/src/tests.rs | 2 +- 3 files changed, 264 insertions(+), 255 deletions(-) diff --git a/frame/staking/src/ledger.rs b/frame/staking/src/ledger.rs index 10630d8d4d8b..6599ffd9a477 100644 --- a/frame/staking/src/ledger.rs +++ b/frame/staking/src/ledger.rs @@ -31,63 +31,25 @@ //! performed through the methods exposed by the [`StakingLedger`] implementation in order to ensure //! state consistency. -use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ defensive, - traits::{Defensive, Get, LockableCurrency, WithdrawReasons}, - BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + traits::{LockableCurrency, WithdrawReasons}, + BoundedVec, }; -use scale_info::TypeInfo; -use sp_runtime::{traits::Zero, Perquintill, Rounding, Saturating}; -use sp_staking::{EraIndex, OnStakingUpdate, StakingAccount}; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_staking::{EraIndex, StakingAccount}; +use sp_std::prelude::*; use crate::{ - log, BalanceOf, Bonded, Config, Error, Ledger, Payee, RewardDestination, UnlockChunk, + BalanceOf, Bonded, Config, Error, Ledger, Payee, RewardDestination, StakingLedger, UnlockChunk, STAKING_ID, }; -/// The ledger of a (bonded) stash. -/// -/// Note: All the reads and mutations to the [`Ledger`], [`Bonded`] and [`Payee`] storage items -/// *MUST* be performed through the methods exposed by this struct, to ensure the consistency of -/// ledger's data and corresponding staking lock -#[derive( - PartialEqNoBound, - EqNoBound, - CloneNoBound, - Encode, - Decode, - RuntimeDebugNoBound, - TypeInfo, - MaxEncodedLen, -)] -#[scale_info(skip_type_params(T))] -pub struct StakingLedger { - /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, - /// The total amount of the stash's balance that we are currently accounting for. - /// It's just `active` plus all the `unlocking` balances. - #[codec(compact)] - pub total: BalanceOf, - /// The total amount of the stash's balance that will be at stake in any forthcoming - /// rounds. - #[codec(compact)] - pub active: BalanceOf, - /// Any balance that is becoming free, which may eventually be transferred out of the stash - /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first - /// in, first out queue where the new (higher value) eras get pushed on the back. - pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, - /// List of eras for which the stakers behind a validator have claimed rewards. Only updated - /// for validators. - pub claimed_rewards: BoundedVec, - /// The controller associated with this ledger's stash. - /// - /// This is not stored on-chain, and is only bundled when the ledger is read from storage. - /// Use [`controller`] function to get the controller associated with the ledger. - #[codec(skip)] - controller: Option, -} +#[cfg(any(feature = "runtime-benchmarks", test))] +use { + codec::{Decode, Encode, MaxEncodedLen}, + scale_info::TypeInfo, + sp_runtime::traits::Zero, +}; impl StakingLedger { #[cfg(any(feature = "runtime-benchmarks", test))] @@ -265,206 +227,6 @@ impl StakingLedger { Ok(()) })? } - - /// Remove entries from `unlocking` that are sufficiently old and reduce the - /// total by the sum of their balances. - pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { - let mut total = self.total; - let unlocking: BoundedVec<_, _> = self - .unlocking - .into_iter() - .filter(|chunk| { - if chunk.era > current_era { - true - } else { - total = total.saturating_sub(chunk.value); - false - } - }) - .collect::>() - .try_into() - .expect( - "filtering items from a bounded vec always leaves length less than bounds. qed", - ); - - Self { - stash: self.stash, - total, - active: self.active, - unlocking, - claimed_rewards: self.claimed_rewards, - controller: self.controller, - } - } - - /// Re-bond funds that were scheduled for unlocking. - /// - /// Returns the updated ledger, and the amount actually rebonded. - pub(crate) fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { - let mut unlocking_balance = BalanceOf::::zero(); - - while let Some(last) = self.unlocking.last_mut() { - if unlocking_balance + last.value <= value { - unlocking_balance += last.value; - self.active += last.value; - self.unlocking.pop(); - } else { - let diff = value - unlocking_balance; - - unlocking_balance += diff; - self.active += diff; - last.value -= diff; - } - - if unlocking_balance >= value { - break - } - } - - (self, unlocking_balance) - } - - /// Slash the staker for a given amount of balance. - /// - /// This implements a proportional slashing system, whereby we set our preference to slash as - /// such: - /// - /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + - /// bonding_duration` and onwards, the slash is divided equally between the active ledger and - /// the unlocking chunks. - /// - If no such chunks exist, then only the active balance is slashed. - /// - /// Note that the above is only a *preference*. If for any reason the active ledger, with or - /// without some portion of the unlocking chunks that are more justified to be slashed are not - /// enough, then the slashing will continue and will consume as much of the active and unlocking - /// chunks as needed. - /// - /// This will never slash more than the given amount. If any of the chunks become dusted, the - /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually - /// slashed. - /// - /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. - /// - /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was - /// applied. - pub(crate) fn slash( - &mut self, - slash_amount: BalanceOf, - minimum_balance: BalanceOf, - slash_era: EraIndex, - ) -> BalanceOf { - if slash_amount.is_zero() { - return Zero::zero() - } - - use sp_runtime::PerThing as _; - let mut remaining_slash = slash_amount; - let pre_slash_total = self.total; - - // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` - // (assuming 28 is the bonding duration) onwards should be slashed. - let slashable_chunks_start = slash_era + T::BondingDuration::get(); - - // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we - // slash first the active chunk, and then `slash_chunks_priority`. - let (maybe_proportional, slash_chunks_priority) = { - if let Some(first_slashable_index) = - self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) - { - // If there exists a chunk who's after the first_slashable_start, then this is a - // proportional slash, because we want to slash active and these chunks - // proportionally. - - // The indices of the first chunk after the slash up through the most recent chunk. - // (The most recent chunk is at greatest from this era) - let affected_indices = first_slashable_index..self.unlocking.len(); - let unbonding_affected_balance = - affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { - if let Some(chunk) = self.unlocking.get(i).defensive() { - sum.saturating_add(chunk.value) - } else { - sum - } - }); - let affected_balance = self.active.saturating_add(unbonding_affected_balance); - let ratio = Perquintill::from_rational_with_rounding( - slash_amount, - affected_balance, - Rounding::Up, - ) - .unwrap_or_else(|_| Perquintill::one()); - ( - Some(ratio), - affected_indices.chain((0..first_slashable_index).rev()).collect::>(), - ) - } else { - // We just slash from the last chunk to the most recent one, if need be. - (None, (0..self.unlocking.len()).rev().collect::>()) - } - }; - - // Helper to update `target` and the ledgers total after accounting for slashing `target`. - log!( - debug, - "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", - slash_amount, - slash_era, - self, - slash_chunks_priority, - maybe_proportional, - ); - - let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { - let mut slash_from_target = if let Some(ratio) = maybe_proportional { - ratio.mul_ceil(*target) - } else { - *slash_remaining - } - // this is the total that that the slash target has. We can't slash more than - // this anyhow! - .min(*target) - // this is the total amount that we would have wanted to slash - // non-proportionally, a proportional slash should never exceed this either! - .min(*slash_remaining); - - // slash out from *target exactly `slash_from_target`. - *target = *target - slash_from_target; - if *target < minimum_balance { - // Slash the rest of the target if it's dust. This might cause the last chunk to be - // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big - // deal. - slash_from_target = - sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) - } - - self.total = self.total.saturating_sub(slash_from_target); - *slash_remaining = slash_remaining.saturating_sub(slash_from_target); - }; - - // If this is *not* a proportional slash, the active will always wiped to 0. - slash_out_of(&mut self.active, &mut remaining_slash); - - let mut slashed_unlocking = BTreeMap::<_, _>::new(); - for i in slash_chunks_priority { - if remaining_slash.is_zero() { - break - } - - if let Some(chunk) = self.unlocking.get_mut(i).defensive() { - slash_out_of(&mut chunk.value, &mut remaining_slash); - // write the new slashed value of this chunk to the map. - slashed_unlocking.insert(chunk.era, chunk.value); - } else { - break - } - } - - // clean unlocking chunks that are set to zero. - self.unlocking.retain(|c| !c.value.is_zero()); - - T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); - pre_slash_total.saturating_sub(self.total) - } } // This structs makes it easy to write tests to compare staking ledgers fetched from storage. This diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index ca616bf229bf..56a76bf9f662 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -303,21 +303,20 @@ mod pallet; use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{ConstU32, Currency, Get, LockIdentifier}, + traits::{ConstU32, Currency, Defensive, Get, LockIdentifier}, weights::Weight, - BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, }; -use ledger::StakingLedger; use scale_info::TypeInfo; use sp_runtime::{ curve::PiecewiseLinear, traits::{AtLeast32BitUnsigned, Convert, StaticLookup, Zero}, - Perbill, RuntimeDebug, + Perbill, Perquintill, Rounding, RuntimeDebug, Saturating, }; pub use sp_staking::StakerStatus; use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, - EraIndex, SessionIndex, StakingAccount, + EraIndex, OnStakingUpdate, SessionIndex, StakingAccount, }; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use weights::WeightInfo; @@ -435,6 +434,254 @@ pub struct UnlockChunk { era: EraIndex, } +/// The ledger of a (bonded) stash. +/// +/// Note: All the reads and mutations to the [`Ledger`], [`Bonded`] and [`Payee`] storage items +/// *MUST* be performed through the methods exposed by this struct, to ensure the consistency of +/// ledger's data and corresponding staking lock +/// +/// TODO: move struct definition and full implementation into `/src/ledger.rs`. Currently +/// leaving here to enforce a clean PR diff, given how critical this logic is. Tracking issue +/// . +#[derive( + PartialEqNoBound, + EqNoBound, + CloneNoBound, + Encode, + Decode, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(T))] +pub struct StakingLedger { + /// The stash account whose balance is actually locked and at stake. + pub stash: T::AccountId, + /// The total amount of the stash's balance that we are currently accounting for. + /// It's just `active` plus all the `unlocking` balances. + #[codec(compact)] + pub total: BalanceOf, + /// The total amount of the stash's balance that will be at stake in any forthcoming + /// rounds. + #[codec(compact)] + pub active: BalanceOf, + /// Any balance that is becoming free, which may eventually be transferred out of the stash + /// (assuming it doesn't get slashed first). It is assumed that this will be treated as a first + /// in, first out queue where the new (higher value) eras get pushed on the back. + pub unlocking: BoundedVec>, T::MaxUnlockingChunks>, + /// List of eras for which the stakers behind a validator have claimed rewards. Only updated + /// for validators. + pub claimed_rewards: BoundedVec, + /// The controller associated with this ledger's stash. + /// + /// This is not stored on-chain, and is only bundled when the ledger is read from storage. + /// Use [`controller`] function to get the controller associated with the ledger. + #[codec(skip)] + controller: Option, +} + +impl StakingLedger { + /// Remove entries from `unlocking` that are sufficiently old and reduce the + /// total by the sum of their balances. + pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + let mut total = self.total; + let unlocking: BoundedVec<_, _> = self + .unlocking + .into_iter() + .filter(|chunk| { + if chunk.era > current_era { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }) + .collect::>() + .try_into() + .expect( + "filtering items from a bounded vec always leaves length less than bounds. qed", + ); + + Self { + stash: self.stash, + total, + active: self.active, + unlocking, + claimed_rewards: self.claimed_rewards, + controller: self.controller, + } + } + + /// Re-bond funds that were scheduled for unlocking. + /// + /// Returns the updated ledger, and the amount actually rebonded. + pub(crate) fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + let mut unlocking_balance = BalanceOf::::zero(); + + while let Some(last) = self.unlocking.last_mut() { + if unlocking_balance + last.value <= value { + unlocking_balance += last.value; + self.active += last.value; + self.unlocking.pop(); + } else { + let diff = value - unlocking_balance; + + unlocking_balance += diff; + self.active += diff; + last.value -= diff; + } + + if unlocking_balance >= value { + break + } + } + + (self, unlocking_balance) + } + + /// Slash the staker for a given amount of balance. + /// + /// This implements a proportional slashing system, whereby we set our preference to slash as + /// such: + /// + /// - If any unlocking chunks exist that are scheduled to be unlocked at `slash_era + + /// bonding_duration` and onwards, the slash is divided equally between the active ledger and + /// the unlocking chunks. + /// - If no such chunks exist, then only the active balance is slashed. + /// + /// Note that the above is only a *preference*. If for any reason the active ledger, with or + /// without some portion of the unlocking chunks that are more justified to be slashed are not + /// enough, then the slashing will continue and will consume as much of the active and unlocking + /// chunks as needed. + /// + /// This will never slash more than the given amount. If any of the chunks become dusted, the + /// last chunk is slashed slightly less to compensate. Returns the amount of funds actually + /// slashed. + /// + /// `slash_era` is the era in which the slash (which is being enacted now) actually happened. + /// + /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was + /// applied. + pub(crate) fn slash( + &mut self, + slash_amount: BalanceOf, + minimum_balance: BalanceOf, + slash_era: EraIndex, + ) -> BalanceOf { + if slash_amount.is_zero() { + return Zero::zero() + } + + use sp_runtime::PerThing as _; + let mut remaining_slash = slash_amount; + let pre_slash_total = self.total; + + // for a `slash_era = x`, any chunk that is scheduled to be unlocked at era `x + 28` + // (assuming 28 is the bonding duration) onwards should be slashed. + let slashable_chunks_start = slash_era + T::BondingDuration::get(); + + // `Some(ratio)` if this is proportional, with `ratio`, `None` otherwise. In both cases, we + // slash first the active chunk, and then `slash_chunks_priority`. + let (maybe_proportional, slash_chunks_priority) = { + if let Some(first_slashable_index) = + self.unlocking.iter().position(|c| c.era >= slashable_chunks_start) + { + // If there exists a chunk who's after the first_slashable_start, then this is a + // proportional slash, because we want to slash active and these chunks + // proportionally. + + // The indices of the first chunk after the slash up through the most recent chunk. + // (The most recent chunk is at greatest from this era) + let affected_indices = first_slashable_index..self.unlocking.len(); + let unbonding_affected_balance = + affected_indices.clone().fold(BalanceOf::::zero(), |sum, i| { + if let Some(chunk) = self.unlocking.get(i).defensive() { + sum.saturating_add(chunk.value) + } else { + sum + } + }); + let affected_balance = self.active.saturating_add(unbonding_affected_balance); + let ratio = Perquintill::from_rational_with_rounding( + slash_amount, + affected_balance, + Rounding::Up, + ) + .unwrap_or_else(|_| Perquintill::one()); + ( + Some(ratio), + affected_indices.chain((0..first_slashable_index).rev()).collect::>(), + ) + } else { + // We just slash from the last chunk to the most recent one, if need be. + (None, (0..self.unlocking.len()).rev().collect::>()) + } + }; + + // Helper to update `target` and the ledgers total after accounting for slashing `target`. + log!( + debug, + "slashing {:?} for era {:?} out of {:?}, priority: {:?}, proportional = {:?}", + slash_amount, + slash_era, + self, + slash_chunks_priority, + maybe_proportional, + ); + + let mut slash_out_of = |target: &mut BalanceOf, slash_remaining: &mut BalanceOf| { + let mut slash_from_target = if let Some(ratio) = maybe_proportional { + ratio.mul_ceil(*target) + } else { + *slash_remaining + } + // this is the total that that the slash target has. We can't slash more than + // this anyhow! + .min(*target) + // this is the total amount that we would have wanted to slash + // non-proportionally, a proportional slash should never exceed this either! + .min(*slash_remaining); + + // slash out from *target exactly `slash_from_target`. + *target = *target - slash_from_target; + if *target < minimum_balance { + // Slash the rest of the target if it's dust. This might cause the last chunk to be + // slightly under-slashed, by at most `MaxUnlockingChunks * ED`, which is not a big + // deal. + slash_from_target = + sp_std::mem::replace(target, Zero::zero()).saturating_add(slash_from_target) + } + + self.total = self.total.saturating_sub(slash_from_target); + *slash_remaining = slash_remaining.saturating_sub(slash_from_target); + }; + + // If this is *not* a proportional slash, the active will always wiped to 0. + slash_out_of(&mut self.active, &mut remaining_slash); + + let mut slashed_unlocking = BTreeMap::<_, _>::new(); + for i in slash_chunks_priority { + if remaining_slash.is_zero() { + break + } + + if let Some(chunk) = self.unlocking.get_mut(i).defensive() { + slash_out_of(&mut chunk.value, &mut remaining_slash); + // write the new slashed value of this chunk to the map. + slashed_unlocking.insert(chunk.era, chunk.value); + } else { + break + } + } + + // clean unlocking chunks that are set to zero. + self.unlocking.retain(|c| !c.value.is_zero()); + + T::EventListeners::on_slash(&self.stash, self.active, &slashed_unlocking); + pre_slash_total.saturating_sub(self.total) + } +} + /// A record of the nominations made by a specific account. #[derive( PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 18713a8221b3..793df5f336b7 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -1,4 +1,4 @@ -//173 This file is part of Substrate. +// This file is part of Substrate. // Copyright (C) Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 From c07a79f3e65cb55ef2621d0b078fd3def11ffd63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 10 Aug 2023 21:47:15 +0200 Subject: [PATCH 45/47] adds tracking issue for moving StakingLedger impl --- frame/staking/src/lib.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 56a76bf9f662..a2bbb1a35a72 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -442,7 +442,7 @@ pub struct UnlockChunk { /// /// TODO: move struct definition and full implementation into `/src/ledger.rs`. Currently /// leaving here to enforce a clean PR diff, given how critical this logic is. Tracking issue -/// . +/// . #[derive( PartialEqNoBound, EqNoBound, @@ -483,7 +483,7 @@ pub struct StakingLedger { impl StakingLedger { /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. - pub(crate) fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + pub fn consolidate_unlocked(self, current_era: EraIndex) -> Self { let mut total = self.total; let unlocking: BoundedVec<_, _> = self .unlocking @@ -515,7 +515,7 @@ impl StakingLedger { /// Re-bond funds that were scheduled for unlocking. /// /// Returns the updated ledger, and the amount actually rebonded. - pub(crate) fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + pub fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { @@ -562,7 +562,7 @@ impl StakingLedger { /// /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was /// applied. - pub(crate) fn slash( + pub fn slash( &mut self, slash_amount: BalanceOf, minimum_balance: BalanceOf, From aee88cc553ebc103fd7713a25fff787f2b1f72f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 10 Aug 2023 22:03:25 +0200 Subject: [PATCH 46/47] rollback to previous impl to decrease diff --- frame/staking/src/lib.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index a2bbb1a35a72..8b34e8a14273 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -483,7 +483,7 @@ pub struct StakingLedger { impl StakingLedger { /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. - pub fn consolidate_unlocked(self, current_era: EraIndex) -> Self { + fn consolidate_unlocked(self, current_era: EraIndex) -> Self { let mut total = self.total; let unlocking: BoundedVec<_, _> = self .unlocking @@ -515,7 +515,7 @@ impl StakingLedger { /// Re-bond funds that were scheduled for unlocking. /// /// Returns the updated ledger, and the amount actually rebonded. - pub fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { + fn rebond(mut self, value: BalanceOf) -> (Self, BalanceOf) { let mut unlocking_balance = BalanceOf::::zero(); while let Some(last) = self.unlocking.last_mut() { @@ -562,7 +562,7 @@ impl StakingLedger { /// /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was /// applied. - pub fn slash( + fn slash( &mut self, slash_amount: BalanceOf, minimum_balance: BalanceOf, From 073ec379e8962e63114b1d49760ac240abcf8780 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gon=C3=A7alo=20Pestana?= Date: Thu, 10 Aug 2023 22:05:08 +0200 Subject: [PATCH 47/47] nit --- frame/staking/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 8b34e8a14273..96b76f43053e 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -562,7 +562,7 @@ impl StakingLedger { /// /// This calls `Config::OnStakingUpdate::on_slash` with information as to how the slash was /// applied. - fn slash( + pub fn slash( &mut self, slash_amount: BalanceOf, minimum_balance: BalanceOf,