Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

improve staking interface methods #14023

Merged
merged 9 commits into from
Apr 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions frame/nomination-pools/benchmarking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,12 +684,12 @@ frame_benchmarking::benchmarks! {
.collect();

assert_ok!(T::Staking::nominate(&pool_account, validators));
assert!(T::Staking::nominations(Pools::<T>::create_bonded_account(1)).is_some());
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_some());

whitelist_account!(depositor);
}:_(RuntimeOrigin::Signed(depositor.clone()), 1)
verify {
assert!(T::Staking::nominations(Pools::<T>::create_bonded_account(1)).is_none());
assert!(T::Staking::nominations(&Pools::<T>::create_bonded_account(1)).is_none());
}

set_commission {
Expand Down
12 changes: 10 additions & 2 deletions frame/nomination-pools/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,14 @@ impl sp_staking::StakingInterface for StakingMock {
BondingDuration::get()
}

fn status(
_: &Self::AccountId,
) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
Nominations::get()
.map(|noms| sp_staking::StakerStatus::Nominator(noms))
.ok_or(DispatchError::Other("NotStash"))
}

fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult {
let mut x = BondedBalanceMap::get();
x.get_mut(who).map(|v| *v += extra);
Expand Down Expand Up @@ -108,15 +116,15 @@ impl sp_staking::StakingInterface for StakingMock {
}

#[cfg(feature = "runtime-benchmarks")]
fn nominations(_: Self::AccountId) -> Option<Vec<Self::AccountId>> {
fn nominations(_: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
Nominations::get()
}

fn stash_by_ctrl(_controller: &Self::AccountId) -> Result<Self::AccountId, DispatchError> {
unimplemented!("method currently not used in testing")
}

fn stake(who: &Self::AccountId) -> Result<Stake<Self>, DispatchError> {
fn stake(who: &Self::AccountId) -> Result<Stake<Balance>, DispatchError> {
match (
UnbondingBalanceMap::get().get(who).copied(),
BondedBalanceMap::get().get(who).copied(),
Expand Down
13 changes: 1 addition & 12 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -311,6 +311,7 @@ use sp_runtime::{
traits::{AtLeast32BitUnsigned, Convert, Saturating, StaticLookup, Zero},
Perbill, Perquintill, Rounding, RuntimeDebug,
};
pub use sp_staking::StakerStatus;
use sp_staking::{
offence::{Offence, OffenceError, ReportOffence},
EraIndex, SessionIndex,
Expand Down Expand Up @@ -381,18 +382,6 @@ impl<AccountId: Ord> Default for EraRewardPoints<AccountId> {
}
}

/// Indicates the initial status of the staker.
#[derive(RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, Clone))]
pub enum StakerStatus<AccountId> {
/// Chilling.
Idle,
/// Declared desire in validating or already participating in it.
Validator,
/// Nominating for a group of other stakers.
Nominator(Vec<AccountId>),
}

/// A destination account for payment.
#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)]
pub enum RewardDestination<AccountId> {
Expand Down
30 changes: 28 additions & 2 deletions frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ use frame_election_provider_support::{
SortedListProvider, VoteWeight, VoterOf,
};
use frame_support::{
defensive,
dispatch::WithPostDispatchInfo,
pallet_prelude::*,
traits::{
Expand Down Expand Up @@ -1608,7 +1609,7 @@ impl<T: Config> StakingInterface for Pallet<T> {
Self::current_era().unwrap_or(Zero::zero())
}

fn stake(who: &Self::AccountId) -> Result<Stake<Self>, DispatchError> {
fn stake(who: &Self::AccountId) -> Result<Stake<BalanceOf<T>>, DispatchError> {
Self::bonded(who)
.and_then(|c| Self::ledger(c))
.map(|l| Stake { total: l.total, active: l.active })
Expand Down Expand Up @@ -1662,8 +1663,33 @@ impl<T: Config> StakingInterface for Pallet<T> {
Self::nominate(RawOrigin::Signed(ctrl).into(), targets)
}

fn status(
who: &Self::AccountId,
) -> Result<sp_staking::StakerStatus<Self::AccountId>, DispatchError> {
let is_bonded = Self::bonded(who).is_some();
if !is_bonded {
return Err(Error::<T>::NotStash.into())
}

let is_validator = Validators::<T>::contains_key(&who);
let is_nominator = Nominators::<T>::get(&who);

use sp_staking::StakerStatus;
match (is_validator, is_nominator.is_some()) {
(false, false) => Ok(StakerStatus::Idle),
(true, false) => Ok(StakerStatus::Validator),
(false, true) => Ok(StakerStatus::Nominator(
is_nominator.expect("is checked above; qed").targets.into_inner(),
)),
(true, true) => {
defensive!("cannot be both validators and nominator");
Err(Error::<T>::BadState.into())
},
}
}

sp_staking::runtime_benchmarks_enabled! {
fn nominations(who: Self::AccountId) -> Option<Vec<T::AccountId>> {
fn nominations(who: &Self::AccountId) -> Option<Vec<T::AccountId>> {
Nominators::<T>::get(who).map(|n| n.targets.into_inner())
}

Expand Down
23 changes: 23 additions & 0 deletions frame/staking/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5824,4 +5824,27 @@ mod staking_interface {
));
});
}

#[test]
fn status() {
ExtBuilder::default().build_and_execute(|| {
// stash of a validator is identified as a validator
assert_eq!(Staking::status(&11).unwrap(), StakerStatus::Validator);
// .. but not the controller.
assert!(Staking::status(&10).is_err());

// stash of nominator is identified as a nominator
assert_eq!(Staking::status(&101).unwrap(), StakerStatus::Nominator(vec![11, 21]));
// .. but not the controller.
assert!(Staking::status(&100).is_err());

// stash of chilled is identified as a chilled
assert_eq!(Staking::status(&41).unwrap(), StakerStatus::Idle);
// .. but not the controller.
assert!(Staking::status(&40).is_err());

// random other account.
assert!(Staking::status(&42).is_err());
})
}
}
2 changes: 2 additions & 0 deletions primitives/staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ readme = "README.md"
targets = ["x86_64-unknown-linux-gnu"]

[dependencies]
serde = { version = "1.0.136", optional = true }
codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
sp-core = { version = "7.0.0", default-features = false, path = "../core" }
Expand All @@ -22,6 +23,7 @@ sp-std = { version = "5.0.0", default-features = false, path = "../std" }
[features]
default = ["std"]
std = [
"serde",
"codec/std",
"scale-info/std",
"sp-core/std",
Expand Down
38 changes: 32 additions & 6 deletions primitives/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
//! A crate which contains primitives that are useful for implementation that uses staking
//! approaches in general. Definitions related to sessions, slashing, etc go here.

use scale_info::TypeInfo;
use sp_core::RuntimeDebug;
use sp_runtime::{DispatchError, DispatchResult};
use sp_std::{collections::btree_map::BTreeMap, vec::Vec};

Expand All @@ -31,6 +33,18 @@ pub type SessionIndex = u32;
/// Counter for the number of eras that have passed.
pub type EraIndex = u32;

/// Representation of the status of a staker.
#[derive(RuntimeDebug, TypeInfo)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize, PartialEq, Eq, Clone))]
pub enum StakerStatus<AccountId> {
/// Chilling.
Idle,
/// Declaring desire in validate, i.e author blocks.
Validator,
/// Declaring desire to nominate, delegate, or generally approve of the given set of others.
Nominator(Vec<AccountId>),
}

/// Trait describing something that implements a hook for any operations to perform when a staker is
/// slashed.
pub trait OnStakerSlash<AccountId, Balance> {
Expand All @@ -57,7 +71,7 @@ impl<AccountId, Balance> OnStakerSlash<AccountId, Balance> for () {

/// A struct that reflects stake that an account has in the staking system. Provides a set of
/// methods to operate on it's properties. Aimed at making `StakingInterface` more concise.
pub struct Stake<T: StakingInterface + ?Sized> {
pub struct Stake<Balance> {
/// The total stake that `stash` has in the staking system. This includes the
/// `active` stake, and any funds currently in the process of unbonding via
/// [`StakingInterface::unbond`].
Expand All @@ -67,10 +81,10 @@ pub struct Stake<T: StakingInterface + ?Sized> {
/// This is only guaranteed to reflect the amount locked by the staking system. If there are
/// non-staking locks on the bonded pair's balance this amount is going to be larger in
/// reality.
pub total: T::Balance,
pub total: Balance,
/// The total amount of the stash's balance that will be at stake in any forthcoming
/// rounds.
pub active: T::Balance,
pub active: Balance,
}

/// A generic representation of a staking implementation.
Expand Down Expand Up @@ -109,21 +123,25 @@ pub trait StakingInterface {
/// This should be the latest planned era that the staking system knows about.
fn current_era() -> EraIndex;

/// Returns the stake of `who`.
fn stake(who: &Self::AccountId) -> Result<Stake<Self>, DispatchError>;
/// Returns the [`Stake`] of `who`.
fn stake(who: &Self::AccountId) -> Result<Stake<Self::Balance>, DispatchError>;

/// Total stake of a staker, `Err` if not a staker.
fn total_stake(who: &Self::AccountId) -> Result<Self::Balance, DispatchError> {
Self::stake(who).map(|s| s.total)
}

/// Total active portion of a staker's [`Stake`], `Err` if not a staker.
fn active_stake(who: &Self::AccountId) -> Result<Self::Balance, DispatchError> {
Self::stake(who).map(|s| s.active)
}

/// Returns whether a staker is unbonding, `Err` if not a staker at all.
fn is_unbonding(who: &Self::AccountId) -> Result<bool, DispatchError> {
Self::stake(who).map(|s| s.active != s.total)
}

/// Returns whether a staker is FULLY unbonding, `Err` if not a staker at all.
fn fully_unbond(who: &Self::AccountId) -> DispatchResult {
Self::unbond(who, Self::stake(who)?.active)
}
Expand Down Expand Up @@ -174,9 +192,17 @@ pub trait StakingInterface {
/// Checks whether an account `staker` has been exposed in an era.
fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool;

/// Return the status of the given staker, `None` if not staked at all.
fn status(who: &Self::AccountId) -> Result<StakerStatus<Self::AccountId>, DispatchError>;

/// Get the nominations of a stash, if they are a nominator, `None` otherwise.
#[cfg(feature = "runtime-benchmarks")]
fn nominations(who: Self::AccountId) -> Option<Vec<Self::AccountId>>;
fn nominations(who: &Self::AccountId) -> Option<Vec<Self::AccountId>> {
match Self::status(who) {
Ok(StakerStatus::Nominator(t)) => Some(t),
_ => None,
}
}

#[cfg(feature = "runtime-benchmarks")]
fn add_era_stakers(
Expand Down