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

Factor out checks for staking's bond & nominate #10751

Closed
wants to merge 2 commits into from
Closed
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
85 changes: 84 additions & 1 deletion frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ use frame_support::{
use frame_system::pallet_prelude::BlockNumberFor;
use pallet_session::historical;
use sp_runtime::{
traits::{Bounded, Convert, SaturatedConversion, Saturating, Zero},
traits::{Bounded, Convert, SaturatedConversion, Saturating, StaticLookup, Zero},
Perbill,
};
use sp_staking::{
Expand Down Expand Up @@ -848,6 +848,89 @@ impl<T: Config> Pallet<T> {
DispatchClass::Mandatory,
);
}

/// Checks for [`Self::bond`] that can be completed at the beginning of the calls logic.
pub(crate) fn ensure_can_bond(
stash: &T::AccountId,
controller: &T::AccountId,
value: BalanceOf<T>,
) -> Result<(), DispatchError> {
if Bonded::<T>::contains_key(&stash) {
Err(Error::<T>::AlreadyBonded)?
}

if Ledger::<T>::contains_key(&controller) {
Err(Error::<T>::AlreadyPaired)?
}

// Reject a bond which is considered to be _dust_.
if value < T::Currency::minimum_balance() {
Err(Error::<T>::InsufficientBond)?
}

Ok(())
}

/// Checks for [`Self::nominate`] that must be called prior to
/// [`Self::do_unchecked_nominate_writes`].
pub(crate) fn ensure_can_nominate(
controller: &T::AccountId,
targets: Vec<<T::Lookup as StaticLookup>::Source>,
) -> Result<(T::AccountId, BoundedVec<T::AccountId, T::MaxNominations>), DispatchError> {
let ledger = Self::ledger(controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.active >= MinNominatorBond::<T>::get(), Error::<T>::InsufficientBond);

// Only check limits if they are not already a nominator.
if !Nominators::<T>::contains_key(&ledger.stash) {
// If this error is reached, we need to adjust the `MinNominatorBond` and start
// calling `chill_other`. Until then, we explicitly block new nominators to protect
// the runtime.
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
ensure!(Nominators::<T>::count() < max_nominators, Error::<T>::TooManyNominators);
}
}

ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::<T>::TooManyTargets);

let old =
Nominators::<T>::get(&ledger.stash).map_or_else(Vec::new, |x| x.targets.into_inner());

let targets: BoundedVec<_, _> = targets
.into_iter()
.map(|t| T::Lookup::lookup(t).map_err(DispatchError::from))
.map(|n| {
n.and_then(|n| {
if old.contains(&n) || !Validators::<T>::get(&n).blocked {
Ok(n)
} else {
Err(Error::<T>::BadTarget.into())
}
})
})
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| Error::<T>::TooManyNominators)?;

Ok((ledger.stash, targets))
}

/// Write the information for nominating. The caller must first call
/// [`Self::ensure_can_nominate`].
pub(crate) fn do_unchecked_nominate(
stash: &T::AccountId,
targets: BoundedVec<T::AccountId, T::MaxNominations>,
) {
let nominations = Nominations {
targets,
// Initial nominations are considered submitted at era 0. See `Nominations` doc.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Initial nominations are considered submitted at era 0. See `Nominations` doc.
// Initial nominations are considered submitted at era 0. See [`Nomination`]'s doc.

submitted_in: Self::current_era().unwrap_or(0),
suppressed: false,
};

Self::do_remove_validator(&stash);
Self::do_add_nominator(&stash, nominations);
}
}

impl<T: Config> ElectionDataProvider for Pallet<T> {
Expand Down
66 changes: 4 additions & 62 deletions frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use frame_support::{
use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*};
use sp_runtime::{
traits::{CheckedSub, SaturatedConversion, StaticLookup, Zero},
DispatchError, Perbill, Percent,
Perbill, Percent,
};
use sp_staking::{EraIndex, SessionIndex};
use sp_std::{convert::From, prelude::*};
Expand Down Expand Up @@ -737,21 +737,9 @@ pub mod pallet {
payee: RewardDestination<T::AccountId>,
) -> DispatchResult {
let stash = ensure_signed(origin)?;

if <Bonded<T>>::contains_key(&stash) {
Err(Error::<T>::AlreadyBonded)?
}

let controller = T::Lookup::lookup(controller)?;

if <Ledger<T>>::contains_key(&controller) {
Err(Error::<T>::AlreadyPaired)?
}

// Reject a bond which is considered to be _dust_.
if value < T::Currency::minimum_balance() {
Err(Error::<T>::InsufficientBond)?
}
Self::ensure_can_bond(&stash, &controller, value)?;

frame_system::Pallet::<T>::inc_consumers(&stash).map_err(|_| Error::<T>::BadState)?;

Expand Down Expand Up @@ -1003,54 +991,8 @@ pub mod pallet {
targets: Vec<<T::Lookup as StaticLookup>::Source>,
) -> DispatchResult {
let controller = ensure_signed(origin)?;

let ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.active >= MinNominatorBond::<T>::get(), Error::<T>::InsufficientBond);
let stash = &ledger.stash;

// Only check limits if they are not already a nominator.
if !Nominators::<T>::contains_key(stash) {
// If this error is reached, we need to adjust the `MinNominatorBond` and start
// calling `chill_other`. Until then, we explicitly block new nominators to protect
// the runtime.
if let Some(max_nominators) = MaxNominatorsCount::<T>::get() {
ensure!(
Nominators::<T>::count() < max_nominators,
Error::<T>::TooManyNominators
);
}
}

ensure!(!targets.is_empty(), Error::<T>::EmptyTargets);
ensure!(targets.len() <= T::MaxNominations::get() as usize, Error::<T>::TooManyTargets);

let old = Nominators::<T>::get(stash).map_or_else(Vec::new, |x| x.targets.into_inner());

let targets: BoundedVec<_, _> = targets
.into_iter()
.map(|t| T::Lookup::lookup(t).map_err(DispatchError::from))
.map(|n| {
n.and_then(|n| {
if old.contains(&n) || !Validators::<T>::get(&n).blocked {
Ok(n)
} else {
Err(Error::<T>::BadTarget.into())
}
})
})
.collect::<Result<Vec<_>, _>>()?
.try_into()
.map_err(|_| Error::<T>::TooManyNominators)?;

let nominations = Nominations {
targets,
// Initial nominations are considered submitted at era 0. See `Nominations` doc.
submitted_in: Self::current_era().unwrap_or(0),
suppressed: false,
};

Self::do_remove_validator(stash);
Self::do_add_nominator(stash, nominations);
let (stash, targets) = Self::ensure_can_nominate(&controller, targets)?;
Self::do_unchecked_nominate(&stash, targets);
Ok(())
}

Expand Down