Skip to content
This repository was archived by the owner on Nov 15, 2023. It is now read-only.
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
1 change: 1 addition & 0 deletions bin/node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,7 @@ impl pallet_staking::Config for Runtime {
// Alternatively, use pallet_staking::UseNominatorsMap<Runtime> to just use the nominators map.
// Note that the aforementioned does not scale to a very large number of nominators.
type SortedListProvider = BagsList;
type MaxUnlockingChunks = ConstU32<32>;
type WeightInfo = pallet_staking::weights::SubstrateWeight<Runtime>;
type BenchmarkingConfig = StakingBenchmarkingConfig;
}
Expand Down
1 change: 1 addition & 0 deletions frame/babe/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,7 @@ impl pallet_staking::Config for Test {
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
type GenesisElectionProvider = Self::ElectionProvider;
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
type MaxUnlockingChunks = ConstU32<32>;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = ();
}
Expand Down
1 change: 1 addition & 0 deletions frame/grandpa/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ impl pallet_staking::Config for Test {
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
type GenesisElectionProvider = Self::ElectionProvider;
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
type MaxUnlockingChunks = ConstU32<32>;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = ();
}
Expand Down
1 change: 1 addition & 0 deletions frame/offences/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ impl pallet_staking::Config for Test {
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
type GenesisElectionProvider = Self::ElectionProvider;
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
type MaxUnlockingChunks = ConstU32<32>;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = ();
}
Expand Down
1 change: 1 addition & 0 deletions frame/session/benchmarking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ impl pallet_staking::Config for Test {
type ElectionProvider = onchain::OnChainSequentialPhragmen<Self>;
type GenesisElectionProvider = Self::ElectionProvider;
type SortedListProvider = pallet_staking::UseNominatorsMap<Self>;
type MaxUnlockingChunks = ConstU32<32>;
type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig;
type WeightInfo = ();
}
Expand Down
4 changes: 2 additions & 2 deletions frame/staking/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ Validators and nominators are rewarded at the end of each era. The total reward
calculated using the era duration and the staking rate (the total amount of tokens staked by
nominators and validators, divided by the total token supply). It aims to incentivize toward a
defined staking rate. The full specification can be found
[here](https://research.web3.foundation/en/latest/polkadot/economics/1-token-economics.html#inflation-model).
[here](https://w3f-research.readthedocs.io/en/latest/polkadot/overview/2-token-economics.html#inflation-model).

Total reward is split among validators and their nominators depending on the number of points
they received during the era. Points are added to a validator using
Expand Down Expand Up @@ -229,7 +229,7 @@ call can be used to actually withdraw the funds.

Note that there is a limitation to the number of fund-chunks that can be scheduled to be
unlocked in the future via [`unbond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.unbond). In case this maximum
(`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful
(`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a successful
call to `withdraw_unbonded` to remove some of the chunks.

### Election Algorithm
Expand Down
10 changes: 5 additions & 5 deletions frame/staking/src/benchmarking.rs
Original file line number Diff line number Diff line change
Expand Up @@ -618,7 +618,7 @@ benchmarks! {
}

rebond {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let l in 1 .. <T as Config>::MaxUnlockingChunks::get();

// clean up any existing state.
clear_validators_and_nominators::<T>();
Expand Down Expand Up @@ -652,7 +652,7 @@ benchmarks! {
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();

for _ in 0 .. l {
staking_ledger.unlocking.push(unlock_chunk.clone())
staking_ledger.unlocking.try_push(unlock_chunk.clone()).expect("Size is smaller than MaxUnlockingChunks, qed");
}
Ledger::<T>::insert(controller.clone(), staking_ledger.clone());
let original_bonded: BalanceOf<T> = staking_ledger.active;
Expand Down Expand Up @@ -702,7 +702,7 @@ benchmarks! {
stash: stash.clone(),
active: T::Currency::minimum_balance() - One::one(),
total: T::Currency::minimum_balance() - One::one(),
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
};
Ledger::<T>::insert(&controller, l);
Expand Down Expand Up @@ -788,15 +788,15 @@ benchmarks! {

#[extra]
do_slash {
let l in 1 .. MAX_UNLOCKING_CHUNKS as u32;
let l in 1 .. <T as Config>::MaxUnlockingChunks::get();
let (stash, controller) = create_stash_controller::<T>(0, 100, Default::default())?;
let mut staking_ledger = Ledger::<T>::get(controller.clone()).unwrap();
let unlock_chunk = UnlockChunk::<BalanceOf<T>> {
value: 1u32.into(),
era: EraIndex::zero(),
};
for _ in 0 .. l {
staking_ledger.unlocking.push(unlock_chunk.clone())
staking_ledger.unlocking.try_push(unlock_chunk.clone()).expect("Size is smaller than MaxUnlockingChunks, qed");
}
Ledger::<T>::insert(controller, staking_ledger);
let slash_amount = T::Currency::minimum_balance() * 10u32.into();
Expand Down
41 changes: 28 additions & 13 deletions frame/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,7 @@
//! calculated using the era duration and the staking rate (the total amount of tokens staked by
//! nominators and validators, divided by the total token supply). It aims to incentivize toward a
//! defined staking rate. The full specification can be found
//! [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model).
//! [here](https://w3f-research.readthedocs.io/en/latest/polkadot/overview/2-token-economics.html#inflation-model).
//!
//! Total reward is split among validators and their nominators depending on the number of points
//! they received during the era. Points are added to a validator using
Expand Down Expand Up @@ -254,7 +254,7 @@
//!
//! Note that there is a limitation to the number of fund-chunks that can be scheduled to be
//! unlocked in the future via [`unbond`](Call::unbond). In case this maximum
//! (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful
//! (`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a successful
//! call to `withdraw_unbonded` to remove some of the chunks.
//!
//! ### Election Algorithm
Expand Down Expand Up @@ -301,9 +301,10 @@ mod pallet;

use codec::{Decode, Encode, HasCompact};
use frame_support::{
traits::{ConstU32, Currency, Get},
pallet_prelude::ConstU32,
traits::{Currency, Get},
weights::Weight,
BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
BoundedVec, CloneNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
};
use scale_info::TypeInfo;
use sp_runtime::{
Expand Down Expand Up @@ -434,8 +435,15 @@ pub struct UnlockChunk<Balance: HasCompact> {
}

/// The ledger of a (bonded) stash.
#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
pub struct StakingLedger<AccountId, Balance: HasCompact> {
/// `UnlockingLimit` is the size limit of the `WeakBoundedVec` representing `unlocking`.
#[derive(PartialEqNoBound, EqNoBound, CloneNoBound, Encode, Decode, RuntimeDebug, TypeInfo)]
#[scale_info(skip_type_params(UnlockingLimit))]
pub struct StakingLedger<AccountId, Balance, UnlockingLimit>
where
AccountId: Eq + Clone,
Balance: HasCompact + Eq + Clone,
UnlockingLimit: Get<u32>,
{
/// The stash account whose balance is actually locked and at stake.
pub stash: AccountId,
/// The total amount of the stash's balance that we are currently accounting for.
Expand All @@ -448,22 +456,25 @@ pub struct StakingLedger<AccountId, Balance: HasCompact> {
pub active: Balance,
/// Any balance that is becoming free, which may eventually be transferred out
/// of the stash (assuming it doesn't get slashed first).
pub unlocking: Vec<UnlockChunk<Balance>>,
pub unlocking: BoundedVec<UnlockChunk<Balance>, UnlockingLimit>,
/// List of eras for which the stakers behind a validator have claimed rewards. Only updated
/// for validators.
pub claimed_rewards: Vec<EraIndex>,
}

impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned + Zero>
StakingLedger<AccountId, Balance>
impl<AccountId, Balance, UnlockingLimit> StakingLedger<AccountId, Balance, UnlockingLimit>
where
AccountId: Eq + Clone,
Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned + Zero + Eq + Clone,
UnlockingLimit: Get<u32>,
{
/// Initializes the default object using the given `validator`.
pub fn default_from(stash: AccountId) -> Self {
Self {
stash,
total: Zero::zero(),
active: Zero::zero(),
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
}
}
Expand All @@ -483,7 +494,9 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
false
}
})
.collect();
.collect::<Vec<UnlockChunk<Balance>>>()
.try_into()
.expect("unlocking vector only reduced in size here.");

Self {
stash: self.stash,
Expand Down Expand Up @@ -522,9 +535,11 @@ impl<AccountId, Balance: HasCompact + Copy + Saturating + AtLeast32BitUnsigned +
}
}

impl<AccountId, Balance> StakingLedger<AccountId, Balance>
impl<AccountId, Balance, UnlockingLimit> StakingLedger<AccountId, Balance, UnlockingLimit>
where
Balance: AtLeast32BitUnsigned + Saturating + Copy,
AccountId: Eq + Clone,
Balance: AtLeast32BitUnsigned + Saturating + Copy + Eq + Clone,
UnlockingLimit: Get<u32>,
{
/// Slash the validator for a given amount of balance. This can grow the value
/// of the slash in the case that the validator has less than `minimum_balance`
Expand Down
1 change: 1 addition & 0 deletions frame/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,7 @@ impl crate::pallet::pallet::Config for Test {
type GenesisElectionProvider = Self::ElectionProvider;
// NOTE: consider a macro and use `UseNominatorsMap<Self>` as well.
type SortedListProvider = BagsList;
type MaxUnlockingChunks = ConstU32<32>;
type BenchmarkingConfig = TestBenchmarkingConfig;
type WeightInfo = ();
}
Expand Down
10 changes: 5 additions & 5 deletions frame/staking/src/pallet/impls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ impl<T: Config> Pallet<T> {
/// This will also update the stash lock.
pub(crate) fn update_ledger(
controller: &T::AccountId,
ledger: &StakingLedger<T::AccountId, BalanceOf<T>>,
ledger: &StakingLedger<T::AccountId, BalanceOf<T>, T::MaxUnlockingChunks>,
) {
T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all());
<Ledger<T>>::insert(controller, ledger);
Expand Down Expand Up @@ -928,7 +928,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: voter.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
Expand All @@ -946,7 +946,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: target.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
Expand Down Expand Up @@ -983,7 +983,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
Expand All @@ -1004,7 +1004,7 @@ impl<T: Config> ElectionDataProvider for Pallet<T> {
stash: v.clone(),
active: stake,
total: stake,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: vec![],
},
);
Expand Down
29 changes: 20 additions & 9 deletions frame/staking/src/pallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ use crate::{
ValidatorPrefs,
};

pub const MAX_UNLOCKING_CHUNKS: usize = 32;
const STAKING_ID: LockIdentifier = *b"staking ";

#[frame_support::pallet]
Expand Down Expand Up @@ -147,6 +146,12 @@ pub mod pallet {
#[pallet::constant]
type MaxNominatorRewardedPerValidator: Get<u32>;

/// Note that there is a limitation to the number of fund-chunks that can be scheduled to be
/// unlocked in the future via [`unbond`](Call::unbond). In case this maximum
/// (`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a
/// successful call to `withdraw_unbonded` to remove some of the chunks.
type MaxUnlockingChunks: Get<u32>;

/// The fraction of the validator set that is safe to be offending.
/// After the threshold is reached a new era will be forced.
type OffendingValidatorsThreshold: Get<Perbill>;
Expand Down Expand Up @@ -218,8 +223,12 @@ pub mod pallet {
/// Map from all (unlocked) "controller" accounts to the info regarding the staking.
#[pallet::storage]
#[pallet::getter(fn ledger)]
pub type Ledger<T: Config> =
StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger<T::AccountId, BalanceOf<T>>>;
pub type Ledger<T: Config> = StorageMap<
_,
Blake2_128Concat,
T::AccountId,
StakingLedger<T::AccountId, BalanceOf<T>, T::MaxUnlockingChunks>,
>;

/// Where the reward payment should be made. Keyed by stash.
#[pallet::storage]
Expand Down Expand Up @@ -771,7 +780,7 @@ pub mod pallet {
stash,
total: value,
active: value,
unlocking: vec![],
unlocking: Default::default(),
claimed_rewards: (last_reward_era..current_era).collect(),
};
Self::update_ledger(&controller, &item);
Expand Down Expand Up @@ -836,7 +845,7 @@ pub mod pallet {
/// Once the unlock period is done, you can call `withdraw_unbonded` to actually move
/// the funds out of management ready for transfer.
///
/// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`)
/// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`)
/// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need
/// to be called first to remove some of the chunks (if possible).
///
Expand All @@ -853,7 +862,6 @@ pub mod pallet {
) -> DispatchResult {
let controller = ensure_signed(origin)?;
let mut ledger = Self::ledger(&controller).ok_or(Error::<T>::NotController)?;
ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::<T>::NoMoreChunks,);

let mut value = value.min(ledger.active);

Expand All @@ -880,7 +888,10 @@ pub mod pallet {

// Note: in case there is no current era it is fine to bond one era more.
let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get();
ledger.unlocking.push(UnlockChunk { value, era });
ledger
.unlocking
.try_push(UnlockChunk { value, era })
.map_err(|_| Error::<T>::NoMoreChunks)?;
// NOTE: ledger must be updated prior to calling `Self::weight_of`.
Self::update_ledger(&controller, &ledger);

Expand Down Expand Up @@ -1347,10 +1358,10 @@ pub mod pallet {
///
/// # <weight>
/// - Time complexity: O(L), where L is unlocking chunks
/// - Bounded by `MAX_UNLOCKING_CHUNKS`.
/// - Bounded by `MaxUnlockingChunks`.
/// - Storage changes: Can't increase storage, only decrease it.
/// # </weight>
#[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))]
#[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get()))]
pub fn rebond(
origin: OriginFor<T>,
#[pallet::compact] value: BalanceOf<T>,
Expand Down
Loading