From fe413cf7a97e6646e0986a1eaf25b6961ab8e887 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:22:03 +0200 Subject: [PATCH 01/87] Era REward Manager --- substrate/frame/staking-async/src/reward.rs | 167 ++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100644 substrate/frame/staking-async/src/reward.rs diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs new file mode 100644 index 0000000000000..251f7af2fdf1f --- /dev/null +++ b/substrate/frame/staking-async/src/reward.rs @@ -0,0 +1,167 @@ +// 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. + +//! Era reward management. +//! +//! Manages the lifecycle of era reward pot accounts: creation, funding +//! via snapshot from the general DAP pot, and cleanup of expired eras. + +use crate::*; +use frame_support::{ + defensive, + traits::{ + fungible::{Balanced, Inspect, Mutate}, + tokens::{Fortitude, Precision, Preservation}, + Defensive, OnUnbalanced, + }, +}; +use sp_runtime::traits::Zero; +use sp_staking::EraIndex; + +/// Manager for era reward pot lifecycle. +pub struct EraRewardManager(core::marker::PhantomData); + +impl EraRewardManager { + /// Creates an era pot account by adding a provider reference. + pub(crate) fn create(era: EraIndex, pot_type: EraPotType) -> T::AccountId { + let pot_account = T::EraPots::era_pot_account(era, pot_type); + frame_system::Pallet::::inc_providers(&pot_account); + pot_account + } + + /// Snapshots the general staker reward pot into an era-specific pot. + /// + /// DAP drips inflation continuously into the general pot. At era boundary, + /// this transfers the accumulated balance (minus ED) into an era pot. + pub(crate) fn snapshot_era_rewards(era: EraIndex) -> BalanceOf { + let staker_era_pot = Self::create(era, EraPotType::StakerRewards); + + let general_staker_pot = + T::GeneralPots::general_pot_account(GeneralPotType::StakerRewards); + + // Leave ED in the general pot to keep it alive. + let staker_balance = T::Currency::reducible_balance( + &general_staker_pot, + Preservation::Preserve, + Fortitude::Polite, + ); + + let actual_staker = if !staker_balance.is_zero() { + match T::Currency::transfer( + &general_staker_pot, + &staker_era_pot, + staker_balance, + Preservation::Preserve, + ) { + Ok(_) => staker_balance, + Err(e) => { + log!(error, "Era {:?}: staker reward transfer failed: {:?}", era, e); + defensive!("Failed to transfer staker rewards to era pot"); + Zero::zero() + }, + } + } else { + Zero::zero() + }; + + log::info!( + target: LOG_TARGET, + "Era {era}: snapshotted staker_rewards={actual_staker:?}" + ); + + actual_staker + } + + /// Destroys an era pot by withdrawing unclaimed rewards and removing the provider. + pub(crate) fn destroy(era: EraIndex, pot_type: EraPotType) { + let pot_account = T::EraPots::era_pot_account(era, pot_type); + let remaining = T::Currency::balance(&pot_account); + + if !remaining.is_zero() { + match T::Currency::withdraw( + &pot_account, + remaining, + Precision::BestEffort, + Preservation::Expendable, + Fortitude::Force, + ) { + Ok(credit) => { + T::UnclaimedRewardHandler::on_unbalanced(credit); + log::debug!( + target: crate::LOG_TARGET, + "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", + remaining, era, pot_type + ); + }, + Err(e) => { + defensive!("Failed to withdraw unclaimed rewards from era pot"); + log::error!( + target: crate::LOG_TARGET, + "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", + era, pot_type, e + ); + }, + } + } + + let _ = frame_system::Pallet::::dec_providers(&pot_account) + .defensive_proof("Provider was added in Self::create; qed"); + } + + /// Checks if an era has a staker rewards pot. + pub(crate) fn has_staker_rewards_pot(era: EraIndex) -> bool { + let pot = T::EraPots::era_pot_account(era, EraPotType::StakerRewards); + frame_system::Pallet::::providers(&pot) > 0 + } + + /// Cleans up pot accounts for a given era. + pub(crate) fn cleanup_era(era: EraIndex) { + Self::destroy(era, EraPotType::StakerRewards); + } +} + +/// Default implementation of the staker reward calculator. +/// +/// Commission-based split: validator gets commission + proportional stake share, +/// nominators get the rest. Incentive weight returns zero (no incentive curve). +pub struct DefaultStakerRewardCalculator(core::marker::PhantomData); + +impl sp_staking::StakerRewardCalculator> + for DefaultStakerRewardCalculator +where + BalanceOf: Into + From, +{ + fn calculate_validator_incentive_weight(_self_stake: BalanceOf) -> BalanceOf { + Zero::zero() + } + + fn calculate_staker_reward( + validator_total_reward: BalanceOf, + validator_commission: Perbill, + validator_own_stake: BalanceOf, + total_stake: BalanceOf, + ) -> sp_staking::StakerRewardResult> { + let validator_commission_payout = validator_commission.mul_floor(validator_total_reward); + let leftover = validator_total_reward.saturating_sub(validator_commission_payout); + let validator_exposure_part = Perbill::from_rational(validator_own_stake, total_stake); + let validator_staking_payout = validator_exposure_part.mul_floor(leftover); + let validator_payout = validator_staking_payout.saturating_add(validator_commission_payout); + let nominator_payout = leftover.saturating_sub(validator_staking_payout); + + sp_staking::StakerRewardResult { validator_payout, nominator_payout } + } +} From ef58054271f9b3b41c4b08a3433a882cf67ca83d Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:22:56 +0200 Subject: [PATCH 02/87] default impl staker reward calculator --- substrate/primitives/staking/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index d58aa4b52c814..39f36dff7c548 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -788,6 +788,24 @@ pub trait StakerRewardCalculator { ) -> StakerRewardResult; } +impl StakerRewardCalculator for () { + fn calculate_validator_incentive_weight(_self_stake: Balance) -> Balance { + Default::default() + } + + fn calculate_staker_reward( + _validator_total_reward: Balance, + _validator_commission: Perbill, + _validator_own_stake: Balance, + _total_stake: Balance, + ) -> StakerRewardResult { + StakerRewardResult { + validator_payout: Default::default(), + nominator_payout: Default::default(), + } + } +} + sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); sp_core::generate_feature_enabled_macro!(std_or_benchmarks_enabled, any(feature = "std", feature = "runtime-benchmarks"), $); From de58f7e86fa54727b33572ced02bd98c59fdbd79 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:29:48 +0200 Subject: [PATCH 03/87] new pallet apis --- Cargo.lock | 3 + .../frame/staking-async/src/pallet/mod.rs | 97 ++++++++++++++----- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8d7dd88196025..306544be0481e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14163,13 +14163,16 @@ dependencies = [ "log", "pallet-bags-list", "pallet-balances", + "pallet-dap", "pallet-staking-async-rc-client", + "pallet-timestamp", "parity-scale-codec", "rand 0.8.5", "rand_chacha 0.3.1", "scale-info", "serde", "sp-application-crypto 30.0.0", + "sp-arithmetic 23.0.0", "sp-core 28.0.0", "sp-io 30.0.0", "sp-npos-elections", diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 2bbe871f6a3a0..7384df124b1ac 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -19,10 +19,9 @@ use crate::{ asset, session_rotation::EraElectionPlanner, slashing, weights::WeightInfo, AccountIdLookupOf, - ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, ExposurePage, Forcing, - LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, - PositiveImbalanceOf, RewardDestination, StakingLedger, UnappliedSlash, UnlockChunk, - ValidatorPrefs, + ActiveEraInfo, BalanceOf, EraRewardPoints, ExposurePage, Forcing, LedgerIntegrityState, + MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, + RewardDestination, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; use alloc::{format, vec::Vec}; use codec::Codec; @@ -255,10 +254,28 @@ pub mod pallet { #[pallet::no_default] type AdminOrigin: EnsureOrigin; - /// The payout for validators and the system for the current era. - /// See [Era payout](./index.html#era-payout). - #[pallet::no_default] - type EraPayout: EraPayout>; + /// Handler for unclaimed era rewards. + /// + /// When era pots are cleaned up past history depth, remaining funds are withdrawn + /// and passed to this handler. Typically wired to DAP. + #[pallet::no_default_bounds] + type UnclaimedRewardHandler: OnUnbalanced>; + + /// Provider for general (non-era) reward pot accounts. + /// + /// DAP drips inflation into these pots. At era boundaries, staking snapshots + /// and transfers the balances to era-specific pots. + type GeneralPots: crate::GeneralPotAccountProvider; + + /// Provider for generating era pot account IDs. + #[pallet::no_default_bounds] + type EraPots: crate::EraPotAccountProvider; + + /// Calculator for staker rewards. + /// + /// Determines how staking rewards are distributed between validators and nominators. + #[pallet::no_default_bounds] + type StakerRewardCalculator: sp_staking::StakerRewardCalculator>; /// The maximum size of each `T::ExposurePage`. /// @@ -341,17 +358,6 @@ pub mod pallet { #[pallet::no_default_bounds] type EventListeners: sp_staking::OnStakingUpdate>; - /// Maximum allowed era duration in milliseconds. - /// - /// This provides a defensive upper bound to cap the effective era duration, preventing - /// excessively long eras from causing runaway inflation (e.g., due to bugs). If the actual - /// era duration exceeds this value, it will be clamped to this maximum. - /// - /// Example: For an ideal era duration of 24 hours (86,400,000 ms), - /// this can be set to 604,800,000 ms (7 days). - #[pallet::constant] - type MaxEraDuration: Get; - /// Maximum number of storage items that can be pruned in a single call. /// /// This controls how many storage items can be deleted in each call to `prune_era_step`. @@ -413,6 +419,10 @@ pub mod pallet { type RewardRemainder = (); type Slash = (); type Reward = (); + type UnclaimedRewardHandler = (); + type GeneralPots = (); + type EraPots = (); + type StakerRewardCalculator = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; @@ -422,7 +432,6 @@ pub mod pallet { type MaxUnlockingChunks = ConstU32<32>; type MaxValidatorSet = ConstU32<100>; type MaxControllersInDeprecationBatch = ConstU32<100>; - type MaxEraDuration = (); type MaxPruningItems = MaxPruningItems; type EventListeners = (); type Filter = Nothing; @@ -458,6 +467,26 @@ pub mod pallet { #[pallet::storage] pub type MinCommission = StorageValue<_, Perbill, ValueQuery>; + /// The maximum commission that validators can set. + /// + /// If not set, defaults to `Perbill::one()` (100%), i.e. no upper limit. + #[pallet::storage] + pub type MaxCommission = StorageValue<_, Perbill, ValueQuery, MaxCommissionDefault>; + + /// Default for MaxCommission: 100% (no restriction). + pub struct MaxCommissionDefault; + impl Get for MaxCommissionDefault { + fn get() -> Perbill { + Perbill::one() + } + } + + /// The era from which legacy minting (mint-on-payout) is permanently disabled. + /// + /// Once set, all eras >= this value must use reward pot transfers instead of minting. + #[pallet::storage] + pub type DisableLegacyMintingEra = StorageValue<_, EraIndex>; + /// Whether nominators are slashable or not. /// /// - When set to `true` (default), nominators are slashed along with validators and must wait @@ -1282,6 +1311,8 @@ pub mod pallet { UnknownValidatorActivation, /// Failed to proceed paged election due to weight limits PagedElectionOutOfWeight { page: PageIndex, required: Weight, had: Weight }, + /// Validator payee is missing when paying rewards. + ValidatorMissingPayee { era: EraIndex }, } #[pallet::error] @@ -1337,6 +1368,10 @@ pub mod pallet { TooManyValidators, /// Commission is too low. Must be at least `MinCommission`. CommissionTooLow, + /// Commission is higher than the allowed maximum `MaxCommission`. + CommissionTooHigh, + /// Era has no reward pot but legacy minting is disabled. + LegacyMintingDisabled, /// Some bound is not met. BoundNotMet, /// Used when attempting to use deprecated controller account logic. @@ -1834,6 +1869,7 @@ pub mod pallet { // ensure their commission is correct. ensure!(prefs.commission >= MinCommission::::get(), Error::::CommissionTooLow); + ensure!(prefs.commission <= MaxCommission::::get(), Error::::CommissionTooHigh); // Only check limits if they are not already a validator. if !Validators::::contains_key(stash) { @@ -2493,12 +2529,17 @@ pub mod pallet { ) -> DispatchResult { ensure_signed(origin)?; let min_commission = MinCommission::::get(); + let max_commission = MaxCommission::::get(); Validators::::try_mutate_exists(validator_stash, |maybe_prefs| { maybe_prefs .as_mut() .map(|prefs| { - (prefs.commission < min_commission) - .then(|| prefs.commission = min_commission) + if prefs.commission < min_commission { + prefs.commission = min_commission; + } + if prefs.commission > max_commission { + prefs.commission = max_commission; + } }) .ok_or(Error::::NotStash) })?; @@ -2835,5 +2876,17 @@ pub mod pallet { pays_fee: frame_support::dispatch::Pays::No, }) } + + /// Sets the maximum commission that validators can set. + /// + /// The dispatch origin must be `T::AdminOrigin`. + #[pallet::call_index(33)] + #[pallet::weight(T::WeightInfo::set_max_commission())] + pub fn set_max_commission(origin: OriginFor, new: Perbill) -> DispatchResult { + T::AdminOrigin::ensure_origin(origin)?; + ensure!(new >= MinCommission::::get(), Error::::CommissionTooLow); + MaxCommission::::put(new); + Ok(()) + } } } From ec33c183f3cce6907a925ffe6cbe116954ddec27 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:37:29 +0200 Subject: [PATCH 04/87] impls --- substrate/frame/staking-async/Cargo.toml | 8 + .../frame/staking-async/src/pallet/impls.rs | 258 +++++++++++++----- 2 files changed, 202 insertions(+), 64 deletions(-) diff --git a/substrate/frame/staking-async/Cargo.toml b/substrate/frame/staking-async/Cargo.toml index a852d8e87571b..5725bcce8530b 100644 --- a/substrate/frame/staking-async/Cargo.toml +++ b/substrate/frame/staking-async/Cargo.toml @@ -29,6 +29,7 @@ sp-application-crypto = { features = ["serde"], workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-npos-elections = { workspace = true } +sp-arithmetic = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-staking = { features = ["serde"], workspace = true } @@ -42,6 +43,8 @@ frame-benchmarking = { workspace = true, default-features = true } frame-support = { features = ["experimental"], workspace = true, default-features = true } pallet-bags-list = { workspace = true, default-features = true } pallet-balances = { workspace = true, default-features = true } +pallet-dap = { workspace = true, default-features = true } +pallet-timestamp = { workspace = true, default-features = true } rand_chacha = { workspace = true, default-features = true } sp-tracing = { workspace = true, default-features = true } substrate-test-utils = { workspace = true } @@ -58,7 +61,10 @@ std = [ "log/std", "pallet-bags-list/std", "pallet-balances/std", + "pallet-dap/std", "pallet-staking-async-rc-client/std", + "pallet-timestamp/std", + "sp-arithmetic/std", "rand/std", "rand_chacha/std", "scale-info/std", @@ -79,6 +85,7 @@ runtime-benchmarks = [ "frame-system/runtime-benchmarks", "pallet-bags-list/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-dap/runtime-benchmarks", "pallet-staking-async-rc-client/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", @@ -89,6 +96,7 @@ try-runtime = [ "frame-system/try-runtime", "pallet-bags-list/try-runtime", "pallet-balances/try-runtime", + "pallet-dap/try-runtime", "pallet-staking-async-rc-client/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 07314b0087565..47410533aa6db 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -24,10 +24,11 @@ use crate::{ session_rotation::{self, Eras, Rotator}, slashing::OffenceRecord, weights::WeightInfo, - BalanceOf, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, Nominations, - NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, StakingLedger, - ValidatorPrefs, STAKING_ID, + BalanceOf, EraPotAccountProvider, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, + Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, + StakingLedger, ValidatorPrefs, STAKING_ID, }; +use sp_staking::StakerRewardCalculator; use alloc::{boxed::Box, vec, vec::Vec}; use frame_election_provider_support::{ bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, ElectionProvider, @@ -355,7 +356,7 @@ impl Pallet { ); // Note: if era has no reward to be claimed, era may be future. - let era_payout = Eras::::get_validators_reward(era).ok_or_else(|| { + let era_payout = Eras::::get_stakers_reward(era).ok_or_else(|| { Error::::InvalidEraToReward .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)) })?; @@ -387,13 +388,6 @@ impl Pallet { // Input data seems good, no errors allowed after this point - // Get Era reward points. It has TOTAL and INDIVIDUAL - // Find the fraction of the era reward that belongs to the validator - // Take that fraction of the eras rewards to split to nominator and validator - // - // Then look at the validator, figure out the proportion of their reward - // which goes to them and each of their nominators. - let era_reward_points = Eras::::get_reward_points(era); let total_reward_points = era_reward_points.total; let validator_reward_points = @@ -410,20 +404,22 @@ impl Pallet { Perbill::from_rational(validator_reward_points, total_reward_points); // This is how much validator + nominators are entitled to. - let validator_total_payout = validator_total_reward_part * era_payout; + let validator_total_payout = validator_total_reward_part.mul_floor(era_payout); let validator_commission = Eras::::get_validator_commission(era, &ledger.stash); - // total commission validator takes across all nominator pages - let validator_total_commission_payout = validator_commission * validator_total_payout; - - let validator_leftover_payout = - validator_total_payout.defensive_saturating_sub(validator_total_commission_payout); - // Now let's calculate how this is split to the validator. - let validator_exposure_part = Perbill::from_rational(exposure.own(), exposure.total()); - let validator_staking_payout = validator_exposure_part * validator_leftover_payout; + + // Use the StakerRewardCalculator trait to calculate reward distribution. + let reward_split = T::StakerRewardCalculator::calculate_staker_reward( + validator_total_payout, + validator_commission, + exposure.own(), + exposure.total(), + ); + let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total()); // validator commission is paid out in fraction across pages proportional to the page stake. - let validator_commission_payout = page_stake_part * validator_total_commission_payout; + let validator_staker_payout_for_page = + page_stake_part.mul_floor(reward_split.validator_payout); Self::deposit_event(Event::::PayoutStarted { era_index: era, @@ -432,63 +428,193 @@ impl Pallet { next: Eras::::get_next_claimable_page(era, &stash), }); - let mut total_imbalance = PositiveImbalanceOf::::zero(); - // We can now make total validator payout: - if let Some((imbalance, dest)) = - Self::make_payout(&stash, validator_staking_payout + validator_commission_payout) + // Check if this era has a staker rewards pot. + let nominator_payout_count: u32 = + if crate::reward::EraRewardManager::::has_staker_rewards_pot(era) { + Self::payout_from_provider( + era, + &stash, + validator_staker_payout_for_page, + &exposure, + reward_split.nominator_payout, + ) + } else { + // LEGACY: Only used for old eras finalised before reward provider impl. + if let Some(disable_era) = DisableLegacyMintingEra::::get() { + if era >= disable_era { + defensive!("Era has no reward pot but legacy minting is disabled!"); + return Err(Error::::LegacyMintingDisabled.into()); + } + } + + Self::payout_legacy_mint( + &stash, + validator_staker_payout_for_page, + &exposure, + reward_split.nominator_payout, + ) + }; + + debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get()); + + Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) + } + + /// Payout stakers from an era reward pot (transfer-based, no minting). + fn payout_from_provider( + era: EraIndex, + stash: &T::AccountId, + validator_payout: BalanceOf, + exposure: &crate::PagedExposure>, + total_nominator_payout: BalanceOf, + ) -> u32 { + let mut nominator_payout_count: u32 = 0; + + if let Some((amount, dest)) = + Self::make_payout_from_provider(era, stash, validator_payout) { - Self::deposit_event(Event::::Rewarded { stash, dest, amount: imbalance.peek() }); - total_imbalance.subsume(imbalance); + Self::deposit_event(Event::::Rewarded { stash: stash.clone(), dest, amount }); } - // Track the number of payout ops to nominators. Note: - // `WeightInfo::payout_stakers_alive_staked` always assumes at least a validator is paid - // out, so we do not need to count their payout op. + let total_nominator_stake = exposure.total().saturating_sub(exposure.own()); + for nominator in exposure.others().iter() { + let nominator_exposure_part = + Perbill::from_rational(nominator.value, total_nominator_stake); + let nominator_reward: BalanceOf = + nominator_exposure_part.mul_floor(total_nominator_payout); + + if let Some((amount, dest)) = + Self::make_payout_from_provider(era, &nominator.who, nominator_reward) + { + nominator_payout_count.saturating_inc(); + Self::deposit_event(Event::::Rewarded { + stash: nominator.who.clone(), + dest, + amount, + }); + } + } + + nominator_payout_count + } + + /// Legacy mint-based payout for pre-upgrade eras. + fn payout_legacy_mint( + stash: &T::AccountId, + validator_payout: BalanceOf, + exposure: &crate::PagedExposure>, + total_nominator_payout: BalanceOf, + ) -> u32 { let mut nominator_payout_count: u32 = 0; + let mut total_imbalance = PositiveImbalanceOf::::zero(); - // 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()); + if let Some((imbalance, dest)) = Self::make_payout_legacy(stash, validator_payout) { + Self::deposit_event(Event::::Rewarded { + stash: stash.clone(), + dest, + amount: imbalance.peek(), + }); + total_imbalance.subsume(imbalance); + } + let total_nominator_stake = exposure.total().saturating_sub(exposure.own()); + for nominator in exposure.others().iter() { + let nominator_exposure_part = + Perbill::from_rational(nominator.value, total_nominator_stake); let nominator_reward: BalanceOf = - nominator_exposure_part * validator_leftover_payout; - // We can now make nominator payout: - if let Some((imbalance, dest)) = Self::make_payout(&nominator.who, nominator_reward) { - // Note: this logic does not count payouts for `RewardDestination::None`. - nominator_payout_count += 1; - let e = Event::::Rewarded { + nominator_exposure_part.mul_floor(total_nominator_payout); + + if let Some((imbalance, dest)) = + Self::make_payout_legacy(&nominator.who, nominator_reward) + { + nominator_payout_count.saturating_inc(); + Self::deposit_event(Event::::Rewarded { stash: nominator.who.clone(), dest, amount: imbalance.peek(), - }; - Self::deposit_event(e); + }); total_imbalance.subsume(imbalance); } } T::Reward::on_unbalanced(total_imbalance); - debug_assert!(nominator_payout_count <= T::MaxExposurePageSize::get()); + nominator_payout_count + } - Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) + /// Determine the payout account from a reward destination. + fn payout_account_for_dest( + stash: &T::AccountId, + dest: &RewardDestination, + ) -> Option { + match dest { + RewardDestination::Stash | RewardDestination::Staked => Some(stash.clone()), + RewardDestination::Account(ref dest_account) => Some(dest_account.clone()), + RewardDestination::None => None, + #[allow(deprecated)] + RewardDestination::Controller => Self::bonded(stash), + } } - /// Chill a stash account. - pub(crate) fn chill_stash(stash: &T::AccountId) { - let chilled_as_validator = Self::do_remove_validator(stash); - let chilled_as_nominator = Self::do_remove_nominator(stash); - if chilled_as_validator || chilled_as_nominator { - Self::deposit_event(Event::::Chilled { stash: stash.clone() }); + /// Make a payment to a staker from an era reward pot (transfer, not mint). + fn make_payout_from_provider( + era: EraIndex, + stash: &T::AccountId, + amount: BalanceOf, + ) -> Option<(BalanceOf, RewardDestination)> { + use frame_support::traits::{fungible::Mutate as FunMutate, tokens::Preservation}; + + if amount.is_zero() { + return None; } + + let dest = match Self::payee(Stash(stash.clone())) { + Some(d) => d, + None => { + defensive!("Staker missing payee"); + Self::deposit_event(Event::::Unexpected( + UnexpectedKind::ValidatorMissingPayee { era }, + )); + return None; + }, + }; + + let payout_account = Self::payout_account_for_dest(stash, &dest)?; + + let staker_rewards_pot = + T::EraPots::era_pot_account(era, crate::EraPotType::StakerRewards); + if let Err(e) = T::Currency::transfer( + &staker_rewards_pot, + &payout_account, + amount, + Preservation::Expendable, + ) { + log!( + error, + "Failed to transfer reward from pot for era {:?}, stash {:?}: {:?}", + era, stash, e + ); + return None; + } + + // For Staked destination, update ledger. + if matches!(dest, RewardDestination::Staked) { + if let Ok(mut ledger) = Self::ledger(Stash(stash.clone())) { + ledger.active += amount; + ledger.total += amount; + let _ = ledger + .update() + .defensive_proof("ledger fetched from storage, so it exists; qed."); + } + } + + Some((amount, dest)) } - /// 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( + /// Legacy: make a payment to a staker by minting new tokens. + fn make_payout_legacy( stash: &T::AccountId, amount: BalanceOf, ) -> Option<(PositiveImbalanceOf, RewardDestination)> { - // noop if amount is zero if amount.is_zero() { return None; } @@ -501,29 +627,33 @@ impl Pallet { ledger.active += amount; ledger.total += amount; let r = asset::mint_into_existing::(stash, amount); - let _ = ledger .update() .defensive_proof("ledger fetched from storage, so it exists; qed."); - Ok(r) }) .unwrap_or_default(), RewardDestination::Account(ref dest_account) => - Some(asset::mint_creating::(&dest_account, amount)), + Some(asset::mint_creating::(dest_account, amount)), RewardDestination::None => None, #[allow(deprecated)] - RewardDestination::Controller => Self::bonded(stash) - .map(|controller| { - defensive!("Paying out controller as reward destination which is deprecated and should be migrated."); - // This should never happen once payees with a `Controller` variant have been migrated. - // But if it does, just pay the controller account. - asset::mint_creating::(&controller, amount) - }), + RewardDestination::Controller => Self::bonded(stash).map(|controller| { + defensive!("Paying out controller as reward destination which is deprecated."); + asset::mint_creating::(&controller, amount) + }), }; maybe_imbalance.map(|imbalance| (imbalance, dest)) } + /// Chill a stash account. + pub(crate) fn chill_stash(stash: &T::AccountId) { + let chilled_as_validator = Self::do_remove_validator(stash); + let chilled_as_nominator = Self::do_remove_nominator(stash); + if chilled_as_validator || chilled_as_nominator { + Self::deposit_event(Event::::Chilled { stash: stash.clone() }); + } + } + /// Remove all associated data of a stash account from the staking system. /// /// Assumes storage is upgraded before calling. From 986390f899e736a26ad20285b4e960f3b27d971d Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:41:40 +0200 Subject: [PATCH 05/87] session rotation --- .../staking-async/src/session_rotation.rs | 85 ++++++------------- 1 file changed, 27 insertions(+), 58 deletions(-) diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index 376bc4b49ebe4..c5b5d6a6bbe47 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -81,11 +81,11 @@ use alloc::{boxed::Box, vec::Vec}; use frame_election_provider_support::{BoundedSupportsOf, ElectionProvider, PageIndex}; use frame_support::{ pallet_prelude::*, - traits::{Defensive, DefensiveMax, DefensiveSaturating, OnUnbalanced, TryCollect}, + traits::{Defensive, DefensiveMax, DefensiveSaturating, TryCollect}, weights::WeightMeter, }; use pallet_staking_async_rc_client::RcClientInterface; -use sp_runtime::{Perbill, Percent, Saturating}; +use sp_runtime::{Perbill, Saturating}; use sp_staking::{ currency_to_vote::CurrencyToVote, Exposure, Page, PagedExposureMetadata, SessionIndex, }; @@ -362,11 +362,11 @@ impl Eras { }; } - pub(crate) fn set_validators_reward(era: EraIndex, amount: BalanceOf) { + pub(crate) fn set_stakers_reward(era: EraIndex, amount: BalanceOf) { ErasValidatorReward::::insert(era, amount); } - pub(crate) fn get_validators_reward(era: EraIndex) -> Option> { + pub(crate) fn get_stakers_reward(era: EraIndex) -> Option> { ErasValidatorReward::::get(era) } @@ -743,6 +743,11 @@ impl Rotator { // cleanup election state EraElectionPlanner::::cleanup(); + // Cleanup era pot accounts for eras that have expired. + if let Some(era_to_cleanup) = starting_era.checked_sub(T::HistoryDepth::get() + 1) { + reward::EraRewardManager::::cleanup_era(era_to_cleanup); + } + // Mark ancient era for lazy pruning instead of immediately pruning it. if let Some(old_era) = starting_era.checked_sub(T::HistoryDepth::get() + 1) { log!(debug, "Marking era {:?} for lazy pruning", old_era); @@ -796,64 +801,28 @@ impl Rotator { }); } - fn end_era(ending_era: &ActiveEraInfo, new_era_start: u64) { - let previous_era_start = ending_era.start.defensive_unwrap_or(new_era_start); - let uncapped_era_duration = new_era_start.saturating_sub(previous_era_start); - - // maybe cap the era duration to the maximum allowed by the runtime. - let cap = T::MaxEraDuration::get(); - let era_duration = if cap == 0 { - // if the cap is zero (not set), we don't cap the era duration. - uncapped_era_duration - } else if uncapped_era_duration > cap { - Pallet::::deposit_event(Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded)); - - // if the cap is set, and era duration exceeds the cap, we cap the era duration to the - // maximum allowed. - log!( - warn, - "capping era duration for era {:?} from {:?} to max allowed {:?}", - ending_era.index, - uncapped_era_duration, - cap - ); - cap - } else { - uncapped_era_duration - }; - - Self::end_era_compute_payout(ending_era, era_duration); - } - - fn end_era_compute_payout(ending_era: &ActiveEraInfo, era_duration: u64) { - let staked = ErasTotalStake::::get(ending_era.index); - let issuance = asset::total_issuance::(); - - log!( - debug, - "computing inflation for era {:?} with duration {:?}", - ending_era.index, - era_duration - ); - let (validator_payout, remainder) = - T::EraPayout::era_payout(staked, issuance, era_duration); + fn end_era(ending_era: &ActiveEraInfo, _new_era_start: u64) { + log!(debug, "snapshotting reward pots for era {:?}", ending_era.index); - let total_payout = validator_payout.saturating_add(remainder); - let max_staked_rewards = MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); + // Snapshot general staker reward pot into era-specific pot. + // DAP has been dripping inflation into the general pot since the last era boundary. + let staker_rewards = + reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); - // apply cap to validators payout and add difference to remainder. - let validator_payout = validator_payout.min(max_staked_rewards * total_payout); - let remainder = total_payout.saturating_sub(validator_payout); + if staker_rewards.is_zero() { + log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index); + } - Pallet::::deposit_event(Event::::EraPaid { - era_index: ending_era.index, - validator_payout, - remainder, - }); + Eras::::set_stakers_reward(ending_era.index, staker_rewards); - // Set ending era reward. - Eras::::set_validators_reward(ending_era.index, validator_payout); - T::RewardRemainder::on_unbalanced(asset::issue::(remainder)); + // Update DisableLegacyMintingEra to prevent legacy minting. + if !staker_rewards.is_zero() { + DisableLegacyMintingEra::::mutate(|maybe_era| { + if maybe_era.is_none() || maybe_era.is_some_and(|e| ending_era.index < e) { + *maybe_era = Some(ending_era.index); + } + }); + } } /// Plans a new era by kicking off the election process. From 9c8e724551de3707b3adfa8603ff90640ee25c2b Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 20:43:45 +0200 Subject: [PATCH 06/87] remove default impl for account id provider --- substrate/frame/staking-async/src/lib.rs | 100 ++++++++++++++++++ .../frame/staking-async/src/pallet/mod.rs | 5 +- substrate/frame/staking-async/src/weights.rs | 9 ++ 3 files changed, 111 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/lib.rs b/substrate/frame/staking-async/src/lib.rs index 9aac50b542639..6dd5894c5554c 100644 --- a/substrate/frame/staking-async/src/lib.rs +++ b/substrate/frame/staking-async/src/lib.rs @@ -191,6 +191,7 @@ pub mod asset; pub mod election_size_tracker; pub mod ledger; mod pallet; +pub mod reward; pub mod session_rotation; pub mod slashing; pub mod weights; @@ -545,6 +546,105 @@ impl Contains for AllStakers { } } +/// Identifies the era pot account for staker rewards. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub enum EraPotType { + /// Pot for staker rewards (nominators + validators). + StakerRewards, +} + +/// Trait for generating era pot account IDs. +pub trait EraPotAccountProvider { + fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId; +} + +/// Seed-based pot account provider for production use. +pub struct Seed(core::marker::PhantomData); + +impl EraPotAccountProvider for Seed +where + AccountId: codec::FullCodec, + S: Get, +{ + fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId { + use sp_runtime::traits::AccountIdConversion; + S::get().into_sub_account_truncating((era, pot_type)) + } +} + +/// Sequential pot account provider for testing. +#[cfg(feature = "std")] +pub struct SequentialTest; + +#[cfg(feature = "std")] +impl EraPotAccountProvider for SequentialTest +where + AccountId: From, +{ + fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId { + let pot_type_offset = match pot_type { + EraPotType::StakerRewards => 0, + }; + AccountId::from(100_000 + (era as u64 * 10) + pot_type_offset) + } +} + +/// Identifies the general (non-era-specific) reward pot. +/// +/// DAP drips inflation into this account continuously. At era boundaries, +/// staking snapshots the balance and transfers it to an era-specific pot. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub enum GeneralPotType { + /// General pot for staker rewards. + StakerRewards, +} + +/// Trait that provides general (non-era-specific) pot accounts. +pub trait GeneralPotAccountProvider { + fn general_pot_account(pot_type: GeneralPotType) -> AccountId; +} + +impl GeneralPotAccountProvider for Seed +where + AccountId: codec::FullCodec, + S: Get, +{ + fn general_pot_account(pot_type: GeneralPotType) -> AccountId { + use sp_runtime::traits::AccountIdConversion; + S::get().into_sub_account_truncating(pot_type) + } +} + +#[cfg(feature = "std")] +impl GeneralPotAccountProvider for SequentialTest +where + AccountId: From, +{ + fn general_pot_account(pot_type: GeneralPotType) -> AccountId { + match pot_type { + GeneralPotType::StakerRewards => AccountId::from(200_000u64), + } + } +} + +/// Budget recipient for staker rewards. +/// +/// Exposes the general staker reward pot so DAP can drip inflation into it. +pub struct StakerRewardRecipient(core::marker::PhantomData); + +impl sp_staking::budget::BudgetRecipient for StakerRewardRecipient +where + G: GeneralPotAccountProvider, +{ + fn budget_key() -> sp_staking::budget::BudgetKey { + sp_staking::budget::BudgetKey::truncate_from(b"staker_rewards".to_vec()) + } + + fn pot_account() -> AccountId { + G::general_pot_account(GeneralPotType::StakerRewards) + } +} + /// A smart type to determine the [`Config::PlanningEraOffset`], given: /// /// * Expected relay session duration, `RS` diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 7384df124b1ac..05454555b6317 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -265,10 +265,11 @@ pub mod pallet { /// /// DAP drips inflation into these pots. At era boundaries, staking snapshots /// and transfers the balances to era-specific pots. + #[pallet::no_default] type GeneralPots: crate::GeneralPotAccountProvider; /// Provider for generating era pot account IDs. - #[pallet::no_default_bounds] + #[pallet::no_default] type EraPots: crate::EraPotAccountProvider; /// Calculator for staker rewards. @@ -420,8 +421,6 @@ pub mod pallet { type Slash = (); type Reward = (); type UnclaimedRewardHandler = (); - type GeneralPots = (); - type EraPots = (); type StakerRewardCalculator = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; diff --git a/substrate/frame/staking-async/src/weights.rs b/substrate/frame/staking-async/src/weights.rs index 7167ec12e2338..02835b1205449 100644 --- a/substrate/frame/staking-async/src/weights.rs +++ b/substrate/frame/staking-async/src/weights.rs @@ -84,6 +84,7 @@ pub trait WeightInfo { fn chill_other() -> Weight; fn force_apply_min_commission() -> Weight; fn set_min_commission() -> Weight; + fn set_max_commission() -> Weight; fn restore_ledger() -> Weight; fn migrate_currency() -> Weight; fn apply_slash(n: u32, ) -> Weight; @@ -722,6 +723,10 @@ impl WeightInfo for SubstrateWeight { Weight::from_parts(4_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } + fn set_max_commission() -> Weight { + // TODO(ank4n): rebench + Self::set_min_commission() + } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) @@ -1634,6 +1639,10 @@ impl WeightInfo for () { Weight::from_parts(4_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + fn set_max_commission() -> Weight { + // TODO(ank4n): rebench + Self::set_min_commission() + } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) From bf84f151afceb9461b9c22df68bd02f2b36c45d5 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:05:38 +0200 Subject: [PATCH 07/87] staking test compile --- substrate/frame/staking-async/src/mock.rs | 137 +++++++++++++----- .../staking-async/src/tests/era_rotation.rs | 62 -------- 2 files changed, 100 insertions(+), 99 deletions(-) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index ddce568c74720..305d55b061002 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -39,7 +39,9 @@ use sp_io; use sp_npos_elections::BalancingConfig; use sp_runtime::{traits::Zero, BuildStorage, Weight}; use sp_staking::{ - currency_to_vote::SaturatingCurrencyToVote, OnStakingUpdate, SessionIndex, StakingAccount, + budget::{BudgetRecipient, IssuanceCurve}, + currency_to_vote::SaturatingCurrencyToVote, + OnStakingUpdate, SessionIndex, StakingAccount, }; use std::collections::BTreeMap; @@ -50,6 +52,7 @@ frame_support::construct_runtime!( pub enum Test { System: frame_system, Balances: pallet_balances, + Dap: pallet_dap, Staking: pallet_staking_async, VoterBagsList: pallet_bags_list::, } @@ -410,53 +413,98 @@ ord_parameter_types! { parameter_types! { pub static RemainderRatio: Perbill = Perbill::from_percent(50); - pub static MaxEraDuration: u64 = time_per_era() * 7; pub const MaxPruningItems: u32 = 100; + pub const DapPalletId: frame_support::PalletId = frame_support::PalletId(*b"dap/buff"); + pub const TestIssuanceCadence: u64 = 0; // drip every block + pub const TestMaxElapsedPerDrip: u64 = 600_000; // 10 minutes } + +/// Mock time provider backed by session_mock::Timestamp. +pub struct MockTime; +impl frame_support::traits::Time for MockTime { + type Moment = u64; + fn now() -> u64 { + session_mock::Timestamp::get() + } +} + +pub fn general_staker_pot() -> AccountId { + SequentialTest::general_pot_account(GeneralPotType::StakerRewards) +} + +impl pallet_dap::Config for Test { + type Currency = Balances; + type PalletId = DapPalletId; + type IssuanceCurve = OneTokenPerMillisecond; + type BudgetRecipients = (Dap, StakerRewardRecipient); + type Time = MockTime; + type IssuanceCadence = TestIssuanceCadence; + type MaxElapsedPerDrip = TestMaxElapsedPerDrip; + type BudgetOrigin = EnsureRoot; + type WeightInfo = (); +} + pub struct OneTokenPerMillisecond; -impl EraPayout for OneTokenPerMillisecond { - fn era_payout( - _total_staked: Balance, - _total_issuance: Balance, - era_duration_millis: u64, - ) -> (Balance, Balance) { - let total = era_duration_millis as Balance; - let remainder = RemainderRatio::get() * total; - let stakers = total - remainder; - (stakers, remainder) +impl IssuanceCurve for OneTokenPerMillisecond { + fn issue(_total_issuance: Balance, elapsed_millis: u64) -> Balance { + elapsed_millis as Balance } } -impl crate::pallet::pallet::Config for Test { - type RuntimeHoldReason = RuntimeHoldReason; +pub(crate) fn staker_reward_key() -> sp_staking::budget::BudgetKey { + as BudgetRecipient>::budget_key() +} + +pub(crate) fn buffer_key() -> sp_staking::budget::BudgetKey { + >::budget_key() +} + +pub(crate) fn build_budget( + entries: &[(sp_staking::budget::BudgetKey, u32)], +) -> pallet_dap::BudgetAllocationMap { + let mut budget = BoundedBTreeMap::new(); + for (key, pct) in entries { + budget.try_insert(key.clone(), Perbill::from_percent(*pct)).unwrap(); + } + budget +} + +pub(crate) fn default_budget() -> pallet_dap::BudgetAllocationMap { + build_budget(&[(staker_reward_key(), 50), (buffer_key(), 50)]) +} + +impl Config for Test { type OldCurrency = Balances; type Currency = Balances; + type RuntimeHoldReason = RuntimeHoldReason; + type CurrencyBalance = Balance; + type CurrencyToVote = SaturatingCurrencyToVote; + type ElectionProvider = TestElectionProvider; + type NominationsQuota = WeightedNominationsQuota<16>; + type HistoryDepth = HistoryDepth; type RewardRemainder = RewardRemainderMock; + type Slash = Dap; + type UnclaimedRewardHandler = Dap; type Reward = MockReward; type SessionsPerEra = SessionsPerEra; + type PlanningEraOffset = PlanningEraOffset; + type BondingDuration = BondingDuration; + type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOfDiverse, EnsureSignedBy>; - type EraPayout = OneTokenPerMillisecond; + type GeneralPots = SequentialTest; + type EraPots = SequentialTest; type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxValidatorSet; - type ElectionProvider = TestElectionProvider; type VoterList = VoterBagsList; type TargetList = UseValidatorsMap; - type NominationsQuota = WeightedNominationsQuota<16>; type MaxUnlockingChunks = MaxUnlockingChunks; - type HistoryDepth = HistoryDepth; - type BondingDuration = BondingDuration; - type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = EventListenerMock; - type MaxEraDuration = MaxEraDuration; type MaxPruningItems = MaxPruningItems; - type PlanningEraOffset = PlanningEraOffset; - type Filter = MockedRestrictList; type RcClientInterface = session_mock::Session; - type CurrencyBalance = Balance; - type CurrencyToVote = SaturatingCurrencyToVote; - type Slash = (); + type Filter = MockedRestrictList; + type StakerRewardCalculator = reward::DefaultStakerRewardCalculator; type WeightInfo = (); } @@ -709,6 +757,25 @@ impl ExtBuilder { ext.execute_with(|| { crate::AreNominatorsSlashable::::put(nominators_slashable); + // Disable legacy minting from era 0 in tests to catch missing era pots early. + crate::DisableLegacyMintingEra::::put(0); + // Set budget allocation: 50% stakers, 50% buffer (must sum to 100%). + pallet_dap::BudgetAllocation::::put(default_budget()); + // Initialize DAP's LastIssuanceTimestamp. + pallet_dap::LastIssuanceTimestamp::::put(INIT_TIMESTAMP); + // Fund general pot accounts with ED so they stay alive across era snapshots. + let ed = ExistentialDeposit::get(); + >::mint_into( + &general_staker_pot(), + ed, + ) + .expect("mint general staker pot"); + // Fund DAP buffer account with ED. + let dap_buffer = as BudgetRecipient< + AccountId, + >>::pot_account(); + >::mint_into(&dap_buffer, ed) + .expect("mint dap buffer"); session_mock::Session::roll_until_active_era(1); RewardRemainderUnbalanced::set(0); if self.flush_events { @@ -765,22 +832,18 @@ pub(crate) fn bond_virtual_nominator( } pub(crate) fn validator_payout_for(duration: u64) -> Balance { - let (payout, _rest) = ::EraPayout::era_payout( - pallet_staking_async::ErasTotalStake::::get(active_era()), - pallet_balances::TotalIssuance::::get(), - duration, - ); + let total_inflation = + OneTokenPerMillisecond::issue(pallet_balances::TotalIssuance::::get(), duration); + // Apply budget allocation to get staker portion + let budget = pallet_dap::BudgetAllocation::::get(); + let staker_pct = budget.get(&staker_reward_key()).copied().unwrap_or(Perbill::zero()); + let payout = staker_pct.mul_floor(total_inflation); assert!(payout > 0); payout } pub(crate) fn total_payout_for(duration: u64) -> Balance { - let (payout, rest) = ::EraPayout::era_payout( - pallet_staking_async::ErasTotalStake::::get(active_era()), - pallet_balances::TotalIssuance::::get(), - duration, - ); - payout + rest + OneTokenPerMillisecond::issue(pallet_balances::TotalIssuance::::get(), duration) } /// Time it takes to finish a session. diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 6fe05bb54c459..40a28120b364d 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -227,68 +227,6 @@ fn activation_timestamp_when_era_planning_not_complete() { todo!("what if we receive an activation timestamp when the era planning (election) is not complete?"); } -#[test] -fn max_era_duration_safety_guard() { - ExtBuilder::default().build_and_execute(|| { - // let's deduce some magic numbers for the test. - let ideal_era_payout = total_payout_for(time_per_era()); - let ideal_treasury_payout = RemainderRatio::get() * ideal_era_payout; - let ideal_validator_payout = ideal_era_payout - ideal_treasury_payout; - // max era duration is capped to 7 times the ideal era duration. - let max_validator_payout = 7 * ideal_validator_payout; - let max_treasury_payout = 7 * ideal_treasury_payout; - - // these are the values we expect to see in the events. - assert_eq!(ideal_treasury_payout, 7500); - assert_eq!(ideal_validator_payout, 7500); - // when the era duration exceeds `MaxEraDuration`, the payouts should be capped to the - // following values. - assert_eq!(max_treasury_payout, 52500); - assert_eq!(max_validator_payout, 52500); - - // GIVEN: we are at end of an era (2). - Session::roll_until_active_era(2); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, - Event::PagedElectionProceeded { page: 0, result: Ok(2) }, - Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { - era_index: 1, - validator_payout: ideal_validator_payout, - remainder: ideal_treasury_payout - }, - Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } - ] - ); - - // WHEN: subsequent era takes longer than MaxEraDuration. - // (this can happen either because of a bug or because a long stall in the chain). - Timestamp::set(Timestamp::get() + 2 * MaxEraDuration::get()); - Session::roll_until_active_era(3); - - // THEN: we should see the payouts capped to the max values. - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 }, - Event::PagedElectionProceeded { page: 0, result: Ok(2) }, - Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 }, - // an event is emitted to indicate something unexpected happened, i.e. the era - // duration exceeded the `MaxEraDuration` limit. - Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded), - // the payouts are capped to the max values. - Event::EraPaid { - era_index: 2, - validator_payout: max_validator_payout, - remainder: max_treasury_payout - }, - Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 } - ] - ); - }); -} #[test] fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { From d8ff4075d5e5840d22c071e47d26650c2ba9aad5 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:06:33 +0200 Subject: [PATCH 08/87] integration test compiles --- .../frame/staking-async/integration-tests/src/ah/mock.rs | 6 ++++-- .../frame/staking-async/integration-tests/src/ah/weights.rs | 3 +++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index 95e1fc69f4295..bfed504789fbd 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -457,13 +457,15 @@ impl pallet_staking_async::Config for Runtime { type ElectionProvider = MultiBlock; - type EraPayout = (); + type UnclaimedRewardHandler = (); + type GeneralPots = pallet_staking_async::SequentialTest; + type EraPots = pallet_staking_async::SequentialTest; + type StakerRewardCalculator = (); type EventListeners = (); type Reward = (); type RewardRemainder = (); type Slash = Dap; type SlashDeferDuration = SlashDeferredDuration; - type MaxEraDuration = (); type MaxPruningItems = MaxPruningItems; type HistoryDepth = ConstU32<7>; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs index 18431b5cb65d7..b63e5b4660036 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs @@ -195,6 +195,9 @@ impl pallet_staking_async::WeightInfo for StakingAsyncWeightInfo { fn set_min_commission() -> Weight { unreachable!() } + fn set_max_commission() -> Weight { + unreachable!() + } fn restore_ledger() -> Weight { unreachable!() } From 87891e4c655d5e0c5be5ca8e062092c3e62c00b9 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:08:38 +0200 Subject: [PATCH 09/87] fmt --- substrate/frame/staking-async/src/mock.rs | 5 ++--- .../frame/staking-async/src/pallet/impls.rs | 18 +++++++++--------- substrate/frame/staking-async/src/reward.rs | 3 +-- .../staking-async/src/session_rotation.rs | 3 +-- .../staking-async/src/tests/era_rotation.rs | 1 - 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 305d55b061002..e2f6db7514948 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -771,9 +771,8 @@ impl ExtBuilder { ) .expect("mint general staker pot"); // Fund DAP buffer account with ED. - let dap_buffer = as BudgetRecipient< - AccountId, - >>::pot_account(); + let dap_buffer = + as BudgetRecipient>::pot_account(); >::mint_into(&dap_buffer, ed) .expect("mint dap buffer"); session_mock::Session::roll_until_active_era(1); diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 47410533aa6db..816469ec5837d 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -28,7 +28,6 @@ use crate::{ Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, StakingLedger, ValidatorPrefs, STAKING_ID, }; -use sp_staking::StakerRewardCalculator; use alloc::{boxed::Box, vec, vec::Vec}; use frame_election_provider_support::{ bounds::CountBound, data_provider, DataProviderBounds, ElectionDataProvider, ElectionProvider, @@ -53,7 +52,7 @@ use sp_runtime::{ }; use sp_staking::{ currency_to_vote::CurrencyToVote, - EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, + EraIndex, OnStakingUpdate, Page, SessionIndex, Stake, StakerRewardCalculator, StakingAccount::{self, Controller, Stash}, StakingInterface, }; @@ -470,8 +469,7 @@ impl Pallet { ) -> u32 { let mut nominator_payout_count: u32 = 0; - if let Some((amount, dest)) = - Self::make_payout_from_provider(era, stash, validator_payout) + if let Some((amount, dest)) = Self::make_payout_from_provider(era, stash, validator_payout) { Self::deposit_event(Event::::Rewarded { stash: stash.clone(), dest, amount }); } @@ -580,8 +578,7 @@ impl Pallet { let payout_account = Self::payout_account_for_dest(stash, &dest)?; - let staker_rewards_pot = - T::EraPots::era_pot_account(era, crate::EraPotType::StakerRewards); + let staker_rewards_pot = T::EraPots::era_pot_account(era, crate::EraPotType::StakerRewards); if let Err(e) = T::Currency::transfer( &staker_rewards_pot, &payout_account, @@ -591,7 +588,9 @@ impl Pallet { log!( error, "Failed to transfer reward from pot for era {:?}, stash {:?}: {:?}", - era, stash, e + era, + stash, + e ); return None; } @@ -633,8 +632,9 @@ impl Pallet { Ok(r) }) .unwrap_or_default(), - RewardDestination::Account(ref dest_account) => - Some(asset::mint_creating::(dest_account, amount)), + RewardDestination::Account(ref dest_account) => { + Some(asset::mint_creating::(dest_account, amount)) + }, RewardDestination::None => None, #[allow(deprecated)] RewardDestination::Controller => Self::bonded(stash).map(|controller| { diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index 251f7af2fdf1f..337cf66968acd 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -50,8 +50,7 @@ impl EraRewardManager { pub(crate) fn snapshot_era_rewards(era: EraIndex) -> BalanceOf { let staker_era_pot = Self::create(era, EraPotType::StakerRewards); - let general_staker_pot = - T::GeneralPots::general_pot_account(GeneralPotType::StakerRewards); + let general_staker_pot = T::GeneralPots::general_pot_account(GeneralPotType::StakerRewards); // Leave ED in the general pot to keep it alive. let staker_balance = T::Currency::reducible_balance( diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index c5b5d6a6bbe47..1b3305da90d66 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -806,8 +806,7 @@ impl Rotator { // Snapshot general staker reward pot into era-specific pot. // DAP has been dripping inflation into the general pot since the last era boundary. - let staker_rewards = - reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); + let staker_rewards = reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); if staker_rewards.is_zero() { log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index); diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 40a28120b364d..0a42b3308ea7d 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -227,7 +227,6 @@ fn activation_timestamp_when_era_planning_not_complete() { todo!("what if we receive an activation timestamp when the era planning (election) is not complete?"); } - #[test] fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { ExtBuilder::default().build_and_execute(|| { From eee0f27057786aeaae7d8cfc54f9780d1eace16e Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:53:38 +0200 Subject: [PATCH 10/87] fix tests minimal change --- .../staking-async/src/session_rotation.rs | 8 +++++++ .../frame/staking-async/src/tests/bonding.rs | 12 +++++------ .../src/tests/election_provider.rs | 20 +++++++++--------- .../staking-async/src/tests/era_rotation.rs | 21 ++++++++++--------- .../frame/staking-async/src/tests/mod.rs | 6 +++--- .../staking-async/src/tests/payout_stakers.rs | 8 +++---- .../frame/staking-async/src/tests/slashing.rs | 8 +++---- 7 files changed, 46 insertions(+), 37 deletions(-) diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index 1b3305da90d66..b1d396aecebed 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -814,6 +814,14 @@ impl Rotator { Eras::::set_stakers_reward(ending_era.index, staker_rewards); + // Note: `remainder` is always zero with DAP-based inflation — treasury/buffer + // allocation is handled by DAP directly. + Pallet::::deposit_event(Event::::EraPaid { + era_index: ending_era.index, + validator_payout: staker_rewards, + remainder: Zero::zero(), + }); + // Update DisableLegacyMintingEra to prevent legacy minting. if !staker_rewards.is_zero() { DisableLegacyMintingEra::::mutate(|maybe_era| { diff --git a/substrate/frame/staking-async/src/tests/bonding.rs b/substrate/frame/staking-async/src/tests/bonding.rs index 21faa3889d688..2b60c0028ec81 100644 --- a/substrate/frame/staking-async/src/tests/bonding.rs +++ b/substrate/frame/staking-async/src/tests/bonding.rs @@ -1013,11 +1013,11 @@ fn bond_with_little_staked_value_bounded() { staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, - Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: 2500 }, + Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: 2499 }, Event::PayoutStarted { era_index: 1, validator_stash: 21, page: 0, next: None }, - Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 2500 }, + Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 2499 }, Event::PayoutStarted { era_index: 1, validator_stash: 31, page: 0, next: None }, - Event::Rewarded { stash: 31, dest: RewardDestination::Staked, amount: 2500 } + Event::Rewarded { stash: 31, dest: RewardDestination::Staked, amount: 2499 } ] ); @@ -1032,11 +1032,11 @@ fn bond_with_little_staked_value_bounded() { staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 2, validator_stash: 1, page: 0, next: None }, - Event::Rewarded { stash: 1, dest: RewardDestination::Account(1), amount: 2500 }, + Event::Rewarded { stash: 1, dest: RewardDestination::Account(1), amount: 2499 }, Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 0, next: None }, - Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: 2500 }, + Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: 2499 }, Event::PayoutStarted { era_index: 2, validator_stash: 21, page: 0, next: None }, - Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 2500 } + Event::Rewarded { stash: 21, dest: RewardDestination::Staked, amount: 2499 } ] ); diff --git a/substrate/frame/staking-async/src/tests/election_provider.rs b/substrate/frame/staking-async/src/tests/election_provider.rs index 55bed312e2698..6d081c737667a 100644 --- a/substrate/frame/staking-async/src/tests/election_provider.rs +++ b/substrate/frame/staking-async/src/tests/election_provider.rs @@ -49,7 +49,7 @@ fn planning_era_offset_less_0() { Event::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 7, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 20000, remainder: 20000 }, + Event::EraPaid { era_index: 0, validator_payout: 20000, remainder: 0 }, Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 1 } ] ); @@ -69,7 +69,7 @@ fn planning_era_offset_less_0() { Event::SessionRotated { starting_session: 14, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 15, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 20000, remainder: 20000 }, + Event::EraPaid { era_index: 1, validator_payout: 20000, remainder: 0 }, Event::SessionRotated { starting_session: 16, active_era: 2, planned_era: 2 } ] ); @@ -101,7 +101,7 @@ fn planning_era_offset_works_1() { Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 17500, remainder: 17500 }, + Event::EraPaid { era_index: 0, validator_payout: 17500, remainder: 0 }, Event::SessionRotated { starting_session: 7, active_era: 1, planned_era: 1 } ] ); @@ -120,7 +120,7 @@ fn planning_era_offset_works_1() { Event::SessionRotated { starting_session: 12, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 13, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 17500, remainder: 17500 }, + Event::EraPaid { era_index: 1, validator_payout: 17500, remainder: 0 }, Event::SessionRotated { starting_session: 14, active_era: 2, planned_era: 2 } ] ); @@ -148,7 +148,7 @@ fn planning_era_offset_works_2() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -166,7 +166,7 @@ fn planning_era_offset_works_2() { Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 } ] ); @@ -194,7 +194,7 @@ fn planning_era_offset_works_smart() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -212,7 +212,7 @@ fn planning_era_offset_works_smart() { Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 } ] ); @@ -241,7 +241,7 @@ fn planning_era_offset_works_smart_with_delay() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -259,7 +259,7 @@ fn planning_era_offset_works_smart_with_delay() { Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 } ] ); diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 0a42b3308ea7d..461a031b69354 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -19,6 +19,7 @@ use crate::{ session_rotation::{Eras, Rotator}, tests::session_mock::{CurrentIndex, Timestamp}, }; +use frame_support::traits::fungible::Inspect; use super::*; @@ -72,7 +73,7 @@ fn forcing_no_forcing_default() { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); @@ -95,7 +96,7 @@ fn forcing_force_always() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -112,9 +113,9 @@ fn forcing_force_always() { Event::PagedElectionProceeded { page: 0, result: Ok(2) }, // by now it is given to mock session, and is buffered Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, // and by now it is activated. Note how the validator payout is less, since the // era duration is less. Note that we immediately plan the next era as well. + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 9, active_era: 2, planned_era: 3 } ] ); @@ -137,7 +138,7 @@ fn forcing_force_new() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -155,9 +156,9 @@ fn forcing_force_new() { Event::PagedElectionProceeded { page: 0, result: Ok(2) }, // by now it is given to mock session, and is buffered Event::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, // and by now it is activated. Note how the validator payout is less, since the // era duration is less. + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 9, active_era: 2, planned_era: 2 } ] ); @@ -173,7 +174,7 @@ fn forcing_force_new() { Event::SessionRotated { starting_session: 13, active_era: 2, planned_era: 3 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 14, active_era: 2, planned_era: 3 }, - Event::EraPaid { era_index: 2, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 2, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 15, active_era: 3, planned_era: 3 } ] ); @@ -265,8 +266,8 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { &staking_events_since_last_call()[..], &[ .., - Event::EraPaid { era_index: 80, validator_payout: 7500, remainder: 7500 }, // NO EraPruned event - pruning is now manual + Event::EraPaid { era_index: 80, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 243, active_era: 81, planned_era: 81 } ] )); @@ -475,7 +476,7 @@ mod inflation { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); @@ -489,7 +490,7 @@ mod inflation { fn max_staked_rewards_default_equal_100() { ExtBuilder::default().build_and_execute(|| { let default_stakers_payout = validator_payout_for(time_per_era()); - assert!(default_stakers_payout > 0); + assert_eq!(default_stakers_payout, Balance::from(time_per_era()) / 2); >::set(Some(Percent::from_parts(100))); Session::roll_until_active_era(2); @@ -500,7 +501,7 @@ mod inflation { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); diff --git a/substrate/frame/staking-async/src/tests/mod.rs b/substrate/frame/staking-async/src/tests/mod.rs index f07674a0d4278..540990b903cd0 100644 --- a/substrate/frame/staking-async/src/tests/mod.rs +++ b/substrate/frame/staking-async/src/tests/mod.rs @@ -203,7 +203,7 @@ fn basic_setup_works() { Event::SessionRotated { starting_session: 1, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 2, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 0, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 3, active_era: 1, planned_era: 1 } ] ); @@ -255,7 +255,7 @@ fn basic_setup_sessions_per_era() { Event::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, - Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 0, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 1, planned_era: 1 } ] ); @@ -275,7 +275,7 @@ fn basic_setup_sessions_per_era() { Event::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 15000 }, + Event::EraPaid { era_index: 1, validator_payout: 15000, remainder: 0 }, Event::SessionRotated { starting_session: 12, active_era: 2, planned_era: 2 } ] ); diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 9242147203b2a..99f7813c703d0 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -227,7 +227,7 @@ fn nominating_and_rewards_should_work() { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); @@ -279,7 +279,7 @@ fn nominating_and_rewards_should_work() { staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 0, next: None }, - Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 500 }, + Event::Rewarded { stash: 11, dest: RewardDestination::Staked, amount: 499 }, Event::Rewarded { stash: 1, dest: RewardDestination::Stash, amount: 750 }, Event::Rewarded { stash: 3, @@ -287,8 +287,8 @@ fn nominating_and_rewards_should_work() { amount: 2500 }, Event::PayoutStarted { era_index: 2, validator_stash: 41, page: 0, next: None }, - Event::Rewarded { stash: 41, dest: RewardDestination::Staked, amount: 2000 }, - Event::Rewarded { stash: 1, dest: RewardDestination::Stash, amount: 1750 } + Event::Rewarded { stash: 41, dest: RewardDestination::Staked, amount: 1999 }, + Event::Rewarded { stash: 1, dest: RewardDestination::Stash, amount: 1751 } ] ); }); diff --git a/substrate/frame/staking-async/src/tests/slashing.rs b/substrate/frame/staking-async/src/tests/slashing.rs index 58f45afd095c9..c3a1c0f8e1bde 100644 --- a/substrate/frame/staking-async/src/tests/slashing.rs +++ b/substrate/frame/staking-async/src/tests/slashing.rs @@ -356,7 +356,7 @@ fn deferred_slashes_are_deferred() { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); @@ -372,7 +372,7 @@ fn deferred_slashes_are_deferred() { Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 }, - Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 } ] ); @@ -431,7 +431,7 @@ fn retroactive_deferred_slashes_two_eras_before() { Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 }, - Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 } ] ); @@ -490,7 +490,7 @@ fn retroactive_deferred_slashes_one_before() { Event::SessionRotated { starting_session: 10, active_era: 3, planned_era: 4 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 11, active_era: 3, planned_era: 4 }, - Event::EraPaid { era_index: 3, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 3, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 12, active_era: 4, planned_era: 4 } ] ); From 2f743543fc854016708d9e75d2c49eda728ab2a9 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:54:30 +0200 Subject: [PATCH 11/87] update EraPaid rustdoc --- substrate/frame/staking-async/src/pallet/mod.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 05454555b6317..018a769152a61 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -1152,8 +1152,11 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { - /// The era payout has been set; the first balance is the validator-payout; the second is - /// the remainder from the maximum amount of reward. + /// The era payout has been set; `validator_payout` is the staker reward budget + /// snapshotted from the DAP general pot. + /// + /// Note: `remainder` is always zero with DAP-based inflation. Treasury/buffer + /// allocation is handled by DAP directly and is no longer reported here. EraPaid { era_index: EraIndex, validator_payout: BalanceOf, From 6059429be695c5dfe9e1c6e1598bdcbae38b8b48 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 21:59:24 +0200 Subject: [PATCH 12/87] fix all test --- .../staking-async/src/tests/era_rotation.rs | 136 +++++++++++------- .../staking-async/src/tests/payout_stakers.rs | 97 +++++++------ 2 files changed, 135 insertions(+), 98 deletions(-) diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 461a031b69354..e14fbd007171b 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -240,10 +240,15 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { &[ .., Event::SessionRotated { starting_session: 236, active_era: 78, planned_era: 79 }, - Event::EraPaid { era_index: 78, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 78, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 237, active_era: 79, planned_era: 79 } ] )); + // Verify era 78 staker pot has been funded (DAP drips into general pot, staking snapshots). + let staker_pot_78 = + ::EraPots::era_pot_account(78, EraPotType::StakerRewards); + let ideal_validator_payout = validator_payout_for(time_per_era()); + assert_eq!(Balances::balance(&staker_pot_78), ideal_validator_payout); // All eras from 1 to current still present assert_ok!(Eras::::era_fully_present(1)); assert_ok!(Eras::::era_fully_present(2)); @@ -283,11 +288,22 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { &staking_events_since_last_call()[..], &[ .., - Event::EraPaid { era_index: 81, validator_payout: 7500, remainder: 7500 }, // NO EraPruned event - pruning is now manual + Event::EraPaid { era_index: 81, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 246, active_era: 82, planned_era: 82 } ] )); + // Verify eras 79-81 staker pots were funded with expected amount. + let expected_per_era = validator_payout_for(time_per_era()); + for era in 79..=81 { + let staker_pot = + ::EraPots::era_pot_account(era, EraPotType::StakerRewards); + assert_eq!( + Balances::balance(&staker_pot), + expected_per_era, + "Era {era} staker pot should have {expected_per_era}" + ); + } // Only old eras (outside pruning window) can be pruned // Try to prune era 2 (should fail as it's within the history window) @@ -463,8 +479,9 @@ mod inflation { #[test] fn max_staked_rewards_default_not_set_works() { ExtBuilder::default().build_and_execute(|| { + // 50% of time_per_era() (other half goes to buffer as per mock::default_budget() let default_stakers_payout = validator_payout_for(time_per_era()); - assert!(default_stakers_payout > 0); + assert_eq!(default_stakers_payout, Balance::from(time_per_era()) / 2); assert_eq!(>::get(), None); @@ -511,53 +528,74 @@ mod inflation { }); } - #[test] - fn max_staked_rewards_works() { - ExtBuilder::default().nominate(true).build_and_execute(|| { - // sets new max staked rewards through set_staking_configs. - assert_ok!(Staking::set_staking_configs( - RuntimeOrigin::root(), - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Noop, - ConfigOp::Set(Percent::from_percent(10)), - ConfigOp::Noop, - )); - - assert_eq!(>::get(), Some(Percent::from_percent(10))); - - // check validators account state. - assert_eq!(Session::validators().len(), 2); - assert!(Session::validators().contains(&11) & Session::validators().contains(&21)); - - // balance of the mock treasury account is 0 - assert_eq!(RewardRemainderUnbalanced::get(), 0); +} - Session::roll_until_active_era(2); - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, - Event::PagedElectionProceeded { page: 0, result: Ok(2) }, - Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 1500, remainder: 13500 }, - Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } - ] - ); +#[test] +fn era_pot_cleanup_after_history_depth() { + ExtBuilder::default().build_and_execute(|| { + // GIVEN: Start at era 2 + Session::roll_until_active_era(2); + let _ = staking_events_since_last_call(); - let treasury_payout = RewardRemainderUnbalanced::get(); - let validators_payout = ErasValidatorReward::::get(1).unwrap(); - let total_payout = treasury_payout + validators_payout; + // Verify era-1 staker pot was funded with expected amount. + let staker_pot_1 = ::EraPots::era_pot_account(1, EraPotType::StakerRewards); + let expected_per_era = validator_payout_for(time_per_era()); + assert_eq!(Balances::balance(&staker_pot_1), expected_per_era); - // total payout is the same - assert_eq!(total_payout, total_payout_for(time_per_era())); - // validators get only 10% - assert_eq!(validators_payout, Percent::from_percent(10) * total_payout); - // treasury gets 90% - assert_eq!(treasury_payout, Percent::from_percent(90) * total_payout); - }) - } + // era we expect to be cleaned up + let cleanup_era = 1; + + // WHEN: Advance past HistoryDepth + // At era (1 + HistoryDepth + 1), era 1 should be cleaned up + // For HistoryDepth = 80: cleanup happens at era 82 + let target_era = cleanup_era + HistoryDepth::get() + 1; + Session::roll_until_active_era(target_era); + let _ = staking_events_since_last_call(); + // Verify rewards were allocated for the eras we advanced through. + + // THEN: Verify era-1 staker pot has been cleaned up + let staker_pot = + ::EraPots::era_pot_account(cleanup_era, EraPotType::StakerRewards); + + assert_eq!(Balances::balance(&staker_pot), 0, "Staker pot should have zero balance"); + assert_eq!(System::providers(&staker_pot), 0, "Staker pot should have no providers"); + }); +} + +#[test] +fn disable_legacy_minting_era_updates_correctly() { + ExtBuilder::default().build_and_execute(|| { + // GIVEN: DisableLegacyMintingEra is set to 0 in test genesis + assert_eq!(DisableLegacyMintingEra::::get(), Some(0)); + + // WHEN: Era 1 ends with non-zero reward allocation + Session::roll_until_active_era(2); + let _ = staking_events_since_last_call(); + + // THEN: DisableLegacyMintingEra remains at 0 + assert_eq!(DisableLegacyMintingEra::::get(), Some(0)); + }); +} + +#[test] +fn disable_legacy_minting_era_write_once_semantics() { + ExtBuilder::default().build_and_execute(|| { + // GIVEN: Clear DisableLegacyMintingEra to simulate pre-migration state + DisableLegacyMintingEra::::kill(); + assert_eq!(DisableLegacyMintingEra::::get(), None); + + // WHEN: First era ends with rewards + Session::roll_until_active_era(2); + let _ = staking_events_since_last_call(); + + // THEN: DisableLegacyMintingEra is set to era 1 + assert_eq!(DisableLegacyMintingEra::::get(), Some(1)); + + // WHEN: More eras end + Session::roll_until_active_era(5); + let _ = staking_events_since_last_call(); + + // THEN: DisableLegacyMintingEra stays at 1 (not updated to higher values) + assert_eq!(DisableLegacyMintingEra::::get(), Some(1)); + }); } diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 99f7813c703d0..081f03fb51380 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -39,7 +39,6 @@ fn rewards_with_nominator_should_work() { // Compute total payout now for whole duration of the session. let validator_payout_0 = validator_payout_for(time_per_era()); - let maximum_payout = total_payout_for(time_per_era()); assert_eq_uvec!(Session::validators(), vec![11, 21]); @@ -63,17 +62,15 @@ fn rewards_with_nominator_should_work() { Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { - era_index: 1, - validator_payout: validator_payout_0, - remainder: maximum_payout - validator_payout_0 - }, + Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); - assert_eq!(mock::RewardRemainderUnbalanced::get(), maximum_payout - validator_payout_0); + // With DAP, treasury rewards are minted directly into buffer (not sent to RewardRemainder) + assert_eq!(mock::RewardRemainderUnbalanced::get(), 0); - // make note of total issuance before rewards. + // make note of total issuance before payouts (rewards already minted at era finalization + // above) let pre_issuance = asset::total_issuance::(); mock::make_all_reward_payment(1); @@ -81,17 +78,18 @@ fn rewards_with_nominator_should_work() { mock::staking_events_since_last_call(), vec![ Event::PayoutStarted { era_index: 1, validator_stash: 11, page: 0, next: None }, - Event::Rewarded { stash: 11, dest: RewardDestination::Account(11), amount: 4000 }, + Event::Rewarded { stash: 11, dest: RewardDestination::Account(11), amount: 3999 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 1000 }, Event::PayoutStarted { era_index: 1, validator_stash: 21, page: 0, next: None }, - Event::Rewarded { stash: 21, dest: RewardDestination::Account(21), amount: 2000 }, + Event::Rewarded { stash: 21, dest: RewardDestination::Account(21), amount: 1999 }, Event::Rewarded { stash: 101, dest: RewardDestination::Account(101), amount: 500 } ] ); - // total issuance should have increased + // With DAP, rewards are minted at era finalization (not during payout) + // So total issuance should not change during payout (just transfers from pot) let post_issuance = asset::total_issuance::(); - assert_eq!(post_issuance, pre_issuance + validator_payout_0); + assert_eq!(post_issuance, pre_issuance); assert_eq_error_rate!( asset::total_balance::(&11), @@ -119,23 +117,25 @@ fn rewards_with_nominator_should_work() { Session::roll_until_active_era(3); - assert_eq!( - mock::RewardRemainderUnbalanced::get(), - maximum_payout * 2 - validator_payout_0 - total_payout_1, - ); + // With DAP, treasury rewards go to buffer (not RewardRemainder) + assert_eq!(mock::RewardRemainderUnbalanced::get(), 0); assert_eq!( mock::staking_events_since_last_call(), vec![ Event::SessionRotated { starting_session: 7, active_era: 2, planned_era: 3 }, Event::PagedElectionProceeded { page: 0, result: Ok(2) }, Event::SessionRotated { starting_session: 8, active_era: 2, planned_era: 3 }, - Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 7500 }, + Event::EraPaid { era_index: 2, validator_payout: 7500, remainder: 0 }, Event::SessionRotated { starting_session: 9, active_era: 3, planned_era: 3 } ] ); + // Capture issuance before payout (rewards already minted during era finalization above) + let pre_payout_2 = asset::total_issuance::(); + mock::make_all_reward_payment(2); - assert_eq!(asset::total_issuance::(), post_issuance + total_payout_1); + // With DAP, payouts just transfer from pot (no minting), so issuance unchanged + assert_eq!(asset::total_issuance::(), pre_payout_2); assert_eq_error_rate!( asset::total_balance::(&11), @@ -880,7 +880,6 @@ fn test_multi_page_payout_stakers_by_page() { assert_eq!(Eras::::exposure_page_count(2, &11), 2); // compute and ensure the reward amount is greater than zero. - let payout = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); // verify the exposures are calculated correctly. @@ -919,8 +918,6 @@ fn test_multi_page_payout_stakers_by_page() { let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left - assert!(pallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); - assert!(pallet_balances::TotalIssuance::::get() < pre_payout_total_issuance + payout); // verify the validator has been rewarded assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout); @@ -943,13 +940,9 @@ fn test_multi_page_payout_stakers_by_page() { // verify the validator was not rewarded the second time assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); - // verify all rewards have been paid out - assert_eq_error_rate!( - pallet_balances::TotalIssuance::::get(), - pre_payout_total_issuance + payout, - 2 - ); - assert!(RewardOnUnbalanceWasCalled::get()); + // With DAP, rewards minted at era finalization, so no change during payout + // Issuance was already increased during Session::roll_until_active_era above + assert_eq!(pallet_balances::TotalIssuance::::get(), pre_payout_total_issuance); // Top 64 nominators of validator 11 automatically paid out, including the validator assert!(asset::stakeable_balance::(&11) > balance); @@ -966,18 +959,22 @@ fn test_multi_page_payout_stakers_by_page() { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let payout = validator_payout_for(time_per_era()); - let pre_payout_total_issuance = pallet_balances::TotalIssuance::::get(); + let total_payout = total_payout_for(time_per_era()); + let pre_roll_issuance = pallet_balances::TotalIssuance::::get(); Session::roll_until_active_era(i); - RewardOnUnbalanceWasCalled::set(false); - mock::make_all_reward_payment(i - 1); + + // Issuance should have increased by total_payout (staker + treasury rewards) assert_eq_error_rate!( pallet_balances::TotalIssuance::::get(), - pre_payout_total_issuance + payout, + pre_roll_issuance + total_payout, 2 ); - assert!(RewardOnUnbalanceWasCalled::get()); + + let post_roll_issuance = pallet_balances::TotalIssuance::::get(); + mock::make_all_reward_payment(i - 1); + // Payout just transfers from pot, so issuance doesnt change + assert_eq!(pallet_balances::TotalIssuance::::get(), post_roll_issuance); // verify we track rewards for each era and page for page in 0..Eras::::exposure_page_count(i - 1, &11) { @@ -1079,7 +1076,6 @@ fn test_multi_page_payout_stakers_backward_compatible() { assert_eq!(Eras::::exposure_page_count(2, &11), 2); // compute and ensure the reward amount is greater than zero. - let payout = validator_payout_for(time_per_era()); Session::roll_until_active_era(3); // verify the exposures are calculated correctly. @@ -1109,8 +1105,6 @@ fn test_multi_page_payout_stakers_backward_compatible() { let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); // verify rewards have been paid out but still some left - assert!(pallet_balances::TotalIssuance::::get() > pre_payout_total_issuance); - assert!(pallet_balances::TotalIssuance::::get() < pre_payout_total_issuance + payout); // verify the validator has been rewarded assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout); @@ -1130,10 +1124,9 @@ fn test_multi_page_payout_stakers_backward_compatible() { // verify all rewards have been paid out assert_eq_error_rate!( pallet_balances::TotalIssuance::::get(), - pre_payout_total_issuance + payout, + pre_payout_total_issuance, 2 ); - assert!(RewardOnUnbalanceWasCalled::get()); // verify all nominators of validator 11 are paid out, including the validator // Validator payout goes to controller. @@ -1151,18 +1144,22 @@ fn test_multi_page_payout_stakers_backward_compatible() { Staking::reward_by_ids(vec![(11, 1)]); // compute and ensure the reward amount is greater than zero. - let payout = validator_payout_for(time_per_era()); - let pre_payout_total_issuance = pallet_balances::TotalIssuance::::get(); + let total_payout = total_payout_for(time_per_era()); + let pre_roll_issuance = pallet_balances::TotalIssuance::::get(); Session::roll_until_active_era(i); - RewardOnUnbalanceWasCalled::set(false); - mock::make_all_reward_payment(i - 1); + + // Issuance should have increased by total_payout (staker + treasury rewards) assert_eq_error_rate!( pallet_balances::TotalIssuance::::get(), - pre_payout_total_issuance + payout, + pre_roll_issuance + total_payout, 2 ); - assert!(RewardOnUnbalanceWasCalled::get()); + + let post_roll_issuance = pallet_balances::TotalIssuance::::get(); + mock::make_all_reward_payment(i - 1); + // Payout just transfers from pot, so issuance doesnt change + assert_eq!(pallet_balances::TotalIssuance::::get(), post_roll_issuance); // verify we track rewards for each era and page for page in 0..Eras::::exposure_page_count(i - 1, &11) { @@ -1488,7 +1485,8 @@ fn test_commission_paid_across_pages() { assert!(before_balance < after_balance); } - assert_eq_error_rate!(asset::stakeable_balance::(&11), initial_balance + payout / 2, 1,); + // Allow error rate of 2 due to floor rounding in payout calculations + assert_eq_error_rate!(asset::stakeable_balance::(&11), initial_balance + payout / 2, 2,); }); } @@ -1649,11 +1647,12 @@ fn test_runtime_api_pending_rewards() { individual: bounded_btree_map![validator_one => 1, validator_two => 1, validator_three => 1], }; ErasRewardPoints::::insert(0, reward); - - // build exposure + // Build exposure let mut individual_exposures: Vec> = vec![]; for i in 0..=MaxExposurePageSize::get() { - individual_exposures.push(IndividualExposure { who: i.into(), value: stake }); + let nominator: AccountId = (10_000 + i).into(); + bond(nominator, stake); + individual_exposures.push(IndividualExposure { who: nominator, value: stake }); } let exposure = Exposure:: { total: stake * (MaxExposurePageSize::get() as Balance + 2), From 1f76881e7c51f640110f9671bbae29cd9c2450e8 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:04:11 +0200 Subject: [PATCH 13/87] reward remainder can be removed --- substrate/frame/staking-async/src/mock.rs | 16 +--------------- .../staking-async/src/tests/era_rotation.rs | 1 - .../staking-async/src/tests/payout_stakers.rs | 5 ----- 3 files changed, 1 insertion(+), 21 deletions(-) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index e2f6db7514948..849a89285a1e6 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -114,19 +114,6 @@ impl pallet_balances::Config for Test { type AccountStore = System; } -parameter_types! { - pub static RewardRemainderUnbalanced: u128 = 0; -} -pub struct RewardRemainderMock; -impl OnUnbalanced> for RewardRemainderMock { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - RewardRemainderUnbalanced::mutate(|v| { - *v += amount.peek(); - }); - drop(amount); - } -} - pub(crate) const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; @@ -482,7 +469,7 @@ impl Config for Test { type ElectionProvider = TestElectionProvider; type NominationsQuota = WeightedNominationsQuota<16>; type HistoryDepth = HistoryDepth; - type RewardRemainder = RewardRemainderMock; + type RewardRemainder = (); type Slash = Dap; type UnclaimedRewardHandler = Dap; type Reward = MockReward; @@ -776,7 +763,6 @@ impl ExtBuilder { >::mint_into(&dap_buffer, ed) .expect("mint dap buffer"); session_mock::Session::roll_until_active_era(1); - RewardRemainderUnbalanced::set(0); if self.flush_events { let _ = staking_events_since_last_call(); } diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index e14fbd007171b..17360033234d1 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -527,7 +527,6 @@ mod inflation { assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); }); } - } #[test] diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 081f03fb51380..1f8bbf1287398 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -66,9 +66,6 @@ fn rewards_with_nominator_should_work() { Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } ] ); - // With DAP, treasury rewards are minted directly into buffer (not sent to RewardRemainder) - assert_eq!(mock::RewardRemainderUnbalanced::get(), 0); - // make note of total issuance before payouts (rewards already minted at era finalization // above) let pre_issuance = asset::total_issuance::(); @@ -117,8 +114,6 @@ fn rewards_with_nominator_should_work() { Session::roll_until_active_era(3); - // With DAP, treasury rewards go to buffer (not RewardRemainder) - assert_eq!(mock::RewardRemainderUnbalanced::get(), 0); assert_eq!( mock::staking_events_since_last_call(), vec![ From ec5bdadff65945936c3a7ad336a21abbc0d97da1 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:09:59 +0200 Subject: [PATCH 14/87] really remove RewardRemainder --- .../runtimes/assets/asset-hub-westend/src/staking.rs | 1 - .../frame/staking-async/integration-tests/src/ah/mock.rs | 1 - .../frame/staking-async/runtimes/parachain/src/staking.rs | 1 - substrate/frame/staking-async/src/mock.rs | 1 - substrate/frame/staking-async/src/pallet/mod.rs | 6 ------ 5 files changed, 10 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 7525e009b9b77..d3656d9398f2a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -279,7 +279,6 @@ impl pallet_staking_async::Config for Runtime { type CurrencyBalance = Balance; type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; - type RewardRemainder = Dap; type Slash = Dap; type Reward = (); type SessionsPerEra = SessionsPerEra; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index bfed504789fbd..549de581a1b67 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -463,7 +463,6 @@ impl pallet_staking_async::Config for Runtime { type StakerRewardCalculator = (); type EventListeners = (); type Reward = (); - type RewardRemainder = (); type Slash = Dap; type SlashDeferDuration = SlashDeferredDuration; type MaxPruningItems = MaxPruningItems; diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index 1296fc73781e8..bee435ccdd34b 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -438,7 +438,6 @@ impl pallet_staking_async::Config for Runtime { type CurrencyBalance = Balance; type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; - type RewardRemainder = (); type Slash = Dap; type Reward = (); type SessionsPerEra = SessionsPerEra; diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 849a89285a1e6..ab9dc745305f5 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -469,7 +469,6 @@ impl Config for Test { type ElectionProvider = TestElectionProvider; type NominationsQuota = WeightedNominationsQuota<16>; type HistoryDepth = HistoryDepth; - type RewardRemainder = (); type Slash = Dap; type UnclaimedRewardHandler = Dap; type Reward = MockReward; diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 018a769152a61..b3d5a0acfbfc9 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -186,11 +186,6 @@ pub mod pallet { #[pallet::constant] type HistoryDepth: Get; - /// Tokens have been minted and are unused for validator-reward. - /// See [Era payout](./index.html#era-payout). - #[pallet::no_default_bounds] - type RewardRemainder: OnUnbalanced>; - /// Handler for the unbalanced reduction when slashing a staker. #[pallet::no_default_bounds] type Slash: OnUnbalanced>; @@ -417,7 +412,6 @@ pub mod pallet { type CurrencyToVote = (); type NominationsQuota = crate::FixedNominationsQuota<16>; type HistoryDepth = ConstU32<84>; - type RewardRemainder = (); type Slash = (); type Reward = (); type UnclaimedRewardHandler = (); From ebe54cde1f49ce0ae38f96ab74f501ac61ee6794 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:14:10 +0200 Subject: [PATCH 15/87] max commission tests --- .../frame/staking-async/src/pallet/mod.rs | 1 + .../frame/staking-async/src/tests/configs.rs | 107 ++++++++++++++++++ 2 files changed, 108 insertions(+) diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index b3d5a0acfbfc9..edd5583672e48 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -2550,6 +2550,7 @@ pub mod pallet { #[pallet::weight(T::WeightInfo::set_min_commission())] pub fn set_min_commission(origin: OriginFor, new: Perbill) -> DispatchResult { T::AdminOrigin::ensure_origin(origin)?; + ensure!(new <= MaxCommission::::get(), Error::::CommissionTooHigh); MinCommission::::put(new); Ok(()) } diff --git a/substrate/frame/staking-async/src/tests/configs.rs b/substrate/frame/staking-async/src/tests/configs.rs index 80a4a3cff8e4f..862b955635203 100644 --- a/substrate/frame/staking-async/src/tests/configs.rs +++ b/substrate/frame/staking-async/src/tests/configs.rs @@ -77,3 +77,110 @@ fn set_staking_configs_works() { assert_eq!(AreNominatorsSlashable::::get(), true); }); } + +#[test] +fn set_max_commission_works() { + ExtBuilder::default().build_and_execute(|| { + let admin = 1; // AdminOrigin (see mock) + let non_admin = 2; + + // GIVEN: Default is 100% + assert_eq!(MaxCommission::::get(), Perbill::one()); + + // WHEN/THEN: Root and admin can set, non-admin cannot + assert_ok!(Staking::set_max_commission(RuntimeOrigin::root(), Perbill::from_percent(50))); + assert_eq!(MaxCommission::::get(), Perbill::from_percent(50)); + + assert_ok!(Staking::set_max_commission( + RuntimeOrigin::signed(admin), + Perbill::from_percent(25), + )); + assert_eq!(MaxCommission::::get(), Perbill::from_percent(25)); + + assert_noop!( + Staking::set_max_commission( + RuntimeOrigin::signed(non_admin), + Perbill::from_percent(10) + ), + BadOrigin + ); + }); +} + +#[test] +fn max_commission_rejects_validate_above_max() { + ExtBuilder::default().build_and_execute(|| { + let alice = 11; // validator + + // GIVEN: MaxCommission set to 10% + MaxCommission::::set(Perbill::from_percent(10)); + + // WHEN/THEN: Above max rejected, at or below accepted + assert_noop!( + Staking::validate( + RuntimeOrigin::signed(alice), + ValidatorPrefs { commission: Perbill::from_percent(11), blocked: false } + ), + Error::::CommissionTooHigh + ); + + assert_ok!(Staking::validate( + RuntimeOrigin::signed(alice), + ValidatorPrefs { commission: Perbill::from_percent(10), blocked: false } + )); + + assert_ok!(Staking::validate( + RuntimeOrigin::signed(alice), + ValidatorPrefs { commission: Perbill::from_percent(5), blocked: false } + )); + }); +} + +#[test] +fn max_commission_min_commission_invariant() { + ExtBuilder::default().build_and_execute(|| { + // GIVEN: MinCommission = 10% + assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(10))); + + // WHEN/THEN: Cannot set max below min + assert_noop!( + Staking::set_max_commission(RuntimeOrigin::root(), Perbill::from_percent(5)), + Error::::CommissionTooLow + ); + + // GIVEN: MaxCommission = 50% + assert_ok!(Staking::set_max_commission(RuntimeOrigin::root(), Perbill::from_percent(50))); + + // WHEN/THEN: Cannot set min above max + assert_noop!( + Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(51)), + Error::::CommissionTooHigh + ); + + // Equal values are fine + assert_ok!(Staking::set_min_commission(RuntimeOrigin::root(), Perbill::from_percent(50))); + }); +} + +#[test] +fn force_apply_min_commission_also_caps_to_max() { + let prefs = |c| ValidatorPrefs { commission: Perbill::from_percent(c), blocked: false }; + ExtBuilder::default().build_and_execute(|| { + let alice = 31; // validator + let bob = 21; // validator + + assert_ok!(Staking::validate(RuntimeOrigin::signed(alice), prefs(50))); + assert_ok!(Staking::validate(RuntimeOrigin::signed(bob), prefs(20))); + + // GIVEN: Max commission set to 30% + MaxCommission::::set(Perbill::from_percent(30)); + + // WHEN/THEN: Alice (50%) is capped to 30% + assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), alice)); + assert_eq!(Validators::::get(alice), prefs(30)); + + // Bob (20%) is already within range — no change + assert_ok!(Staking::force_apply_min_commission(RuntimeOrigin::signed(1), bob)); + assert_eq!(Validators::::get(bob), prefs(20)); + }); +} From c6e7a453af12edb92d42c4de87428dffcbdd4dd6 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:43:12 +0200 Subject: [PATCH 16/87] integrate runtime --- .../assets/asset-hub-westend/src/lib.rs | 31 +++++++++++ .../assets/asset-hub-westend/src/staking.rs | 50 +++++++++--------- .../src/weights/pallet_staking_async.rs | 4 ++ substrate/frame/dap/src/lib.rs | 2 +- .../runtimes/parachain/src/staking.rs | 51 ++++++++++--------- 5 files changed, 89 insertions(+), 49 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 7a0fad900af84..a670c0244c94c 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1581,6 +1581,35 @@ parameter_types! { ); } +/// Provides the initial `LastIssuanceTimestamp` for DAP migration. +pub struct DapLastIssuanceTimestamp; +impl frame_support::traits::Get for DapLastIssuanceTimestamp { + fn get() -> u64 { + pallet_staking_async::ActiveEra::::get() + .and_then(|era| era.start) + .unwrap_or(0) + } +} + +/// Default budget: 85% staker rewards, 15% buffer. +pub struct DefaultDapBudget; +impl frame_support::traits::Get for DefaultDapBudget { + fn get() -> pallet_dap::BudgetAllocationMap { + use sp_runtime::Perbill; + use sp_staking::budget::BudgetRecipientList; + + let recipients = ::BudgetRecipients::recipients(); + // [dap (buffer), StakerRewardRecipient] + let percentages = [Perbill::from_percent(15), Perbill::from_percent(85)]; + + let mut map = pallet_dap::BudgetAllocationMap::new(); + for ((key, _), perbill) in recipients.into_iter().zip(percentages) { + let _ = map.try_insert(key, perbill); + } + map + } +} + /// Migrations to apply on runtime upgrade. pub type Migrations = ( // v9420 @@ -1620,6 +1649,8 @@ pub type Migrations = ( // permanent pallet_xcm::migration::MigrateToLatestXcmVersion, cumulus_pallet_aura_ext::migration::MigrateV0ToV1, + // unreleased + pallet_dap::migrations::MigrateV1ToV2, ); /// Asset Hub Westend has some undecodable storage, delete it. diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index d3656d9398f2a..54979781697a7 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -228,29 +228,28 @@ impl pallet_bags_list::Config for Runtime { type WeightInfo = weights::pallet_bags_list::WeightInfo; } -pub struct EraPayout; -impl pallet_staking_async::EraPayout for EraPayout { - fn era_payout( - _total_staked: Balance, - _total_issuance: Balance, - era_duration_millis: u64, - ) -> (Balance, Balance) { +parameter_types! { + pub const StakingPotsPalletId: PalletId = PalletId(*b"py/stkng"); +} + +/// Polkadot inflation curve for DAP. +/// +/// Same computation as the previous `EraPayout` but returns total emission. +/// The budget split (ex. 85/15 staker/treasury) is now handled by pallet-dap. +pub struct PolkadotIssuanceCurve; +impl sp_staking::budget::IssuanceCurve for PolkadotIssuanceCurve { + fn issue(_total_issuance: Balance, elapsed_millis: u64) -> Balance { const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100; - // A normal-sized era will have 1 / 365.25 here: - let relative_era_len = - FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into()); + let relative_period = + FixedU128::from_rational(elapsed_millis.into(), MILLISECONDS_PER_YEAR.into()); - // Fixed total TI that we use as baseline for the issuance. + // Fixed total TI baseline for issuance (hard-pressure curve). let fixed_total_issuance: i128 = 5_216_342_402_773_185_773; let fixed_inflation_rate = FixedU128::from_rational(8, 100); let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance); - let era_emission = relative_era_len.saturating_mul_int(yearly_emission); - // 15% to treasury, as per Polkadot ref 1139. - let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission); - let to_stakers = era_emission.saturating_sub(to_treasury); - - (to_stakers.saturated_into(), to_treasury.saturated_into()) + let emission = relative_period.saturating_mul_int(yearly_emission); + emission.saturated_into() } } @@ -268,7 +267,6 @@ parameter_types! { pub const MaxControllersInDeprecationBatch: u32 = 751; // alias for 16, which is the max nominations per nominator in the runtime. pub const MaxNominations: u32 = ::LIMIT as u32; - pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64; pub MaxPruningItems: u32 = 100; } @@ -280,13 +278,16 @@ impl pallet_staking_async::Config for Runtime { type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; type Slash = Dap; + type UnclaimedRewardHandler = Dap; type Reward = (); type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOf, StakingAdmin>; - type EraPayout = EraPayout; + type GeneralPots = pallet_staking_async::Seed; + type EraPots = pallet_staking_async::Seed; + type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; @@ -299,7 +300,6 @@ impl pallet_staking_async::Config for Runtime { type EventListeners = (NominationPools, DelegatedStaking); type PlanningEraOffset = ConstU32<6>; type RcClientInterface = StakingRcClient; - type MaxEraDuration = MaxEraDuration; type MaxPruningItems = MaxPruningItems; type WeightInfo = weights::pallet_staking_async::WeightInfo; } @@ -352,9 +352,13 @@ parameter_types! { impl pallet_dap::Config for Runtime { type Currency = Balances; type PalletId = DapPalletId; - /// Noop — DAP does not mint until budget drip is enabled. - type IssuanceCurve = (); - type BudgetRecipients = (pallet_dap::Pallet,); + type IssuanceCurve = PolkadotIssuanceCurve; + type BudgetRecipients = ( + pallet_dap::Pallet, + pallet_staking_async::StakerRewardRecipient< + pallet_staking_async::Seed, + >, + ); type Time = pallet_timestamp::Pallet; type IssuanceCadence = IssuanceCadence; type MaxElapsedPerDrip = MaxElapsedPerDrip; diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs index 07c01c75aaf74..9c851db7fc1ae 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs @@ -721,6 +721,10 @@ impl pallet_staking_async::WeightInfo for WeightInfo .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } + fn set_max_commission() -> Weight { + // TODO(ank4n): re-bench + Self::set_min_commission() + } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) diff --git a/substrate/frame/dap/src/lib.rs b/substrate/frame/dap/src/lib.rs index f8bf6a73f458f..1e3698d71cf52 100644 --- a/substrate/frame/dap/src/lib.rs +++ b/substrate/frame/dap/src/lib.rs @@ -82,7 +82,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; /// The in-code storage version. - const STORAGE_VERSION: StorageVersion = StorageVersion::new(1); + const STORAGE_VERSION: StorageVersion = StorageVersion::new(2); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index bee435ccdd34b..476da199711e4 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -382,29 +382,27 @@ impl pallet_bags_list::Config for Runtime { type MaxAutoRebagPerBlock = (); } -pub struct EraPayout; -impl pallet_staking_async::EraPayout for EraPayout { - fn era_payout( - _total_staked: Balance, - _total_issuance: Balance, - era_duration_millis: u64, - ) -> (Balance, Balance) { +parameter_types! { + pub const StakingPotsPalletId: frame_support::PalletId = frame_support::PalletId(*b"py/stkng"); +} + +/// Polkadot inflation curve for DAP. +/// +/// Same computation as the previous `EraPayout` but returns total emission +/// (the staker/treasury split is now handled by DAP budget allocation). +pub struct PolkadotIssuanceCurve; +impl sp_staking::budget::IssuanceCurve for PolkadotIssuanceCurve { + fn issue(_total_issuance: Balance, elapsed_millis: u64) -> Balance { const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100; - // A normal-sized era will have 1 / 365.25 here: - let relative_era_len = - FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into()); + let relative_period = + FixedU128::from_rational(elapsed_millis.into(), MILLISECONDS_PER_YEAR.into()); - // Fixed total TI that we use as baseline for the issuance. let fixed_total_issuance: i128 = 5_216_342_402_773_185_773; let fixed_inflation_rate = FixedU128::from_rational(8, 100); let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance); - let era_emission = relative_era_len.saturating_mul_int(yearly_emission); - // 15% to treasury, as per Polkadot ref 1139. - let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission); - let to_stakers = era_emission.saturating_sub(to_treasury); - - (to_stakers.saturated_into(), to_treasury.saturated_into()) + let emission = relative_period.saturating_mul_int(yearly_emission); + emission.saturated_into() } } @@ -424,10 +422,6 @@ parameter_types! { // of nominators. pub const MaxControllersInDeprecationBatch: u32 = 751; pub const MaxNominations: u32 = ::LIMIT as u32; - // Note: In WAH, this should be set closer to the ideal era duration to trigger capping more - // frequently. On Kusama and Polkadot, a higher value like 7 × ideal_era_duration is more - // appropriate. - pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64; pub MaxPruningItems: u32 = 100; } @@ -445,7 +439,10 @@ impl pallet_staking_async::Config for Runtime { type SlashDeferDuration = SlashDeferDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type AdminOrigin = EitherOf, StakingAdmin>; - type EraPayout = EraPayout; + type UnclaimedRewardHandler = Dap; + type GeneralPots = pallet_staking_async::Seed; + type EraPots = pallet_staking_async::Seed; + type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; @@ -457,7 +454,6 @@ impl pallet_staking_async::Config for Runtime { type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = pallet_staking_async::weights::SubstrateWeight; - type MaxEraDuration = MaxEraDuration; type MaxPruningItems = MaxPruningItems; type PlanningEraOffset = pallet_staking_async::PlanningEraOffsetOf>; @@ -499,8 +495,13 @@ parameter_types! { impl pallet_dap::Config for Runtime { type Currency = Balances; type PalletId = DapPalletId; - type IssuanceCurve = (); - type BudgetRecipients = (pallet_dap::Pallet,); + type IssuanceCurve = PolkadotIssuanceCurve; + type BudgetRecipients = ( + pallet_dap::Pallet, + pallet_staking_async::StakerRewardRecipient< + pallet_staking_async::Seed, + >, + ); type Time = pallet_timestamp::Pallet; type IssuanceCadence = DapIssuanceCadence; type MaxElapsedPerDrip = DapMaxElapsedPerDrip; From 0ce116758b093a24c3afc18cf1c3fe80c263761f Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:43:39 +0200 Subject: [PATCH 17/87] fmt --- .../runtimes/assets/asset-hub-westend/src/staking.rs | 3 ++- .../frame/staking-async/runtimes/parachain/src/staking.rs | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 54979781697a7..5fd7338c8c8cf 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -287,7 +287,8 @@ impl pallet_staking_async::Config for Runtime { type AdminOrigin = EitherOf, StakingAdmin>; type GeneralPots = pallet_staking_async::Seed; type EraPots = pallet_staking_async::Seed; - type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; + type StakerRewardCalculator = + pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index 476da199711e4..7fa4ed296c018 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -442,7 +442,8 @@ impl pallet_staking_async::Config for Runtime { type UnclaimedRewardHandler = Dap; type GeneralPots = pallet_staking_async::Seed; type EraPots = pallet_staking_async::Seed; - type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; + type StakerRewardCalculator = + pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; From 2bf54f149f99cf71956336bc4ea6f6719a9c4639 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:48:02 +0200 Subject: [PATCH 18/87] prdoc --- prdoc/pr_11616.prdoc | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 prdoc/pr_11616.prdoc diff --git a/prdoc/pr_11616.prdoc b/prdoc/pr_11616.prdoc new file mode 100644 index 0000000000000..1249bdfc47d14 --- /dev/null +++ b/prdoc/pr_11616.prdoc @@ -0,0 +1,27 @@ +title: Move era reward minting from staking to DAP +doc: +- audience: Runtime Dev + description: |- + Staking no longer mints tokens during `payout_stakers`. Instead, DAP drips inflation + into a general staker reward pot, and staking snapshots that into era-specific pots at + each era boundary. Payouts transfer from era pots. A legacy minting fallback is preserved + for pre-migration eras. + + Runtimes now provide a `PolkadotIssuanceCurve` to DAP (replacing the old `EraPayout`) and + register `StakerRewardRecipient` as a budget recipient. + + DAP storage version bumped to V2 with migration for `LastIssuanceTimestamp` and + `BudgetAllocation`. +crates: +- name: pallet-staking-async + bump: major +- name: pallet-dap + bump: minor +- name: sp-staking + bump: minor +- name: asset-hub-westend-runtime + bump: major +- name: pallet-staking-async-parachain-runtime + bump: major +- name: pallet-ahm-test + bump: patch From 1bca7fc0f8cb3e152840dd5f9bd1e40eb7b18786 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:54:05 +0200 Subject: [PATCH 19/87] zepter --- substrate/frame/staking-async/Cargo.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate/frame/staking-async/Cargo.toml b/substrate/frame/staking-async/Cargo.toml index 5725bcce8530b..4ccb602e38ea7 100644 --- a/substrate/frame/staking-async/Cargo.toml +++ b/substrate/frame/staking-async/Cargo.toml @@ -89,6 +89,7 @@ runtime-benchmarks = [ "pallet-staking-async-rc-client/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks" ] try-runtime = [ "frame-election-provider-support/try-runtime", @@ -99,4 +100,5 @@ try-runtime = [ "pallet-dap/try-runtime", "pallet-staking-async-rc-client/try-runtime", "sp-runtime/try-runtime", + "pallet-timestamp/try-runtime" ] From 83a890e8a4635f626c3846efd579fc310d202ee0 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 22:59:29 +0200 Subject: [PATCH 20/87] taplo --- substrate/frame/staking-async/Cargo.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/substrate/frame/staking-async/Cargo.toml b/substrate/frame/staking-async/Cargo.toml index 4ccb602e38ea7..87a673d9c4d5f 100644 --- a/substrate/frame/staking-async/Cargo.toml +++ b/substrate/frame/staking-async/Cargo.toml @@ -26,10 +26,10 @@ rand_chacha = { workspace = true } scale-info = { features = ["derive", "serde"], workspace = true } serde = { features = ["alloc", "derive"], workspace = true } sp-application-crypto = { features = ["serde"], workspace = true } +sp-arithmetic = { workspace = true } sp-core = { workspace = true } sp-io = { workspace = true } sp-npos-elections = { workspace = true } -sp-arithmetic = { workspace = true } sp-runtime = { features = ["serde"], workspace = true } sp-staking = { features = ["serde"], workspace = true } @@ -64,12 +64,12 @@ std = [ "pallet-dap/std", "pallet-staking-async-rc-client/std", "pallet-timestamp/std", - "sp-arithmetic/std", "rand/std", "rand_chacha/std", "scale-info/std", "serde/std", "sp-application-crypto/std", + "sp-arithmetic/std", "sp-core/std", "sp-core/std", "sp-io/std", @@ -87,9 +87,9 @@ runtime-benchmarks = [ "pallet-balances/runtime-benchmarks", "pallet-dap/runtime-benchmarks", "pallet-staking-async-rc-client/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", - "pallet-timestamp/runtime-benchmarks" ] try-runtime = [ "frame-election-provider-support/try-runtime", @@ -99,6 +99,6 @@ try-runtime = [ "pallet-balances/try-runtime", "pallet-dap/try-runtime", "pallet-staking-async-rc-client/try-runtime", + "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", - "pallet-timestamp/try-runtime" ] From e8399317b999acbbb576281649e2903fe122d788 Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 1 Apr 2026 23:06:52 +0200 Subject: [PATCH 21/87] todo for dap bench --- substrate/frame/dap/src/weights.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/substrate/frame/dap/src/weights.rs b/substrate/frame/dap/src/weights.rs index 4429681241221..d8a456376df42 100644 --- a/substrate/frame/dap/src/weights.rs +++ b/substrate/frame/dap/src/weights.rs @@ -36,9 +36,11 @@ pub trait WeightInfo { /// Default weights (not benchmarked). impl WeightInfo for () { fn set_budget_allocation() -> Weight { + // TODO(ank4n): run bench Weight::zero() } fn drip_issuance() -> Weight { + // TODO(ank4n): run bench Weight::zero() } } From d44646a3e8fece00fc32a129ec97dbaff70b6041 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 00:25:11 +0000 Subject: [PATCH 22/87] Update from github-actions[bot] running command 'bench --pallet pallet_staking_async' --- .../src/weights/pallet_staking_async.rs | 273 +++++++++--------- 1 file changed, 137 insertions(+), 136 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs index 9c851db7fc1ae..b3b5b08e5170a 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs @@ -16,9 +16,9 @@ //! Autogenerated weights for `pallet_staking_async` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2026-03-20, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-04-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `bc292dbdd291`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `21ba8909b2ce`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -68,8 +68,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `6793` // Estimated: `4218` - // Minimum execution time: 109_433_000 picoseconds. - Weight::from_parts(116_816_000, 0) + // Minimum execution time: 108_311_000 picoseconds. + Weight::from_parts(114_482_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -96,8 +96,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8254` // Estimated: `8877` - // Minimum execution time: 300_420_000 picoseconds. - Weight::from_parts(312_182_000, 0) + // Minimum execution time: 304_196_000 picoseconds. + Weight::from_parts(313_138_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) @@ -130,8 +130,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8415` // Estimated: `8877` - // Minimum execution time: 267_215_000 picoseconds. - Weight::from_parts(278_969_000, 0) + // Minimum execution time: 268_765_000 picoseconds. + Weight::from_parts(291_784_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().writes(6)) @@ -145,7 +145,7 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(3231), added: 5706, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -162,8 +162,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7393` // Estimated: `6696` - // Minimum execution time: 128_228_000 picoseconds. - Weight::from_parts(134_860_000, 0) + // Minimum execution time: 126_325_000 picoseconds. + Weight::from_parts(132_812_000, 0) .saturating_add(Weight::from_parts(0, 6696)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(2)) @@ -177,7 +177,7 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(3231), added: 5706, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -208,8 +208,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8340` // Estimated: `6696` - // Minimum execution time: 307_882_000 picoseconds. - Weight::from_parts(325_627_000, 0) + // Minimum execution time: 307_837_000 picoseconds. + Weight::from_parts(324_204_000, 0) .saturating_add(Weight::from_parts(0, 6696)) .saturating_add(T::DbWeight::get().reads(17)) .saturating_add(T::DbWeight::get().writes(12)) @@ -222,6 +222,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:1 w:0) @@ -242,10 +244,10 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `9885` // Estimated: `4218` - // Minimum execution time: 107_406_000 picoseconds. - Weight::from_parts(113_955_000, 0) + // Minimum execution time: 106_705_000 picoseconds. + Weight::from_parts(115_524_000, 0) .saturating_add(Weight::from_parts(0, 4218)) - .saturating_add(T::DbWeight::get().reads(12)) + .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(5)) } /// Storage: `Staking::Ledger` (r:1 w:0) @@ -259,11 +261,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `12187 + k * (1121 ±0)` // Estimated: `4218 + k * (3033 ±0)` - // Minimum execution time: 79_016_000 picoseconds. - Weight::from_parts(88_648_086, 0) + // Minimum execution time: 76_949_000 picoseconds. + Weight::from_parts(77_003_657, 0) .saturating_add(Weight::from_parts(0, 4218)) - // Standard Error: 22_509 - .saturating_add(Weight::from_parts(15_644_635, 0).saturating_mul(k.into())) + // Standard Error: 24_064 + .saturating_add(Weight::from_parts(15_507_941, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -298,11 +300,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5074 + n * (68 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 180_689_000 picoseconds. - Weight::from_parts(197_222_688, 0) + // Minimum execution time: 179_370_000 picoseconds. + Weight::from_parts(200_153_114, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 65_656 - .saturating_add(Weight::from_parts(5_564_113, 0).saturating_mul(n.into())) + // Standard Error: 68_192 + .saturating_add(Weight::from_parts(6_393_807, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -330,8 +332,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5217` // Estimated: `6248` - // Minimum execution time: 170_145_000 picoseconds. - Weight::from_parts(176_873_000, 0) + // Minimum execution time: 172_200_000 picoseconds. + Weight::from_parts(183_200_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(6)) @@ -346,8 +348,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `4040` // Estimated: `4218` - // Minimum execution time: 35_981_000 picoseconds. - Weight::from_parts(38_831_000, 0) + // Minimum execution time: 36_154_000 picoseconds. + Weight::from_parts(38_696_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -362,8 +364,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5712` // Estimated: `4218` - // Minimum execution time: 45_917_000 picoseconds. - Weight::from_parts(48_850_000, 0) + // Minimum execution time: 45_224_000 picoseconds. + Weight::from_parts(48_581_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -376,8 +378,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5383` // Estimated: `7446` - // Minimum execution time: 47_479_000 picoseconds. - Weight::from_parts(52_515_000, 0) + // Minimum execution time: 47_104_000 picoseconds. + Weight::from_parts(50_114_000, 0) .saturating_add(Weight::from_parts(0, 7446)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -388,8 +390,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_665_000 picoseconds. - Weight::from_parts(3_025_000, 0) + // Minimum execution time: 2_580_000 picoseconds. + Weight::from_parts(2_978_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -399,8 +401,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_828_000 picoseconds. - Weight::from_parts(15_540_000, 0) + // Minimum execution time: 12_315_000 picoseconds. + Weight::from_parts(15_877_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -410,8 +412,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_812_000 picoseconds. - Weight::from_parts(15_578_000, 0) + // Minimum execution time: 12_296_000 picoseconds. + Weight::from_parts(15_742_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -421,8 +423,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_058_000 picoseconds. - Weight::from_parts(15_629_000, 0) + // Minimum execution time: 11_975_000 picoseconds. + Weight::from_parts(15_723_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -437,11 +439,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `195432 + u * (1105 ±0)` // Estimated: `990 + u * (6456 ±0)` - // Minimum execution time: 5_564_000 picoseconds. - Weight::from_parts(526_794_998, 0) + // Minimum execution time: 5_643_000 picoseconds. + Weight::from_parts(62_673_802, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 68_074 - .saturating_add(Weight::from_parts(44_562_659, 0).saturating_mul(u.into())) + // Standard Error: 52_849 + .saturating_add(Weight::from_parts(48_322_003, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 6456).saturating_mul(u.into())) @@ -478,8 +480,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8285` // Estimated: `6248` - // Minimum execution time: 268_199_000 picoseconds. - Weight::from_parts(277_067_000, 0) + // Minimum execution time: 277_917_000 picoseconds. + Weight::from_parts(289_429_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(13)) @@ -491,11 +493,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `633` // Estimated: `39479` - // Minimum execution time: 15_560_000 picoseconds. - Weight::from_parts(15_966_000, 0) + // Minimum execution time: 15_308_000 picoseconds. + Weight::from_parts(16_065_000, 0) .saturating_add(Weight::from_parts(0, 39479)) - // Standard Error: 6_047 - .saturating_add(Weight::from_parts(2_809_865, 0).saturating_mul(s.into())) + // Standard Error: 6_090 + .saturating_add(Weight::from_parts(2_949_210, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -511,7 +513,7 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:65 w:65) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:65 w:65) + /// Storage: `System::Account` (r:66 w:65) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:65 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) @@ -523,19 +525,21 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorPrefs` (r:1 w:0) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Staking::DisableLegacyMintingEra` (r:1 w:0) + /// Proof: `Staking::DisableLegacyMintingEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:65 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 64]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `32987 + n * (3657 ±0)` + // Measured: `33593 + n * (3657 ±0)` // Estimated: `39483 + n * (3228 ±0)` - // Minimum execution time: 318_295_000 picoseconds. - Weight::from_parts(553_237_641, 0) + // Minimum execution time: 354_364_000 picoseconds. + Weight::from_parts(612_905_812, 0) .saturating_add(Weight::from_parts(0, 39483)) - // Standard Error: 230_302 - .saturating_add(Weight::from_parts(132_316_114, 0).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(13)) + // Standard Error: 228_202 + .saturating_add(Weight::from_parts(134_482_591, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(4)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) @@ -564,11 +568,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8255 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 235_887_000 picoseconds. - Weight::from_parts(257_581_588, 0) + // Minimum execution time: 244_945_000 picoseconds. + Weight::from_parts(270_161_493, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 43_297 - .saturating_add(Weight::from_parts(301_031, 0).saturating_mul(l.into())) + // Standard Error: 19_476 + .saturating_add(Weight::from_parts(75_895, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -606,8 +610,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8348` // Estimated: `6248` - // Minimum execution time: 296_453_000 picoseconds. - Weight::from_parts(318_628_000, 0) + // Minimum execution time: 300_149_000 picoseconds. + Weight::from_parts(320_107_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(12)) @@ -632,8 +636,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_048_000 picoseconds. - Weight::from_parts(5_698_000, 0) + // Minimum execution time: 5_100_000 picoseconds. + Weight::from_parts(5_716_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -657,8 +661,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_725_000 picoseconds. - Weight::from_parts(5_276_000, 0) + // Minimum execution time: 4_637_000 picoseconds. + Weight::from_parts(5_212_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(8)) } @@ -690,41 +694,42 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5350` // Estimated: `6248` - // Minimum execution time: 195_830_000 picoseconds. - Weight::from_parts(207_254_000, 0) + // Minimum execution time: 207_112_000 picoseconds. + Weight::from_parts(218_947_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(6)) } /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: // Measured: `640` // Estimated: `3510` - // Minimum execution time: 35_122_000 picoseconds. - Weight::from_parts(37_293_000, 0) + // Minimum execution time: 35_314_000 picoseconds. + Weight::from_parts(38_212_000, 0) .saturating_add(Weight::from_parts(0, 3510)) - .saturating_add(T::DbWeight::get().reads(2)) + .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:0 w:1) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 2_592_000 picoseconds. - Weight::from_parts(2_958_000, 0) - .saturating_add(Weight::from_parts(0, 0)) + // Measured: `596` + // Estimated: `1489` + // Minimum execution time: 7_875_000 picoseconds. + Weight::from_parts(8_702_000, 0) + .saturating_add(Weight::from_parts(0, 1489)) + .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - fn set_max_commission() -> Weight { - // TODO(ank4n): re-bench - Self::set_min_commission() - } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) @@ -741,8 +746,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `6948` // Estimated: `4764` - // Minimum execution time: 76_233_000 picoseconds. - Weight::from_parts(80_864_000, 0) + // Minimum execution time: 74_254_000 picoseconds. + Weight::from_parts(78_808_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -763,8 +768,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7019` // Estimated: `4764` - // Minimum execution time: 123_078_000 picoseconds. - Weight::from_parts(128_553_000, 0) + // Minimum execution time: 119_374_000 picoseconds. + Weight::from_parts(124_774_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -794,11 +799,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `27526 + n * (2979 ±0)` // Estimated: `39479 + n * (3228 ±0)` - // Minimum execution time: 246_238_000 picoseconds. - Weight::from_parts(420_898_336, 0) + // Minimum execution time: 253_956_000 picoseconds. + Weight::from_parts(454_619_406, 0) .saturating_add(Weight::from_parts(0, 39479)) - // Standard Error: 192_241 - .saturating_add(Weight::from_parts(92_109_764, 0).saturating_mul(n.into())) + // Standard Error: 225_245 + .saturating_add(Weight::from_parts(93_395_321, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -808,7 +813,7 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Storage: `Staking::ProcessingOffence` (r:1 w:1) /// Proof: `Staking::ProcessingOffence` (`max_values`: Some(1), `max_size`: Some(85), added: 580, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueue` (r:2 w:1) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashRewardFraction` (r:1 w:0) @@ -823,8 +828,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `3602` // Estimated: `6617` - // Minimum execution time: 157_089_000 picoseconds. - Weight::from_parts(163_737_000, 0) + // Minimum execution time: 162_751_000 picoseconds. + Weight::from_parts(170_892_000, 0) .saturating_add(Weight::from_parts(0, 6617)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -840,17 +845,17 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Storage: `Staking::OffenceQueue` (r:500 w:500) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(17), added: 512, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// The range of component `v` is `[2, 500]`. fn rc_on_offence(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `667 + v * (96 ±0)` - // Estimated: `1510 + v * (2576 ±0)` - // Minimum execution time: 90_289_000 picoseconds. - Weight::from_parts(6_594_029, 0) - .saturating_add(Weight::from_parts(0, 1510)) - // Standard Error: 17_284 - .saturating_add(Weight::from_parts(19_629_734, 0).saturating_mul(v.into())) + // Estimated: `1534 + v * (2576 ±0)` + // Minimum execution time: 94_505_000 picoseconds. + Weight::from_parts(18_215_037, 0) + .saturating_add(Weight::from_parts(0, 1534)) + // Standard Error: 18_412 + .saturating_add(Weight::from_parts(19_585_018, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -863,10 +868,6 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:1) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:1 w:0) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::MaxStakedRewards` (r:1 w:0) - /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::BondedEras` (r:1 w:1) @@ -889,12 +890,12 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::ElectableStashes` (`max_values`: Some(1), `max_size`: Some(32002), added: 32497, mode: `MaxEncodedLen`) fn rc_on_session_report() -> Weight { // Proof Size summary in bytes: - // Measured: `2431` + // Measured: `2315` // Estimated: `39483` - // Minimum execution time: 516_585_000 picoseconds. - Weight::from_parts(530_117_000, 0) + // Minimum execution time: 493_884_000 picoseconds. + Weight::from_parts(513_624_000, 0) .saturating_add(Weight::from_parts(0, 39483)) - .saturating_add(T::DbWeight::get().reads(10)) + .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(11)) } /// Storage: `Staking::ActiveEra` (r:1 w:0) @@ -908,11 +909,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7912 + v * (3 ±0)` // Estimated: `258665 + v * (4 ±0)` - // Minimum execution time: 165_109_000 picoseconds. - Weight::from_parts(186_409_460, 0) + // Minimum execution time: 163_132_000 picoseconds. + Weight::from_parts(188_486_108, 0) .saturating_add(Weight::from_parts(0, 258665)) - // Standard Error: 1_200 - .saturating_add(Weight::from_parts(47_505, 0).saturating_mul(v.into())) + // Standard Error: 1_149 + .saturating_add(Weight::from_parts(54_377, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(103)) .saturating_add(T::DbWeight::get().writes(100)) .saturating_add(Weight::from_parts(0, 4).saturating_mul(v.into())) @@ -927,12 +928,12 @@ impl pallet_staking_async::WeightInfo for WeightInfo fn prune_era_stakers_overview(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `7425` - // Estimated: `202015 + v * (83 ±0)` - // Minimum execution time: 39_079_000 picoseconds. - Weight::from_parts(163_360_821, 0) + // Estimated: `202015 + v * (83 ±21)` + // Minimum execution time: 39_146_000 picoseconds. + Weight::from_parts(154_515_581, 0) .saturating_add(Weight::from_parts(0, 202015)) - // Standard Error: 4_565 - .saturating_add(Weight::from_parts(75_583, 0).saturating_mul(v.into())) + // Standard Error: 3_939 + .saturating_add(Weight::from_parts(93_568, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 83).saturating_mul(v.into())) @@ -948,11 +949,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5865` // Estimated: `200811 + v * (83 ±21)` - // Minimum execution time: 39_182_000 picoseconds. - Weight::from_parts(149_045_041, 0) + // Minimum execution time: 40_570_000 picoseconds. + Weight::from_parts(148_375_299, 0) .saturating_add(Weight::from_parts(0, 200811)) - // Standard Error: 3_528 - .saturating_add(Weight::from_parts(92_154, 0).saturating_mul(v.into())) + // Standard Error: 3_742 + .saturating_add(Weight::from_parts(103_890, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 83).saturating_mul(v.into())) @@ -968,11 +969,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8873` // Estimated: `203149 + v * (84 ±0)` - // Minimum execution time: 37_632_000 picoseconds. - Weight::from_parts(148_930_188, 0) + // Minimum execution time: 40_285_000 picoseconds. + Weight::from_parts(151_088_206, 0) .saturating_add(Weight::from_parts(0, 203149)) - // Standard Error: 3_824 - .saturating_add(Weight::from_parts(103_701, 0).saturating_mul(v.into())) + // Standard Error: 3_736 + .saturating_add(Weight::from_parts(106_061, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 84).saturating_mul(v.into())) @@ -987,8 +988,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 30_149_000 picoseconds. - Weight::from_parts(32_250_000, 0) + // Minimum execution time: 29_977_000 picoseconds. + Weight::from_parts(33_405_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -1003,8 +1004,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(31_859_000, 0) + // Minimum execution time: 30_422_000 picoseconds. + Weight::from_parts(33_658_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -1021,8 +1022,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 31_615_000 picoseconds. - Weight::from_parts(34_414_000, 0) + // Minimum execution time: 31_969_000 picoseconds. + Weight::from_parts(35_119_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -1037,12 +1038,12 @@ impl pallet_staking_async::WeightInfo for WeightInfo fn prune_era_validator_slash_in_era(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `5499 + v * (3 ±0)` - // Estimated: `132078 + v * (177 ±23)` - // Minimum execution time: 43_780_000 picoseconds. - Weight::from_parts(117_898_212, 0) + // Estimated: `132078 + v * (177 ±0)` + // Minimum execution time: 43_443_000 picoseconds. + Weight::from_parts(115_249_407, 0) .saturating_add(Weight::from_parts(0, 132078)) - // Standard Error: 4_265 - .saturating_add(Weight::from_parts(133_270, 0).saturating_mul(v.into())) + // Standard Error: 3_964 + .saturating_add(Weight::from_parts(139_417, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(49)) .saturating_add(T::DbWeight::get().writes(48)) .saturating_add(Weight::from_parts(0, 177).saturating_mul(v.into())) From 7028a802c741753f28752681128b1ac709a6ed48 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 06:25:41 +0200 Subject: [PATCH 23/87] add missing bench for max commission --- .../src/weights/pallet_staking_async.rs | 5 +++++ substrate/frame/staking-async/src/benchmarking.rs | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs index b3b5b08e5170a..25085db75101e 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs @@ -730,6 +730,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } + + fn set_max_commission() -> Weight { + todo!() + } + /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index da0e14c554225..90b02da6f0f53 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -939,6 +939,16 @@ mod benchmarks { assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); } + #[benchmark] + fn set_max_commission() { + let max_commission = Perbill::max_value(); + + #[extrinsic_call] + _(RawOrigin::Root, max_commission); + + assert_eq!(MaxCommission::::get(), Perbill::from_percent(100)); + } + #[benchmark] fn restore_ledger() -> Result<(), BenchmarkError> { let (stash, controller) = create_stash_controller::(0, 100, RewardDestination::Staked)?; From 869a3b24df8bb3642bb749b8b37a906199288506 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:00:34 +0200 Subject: [PATCH 24/87] fix test-delegate-stake compile --- .../test-delegate-stake/src/mock.rs | 16 ++-------------- substrate/frame/staking-async/src/mock.rs | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index 51f3f2bbab606..0d2b0b102fcb9 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -91,19 +91,6 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; - pub static EraPayout: (Balance, Balance) = (1000, 100); -} - -/// A simple EraPayout implementation for testing that returns fixed values. -pub struct TestEraPayout; -impl pallet_staking_async::EraPayout for TestEraPayout { - fn era_payout( - _total_staked: Balance, - _total_issuance: Balance, - _era_duration_millis: u64, - ) -> (Balance, Balance) { - EraPayout::get() - } } /// A mock RcClientInterface for tests that don't need actual session/validator set management. @@ -126,7 +113,8 @@ impl pallet_staking_async::Config for Runtime { type Currency = Balances; type AdminOrigin = frame_system::EnsureRoot; type BondingDuration = BondingDuration; - type EraPayout = TestEraPayout; + type GeneralPots = pallet_staking_async::SequentialTest; + type EraPots = pallet_staking_async::SequentialTest; type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>; type VoterList = VoterList; diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index ab9dc745305f5..fa006b820e8bb 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -29,7 +29,7 @@ use frame_election_provider_support::{ }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, Get, Imbalance, OnUnbalanced}, + traits::{EitherOfDiverse, Get, OnUnbalanced}, weights::constants::RocksDbWeight, }; use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot, EnsureSignedBy}; From 3283c721f63c269858ae31cb4829a0aff98b71d1 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:02:46 +0200 Subject: [PATCH 25/87] dev weight for staking async --- substrate/frame/staking-async/src/weights.rs | 814 +++++++++++-------- 1 file changed, 475 insertions(+), 339 deletions(-) diff --git a/substrate/frame/staking-async/src/weights.rs b/substrate/frame/staking-async/src/weights.rs index 02835b1205449..fa1bb6f818995 100644 --- a/substrate/frame/staking-async/src/weights.rs +++ b/substrate/frame/staking-async/src/weights.rs @@ -19,7 +19,7 @@ //! Autogenerated weights for `pallet_staking_async` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2026-01-05, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-04-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` //! HOSTNAME: `Mac`, CPU: `` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` @@ -115,15 +115,15 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: // Measured: `4933` // Estimated: `4218` - // Minimum execution time: 96_000_000 picoseconds. - Weight::from_parts(96_000_000, 4218) + // Minimum execution time: 80_000_000 picoseconds. + Weight::from_parts(116_000_000, 4218) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -138,7 +138,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -147,10 +147,10 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `5955` + // Measured: `5956` // Estimated: `8877` - // Minimum execution time: 179_000_000 picoseconds. - Weight::from_parts(179_000_000, 8877) + // Minimum execution time: 157_000_000 picoseconds. + Weight::from_parts(193_000_000, 8877) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(7_u64)) } @@ -162,14 +162,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) - /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:1 w:0) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) + /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -178,11 +180,11 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `6104` + // Measured: `6117` // Estimated: `8877` - // Minimum execution time: 136_000_000 picoseconds. - Weight::from_parts(136_000_000, 8877) - .saturating_add(T::DbWeight::get().reads(14_u64)) + // Minimum execution time: 154_000_000 picoseconds. + Weight::from_parts(179_000_000, 8877) + .saturating_add(T::DbWeight::get().reads(15_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) @@ -194,7 +196,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(24735), added: 27210, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -202,7 +204,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `DelegatedStaking::Agents` (r:1 w:0) @@ -211,8 +213,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `5425` // Estimated: `28200` - // Minimum execution time: 107_000_000 picoseconds. - Weight::from_parts(107_000_000, 28200) + // Minimum execution time: 82_000_000 picoseconds. + Weight::from_parts(106_000_000, 28200) .saturating_add(T::DbWeight::get().reads(11_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -225,7 +227,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(24735), added: 27210, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -233,7 +235,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::VirtualStakers` (r:1 w:1) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -250,14 +252,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn withdraw_unbonded_kill() -> Weight { // Proof Size summary in bytes: // Measured: `6042` // Estimated: `28200` - // Minimum execution time: 207_000_000 picoseconds. - Weight::from_parts(207_000_000, 28200) + // Minimum execution time: 164_000_000 picoseconds. + Weight::from_parts(218_000_000, 28200) .saturating_add(T::DbWeight::get().reads(17_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -267,6 +271,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:1 w:0) @@ -287,9 +293,9 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `7492` // Estimated: `4218` - // Minimum execution time: 82_000_000 picoseconds. - Weight::from_parts(82_000_000, 4218) - .saturating_add(T::DbWeight::get().reads(12_u64)) + // Minimum execution time: 66_000_000 picoseconds. + Weight::from_parts(96_000_000, 4218) + .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(5_u64)) } /// Storage: `Staking::Ledger` (r:1 w:0) @@ -299,14 +305,18 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::Nominators` (r:128 w:128) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// The range of component `k` is `[1, 128]`. - fn kick(_k: u32, ) -> Weight { + fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4371 + k * (766 ±0)` - // Estimated: `389214` - // Minimum execution time: 49_000_000 picoseconds. - Weight::from_parts(1_084_000_000, 389214) - .saturating_add(T::DbWeight::get().reads(130_u64)) - .saturating_add(T::DbWeight::get().writes(128_u64)) + // Estimated: `4218 + k * (3033 ±0)` + // Minimum execution time: 50_000_000 picoseconds. + Weight::from_parts(87_035_870, 4218) + // Standard Error: 538_704 + .saturating_add(Weight::from_parts(11_741_907, 0).saturating_mul(k.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -333,14 +343,18 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::CounterForNominators` (r:1 w:1) /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 16]`. - fn nominate(_n: u32, ) -> Weight { + fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `3982 + n * (69 ±0)` - // Estimated: `43830` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(175_000_000, 43830) - .saturating_add(T::DbWeight::get().reads(29_u64)) + // Estimated: `6248 + n * (2520 ±0)` + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(154_058_666, 6248) + // Standard Error: 1_195_897 + .saturating_add(Weight::from_parts(9_541_333, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(13_u64)) + .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -364,8 +378,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `4051` // Estimated: `6248` - // Minimum execution time: 91_000_000 picoseconds. - Weight::from_parts(91_000_000, 6248) + // Minimum execution time: 94_000_000 picoseconds. + Weight::from_parts(118_000_000, 6248) .saturating_add(T::DbWeight::get().reads(10_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -379,8 +393,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3077` // Estimated: `4218` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(30_000_000, 4218) + // Minimum execution time: 23_000_000 picoseconds. + Weight::from_parts(31_000_000, 4218) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -394,8 +408,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `4286` // Estimated: `4218` - // Minimum execution time: 38_000_000 picoseconds. - Weight::from_parts(38_000_000, 4218) + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(42_000_000, 4218) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } @@ -407,8 +421,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `3716` // Estimated: `7446` - // Minimum execution time: 37_000_000 picoseconds. - Weight::from_parts(37_000_000, 7446) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(39_000_000, 7446) .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -418,8 +432,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_000_000, 0) + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -428,8 +442,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_000_000 picoseconds. - Weight::from_parts(14_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(37_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -438,8 +452,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(33_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -448,8 +462,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(40_000_000, 0) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:1502 w:1502) @@ -459,14 +473,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::Payee` (r:751 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `u` is `[0, 751]`. - fn deprecate_controller_batch(_u: u32, ) -> Weight { + fn deprecate_controller_batch(u: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + u * (465 ±0)` - // Estimated: `4849446` - // Minimum execution time: 39_000_000 picoseconds. - Weight::from_parts(18_389_000_000, 4849446) - .saturating_add(T::DbWeight::get().reads(3004_u64)) - .saturating_add(T::DbWeight::get().writes(2253_u64)) + // Estimated: `9 + u * (6457 ±0)` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(19_500_000, 9) + // Standard Error: 1_530_385 + .saturating_add(Weight::from_parts(43_187_083, 0).saturating_mul(u.into())) + .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(u.into()))) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(u.into()))) + .saturating_add(Weight::from_parts(0, 6457).saturating_mul(u.into())) } /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) @@ -475,7 +492,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::VirtualStakers` (r:1 w:1) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -494,31 +511,35 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn force_unstake() -> Weight { // Proof Size summary in bytes: // Measured: `5986` // Estimated: `6248` - // Minimum execution time: 146_000_000 picoseconds. - Weight::from_parts(146_000_000, 6248) + // Minimum execution time: 158_000_000 picoseconds. + Weight::from_parts(217_000_000, 6248) .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(12_u64)) + .saturating_add(T::DbWeight::get().writes(13_u64)) } /// Storage: `Staking::CancelledSlashes` (r:1 w:1) /// Proof: `Staking::CancelledSlashes` (`max_values`: None, `max_size`: Some(36014), added: 38489, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 1000]`. - fn cancel_deferred_slash(_s: u32, ) -> Weight { + fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `600` // Estimated: `39479` - // Minimum execution time: 16_000_000 picoseconds. - Weight::from_parts(3_259_000_000, 39479) + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(27_625_962, 39479) + // Standard Error: 48_123 + .saturating_add(Weight::from_parts(4_136_333, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `Staking::ClaimedRewards` (r:1 w:1) - /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) @@ -527,12 +548,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:513 w:513) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:513 w:513) + /// Storage: `System::Account` (r:514 w:513) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:513 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:513 w:513) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x6f320d44e42312c78638e6c92dff65af` (r:1 w:0) /// Proof: UNKNOWN KEY `0x6f320d44e42312c78638e6c92dff65af` (r:1 w:0) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) @@ -541,18 +562,24 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorPrefs` (r:1 w:0) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Staking::DisableLegacyMintingEra` (r:1 w:0) + /// Proof: `Staking::DisableLegacyMintingEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:513 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 512]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `9500 + n * (2363 ±0)` - // Estimated: `1656954` - // Minimum execution time: 257_000_000 picoseconds. - Weight::from_parts(49_123_000_000, 9500) - .saturating_add(Weight::from_parts(0, 2363).saturating_mul(n.into())) - .saturating_add(T::DbWeight::get().reads(3086_u64)) - .saturating_add(T::DbWeight::get().writes(1540_u64)) + // Measured: `7291 + n * (956 ±0)` + // Estimated: `39483 + n * (3228 ±0)` + // Minimum execution time: 280_000_000 picoseconds. + Weight::from_parts(381_399_999, 39483) + // Standard Error: 5_324_395 + .saturating_add(Weight::from_parts(159_430_859, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(16_u64)) + .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(4_u64)) + .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3228).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -565,7 +592,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -573,12 +600,14 @@ impl WeightInfo for SubstrateWeight { /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// The range of component `l` is `[1, 32]`. - fn rebond(_l: u32, ) -> Weight { + fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `5957 + l * (4 ±0)` + // Measured: `5958 + l * (4 ±0)` // Estimated: `8877` - // Minimum execution time: 127_000_000 picoseconds. - Weight::from_parts(130_000_000, 8877) + // Minimum execution time: 134_000_000 picoseconds. + Weight::from_parts(215_834_838, 8877) + // Standard Error: 562_059 + .saturating_add(Weight::from_parts(1_325_161, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(12_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } @@ -593,7 +622,7 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -610,14 +639,16 @@ impl WeightInfo for SubstrateWeight { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn reap_stash() -> Weight { // Proof Size summary in bytes: // Measured: `6050` // Estimated: `6248` - // Minimum execution time: 162_000_000 picoseconds. - Weight::from_parts(162_000_000, 6248) + // Minimum execution time: 168_000_000 picoseconds. + Weight::from_parts(223_000_000, 6248) .saturating_add(T::DbWeight::get().reads(14_u64)) - .saturating_add(T::DbWeight::get().writes(11_u64)) + .saturating_add(T::DbWeight::get().writes(12_u64)) } /// Storage: `Staking::AreNominatorsSlashable` (r:0 w:1) /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) @@ -639,8 +670,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_000_000 picoseconds. - Weight::from_parts(8_000_000, 0) + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(13_000_000, 0) .saturating_add(T::DbWeight::get().writes(8_u64)) } /// Storage: `Staking::AreNominatorsSlashable` (r:0 w:1) @@ -663,7 +694,7 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_000_000 picoseconds. + // Minimum execution time: 4_000_000 picoseconds. Weight::from_parts(8_000_000, 0) .saturating_add(T::DbWeight::get().writes(8_u64)) } @@ -695,44 +726,58 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `4184` // Estimated: `6248` - // Minimum execution time: 101_000_000 picoseconds. - Weight::from_parts(101_000_000, 6248) + // Minimum execution time: 116_000_000 picoseconds. + Weight::from_parts(141_000_000, 6248) .saturating_add(T::DbWeight::get().reads(13_u64)) .saturating_add(T::DbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: // Measured: `607` // Estimated: `3510` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(25_000_000, 3510) - .saturating_add(T::DbWeight::get().reads(2_u64)) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(38_000_000, 3510) + .saturating_add(T::DbWeight::get().reads(3_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:0 w:1) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) + // Measured: `563` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(12_000_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) .saturating_add(T::DbWeight::get().writes(1_u64)) } + /// Storage: `Staking::MinCommission` (r:1 w:0) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:0 w:1) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_max_commission() -> Weight { - // TODO(ank4n): rebench - Self::set_min_commission() + // Proof Size summary in bytes: + // Measured: `563` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(12_000_000, 1489) + .saturating_add(T::DbWeight::get().reads(1_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:0) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -743,8 +788,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `5012` // Estimated: `4764` - // Minimum execution time: 62_000_000 picoseconds. - Weight::from_parts(62_000_000, 4764) + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(65_000_000, 4764) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -759,13 +804,13 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) fn migrate_currency() -> Weight { // Proof Size summary in bytes: // Measured: `5122` // Estimated: `4764` - // Minimum execution time: 106_000_000 picoseconds. - Weight::from_parts(106_000_000, 4764) + // Minimum execution time: 87_000_000 picoseconds. + Weight::from_parts(113_000_000, 4764) .saturating_add(T::DbWeight::get().reads(6_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -783,26 +828,31 @@ impl WeightInfo for SubstrateWeight { /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `DelegatedStaking::Agents` (r:513 w:513) /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:513 w:513) + /// Storage: `System::Account` (r:514 w:514) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:513 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:513 w:513) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 512]`. - fn apply_slash(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `5844 + n * (822 ±0)` - // Estimated: `1656954` - // Minimum execution time: 146_000_000 picoseconds. - Weight::from_parts(27_753_000_000, 1656954) - .saturating_add(T::DbWeight::get().reads(3594_u64)) - .saturating_add(T::DbWeight::get().writes(2053_u64)) + fn apply_slash(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `6750 + n * (820 ±0)` + // Estimated: `39479 + n * (3228 ±0)` + // Minimum execution time: 165_000_000 picoseconds. + Weight::from_parts(261_799_999, 39479) + // Standard Error: 3_732_993 + .saturating_add(Weight::from_parts(100_390_234, 0).saturating_mul(n.into())) + .saturating_add(T::DbWeight::get().reads(11_u64)) + .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(T::DbWeight::get().writes(6_u64)) + .saturating_add(T::DbWeight::get().writes((4_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3228).saturating_mul(n.into())) } /// Storage: `Staking::ProcessingOffence` (r:1 w:1) /// Proof: `Staking::ProcessingOffence` (`max_values`: Some(1), `max_size`: Some(85), added: 580, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueue` (r:2 w:1) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashRewardFraction` (r:1 w:0) @@ -817,8 +867,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `19508` // Estimated: `28121` - // Minimum execution time: 227_000_000 picoseconds. - Weight::from_parts(227_000_000, 28121) + // Minimum execution time: 180_000_000 picoseconds. + Weight::from_parts(191_000_000, 28121) .saturating_add(T::DbWeight::get().reads(7_u64)) .saturating_add(T::DbWeight::get().writes(4_u64)) } @@ -833,16 +883,21 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::OffenceQueue` (r:500 w:500) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// The range of component `v` is `[2, 500]`. - fn rc_on_offence(_v: u32, ) -> Weight { + fn rc_on_offence(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `787 + v * (96 ±0)` - // Estimated: `1288990` - // Minimum execution time: 71_000_000 picoseconds. - Weight::from_parts(8_719_000_000, 1288990) - .saturating_add(T::DbWeight::get().reads(1503_u64)) - .saturating_add(T::DbWeight::get().writes(1001_u64)) + // Estimated: `1534 + v * (2576 ±0)` + // Minimum execution time: 77_000_000 picoseconds. + Weight::from_parts(60_927_710, 1534) + // Standard Error: 1_794_218 + .saturating_add(Weight::from_parts(23_286_144, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(v.into()))) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(T::DbWeight::get().writes((2_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 2576).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:1) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) @@ -850,10 +905,8 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:1) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:1 w:0) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::MaxStakedRewards` (r:1 w:0) - /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::BondedEras` (r:1 w:1) /// Proof: `Staking::BondedEras` (`max_values`: Some(1), `max_size`: Some(25), added: 520, mode: `MaxEncodedLen`) /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) @@ -882,12 +935,12 @@ impl WeightInfo for SubstrateWeight { /// Proof: `Staking::ElectableStashes` (`max_values`: Some(1), `max_size`: Some(32002), added: 32497, mode: `MaxEncodedLen`) fn rc_on_session_report() -> Weight { // Proof Size summary in bytes: - // Measured: `1340` + // Measured: `1958` // Estimated: `39483` - // Minimum execution time: 350_000_000 picoseconds. - Weight::from_parts(350_000_000, 39483) - .saturating_add(T::DbWeight::get().reads(13_u64)) - .saturating_add(T::DbWeight::get().writes(10_u64)) + // Minimum execution time: 331_000_000 picoseconds. + Weight::from_parts(385_000_000, 39483) + .saturating_add(T::DbWeight::get().reads(12_u64)) + .saturating_add(T::DbWeight::get().writes(11_u64)) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -896,14 +949,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::ErasStakersPaged` (r:101 w:100) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: Some(24656), added: 27131, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_stakers_paged(_v: u32, ) -> Weight { + fn prune_era_stakers_paged(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `975 + v * (9 ±0)` - // Estimated: `261435` + // Estimated: `6670 + v * (255 ±0)` // Minimum execution time: 28_000_000 picoseconds. - Weight::from_parts(145_000_000, 261435) - .saturating_add(T::DbWeight::get().reads(103_u64)) - .saturating_add(T::DbWeight::get().writes(100_u64)) + Weight::from_parts(39_236_005, 6670) + // Standard Error: 10_976 + .saturating_add(Weight::from_parts(199_892, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(4_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 255).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -912,14 +968,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::ErasStakersOverview` (r:101 w:100) /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_stakers_overview(_v: u32, ) -> Weight { + fn prune_era_stakers_overview(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `845 + v * (6 ±0)` - // Estimated: `258652` - // Minimum execution time: 24_000_000 picoseconds. - Weight::from_parts(166_000_000, 258652) - .saturating_add(T::DbWeight::get().reads(103_u64)) - .saturating_add(T::DbWeight::get().writes(100_u64)) + // Estimated: `4310 + v * (255 ±0)` + // Minimum execution time: 25_000_000 picoseconds. + Weight::from_parts(49_105_252, 4310) + // Standard Error: 12_734 + .saturating_add(Weight::from_parts(218_277, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 255).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -928,14 +987,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::ErasValidatorPrefs` (r:101 w:100) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_validator_prefs(_v: u32, ) -> Weight { + fn prune_era_validator_prefs(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `886 + v * (5 ±0)` - // Estimated: `257193` - // Minimum execution time: 23_000_000 picoseconds. - Weight::from_parts(148_000_000, 257193) - .saturating_add(T::DbWeight::get().reads(103_u64)) - .saturating_add(T::DbWeight::get().writes(100_u64)) + // Estimated: `4352 + v * (253 ±0)` + // Minimum execution time: 24_000_000 picoseconds. + Weight::from_parts(50_196_802, 4352) + // Standard Error: 12_247 + .saturating_add(Weight::from_parts(197_934, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 253).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -944,14 +1006,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::ClaimedRewards` (r:101 w:100) /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_claimed_rewards(_v: u32, ) -> Weight { + fn prune_era_claimed_rewards(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `836 + v * (6 ±0)` - // Estimated: `257878` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(149_000_000, 257878) - .saturating_add(T::DbWeight::get().reads(103_u64)) - .saturating_add(T::DbWeight::get().writes(100_u64)) + // Estimated: `4302 + v * (254 ±0)` + // Minimum execution time: 24_000_000 picoseconds. + Weight::from_parts(49_021_940, 4302) + // Standard Error: 10_834 + .saturating_add(Weight::from_parts(167_248, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(3_u64)) + .saturating_add(T::DbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 254).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -963,8 +1028,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 4221) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(22_000_000, 4221) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -978,8 +1043,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 4221) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(24_000_000, 4221) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(2_u64)) } @@ -995,8 +1060,8 @@ impl WeightInfo for SubstrateWeight { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 4221) + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(26_000_000, 4221) .saturating_add(T::DbWeight::get().reads(2_u64)) .saturating_add(T::DbWeight::get().writes(3_u64)) } @@ -1007,14 +1072,17 @@ impl WeightInfo for SubstrateWeight { /// Storage: `Staking::ValidatorSlashInEra` (r:101 w:100) /// Proof: `Staking::ValidatorSlashInEra` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_validator_slash_in_era(_v: u32, ) -> Weight { + fn prune_era_validator_slash_in_era(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `787 + v * (7 ±0)` - // Estimated: `259023` - // Minimum execution time: 33_000_000 picoseconds. - Weight::from_parts(154_000_000, 259023) - .saturating_add(T::DbWeight::get().reads(103_u64)) - .saturating_add(T::DbWeight::get().writes(100_u64)) + // Estimated: `259023 + v * (7 ±0)` + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(50_321_349, 259023) + // Standard Error: 11_862 + .saturating_add(Weight::from_parts(178_650, 0).saturating_mul(v.into())) + .saturating_add(T::DbWeight::get().reads(2_u64)) + .saturating_add(T::DbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 7).saturating_mul(v.into())) } } @@ -1031,15 +1099,15 @@ impl WeightInfo for () { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) fn bond() -> Weight { // Proof Size summary in bytes: // Measured: `4933` // Estimated: `4218` - // Minimum execution time: 96_000_000 picoseconds. - Weight::from_parts(96_000_000, 4218) + // Minimum execution time: 80_000_000 picoseconds. + Weight::from_parts(116_000_000, 4218) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1054,7 +1122,7 @@ impl WeightInfo for () { /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -1063,10 +1131,10 @@ impl WeightInfo for () { /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn bond_extra() -> Weight { // Proof Size summary in bytes: - // Measured: `5955` + // Measured: `5956` // Estimated: `8877` - // Minimum execution time: 179_000_000 picoseconds. - Weight::from_parts(179_000_000, 8877) + // Minimum execution time: 157_000_000 picoseconds. + Weight::from_parts(193_000_000, 8877) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(7_u64)) } @@ -1078,14 +1146,16 @@ impl WeightInfo for () { /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) /// Proof: `Staking::MinNominatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) - /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) - /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:1 w:0) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) + /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) + /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -1094,11 +1164,11 @@ impl WeightInfo for () { /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) fn unbond() -> Weight { // Proof Size summary in bytes: - // Measured: `6104` + // Measured: `6117` // Estimated: `8877` - // Minimum execution time: 136_000_000 picoseconds. - Weight::from_parts(136_000_000, 8877) - .saturating_add(RocksDbWeight::get().reads(14_u64)) + // Minimum execution time: 154_000_000 picoseconds. + Weight::from_parts(179_000_000, 8877) + .saturating_add(RocksDbWeight::get().reads(15_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::Ledger` (r:1 w:1) @@ -1110,7 +1180,7 @@ impl WeightInfo for () { /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(24735), added: 27210, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -1118,7 +1188,7 @@ impl WeightInfo for () { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `NominationPools::ReversePoolIdLookup` (r:1 w:0) /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `DelegatedStaking::Agents` (r:1 w:0) @@ -1127,8 +1197,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `5425` // Estimated: `28200` - // Minimum execution time: 107_000_000 picoseconds. - Weight::from_parts(107_000_000, 28200) + // Minimum execution time: 82_000_000 picoseconds. + Weight::from_parts(106_000_000, 28200) .saturating_add(RocksDbWeight::get().reads(11_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1141,7 +1211,7 @@ impl WeightInfo for () { /// Storage: `Staking::UnappliedSlashes` (r:1 w:0) /// Proof: `Staking::UnappliedSlashes` (`max_values`: None, `max_size`: Some(24735), added: 27210, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:0) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::MinValidatorBond` (r:1 w:0) /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinNominatorBond` (r:1 w:0) @@ -1149,7 +1219,7 @@ impl WeightInfo for () { /// Storage: `Staking::VirtualStakers` (r:1 w:1) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -1166,14 +1236,16 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn withdraw_unbonded_kill() -> Weight { // Proof Size summary in bytes: // Measured: `6042` // Estimated: `28200` - // Minimum execution time: 207_000_000 picoseconds. - Weight::from_parts(207_000_000, 28200) + // Minimum execution time: 164_000_000 picoseconds. + Weight::from_parts(218_000_000, 28200) .saturating_add(RocksDbWeight::get().reads(17_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -1183,6 +1255,8 @@ impl WeightInfo for () { /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:1 w:0) @@ -1203,9 +1277,9 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `7492` // Estimated: `4218` - // Minimum execution time: 82_000_000 picoseconds. - Weight::from_parts(82_000_000, 4218) - .saturating_add(RocksDbWeight::get().reads(12_u64)) + // Minimum execution time: 66_000_000 picoseconds. + Weight::from_parts(96_000_000, 4218) + .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(5_u64)) } /// Storage: `Staking::Ledger` (r:1 w:0) @@ -1215,14 +1289,18 @@ impl WeightInfo for () { /// Storage: `Staking::Nominators` (r:128 w:128) /// Proof: `Staking::Nominators` (`max_values`: None, `max_size`: Some(558), added: 3033, mode: `MaxEncodedLen`) /// The range of component `k` is `[1, 128]`. - fn kick(_k: u32, ) -> Weight { + fn kick(k: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `4371 + k * (766 ±0)` - // Estimated: `389214` - // Minimum execution time: 49_000_000 picoseconds. - Weight::from_parts(1_084_000_000, 389214) - .saturating_add(RocksDbWeight::get().reads(130_u64)) - .saturating_add(RocksDbWeight::get().writes(128_u64)) + // Estimated: `4218 + k * (3033 ±0)` + // Minimum execution time: 50_000_000 picoseconds. + Weight::from_parts(87_035_870, 4218) + // Standard Error: 538_704 + .saturating_add(Weight::from_parts(11_741_907, 0).saturating_mul(k.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(k.into()))) + .saturating_add(RocksDbWeight::get().writes((1_u64).saturating_mul(k.into()))) + .saturating_add(Weight::from_parts(0, 3033).saturating_mul(k.into())) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -1249,14 +1327,18 @@ impl WeightInfo for () { /// Storage: `Staking::CounterForNominators` (r:1 w:1) /// Proof: `Staking::CounterForNominators` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// The range of component `n` is `[1, 16]`. - fn nominate(_n: u32, ) -> Weight { + fn nominate(n: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `3982 + n * (69 ±0)` - // Estimated: `43830` - // Minimum execution time: 99_000_000 picoseconds. - Weight::from_parts(175_000_000, 43830) - .saturating_add(RocksDbWeight::get().reads(29_u64)) + // Estimated: `6248 + n * (2520 ±0)` + // Minimum execution time: 100_000_000 picoseconds. + Weight::from_parts(154_058_666, 6248) + // Standard Error: 1_195_897 + .saturating_add(Weight::from_parts(9_541_333, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(13_u64)) + .saturating_add(RocksDbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(Weight::from_parts(0, 2520).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:0) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -1280,8 +1362,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4051` // Estimated: `6248` - // Minimum execution time: 91_000_000 picoseconds. - Weight::from_parts(91_000_000, 6248) + // Minimum execution time: 94_000_000 picoseconds. + Weight::from_parts(118_000_000, 6248) .saturating_add(RocksDbWeight::get().reads(10_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1295,8 +1377,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3077` // Estimated: `4218` - // Minimum execution time: 30_000_000 picoseconds. - Weight::from_parts(30_000_000, 4218) + // Minimum execution time: 23_000_000 picoseconds. + Weight::from_parts(31_000_000, 4218) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1310,8 +1392,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4286` // Estimated: `4218` - // Minimum execution time: 38_000_000 picoseconds. - Weight::from_parts(38_000_000, 4218) + // Minimum execution time: 29_000_000 picoseconds. + Weight::from_parts(42_000_000, 4218) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } @@ -1323,8 +1405,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `3716` // Estimated: `7446` - // Minimum execution time: 37_000_000 picoseconds. - Weight::from_parts(37_000_000, 7446) + // Minimum execution time: 28_000_000 picoseconds. + Weight::from_parts(39_000_000, 7446) .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1334,8 +1416,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 3_000_000 picoseconds. - Weight::from_parts(3_000_000, 0) + // Minimum execution time: 2_000_000 picoseconds. + Weight::from_parts(6_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1344,8 +1426,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 14_000_000 picoseconds. - Weight::from_parts(14_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(37_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1354,8 +1436,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_000_000 picoseconds. - Weight::from_parts(12_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(33_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ForceEra` (r:0 w:1) @@ -1364,8 +1446,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 0) + // Minimum execution time: 11_000_000 picoseconds. + Weight::from_parts(40_000_000, 0) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::Ledger` (r:1502 w:1502) @@ -1375,14 +1457,17 @@ impl WeightInfo for () { /// Storage: `Staking::Payee` (r:751 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `u` is `[0, 751]`. - fn deprecate_controller_batch(_u: u32, ) -> Weight { + fn deprecate_controller_batch(u: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `0 + u * (465 ±0)` - // Estimated: `4849446` - // Minimum execution time: 39_000_000 picoseconds. - Weight::from_parts(18_389_000_000, 4849446) - .saturating_add(RocksDbWeight::get().reads(3004_u64)) - .saturating_add(RocksDbWeight::get().writes(2253_u64)) + // Estimated: `9 + u * (6457 ±0)` + // Minimum execution time: 8_000_000 picoseconds. + Weight::from_parts(19_500_000, 9) + // Standard Error: 1_530_385 + .saturating_add(Weight::from_parts(43_187_083, 0).saturating_mul(u.into())) + .saturating_add(RocksDbWeight::get().reads((4_u64).saturating_mul(u.into()))) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(u.into()))) + .saturating_add(Weight::from_parts(0, 6457).saturating_mul(u.into())) } /// Storage: `Staking::Bonded` (r:1 w:1) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) @@ -1391,7 +1476,7 @@ impl WeightInfo for () { /// Storage: `Staking::VirtualStakers` (r:1 w:1) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `System::Account` (r:1 w:1) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) @@ -1410,31 +1495,35 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn force_unstake() -> Weight { // Proof Size summary in bytes: // Measured: `5986` // Estimated: `6248` - // Minimum execution time: 146_000_000 picoseconds. - Weight::from_parts(146_000_000, 6248) + // Minimum execution time: 158_000_000 picoseconds. + Weight::from_parts(217_000_000, 6248) .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(12_u64)) + .saturating_add(RocksDbWeight::get().writes(13_u64)) } /// Storage: `Staking::CancelledSlashes` (r:1 w:1) /// Proof: `Staking::CancelledSlashes` (`max_values`: None, `max_size`: Some(36014), added: 38489, mode: `MaxEncodedLen`) /// The range of component `s` is `[1, 1000]`. - fn cancel_deferred_slash(_s: u32, ) -> Weight { + fn cancel_deferred_slash(s: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `600` // Estimated: `39479` - // Minimum execution time: 16_000_000 picoseconds. - Weight::from_parts(3_259_000_000, 39479) + // Minimum execution time: 17_000_000 picoseconds. + Weight::from_parts(27_625_962, 39479) + // Standard Error: 48_123 + .saturating_add(Weight::from_parts(4_136_333, 0).saturating_mul(s.into())) .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Staking::ErasStakersOverview` (r:1 w:0) /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `MaxEncodedLen`) /// Storage: `Staking::ClaimedRewards` (r:1 w:1) - /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) + /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:0) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorReward` (r:1 w:0) @@ -1443,12 +1532,12 @@ impl WeightInfo for () { /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:513 w:513) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:513 w:513) + /// Storage: `System::Account` (r:514 w:513) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:513 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:513 w:513) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: UNKNOWN KEY `0x6f320d44e42312c78638e6c92dff65af` (r:1 w:0) /// Proof: UNKNOWN KEY `0x6f320d44e42312c78638e6c92dff65af` (r:1 w:0) /// Storage: `Staking::ErasStakersPaged` (r:1 w:0) @@ -1457,18 +1546,24 @@ impl WeightInfo for () { /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorPrefs` (r:1 w:0) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) + /// Storage: `Staking::DisableLegacyMintingEra` (r:1 w:0) + /// Proof: `Staking::DisableLegacyMintingEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:513 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 512]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `9500 + n * (2363 ±0)` - // Estimated: `1656954` - // Minimum execution time: 257_000_000 picoseconds. - Weight::from_parts(49_123_000_000, 9500) - .saturating_add(Weight::from_parts(0, 2363).saturating_mul(n.into())) - .saturating_add(RocksDbWeight::get().reads(3086_u64)) - .saturating_add(RocksDbWeight::get().writes(1540_u64)) + // Measured: `7291 + n * (956 ±0)` + // Estimated: `39483 + n * (3228 ±0)` + // Minimum execution time: 280_000_000 picoseconds. + Weight::from_parts(381_399_999, 39483) + // Standard Error: 5_324_395 + .saturating_add(Weight::from_parts(159_430_859, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(16_u64)) + .saturating_add(RocksDbWeight::get().reads((6_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(4_u64)) + .saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3228).saturating_mul(n.into())) } /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) @@ -1481,7 +1576,7 @@ impl WeightInfo for () { /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `VoterList::ListNodes` (r:3 w:3) /// Proof: `VoterList::ListNodes` (`max_values`: None, `max_size`: Some(154), added: 2629, mode: `MaxEncodedLen`) /// Storage: `VoterList::Lock` (r:1 w:0) @@ -1489,12 +1584,14 @@ impl WeightInfo for () { /// Storage: `VoterList::ListBags` (r:2 w:2) /// Proof: `VoterList::ListBags` (`max_values`: None, `max_size`: Some(82), added: 2557, mode: `MaxEncodedLen`) /// The range of component `l` is `[1, 32]`. - fn rebond(_l: u32, ) -> Weight { + fn rebond(l: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `5957 + l * (4 ±0)` + // Measured: `5958 + l * (4 ±0)` // Estimated: `8877` - // Minimum execution time: 127_000_000 picoseconds. - Weight::from_parts(130_000_000, 8877) + // Minimum execution time: 134_000_000 picoseconds. + Weight::from_parts(215_834_838, 8877) + // Standard Error: 562_059 + .saturating_add(Weight::from_parts(1_325_161, 0).saturating_mul(l.into())) .saturating_add(RocksDbWeight::get().reads(12_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } @@ -1509,7 +1606,7 @@ impl WeightInfo for () { /// Storage: `Staking::Ledger` (r:1 w:1) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:0) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) /// Storage: `Staking::Nominators` (r:1 w:1) @@ -1526,14 +1623,16 @@ impl WeightInfo for () { /// Proof: `VoterList::CounterForListNodes` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:0 w:1) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) + /// Storage: `Staking::LastValidatorEra` (r:0 w:1) + /// Proof: `Staking::LastValidatorEra` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) fn reap_stash() -> Weight { // Proof Size summary in bytes: // Measured: `6050` // Estimated: `6248` - // Minimum execution time: 162_000_000 picoseconds. - Weight::from_parts(162_000_000, 6248) + // Minimum execution time: 168_000_000 picoseconds. + Weight::from_parts(223_000_000, 6248) .saturating_add(RocksDbWeight::get().reads(14_u64)) - .saturating_add(RocksDbWeight::get().writes(11_u64)) + .saturating_add(RocksDbWeight::get().writes(12_u64)) } /// Storage: `Staking::AreNominatorsSlashable` (r:0 w:1) /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) @@ -1555,8 +1654,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_000_000 picoseconds. - Weight::from_parts(8_000_000, 0) + // Minimum execution time: 5_000_000 picoseconds. + Weight::from_parts(13_000_000, 0) .saturating_add(RocksDbWeight::get().writes(8_u64)) } /// Storage: `Staking::AreNominatorsSlashable` (r:0 w:1) @@ -1579,7 +1678,7 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_000_000 picoseconds. + // Minimum execution time: 4_000_000 picoseconds. Weight::from_parts(8_000_000, 0) .saturating_add(RocksDbWeight::get().writes(8_u64)) } @@ -1611,44 +1710,58 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `4184` // Estimated: `6248` - // Minimum execution time: 101_000_000 picoseconds. - Weight::from_parts(101_000_000, 6248) + // Minimum execution time: 116_000_000 picoseconds. + Weight::from_parts(141_000_000, 6248) .saturating_add(RocksDbWeight::get().reads(13_u64)) .saturating_add(RocksDbWeight::get().writes(6_u64)) } /// Storage: `Staking::MinCommission` (r:1 w:0) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Validators` (r:1 w:1) /// Proof: `Staking::Validators` (`max_values`: None, `max_size`: Some(45), added: 2520, mode: `MaxEncodedLen`) fn force_apply_min_commission() -> Weight { // Proof Size summary in bytes: // Measured: `607` // Estimated: `3510` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(25_000_000, 3510) - .saturating_add(RocksDbWeight::get().reads(2_u64)) + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(38_000_000, 3510) + .saturating_add(RocksDbWeight::get().reads(3_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Staking::MaxCommission` (r:1 w:0) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::MinCommission` (r:0 w:1) /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_min_commission() -> Weight { // Proof Size summary in bytes: - // Measured: `0` - // Estimated: `0` - // Minimum execution time: 4_000_000 picoseconds. - Weight::from_parts(4_000_000, 0) + // Measured: `563` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(12_000_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) .saturating_add(RocksDbWeight::get().writes(1_u64)) } + /// Storage: `Staking::MinCommission` (r:1 w:0) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:0 w:1) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_max_commission() -> Weight { - // TODO(ank4n): rebench - Self::set_min_commission() + // Proof Size summary in bytes: + // Measured: `563` + // Estimated: `1489` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(12_000_000, 1489) + .saturating_add(RocksDbWeight::get().reads(1_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:0) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// Storage: `Balances::Locks` (r:1 w:0) /// Proof: `Balances::Locks` (`max_values`: None, `max_size`: Some(1299), added: 3774, mode: `MaxEncodedLen`) /// Storage: `Staking::Bonded` (r:1 w:1) @@ -1659,8 +1772,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `5012` // Estimated: `4764` - // Minimum execution time: 62_000_000 picoseconds. - Weight::from_parts(62_000_000, 4764) + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(65_000_000, 4764) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1675,13 +1788,13 @@ impl WeightInfo for () { /// Storage: `Balances::Freezes` (r:1 w:0) /// Proof: `Balances::Freezes` (`max_values`: None, `max_size`: Some(85), added: 2560, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:1 w:1) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) fn migrate_currency() -> Weight { // Proof Size summary in bytes: // Measured: `5122` // Estimated: `4764` - // Minimum execution time: 106_000_000 picoseconds. - Weight::from_parts(106_000_000, 4764) + // Minimum execution time: 87_000_000 picoseconds. + Weight::from_parts(113_000_000, 4764) .saturating_add(RocksDbWeight::get().reads(6_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1699,26 +1812,31 @@ impl WeightInfo for () { /// Proof: `NominationPools::ReversePoolIdLookup` (`max_values`: None, `max_size`: Some(44), added: 2519, mode: `MaxEncodedLen`) /// Storage: `DelegatedStaking::Agents` (r:513 w:513) /// Proof: `DelegatedStaking::Agents` (`max_values`: None, `max_size`: Some(120), added: 2595, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:513 w:513) + /// Storage: `System::Account` (r:514 w:514) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:513 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) /// Storage: `Balances::Holds` (r:513 w:513) - /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(211), added: 2686, mode: `MaxEncodedLen`) + /// Proof: `Balances::Holds` (`max_values`: None, `max_size`: Some(229), added: 2704, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 512]`. - fn apply_slash(_n: u32, ) -> Weight { - // Proof Size summary in bytes: - // Measured: `5844 + n * (822 ±0)` - // Estimated: `1656954` - // Minimum execution time: 146_000_000 picoseconds. - Weight::from_parts(27_753_000_000, 1656954) - .saturating_add(RocksDbWeight::get().reads(3594_u64)) - .saturating_add(RocksDbWeight::get().writes(2053_u64)) + fn apply_slash(n: u32, ) -> Weight { + // Proof Size summary in bytes: + // Measured: `6750 + n * (820 ±0)` + // Estimated: `39479 + n * (3228 ±0)` + // Minimum execution time: 165_000_000 picoseconds. + Weight::from_parts(261_799_999, 39479) + // Standard Error: 3_732_993 + .saturating_add(Weight::from_parts(100_390_234, 0).saturating_mul(n.into())) + .saturating_add(RocksDbWeight::get().reads(11_u64)) + .saturating_add(RocksDbWeight::get().reads((7_u64).saturating_mul(n.into()))) + .saturating_add(RocksDbWeight::get().writes(6_u64)) + .saturating_add(RocksDbWeight::get().writes((4_u64).saturating_mul(n.into()))) + .saturating_add(Weight::from_parts(0, 3228).saturating_mul(n.into())) } /// Storage: `Staking::ProcessingOffence` (r:1 w:1) /// Proof: `Staking::ProcessingOffence` (`max_values`: Some(1), `max_size`: Some(85), added: 580, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueue` (r:2 w:1) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::SlashRewardFraction` (r:1 w:0) @@ -1733,8 +1851,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `19508` // Estimated: `28121` - // Minimum execution time: 227_000_000 picoseconds. - Weight::from_parts(227_000_000, 28121) + // Minimum execution time: 180_000_000 picoseconds. + Weight::from_parts(191_000_000, 28121) .saturating_add(RocksDbWeight::get().reads(7_u64)) .saturating_add(RocksDbWeight::get().writes(4_u64)) } @@ -1749,16 +1867,21 @@ impl WeightInfo for () { /// Storage: `Staking::OffenceQueue` (r:500 w:500) /// Proof: `Staking::OffenceQueue` (`max_values`: None, `max_size`: Some(101), added: 2576, mode: `MaxEncodedLen`) /// Storage: `Staking::OffenceQueueEras` (r:1 w:1) - /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(9), added: 504, mode: `MaxEncodedLen`) + /// Proof: `Staking::OffenceQueueEras` (`max_values`: Some(1), `max_size`: Some(49), added: 544, mode: `MaxEncodedLen`) /// The range of component `v` is `[2, 500]`. - fn rc_on_offence(_v: u32, ) -> Weight { + fn rc_on_offence(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `787 + v * (96 ±0)` - // Estimated: `1288990` - // Minimum execution time: 71_000_000 picoseconds. - Weight::from_parts(8_719_000_000, 1288990) - .saturating_add(RocksDbWeight::get().reads(1503_u64)) - .saturating_add(RocksDbWeight::get().writes(1001_u64)) + // Estimated: `1534 + v * (2576 ±0)` + // Minimum execution time: 77_000_000 picoseconds. + Weight::from_parts(60_927_710, 1534) + // Standard Error: 1_794_218 + .saturating_add(Weight::from_parts(23_286_144, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(v.into()))) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(RocksDbWeight::get().writes((2_u64).saturating_mul(v.into()))) + .saturating_add(Weight::from_parts(0, 2576).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:1) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `MaxEncodedLen`) @@ -1766,10 +1889,8 @@ impl WeightInfo for () { /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::CurrentEra` (r:1 w:1) /// Proof: `Staking::CurrentEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::ErasTotalStake` (r:1 w:0) - /// Proof: `Staking::ErasTotalStake` (`max_values`: None, `max_size`: Some(28), added: 2503, mode: `MaxEncodedLen`) - /// Storage: `Staking::MaxStakedRewards` (r:1 w:0) - /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:1 w:1) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::BondedEras` (r:1 w:1) /// Proof: `Staking::BondedEras` (`max_values`: Some(1), `max_size`: Some(25), added: 520, mode: `MaxEncodedLen`) /// Storage: `Staking::AreNominatorsSlashable` (r:1 w:0) @@ -1798,12 +1919,12 @@ impl WeightInfo for () { /// Proof: `Staking::ElectableStashes` (`max_values`: Some(1), `max_size`: Some(32002), added: 32497, mode: `MaxEncodedLen`) fn rc_on_session_report() -> Weight { // Proof Size summary in bytes: - // Measured: `1340` + // Measured: `1958` // Estimated: `39483` - // Minimum execution time: 350_000_000 picoseconds. - Weight::from_parts(350_000_000, 39483) - .saturating_add(RocksDbWeight::get().reads(13_u64)) - .saturating_add(RocksDbWeight::get().writes(10_u64)) + // Minimum execution time: 331_000_000 picoseconds. + Weight::from_parts(385_000_000, 39483) + .saturating_add(RocksDbWeight::get().reads(12_u64)) + .saturating_add(RocksDbWeight::get().writes(11_u64)) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -1812,14 +1933,17 @@ impl WeightInfo for () { /// Storage: `Staking::ErasStakersPaged` (r:101 w:100) /// Proof: `Staking::ErasStakersPaged` (`max_values`: None, `max_size`: Some(24656), added: 27131, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_stakers_paged(_v: u32, ) -> Weight { + fn prune_era_stakers_paged(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `975 + v * (9 ±0)` - // Estimated: `261435` + // Estimated: `6670 + v * (255 ±0)` // Minimum execution time: 28_000_000 picoseconds. - Weight::from_parts(145_000_000, 261435) - .saturating_add(RocksDbWeight::get().reads(103_u64)) - .saturating_add(RocksDbWeight::get().writes(100_u64)) + Weight::from_parts(39_236_005, 6670) + // Standard Error: 10_976 + .saturating_add(Weight::from_parts(199_892, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(4_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) + .saturating_add(Weight::from_parts(0, 255).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -1828,14 +1952,17 @@ impl WeightInfo for () { /// Storage: `Staking::ErasStakersOverview` (r:101 w:100) /// Proof: `Staking::ErasStakersOverview` (`max_values`: None, `max_size`: Some(92), added: 2567, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_stakers_overview(_v: u32, ) -> Weight { + fn prune_era_stakers_overview(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `845 + v * (6 ±0)` - // Estimated: `258652` - // Minimum execution time: 24_000_000 picoseconds. - Weight::from_parts(166_000_000, 258652) - .saturating_add(RocksDbWeight::get().reads(103_u64)) - .saturating_add(RocksDbWeight::get().writes(100_u64)) + // Estimated: `4310 + v * (255 ±0)` + // Minimum execution time: 25_000_000 picoseconds. + Weight::from_parts(49_105_252, 4310) + // Standard Error: 12_734 + .saturating_add(Weight::from_parts(218_277, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 255).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -1844,14 +1971,17 @@ impl WeightInfo for () { /// Storage: `Staking::ErasValidatorPrefs` (r:101 w:100) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_validator_prefs(_v: u32, ) -> Weight { + fn prune_era_validator_prefs(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `886 + v * (5 ±0)` - // Estimated: `257193` - // Minimum execution time: 23_000_000 picoseconds. - Weight::from_parts(148_000_000, 257193) - .saturating_add(RocksDbWeight::get().reads(103_u64)) - .saturating_add(RocksDbWeight::get().writes(100_u64)) + // Estimated: `4352 + v * (253 ±0)` + // Minimum execution time: 24_000_000 picoseconds. + Weight::from_parts(50_196_802, 4352) + // Standard Error: 12_247 + .saturating_add(Weight::from_parts(197_934, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 253).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -1860,14 +1990,17 @@ impl WeightInfo for () { /// Storage: `Staking::ClaimedRewards` (r:101 w:100) /// Proof: `Staking::ClaimedRewards` (`max_values`: None, `max_size`: Some(61), added: 2536, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_claimed_rewards(_v: u32, ) -> Weight { + fn prune_era_claimed_rewards(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `836 + v * (6 ±0)` - // Estimated: `257878` - // Minimum execution time: 25_000_000 picoseconds. - Weight::from_parts(149_000_000, 257878) - .saturating_add(RocksDbWeight::get().reads(103_u64)) - .saturating_add(RocksDbWeight::get().writes(100_u64)) + // Estimated: `4302 + v * (254 ±0)` + // Minimum execution time: 24_000_000 picoseconds. + Weight::from_parts(49_021_940, 4302) + // Standard Error: 10_834 + .saturating_add(Weight::from_parts(167_248, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(3_u64)) + .saturating_add(RocksDbWeight::get().writes(2_u64)) + .saturating_add(Weight::from_parts(0, 254).saturating_mul(v.into())) } /// Storage: `Staking::ActiveEra` (r:1 w:0) /// Proof: `Staking::ActiveEra` (`max_values`: Some(1), `max_size`: Some(13), added: 508, mode: `Measured`) @@ -1879,8 +2012,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 4221) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(22_000_000, 4221) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1894,8 +2027,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 19_000_000 picoseconds. - Weight::from_parts(19_000_000, 4221) + // Minimum execution time: 14_000_000 picoseconds. + Weight::from_parts(24_000_000, 4221) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(2_u64)) } @@ -1911,8 +2044,8 @@ impl WeightInfo for () { // Proof Size summary in bytes: // Measured: `756` // Estimated: `4221` - // Minimum execution time: 22_000_000 picoseconds. - Weight::from_parts(22_000_000, 4221) + // Minimum execution time: 15_000_000 picoseconds. + Weight::from_parts(26_000_000, 4221) .saturating_add(RocksDbWeight::get().reads(2_u64)) .saturating_add(RocksDbWeight::get().writes(3_u64)) } @@ -1923,13 +2056,16 @@ impl WeightInfo for () { /// Storage: `Staking::ValidatorSlashInEra` (r:101 w:100) /// Proof: `Staking::ValidatorSlashInEra` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `Measured`) /// The range of component `v` is `[1, 1000]`. - fn prune_era_validator_slash_in_era(_v: u32, ) -> Weight { + fn prune_era_validator_slash_in_era(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `787 + v * (7 ±0)` - // Estimated: `259023` - // Minimum execution time: 33_000_000 picoseconds. - Weight::from_parts(154_000_000, 259023) - .saturating_add(RocksDbWeight::get().reads(103_u64)) - .saturating_add(RocksDbWeight::get().writes(100_u64)) + // Estimated: `259023 + v * (7 ±0)` + // Minimum execution time: 27_000_000 picoseconds. + Weight::from_parts(50_321_349, 259023) + // Standard Error: 11_862 + .saturating_add(Weight::from_parts(178_650, 0).saturating_mul(v.into())) + .saturating_add(RocksDbWeight::get().reads(2_u64)) + .saturating_add(RocksDbWeight::get().writes(1_u64)) + .saturating_add(Weight::from_parts(0, 7).saturating_mul(v.into())) } } From 4912afb7f47a8b2e3accac12922dc8f6c909858e Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:20:53 +0200 Subject: [PATCH 26/87] fix drip issuance bench --- Cargo.lock | 1 + substrate/frame/dap/Cargo.toml | 4 ++++ substrate/frame/dap/src/benchmarking.rs | 8 ++++---- .../frame/staking-async/runtimes/parachain/src/lib.rs | 1 + 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b778bfd16b360..4ff016509bbf6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12678,6 +12678,7 @@ dependencies = [ "frame-system", "log", "pallet-balances", + "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-core 28.0.0", diff --git a/substrate/frame/dap/Cargo.toml b/substrate/frame/dap/Cargo.toml index a5b0640fbe763..4e3b17ec54e9e 100644 --- a/substrate/frame/dap/Cargo.toml +++ b/substrate/frame/dap/Cargo.toml @@ -21,6 +21,7 @@ frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } scale-info = { features = ["derive"], workspace = true } +pallet-timestamp = { workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } @@ -37,6 +38,7 @@ std = [ "frame-support/std", "frame-system/std", "log/std", + "pallet-timestamp/std", "scale-info/std", "sp-core/std", "sp-io/std", @@ -48,6 +50,7 @@ runtime-benchmarks = [ "frame-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "pallet-balances/runtime-benchmarks", + "pallet-timestamp/runtime-benchmarks", "sp-runtime/runtime-benchmarks", "sp-staking/runtime-benchmarks", ] @@ -55,5 +58,6 @@ try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", "pallet-balances/try-runtime", + "pallet-timestamp/try-runtime", "sp-runtime/try-runtime", ] diff --git a/substrate/frame/dap/src/benchmarking.rs b/substrate/frame/dap/src/benchmarking.rs index 7453fb281025a..56445ed0311b9 100644 --- a/substrate/frame/dap/src/benchmarking.rs +++ b/substrate/frame/dap/src/benchmarking.rs @@ -23,7 +23,7 @@ use frame_support::traits::Time; use frame_system::RawOrigin; use sp_staking::budget::BudgetRecipientList; -#[benchmarks] +#[benchmarks(where T: pallet_timestamp::Config)] mod benchmarks { use super::*; @@ -62,8 +62,9 @@ mod benchmarks { let allocations = build_even_allocation::(); BudgetAllocation::::put(allocations); - // Seed the timestamp so the drip fires. - let now: u64 = T::Time::now().saturated_into(); + // Set a timestamp so the drip fires. + let now: u64 = 1_000_000; + pallet_timestamp::Now::::put(now); let past = now.saturating_sub(T::IssuanceCadence::get() + 1); LastIssuanceTimestamp::::put(past); @@ -72,7 +73,6 @@ mod benchmarks { Pallet::::drip_issuance(); } - // Timestamp should be updated. assert!(LastIssuanceTimestamp::::get() > past); } } diff --git a/substrate/frame/staking-async/runtimes/parachain/src/lib.rs b/substrate/frame/staking-async/runtimes/parachain/src/lib.rs index e67468a490b87..0bcf33f480289 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/lib.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/lib.rs @@ -1343,6 +1343,7 @@ mod benches { [pallet_bags_list, VoterList] [pallet_balances, Balances] [pallet_conviction_voting, ConvictionVoting] + [pallet_dap, Dap] [pallet_election_provider_multi_block, MultiBlockElection] [pallet_election_provider_multi_block_verifier, MultiBlockElectionVerifier] [pallet_election_provider_multi_block_unsigned, MultiBlockElectionUnsigned] From 5e17025f36e6d8d841fb26bca7c5827af7ed1151 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:25:58 +0200 Subject: [PATCH 27/87] weights --- substrate/frame/dap/src/benchmarking.rs | 1 - substrate/frame/dap/src/weights.rs | 88 ++++++++++++++++--------- 2 files changed, 56 insertions(+), 33 deletions(-) diff --git a/substrate/frame/dap/src/benchmarking.rs b/substrate/frame/dap/src/benchmarking.rs index 56445ed0311b9..5a8425651e639 100644 --- a/substrate/frame/dap/src/benchmarking.rs +++ b/substrate/frame/dap/src/benchmarking.rs @@ -19,7 +19,6 @@ use super::*; use frame_benchmarking::v2::*; -use frame_support::traits::Time; use frame_system::RawOrigin; use sp_staking::budget::BudgetRecipientList; diff --git a/substrate/frame/dap/src/weights.rs b/substrate/frame/dap/src/weights.rs index d8a456376df42..e05aac7a2b645 100644 --- a/substrate/frame/dap/src/weights.rs +++ b/substrate/frame/dap/src/weights.rs @@ -1,46 +1,70 @@ -// 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. - -//! Placeholder weights for `pallet_dap`. +//! Autogenerated weights for `pallet_dap` //! -//! These weights are not benchmarked. Replace with actual benchmarked weights -//! via `frame-omni-bencher` before deploying to production. +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2026-04-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `M3-Box.local`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// ./target/release/frame-omni-bencher +// v1 +// benchmark +// pallet +// --runtime +// ./target/release/wbuild/pallet-staking-async-parachain-runtime/pallet_staking_async_parachain_runtime.compact.compressed.wasm +// --pallet +// pallet_dap +// --extrinsic +// * +// --steps +// 2 +// --repeat +// 1 +// --output +// substrate/frame/dap/src/weights.rs +// --genesis-builder-preset +// fake-dev #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] -use frame_support::weights::Weight; - -/// Weight functions needed for `pallet_dap`. -pub trait WeightInfo { - fn set_budget_allocation() -> Weight; - fn drip_issuance() -> Weight; -} +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; -/// Default weights (not benchmarked). -impl WeightInfo for () { +/// Weight functions for `pallet_dap`. +pub struct WeightInfo(PhantomData); +impl pallet_dap::WeightInfo for WeightInfo { + /// Storage: `Dap::BudgetAllocation` (r:0 w:1) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) fn set_budget_allocation() -> Weight { - // TODO(ank4n): run bench - Weight::zero() + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 7_000_000 picoseconds. + Weight::from_parts(11_000_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) } + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::LastIssuanceTimestamp` (r:1 w:1) + /// Proof: `Dap::LastIssuanceTimestamp` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::BudgetAllocation` (r:1 w:0) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) fn drip_issuance() -> Weight { - // TODO(ank4n): run bench - Weight::zero() + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `6196` + // Minimum execution time: 49_000_000 picoseconds. + Weight::from_parts(60_000_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) } } From 0a4040e814f9128efdf6bdcf3224f590b5622668 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:26:45 +0200 Subject: [PATCH 28/87] taplo --- substrate/frame/dap/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/dap/Cargo.toml b/substrate/frame/dap/Cargo.toml index 4e3b17ec54e9e..ea659b8316249 100644 --- a/substrate/frame/dap/Cargo.toml +++ b/substrate/frame/dap/Cargo.toml @@ -20,8 +20,8 @@ frame-benchmarking = { optional = true, workspace = true } frame-support = { workspace = true } frame-system = { workspace = true } log = { workspace = true } -scale-info = { features = ["derive"], workspace = true } pallet-timestamp = { workspace = true } +scale-info = { features = ["derive"], workspace = true } sp-runtime = { workspace = true } sp-staking = { workspace = true } From dda4321473f3d369d27a0d12d5d16e32088f7c28 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:28:45 +0200 Subject: [PATCH 29/87] add dap bench to WAH --- cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index a670c0244c94c..ba4b325f28644 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1854,6 +1854,7 @@ mod benches { [pallet_bags_list, VoterList] [pallet_balances, Balances] [pallet_conviction_voting, ConvictionVoting] + [pallet_dap, Dap] [pallet_election_provider_multi_block, MultiBlockElection] [pallet_election_provider_multi_block::verifier, MultiBlockElectionVerifier] [pallet_election_provider_multi_block::unsigned, MultiBlockElectionUnsigned] From 07b284028c73c937667212c6c8bf5b1665fb8428 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 07:41:35 +0200 Subject: [PATCH 30/87] fix weight run with frame template --- substrate/frame/dap/src/weights.rs | 85 ++++++++++++++++++++++++------ 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/substrate/frame/dap/src/weights.rs b/substrate/frame/dap/src/weights.rs index e05aac7a2b645..468debb8f7221 100644 --- a/substrate/frame/dap/src/weights.rs +++ b/substrate/frame/dap/src/weights.rs @@ -1,11 +1,28 @@ +// 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. + //! Autogenerated weights for `pallet_dap` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 //! DATE: 2026-04-02, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `M3-Box.local`, CPU: `` -//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 +//! HOSTNAME: `Mac`, CPU: `` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: `1024` // Executed Command: // ./target/release/frame-omni-bencher @@ -26,28 +43,67 @@ // substrate/frame/dap/src/weights.rs // --genesis-builder-preset // fake-dev +// --template +// substrate/.maintain/frame-weight-template.hbs #![cfg_attr(rustfmt, rustfmt_skip)] #![allow(unused_parens)] #![allow(unused_imports)] #![allow(missing_docs)] +#![allow(dead_code)] -use frame_support::{traits::Get, weights::Weight}; +use frame_support::{traits::Get, weights::{Weight, constants::RocksDbWeight}}; use core::marker::PhantomData; -/// Weight functions for `pallet_dap`. -pub struct WeightInfo(PhantomData); -impl pallet_dap::WeightInfo for WeightInfo { +/// Weight functions needed for `pallet_dap`. +pub trait WeightInfo { + fn set_budget_allocation() -> Weight; + fn drip_issuance() -> Weight; +} + +/// Weights for `pallet_dap` using the Substrate node and recommended hardware. +pub struct SubstrateWeight(PhantomData); +impl WeightInfo for SubstrateWeight { + /// Storage: `Dap::BudgetAllocation` (r:0 w:1) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) + fn set_budget_allocation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(10_000_000, 0) + .saturating_add(T::DbWeight::get().writes(1_u64)) + } + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::LastIssuanceTimestamp` (r:1 w:1) + /// Proof: `Dap::LastIssuanceTimestamp` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::BudgetAllocation` (r:1 w:0) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn drip_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `1738` + // Estimated: `6196` + // Minimum execution time: 50_000_000 picoseconds. + Weight::from_parts(57_000_000, 6196) + .saturating_add(T::DbWeight::get().reads(5_u64)) + .saturating_add(T::DbWeight::get().writes(3_u64)) + } +} + +// For backwards compatibility and tests. +impl WeightInfo for () { /// Storage: `Dap::BudgetAllocation` (r:0 w:1) /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) fn set_budget_allocation() -> Weight { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 7_000_000 picoseconds. - Weight::from_parts(11_000_000, 0) - .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(1)) + // Minimum execution time: 6_000_000 picoseconds. + Weight::from_parts(10_000_000, 0) + .saturating_add(RocksDbWeight::get().writes(1_u64)) } /// Storage: `Timestamp::Now` (r:1 w:0) /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) @@ -61,10 +117,9 @@ impl pallet_dap::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `1738` // Estimated: `6196` - // Minimum execution time: 49_000_000 picoseconds. - Weight::from_parts(60_000_000, 0) - .saturating_add(Weight::from_parts(0, 6196)) - .saturating_add(T::DbWeight::get().reads(5)) - .saturating_add(T::DbWeight::get().writes(3)) + // Minimum execution time: 50_000_000 picoseconds. + Weight::from_parts(57_000_000, 6196) + .saturating_add(RocksDbWeight::get().reads(5_u64)) + .saturating_add(RocksDbWeight::get().writes(3_u64)) } } From 1aa31eaf0630e512633589d2321b7bb650462311 Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 2 Apr 2026 07:17:43 +0000 Subject: [PATCH 31/87] Update from github-actions[bot] running command 'bench --pallet pallet_dap' --- .../src/weights/pallet_dap.rs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs new file mode 100644 index 0000000000000..04a0da2787bde --- /dev/null +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs @@ -0,0 +1,82 @@ +// 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. + +//! Autogenerated weights for `pallet_dap` +//! +//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 +//! DATE: 2026-04-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! WORST CASE MAP SIZE: `1000000` +//! HOSTNAME: `203c6848e0f8`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 + +// Executed Command: +// frame-omni-bencher +// v1 +// benchmark +// pallet +// --extrinsic=* +// --runtime=target/production/wbuild/asset-hub-westend-runtime/asset_hub_westend_runtime.wasm +// --pallet=pallet_dap +// --header=/__w/polkadot-sdk/polkadot-sdk/cumulus/file_header.txt +// --output=./cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights +// --wasm-execution=compiled +// --steps=50 +// --repeat=20 +// --heap-pages=4096 +// --no-storage-info +// --no-min-squares +// --no-median-slopes + +#![cfg_attr(rustfmt, rustfmt_skip)] +#![allow(unused_parens)] +#![allow(unused_imports)] +#![allow(missing_docs)] + +use frame_support::{traits::Get, weights::Weight}; +use core::marker::PhantomData; + +/// Weight functions for `pallet_dap`. +pub struct WeightInfo(PhantomData); +impl pallet_dap::WeightInfo for WeightInfo { + /// Storage: `Dap::BudgetAllocation` (r:0 w:1) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) + fn set_budget_allocation() -> Weight { + // Proof Size summary in bytes: + // Measured: `0` + // Estimated: `0` + // Minimum execution time: 8_333_000 picoseconds. + Weight::from_parts(9_027_000, 0) + .saturating_add(Weight::from_parts(0, 0)) + .saturating_add(T::DbWeight::get().writes(1)) + } + /// Storage: `Timestamp::Now` (r:1 w:0) + /// Proof: `Timestamp::Now` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::LastIssuanceTimestamp` (r:1 w:1) + /// Proof: `Dap::LastIssuanceTimestamp` (`max_values`: Some(1), `max_size`: Some(8), added: 503, mode: `MaxEncodedLen`) + /// Storage: `Dap::BudgetAllocation` (r:1 w:0) + /// Proof: `Dap::BudgetAllocation` (`max_values`: Some(1), `max_size`: Some(593), added: 1088, mode: `MaxEncodedLen`) + /// Storage: `System::Account` (r:2 w:2) + /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) + fn drip_issuance() -> Weight { + // Proof Size summary in bytes: + // Measured: `2541` + // Estimated: `6196` + // Minimum execution time: 63_691_000 picoseconds. + Weight::from_parts(66_425_000, 0) + .saturating_add(Weight::from_parts(0, 6196)) + .saturating_add(T::DbWeight::get().reads(5)) + .saturating_add(T::DbWeight::get().writes(3)) + } +} From 8706c6c45dd0863b39a65f9208c0117add453188 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 16:24:35 +0200 Subject: [PATCH 32/87] ensure pot is created --- substrate/frame/staking-async/src/benchmarking.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 90b02da6f0f53..493bec6717e87 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -124,6 +124,11 @@ pub(crate) fn create_validator_with_nominators( .saturating_mul(1000u32.into()); >::insert(planned_era, total_payout); + // Create and fund the era reward pot so payout_stakers can transfer from it. + let era_pot = + crate::reward::EraRewardManager::::create(planned_era, EraPotType::StakerRewards); + let _ = asset::mint_creating::(&era_pot, total_payout); + Ok((v_stash, nominators, planned_era)) } From a811db664c7347ef90e89b0fc8b476e3c087e2f3 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 2 Apr 2026 16:29:59 +0200 Subject: [PATCH 33/87] disable legacy minting --- substrate/frame/staking-async/src/benchmarking.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 493bec6717e87..63470d3db7b6e 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -59,6 +59,9 @@ pub(crate) fn create_validator_with_nominators( // TODO: this can be replaced with `testing_utils` version? // Clean up any existing state. clear_validators_and_nominators::(); + + // Disable legacy minting so benchmarks always exercise the reward-pot path. + DisableLegacyMintingEra::::put(0); let mut points_total = 0; let mut points_individual = Vec::new(); From e77d44e0ea9d87a2ca58be8ecb1addcc4c3bce73 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 10:04:30 +0200 Subject: [PATCH 34/87] failing test --- .../staking-async/src/tests/payout_stakers.rs | 56 +++++++++++++++---- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 1f8bbf1287398..b739075085c84 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -912,31 +912,52 @@ fn test_multi_page_payout_stakers_by_page() { let controller_balance_after_p0_payout = asset::stakeable_balance::(&11); - // verify rewards have been paid out but still some left - // verify the validator has been rewarded assert!(controller_balance_after_p0_payout > controller_balance_before_p0_payout); + // Verify validator reward for page 0: with 0% commission, the validator's total + // reward is proportional to their own stake. This is prorated across pages by + // each page's stake fraction. + let era_reward = Eras::::get_stakers_reward(2).unwrap(); + let validator_total_reward = + Perbill::from_rational(1000 as Balance, total_exposure).mul_floor(era_reward); + let page_0_total = actual_exposure_0.page_total(); + let page_0_part = Perbill::from_rational(page_0_total, total_exposure); + let expected_page_0_reward = page_0_part.mul_floor(validator_total_reward); + let actual_validator_reward = + controller_balance_after_p0_payout - controller_balance_before_p0_payout; + assert_eq!( + actual_validator_reward, expected_page_0_reward, + "Validator page 0 reward: got {} expected {}", + actual_validator_reward, expected_page_0_reward, + ); + // Payout the second and last page of nominators assert_ok!(Staking::payout_stakers_by_page(RuntimeOrigin::signed(1337), 11, 2, 1)); - // verify `Rewarded` events are being executed for the second page. + // verify `Rewarded` events for second page (validator is also rewarded on each page). let events = staking_events_since_last_call(); assert!(matches!( events.as_slice(), &[ Event::PayoutStarted { era_index: 2, validator_stash: 11, page: 1, next: None }, + Event::Rewarded { stash: 11, dest: RewardDestination::Stash, amount: _ }, Event::Rewarded { stash: 1065, dest: RewardDestination::Stash, amount: _ }, Event::Rewarded { stash: 1066, dest: RewardDestination::Stash, amount: _ }, .. ] )); - // verify the validator was not rewarded the second time - assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); + // Validator is rewarded on every page (prorated by page stake). + let controller_balance_after_p1_payout = asset::stakeable_balance::(&11); + assert!(controller_balance_after_p1_payout > controller_balance_after_p0_payout); + + // Total validator reward across both pages should equal the full own-stake reward. + let total_validator_paid = + controller_balance_after_p1_payout - controller_balance_before_p0_payout; + assert_eq_error_rate!(total_validator_paid, validator_total_reward, 1); // With DAP, rewards minted at era finalization, so no change during payout - // Issuance was already increased during Session::roll_until_active_era above assert_eq!(pallet_balances::TotalIssuance::::get(), pre_payout_total_issuance); // Top 64 nominators of validator 11 automatically paid out, including the validator @@ -1113,8 +1134,8 @@ fn test_multi_page_payout_stakers_backward_compatible() { Error::::AlreadyClaimed.with_weight(err_weight) ); - // verify the validator was not rewarded the second time - assert_eq!(asset::stakeable_balance::(&11), controller_balance_after_p0_payout); + // Validator is rewarded on every page (prorated by page stake). + assert!(asset::stakeable_balance::(&11) > controller_balance_after_p0_payout); // verify all rewards have been paid out assert_eq_error_rate!( @@ -1434,10 +1455,10 @@ fn payout_stakers_handles_basic_errors() { #[test] fn test_commission_paid_across_pages() { ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let balance = 1; + let balance = 10_000; let commission = 50; - // Create a validator: + // Create a validator with significant self-stake to make own-stake reward visible. bond_validator(11, balance); assert_ok!(Staking::validate( RuntimeOrigin::signed(11), @@ -1480,8 +1501,19 @@ fn test_commission_paid_across_pages() { assert!(before_balance < after_balance); } - // Allow error rate of 2 due to floor rounding in payout calculations - assert_eq_error_rate!(asset::stakeable_balance::(&11), initial_balance + payout / 2, 2,); + // The validator should receive: + // - 50% commission on the total era reward (payout) + // - proportional own-stake share of the remaining 50% + let total_exposure: Balance = + balance + (0..200).map(|i| balance + i as Balance).sum::(); + let commission_reward = Perbill::from_percent(commission).mul_floor(payout); + let leftover = payout.saturating_sub(commission_reward); + let own_stake_reward = + Perbill::from_rational(balance, total_exposure).mul_floor(leftover); + let expected_total = commission_reward + own_stake_reward; + let actual_total = asset::stakeable_balance::(&11) - initial_balance; + // Allow error of 1 per page due to floor rounding across 4 pages. + assert_eq_error_rate!(actual_total, expected_total, 4); }); } From cb162753a9da5e05c9f0eb2e6e696493f6eb2ea5 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 10:47:39 +0200 Subject: [PATCH 35/87] the fix --- substrate/frame/staking-async/src/pallet/impls.rs | 12 +++++++++--- substrate/frame/staking-async/src/pallet/mod.rs | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 816469ec5837d..15b54877cae74 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -407,16 +407,22 @@ impl Pallet { let validator_commission = Eras::::get_validator_commission(era, &ledger.stash); - // Use the StakerRewardCalculator trait to calculate reward distribution. + // Use the overview's own-stake (not the page's, which is zeroed on pages > 0) + // so the calculator sees the full validator self-stake for reward computation. + let overview_own = ErasStakersOverview::::get(era, &stash) + .map(|o| o.own) + .unwrap_or_default(); + let reward_split = T::StakerRewardCalculator::calculate_staker_reward( validator_total_payout, validator_commission, - exposure.own(), + overview_own, exposure.total(), ); + // Prorate the validator's reward (commission + own-stake share) across pages + // proportional to each page's stake relative to total. let page_stake_part = Perbill::from_rational(exposure.page_total(), exposure.total()); - // validator commission is paid out in fraction across pages proportional to the page stake. let validator_staker_payout_for_page = page_stake_part.mul_floor(reward_split.validator_payout); diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index edd5583672e48..2d8f1f20c0659 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -2572,6 +2572,10 @@ pub mod pallet { /// backing a validator to receive the reward. The nominators are not sorted across pages /// and so it should not be assumed the highest staker would be on the topmost page and vice /// versa. If rewards are not claimed in [`Config::HistoryDepth`] eras, they are lost. + /// + /// The validator's own reward (commission + own-stake share) is prorated across pages + /// proportional to each page's stake. The full validator reward is the sum across all + /// pages. #[pallet::call_index(26)] #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked(T::MaxExposurePageSize::get()))] pub fn payout_stakers_by_page( From e733237c8c22a8407657d0ba5dc0847cf6585384 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 11:10:55 +0200 Subject: [PATCH 36/87] remove staked rewards --- .../frame/staking-async/src/benchmarking.rs | 4 --- .../frame/staking-async/src/pallet/mod.rs | 8 ----- .../frame/staking-async/src/tests/bonding.rs | 10 ------ .../frame/staking-async/src/tests/configs.rs | 5 --- .../staking-async/src/tests/era_rotation.rs | 32 ++----------------- .../staking-async/src/tests/payout_stakers.rs | 1 - 6 files changed, 2 insertions(+), 58 deletions(-) diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 63470d3db7b6e..a7912694d08aa 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -830,7 +830,6 @@ mod benchmarks { ConfigOp::Set(u32::MAX), ConfigOp::Set(Percent::max_value()), ConfigOp::Set(Perbill::max_value()), - ConfigOp::Set(Percent::max_value()), ConfigOp::Set(false), ); @@ -840,7 +839,6 @@ mod benchmarks { assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); - assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); assert_eq!(AreNominatorsSlashable::::get(), false); } @@ -856,7 +854,6 @@ mod benchmarks { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ); assert!(!MinNominatorBond::::exists()); @@ -865,7 +862,6 @@ mod benchmarks { assert!(!MaxValidatorsCount::::exists()); assert!(!ChillThreshold::::exists()); assert!(!MinCommission::::exists()); - assert!(!MaxStakedRewards::::exists()); assert!(!AreNominatorsSlashable::::exists()); } diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 2d8f1f20c0659..e601f21550d95 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -794,12 +794,6 @@ pub mod pallet { #[pallet::storage] pub type ForceEra = StorageValue<_, Forcing, ValueQuery>; - /// Maximum staked rewards, i.e. the percentage of the era inflation that - /// is used for stake rewards. - /// See [Era payout](./index.html#era-payout). - #[pallet::storage] - pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; - /// The percentage of the slash that is distributed to reporters. /// /// The rest of the slashed value is handled by the `Slash`. @@ -2398,7 +2392,6 @@ pub mod pallet { max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, - max_staked_rewards: ConfigOp, are_nominators_slashable: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -2419,7 +2412,6 @@ pub mod pallet { config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); - config_op_exp!(MaxStakedRewards, max_staked_rewards); config_op_exp!(AreNominatorsSlashable, are_nominators_slashable); Ok(()) } diff --git a/substrate/frame/staking-async/src/tests/bonding.rs b/substrate/frame/staking-async/src/tests/bonding.rs index 2b60c0028ec81..9e82fef656f13 100644 --- a/substrate/frame/staking-async/src/tests/bonding.rs +++ b/substrate/frame/staking-async/src/tests/bonding.rs @@ -1692,7 +1692,6 @@ mod staking_bounds_chill_other { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Noop, )); @@ -1716,7 +1715,6 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // Still can't chill these users @@ -1739,7 +1737,6 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // Still can't chill these users @@ -1762,7 +1759,6 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // Still can't chill these users @@ -1784,7 +1780,6 @@ mod staking_bounds_chill_other { ConfigOp::Set(10), ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, ConfigOp::Noop, )); @@ -1808,7 +1803,6 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // Still can't chill these users @@ -1831,7 +1825,6 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // Still can't chill these users @@ -1854,7 +1847,6 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); // 16 people total because tests start with 2 active one @@ -1903,7 +1895,6 @@ mod staking_bounds_chill_other { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, - ConfigOp::Noop, )); // can create `max - validator_count` validators @@ -1975,7 +1966,6 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![11])); assert_ok!(Staking::validate( diff --git a/substrate/frame/staking-async/src/tests/configs.rs b/substrate/frame/staking-async/src/tests/configs.rs index 862b955635203..9281d7b086dc2 100644 --- a/substrate/frame/staking-async/src/tests/configs.rs +++ b/substrate/frame/staking-async/src/tests/configs.rs @@ -29,7 +29,6 @@ fn set_staking_configs_works() { ConfigOp::Set(20), ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Set(Zero::zero()), - ConfigOp::Set(Zero::zero()), ConfigOp::Set(false), )); assert_eq!(MinNominatorBond::::get(), 1_500); @@ -38,7 +37,6 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), Some(20)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); - assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(0))); assert_eq!(AreNominatorsSlashable::::get(), false); // noop does nothing @@ -51,7 +49,6 @@ fn set_staking_configs_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, - ConfigOp::Noop, ))); // removing works @@ -64,7 +61,6 @@ fn set_staking_configs_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, - ConfigOp::Remove, )); assert_eq!(MinNominatorBond::::get(), 0); assert_eq!(MinValidatorBond::::get(), 0); @@ -72,7 +68,6 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), None); assert_eq!(ChillThreshold::::get(), None); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); - assert_eq!(MaxStakedRewards::::get(), None); // AreNominatorsSlashable defaults to true when removed assert_eq!(AreNominatorsSlashable::::get(), true); }); diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 17360033234d1..d468540dc40e5 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -477,14 +477,12 @@ mod inflation { use super::*; #[test] - fn max_staked_rewards_default_not_set_works() { + fn dap_budget_allocation_determines_staker_rewards() { ExtBuilder::default().build_and_execute(|| { - // 50% of time_per_era() (other half goes to buffer as per mock::default_budget() + // 50% of time_per_era() goes to stakers (other half to buffer per mock::default_budget()) let default_stakers_payout = validator_payout_for(time_per_era()); assert_eq!(default_stakers_payout, Balance::from(time_per_era()) / 2); - assert_eq!(>::get(), None); - Session::roll_until_active_era(2); assert_eq!( @@ -498,35 +496,9 @@ mod inflation { ] ); - // the final stakers reward is the same as the reward before applied the cap. assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); }) } - - #[test] - fn max_staked_rewards_default_equal_100() { - ExtBuilder::default().build_and_execute(|| { - let default_stakers_payout = validator_payout_for(time_per_era()); - assert_eq!(default_stakers_payout, Balance::from(time_per_era()) / 2); - >::set(Some(Percent::from_parts(100))); - - Session::roll_until_active_era(2); - - assert_eq!( - staking_events_since_last_call(), - vec![ - Event::SessionRotated { starting_session: 4, active_era: 1, planned_era: 2 }, - Event::PagedElectionProceeded { page: 0, result: Ok(2) }, - Event::SessionRotated { starting_session: 5, active_era: 1, planned_era: 2 }, - Event::EraPaid { era_index: 1, validator_payout: 7500, remainder: 0 }, - Event::SessionRotated { starting_session: 6, active_era: 2, planned_era: 2 } - ] - ); - - // the final stakers reward is the same as the reward before applied the cap. - assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); - }); - } } #[test] diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index b739075085c84..c37e5f27a37ac 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -588,7 +588,6 @@ fn min_commission_works() { ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), ConfigOp::Noop, - ConfigOp::Noop, )); // can't make it less than 10 now From d6e23b8ed70978a9b186ce4d89b1762952a14ceb Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 11:45:14 +0200 Subject: [PATCH 37/87] feedback --- substrate/frame/staking-async/src/session_rotation.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index b1d396aecebed..3403c54308949 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -743,13 +743,9 @@ impl Rotator { // cleanup election state EraElectionPlanner::::cleanup(); - // Cleanup era pot accounts for eras that have expired. - if let Some(era_to_cleanup) = starting_era.checked_sub(T::HistoryDepth::get() + 1) { - reward::EraRewardManager::::cleanup_era(era_to_cleanup); - } - - // Mark ancient era for lazy pruning instead of immediately pruning it. + // Cleanup era pot accounts and mark for lazy pruning. if let Some(old_era) = starting_era.checked_sub(T::HistoryDepth::get() + 1) { + reward::EraRewardManager::::cleanup_era(old_era); log!(debug, "Marking era {:?} for lazy pruning", old_era); EraPruningState::::insert(old_era, PruningStep::ErasStakersPaged); } From aa1377178a21f4571aa19c5eca717e4a47a41352 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 11:46:20 +0200 Subject: [PATCH 38/87] fmt --- substrate/frame/staking-async/src/pallet/impls.rs | 5 ++--- substrate/frame/staking-async/src/tests/era_rotation.rs | 3 ++- substrate/frame/staking-async/src/tests/payout_stakers.rs | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 15b54877cae74..345b5c284429f 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -409,9 +409,8 @@ impl Pallet { // Use the overview's own-stake (not the page's, which is zeroed on pages > 0) // so the calculator sees the full validator self-stake for reward computation. - let overview_own = ErasStakersOverview::::get(era, &stash) - .map(|o| o.own) - .unwrap_or_default(); + let overview_own = + ErasStakersOverview::::get(era, &stash).map(|o| o.own).unwrap_or_default(); let reward_split = T::StakerRewardCalculator::calculate_staker_reward( validator_total_payout, diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index d468540dc40e5..9e523aa25404a 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -479,7 +479,8 @@ mod inflation { #[test] fn dap_budget_allocation_determines_staker_rewards() { ExtBuilder::default().build_and_execute(|| { - // 50% of time_per_era() goes to stakers (other half to buffer per mock::default_budget()) + // 50% of time_per_era() goes to stakers (other half to buffer per + // mock::default_budget()) let default_stakers_payout = validator_payout_for(time_per_era()); assert_eq!(default_stakers_payout, Balance::from(time_per_era()) / 2); diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index c37e5f27a37ac..68153b958c7d8 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -1507,8 +1507,7 @@ fn test_commission_paid_across_pages() { balance + (0..200).map(|i| balance + i as Balance).sum::(); let commission_reward = Perbill::from_percent(commission).mul_floor(payout); let leftover = payout.saturating_sub(commission_reward); - let own_stake_reward = - Perbill::from_rational(balance, total_exposure).mul_floor(leftover); + let own_stake_reward = Perbill::from_rational(balance, total_exposure).mul_floor(leftover); let expected_total = commission_reward + own_stake_reward; let actual_total = asset::stakeable_balance::(&11) - initial_balance; // Allow error of 1 per page due to floor rounding across 4 pages. From ff0de15103fcaddcc2bc6cfe7582176d124d5e2f Mon Sep 17 00:00:00 2001 From: Paolo La Camera Date: Fri, 3 Apr 2026 14:49:09 +0200 Subject: [PATCH 39/87] fix benchmarking compilation after removal of max_staked_rewards --- substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs | 1 - substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs | 1 - substrate/frame/staking-async/src/benchmarking.rs | 1 - 3 files changed, 3 deletions(-) diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index 231ca451a6f05..b8faf02ccabe8 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -263,7 +263,6 @@ fn pool_chill_e2e() { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, - pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index 0d2b0b102fcb9..f82ebedefdebf 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -369,7 +369,6 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, - pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index a7912694d08aa..3f8c575763798 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -887,7 +887,6 @@ mod benchmarks { ConfigOp::Set(Percent::from_percent(0)), ConfigOp::Set(Zero::zero()), ConfigOp::Noop, - ConfigOp::Noop, )?; let caller = whitelisted_caller(); From 4dfa78ce73491752c2a08b98211bc86988c1be9f Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 13:29:08 +0000 Subject: [PATCH 40/87] Update from github-actions[bot] running command 'bench --pallet pallet_dap --runtime asset-hub-westend' --- .../asset-hub-westend/src/weights/pallet_dap.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs index 04a0da2787bde..0094de4147c46 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_dap.rs @@ -16,9 +16,9 @@ //! Autogenerated weights for `pallet_dap` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2026-04-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-04-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `203c6848e0f8`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `5851f50484f3`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -56,8 +56,8 @@ impl pallet_dap::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 8_333_000 picoseconds. - Weight::from_parts(9_027_000, 0) + // Minimum execution time: 7_979_000 picoseconds. + Weight::from_parts(8_699_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -73,8 +73,8 @@ impl pallet_dap::WeightInfo for WeightInfo { // Proof Size summary in bytes: // Measured: `2541` // Estimated: `6196` - // Minimum execution time: 63_691_000 picoseconds. - Weight::from_parts(66_425_000, 0) + // Minimum execution time: 62_672_000 picoseconds. + Weight::from_parts(65_724_000, 0) .saturating_add(Weight::from_parts(0, 6196)) .saturating_add(T::DbWeight::get().reads(5)) .saturating_add(T::DbWeight::get().writes(3)) From fd11b5567671f1bca7e1258cef626933b6b73468 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 3 Apr 2026 16:09:55 +0200 Subject: [PATCH 41/87] dap in kitchensink --- substrate/bin/node/runtime/src/lib.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs index c894e68e5da97..f80f550747349 100644 --- a/substrate/bin/node/runtime/src/lib.rs +++ b/substrate/bin/node/runtime/src/lib.rs @@ -772,6 +772,24 @@ impl pallet_staking::Config for Runtime { type MaxValidatorSet = ConstU32<1000>; } +parameter_types! { + pub const DapPalletId: PalletId = PalletId(*b"dap/buff"); + pub const DapIssuanceCadence: u64 = 0; // drip every block + pub const DapMaxElapsedPerDrip: u64 = 600_000; +} + +impl pallet_dap::Config for Runtime { + type Currency = Balances; + type PalletId = DapPalletId; + type IssuanceCurve = (); + type BudgetRecipients = (pallet_dap::Pallet,); + type Time = Timestamp; + type IssuanceCadence = DapIssuanceCadence; + type MaxElapsedPerDrip = DapMaxElapsedPerDrip; + type BudgetOrigin = EnsureRoot; + type WeightInfo = (); +} + impl pallet_fast_unstake::Config for Runtime { type RuntimeEvent = RuntimeEvent; type ControlOrigin = frame_system::EnsureRoot; @@ -2905,6 +2923,9 @@ mod runtime { #[runtime::pallet_index(93)] pub type VestingPrecompiles = pallet_vesting_precompiles::pallet::Pallet; + + #[runtime::pallet_index(94)] + pub type Dap = pallet_dap::Pallet; } /// The address format for describing accounts. @@ -3200,6 +3221,7 @@ mod benches { [pallet_glutton, Glutton] [pallet_session, SessionBench::] [pallet_society, Society] + [pallet_dap, Dap] [pallet_staking, Staking] [pallet_state_trie_migration, StateTrieMigration] [pallet_sudo, Sudo] From f0ca2a0fcff58b617403299cd99639b076587f5f Mon Sep 17 00:00:00 2001 From: "cmd[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 3 Apr 2026 15:05:39 +0000 Subject: [PATCH 42/87] Update from github-actions[bot] running command 'bench --pallet pallet_staking_async --runtime asset-hub-westend' --- .../src/weights/pallet_staking_async.rs | 253 +++++++++--------- 1 file changed, 128 insertions(+), 125 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs index 25085db75101e..1e6a9ce0397b1 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs @@ -16,9 +16,9 @@ //! Autogenerated weights for `pallet_staking_async` //! //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0 -//! DATE: 2026-04-02, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` +//! DATE: 2026-04-03, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]` //! WORST CASE MAP SIZE: `1000000` -//! HOSTNAME: `21ba8909b2ce`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` +//! HOSTNAME: `b52a53e3525a`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz` //! WASM-EXECUTION: `Compiled`, CHAIN: `None`, DB CACHE: 1024 // Executed Command: @@ -68,8 +68,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `6793` // Estimated: `4218` - // Minimum execution time: 108_311_000 picoseconds. - Weight::from_parts(114_482_000, 0) + // Minimum execution time: 111_792_000 picoseconds. + Weight::from_parts(117_911_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(4)) @@ -96,8 +96,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8254` // Estimated: `8877` - // Minimum execution time: 304_196_000 picoseconds. - Weight::from_parts(313_138_000, 0) + // Minimum execution time: 289_464_000 picoseconds. + Weight::from_parts(302_643_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(7)) @@ -130,8 +130,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8415` // Estimated: `8877` - // Minimum execution time: 268_765_000 picoseconds. - Weight::from_parts(291_784_000, 0) + // Minimum execution time: 260_669_000 picoseconds. + Weight::from_parts(271_525_000, 0) .saturating_add(Weight::from_parts(0, 8877)) .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().writes(6)) @@ -162,8 +162,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7393` // Estimated: `6696` - // Minimum execution time: 126_325_000 picoseconds. - Weight::from_parts(132_812_000, 0) + // Minimum execution time: 125_250_000 picoseconds. + Weight::from_parts(131_726_000, 0) .saturating_add(Weight::from_parts(0, 6696)) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().writes(2)) @@ -208,8 +208,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8340` // Estimated: `6696` - // Minimum execution time: 307_837_000 picoseconds. - Weight::from_parts(324_204_000, 0) + // Minimum execution time: 286_349_000 picoseconds. + Weight::from_parts(304_400_000, 0) .saturating_add(Weight::from_parts(0, 6696)) .saturating_add(T::DbWeight::get().reads(17)) .saturating_add(T::DbWeight::get().writes(12)) @@ -244,8 +244,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `9885` // Estimated: `4218` - // Minimum execution time: 106_705_000 picoseconds. - Weight::from_parts(115_524_000, 0) + // Minimum execution time: 107_867_000 picoseconds. + Weight::from_parts(115_013_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(5)) @@ -261,11 +261,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `12187 + k * (1121 ±0)` // Estimated: `4218 + k * (3033 ±0)` - // Minimum execution time: 76_949_000 picoseconds. - Weight::from_parts(77_003_657, 0) + // Minimum execution time: 75_863_000 picoseconds. + Weight::from_parts(72_697_313, 0) .saturating_add(Weight::from_parts(0, 4218)) - // Standard Error: 24_064 - .saturating_add(Weight::from_parts(15_507_941, 0).saturating_mul(k.into())) + // Standard Error: 22_917 + .saturating_add(Weight::from_parts(13_826_849, 0).saturating_mul(k.into())) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(k.into()))) .saturating_add(T::DbWeight::get().writes((1_u64).saturating_mul(k.into()))) @@ -300,11 +300,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5074 + n * (68 ±0)` // Estimated: `6248 + n * (2520 ±0)` - // Minimum execution time: 179_370_000 picoseconds. - Weight::from_parts(200_153_114, 0) + // Minimum execution time: 175_956_000 picoseconds. + Weight::from_parts(190_115_620, 0) .saturating_add(Weight::from_parts(0, 6248)) - // Standard Error: 68_192 - .saturating_add(Weight::from_parts(6_393_807, 0).saturating_mul(n.into())) + // Standard Error: 47_853 + .saturating_add(Weight::from_parts(6_333_479, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().reads((1_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -332,8 +332,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5217` // Estimated: `6248` - // Minimum execution time: 172_200_000 picoseconds. - Weight::from_parts(183_200_000, 0) + // Minimum execution time: 168_220_000 picoseconds. + Weight::from_parts(177_865_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(10)) .saturating_add(T::DbWeight::get().writes(6)) @@ -348,8 +348,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `4040` // Estimated: `4218` - // Minimum execution time: 36_154_000 picoseconds. - Weight::from_parts(38_696_000, 0) + // Minimum execution time: 37_165_000 picoseconds. + Weight::from_parts(39_651_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(1)) @@ -364,8 +364,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5712` // Estimated: `4218` - // Minimum execution time: 45_224_000 picoseconds. - Weight::from_parts(48_581_000, 0) + // Minimum execution time: 48_333_000 picoseconds. + Weight::from_parts(51_339_000, 0) .saturating_add(Weight::from_parts(0, 4218)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -378,8 +378,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5383` // Estimated: `7446` - // Minimum execution time: 47_104_000 picoseconds. - Weight::from_parts(50_114_000, 0) + // Minimum execution time: 47_842_000 picoseconds. + Weight::from_parts(50_824_000, 0) .saturating_add(Weight::from_parts(0, 7446)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(3)) @@ -390,8 +390,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 2_580_000 picoseconds. - Weight::from_parts(2_978_000, 0) + // Minimum execution time: 2_587_000 picoseconds. + Weight::from_parts(3_019_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -401,8 +401,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_315_000 picoseconds. - Weight::from_parts(15_877_000, 0) + // Minimum execution time: 12_127_000 picoseconds. + Weight::from_parts(15_833_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -412,8 +412,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 12_296_000 picoseconds. - Weight::from_parts(15_742_000, 0) + // Minimum execution time: 12_160_000 picoseconds. + Weight::from_parts(15_898_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -423,8 +423,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 11_975_000 picoseconds. - Weight::from_parts(15_723_000, 0) + // Minimum execution time: 12_209_000 picoseconds. + Weight::from_parts(15_859_000, 0) .saturating_add(Weight::from_parts(0, 0)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -439,11 +439,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `195432 + u * (1105 ±0)` // Estimated: `990 + u * (6456 ±0)` - // Minimum execution time: 5_643_000 picoseconds. - Weight::from_parts(62_673_802, 0) + // Minimum execution time: 5_872_000 picoseconds. + Weight::from_parts(6_028_000, 0) .saturating_add(Weight::from_parts(0, 990)) - // Standard Error: 52_849 - .saturating_add(Weight::from_parts(48_322_003, 0).saturating_mul(u.into())) + // Standard Error: 27_620 + .saturating_add(Weight::from_parts(46_723_558, 0).saturating_mul(u.into())) .saturating_add(T::DbWeight::get().reads((4_u64).saturating_mul(u.into()))) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(u.into()))) .saturating_add(Weight::from_parts(0, 6456).saturating_mul(u.into())) @@ -480,8 +480,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8285` // Estimated: `6248` - // Minimum execution time: 277_917_000 picoseconds. - Weight::from_parts(289_429_000, 0) + // Minimum execution time: 270_308_000 picoseconds. + Weight::from_parts(276_327_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(13)) @@ -493,11 +493,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `633` // Estimated: `39479` - // Minimum execution time: 15_308_000 picoseconds. - Weight::from_parts(16_065_000, 0) + // Minimum execution time: 15_468_000 picoseconds. + Weight::from_parts(16_053_000, 0) .saturating_add(Weight::from_parts(0, 39479)) - // Standard Error: 6_090 - .saturating_add(Weight::from_parts(2_949_210, 0).saturating_mul(s.into())) + // Standard Error: 6_086 + .saturating_add(Weight::from_parts(2_893_844, 0).saturating_mul(s.into())) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } @@ -513,7 +513,7 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) /// Storage: `Staking::Ledger` (r:65 w:65) /// Proof: `Staking::Ledger` (`max_values`: None, `max_size`: Some(753), added: 3228, mode: `MaxEncodedLen`) - /// Storage: `System::Account` (r:66 w:65) + /// Storage: `System::Account` (r:66 w:66) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:65 w:0) /// Proof: `Staking::VirtualStakers` (`max_values`: None, `max_size`: Some(40), added: 2515, mode: `MaxEncodedLen`) @@ -525,23 +525,21 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::ErasRewardPoints` (`max_values`: None, `max_size`: Some(36018), added: 38493, mode: `MaxEncodedLen`) /// Storage: `Staking::ErasValidatorPrefs` (r:1 w:0) /// Proof: `Staking::ErasValidatorPrefs` (`max_values`: None, `max_size`: Some(57), added: 2532, mode: `MaxEncodedLen`) - /// Storage: `Staking::DisableLegacyMintingEra` (r:1 w:0) - /// Proof: `Staking::DisableLegacyMintingEra` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) /// Storage: `Staking::Payee` (r:65 w:0) /// Proof: `Staking::Payee` (`max_values`: None, `max_size`: Some(73), added: 2548, mode: `MaxEncodedLen`) /// The range of component `n` is `[0, 64]`. fn payout_stakers_alive_staked(n: u32, ) -> Weight { // Proof Size summary in bytes: - // Measured: `33593 + n * (3657 ±0)` + // Measured: `34212 + n * (3663 ±0)` // Estimated: `39483 + n * (3228 ±0)` - // Minimum execution time: 354_364_000 picoseconds. - Weight::from_parts(612_905_812, 0) + // Minimum execution time: 349_810_000 picoseconds. + Weight::from_parts(609_930_207, 0) .saturating_add(Weight::from_parts(0, 39483)) - // Standard Error: 228_202 - .saturating_add(Weight::from_parts(134_482_591, 0).saturating_mul(n.into())) + // Standard Error: 201_387 + .saturating_add(Weight::from_parts(147_405_307, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(15)) .saturating_add(T::DbWeight::get().reads((6_u64).saturating_mul(n.into()))) - .saturating_add(T::DbWeight::get().writes(4)) + .saturating_add(T::DbWeight::get().writes(6)) .saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(n.into()))) .saturating_add(Weight::from_parts(0, 3228).saturating_mul(n.into())) } @@ -568,11 +566,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8255 + l * (5 ±0)` // Estimated: `8877` - // Minimum execution time: 244_945_000 picoseconds. - Weight::from_parts(270_161_493, 0) + // Minimum execution time: 234_794_000 picoseconds. + Weight::from_parts(257_470_852, 0) .saturating_add(Weight::from_parts(0, 8877)) - // Standard Error: 19_476 - .saturating_add(Weight::from_parts(75_895, 0).saturating_mul(l.into())) + // Standard Error: 26_921 + .saturating_add(Weight::from_parts(241_972, 0).saturating_mul(l.into())) .saturating_add(T::DbWeight::get().reads(12)) .saturating_add(T::DbWeight::get().writes(6)) } @@ -610,8 +608,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8348` // Estimated: `6248` - // Minimum execution time: 300_149_000 picoseconds. - Weight::from_parts(320_107_000, 0) + // Minimum execution time: 290_743_000 picoseconds. + Weight::from_parts(305_342_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(14)) .saturating_add(T::DbWeight::get().writes(12)) @@ -624,8 +622,6 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) - /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -636,10 +632,10 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 5_100_000 picoseconds. - Weight::from_parts(5_716_000, 0) + // Minimum execution time: 4_488_000 picoseconds. + Weight::from_parts(5_079_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(8)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::AreNominatorsSlashable` (r:0 w:1) /// Proof: `Staking::AreNominatorsSlashable` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) @@ -649,8 +645,6 @@ impl pallet_staking_async::WeightInfo for WeightInfo /// Proof: `Staking::MinValidatorBond` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxValidatorsCount` (r:0 w:1) /// Proof: `Staking::MaxValidatorsCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) - /// Storage: `Staking::MaxStakedRewards` (r:0 w:1) - /// Proof: `Staking::MaxStakedRewards` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::ChillThreshold` (r:0 w:1) /// Proof: `Staking::ChillThreshold` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`) /// Storage: `Staking::MaxNominatorsCount` (r:0 w:1) @@ -661,10 +655,10 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `0` // Estimated: `0` - // Minimum execution time: 4_637_000 picoseconds. - Weight::from_parts(5_212_000, 0) + // Minimum execution time: 4_258_000 picoseconds. + Weight::from_parts(4_762_000, 0) .saturating_add(Weight::from_parts(0, 0)) - .saturating_add(T::DbWeight::get().writes(8)) + .saturating_add(T::DbWeight::get().writes(7)) } /// Storage: `Staking::Bonded` (r:1 w:0) /// Proof: `Staking::Bonded` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`) @@ -694,8 +688,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5350` // Estimated: `6248` - // Minimum execution time: 207_112_000 picoseconds. - Weight::from_parts(218_947_000, 0) + // Minimum execution time: 200_847_000 picoseconds. + Weight::from_parts(216_770_000, 0) .saturating_add(Weight::from_parts(0, 6248)) .saturating_add(T::DbWeight::get().reads(13)) .saturating_add(T::DbWeight::get().writes(6)) @@ -710,8 +704,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `640` // Estimated: `3510` - // Minimum execution time: 35_314_000 picoseconds. - Weight::from_parts(38_212_000, 0) + // Minimum execution time: 36_882_000 picoseconds. + Weight::from_parts(38_356_000, 0) .saturating_add(Weight::from_parts(0, 3510)) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().writes(1)) @@ -724,17 +718,26 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `596` // Estimated: `1489` - // Minimum execution time: 7_875_000 picoseconds. - Weight::from_parts(8_702_000, 0) + // Minimum execution time: 8_080_000 picoseconds. + Weight::from_parts(8_876_000, 0) .saturating_add(Weight::from_parts(0, 1489)) .saturating_add(T::DbWeight::get().reads(1)) .saturating_add(T::DbWeight::get().writes(1)) } - + /// Storage: `Staking::MinCommission` (r:1 w:0) + /// Proof: `Staking::MinCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) + /// Storage: `Staking::MaxCommission` (r:0 w:1) + /// Proof: `Staking::MaxCommission` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`) fn set_max_commission() -> Weight { - todo!() + // Proof Size summary in bytes: + // Measured: `596` + // Estimated: `1489` + // Minimum execution time: 8_042_000 picoseconds. + Weight::from_parts(8_839_000, 0) + .saturating_add(Weight::from_parts(0, 1489)) + .saturating_add(T::DbWeight::get().reads(1)) + .saturating_add(T::DbWeight::get().writes(1)) } - /// Storage: `System::Account` (r:1 w:0) /// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`) /// Storage: `Staking::VirtualStakers` (r:1 w:0) @@ -751,8 +754,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `6948` // Estimated: `4764` - // Minimum execution time: 74_254_000 picoseconds. - Weight::from_parts(78_808_000, 0) + // Minimum execution time: 74_616_000 picoseconds. + Weight::from_parts(78_673_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -773,8 +776,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7019` // Estimated: `4764` - // Minimum execution time: 119_374_000 picoseconds. - Weight::from_parts(124_774_000, 0) + // Minimum execution time: 119_682_000 picoseconds. + Weight::from_parts(124_754_000, 0) .saturating_add(Weight::from_parts(0, 4764)) .saturating_add(T::DbWeight::get().reads(6)) .saturating_add(T::DbWeight::get().writes(2)) @@ -804,11 +807,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `27526 + n * (2979 ±0)` // Estimated: `39479 + n * (3228 ±0)` - // Minimum execution time: 253_956_000 picoseconds. - Weight::from_parts(454_619_406, 0) + // Minimum execution time: 243_689_000 picoseconds. + Weight::from_parts(449_629_036, 0) .saturating_add(Weight::from_parts(0, 39479)) - // Standard Error: 225_245 - .saturating_add(Weight::from_parts(93_395_321, 0).saturating_mul(n.into())) + // Standard Error: 205_087 + .saturating_add(Weight::from_parts(88_523_865, 0).saturating_mul(n.into())) .saturating_add(T::DbWeight::get().reads(11)) .saturating_add(T::DbWeight::get().reads((7_u64).saturating_mul(n.into()))) .saturating_add(T::DbWeight::get().writes(6)) @@ -833,8 +836,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `3602` // Estimated: `6617` - // Minimum execution time: 162_751_000 picoseconds. - Weight::from_parts(170_892_000, 0) + // Minimum execution time: 158_793_000 picoseconds. + Weight::from_parts(165_595_000, 0) .saturating_add(Weight::from_parts(0, 6617)) .saturating_add(T::DbWeight::get().reads(7)) .saturating_add(T::DbWeight::get().writes(4)) @@ -856,11 +859,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `667 + v * (96 ±0)` // Estimated: `1534 + v * (2576 ±0)` - // Minimum execution time: 94_505_000 picoseconds. - Weight::from_parts(18_215_037, 0) + // Minimum execution time: 94_212_000 picoseconds. + Weight::from_parts(19_538_154, 0) .saturating_add(Weight::from_parts(0, 1534)) - // Standard Error: 18_412 - .saturating_add(Weight::from_parts(19_585_018, 0).saturating_mul(v.into())) + // Standard Error: 18_481 + .saturating_add(Weight::from_parts(19_650_187, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(3)) .saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(v.into()))) .saturating_add(T::DbWeight::get().writes(1)) @@ -897,8 +900,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `2315` // Estimated: `39483` - // Minimum execution time: 493_884_000 picoseconds. - Weight::from_parts(513_624_000, 0) + // Minimum execution time: 488_096_000 picoseconds. + Weight::from_parts(510_203_000, 0) .saturating_add(Weight::from_parts(0, 39483)) .saturating_add(T::DbWeight::get().reads(8)) .saturating_add(T::DbWeight::get().writes(11)) @@ -914,11 +917,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `7912 + v * (3 ±0)` // Estimated: `258665 + v * (4 ±0)` - // Minimum execution time: 163_132_000 picoseconds. - Weight::from_parts(188_486_108, 0) + // Minimum execution time: 160_819_000 picoseconds. + Weight::from_parts(183_089_980, 0) .saturating_add(Weight::from_parts(0, 258665)) - // Standard Error: 1_149 - .saturating_add(Weight::from_parts(54_377, 0).saturating_mul(v.into())) + // Standard Error: 1_298 + .saturating_add(Weight::from_parts(47_649, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(103)) .saturating_add(T::DbWeight::get().writes(100)) .saturating_add(Weight::from_parts(0, 4).saturating_mul(v.into())) @@ -933,12 +936,12 @@ impl pallet_staking_async::WeightInfo for WeightInfo fn prune_era_stakers_overview(v: u32, ) -> Weight { // Proof Size summary in bytes: // Measured: `7425` - // Estimated: `202015 + v * (83 ±21)` - // Minimum execution time: 39_146_000 picoseconds. - Weight::from_parts(154_515_581, 0) + // Estimated: `202015 + v * (83 ±0)` + // Minimum execution time: 36_678_000 picoseconds. + Weight::from_parts(149_440_271, 0) .saturating_add(Weight::from_parts(0, 202015)) - // Standard Error: 3_939 - .saturating_add(Weight::from_parts(93_568, 0).saturating_mul(v.into())) + // Standard Error: 3_880 + .saturating_add(Weight::from_parts(89_300, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 83).saturating_mul(v.into())) @@ -954,11 +957,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5865` // Estimated: `200811 + v * (83 ±21)` - // Minimum execution time: 40_570_000 picoseconds. - Weight::from_parts(148_375_299, 0) + // Minimum execution time: 37_157_000 picoseconds. + Weight::from_parts(144_946_839, 0) .saturating_add(Weight::from_parts(0, 200811)) - // Standard Error: 3_742 - .saturating_add(Weight::from_parts(103_890, 0).saturating_mul(v.into())) + // Standard Error: 3_684 + .saturating_add(Weight::from_parts(95_902, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 83).saturating_mul(v.into())) @@ -974,11 +977,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `8873` // Estimated: `203149 + v * (84 ±0)` - // Minimum execution time: 40_285_000 picoseconds. - Weight::from_parts(151_088_206, 0) + // Minimum execution time: 37_898_000 picoseconds. + Weight::from_parts(149_425_614, 0) .saturating_add(Weight::from_parts(0, 203149)) - // Standard Error: 3_736 - .saturating_add(Weight::from_parts(106_061, 0).saturating_mul(v.into())) + // Standard Error: 3_851 + .saturating_add(Weight::from_parts(92_645, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(81)) .saturating_add(T::DbWeight::get().writes(79)) .saturating_add(Weight::from_parts(0, 84).saturating_mul(v.into())) @@ -993,8 +996,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 29_977_000 picoseconds. - Weight::from_parts(33_405_000, 0) + // Minimum execution time: 28_829_000 picoseconds. + Weight::from_parts(31_103_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -1009,8 +1012,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 30_422_000 picoseconds. - Weight::from_parts(33_658_000, 0) + // Minimum execution time: 29_373_000 picoseconds. + Weight::from_parts(31_149_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(2)) @@ -1027,8 +1030,8 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `789` // Estimated: `4254` - // Minimum execution time: 31_969_000 picoseconds. - Weight::from_parts(35_119_000, 0) + // Minimum execution time: 30_788_000 picoseconds. + Weight::from_parts(33_148_000, 0) .saturating_add(Weight::from_parts(0, 4254)) .saturating_add(T::DbWeight::get().reads(2)) .saturating_add(T::DbWeight::get().writes(3)) @@ -1044,11 +1047,11 @@ impl pallet_staking_async::WeightInfo for WeightInfo // Proof Size summary in bytes: // Measured: `5499 + v * (3 ±0)` // Estimated: `132078 + v * (177 ±0)` - // Minimum execution time: 43_443_000 picoseconds. - Weight::from_parts(115_249_407, 0) + // Minimum execution time: 41_382_000 picoseconds. + Weight::from_parts(110_764_421, 0) .saturating_add(Weight::from_parts(0, 132078)) - // Standard Error: 3_964 - .saturating_add(Weight::from_parts(139_417, 0).saturating_mul(v.into())) + // Standard Error: 4_225 + .saturating_add(Weight::from_parts(134_298, 0).saturating_mul(v.into())) .saturating_add(T::DbWeight::get().reads(49)) .saturating_add(T::DbWeight::get().writes(48)) .saturating_add(Weight::from_parts(0, 177).saturating_mul(v.into())) From 24564435e4822e03bda36e6bc43ab87baa6729a2 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 11:09:26 +0200 Subject: [PATCH 43/87] kusama compat --- .../assets/asset-hub-westend/src/staking.rs | 4 + .../test-delegate-stake/src/lib.rs | 1 + .../test-delegate-stake/src/mock.rs | 5 +- .../integration-tests/src/ah/mock.rs | 4 + .../runtimes/parachain/src/staking.rs | 4 + .../frame/staking-async/src/benchmarking.rs | 6 +- substrate/frame/staking-async/src/lib.rs | 35 ++++- substrate/frame/staking-async/src/mock.rs | 89 +++++++++--- .../frame/staking-async/src/pallet/impls.rs | 2 +- .../frame/staking-async/src/pallet/mod.rs | 82 ++++++++--- .../staking-async/src/session_rotation.rs | 67 +++++++-- .../frame/staking-async/src/tests/bonding.rs | 10 ++ .../frame/staking-async/src/tests/configs.rs | 5 + .../staking-async/src/tests/era_rotation.rs | 23 +-- .../staking-async/src/tests/legacy_reward.rs | 135 ++++++++++++++++++ .../frame/staking-async/src/tests/mod.rs | 1 + .../staking-async/src/tests/payout_stakers.rs | 1 + 17 files changed, 407 insertions(+), 67 deletions(-) create mode 100644 substrate/frame/staking-async/src/tests/legacy_reward.rs diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 5fd7338c8c8cf..598a07cfcc1f4 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -277,9 +277,13 @@ impl pallet_staking_async::Config for Runtime { type CurrencyBalance = Balance; type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; + type RewardRemainder = (); type Slash = Dap; type UnclaimedRewardHandler = Dap; type Reward = (); + type EraPayout = (); + type MaxEraDuration = (); + type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index b8faf02ccabe8..9260d37700226 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -263,6 +263,7 @@ fn pool_chill_e2e() { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, + pallet_staking_async::ConfigOp::Noop, // max_staked_rewards pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index f82ebedefdebf..aa0816d303b8d 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_ok, derive_impl, pallet_prelude::*, parameter_types, - traits::{ConstU64, ConstU8, Nothing, VariantCountOf}, + traits::{ConstBool, ConstU64, ConstU8, Nothing, VariantCountOf}, PalletId, }; use frame_system::EnsureRoot; @@ -112,6 +112,8 @@ impl pallet_staking_async::Config for Runtime { type OldCurrency = Balances; type Currency = Balances; type AdminOrigin = frame_system::EnsureRoot; + type EraPayout = (); + type DisableMinting = ConstBool; type BondingDuration = BondingDuration; type GeneralPots = pallet_staking_async::SequentialTest; type EraPots = pallet_staking_async::SequentialTest; @@ -369,6 +371,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, + pallet_staking_async::ConfigOp::Noop, // max_staked_rewards pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index 549de581a1b67..b0819f43a3778 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -457,6 +457,10 @@ impl pallet_staking_async::Config for Runtime { type ElectionProvider = MultiBlock; + type RewardRemainder = (); + type EraPayout = (); + type MaxEraDuration = (); + type DisableMinting = ConstBool; type UnclaimedRewardHandler = (); type GeneralPots = pallet_staking_async::SequentialTest; type EraPots = pallet_staking_async::SequentialTest; diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index 7fa4ed296c018..ad7a6b8aa79ec 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -432,8 +432,12 @@ impl pallet_staking_async::Config for Runtime { type CurrencyBalance = Balance; type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; + type RewardRemainder = (); type Slash = Dap; type Reward = (); + type EraPayout = (); + type MaxEraDuration = (); + type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 3f8c575763798..78a0c76680e7d 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -61,7 +61,7 @@ pub(crate) fn create_validator_with_nominators( clear_validators_and_nominators::(); // Disable legacy minting so benchmarks always exercise the reward-pot path. - DisableLegacyMintingEra::::put(0); + DisableMintingGuard::::put(0); let mut points_total = 0; let mut points_individual = Vec::new(); @@ -830,6 +830,7 @@ mod benchmarks { ConfigOp::Set(u32::MAX), ConfigOp::Set(Percent::max_value()), ConfigOp::Set(Perbill::max_value()), + ConfigOp::Set(Percent::max_value()), ConfigOp::Set(false), ); @@ -839,6 +840,7 @@ mod benchmarks { assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(100))); assert_eq!(AreNominatorsSlashable::::get(), false); } @@ -854,6 +856,7 @@ mod benchmarks { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, ); assert!(!MinNominatorBond::::exists()); @@ -887,6 +890,7 @@ mod benchmarks { ConfigOp::Set(Percent::from_percent(0)), ConfigOp::Set(Zero::zero()), ConfigOp::Noop, + ConfigOp::Noop, )?; let caller = whitelisted_caller(); diff --git a/substrate/frame/staking-async/src/lib.rs b/substrate/frame/staking-async/src/lib.rs index 6dd5894c5554c..1e5423548d8d6 100644 --- a/substrate/frame/staking-async/src/lib.rs +++ b/substrate/frame/staking-async/src/lib.rs @@ -29,19 +29,40 @@ //! While `pallet-staking` was somewhat general-purpose, this pallet is absolutely NOT right from //! the get-go: It is designed to be used ONLY in Polkadot/Kusama AssetHub system parachains. //! -//! The workings of this pallet can be divided into a number of subsystems, as follows. +//! ## Reward and Inflation //! -//! ## User Interactions +//! This pallet supports two reward modes, controlled by [`Config::DisableMinting`]: //! -//! TODO +//! ### Non-minting mode (`DisableMinting = true`) //! -//! ## Session and Era Rotation +//! Staking does **not** mint tokens. It expects an external source (e.g. `pallet-dap`) to +//! fund the general staker reward pot ([`GeneralPotAccountProvider`]). At each era boundary, +//! staking snapshots the accumulated balance into an era-specific pot via +//! [`EraRewardManager`](reward::EraRewardManager). Payouts transfer from the era pot. //! -//! TODO +//! Unclaimed rewards from expired eras (past `HistoryDepth`) are withdrawn and passed to +//! [`Config::UnclaimedRewardHandler`]. //! -//! ## Exposure Collection +//! [`DisableMintingGuard`] is set on the first successful snapshot as a safety net — it +//! prevents the payout side from falling back to legacy minting for eras that should have +//! reward pots. //! -//! TODO +//! ### Legacy minting mode (`DisableMinting = false`) +//! +//! At era boundary, [`Config::EraPayout`] computes inflation based on `total_staked`, +//! `total_issuance`, and era duration. Tokens are minted on-the-fly during `payout_stakers`. +//! The treasury remainder is sent to [`Config::RewardRemainder`]. [`MaxStakedRewards`] can +//! cap the staker portion. [`Config::MaxEraDuration`] caps the effective era duration. +//! +//! This mode is kept for Kusama/non-polkadot runtime's compatibility where inflation depends on the +//! staking ratio. +//! +//! ### Switching modes +//! +//! Switching from legacy to non-minting is a one-way migration. Once `DisableMinting` is +//! set to `true`, it must **never** be switched back — eras created in non-minting mode +//! have funded reward pots, and switching to legacy would orphan those pots and cause +//! double-minting. //! //! ## Slashing Pipeline and Withdrawal Restrictions //! diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index fa006b820e8bb..72693587ed4e5 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -399,7 +399,10 @@ ord_parameter_types! { } parameter_types! { + pub static RewardRemainderUnbalanced: u128 = 0; + pub static UseLegacyEraPayout: bool = false; pub static RemainderRatio: Perbill = Perbill::from_percent(50); + pub static MaxEraDuration: u64 = 0; pub const MaxPruningItems: u32 = 100; pub const DapPalletId: frame_support::PalletId = frame_support::PalletId(*b"dap/buff"); pub const TestIssuanceCadence: u64 = 0; // drip every block @@ -415,6 +418,46 @@ impl frame_support::traits::Time for MockTime { } } +pub struct RewardRemainderMock; +impl OnUnbalanced> for RewardRemainderMock { + fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { + use frame_support::traits::tokens::imbalance::Imbalance; + RewardRemainderUnbalanced::mutate(|v| { + *v += amount.peek(); + }); + drop(amount); + } +} + +/// Switchable EraPayout: returns (0,0) in DAP mode, real values in legacy mode. +pub struct TestEraPayout; +impl EraPayout for TestEraPayout { + fn era_payout( + _total_staked: Balance, + _total_issuance: Balance, + era_duration_millis: u64, + ) -> (Balance, Balance) { + if !UseLegacyEraPayout::get() { + // DAP mode: return (0, 0) + (0, 0) + } else { + // Legacy mode: same as old OneTokenPerMillisecond + RemainderRatio + let total = era_duration_millis as Balance; + let remainder = RemainderRatio::get() * total; + let stakers = total - remainder; + (stakers, remainder) + } + } +} + +/// DisableMinting follows UseLegacyEraPayout: when legacy is on, minting is NOT disabled. +pub struct DisableMintingMode; +impl Get for DisableMintingMode { + fn get() -> bool { + !UseLegacyEraPayout::get() + } +} + pub fn general_staker_pot() -> AccountId { SequentialTest::general_pot_account(GeneralPotType::StakerRewards) } @@ -469,9 +512,13 @@ impl Config for Test { type ElectionProvider = TestElectionProvider; type NominationsQuota = WeightedNominationsQuota<16>; type HistoryDepth = HistoryDepth; + type RewardRemainder = RewardRemainderMock; type Slash = Dap; type UnclaimedRewardHandler = Dap; type Reward = MockReward; + type EraPayout = TestEraPayout; + type MaxEraDuration = MaxEraDuration; + type DisableMinting = DisableMintingMode; type SessionsPerEra = SessionsPerEra; type PlanningEraOffset = PlanningEraOffset; type BondingDuration = BondingDuration; @@ -576,6 +623,11 @@ impl ExtBuilder { ElectionDelay::set(delay); self } + /// Switch to legacy reward mode (EraPayout-based minting instead of DAP pots). + pub(crate) fn legacy_reward_mode(self) -> Self { + UseLegacyEraPayout::set(true); + self + } pub(crate) fn nominate(mut self, nominate: bool) -> Self { self.nominate = nominate; self @@ -743,24 +795,27 @@ impl ExtBuilder { ext.execute_with(|| { crate::AreNominatorsSlashable::::put(nominators_slashable); - // Disable legacy minting from era 0 in tests to catch missing era pots early. - crate::DisableLegacyMintingEra::::put(0); - // Set budget allocation: 50% stakers, 50% buffer (must sum to 100%). - pallet_dap::BudgetAllocation::::put(default_budget()); - // Initialize DAP's LastIssuanceTimestamp. - pallet_dap::LastIssuanceTimestamp::::put(INIT_TIMESTAMP); - // Fund general pot accounts with ED so they stay alive across era snapshots. - let ed = ExistentialDeposit::get(); - >::mint_into( - &general_staker_pot(), - ed, - ) - .expect("mint general staker pot"); - // Fund DAP buffer account with ED. - let dap_buffer = - as BudgetRecipient>::pot_account(); - >::mint_into(&dap_buffer, ed) + + if !UseLegacyEraPayout::get() { + // DAP mode: set up budget, fund pots, enable minting guard. + crate::DisableMintingGuard::::put(0); + pallet_dap::BudgetAllocation::::put(default_budget()); + pallet_dap::LastIssuanceTimestamp::::put(INIT_TIMESTAMP); + let ed = ExistentialDeposit::get(); + >::mint_into( + &general_staker_pot(), + ed, + ) + .expect("mint general staker pot"); + let dap_buffer = + as BudgetRecipient>::pot_account(); + >::mint_into( + &dap_buffer, + ed, + ) .expect("mint dap buffer"); + } + // Legacy mode: no DAP setup needed. EraPayout computes inflation, staking mints. session_mock::Session::roll_until_active_era(1); if self.flush_events { let _ = staking_events_since_last_call(); diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 345b5c284429f..cb2d4b9159864 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -444,7 +444,7 @@ impl Pallet { ) } else { // LEGACY: Only used for old eras finalised before reward provider impl. - if let Some(disable_era) = DisableLegacyMintingEra::::get() { + if let Some(disable_era) = DisableMintingGuard::::get() { if era >= disable_era { defensive!("Era has no reward pot but legacy minting is disabled!"); return Err(Error::::LegacyMintingDisabled.into()); diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index e601f21550d95..12f05dd2cb0d1 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -19,7 +19,8 @@ use crate::{ asset, session_rotation::EraElectionPlanner, slashing, weights::WeightInfo, AccountIdLookupOf, - ActiveEraInfo, BalanceOf, EraRewardPoints, ExposurePage, Forcing, LedgerIntegrityState, + ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, ExposurePage, Forcing, + LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; @@ -186,13 +187,17 @@ pub mod pallet { #[pallet::constant] type HistoryDepth: Get; + /// Receives the treasury remainder in legacy minting mode (`DisableMinting = false`). + /// Unused in non-minting mode. + #[pallet::no_default_bounds] + type RewardRemainder: OnUnbalanced>; + /// Handler for the unbalanced reduction when slashing a staker. #[pallet::no_default_bounds] type Slash: OnUnbalanced>; - /// Handler for the unbalanced increment when rewarding a staker. - /// NOTE: in most cases, the implementation of `OnUnbalanced` should modify the total - /// issuance. + /// Receives minted reward imbalances in legacy minting mode (`DisableMinting = false`). + /// Unused in non-minting mode where payouts are transfers from era pots. #[pallet::no_default_bounds] type Reward: OnUnbalanced>; @@ -249,21 +254,48 @@ pub mod pallet { #[pallet::no_default] type AdminOrigin: EnsureOrigin; - /// Handler for unclaimed era rewards. + /// Era payout computation for legacy minting mode (`DisableMinting = false`). + /// + /// Computes `(staker_payout, remainder)` from total staked, total issuance, and + /// era duration. Not called in non-minting mode — can be set to `()`. + #[pallet::no_default] + type EraPayout: EraPayout>; + + /// Maximum allowed era duration in milliseconds. + /// + /// Caps the effective era duration to prevent runaway inflation in legacy mode. + /// Set to 0 to disable capping. Unused in non-minting mode. + #[pallet::constant] + type MaxEraDuration: Get; + + /// When `true`, staking does not mint. It expects an external source to fund + /// the general reward pot. At era boundary, rewards are snapshotted from + /// the pot. `EraPayout` is not called. + /// + /// When `false`, staking uses the legacy path: `EraPayout` computes inflation, + /// tokens are minted on-the-fly during payout. /// - /// When era pots are cleaned up past history depth, remaining funds are withdrawn - /// and passed to this handler. Typically wired to DAP. + /// **Irreversible**: once set to `true`, must never be switched back. Eras + /// created in non-minting mode have funded reward pots — switching to legacy + /// would orphan those pots and cause double-minting. + #[pallet::constant] + type DisableMinting: Get; + + /// Handler for unclaimed era rewards (non-minting mode only). + /// + /// When era pots are cleaned up past `HistoryDepth`, remaining funds are + /// withdrawn and passed to this handler. #[pallet::no_default_bounds] type UnclaimedRewardHandler: OnUnbalanced>; - /// Provider for general (non-era) reward pot accounts. + /// Provider for general (non-era) reward pot accounts (non-minting mode only). /// - /// DAP drips inflation into these pots. At era boundaries, staking snapshots - /// and transfers the balances to era-specific pots. + /// An external source funds these pots. At era boundaries, staking snapshots + /// the accumulated balance into era-specific pots. #[pallet::no_default] type GeneralPots: crate::GeneralPotAccountProvider; - /// Provider for generating era pot account IDs. + /// Provider for generating era pot account IDs (non-minting mode only). #[pallet::no_default] type EraPots: crate::EraPotAccountProvider; @@ -412,10 +444,13 @@ pub mod pallet { type CurrencyToVote = (); type NominationsQuota = crate::FixedNominationsQuota<16>; type HistoryDepth = ConstU32<84>; + type RewardRemainder = (); type Slash = (); type Reward = (); type UnclaimedRewardHandler = (); type StakerRewardCalculator = (); + type MaxEraDuration = (); + type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; @@ -474,11 +509,15 @@ pub mod pallet { } } - /// The era from which legacy minting (mint-on-payout) is permanently disabled. + /// Safety guard: the era from which legacy minting is permanently disabled on the + /// payout side. **Irreversible** — once set, should never be cleared. /// - /// Once set, all eras >= this value must use reward pot transfers instead of minting. + /// Separate from [`Config::DisableMinting`] which controls the `end_era` path. + /// This storage guards against minting during payout for eras that were created + /// in DAP mode. Set automatically by `end_era_dap` on first successful pot snapshot. + /// In legacy mode (Kusama), this is never set and the guard is inactive. #[pallet::storage] - pub type DisableLegacyMintingEra = StorageValue<_, EraIndex>; + pub type DisableMintingGuard = StorageValue<_, EraIndex>; /// Whether nominators are slashable or not. /// @@ -778,6 +817,11 @@ pub mod pallet { #[pallet::storage] pub type ErasValidatorReward = StorageMap<_, Twox64Concat, EraIndex, BalanceOf>; + /// Maximum staked rewards, i.e. the percentage of the era inflation that + /// is used for stake rewards. Only used in legacy minting mode (`DisableMinting = false`). + #[pallet::storage] + pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; + /// Rewards for the last [`Config::HistoryDepth`] eras. /// If reward hasn't been set or has been removed then 0 reward is returned. #[pallet::storage] @@ -1140,11 +1184,11 @@ pub mod pallet { #[pallet::event] #[pallet::generate_deposit(pub fn deposit_event)] pub enum Event { - /// The era payout has been set; `validator_payout` is the staker reward budget - /// snapshotted from the DAP general pot. + /// The era payout has been set. /// - /// Note: `remainder` is always zero with DAP-based inflation. Treasury/buffer - /// allocation is handled by DAP directly and is no longer reported here. + /// In non-minting mode, `validator_payout` is the staker reward budget + /// snapshotted from the general pot, and `remainder` is always zero. + /// In legacy minting mode, both fields reflect the `EraPayout` computation. EraPaid { era_index: EraIndex, validator_payout: BalanceOf, @@ -2392,6 +2436,7 @@ pub mod pallet { max_validator_count: ConfigOp, chill_threshold: ConfigOp, min_commission: ConfigOp, + max_staked_rewards: ConfigOp, are_nominators_slashable: ConfigOp, ) -> DispatchResult { ensure_root(origin)?; @@ -2412,6 +2457,7 @@ pub mod pallet { config_op_exp!(MaxValidatorsCount, max_validator_count); config_op_exp!(ChillThreshold, chill_threshold); config_op_exp!(MinCommission, min_commission); + config_op_exp!(MaxStakedRewards, max_staked_rewards); config_op_exp!(AreNominatorsSlashable, are_nominators_slashable); Ok(()) } diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index 3403c54308949..63d88b520c101 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -81,11 +81,11 @@ use alloc::{boxed::Box, vec::Vec}; use frame_election_provider_support::{BoundedSupportsOf, ElectionProvider, PageIndex}; use frame_support::{ pallet_prelude::*, - traits::{Defensive, DefensiveMax, DefensiveSaturating, TryCollect}, + traits::{Defensive, DefensiveMax, DefensiveSaturating, OnUnbalanced, TryCollect}, weights::WeightMeter, }; use pallet_staking_async_rc_client::RcClientInterface; -use sp_runtime::{Perbill, Saturating}; +use sp_runtime::{Perbill, Percent, Saturating}; use sp_staking::{ currency_to_vote::CurrencyToVote, Exposure, Page, PagedExposureMetadata, SessionIndex, }; @@ -797,12 +797,60 @@ impl Rotator { }); } - fn end_era(ending_era: &ActiveEraInfo, _new_era_start: u64) { - log!(debug, "snapshotting reward pots for era {:?}", ending_era.index); + fn end_era(ending_era: &ActiveEraInfo, new_era_start: u64) { + if T::DisableMinting::get() { + Self::end_era_dap(ending_era); + } else { + Self::end_era_legacy(ending_era, new_era_start); + } + } + + /// Legacy end-era: compute inflation via `EraPayout`, mint, send remainder. + fn end_era_legacy(ending_era: &ActiveEraInfo, new_era_start: u64) { + let previous_era_start = ending_era.start.defensive_unwrap_or(new_era_start); + let era_duration = new_era_start.saturating_sub(previous_era_start); + + let cap = T::MaxEraDuration::get(); + let era_duration = if cap == 0 || era_duration <= cap { + era_duration + } else { + Pallet::::deposit_event(Event::Unexpected( + UnexpectedKind::EraDurationBoundExceeded, + )); + log!( + warn, + "capping era duration for era {:?} from {:?} to max {:?}", + ending_era.index, era_duration, cap + ); + cap + }; + + let staked = ErasTotalStake::::get(ending_era.index); + let issuance = asset::total_issuance::(); + let (validator_payout, remainder) = + T::EraPayout::era_payout(staked, issuance, era_duration); + + let total_payout = validator_payout.saturating_add(remainder); + let max_staked_rewards = + MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); + + let validator_payout = validator_payout.min(max_staked_rewards * total_payout); + let remainder = total_payout.saturating_sub(validator_payout); + + Pallet::::deposit_event(Event::::EraPaid { + era_index: ending_era.index, + validator_payout, + remainder, + }); + + Eras::::set_stakers_reward(ending_era.index, validator_payout); + T::RewardRemainder::on_unbalanced(asset::issue::(remainder)); + } - // Snapshot general staker reward pot into era-specific pot. - // DAP has been dripping inflation into the general pot since the last era boundary. - let staker_rewards = reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); + /// DAP end-era: snapshot from general reward pot into era-specific pot. + fn end_era_dap(ending_era: &ActiveEraInfo) { + let staker_rewards = + reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); if staker_rewards.is_zero() { log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index); @@ -810,17 +858,14 @@ impl Rotator { Eras::::set_stakers_reward(ending_era.index, staker_rewards); - // Note: `remainder` is always zero with DAP-based inflation — treasury/buffer - // allocation is handled by DAP directly. Pallet::::deposit_event(Event::::EraPaid { era_index: ending_era.index, validator_payout: staker_rewards, remainder: Zero::zero(), }); - // Update DisableLegacyMintingEra to prevent legacy minting. if !staker_rewards.is_zero() { - DisableLegacyMintingEra::::mutate(|maybe_era| { + DisableMintingGuard::::mutate(|maybe_era| { if maybe_era.is_none() || maybe_era.is_some_and(|e| ending_era.index < e) { *maybe_era = Some(ending_era.index); } diff --git a/substrate/frame/staking-async/src/tests/bonding.rs b/substrate/frame/staking-async/src/tests/bonding.rs index 9e82fef656f13..987ef7b1f5a46 100644 --- a/substrate/frame/staking-async/src/tests/bonding.rs +++ b/substrate/frame/staking-async/src/tests/bonding.rs @@ -1693,6 +1693,7 @@ mod staking_bounds_chill_other { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, + ConfigOp::Noop, )); // Can't chill these users @@ -1715,6 +1716,7 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1737,6 +1739,7 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1759,6 +1762,7 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1781,6 +1785,7 @@ mod staking_bounds_chill_other { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1803,6 +1808,7 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1825,6 +1831,7 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // Still can't chill these users @@ -1847,6 +1854,7 @@ mod staking_bounds_chill_other { ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); // 16 people total because tests start with 2 active one @@ -1895,6 +1903,7 @@ mod staking_bounds_chill_other { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Noop, + ConfigOp::Noop, )); // can create `max - validator_count` validators @@ -1966,6 +1975,7 @@ mod staking_bounds_chill_other { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, )); assert_ok!(Staking::nominate(RuntimeOrigin::signed(last_nominator), vec![11])); assert_ok!(Staking::validate( diff --git a/substrate/frame/staking-async/src/tests/configs.rs b/substrate/frame/staking-async/src/tests/configs.rs index 9281d7b086dc2..e1946a50abc82 100644 --- a/substrate/frame/staking-async/src/tests/configs.rs +++ b/substrate/frame/staking-async/src/tests/configs.rs @@ -29,6 +29,7 @@ fn set_staking_configs_works() { ConfigOp::Set(20), ConfigOp::Set(Percent::from_percent(75)), ConfigOp::Set(Zero::zero()), + ConfigOp::Set(Percent::from_percent(95)), ConfigOp::Set(false), )); assert_eq!(MinNominatorBond::::get(), 1_500); @@ -37,6 +38,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), Some(20)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(75))); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), Some(Percent::from_percent(95))); assert_eq!(AreNominatorsSlashable::::get(), false); // noop does nothing @@ -49,6 +51,7 @@ fn set_staking_configs_works() { ConfigOp::Noop, ConfigOp::Noop, ConfigOp::Noop, + ConfigOp::Noop, ))); // removing works @@ -61,6 +64,7 @@ fn set_staking_configs_works() { ConfigOp::Remove, ConfigOp::Remove, ConfigOp::Remove, + ConfigOp::Remove, )); assert_eq!(MinNominatorBond::::get(), 0); assert_eq!(MinValidatorBond::::get(), 0); @@ -68,6 +72,7 @@ fn set_staking_configs_works() { assert_eq!(MaxValidatorsCount::::get(), None); assert_eq!(ChillThreshold::::get(), None); assert_eq!(MinCommission::::get(), Perbill::from_percent(0)); + assert_eq!(MaxStakedRewards::::get(), None); // AreNominatorsSlashable defaults to true when removed assert_eq!(AreNominatorsSlashable::::get(), true); }); diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 9e523aa25404a..5dfa05c7f98ad 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -500,6 +500,7 @@ mod inflation { assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); }) } + } #[test] @@ -537,37 +538,37 @@ fn era_pot_cleanup_after_history_depth() { #[test] fn disable_legacy_minting_era_updates_correctly() { ExtBuilder::default().build_and_execute(|| { - // GIVEN: DisableLegacyMintingEra is set to 0 in test genesis - assert_eq!(DisableLegacyMintingEra::::get(), Some(0)); + // GIVEN: DisableMintingGuard is set to 0 in test genesis + assert_eq!(DisableMintingGuard::::get(), Some(0)); // WHEN: Era 1 ends with non-zero reward allocation Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); - // THEN: DisableLegacyMintingEra remains at 0 - assert_eq!(DisableLegacyMintingEra::::get(), Some(0)); + // THEN: DisableMintingGuard remains at 0 + assert_eq!(DisableMintingGuard::::get(), Some(0)); }); } #[test] fn disable_legacy_minting_era_write_once_semantics() { ExtBuilder::default().build_and_execute(|| { - // GIVEN: Clear DisableLegacyMintingEra to simulate pre-migration state - DisableLegacyMintingEra::::kill(); - assert_eq!(DisableLegacyMintingEra::::get(), None); + // GIVEN: Clear DisableMintingGuard to simulate pre-migration state + DisableMintingGuard::::kill(); + assert_eq!(DisableMintingGuard::::get(), None); // WHEN: First era ends with rewards Session::roll_until_active_era(2); let _ = staking_events_since_last_call(); - // THEN: DisableLegacyMintingEra is set to era 1 - assert_eq!(DisableLegacyMintingEra::::get(), Some(1)); + // THEN: DisableMintingGuard is set to era 1 + assert_eq!(DisableMintingGuard::::get(), Some(1)); // WHEN: More eras end Session::roll_until_active_era(5); let _ = staking_events_since_last_call(); - // THEN: DisableLegacyMintingEra stays at 1 (not updated to higher values) - assert_eq!(DisableLegacyMintingEra::::get(), Some(1)); + // THEN: DisableMintingGuard stays at 1 (not updated to higher values) + assert_eq!(DisableMintingGuard::::get(), Some(1)); }); } diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs new file mode 100644 index 0000000000000..29241ce487b0e --- /dev/null +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -0,0 +1,135 @@ +//! Tests for legacy reward mode (EraPayout-based minting). +//! +//! Legacy mode is used on Kusama where inflation depends on the staking ratio. +//! These tests verify that the old mint-on-payout path works correctly when +//! `DisableMinting = false`. + +use super::*; +use crate::mock::*; +use frame_support::assert_ok; + +#[test] +fn legacy_end_era_computes_inflation_and_emits_era_paid() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + let era_duration = time_per_era(); + let total = era_duration as Balance; + let expected_remainder = RemainderRatio::get() * total; + let expected_stakers = total - expected_remainder; + + Session::roll_until_active_era(2); + + // Legacy mode emits EraPaid with real remainder. + assert!(staking_events_since_last_call().contains(&Event::EraPaid { + era_index: 1, + validator_payout: expected_stakers, + remainder: expected_remainder, + })); + + // Era reward is stored for later payout. + assert_eq!(ErasValidatorReward::::get(1).unwrap(), expected_stakers); + }); +} + +#[test] +fn legacy_reward_remainder_handler_called() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + RewardRemainderUnbalanced::set(0); + + Session::roll_until_active_era(2); + + // RewardRemainder handler should have received the treasury portion. + assert!(RewardRemainderUnbalanced::get() > 0); + + let total = time_per_era() as Balance; + let expected_remainder = RemainderRatio::get() * total; + assert_eq!(RewardRemainderUnbalanced::get(), expected_remainder); + }); +} + +#[test] +fn legacy_payout_mints_tokens() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + Staking::reward_by_ids(vec![(11, 1)]); + Session::roll_until_active_era(2); + + let pre_payout_issuance = pallet_balances::TotalIssuance::::get(); + + // Payout should mint (increase total issuance). + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + + assert!(pallet_balances::TotalIssuance::::get() > pre_payout_issuance); + }); +} + +#[test] +fn legacy_max_staked_rewards_caps_staker_payout() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + // Set MaxStakedRewards to 10%. + MaxStakedRewards::::set(Some(Percent::from_percent(10))); + + Staking::reward_by_ids(vec![(11, 1)]); + Session::roll_until_active_era(2); + + let total = time_per_era() as Balance; + let expected_stakers = Percent::from_percent(10) * total; + let expected_remainder = total - expected_stakers; + + assert!(staking_events_since_last_call().contains(&Event::EraPaid { + era_index: 1, + validator_payout: expected_stakers, + remainder: expected_remainder, + })); + + assert_eq!(ErasValidatorReward::::get(1).unwrap(), expected_stakers); + }); +} + +#[test] +fn legacy_max_era_duration_caps_payout() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + // Set MaxEraDuration to half of time_per_era. + let half = time_per_era() / 2; + MaxEraDuration::set(half); + + Session::roll_until_active_era(2); + + let capped_total = half as Balance; + let expected_remainder = RemainderRatio::get() * capped_total; + let expected_stakers = capped_total - expected_remainder; + + let events = staking_events_since_last_call(); + assert!(events.contains(&Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded))); + assert!(events.contains(&Event::EraPaid { + era_index: 1, + validator_payout: expected_stakers, + remainder: expected_remainder, + })); + }); +} + +#[test] +fn legacy_disable_minting_guard_not_set() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + // In legacy mode, the guard should never be set. + assert_eq!(DisableMintingGuard::::get(), None); + + Session::roll_until_active_era(5); + + // Still not set after multiple eras. + assert_eq!(DisableMintingGuard::::get(), None); + }); +} + +#[test] +fn legacy_no_era_pots_created() { + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + Staking::reward_by_ids(vec![(11, 1)]); + Session::roll_until_active_era(2); + + // No reward pot should exist for this era. + assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(1)); + + // Payout still works (via legacy mint). + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + }); +} diff --git a/substrate/frame/staking-async/src/tests/mod.rs b/substrate/frame/staking-async/src/tests/mod.rs index 540990b903cd0..85f0689dedc37 100644 --- a/substrate/frame/staking-async/src/tests/mod.rs +++ b/substrate/frame/staking-async/src/tests/mod.rs @@ -43,6 +43,7 @@ mod election_provider; mod era_rotation; mod force_unstake_kill_stash; mod ledger; +mod legacy_reward; mod nominators_no_slashing; mod payout_stakers; mod slashing; diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 68153b958c7d8..30329ae383753 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -588,6 +588,7 @@ fn min_commission_works() { ConfigOp::Remove, ConfigOp::Set(Perbill::from_percent(10)), ConfigOp::Noop, + ConfigOp::Noop, )); // can't make it less than 10 now From d22f812acbbaa96b543f162dbc38afb4346d3e33 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 13:13:07 +0200 Subject: [PATCH 44/87] bench fix --- substrate/frame/staking-async/src/benchmarking.rs | 1 + substrate/frame/staking-async/src/tests/legacy_reward.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 78a0c76680e7d..1ed5de5322b8d 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -865,6 +865,7 @@ mod benchmarks { assert!(!MaxValidatorsCount::::exists()); assert!(!ChillThreshold::::exists()); assert!(!MinCommission::::exists()); + assert!(!MaxStakedRewards::::exists()); assert!(!AreNominatorsSlashable::::exists()); } diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs index 29241ce487b0e..dda190b310028 100644 --- a/substrate/frame/staking-async/src/tests/legacy_reward.rs +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -5,7 +5,6 @@ //! `DisableMinting = false`. use super::*; -use crate::mock::*; use frame_support::assert_ok; #[test] From 2b04c3acda837bea3151b1fd0a93a8133d7b1217 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 13:19:54 +0200 Subject: [PATCH 45/87] fmt --- substrate/frame/staking-async/src/pallet/mod.rs | 6 +++--- .../frame/staking-async/src/session_rotation.rs | 14 ++++++-------- .../frame/staking-async/src/tests/era_rotation.rs | 1 - 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 12f05dd2cb0d1..5fd382f4326d8 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -20,9 +20,9 @@ use crate::{ asset, session_rotation::EraElectionPlanner, slashing, weights::WeightInfo, AccountIdLookupOf, ActiveEraInfo, BalanceOf, EraPayout, EraRewardPoints, ExposurePage, Forcing, - LedgerIntegrityState, - MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, PositiveImbalanceOf, - RewardDestination, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, + LedgerIntegrityState, MaxNominationsOf, NegativeImbalanceOf, Nominations, NominationsQuota, + PositiveImbalanceOf, RewardDestination, StakingLedger, UnappliedSlash, UnlockChunk, + ValidatorPrefs, }; use alloc::{format, vec::Vec}; use codec::Codec; diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index 63d88b520c101..2e7175c817a61 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -814,13 +814,13 @@ impl Rotator { let era_duration = if cap == 0 || era_duration <= cap { era_duration } else { - Pallet::::deposit_event(Event::Unexpected( - UnexpectedKind::EraDurationBoundExceeded, - )); + Pallet::::deposit_event(Event::Unexpected(UnexpectedKind::EraDurationBoundExceeded)); log!( warn, "capping era duration for era {:?} from {:?} to max {:?}", - ending_era.index, era_duration, cap + ending_era.index, + era_duration, + cap ); cap }; @@ -831,8 +831,7 @@ impl Rotator { T::EraPayout::era_payout(staked, issuance, era_duration); let total_payout = validator_payout.saturating_add(remainder); - let max_staked_rewards = - MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); + let max_staked_rewards = MaxStakedRewards::::get().unwrap_or(Percent::from_percent(100)); let validator_payout = validator_payout.min(max_staked_rewards * total_payout); let remainder = total_payout.saturating_sub(validator_payout); @@ -849,8 +848,7 @@ impl Rotator { /// DAP end-era: snapshot from general reward pot into era-specific pot. fn end_era_dap(ending_era: &ActiveEraInfo) { - let staker_rewards = - reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); + let staker_rewards = reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); if staker_rewards.is_zero() { log!(warn, "Era {:?} has zero staker rewards in general pot", ending_era.index); diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 5dfa05c7f98ad..21bf99fa0b94d 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -500,7 +500,6 @@ mod inflation { assert_eq!(ErasValidatorReward::::get(0).unwrap(), default_stakers_payout); }) } - } #[test] From 6f7b2b9cb1319f05595c2a7e9f4cb7184e841028 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 13:22:17 +0200 Subject: [PATCH 46/87] license file --- .../staking-async/src/tests/legacy_reward.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs index dda190b310028..dfc66fa4b6923 100644 --- a/substrate/frame/staking-async/src/tests/legacy_reward.rs +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -1,3 +1,20 @@ +// 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. + //! Tests for legacy reward mode (EraPayout-based minting). //! //! Legacy mode is used on Kusama where inflation depends on the staking ratio. From 48e67bffea1747151f743720d529fd6a77abb6a4 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 13:30:46 +0200 Subject: [PATCH 47/87] disable mint integrity test --- substrate/frame/staking-async/src/pallet/mod.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 5fd382f4326d8..df0930d79c526 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -1635,6 +1635,20 @@ pub mod pallet { "MaxPruningItems must be at least 100 for efficient pruning, got: {}", T::MaxPruningItems::get() ); + + // If minting is disabled, EraPayout must be a noop to prevent double-minting. + if T::DisableMinting::get() { + let (v, r) = T::EraPayout::era_payout( + BalanceOf::::from(1u64), + BalanceOf::::from(1u64), + 1000u64, + ); + assert!( + v.is_zero() && r.is_zero(), + "DisableMinting is true but EraPayout returns non-zero. \ + Set EraPayout = () when DisableMinting = true." + ); + } } #[cfg(feature = "try-runtime")] From 9bd2cb907dbef1edb1d0a50a7e735d7eb3a60f99 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 13:37:02 +0200 Subject: [PATCH 48/87] disable minting try state check --- .../frame/staking-async/src/pallet/impls.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index cb2d4b9159864..aac13990d80ce 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -1961,10 +1961,43 @@ impl Pallet { Self::check_paged_exposures()?; Self::check_count()?; Self::check_slash_health()?; + Self::check_reward_mode_consistency()?; Ok(()) } + /// Checks consistency of the reward mode configuration and storage. + /// + /// In non-minting mode (`DisableMinting = true`): + /// - All eras >= `DisableMintingGuard` within `HistoryDepth` should have reward pots. + fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> { + if T::DisableMinting::get() { + if let Some(guard_era) = DisableMintingGuard::::get() { + let active_era = crate::session_rotation::Rotator::::active_era(); + let history_depth = T::HistoryDepth::get(); + let oldest = active_era.saturating_sub(history_depth); + for era in oldest..active_era { + if era >= guard_era { + ensure!( + crate::reward::EraRewardManager::::has_staker_rewards_pot(era), + "Era pot missing for guarded era. Rewards cannot be paid." + ); + } + } + } else { + // TODO: convert to `ensure!` once deployed and the first era has been + // snapshotted. Before deployment this is expected (guard is set on first + // successful DAP snapshot in end_era_dap). + log!( + warn, + "DisableMinting=true but DisableMintingGuard is not set. \ + Expected only before the first era boundary after migration." + ); + } + } + Ok(()) + } + /// Invariants: /// * A controller should not be associated with more than one ledger. /// * A bonded (stash, controller) pair should have only one associated ledger. I.e. if the From a85069192c3624229f4e4b53594845dff7e172c1 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 15:27:30 +0200 Subject: [PATCH 49/87] minimise diff --- .../assets/asset-hub-westend/src/staking.rs | 16 +++--- .../test-delegate-stake/src/lib.rs | 2 +- .../test-delegate-stake/src/mock.rs | 2 +- .../runtimes/parachain/src/staking.rs | 16 +++--- .../frame/staking-async/src/pallet/mod.rs | 54 ++++++++++++------- 5 files changed, 52 insertions(+), 38 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 598a07cfcc1f4..32b9bf7f2d8bc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -279,20 +279,13 @@ impl pallet_staking_async::Config for Runtime { type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; type RewardRemainder = (); type Slash = Dap; - type UnclaimedRewardHandler = Dap; type Reward = (); - type EraPayout = (); - type MaxEraDuration = (); - type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOf, StakingAdmin>; - type GeneralPots = pallet_staking_async::Seed; - type EraPots = pallet_staking_async::Seed; - type StakerRewardCalculator = - pallet_staking_async::reward::DefaultStakerRewardCalculator; + type EraPayout = (); type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; @@ -305,6 +298,13 @@ impl pallet_staking_async::Config for Runtime { type EventListeners = (NominationPools, DelegatedStaking); type PlanningEraOffset = ConstU32<6>; type RcClientInterface = StakingRcClient; + type MaxEraDuration = (); + type DisableMinting = ConstBool; + type UnclaimedRewardHandler = Dap; + type GeneralPots = pallet_staking_async::Seed; + type EraPots = pallet_staking_async::Seed; + type StakerRewardCalculator = + pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; type WeightInfo = weights::pallet_staking_async::WeightInfo; } diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs index 9260d37700226..231ca451a6f05 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/lib.rs @@ -263,7 +263,7 @@ fn pool_chill_e2e() { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, - pallet_staking_async::ConfigOp::Noop, // max_staked_rewards + pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index aa0816d303b8d..512568520d8b9 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -371,7 +371,7 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, - pallet_staking_async::ConfigOp::Noop, // max_staked_rewards + pallet_staking_async::ConfigOp::Noop, pallet_staking_async::ConfigOp::Noop, // are_nominators_slashable )); diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index ad7a6b8aa79ec..c8cd696edb887 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -435,19 +435,12 @@ impl pallet_staking_async::Config for Runtime { type RewardRemainder = (); type Slash = Dap; type Reward = (); - type EraPayout = (); - type MaxEraDuration = (); - type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; type SlashDeferDuration = SlashDeferDuration; type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type AdminOrigin = EitherOf, StakingAdmin>; - type UnclaimedRewardHandler = Dap; - type GeneralPots = pallet_staking_async::Seed; - type EraPots = pallet_staking_async::Seed; - type StakerRewardCalculator = - pallet_staking_async::reward::DefaultStakerRewardCalculator; + type EraPayout = (); type MaxExposurePageSize = MaxExposurePageSize; type ElectionProvider = MultiBlockElection; type VoterList = VoterList; @@ -459,6 +452,13 @@ impl pallet_staking_async::Config for Runtime { type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = pallet_staking_async::weights::SubstrateWeight; + type MaxEraDuration = (); + type DisableMinting = ConstBool; + type UnclaimedRewardHandler = Dap; + type GeneralPots = pallet_staking_async::Seed; + type EraPots = pallet_staking_async::Seed; + type StakerRewardCalculator = + pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; type PlanningEraOffset = pallet_staking_async::PlanningEraOffsetOf>; diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index df0930d79c526..e7ff2fe4029d9 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -187,8 +187,9 @@ pub mod pallet { #[pallet::constant] type HistoryDepth: Get; - /// Receives the treasury remainder in legacy minting mode (`DisableMinting = false`). - /// Unused in non-minting mode. + /// Tokens have been minted and are unused for validator-reward. + /// + /// Only used in legacy minting mode (`DisableMinting = false`). #[pallet::no_default_bounds] type RewardRemainder: OnUnbalanced>; @@ -196,8 +197,11 @@ pub mod pallet { #[pallet::no_default_bounds] type Slash: OnUnbalanced>; - /// Receives minted reward imbalances in legacy minting mode (`DisableMinting = false`). - /// Unused in non-minting mode where payouts are transfers from era pots. + /// Handler for the unbalanced increment when rewarding a staker. + /// NOTE: in most cases, the implementation of `OnUnbalanced` should modify the total + /// issuance. + /// + /// Only used in legacy minting mode (`DisableMinting = false`). #[pallet::no_default_bounds] type Reward: OnUnbalanced>; @@ -254,20 +258,14 @@ pub mod pallet { #[pallet::no_default] type AdminOrigin: EnsureOrigin; - /// Era payout computation for legacy minting mode (`DisableMinting = false`). + /// The payout for validators and the system for the current era. + /// See [Era payout](./index.html#era-payout). /// - /// Computes `(staker_payout, remainder)` from total staked, total issuance, and - /// era duration. Not called in non-minting mode — can be set to `()`. + /// Only used in legacy minting mode (`DisableMinting = false`). + /// Should be set to () in non-minting mode. #[pallet::no_default] type EraPayout: EraPayout>; - /// Maximum allowed era duration in milliseconds. - /// - /// Caps the effective era duration to prevent runaway inflation in legacy mode. - /// Set to 0 to disable capping. Unused in non-minting mode. - #[pallet::constant] - type MaxEraDuration: Get; - /// When `true`, staking does not mint. It expects an external source to fund /// the general reward pot. At era boundary, rewards are snapshotted from /// the pot. `EraPayout` is not called. @@ -386,6 +384,19 @@ pub mod pallet { #[pallet::no_default_bounds] type EventListeners: sp_staking::OnStakingUpdate>; + /// Maximum allowed era duration in milliseconds. + /// + /// This provides a defensive upper bound to cap the effective era duration, preventing + /// excessively long eras from causing runaway inflation (e.g., due to bugs). If the actual + /// era duration exceeds this value, it will be clamped to this maximum. + /// + /// Example: For an ideal era duration of 24 hours (86,400,000 ms), + /// this can be set to 604,800,000 ms (7 days). + /// + /// Only used in legacy minting mode (`DisableMinting = false`). + #[pallet::constant] + type MaxEraDuration: Get; + /// Maximum number of storage items that can be pruned in a single call. /// /// This controls how many storage items can be deleted in each call to `prune_era_step`. @@ -449,7 +460,6 @@ pub mod pallet { type Reward = (); type UnclaimedRewardHandler = (); type StakerRewardCalculator = (); - type MaxEraDuration = (); type DisableMinting = ConstBool; type SessionsPerEra = SessionsPerEra; type BondingDuration = BondingDuration; @@ -460,6 +470,8 @@ pub mod pallet { type MaxUnlockingChunks = ConstU32<32>; type MaxValidatorSet = ConstU32<100>; type MaxControllersInDeprecationBatch = ConstU32<100>; + type MaxEraDuration = (); + type MaxPruningItems = MaxPruningItems; type EventListeners = (); type Filter = Nothing; @@ -817,11 +829,6 @@ pub mod pallet { #[pallet::storage] pub type ErasValidatorReward = StorageMap<_, Twox64Concat, EraIndex, BalanceOf>; - /// Maximum staked rewards, i.e. the percentage of the era inflation that - /// is used for stake rewards. Only used in legacy minting mode (`DisableMinting = false`). - #[pallet::storage] - pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; - /// Rewards for the last [`Config::HistoryDepth`] eras. /// If reward hasn't been set or has been removed then 0 reward is returned. #[pallet::storage] @@ -838,6 +845,13 @@ pub mod pallet { #[pallet::storage] pub type ForceEra = StorageValue<_, Forcing, ValueQuery>; + /// Maximum staked rewards, i.e. the percentage of the era inflation that + /// is used for stake rewards. + /// + /// /// Only used in legacy minting mode (`DisableMinting = false`). + #[pallet::storage] + pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; + /// The percentage of the slash that is distributed to reporters. /// /// The rest of the slashed value is handled by the `Slash`. From 7b421395e6ec0198027779c43cfc74b1d2042888 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 15:38:39 +0200 Subject: [PATCH 50/87] fix nominator reward part --- substrate/frame/staking-async/src/pallet/impls.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index aac13990d80ce..1e9e0c005f059 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -440,6 +440,7 @@ impl Pallet { &stash, validator_staker_payout_for_page, &exposure, + overview_own, reward_split.nominator_payout, ) } else { @@ -455,6 +456,7 @@ impl Pallet { &stash, validator_staker_payout_for_page, &exposure, + overview_own, reward_split.nominator_payout, ) }; @@ -470,6 +472,7 @@ impl Pallet { stash: &T::AccountId, validator_payout: BalanceOf, exposure: &crate::PagedExposure>, + overview_own: BalanceOf, total_nominator_payout: BalanceOf, ) -> u32 { let mut nominator_payout_count: u32 = 0; @@ -479,7 +482,7 @@ impl Pallet { Self::deposit_event(Event::::Rewarded { stash: stash.clone(), dest, amount }); } - let total_nominator_stake = exposure.total().saturating_sub(exposure.own()); + let total_nominator_stake = exposure.total().saturating_sub(overview_own); for nominator in exposure.others().iter() { let nominator_exposure_part = Perbill::from_rational(nominator.value, total_nominator_stake); @@ -506,6 +509,7 @@ impl Pallet { stash: &T::AccountId, validator_payout: BalanceOf, exposure: &crate::PagedExposure>, + overview_own: BalanceOf, total_nominator_payout: BalanceOf, ) -> u32 { let mut nominator_payout_count: u32 = 0; @@ -520,7 +524,7 @@ impl Pallet { total_imbalance.subsume(imbalance); } - let total_nominator_stake = exposure.total().saturating_sub(exposure.own()); + let total_nominator_stake = exposure.total().saturating_sub(overview_own); for nominator in exposure.others().iter() { let nominator_exposure_part = Perbill::from_rational(nominator.value, total_nominator_stake); From 1aa1d075a32f297349af607c72dd9a6dc502b979 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 15:58:52 +0200 Subject: [PATCH 51/87] esnure nominator underpay is detected --- substrate/frame/staking-async/src/mock.rs | 36 +++++++++---------- .../staking-async/src/tests/payout_stakers.rs | 14 +++++++- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 72693587ed4e5..84464b82c4564 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -506,38 +506,38 @@ pub(crate) fn default_budget() -> pallet_dap::BudgetAllocationMap { impl Config for Test { type OldCurrency = Balances; type Currency = Balances; - type RuntimeHoldReason = RuntimeHoldReason; - type CurrencyBalance = Balance; - type CurrencyToVote = SaturatingCurrencyToVote; - type ElectionProvider = TestElectionProvider; - type NominationsQuota = WeightedNominationsQuota<16>; - type HistoryDepth = HistoryDepth; type RewardRemainder = RewardRemainderMock; - type Slash = Dap; - type UnclaimedRewardHandler = Dap; type Reward = MockReward; - type EraPayout = TestEraPayout; - type MaxEraDuration = MaxEraDuration; - type DisableMinting = DisableMintingMode; type SessionsPerEra = SessionsPerEra; - type PlanningEraOffset = PlanningEraOffset; - type BondingDuration = BondingDuration; - type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type SlashDeferDuration = SlashDeferDuration; type AdminOrigin = EitherOfDiverse, EnsureSignedBy>; - type GeneralPots = SequentialTest; - type EraPots = SequentialTest; + type EraPayout = TestEraPayout; type MaxExposurePageSize = MaxExposurePageSize; type MaxValidatorSet = MaxValidatorSet; + type ElectionProvider = TestElectionProvider; type VoterList = VoterBagsList; type TargetList = UseValidatorsMap; + type NominationsQuota = WeightedNominationsQuota<16>; type MaxUnlockingChunks = MaxUnlockingChunks; + type HistoryDepth = HistoryDepth; + type BondingDuration = BondingDuration; + type NominatorFastUnbondDuration = NominatorFastUnbondDuration; type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = EventListenerMock; + type MaxEraDuration = MaxEraDuration; + type DisableMinting = DisableMintingMode; + type UnclaimedRewardHandler = Dap; + type GeneralPots = SequentialTest; + type EraPots = SequentialTest; + type StakerRewardCalculator = reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; - type RcClientInterface = session_mock::Session; + type PlanningEraOffset = PlanningEraOffset; type Filter = MockedRestrictList; - type StakerRewardCalculator = reward::DefaultStakerRewardCalculator; + type RcClientInterface = session_mock::Session; + type CurrencyBalance = Balance; + type CurrencyToVote = SaturatingCurrencyToVote; + type Slash = Dap; + type RuntimeHoldReason = RuntimeHoldReason; type WeightInfo = (); } diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 30329ae383753..3921790f8a4c6 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -960,7 +960,19 @@ fn test_multi_page_payout_stakers_by_page() { // With DAP, rewards minted at era finalization, so no change during payout assert_eq!(pallet_balances::TotalIssuance::::get(), pre_payout_total_issuance); - // Top 64 nominators of validator 11 automatically paid out, including the validator + // Verify total nominator rewards: sum of all nominator balance increases should + // equal the nominator payout (within rounding tolerance). + let total_nominator_reward: Balance = (0..100) + .map(|i| { + let nom_id = 1000 + i; + let initial = balance + i as Balance; + asset::stakeable_balance::(&nom_id).saturating_sub(initial) + }) + .sum(); + let expected_nominator_total = era_reward.saturating_sub(validator_total_reward); + // Allow 1 per nominator rounding tolerance. + assert_eq_error_rate!(total_nominator_reward, expected_nominator_total, 100); + assert!(asset::stakeable_balance::(&11) > balance); for i in 0..100 { assert!(asset::stakeable_balance::(&(1000 + i)) > balance + i as Balance); From a123a1e2088990c0b151bba2202e2b55c33891be Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 17:15:53 +0200 Subject: [PATCH 52/87] cosmetic --- .../assets/asset-hub-westend/src/staking.rs | 5 +- .../test-delegate-stake/src/mock.rs | 17 +++++- .../runtimes/parachain/src/staking.rs | 3 +- substrate/frame/staking-async/src/mock.rs | 26 ++++---- .../frame/staking-async/src/pallet/mod.rs | 11 ++-- .../staking-async/src/tests/legacy_reward.rs | 61 +++++-------------- 6 files changed, 55 insertions(+), 68 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 32b9bf7f2d8bc..c127cbbe99f54 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -267,6 +267,7 @@ parameter_types! { pub const MaxControllersInDeprecationBatch: u32 = 751; // alias for 16, which is the max nominations per nominator in the runtime. pub const MaxNominations: u32 = ::LIMIT as u32; + pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64; pub MaxPruningItems: u32 = 100; } @@ -277,7 +278,7 @@ impl pallet_staking_async::Config for Runtime { type CurrencyBalance = Balance; type RuntimeHoldReason = RuntimeHoldReason; type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote; - type RewardRemainder = (); + type RewardRemainder = Dap; type Slash = Dap; type Reward = (); type SessionsPerEra = SessionsPerEra; @@ -298,7 +299,7 @@ impl pallet_staking_async::Config for Runtime { type EventListeners = (NominationPools, DelegatedStaking); type PlanningEraOffset = ConstU32<6>; type RcClientInterface = StakingRcClient; - type MaxEraDuration = (); + type MaxEraDuration = MaxEraDuration; type DisableMinting = ConstBool; type UnclaimedRewardHandler = Dap; type GeneralPots = pallet_staking_async::Seed; diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index 512568520d8b9..d673857ef8f8b 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -91,6 +91,19 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; pub static BondingDuration: u32 = 3; + pub static EraPayout: (Balance, Balance) = (1000, 100); +} + +/// A simple EraPayout implementation for testing that returns fixed values. +pub struct TestEraPayout; +impl pallet_staking_async::EraPayout for TestEraPayout { + fn era_payout( + _total_staked: Balance, + _total_issuance: Balance, + _era_duration_millis: u64, + ) -> (Balance, Balance) { + EraPayout::get() + } } /// A mock RcClientInterface for tests that don't need actual session/validator set management. @@ -112,8 +125,8 @@ impl pallet_staking_async::Config for Runtime { type OldCurrency = Balances; type Currency = Balances; type AdminOrigin = frame_system::EnsureRoot; - type EraPayout = (); - type DisableMinting = ConstBool; + type EraPayout = TestEraPayout; + type DisableMinting = ConstBool; type BondingDuration = BondingDuration; type GeneralPots = pallet_staking_async::SequentialTest; type EraPots = pallet_staking_async::SequentialTest; diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index c8cd696edb887..53c79fe4029ac 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -422,6 +422,7 @@ parameter_types! { // of nominators. pub const MaxControllersInDeprecationBatch: u32 = 751; pub const MaxNominations: u32 = ::LIMIT as u32; + pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64; pub MaxPruningItems: u32 = 100; } @@ -452,7 +453,7 @@ impl pallet_staking_async::Config for Runtime { type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch; type EventListeners = (NominationPools, DelegatedStaking); type WeightInfo = pallet_staking_async::weights::SubstrateWeight; - type MaxEraDuration = (); + type MaxEraDuration = MaxEraDuration; type DisableMinting = ConstBool; type UnclaimedRewardHandler = Dap; type GeneralPots = pallet_staking_async::Seed; diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 84464b82c4564..d0076fca0640a 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -114,6 +114,20 @@ impl pallet_balances::Config for Test { type AccountStore = System; } +parameter_types! { + pub static RewardRemainderUnbalanced: u128 = 0; +} +pub struct RewardRemainderMock; +impl OnUnbalanced> for RewardRemainderMock { + fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { + use frame_support::traits::tokens::imbalance::Imbalance; + RewardRemainderUnbalanced::mutate(|v| { + *v += amount.peek(); + }); + drop(amount); + } +} + pub(crate) const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; @@ -399,7 +413,6 @@ ord_parameter_types! { } parameter_types! { - pub static RewardRemainderUnbalanced: u128 = 0; pub static UseLegacyEraPayout: bool = false; pub static RemainderRatio: Perbill = Perbill::from_percent(50); pub static MaxEraDuration: u64 = 0; @@ -418,17 +431,6 @@ impl frame_support::traits::Time for MockTime { } } -pub struct RewardRemainderMock; -impl OnUnbalanced> for RewardRemainderMock { - fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - use frame_support::traits::tokens::imbalance::Imbalance; - RewardRemainderUnbalanced::mutate(|v| { - *v += amount.peek(); - }); - drop(amount); - } -} - /// Switchable EraPayout: returns (0,0) in DAP mode, real values in legacy mode. pub struct TestEraPayout; impl EraPayout for TestEraPayout { diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index e7ff2fe4029d9..0f47bae01904c 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -471,7 +471,6 @@ pub mod pallet { type MaxValidatorSet = ConstU32<100>; type MaxControllersInDeprecationBatch = ConstU32<100>; type MaxEraDuration = (); - type MaxPruningItems = MaxPruningItems; type EventListeners = (); type Filter = Nothing; @@ -848,7 +847,7 @@ pub mod pallet { /// Maximum staked rewards, i.e. the percentage of the era inflation that /// is used for stake rewards. /// - /// /// Only used in legacy minting mode (`DisableMinting = false`). + /// Only used in legacy minting mode (`DisableMinting = false`). #[pallet::storage] pub type MaxStakedRewards = StorageValue<_, Percent, OptionQuery>; @@ -1416,10 +1415,6 @@ pub mod pallet { TooManyValidators, /// Commission is too low. Must be at least `MinCommission`. CommissionTooLow, - /// Commission is higher than the allowed maximum `MaxCommission`. - CommissionTooHigh, - /// Era has no reward pot but legacy minting is disabled. - LegacyMintingDisabled, /// Some bound is not met. BoundNotMet, /// Used when attempting to use deprecated controller account logic. @@ -1448,6 +1443,10 @@ pub mod pallet { EraNotPrunable, /// The slash has been cancelled and cannot be applied. CancelledSlash, + /// Commission is higher than the allowed maximum `MaxCommission`. + CommissionTooHigh, + /// Era has no reward pot but legacy minting is disabled. + LegacyMintingDisabled, } impl Pallet { diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs index dfc66fa4b6923..bbd86bd87edd2 100644 --- a/substrate/frame/staking-async/src/tests/legacy_reward.rs +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -25,54 +25,42 @@ use super::*; use frame_support::assert_ok; #[test] -fn legacy_end_era_computes_inflation_and_emits_era_paid() { +fn legacy_era_payout_and_mint_works() { ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { let era_duration = time_per_era(); let total = era_duration as Balance; let expected_remainder = RemainderRatio::get() * total; let expected_stakers = total - expected_remainder; + Staking::reward_by_ids(vec![(11, 1)]); + RewardRemainderUnbalanced::set(0); + Session::roll_until_active_era(2); - // Legacy mode emits EraPaid with real remainder. + // THEN: EraPaid emitted with real remainder. assert!(staking_events_since_last_call().contains(&Event::EraPaid { era_index: 1, validator_payout: expected_stakers, remainder: expected_remainder, })); - // Era reward is stored for later payout. + // THEN: era reward stored. assert_eq!(ErasValidatorReward::::get(1).unwrap(), expected_stakers); - }); -} - -#[test] -fn legacy_reward_remainder_handler_called() { - ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - RewardRemainderUnbalanced::set(0); - Session::roll_until_active_era(2); - - // RewardRemainder handler should have received the treasury portion. - assert!(RewardRemainderUnbalanced::get() > 0); - - let total = time_per_era() as Balance; - let expected_remainder = RemainderRatio::get() * total; + // THEN: RewardRemainder handler received treasury portion. assert_eq!(RewardRemainderUnbalanced::get(), expected_remainder); - }); -} -#[test] -fn legacy_payout_mints_tokens() { - ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - Staking::reward_by_ids(vec![(11, 1)]); - Session::roll_until_active_era(2); + // THEN: no era pot created (legacy doesn't use pots). + assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(1)); - let pre_payout_issuance = pallet_balances::TotalIssuance::::get(); + // THEN: DisableMintingGuard not set (legacy mode never sets it). + assert_eq!(DisableMintingGuard::::get(), None); - // Payout should mint (increase total issuance). + // WHEN: payout is claimed. + let pre_payout_issuance = pallet_balances::TotalIssuance::::get(); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); + // THEN: payout mints tokens (issuance increases). assert!(pallet_balances::TotalIssuance::::get() > pre_payout_issuance); }); } @@ -80,7 +68,6 @@ fn legacy_payout_mints_tokens() { #[test] fn legacy_max_staked_rewards_caps_staker_payout() { ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - // Set MaxStakedRewards to 10%. MaxStakedRewards::::set(Some(Percent::from_percent(10))); Staking::reward_by_ids(vec![(11, 1)]); @@ -103,7 +90,6 @@ fn legacy_max_staked_rewards_caps_staker_payout() { #[test] fn legacy_max_era_duration_caps_payout() { ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - // Set MaxEraDuration to half of time_per_era. let half = time_per_era() / 2; MaxEraDuration::set(half); @@ -124,28 +110,13 @@ fn legacy_max_era_duration_caps_payout() { } #[test] -fn legacy_disable_minting_guard_not_set() { +fn legacy_guard_stays_unset_across_eras() { ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - // In legacy mode, the guard should never be set. assert_eq!(DisableMintingGuard::::get(), None); Session::roll_until_active_era(5); - // Still not set after multiple eras. + // Guard never set in legacy mode, even after multiple eras. assert_eq!(DisableMintingGuard::::get(), None); }); } - -#[test] -fn legacy_no_era_pots_created() { - ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { - Staking::reward_by_ids(vec![(11, 1)]); - Session::roll_until_active_era(2); - - // No reward pot should exist for this era. - assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(1)); - - // Payout still works (via legacy mint). - assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, 1)); - }); -} From e5e712021b3b757ab2cd931f328c778f6a0c5aa6 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 6 Apr 2026 17:23:56 +0200 Subject: [PATCH 53/87] update prdoc --- prdoc/pr_11616.prdoc | 27 ++++++++++++++++------- substrate/frame/staking-async/src/mock.rs | 5 ++--- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/prdoc/pr_11616.prdoc b/prdoc/pr_11616.prdoc index 1249bdfc47d14..e25ae6f8f091b 100644 --- a/prdoc/pr_11616.prdoc +++ b/prdoc/pr_11616.prdoc @@ -2,16 +2,23 @@ title: Move era reward minting from staking to DAP doc: - audience: Runtime Dev description: |- - Staking no longer mints tokens during `payout_stakers`. Instead, DAP drips inflation - into a general staker reward pot, and staking snapshots that into era-specific pots at - each era boundary. Payouts transfer from era pots. A legacy minting fallback is preserved - for pre-migration eras. + Introduces dual-mode era rewards in `pallet-staking-async`, controlled by + `Config::DisableMinting`: + - `true` (non-minting): staking expects an external source to fund a general reward + pot. At era boundary, the balance is snapshotted into an era-specific pot. Payouts + transfer from the pot. Unclaimed rewards returned via `UnclaimedRewardHandler`. + - `false` (legacy minting): `EraPayout` computes inflation, tokens minted on payout, + remainder sent to `RewardRemainder`. Kept for Kusama compatibility. - Runtimes now provide a `PolkadotIssuanceCurve` to DAP (replacing the old `EraPayout`) and - register `StakerRewardRecipient` as a budget recipient. + Switching from legacy to non-minting is irreversible. - DAP storage version bumped to V2 with migration for `LastIssuanceTimestamp` and - `BudgetAllocation`. + New config: `DisableMinting`, `UnclaimedRewardHandler`, `GeneralPots`, `EraPots`, + `StakerRewardCalculator`. New storage: `MaxCommission`, `DisableMintingGuard`. + New extrinsic: `set_max_commission`. + + Runtimes using non-minting mode provide a `PolkadotIssuanceCurve` to DAP and + register `StakerRewardRecipient` as a budget recipient. DAP storage version + bumped to V2 with migration. crates: - name: pallet-staking-async bump: major @@ -25,3 +32,7 @@ crates: bump: major - name: pallet-ahm-test bump: patch +- name: kitchensink-runtime + bump: major +- name: pallet-nomination-pools-test-delegate-stake + bump: patch diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index d0076fca0640a..bcc7654d28c4f 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -29,7 +29,7 @@ use frame_election_provider_support::{ }; use frame_support::{ assert_ok, derive_impl, ord_parameter_types, parameter_types, - traits::{EitherOfDiverse, Get, OnUnbalanced}, + traits::{EitherOfDiverse, Get, Imbalance, OnUnbalanced}, weights::constants::RocksDbWeight, }; use frame_system::{pallet_prelude::BlockNumberFor, EnsureRoot, EnsureSignedBy}; @@ -120,7 +120,6 @@ parameter_types! { pub struct RewardRemainderMock; impl OnUnbalanced> for RewardRemainderMock { fn on_nonzero_unbalanced(amount: NegativeImbalanceOf) { - use frame_support::traits::tokens::imbalance::Imbalance; RewardRemainderUnbalanced::mutate(|v| { *v += amount.peek(); }); @@ -415,7 +414,7 @@ ord_parameter_types! { parameter_types! { pub static UseLegacyEraPayout: bool = false; pub static RemainderRatio: Perbill = Perbill::from_percent(50); - pub static MaxEraDuration: u64 = 0; + pub static MaxEraDuration: u64 = time_per_era() * 7; pub const MaxPruningItems: u32 = 100; pub const DapPalletId: frame_support::PalletId = frame_support::PalletId(*b"dap/buff"); pub const TestIssuanceCadence: u64 = 0; // drip every block From 864f440965fa2372a5e5765a96047b79617eeadf Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 22:18:30 +0200 Subject: [PATCH 54/87] guard against destroy if pot was never created --- substrate/frame/staking-async/src/reward.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index 337cf66968acd..b999233fd0431 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -37,7 +37,13 @@ pub struct EraRewardManager(core::marker::PhantomData); impl EraRewardManager { /// Creates an era pot account by adding a provider reference. + /// + /// Should only be called in non-minting mode (`DisableMinting = true`). pub(crate) fn create(era: EraIndex, pot_type: EraPotType) -> T::AccountId { + debug_assert!( + T::DisableMinting::get(), + "Era pots should only be created when DisableMinting is true" + ); let pot_account = T::EraPots::era_pot_account(era, pot_type); frame_system::Pallet::::inc_providers(&pot_account); pot_account @@ -86,8 +92,16 @@ impl EraRewardManager { } /// Destroys an era pot by withdrawing unclaimed rewards and removing the provider. + /// + /// No-op if the pot was never created (e.g. in legacy minting mode). pub(crate) fn destroy(era: EraIndex, pot_type: EraPotType) { let pot_account = T::EraPots::era_pot_account(era, pot_type); + + // Skip if pot was never created (legacy mode doesn't create pots). + if frame_system::Pallet::::providers(&pot_account) == 0 { + return; + } + let remaining = T::Currency::balance(&pot_account); if !remaining.is_zero() { From 39caae013fe244080de2fe10e7671b6eacb442d4 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 22:22:27 +0200 Subject: [PATCH 55/87] rename ValidatorMissingPayee to just MissingPayee --- substrate/frame/staking-async/src/pallet/impls.rs | 2 +- substrate/frame/staking-async/src/pallet/mod.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 66060b4ef3fa4..bdb8d141e7e08 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -579,7 +579,7 @@ impl Pallet { None => { defensive!("Staker missing payee"); Self::deposit_event(Event::::Unexpected( - UnexpectedKind::ValidatorMissingPayee { era }, + UnexpectedKind::MissingPayee { era }, )); return None; }, diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 0f47bae01904c..98a6ef3c3bec5 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -1358,8 +1358,8 @@ pub mod pallet { UnknownValidatorActivation, /// Failed to proceed paged election due to weight limits PagedElectionOutOfWeight { page: PageIndex, required: Weight, had: Weight }, - /// Validator payee is missing when paying rewards. - ValidatorMissingPayee { era: EraIndex }, + /// Payee not set for a staker when paying rewards. + MissingPayee { era: EraIndex }, } #[pallet::error] From 6ededb6dae1918bea6ddb169f38d1720bfd713a0 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 22:26:10 +0200 Subject: [PATCH 56/87] add stash to missing payee --- substrate/frame/staking-async/src/pallet/impls.rs | 2 +- substrate/frame/staking-async/src/pallet/mod.rs | 10 ++++++---- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index bdb8d141e7e08..d89dd3a4aa019 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -579,7 +579,7 @@ impl Pallet { None => { defensive!("Staker missing payee"); Self::deposit_event(Event::::Unexpected( - UnexpectedKind::MissingPayee { era }, + UnexpectedKind::MissingPayee { era, stash: stash.clone() }, )); return None; }, diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 98a6ef3c3bec5..266132ecef96c 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -1332,7 +1332,7 @@ pub mod pallet { }, /// Something occurred that should never happen under normal operation. /// Logged as an event for fail-safe observability. - Unexpected(UnexpectedKind), + Unexpected(UnexpectedKind), /// An offence was reported that was too old to be processed, and thus was dropped. OffenceTooOld { offence_era: EraIndex, @@ -1350,8 +1350,10 @@ pub mod pallet { /// These variants are emitted as [`Event::Unexpected`] and indicate a defensive check has /// failed. While these should never occur under normal operation, they are useful for /// diagnosing issues in production or test environments. - #[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, Debug)] - pub enum UnexpectedKind { + #[derive(Clone, Encode, Decode, DecodeWithMemTracking, PartialEq, TypeInfo, DebugNoBound)] + #[codec(mel_bound())] + #[scale_info(skip_type_params(T))] + pub enum UnexpectedKind { /// Emitted when calculated era duration exceeds the configured maximum. EraDurationBoundExceeded, /// Received a validator activation event that is not recognized. @@ -1359,7 +1361,7 @@ pub mod pallet { /// Failed to proceed paged election due to weight limits PagedElectionOutOfWeight { page: PageIndex, required: Weight, had: Weight }, /// Payee not set for a staker when paying rewards. - MissingPayee { era: EraIndex }, + MissingPayee { era: EraIndex, stash: T::AccountId }, } #[pallet::error] From 2552fb6a8e0cb53aa2b9223a3f00d449e815e548 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 22:26:43 +0200 Subject: [PATCH 57/87] fmt --- substrate/frame/staking-async/src/pallet/impls.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index d89dd3a4aa019..2a1e24cfa9db0 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -578,9 +578,10 @@ impl Pallet { Some(d) => d, None => { defensive!("Staker missing payee"); - Self::deposit_event(Event::::Unexpected( - UnexpectedKind::MissingPayee { era, stash: stash.clone() }, - )); + Self::deposit_event(Event::::Unexpected(UnexpectedKind::MissingPayee { + era, + stash: stash.clone(), + })); return None; }, }; From 4007ee124f4e8f5d58721e6dcf38e58401c1042a Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 22:37:07 +0200 Subject: [PATCH 58/87] ensure if guard set, disable minting in config is always set --- substrate/frame/staking-async/src/pallet/impls.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 2a1e24cfa9db0..b4cc174bf1cfa 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -1973,9 +1973,18 @@ impl Pallet { /// Checks consistency of the reward mode configuration and storage. /// - /// In non-minting mode (`DisableMinting = true`): - /// - All eras >= `DisableMintingGuard` within `HistoryDepth` should have reward pots. + /// - If `DisableMintingGuard` is set, `DisableMinting` must be `true` (irreversible). + /// - In non-minting mode, all eras >= guard within `HistoryDepth` should have pots. fn check_reward_mode_consistency() -> Result<(), TryRuntimeError> { + // If the guard is set, DisableMinting must be true. Catching "switched back" misconfig. + if DisableMintingGuard::::get().is_some() { + ensure!( + T::DisableMinting::get(), + "DisableMintingGuard is set but DisableMinting is false. \ + Switching from non-minting back to legacy mode is not supported." + ); + } + if T::DisableMinting::get() { if let Some(guard_era) = DisableMintingGuard::::get() { let active_era = crate::session_rotation::Rotator::::active_era(); From 520eddf898ae4bd4be554c911baface24efcd051 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 23:11:51 +0200 Subject: [PATCH 59/87] e2e tests --- .../integration-tests/src/ah/test.rs | 85 ++++++++++++++++++- .../integration-tests/src/ah/weights.rs | 2 +- .../staking-async/src/tests/legacy_reward.rs | 53 ++++++++++++ 3 files changed, 138 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 80c6878e9cbe8..a1b2a37b1e778 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -25,7 +25,8 @@ use pallet_election_provider_multi_block::{ }; use pallet_staking_async::{ self as staking_async, session_rotation::Rotator, ActiveEra, ActiveEraInfo, CurrentEra, - Event as StakingEvent, + DisableMintingGuard, EraPotAccountProvider, EraPotType, ErasValidatorReward, Event as StakingEvent, + GeneralPotAccountProvider, GeneralPotType, SequentialTest, }; use pallet_staking_async_rc_client::{ self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport, @@ -2110,3 +2111,85 @@ mod session_keys { }); } } + +/// Test that DAP-mode era payout works end-to-end: era rotation creates funded pots, +/// payouts transfer from pots, unclaimed rewards are cleaned up. +#[test] +fn dap_era_payout_e2e() { + ExtBuilder::default().local_queue().build().execute_with(|| { + let general_pot = SequentialTest::general_pot_account(GeneralPotType::StakerRewards); + let reward_amount: Balance = 10_000; + let ed = 1; + + // Fund the general staker pot so snapshot has something to transfer. + >::mint_into( + &general_pot, + ed + reward_amount, + ) + .unwrap(); + + // Roll to era 1. + let validators = roll_until_next_active(1); + assert!(!validators.is_empty()); + assert_eq!(Rotator::::active_era(), 1); + + // Era 0 should have a pot with funds from the snapshot. + assert!(DisableMintingGuard::::get().is_some()); + let era_pot = SequentialTest::era_pot_account(0, EraPotType::StakerRewards); + assert!(System::providers(&era_pot) > 0); + assert!(ErasValidatorReward::::get(0).unwrap() > 0); + + // Fund pot again for era 1 snapshot. + >::mint_into( + &general_pot, + reward_amount, + ) + .unwrap(); + + // Add reward points for era 1 validators so payout has something to pay. + staking_async::ErasRewardPoints::::mutate(1, |points| { + points.total = 4; + points.individual.try_insert(3, 1).unwrap(); + points.individual.try_insert(5, 1).unwrap(); + points.individual.try_insert(6, 1).unwrap(); + points.individual.try_insert(8, 1).unwrap(); + }); + + // Roll to era 2 so era 1 becomes claimable. + roll_until_next_active(7); + assert_eq!(Rotator::::active_era(), 2); + + let payout_era = 1; + assert!(ErasValidatorReward::::get(payout_era).unwrap() > 0); + + // Drain events before payout. + let _ = staking_events_since_last_call(); + + // Payout era 1 — should transfer from pot, not mint. + let pre_issuance = + >::total_issuance(); + + // Validator 3 was elected (default election picks [3, 5, 6, 8]). + assert_ok!(staking_async::Pallet::::payout_stakers( + RuntimeOrigin::signed(999), + 3, + payout_era, + )); + + // Issuance unchanged — transfer from pot, not mint. + let post_issuance = + >::total_issuance(); + assert_eq!(post_issuance, pre_issuance); + + // Validator received reward (payout started, even if zero points — the pot transfer worked). + let events = staking_events_since_last_call(); + assert!( + events.iter().any(|e| matches!( + e, + StakingEvent::PayoutStarted { validator_stash: 3, .. } + )), + "Expected PayoutStarted for validator 3, got: {:?}", + events + ); + }); +} diff --git a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs index b63e5b4660036..8e8b07acf6c9d 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs @@ -172,7 +172,7 @@ impl pallet_staking_async::WeightInfo for StakingAsyncWeightInfo { unreachable!() } fn payout_stakers_alive_staked(_: u32) -> Weight { - unreachable!() + Weight::default() } fn rebond(_: u32) -> Weight { unreachable!() diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs index bbd86bd87edd2..040c38fd5bd03 100644 --- a/substrate/frame/staking-async/src/tests/legacy_reward.rs +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -120,3 +120,56 @@ fn legacy_guard_stays_unset_across_eras() { assert_eq!(DisableMintingGuard::::get(), None); }); } + +#[test] +fn legacy_to_dap_migration_flow() { + // Start in legacy mode, run a few eras, then switch to DAP mode and verify + // old-era payouts still work (legacy mint) while new-era payouts use pots. + ExtBuilder::default().legacy_reward_mode().build_and_execute(|| { + // GIVEN: legacy mode, era 1 with reward points. + Staking::reward_by_ids(vec![(11, 1)]); + Session::roll_until_active_era(2); + + let legacy_era = 1; + assert_eq!(DisableMintingGuard::::get(), None); + assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(legacy_era)); + assert!(ErasValidatorReward::::get(legacy_era).unwrap() > 0); + + // WHEN: switch to DAP mode. + UseLegacyEraPayout::set(false); + // Set up DAP infrastructure. + pallet_dap::BudgetAllocation::::put(default_budget()); + pallet_dap::LastIssuanceTimestamp::::put(session_mock::Timestamp::get()); + let ed = ExistentialDeposit::get(); + let _ = >::mint_into( + &general_staker_pot(), + ed, + ); + let dap_buffer = + as sp_staking::budget::BudgetRecipient>::pot_account(); + let _ = >::mint_into( + &dap_buffer, + ed, + ); + + // Run more eras in DAP mode. + Staking::reward_by_ids(vec![(11, 1)]); + Session::roll_until_active_era(3); + + let dap_era = 2; + + // THEN: guard is now set (DAP snapshotted successfully). + assert!(DisableMintingGuard::::get().is_some()); + // New era has a pot. + assert!(crate::reward::EraRewardManager::::has_staker_rewards_pot(dap_era)); + + // THEN: old era payout (legacy) still works — no pot, uses mint. + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, legacy_era)); + let pre_issuance = pallet_balances::TotalIssuance::::get(); + + // THEN: new era payout (DAP) works — uses pot transfer. + assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, dap_era)); + // DAP payout doesn't change issuance (transfer, not mint). + assert_eq!(pallet_balances::TotalIssuance::::get(), pre_issuance); + }); +} From c01c326736393b653477920fe7d71d92df892759 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 23:16:55 +0200 Subject: [PATCH 60/87] improve legacy -> dap mode test --- substrate/frame/staking-async/src/mock.rs | 33 ++++++++++--------- .../staking-async/src/tests/legacy_reward.rs | 19 +++-------- 2 files changed, 22 insertions(+), 30 deletions(-) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index bcc7654d28c4f..3d488365c8dc4 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -504,6 +504,22 @@ pub(crate) fn default_budget() -> pallet_dap::BudgetAllocationMap { build_budget(&[(staker_reward_key(), 50), (buffer_key(), 50)]) } +/// Set up DAP infrastructure: budget allocation, timestamp, fund pots with ED. +pub(crate) fn setup_dap() { + pallet_dap::BudgetAllocation::::put(default_budget()); + pallet_dap::LastIssuanceTimestamp::::put(session_mock::Timestamp::get()); + let ed = ExistentialDeposit::get(); + >::mint_into( + &general_staker_pot(), + ed, + ) + .expect("mint general staker pot"); + let dap_buffer = + as sp_staking::budget::BudgetRecipient>::pot_account(); + >::mint_into(&dap_buffer, ed) + .expect("mint dap buffer"); +} + impl Config for Test { type OldCurrency = Balances; type Currency = Balances; @@ -798,23 +814,8 @@ impl ExtBuilder { crate::AreNominatorsSlashable::::put(nominators_slashable); if !UseLegacyEraPayout::get() { - // DAP mode: set up budget, fund pots, enable minting guard. crate::DisableMintingGuard::::put(0); - pallet_dap::BudgetAllocation::::put(default_budget()); - pallet_dap::LastIssuanceTimestamp::::put(INIT_TIMESTAMP); - let ed = ExistentialDeposit::get(); - >::mint_into( - &general_staker_pot(), - ed, - ) - .expect("mint general staker pot"); - let dap_buffer = - as BudgetRecipient>::pot_account(); - >::mint_into( - &dap_buffer, - ed, - ) - .expect("mint dap buffer"); + setup_dap(); } // Legacy mode: no DAP setup needed. EraPayout computes inflation, staking mints. session_mock::Session::roll_until_active_era(1); diff --git a/substrate/frame/staking-async/src/tests/legacy_reward.rs b/substrate/frame/staking-async/src/tests/legacy_reward.rs index 040c38fd5bd03..5b87414095b71 100644 --- a/substrate/frame/staking-async/src/tests/legacy_reward.rs +++ b/substrate/frame/staking-async/src/tests/legacy_reward.rs @@ -137,20 +137,7 @@ fn legacy_to_dap_migration_flow() { // WHEN: switch to DAP mode. UseLegacyEraPayout::set(false); - // Set up DAP infrastructure. - pallet_dap::BudgetAllocation::::put(default_budget()); - pallet_dap::LastIssuanceTimestamp::::put(session_mock::Timestamp::get()); - let ed = ExistentialDeposit::get(); - let _ = >::mint_into( - &general_staker_pot(), - ed, - ); - let dap_buffer = - as sp_staking::budget::BudgetRecipient>::pot_account(); - let _ = >::mint_into( - &dap_buffer, - ed, - ); + setup_dap(); // Run more eras in DAP mode. Staking::reward_by_ids(vec![(11, 1)]); @@ -164,7 +151,11 @@ fn legacy_to_dap_migration_flow() { assert!(crate::reward::EraRewardManager::::has_staker_rewards_pot(dap_era)); // THEN: old era payout (legacy) still works — no pot, uses mint. + let pre_legacy_issuance = pallet_balances::TotalIssuance::::get(); assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), 11, legacy_era)); + // Legacy payout mints — issuance increases. + assert!(pallet_balances::TotalIssuance::::get() > pre_legacy_issuance); + let pre_issuance = pallet_balances::TotalIssuance::::get(); // THEN: new era payout (DAP) works — uses pot transfer. From 8b1468126016cbbc59bd48e19b847693942045a9 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 23:17:55 +0200 Subject: [PATCH 61/87] fmt --- .../staking-async/integration-tests/src/ah/test.rs | 14 +++++++------- substrate/frame/staking-async/src/mock.rs | 7 ++----- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index a1b2a37b1e778..35aae9efbeeb9 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -25,8 +25,8 @@ use pallet_election_provider_multi_block::{ }; use pallet_staking_async::{ self as staking_async, session_rotation::Rotator, ActiveEra, ActiveEraInfo, CurrentEra, - DisableMintingGuard, EraPotAccountProvider, EraPotType, ErasValidatorReward, Event as StakingEvent, - GeneralPotAccountProvider, GeneralPotType, SequentialTest, + DisableMintingGuard, EraPotAccountProvider, EraPotType, ErasValidatorReward, + Event as StakingEvent, GeneralPotAccountProvider, GeneralPotType, SequentialTest, }; use pallet_staking_async_rc_client::{ self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport, @@ -2181,13 +2181,13 @@ fn dap_era_payout_e2e() { >::total_issuance(); assert_eq!(post_issuance, pre_issuance); - // Validator received reward (payout started, even if zero points — the pot transfer worked). + // Validator received reward (payout started, even if zero points — the pot transfer + // worked). let events = staking_events_since_last_call(); assert!( - events.iter().any(|e| matches!( - e, - StakingEvent::PayoutStarted { validator_stash: 3, .. } - )), + events + .iter() + .any(|e| matches!(e, StakingEvent::PayoutStarted { validator_stash: 3, .. })), "Expected PayoutStarted for validator 3, got: {:?}", events ); diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 3d488365c8dc4..01d056918d4f3 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -509,11 +509,8 @@ pub(crate) fn setup_dap() { pallet_dap::BudgetAllocation::::put(default_budget()); pallet_dap::LastIssuanceTimestamp::::put(session_mock::Timestamp::get()); let ed = ExistentialDeposit::get(); - >::mint_into( - &general_staker_pot(), - ed, - ) - .expect("mint general staker pot"); + >::mint_into(&general_staker_pot(), ed) + .expect("mint general staker pot"); let dap_buffer = as sp_staking::budget::BudgetRecipient>::pot_account(); >::mint_into(&dap_buffer, ed) From 8f43b9701485bbb6aa9bc1e6160d7e3931105749 Mon Sep 17 00:00:00 2001 From: Ankan Date: Tue, 7 Apr 2026 23:24:55 +0200 Subject: [PATCH 62/87] mock setup to switch impls --- .../integration-tests/src/ah/mock.rs | 59 ++++++- .../integration-tests/src/ah/test.rs | 159 +++++++++++------- 2 files changed, 153 insertions(+), 65 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index b0819f43a3778..392619487e74d 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -69,15 +69,20 @@ parameter_types! { pub static NextPollWeight: Option = None; } +/// Block time in milliseconds +pub const BLOCK_TIME: u64 = 12_000; + pub fn roll_next() { let now = System::block_number(); let next = now + 1; + MockTime::set(MockTime::get() + BLOCK_TIME); System::set_block_number(next); // Re-init frame-system, as execute would do. This resets the block weight usage counter, as we // are using a realistic weight meter here. frame_system::BlockWeight::::kill(); + System::register_extra_weight_unchecked(Dap::on_initialize(next), DispatchClass::Mandatory); System::register_extra_weight_unchecked(Staking::on_initialize(next), DispatchClass::Mandatory); System::register_extra_weight_unchecked( RcClient::on_initialize(next), @@ -255,7 +260,7 @@ pub(crate) fn roll_until_next_active(mut end_index: SessionIndex) -> Vec; + type DisableMinting = DisableMintingMode; type UnclaimedRewardHandler = (); type GeneralPots = pallet_staking_async::SequentialTest; type EraPots = pallet_staking_async::SequentialTest; - type StakerRewardCalculator = (); + type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type EventListeners = (); type Reward = (); type Slash = Dap; @@ -514,9 +519,10 @@ impl pallet_staking_async_rc_client::Config for Runtime { parameter_types! { pub const DapPalletId: frame_support::PalletId = frame_support::PalletId(*b"dap/buff"); - pub const DapIssuanceCadence: u64 = 60_000; + pub const DapIssuanceCadence: u64 = 0; // drip every block pub const DapMaxElapsedPerDrip: u64 = 600_000; pub static MockTime: u64 = 0; + pub static UseLegacyEraPayout: bool = false; } impl frame_support::traits::Time for MockTime { @@ -526,11 +532,50 @@ impl frame_support::traits::Time for MockTime { } } +/// Switchable EraPayout: returns (0,0) in DAP mode, real values in legacy mode. +pub struct TestEraPayout; +impl pallet_staking_async::EraPayout for TestEraPayout { + fn era_payout( + _total_staked: Balance, + _total_issuance: Balance, + era_duration_millis: u64, + ) -> (Balance, Balance) { + if !UseLegacyEraPayout::get() { + (0, 0) + } else { + // 1 token per millisecond, 50/50 split. + let total = era_duration_millis as Balance; + let remainder = total / 2; + let stakers = total - remainder; + (stakers, remainder) + } + } +} + +/// Switchable DisableMinting: follows UseLegacyEraPayout. +pub struct DisableMintingMode; +impl frame_support::traits::Get for DisableMintingMode { + fn get() -> bool { + !UseLegacyEraPayout::get() + } +} + +/// Simple issuance: 1 token per millisecond elapsed. +pub struct OneTokenPerMillisecond; +impl sp_staking::budget::IssuanceCurve for OneTokenPerMillisecond { + fn issue(_total_issuance: Balance, elapsed_millis: u64) -> Balance { + elapsed_millis as Balance + } +} + impl pallet_dap::Config for Runtime { type Currency = Balances; type PalletId = DapPalletId; - type IssuanceCurve = (); - type BudgetRecipients = (pallet_dap::Pallet,); + type IssuanceCurve = OneTokenPerMillisecond; + type BudgetRecipients = ( + pallet_dap::Pallet, + pallet_staking_async::StakerRewardRecipient, + ); type Time = MockTime; type IssuanceCadence = DapIssuanceCadence; type MaxElapsedPerDrip = DapMaxElapsedPerDrip; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 35aae9efbeeb9..542952294ac3f 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -31,6 +31,7 @@ use pallet_staking_async::{ use pallet_staking_async_rc_client::{ self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport, }; +use frame_support::traits::fungible::{Inspect, Mutate}; // Tests that are specific to Asset Hub. #[test] @@ -1339,7 +1340,9 @@ mod poll_operations { // election for era 2 has started assert_eq!(CurrentEra::::get(), Some(2)); assert_eq!(Rotator::::active_era_start_session_index(), 7); - assert_eq!(ActiveEra::::get(), Some(ActiveEraInfo { index: 1, start: Some(1000) })); + let active_era = ActiveEra::::get().unwrap(); + assert_eq!(active_era.index, 1); + assert!(active_era.start.is_some()); assert!(OutgoingValidatorSet::::get().is_none()); // roll until signed and submit a solution. @@ -2112,84 +2115,124 @@ mod session_keys { } } -/// Test that DAP-mode era payout works end-to-end: era rotation creates funded pots, -/// payouts transfer from pots, unclaimed rewards are cleaned up. +/// E2E: start in legacy mode, run an era with legacy payouts, switch to DAP mode +/// via governance, verify DAP drips fund era pots and payouts transfer without minting. #[test] -fn dap_era_payout_e2e() { +fn legacy_to_dap_era_payout_e2e() { ExtBuilder::default().local_queue().build().execute_with(|| { - let general_pot = SequentialTest::general_pot_account(GeneralPotType::StakerRewards); - let reward_amount: Balance = 10_000; - let ed = 1; + let val_a: AccountId = 3; // validator (elected) - // Fund the general staker pot so snapshot has something to transfer. - >::mint_into( - &general_pot, - ed + reward_amount, - ) - .unwrap(); + // GIVEN: legacy mode. + UseLegacyEraPayout::set(true); + // Set payees for elected validators [3, 5, 6, 8] (genesis doesn't set them). + for v in [3u64, 5, 6, 8] { + staking_async::Payee::::insert(v, staking_async::RewardDestination::Stash); + } - // Roll to era 1. - let validators = roll_until_next_active(1); - assert!(!validators.is_empty()); + // WHEN: roll to era 1 (legacy end_era computes inflation via EraPayout). + roll_until_next_active(1); assert_eq!(Rotator::::active_era(), 1); - // Era 0 should have a pot with funds from the snapshot. - assert!(DisableMintingGuard::::get().is_some()); - let era_pot = SequentialTest::era_pot_account(0, EraPotType::StakerRewards); - assert!(System::providers(&era_pot) > 0); - assert!(ErasValidatorReward::::get(0).unwrap() > 0); - - // Fund pot again for era 1 snapshot. - >::mint_into( - &general_pot, - reward_amount, - ) - .unwrap(); + // THEN: legacy mode — no pots, no guard. + assert_eq!(DisableMintingGuard::::get(), None); + assert_eq!( + System::providers(&SequentialTest::era_pot_account(0, EraPotType::StakerRewards)), + 0 + ); - // Add reward points for era 1 validators so payout has something to pay. + // Reward points for era 1 (era 0 has no exposure — pre-election). staking_async::ErasRewardPoints::::mutate(1, |points| { points.total = 4; - points.individual.try_insert(3, 1).unwrap(); - points.individual.try_insert(5, 1).unwrap(); - points.individual.try_insert(6, 1).unwrap(); - points.individual.try_insert(8, 1).unwrap(); + for v in [3, 5, 6, 8] { points.individual.try_insert(v, 1).unwrap(); } }); - // Roll to era 2 so era 1 becomes claimable. + // WHEN: roll to era 2 (still legacy mode). roll_until_next_active(7); assert_eq!(Rotator::::active_era(), 2); - let payout_era = 1; - assert!(ErasValidatorReward::::get(payout_era).unwrap() > 0); + // THEN: EraPaid for era 1 with non-zero remainder (legacy 50/50 split). + let events = staking_events_since_last_call(); + assert!(events.iter().any(|e| matches!( + e, StakingEvent::EraPaid { era_index: 1, remainder, .. } if *remainder > 0 + ))); + + // WHEN: switch to DAP mode (simulates runtime upgrade). + UseLegacyEraPayout::set(false); + + // Governance: set DAP budget (85% stakers, 15% buffer). + let staker_key = + as + sp_staking::budget::BudgetRecipient>::budget_key(); + let buffer_key = + as sp_staking::budget::BudgetRecipient>::budget_key(); + let mut budget = pallet_dap::BudgetAllocationMap::new(); + budget.try_insert(staker_key, Perbill::from_percent(85)).unwrap(); + budget.try_insert(buffer_key, Perbill::from_percent(15)).unwrap(); + assert_ok!(pallet_dap::Pallet::::set_budget_allocation( + RuntimeOrigin::root(), budget, + )); + + // Initialize DAP and fund general staker pot with ED. + pallet_dap::LastIssuanceTimestamp::::put(MockTime::get()); + let general_pot = SequentialTest::general_pot_account(GeneralPotType::StakerRewards); + Balances::mint_into(&general_pot, 1).unwrap(); + + // WHEN: payout era 1 (legacy era) while already in DAP mode. + // Era 1 has no pot (created in legacy mode) — falls back to legacy mint. + let pre_issuance = Balances::total_issuance(); + assert_ok!(staking_async::Pallet::::payout_stakers( + RuntimeOrigin::signed(999), val_a, 1, + )); + // THEN: legacy payout mints even though DAP mode is active. + assert!(Balances::total_issuance() > pre_issuance); + + // Reward points for era 2. + staking_async::ErasRewardPoints::::mutate(2, |points| { + points.total = 4; + for v in [3, 5, 6, 8] { points.individual.try_insert(v, 1).unwrap(); } + }); - // Drain events before payout. + // WHEN: roll to era 3. Each block drips 12_000ms of inflation via DAP. let _ = staking_events_since_last_call(); + roll_until_next_active(13); + assert_eq!(Rotator::::active_era(), 3); - // Payout era 1 — should transfer from pot, not mint. - let pre_issuance = - >::total_issuance(); + // THEN: guard set, era 2 has pot with funds. + assert!(DisableMintingGuard::::get().is_some()); + let era_2_pot = SequentialTest::era_pot_account(2, EraPotType::StakerRewards); + assert!(System::providers(&era_2_pot) > 0); + let era_2_reward = ErasValidatorReward::::get(2).unwrap(); + assert!(era_2_reward > 0); + + // THEN: EraPaid with remainder=0 (DAP mode). + let events = staking_events_since_last_call(); + assert!(events.iter().any(|e| matches!( + e, StakingEvent::EraPaid { era_index: 2, remainder, .. } if *remainder == 0 + ))); + + // WHEN: payout era 2 (DAP pot transfer). + let pre_issuance = Balances::total_issuance(); + let balance_before = Balances::total_balance(&val_a); + let _ = staking_events_since_last_call(); - // Validator 3 was elected (default election picks [3, 5, 6, 8]). assert_ok!(staking_async::Pallet::::payout_stakers( - RuntimeOrigin::signed(999), - 3, - payout_era, + RuntimeOrigin::signed(999), val_a, 2, )); - // Issuance unchanged — transfer from pot, not mint. - let post_issuance = - >::total_issuance(); - assert_eq!(post_issuance, pre_issuance); - - // Validator received reward (payout started, even if zero points — the pot transfer - // worked). + // THEN: issuance unchanged (transfer, not mint). + assert_eq!(Balances::total_issuance(), pre_issuance); + // THEN: validator balance increased. + assert!(Balances::total_balance(&val_a) > balance_before); + // THEN: correct events. let events = staking_events_since_last_call(); - assert!( - events - .iter() - .any(|e| matches!(e, StakingEvent::PayoutStarted { validator_stash: 3, .. })), - "Expected PayoutStarted for validator 3, got: {:?}", - events - ); + assert!(events.iter().any(|e| matches!( + e, StakingEvent::PayoutStarted { era_index: 2, validator_stash, .. } + if *validator_stash == val_a + ))); + assert!(events.iter().any(|e| matches!( + e, StakingEvent::Rewarded { stash, .. } if *stash == val_a + ))); }); + + UseLegacyEraPayout::set(false); } From 14980ee985e5a6c16ac60e713295509dc17ef3ae Mon Sep 17 00:00:00 2001 From: Ankan Date: Wed, 8 Apr 2026 00:29:51 +0200 Subject: [PATCH 63/87] fmt --- .../integration-tests/src/ah/mock.rs | 3 ++- .../integration-tests/src/ah/test.rs | 26 ++++++++++++------- 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index 392619487e74d..34dc88f8c7db8 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -469,7 +469,8 @@ impl pallet_staking_async::Config for Runtime { type UnclaimedRewardHandler = (); type GeneralPots = pallet_staking_async::SequentialTest; type EraPots = pallet_staking_async::SequentialTest; - type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; + type StakerRewardCalculator = + pallet_staking_async::reward::DefaultStakerRewardCalculator; type EventListeners = (); type Reward = (); type Slash = Dap; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 542952294ac3f..bd9cbebb3f0ff 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -18,7 +18,10 @@ use crate::{ah::mock::*, rc, shared}; use frame::prelude::Perbill; use frame_election_provider_support::Weight; -use frame_support::{assert_ok, hypothetically, traits::fungible::hold::Inspect as HoldInspect}; +use frame_support::{ + assert_ok, hypothetically, + traits::fungible::{hold::Inspect as HoldInspect, Inspect, Mutate}, +}; use pallet_election_provider_multi_block::{ unsigned::miner::OffchainWorkerMiner, verifier::Event as VerifierEvent, CurrentPhase, ElectionScore, Event as ElectionEvent, Phase, @@ -31,7 +34,6 @@ use pallet_staking_async::{ use pallet_staking_async_rc_client::{ self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport, }; -use frame_support::traits::fungible::{Inspect, Mutate}; // Tests that are specific to Asset Hub. #[test] @@ -2143,7 +2145,9 @@ fn legacy_to_dap_era_payout_e2e() { // Reward points for era 1 (era 0 has no exposure — pre-election). staking_async::ErasRewardPoints::::mutate(1, |points| { points.total = 4; - for v in [3, 5, 6, 8] { points.individual.try_insert(v, 1).unwrap(); } + for v in [3, 5, 6, 8] { + points.individual.try_insert(v, 1).unwrap(); + } }); // WHEN: roll to era 2 (still legacy mode). @@ -2168,9 +2172,7 @@ fn legacy_to_dap_era_payout_e2e() { let mut budget = pallet_dap::BudgetAllocationMap::new(); budget.try_insert(staker_key, Perbill::from_percent(85)).unwrap(); budget.try_insert(buffer_key, Perbill::from_percent(15)).unwrap(); - assert_ok!(pallet_dap::Pallet::::set_budget_allocation( - RuntimeOrigin::root(), budget, - )); + assert_ok!(pallet_dap::Pallet::::set_budget_allocation(RuntimeOrigin::root(), budget,)); // Initialize DAP and fund general staker pot with ED. pallet_dap::LastIssuanceTimestamp::::put(MockTime::get()); @@ -2181,7 +2183,9 @@ fn legacy_to_dap_era_payout_e2e() { // Era 1 has no pot (created in legacy mode) — falls back to legacy mint. let pre_issuance = Balances::total_issuance(); assert_ok!(staking_async::Pallet::::payout_stakers( - RuntimeOrigin::signed(999), val_a, 1, + RuntimeOrigin::signed(999), + val_a, + 1, )); // THEN: legacy payout mints even though DAP mode is active. assert!(Balances::total_issuance() > pre_issuance); @@ -2189,7 +2193,9 @@ fn legacy_to_dap_era_payout_e2e() { // Reward points for era 2. staking_async::ErasRewardPoints::::mutate(2, |points| { points.total = 4; - for v in [3, 5, 6, 8] { points.individual.try_insert(v, 1).unwrap(); } + for v in [3, 5, 6, 8] { + points.individual.try_insert(v, 1).unwrap(); + } }); // WHEN: roll to era 3. Each block drips 12_000ms of inflation via DAP. @@ -2216,7 +2222,9 @@ fn legacy_to_dap_era_payout_e2e() { let _ = staking_events_since_last_call(); assert_ok!(staking_async::Pallet::::payout_stakers( - RuntimeOrigin::signed(999), val_a, 2, + RuntimeOrigin::signed(999), + val_a, + 2, )); // THEN: issuance unchanged (transfer, not mint). From 29c8ee5e12b0ca67247065f3868fcd1520e5ad98 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 9 Apr 2026 00:47:31 +0200 Subject: [PATCH 64/87] papi e2e test --- .../runtimes/papi-tests/src/test-case.ts | 3 +- .../papi-tests/tests/dap-dripping.test.ts | 87 +++++++++++++++++++ .../runtimes/papi-tests/zn-m.toml | 2 +- .../runtimes/papi-tests/zn-s.toml | 2 +- 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts diff --git a/substrate/frame/staking-async/runtimes/papi-tests/src/test-case.ts b/substrate/frame/staking-async/runtimes/papi-tests/src/test-case.ts index 40466bbc276ca..3afec5255b7bf 100644 --- a/substrate/frame/staking-async/runtimes/papi-tests/src/test-case.ts +++ b/substrate/frame/staking-async/runtimes/papi-tests/src/test-case.ts @@ -415,7 +415,8 @@ export async function runTest( e.event.type == "MultiBlockElection" || e.event.type == "MultiBlockElectionSigned" || e.event.type == "MultiBlockElectionVerifier" || - e.event.type == "StakingRcClient" + e.event.type == "StakingRcClient" || + e.event.type == "Dap" ) .map((e) => ({ module: e.event.type, diff --git a/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts b/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts new file mode 100644 index 0000000000000..121b7d35a0cc4 --- /dev/null +++ b/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts @@ -0,0 +1,87 @@ +import { test, expect } from "bun:test"; +import { Presets } from "../src"; +import { runPresetUntilLaunched } from "../src/cmd"; +import { Chain, EventOutcome, Observe, runTest, TestCase } from "../src/test-case"; +import { Binary } from "polkadot-api"; +import { alice, getApis, GlobalTimeout, logger } from "../src/utils"; + +const PRESET: Presets = Presets.FakeDot; + +/// Set budget allocation via sudo: 85% staker_rewards, 15% buffer. +/// Keys must be sorted (BTreeMap) and Perbill values must sum to 1_000_000_000. +async function setBudgetAllocation(apis: Awaited>): Promise { + const { paraApi } = apis; + + const call = paraApi.tx.Dap.set_budget_allocation({ + new_allocations: [ + [Binary.fromText("buffer"), 150_000_000], + [Binary.fromText("staker_rewards"), 850_000_000], + ], + }).decodedCall; + + const res = await paraApi.tx.Sudo.sudo({ call }).signAndSubmit(alice); + return res.ok; +} + +test( + `DAP dripping and era reward snapshot on ${PRESET}`, + async () => { + const { killZn, paraLog } = await runPresetUntilLaunched(PRESET); + const apis = await getApis(); + + let budgetSet = false; + let dripCount = 0; + + const checkDrip = (x: any) => { + dripCount++; + const minted = BigInt(x.total_minted); + logger.info(`DAP drip #${dripCount}: total_minted=${minted}, elapsed=${x.elapsed_millis}ms`); + return minted > 0n; + }; + + const testCase = new TestCase( + [ + Observe.on(Chain.Relay, "Session", "NewSession") + .byBlock(11) + .onPass(() => { + setBudgetAllocation(apis).then((ok) => { + budgetSet = ok; + logger.info(`Budget allocation set: ${ok}`); + }).catch((err) => { + logger.error(`setBudgetAllocation failed: ${err}`); + }); + }), + + Observe.on(Chain.Parachain, "Dap", "BudgetAllocationUpdated"), + + // Two sequential drip observations to confirm continuous dripping. + Observe.on(Chain.Parachain, "Dap", "IssuanceMinted").withDataCheck(checkDrip), + Observe.on(Chain.Parachain, "Dap", "IssuanceMinted").withDataCheck(checkDrip), + + Observe.on(Chain.Parachain, "Staking", "SessionRotated"), + + // In DAP mode: remainder == 0 (era reward pre-funded by pot, not legacy inflation), + // payout > 0 (transferred from era pot). + Observe.on(Chain.Parachain, "Staking", "EraPaid") + .withDataCheck((x: any) => { + const payout = BigInt(x.validator_payout); + const remainder = BigInt(x.remainder); + logger.info( + `EraPaid: era=${x.era_index}, validator_payout=${payout}, remainder=${remainder}` + ); + return remainder === 0n && payout > 0n; + }), + ].map((s) => s.build()), + true, + () => { + killZn(); + } + ); + + const outcome = await runTest(testCase, apis, paraLog); + expect(budgetSet).toBe(true); + expect(dripCount).toBe(2); + expect(outcome).toEqual(EventOutcome.Done); + }, + { timeout: GlobalTimeout } +); diff --git a/substrate/frame/staking-async/runtimes/papi-tests/zn-m.toml b/substrate/frame/staking-async/runtimes/papi-tests/zn-m.toml index 5057cf5cfc157..16c7caa5400ee 100644 --- a/substrate/frame/staking-async/runtimes/papi-tests/zn-m.toml +++ b/substrate/frame/staking-async/runtimes/papi-tests/zn-m.toml @@ -36,5 +36,5 @@ name = "charlie" rpc_port = 9946 args = [ "--authoring=slot-based", - "-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug", + "-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug,runtime::dap=debug,runtime::staking-async=info", ] diff --git a/substrate/frame/staking-async/runtimes/papi-tests/zn-s.toml b/substrate/frame/staking-async/runtimes/papi-tests/zn-s.toml index d0641b2e2ba24..ee2008fd017ea 100644 --- a/substrate/frame/staking-async/runtimes/papi-tests/zn-s.toml +++ b/substrate/frame/staking-async/runtimes/papi-tests/zn-s.toml @@ -28,5 +28,5 @@ name = "charlie" rpc_port = 9946 args = [ "--authoring=slot-based", - "-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug,xcm=debug,parachain-system=debug,runtime=info", + "-lruntime::system=debug,runtime::multiblock-election=trace,runtime::staking=debug,runtime::staking::rc-client=trace,runtime::rc-client=debug,runtime::dap=debug,runtime::staking-async=info,xcm=debug,parachain-system=debug,runtime=info", ] From 0bfd62cadddc34f33e107a4a05962e58b29ffc61 Mon Sep 17 00:00:00 2001 From: Ankan Date: Thu, 9 Apr 2026 02:27:52 +0200 Subject: [PATCH 65/87] more assertions --- .../papi-tests/tests/dap-dripping.test.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts b/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts index 121b7d35a0cc4..9cd5a6a549eb4 100644 --- a/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts +++ b/substrate/frame/staking-async/runtimes/papi-tests/tests/dap-dripping.test.ts @@ -31,12 +31,26 @@ test( let budgetSet = false; let dripCount = 0; + let perDripStakerReward = 0n; + + // DapIssuanceCadence = 60_000ms, parachain block time = 6_000ms. + // elapsed should be cadence or cadence + one block at most. + const CADENCE_MS = 60_000; + const MAX_ELAPSED_MS = 72_000; const checkDrip = (x: any) => { dripCount++; const minted = BigInt(x.total_minted); - logger.info(`DAP drip #${dripCount}: total_minted=${minted}, elapsed=${x.elapsed_millis}ms`); - return minted > 0n; + const elapsed = Number(x.elapsed_millis); + logger.info(`DAP drip #${dripCount}: total_minted=${minted}, elapsed=${elapsed}ms`); + + // Track 85% staker share from a "clean" drip (exactly at cadence). + if (elapsed === CADENCE_MS && perDripStakerReward === 0n) { + // 85% of total_minted goes to staker pot (Perbill::mul_floor). + perDripStakerReward = (minted * 850_000_000n) / 1_000_000_000n; + } + + return minted > 0n && elapsed >= CADENCE_MS && elapsed <= MAX_ELAPSED_MS; }; const testCase = new TestCase( @@ -62,14 +76,25 @@ test( // In DAP mode: remainder == 0 (era reward pre-funded by pot, not legacy inflation), // payout > 0 (transferred from era pot). + // payout should be multiple drips worth of staker rewards (85% of each drip). Observe.on(Chain.Parachain, "Staking", "EraPaid") .withDataCheck((x: any) => { const payout = BigInt(x.validator_payout); const remainder = BigInt(x.remainder); + + // Verify payout is a whole number of drips worth of staker rewards. + // Rounding from Perbill::mul_floor means payout <= N * per_drip_total * 0.85. + let dripMultiple = 0n; + if (perDripStakerReward > 0n) { + dripMultiple = payout / perDripStakerReward; + } logger.info( - `EraPaid: era=${x.era_index}, validator_payout=${payout}, remainder=${remainder}` + `EraPaid: era=${x.era_index}, validator_payout=${payout}, remainder=${remainder}, ` + + `~${dripMultiple} drips worth of staker rewards` ); - return remainder === 0n && payout > 0n; + + // Payout should represent multiple drips (era spans many blocks). + return remainder === 0n && payout > 0n && dripMultiple >= 3n; }), ].map((s) => s.build()), true, From b71f5b391c182a8f6d9e917620a3aa7d07f5f81b Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 07:23:14 +0200 Subject: [PATCH 66/87] hard assertion --- .../frame/staking-async/integration-tests/src/ah/test.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index bd9cbebb3f0ff..a59929e2363e8 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2154,10 +2154,11 @@ fn legacy_to_dap_era_payout_e2e() { roll_until_next_active(7); assert_eq!(Rotator::::active_era(), 2); - // THEN: EraPaid for era 1 with non-zero remainder (legacy 50/50 split). + // THEN: EraPaid for era 1 with 50/50 split (remainder == validator_payout). let events = staking_events_since_last_call(); assert!(events.iter().any(|e| matches!( - e, StakingEvent::EraPaid { era_index: 1, remainder, .. } if *remainder > 0 + e, StakingEvent::EraPaid { era_index: 1, remainder, validator_payout, .. } + if *remainder == *validator_payout ))); // WHEN: switch to DAP mode (simulates runtime upgrade). From fd3cfe7a11d092739b46f038fb71689d35151f91 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 07:28:10 +0200 Subject: [PATCH 67/87] assert for era 0 --- .../frame/staking-async/integration-tests/src/ah/test.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index a59929e2363e8..a907b88d97ade 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2142,6 +2142,13 @@ fn legacy_to_dap_era_payout_e2e() { 0 ); + // THEN: EraPaid for era 0 with 50/50 split. + let events = staking_events_since_last_call(); + assert!(events.iter().any(|e| matches!( + e, StakingEvent::EraPaid { era_index: 0, remainder, validator_payout, .. } + if *remainder == *validator_payout + ))); + // Reward points for era 1 (era 0 has no exposure — pre-election). staking_async::ErasRewardPoints::::mutate(1, |points| { points.total = 4; From 87f6e3fb5534c8be55318b21b9642b0a36476674 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 07:35:32 +0200 Subject: [PATCH 68/87] assert on all events --- .../integration-tests/src/ah/test.rs | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index a907b88d97ade..98fdf4ce4c34a 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2237,17 +2237,27 @@ fn legacy_to_dap_era_payout_e2e() { // THEN: issuance unchanged (transfer, not mint). assert_eq!(Balances::total_issuance(), pre_issuance); - // THEN: validator balance increased. - assert!(Balances::total_balance(&val_a) > balance_before); - // THEN: correct events. + // THEN: validator gets 1/4 of era reward (4 validators, equal points, 0% commission). + let expected_reward = era_2_reward / 4; + assert_eq!(Balances::total_balance(&val_a) - balance_before, expected_reward); + // THEN: exactly two events — payout started + validator rewarded. let events = staking_events_since_last_call(); - assert!(events.iter().any(|e| matches!( - e, StakingEvent::PayoutStarted { era_index: 2, validator_stash, .. } - if *validator_stash == val_a - ))); - assert!(events.iter().any(|e| matches!( - e, StakingEvent::Rewarded { stash, .. } if *stash == val_a - ))); + assert_eq!( + events, + vec![ + StakingEvent::PayoutStarted { + era_index: 2, + validator_stash: val_a, + page: 0, + next: None, + }, + StakingEvent::Rewarded { + stash: val_a, + dest: staking_async::RewardDestination::Stash, + amount: expected_reward, + }, + ] + ); }); UseLegacyEraPayout::set(false); From da0fb80d3a5c705ee8c124cbd4884d3836caf715 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 07:45:30 +0200 Subject: [PATCH 69/87] check all era events --- .../integration-tests/src/ah/test.rs | 52 +++++++++++++------ 1 file changed, 37 insertions(+), 15 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 98fdf4ce4c34a..fe7483661d291 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2143,11 +2143,18 @@ fn legacy_to_dap_era_payout_e2e() { ); // THEN: EraPaid for era 0 with 50/50 split. - let events = staking_events_since_last_call(); - assert!(events.iter().any(|e| matches!( - e, StakingEvent::EraPaid { era_index: 0, remainder, validator_payout, .. } - if *remainder == *validator_payout - ))); + let era_paid: Vec<_> = staking_events_since_last_call() + .into_iter() + .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) + .collect(); + assert_eq!( + era_paid, + vec![StakingEvent::EraPaid { + era_index: 0, + validator_payout: 162000, + remainder: 162000, + }] + ); // Reward points for era 1 (era 0 has no exposure — pre-election). staking_async::ErasRewardPoints::::mutate(1, |points| { @@ -2161,12 +2168,19 @@ fn legacy_to_dap_era_payout_e2e() { roll_until_next_active(7); assert_eq!(Rotator::::active_era(), 2); - // THEN: EraPaid for era 1 with 50/50 split (remainder == validator_payout). - let events = staking_events_since_last_call(); - assert!(events.iter().any(|e| matches!( - e, StakingEvent::EraPaid { era_index: 1, remainder, validator_payout, .. } - if *remainder == *validator_payout - ))); + // THEN: EraPaid for era 1 with 50/50 split. + let era_paid: Vec<_> = staking_events_since_last_call() + .into_iter() + .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) + .collect(); + assert_eq!( + era_paid, + vec![StakingEvent::EraPaid { + era_index: 1, + validator_payout: 162000, + remainder: 162000, + }] + ); // WHEN: switch to DAP mode (simulates runtime upgrade). UseLegacyEraPayout::set(false); @@ -2219,10 +2233,18 @@ fn legacy_to_dap_era_payout_e2e() { assert!(era_2_reward > 0); // THEN: EraPaid with remainder=0 (DAP mode). - let events = staking_events_since_last_call(); - assert!(events.iter().any(|e| matches!( - e, StakingEvent::EraPaid { era_index: 2, remainder, .. } if *remainder == 0 - ))); + let era_paid: Vec<_> = staking_events_since_last_call() + .into_iter() + .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) + .collect(); + assert_eq!( + era_paid, + vec![StakingEvent::EraPaid { + era_index: 2, + validator_payout: era_2_reward, + remainder: 0, + }] + ); // WHEN: payout era 2 (DAP pot transfer). let pre_issuance = Balances::total_issuance(); From f85c6dc123d2a6f8d7eb4d36714ad6c21a5bd72b Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 07:51:31 +0200 Subject: [PATCH 70/87] assert on all events --- .../integration-tests/src/ah/test.rs | 109 +++++++++++++----- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index fe7483661d291..f1288821d1db4 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2143,17 +2143,24 @@ fn legacy_to_dap_era_payout_e2e() { ); // THEN: EraPaid for era 0 with 50/50 split. - let era_paid: Vec<_> = staking_events_since_last_call() - .into_iter() - .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) - .collect(); assert_eq!( - era_paid, - vec![StakingEvent::EraPaid { - era_index: 0, - validator_payout: 162000, - remainder: 162000, - }] + staking_events_since_last_call(), + vec![ + StakingEvent::SessionRotated { starting_session: 2, active_era: 0, planned_era: 1 }, + StakingEvent::PagedElectionProceeded { page: 2, result: Ok(4) }, + StakingEvent::PagedElectionProceeded { page: 1, result: Ok(0) }, + StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }, + StakingEvent::SessionRotated { starting_session: 3, active_era: 0, planned_era: 1 }, + StakingEvent::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, + StakingEvent::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, + StakingEvent::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 }, + StakingEvent::EraPaid { + era_index: 0, + validator_payout: 162000, + remainder: 162000, + }, + StakingEvent::SessionRotated { starting_session: 7, active_era: 1, planned_era: 2 }, + ] ); // Reward points for era 1 (era 0 has no exposure — pre-election). @@ -2169,17 +2176,28 @@ fn legacy_to_dap_era_payout_e2e() { assert_eq!(Rotator::::active_era(), 2); // THEN: EraPaid for era 1 with 50/50 split. - let era_paid: Vec<_> = staking_events_since_last_call() - .into_iter() - .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) - .collect(); assert_eq!( - era_paid, - vec![StakingEvent::EraPaid { - era_index: 1, - validator_payout: 162000, - remainder: 162000, - }] + staking_events_since_last_call(), + vec![ + StakingEvent::PagedElectionProceeded { page: 2, result: Ok(4) }, + StakingEvent::PagedElectionProceeded { page: 1, result: Ok(0) }, + StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }, + StakingEvent::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 }, + StakingEvent::SessionRotated { starting_session: 9, active_era: 1, planned_era: 2 }, + StakingEvent::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, + StakingEvent::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, + StakingEvent::SessionRotated { starting_session: 12, active_era: 1, planned_era: 2 }, + StakingEvent::EraPaid { + era_index: 1, + validator_payout: 162000, + remainder: 162000, + }, + StakingEvent::SessionRotated { + starting_session: 13, + active_era: 2, + planned_era: 3, + }, + ] ); // WHEN: switch to DAP mode (simulates runtime upgrade). @@ -2233,17 +2251,48 @@ fn legacy_to_dap_era_payout_e2e() { assert!(era_2_reward > 0); // THEN: EraPaid with remainder=0 (DAP mode). - let era_paid: Vec<_> = staking_events_since_last_call() - .into_iter() - .filter(|e| matches!(e, StakingEvent::EraPaid { .. })) - .collect(); assert_eq!( - era_paid, - vec![StakingEvent::EraPaid { - era_index: 2, - validator_payout: era_2_reward, - remainder: 0, - }] + staking_events_since_last_call(), + vec![ + StakingEvent::PagedElectionProceeded { page: 2, result: Ok(4) }, + StakingEvent::PagedElectionProceeded { page: 1, result: Ok(0) }, + StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }, + StakingEvent::SessionRotated { + starting_session: 14, + active_era: 2, + planned_era: 3, + }, + StakingEvent::SessionRotated { + starting_session: 15, + active_era: 2, + planned_era: 3, + }, + StakingEvent::SessionRotated { + starting_session: 16, + active_era: 2, + planned_era: 3, + }, + StakingEvent::SessionRotated { + starting_session: 17, + active_era: 2, + planned_era: 3, + }, + StakingEvent::SessionRotated { + starting_session: 18, + active_era: 2, + planned_era: 3, + }, + StakingEvent::EraPaid { + era_index: 2, + validator_payout: era_2_reward, + remainder: 0, + }, + StakingEvent::SessionRotated { + starting_session: 19, + active_era: 3, + planned_era: 4, + }, + ] ); // WHEN: payout era 2 (DAP pot transfer). From 5be95b0c8e31e7ee02b7f61af052e3fb206b7562 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 08:12:30 +0200 Subject: [PATCH 71/87] rename to IssuanceCurve --- .../runtimes/assets/asset-hub-westend/src/staking.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index c127cbbe99f54..8c57fd14657dc 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -232,12 +232,12 @@ parameter_types! { pub const StakingPotsPalletId: PalletId = PalletId(*b"py/stkng"); } -/// Polkadot inflation curve for DAP. +/// Westend inflation curve for DAP. /// /// Same computation as the previous `EraPayout` but returns total emission. /// The budget split (ex. 85/15 staker/treasury) is now handled by pallet-dap. -pub struct PolkadotIssuanceCurve; -impl sp_staking::budget::IssuanceCurve for PolkadotIssuanceCurve { +pub struct IssuanceCurve; +impl sp_staking::budget::IssuanceCurve for IssuanceCurve { fn issue(_total_issuance: Balance, elapsed_millis: u64) -> Balance { const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100; let relative_period = @@ -358,7 +358,7 @@ parameter_types! { impl pallet_dap::Config for Runtime { type Currency = Balances; type PalletId = DapPalletId; - type IssuanceCurve = PolkadotIssuanceCurve; + type IssuanceCurve = IssuanceCurve; type BudgetRecipients = ( pallet_dap::Pallet, pallet_staking_async::StakerRewardRecipient< From f953b6dfba1f07a7b60c02601bdf6af096e875ab Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 08:16:41 +0200 Subject: [PATCH 72/87] fix comments --- prdoc/pr_11616.prdoc | 2 +- substrate/frame/staking-async/src/tests/payout_stakers.rs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/prdoc/pr_11616.prdoc b/prdoc/pr_11616.prdoc index e25ae6f8f091b..198c06c340afd 100644 --- a/prdoc/pr_11616.prdoc +++ b/prdoc/pr_11616.prdoc @@ -16,7 +16,7 @@ doc: `StakerRewardCalculator`. New storage: `MaxCommission`, `DisableMintingGuard`. New extrinsic: `set_max_commission`. - Runtimes using non-minting mode provide a `PolkadotIssuanceCurve` to DAP and + Runtimes using non-minting mode provide an `IssuanceCurve` impl to DAP and register `StakerRewardRecipient` as a budget recipient. DAP storage version bumped to V2 with migration. crates: diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index 3921790f8a4c6..af668d73ad29a 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -83,8 +83,8 @@ fn rewards_with_nominator_should_work() { ] ); - // With DAP, rewards are minted at era finalization (not during payout) - // So total issuance should not change during payout (just transfers from pot) + // In non-minting mode, rewards are minted externally (e.g. pallet-dap) and + // snapshotted into era pots. Payouts transfer from pot, so issuance is unchanged. let post_issuance = asset::total_issuance::(); assert_eq!(post_issuance, pre_issuance); @@ -125,7 +125,7 @@ fn rewards_with_nominator_should_work() { ] ); - // Capture issuance before payout (rewards already minted during era finalization above) + // Capture issuance before payout (rewards should already be minted) let pre_payout_2 = asset::total_issuance::(); mock::make_all_reward_payment(2); @@ -957,7 +957,7 @@ fn test_multi_page_payout_stakers_by_page() { controller_balance_after_p1_payout - controller_balance_before_p0_payout; assert_eq_error_rate!(total_validator_paid, validator_total_reward, 1); - // With DAP, rewards minted at era finalization, so no change during payout + // In non-minting mode, payouts transfer from pot, so issuance is unchanged assert_eq!(pallet_balances::TotalIssuance::::get(), pre_payout_total_issuance); // Verify total nominator rewards: sum of all nominator balance increases should From 06867429434966a595bb7775972539f85ac255bf Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 08:17:24 +0200 Subject: [PATCH 73/87] fmt --- .../integration-tests/src/ah/test.rs | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index f1288821d1db4..252ec589cd256 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2154,11 +2154,7 @@ fn legacy_to_dap_era_payout_e2e() { StakingEvent::SessionRotated { starting_session: 4, active_era: 0, planned_era: 1 }, StakingEvent::SessionRotated { starting_session: 5, active_era: 0, planned_era: 1 }, StakingEvent::SessionRotated { starting_session: 6, active_era: 0, planned_era: 1 }, - StakingEvent::EraPaid { - era_index: 0, - validator_payout: 162000, - remainder: 162000, - }, + StakingEvent::EraPaid { era_index: 0, validator_payout: 162000, remainder: 162000 }, StakingEvent::SessionRotated { starting_session: 7, active_era: 1, planned_era: 2 }, ] ); @@ -2184,14 +2180,22 @@ fn legacy_to_dap_era_payout_e2e() { StakingEvent::PagedElectionProceeded { page: 0, result: Ok(0) }, StakingEvent::SessionRotated { starting_session: 8, active_era: 1, planned_era: 2 }, StakingEvent::SessionRotated { starting_session: 9, active_era: 1, planned_era: 2 }, - StakingEvent::SessionRotated { starting_session: 10, active_era: 1, planned_era: 2 }, - StakingEvent::SessionRotated { starting_session: 11, active_era: 1, planned_era: 2 }, - StakingEvent::SessionRotated { starting_session: 12, active_era: 1, planned_era: 2 }, - StakingEvent::EraPaid { - era_index: 1, - validator_payout: 162000, - remainder: 162000, + StakingEvent::SessionRotated { + starting_session: 10, + active_era: 1, + planned_era: 2 + }, + StakingEvent::SessionRotated { + starting_session: 11, + active_era: 1, + planned_era: 2 + }, + StakingEvent::SessionRotated { + starting_session: 12, + active_era: 1, + planned_era: 2 }, + StakingEvent::EraPaid { era_index: 1, validator_payout: 162000, remainder: 162000 }, StakingEvent::SessionRotated { starting_session: 13, active_era: 2, From 229300f13456c606a1b4d784b5f8a0c47ee07841 Mon Sep 17 00:00:00 2001 From: Ankan Date: Fri, 10 Apr 2026 08:25:23 +0200 Subject: [PATCH 74/87] remove misleading hard-pressure curve comment --- .../parachains/runtimes/assets/asset-hub-westend/src/staking.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 8c57fd14657dc..10fd18e27149f 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -243,7 +243,7 @@ impl sp_staking::budget::IssuanceCurve for IssuanceCurve { let relative_period = FixedU128::from_rational(elapsed_millis.into(), MILLISECONDS_PER_YEAR.into()); - // Fixed total TI baseline for issuance (hard-pressure curve). + // Fixed total TI that we use as baseline for the issuance. let fixed_total_issuance: i128 = 5_216_342_402_773_185_773; let fixed_inflation_rate = FixedU128::from_rational(8, 100); let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance); From e9bac7459be5a636d79e3792507f1fcd9f7e6463 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 11:02:32 +0200 Subject: [PATCH 75/87] pr feedbacks --- substrate/frame/staking-async/src/reward.rs | 25 +++++++---------- .../staking-async/src/session_rotation.rs | 3 +++ .../staking-async/src/tests/payout_stakers.rs | 27 +++++++++++++++++++ substrate/primitives/staking/src/lib.rs | 4 +-- 4 files changed, 41 insertions(+), 18 deletions(-) diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index b999233fd0431..c4d8313796bfe 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -83,10 +83,7 @@ impl EraRewardManager { Zero::zero() }; - log::info!( - target: LOG_TARGET, - "Era {era}: snapshotted staker_rewards={actual_staker:?}" - ); + log!(info, "Era {:?}: snapshotted staker_rewards={:?}", era, actual_staker); actual_staker } @@ -114,19 +111,11 @@ impl EraRewardManager { ) { Ok(credit) => { T::UnclaimedRewardHandler::on_unbalanced(credit); - log::debug!( - target: crate::LOG_TARGET, - "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", - remaining, era, pot_type - ); + log!(debug, "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", remaining, era, pot_type); }, Err(e) => { defensive!("Failed to withdraw unclaimed rewards from era pot"); - log::error!( - target: crate::LOG_TARGET, - "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", - era, pot_type, e - ); + log!(error, "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", era, pot_type, e); }, } } @@ -166,15 +155,19 @@ where validator_total_reward: BalanceOf, validator_commission: Perbill, validator_own_stake: BalanceOf, - total_stake: BalanceOf, + total_exposure: BalanceOf, ) -> sp_staking::StakerRewardResult> { let validator_commission_payout = validator_commission.mul_floor(validator_total_reward); let leftover = validator_total_reward.saturating_sub(validator_commission_payout); - let validator_exposure_part = Perbill::from_rational(validator_own_stake, total_stake); + let validator_exposure_part = Perbill::from_rational(validator_own_stake, total_exposure); let validator_staking_payout = validator_exposure_part.mul_floor(leftover); let validator_payout = validator_staking_payout.saturating_add(validator_commission_payout); let nominator_payout = leftover.saturating_sub(validator_staking_payout); + // Commission rounding means these may not sum exactly to validator_total_reward, + // but they should never exceed it. + debug_assert!(validator_payout + nominator_payout <= validator_total_reward); + sp_staking::StakerRewardResult { validator_payout, nominator_payout } } } diff --git a/substrate/frame/staking-async/src/session_rotation.rs b/substrate/frame/staking-async/src/session_rotation.rs index 2e7175c817a61..c8e387c056f53 100644 --- a/substrate/frame/staking-async/src/session_rotation.rs +++ b/substrate/frame/staking-async/src/session_rotation.rs @@ -847,6 +847,9 @@ impl Rotator { } /// DAP end-era: snapshot from general reward pot into era-specific pot. + /// + /// The snapshotted amount is stored in `ErasValidatorReward` — this represents the total + /// reward budget for the era before any payouts. Individual payouts draw from the era pot. fn end_era_dap(ending_era: &ActiveEraInfo) { let staker_rewards = reward::EraRewardManager::::snapshot_era_rewards(ending_era.index); diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index af668d73ad29a..e9cc441d21df1 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -19,6 +19,7 @@ use super::*; use crate::session_rotation::Eras; use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}; use sp_runtime::{bounded_btree_map, traits::Dispatchable}; +use crate::EraPotType; #[test] fn rewards_with_nominator_should_work() { @@ -1730,3 +1731,29 @@ fn test_runtime_api_pending_rewards() { assert_ok!(Staking::payout_stakers(RuntimeOrigin::signed(1337), validator_two, 0)); }); } + +#[test] +#[should_panic(expected = "Era has no reward pot but legacy minting is disabled!")] +fn payout_fails_when_pot_missing_and_minting_disabled() { + // Payout should fail with LegacyMintingDisabled when the era has no reward pot + // but DisableMintingGuard prevents legacy minting. + ExtBuilder::default().build_and_execute(|| { + let validator = 11; // validator + + Staking::reward_by_ids(vec![(validator, 1)]); + Session::roll_until_active_era(2); + + let era = 1; + assert!(ErasValidatorReward::::get(era).unwrap() > 0); + assert!(DisableMintingGuard::::get().is_some()); + + // GIVEN: era pot exists in DAP mode — destroy it to simulate corruption. + let pot = SequentialTest::era_pot_account(era, EraPotType::StakerRewards); + assert!(crate::reward::EraRewardManager::::has_staker_rewards_pot(era)); + frame_system::Account::::remove::<&AccountId>(&pot); + assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(era)); + + // WHEN: payout is attempted — defensive! panics (returns LegacyMintingDisabled in prod). + let _ = Staking::payout_stakers(RuntimeOrigin::signed(1337), validator, era); + }); +} diff --git a/substrate/primitives/staking/src/lib.rs b/substrate/primitives/staking/src/lib.rs index 39f36dff7c548..b1085bfa949c0 100644 --- a/substrate/primitives/staking/src/lib.rs +++ b/substrate/primitives/staking/src/lib.rs @@ -784,7 +784,7 @@ pub trait StakerRewardCalculator { validator_total_reward: Balance, validator_commission: Perbill, validator_own_stake: Balance, - total_stake: Balance, + total_exposure: Balance, ) -> StakerRewardResult; } @@ -797,7 +797,7 @@ impl StakerRewardCalculator for () { _validator_total_reward: Balance, _validator_commission: Perbill, _validator_own_stake: Balance, - _total_stake: Balance, + _total_exposure: Balance, ) -> StakerRewardResult { StakerRewardResult { validator_payout: Default::default(), From 3b1ca1dfe53feedac50b250393bbc958f0c952ea Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 11:03:40 +0200 Subject: [PATCH 76/87] fmt --- substrate/frame/staking-async/src/reward.rs | 16 ++++++++++++++-- .../staking-async/src/tests/payout_stakers.rs | 3 +-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index c4d8313796bfe..91629f5e2972a 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -111,11 +111,23 @@ impl EraRewardManager { ) { Ok(credit) => { T::UnclaimedRewardHandler::on_unbalanced(credit); - log!(debug, "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", remaining, era, pot_type); + log!( + debug, + "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", + remaining, + era, + pot_type + ); }, Err(e) => { defensive!("Failed to withdraw unclaimed rewards from era pot"); - log!(error, "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", era, pot_type, e); + log!( + error, + "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", + era, + pot_type, + e + ); }, } } diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index e9cc441d21df1..d51618544e435 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -16,10 +16,9 @@ // limitations under the License. use super::*; -use crate::session_rotation::Eras; +use crate::{session_rotation::Eras, EraPotType}; use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}; use sp_runtime::{bounded_btree_map, traits::Dispatchable}; -use crate::EraPotType; #[test] fn rewards_with_nominator_should_work() { From eba58d980984c8bce454b46e3cf6a959d3ac9095 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 11:06:04 +0200 Subject: [PATCH 77/87] debug assert exact --- substrate/frame/staking-async/src/reward.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index 91629f5e2972a..1558ed53a4936 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -176,9 +176,8 @@ where let validator_payout = validator_staking_payout.saturating_add(validator_commission_payout); let nominator_payout = leftover.saturating_sub(validator_staking_payout); - // Commission rounding means these may not sum exactly to validator_total_reward, - // but they should never exceed it. - debug_assert!(validator_payout + nominator_payout <= validator_total_reward); + // Validator and nominator payout is exactly same as total reward. + debug_assert_eq!(validator_payout + nominator_payout, validator_total_reward); sp_staking::StakerRewardResult { validator_payout, nominator_payout } } From a24b23310fb281b4e023bca11ee0881ac6bf0b05 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 15:41:41 +0200 Subject: [PATCH 78/87] combine enum for general and era pots --- substrate/frame/staking-async/src/lib.rs | 85 +++++++------------ .../frame/staking-async/src/pallet/impls.rs | 8 +- .../frame/staking-async/src/pallet/mod.rs | 12 +-- substrate/frame/staking-async/src/reward.rs | 21 ++--- 4 files changed, 49 insertions(+), 77 deletions(-) diff --git a/substrate/frame/staking-async/src/lib.rs b/substrate/frame/staking-async/src/lib.rs index 1e5423548d8d6..763f079d46280 100644 --- a/substrate/frame/staking-async/src/lib.rs +++ b/substrate/frame/staking-async/src/lib.rs @@ -567,29 +567,39 @@ impl Contains for AllStakers { } } -/// Identifies the era pot account for staker rewards. +/// Kind of reward managed by staking pots. #[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] -pub enum EraPotType { - /// Pot for staker rewards (nominators + validators). +pub enum RewardKind { + /// Staker rewards (nominators + validators). StakerRewards, } -/// Trait for generating era pot account IDs. -pub trait EraPotAccountProvider { - fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId; +/// Identifies a reward pot account. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] +pub enum RewardPot { + /// General pot: funded by an external source (e.g. pallet-dap). + /// At era boundaries, staking snapshots the balance into an era-specific pot. + General(RewardKind), + /// Era-specific pot: snapshotted from the general pot at era boundaries. + Era(EraIndex, RewardKind), +} + +/// Trait for generating reward pot account IDs. +pub trait PotAccountProvider { + fn pot_account(pot: RewardPot) -> AccountId; } /// Seed-based pot account provider for production use. pub struct Seed(core::marker::PhantomData); -impl EraPotAccountProvider for Seed +impl PotAccountProvider for Seed where AccountId: codec::FullCodec, S: Get, { - fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId { + fn pot_account(pot: RewardPot) -> AccountId { use sp_runtime::traits::AccountIdConversion; - S::get().into_sub_account_truncating((era, pot_type)) + S::get().into_sub_account_truncating(pot) } } @@ -598,52 +608,15 @@ where pub struct SequentialTest; #[cfg(feature = "std")] -impl EraPotAccountProvider for SequentialTest -where - AccountId: From, -{ - fn era_pot_account(era: EraIndex, pot_type: EraPotType) -> AccountId { - let pot_type_offset = match pot_type { - EraPotType::StakerRewards => 0, - }; - AccountId::from(100_000 + (era as u64 * 10) + pot_type_offset) - } -} - -/// Identifies the general (non-era-specific) reward pot. -/// -/// DAP drips inflation into this account continuously. At era boundaries, -/// staking snapshots the balance and transfers it to an era-specific pot. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Encode, Decode, TypeInfo)] -pub enum GeneralPotType { - /// General pot for staker rewards. - StakerRewards, -} - -/// Trait that provides general (non-era-specific) pot accounts. -pub trait GeneralPotAccountProvider { - fn general_pot_account(pot_type: GeneralPotType) -> AccountId; -} - -impl GeneralPotAccountProvider for Seed -where - AccountId: codec::FullCodec, - S: Get, -{ - fn general_pot_account(pot_type: GeneralPotType) -> AccountId { - use sp_runtime::traits::AccountIdConversion; - S::get().into_sub_account_truncating(pot_type) - } -} - -#[cfg(feature = "std")] -impl GeneralPotAccountProvider for SequentialTest +impl PotAccountProvider for SequentialTest where AccountId: From, { - fn general_pot_account(pot_type: GeneralPotType) -> AccountId { - match pot_type { - GeneralPotType::StakerRewards => AccountId::from(200_000u64), + fn pot_account(pot: RewardPot) -> AccountId { + match pot { + RewardPot::General(RewardKind::StakerRewards) => AccountId::from(200_000u64), + RewardPot::Era(era, RewardKind::StakerRewards) => + AccountId::from(100_000 + (era as u64 * 10)), } } } @@ -651,18 +624,18 @@ where /// Budget recipient for staker rewards. /// /// Exposes the general staker reward pot so DAP can drip inflation into it. -pub struct StakerRewardRecipient(core::marker::PhantomData); +pub struct StakerRewardRecipient

(core::marker::PhantomData

); -impl sp_staking::budget::BudgetRecipient for StakerRewardRecipient +impl sp_staking::budget::BudgetRecipient for StakerRewardRecipient

where - G: GeneralPotAccountProvider, + P: PotAccountProvider, { fn budget_key() -> sp_staking::budget::BudgetKey { sp_staking::budget::BudgetKey::truncate_from(b"staker_rewards".to_vec()) } fn pot_account() -> AccountId { - G::general_pot_account(GeneralPotType::StakerRewards) + P::pot_account(RewardPot::General(RewardKind::StakerRewards)) } } diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index f3adc3b261571..8bb5f8498c87f 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -24,8 +24,9 @@ use crate::{ session_rotation::{self, Eras, Rotator}, slashing::OffenceRecord, weights::WeightInfo, - BalanceOf, EraPotAccountProvider, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, - Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, SnapshotStatus, + BalanceOf, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, PotAccountProvider, + Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, RewardKind, RewardPot, + SnapshotStatus, StakingLedger, ValidatorPrefs, STAKING_ID, }; use alloc::{boxed::Box, vec, vec::Vec}; @@ -588,7 +589,8 @@ impl Pallet { let payout_account = Self::payout_account_for_dest(stash, &dest)?; - let staker_rewards_pot = T::EraPots::era_pot_account(era, crate::EraPotType::StakerRewards); + let staker_rewards_pot = + T::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards)); if let Err(e) = T::Currency::transfer( &staker_rewards_pot, &payout_account, diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 266132ecef96c..3ddb0b6976d23 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -286,16 +286,12 @@ pub mod pallet { #[pallet::no_default_bounds] type UnclaimedRewardHandler: OnUnbalanced>; - /// Provider for general (non-era) reward pot accounts (non-minting mode only). + /// Provider for generating reward pot account IDs (non-minting mode only). /// - /// An external source funds these pots. At era boundaries, staking snapshots - /// the accumulated balance into era-specific pots. + /// Provides both general pots (funded by an external source like pallet-dap) + /// and era-specific pots (snapshotted at era boundaries). #[pallet::no_default] - type GeneralPots: crate::GeneralPotAccountProvider; - - /// Provider for generating era pot account IDs (non-minting mode only). - #[pallet::no_default] - type EraPots: crate::EraPotAccountProvider; + type RewardPots: crate::PotAccountProvider; /// Calculator for staker rewards. /// diff --git a/substrate/frame/staking-async/src/reward.rs b/substrate/frame/staking-async/src/reward.rs index 1558ed53a4936..d7fcd874077bb 100644 --- a/substrate/frame/staking-async/src/reward.rs +++ b/substrate/frame/staking-async/src/reward.rs @@ -39,12 +39,12 @@ impl EraRewardManager { /// Creates an era pot account by adding a provider reference. /// /// Should only be called in non-minting mode (`DisableMinting = true`). - pub(crate) fn create(era: EraIndex, pot_type: EraPotType) -> T::AccountId { + pub(crate) fn create(era: EraIndex, kind: RewardKind) -> T::AccountId { debug_assert!( T::DisableMinting::get(), "Era pots should only be created when DisableMinting is true" ); - let pot_account = T::EraPots::era_pot_account(era, pot_type); + let pot_account = T::RewardPots::pot_account(RewardPot::Era(era, kind)); frame_system::Pallet::::inc_providers(&pot_account); pot_account } @@ -54,9 +54,10 @@ impl EraRewardManager { /// DAP drips inflation continuously into the general pot. At era boundary, /// this transfers the accumulated balance (minus ED) into an era pot. pub(crate) fn snapshot_era_rewards(era: EraIndex) -> BalanceOf { - let staker_era_pot = Self::create(era, EraPotType::StakerRewards); + let staker_era_pot = Self::create(era, RewardKind::StakerRewards); - let general_staker_pot = T::GeneralPots::general_pot_account(GeneralPotType::StakerRewards); + let general_staker_pot = + T::RewardPots::pot_account(RewardPot::General(RewardKind::StakerRewards)); // Leave ED in the general pot to keep it alive. let staker_balance = T::Currency::reducible_balance( @@ -91,8 +92,8 @@ impl EraRewardManager { /// Destroys an era pot by withdrawing unclaimed rewards and removing the provider. /// /// No-op if the pot was never created (e.g. in legacy minting mode). - pub(crate) fn destroy(era: EraIndex, pot_type: EraPotType) { - let pot_account = T::EraPots::era_pot_account(era, pot_type); + pub(crate) fn destroy(era: EraIndex, kind: RewardKind) { + let pot_account = T::RewardPots::pot_account(RewardPot::Era(era, kind)); // Skip if pot was never created (legacy mode doesn't create pots). if frame_system::Pallet::::providers(&pot_account) == 0 { @@ -116,7 +117,7 @@ impl EraRewardManager { "Withdrew {:?} unclaimed rewards from era {:?} {:?} pot", remaining, era, - pot_type + kind ); }, Err(e) => { @@ -125,7 +126,7 @@ impl EraRewardManager { error, "Era {:?} {:?}: unclaimed reward withdrawal failed: {:?}", era, - pot_type, + kind, e ); }, @@ -138,13 +139,13 @@ impl EraRewardManager { /// Checks if an era has a staker rewards pot. pub(crate) fn has_staker_rewards_pot(era: EraIndex) -> bool { - let pot = T::EraPots::era_pot_account(era, EraPotType::StakerRewards); + let pot = T::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards)); frame_system::Pallet::::providers(&pot) > 0 } /// Cleans up pot accounts for a given era. pub(crate) fn cleanup_era(era: EraIndex) { - Self::destroy(era, EraPotType::StakerRewards); + Self::destroy(era, RewardKind::StakerRewards); } } From b98a8a54351829ca65df92c86103066be2620da1 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 15:42:44 +0200 Subject: [PATCH 79/87] fix compile --- .../runtimes/assets/asset-hub-westend/src/staking.rs | 3 +-- .../staking-async/integration-tests/src/ah/mock.rs | 3 +-- .../staking-async/integration-tests/src/ah/test.rs | 10 +++++----- .../staking-async/runtimes/parachain/src/staking.rs | 3 +-- substrate/frame/staking-async/src/benchmarking.rs | 2 +- substrate/frame/staking-async/src/mock.rs | 5 ++--- .../frame/staking-async/src/tests/era_rotation.rs | 8 ++++---- .../frame/staking-async/src/tests/payout_stakers.rs | 4 ++-- 8 files changed, 17 insertions(+), 21 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs index 10fd18e27149f..b70915a1bf33d 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/staking.rs @@ -302,8 +302,7 @@ impl pallet_staking_async::Config for Runtime { type MaxEraDuration = MaxEraDuration; type DisableMinting = ConstBool; type UnclaimedRewardHandler = Dap; - type GeneralPots = pallet_staking_async::Seed; - type EraPots = pallet_staking_async::Seed; + type RewardPots = pallet_staking_async::Seed; type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index 34dc88f8c7db8..796d5a0a936dd 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -467,8 +467,7 @@ impl pallet_staking_async::Config for Runtime { type MaxEraDuration = (); type DisableMinting = DisableMintingMode; type UnclaimedRewardHandler = (); - type GeneralPots = pallet_staking_async::SequentialTest; - type EraPots = pallet_staking_async::SequentialTest; + type RewardPots = pallet_staking_async::SequentialTest; type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type EventListeners = (); diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 252ec589cd256..796e431776997 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -28,8 +28,8 @@ use pallet_election_provider_multi_block::{ }; use pallet_staking_async::{ self as staking_async, session_rotation::Rotator, ActiveEra, ActiveEraInfo, CurrentEra, - DisableMintingGuard, EraPotAccountProvider, EraPotType, ErasValidatorReward, - Event as StakingEvent, GeneralPotAccountProvider, GeneralPotType, SequentialTest, + DisableMintingGuard, ErasValidatorReward, Event as StakingEvent, PotAccountProvider, + RewardKind, RewardPot, SequentialTest, }; use pallet_staking_async_rc_client::{ self as rc_client, OutgoingValidatorSet, UnexpectedKind, ValidatorSetReport, @@ -2138,7 +2138,7 @@ fn legacy_to_dap_era_payout_e2e() { // THEN: legacy mode — no pots, no guard. assert_eq!(DisableMintingGuard::::get(), None); assert_eq!( - System::providers(&SequentialTest::era_pot_account(0, EraPotType::StakerRewards)), + System::providers(&SequentialTest::pot_account(RewardPot::Era(0, RewardKind::StakerRewards))), 0 ); @@ -2220,7 +2220,7 @@ fn legacy_to_dap_era_payout_e2e() { // Initialize DAP and fund general staker pot with ED. pallet_dap::LastIssuanceTimestamp::::put(MockTime::get()); - let general_pot = SequentialTest::general_pot_account(GeneralPotType::StakerRewards); + let general_pot = SequentialTest::pot_account(RewardPot::General(RewardKind::StakerRewards)); Balances::mint_into(&general_pot, 1).unwrap(); // WHEN: payout era 1 (legacy era) while already in DAP mode. @@ -2249,7 +2249,7 @@ fn legacy_to_dap_era_payout_e2e() { // THEN: guard set, era 2 has pot with funds. assert!(DisableMintingGuard::::get().is_some()); - let era_2_pot = SequentialTest::era_pot_account(2, EraPotType::StakerRewards); + let era_2_pot = SequentialTest::pot_account(RewardPot::Era(2, RewardKind::StakerRewards)); assert!(System::providers(&era_2_pot) > 0); let era_2_reward = ErasValidatorReward::::get(2).unwrap(); assert!(era_2_reward > 0); diff --git a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs index 53c79fe4029ac..299bb0ddb1ce0 100644 --- a/substrate/frame/staking-async/runtimes/parachain/src/staking.rs +++ b/substrate/frame/staking-async/runtimes/parachain/src/staking.rs @@ -456,8 +456,7 @@ impl pallet_staking_async::Config for Runtime { type MaxEraDuration = MaxEraDuration; type DisableMinting = ConstBool; type UnclaimedRewardHandler = Dap; - type GeneralPots = pallet_staking_async::Seed; - type EraPots = pallet_staking_async::Seed; + type RewardPots = pallet_staking_async::Seed; type StakerRewardCalculator = pallet_staking_async::reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; diff --git a/substrate/frame/staking-async/src/benchmarking.rs b/substrate/frame/staking-async/src/benchmarking.rs index 1ed5de5322b8d..9ce4be212a202 100644 --- a/substrate/frame/staking-async/src/benchmarking.rs +++ b/substrate/frame/staking-async/src/benchmarking.rs @@ -129,7 +129,7 @@ pub(crate) fn create_validator_with_nominators( // Create and fund the era reward pot so payout_stakers can transfer from it. let era_pot = - crate::reward::EraRewardManager::::create(planned_era, EraPotType::StakerRewards); + crate::reward::EraRewardManager::::create(planned_era, RewardKind::StakerRewards); let _ = asset::mint_creating::(&era_pot, total_payout); Ok((v_stash, nominators, planned_era)) diff --git a/substrate/frame/staking-async/src/mock.rs b/substrate/frame/staking-async/src/mock.rs index 01d056918d4f3..815401c6e9666 100644 --- a/substrate/frame/staking-async/src/mock.rs +++ b/substrate/frame/staking-async/src/mock.rs @@ -460,7 +460,7 @@ impl Get for DisableMintingMode { } pub fn general_staker_pot() -> AccountId { - SequentialTest::general_pot_account(GeneralPotType::StakerRewards) + SequentialTest::pot_account(RewardPot::General(RewardKind::StakerRewards)) } impl pallet_dap::Config for Test { @@ -541,8 +541,7 @@ impl Config for Test { type MaxEraDuration = MaxEraDuration; type DisableMinting = DisableMintingMode; type UnclaimedRewardHandler = Dap; - type GeneralPots = SequentialTest; - type EraPots = SequentialTest; + type RewardPots = SequentialTest; type StakerRewardCalculator = reward::DefaultStakerRewardCalculator; type MaxPruningItems = MaxPruningItems; type PlanningEraOffset = PlanningEraOffset; diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index 21bf99fa0b94d..a1d1fda1bf038 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -246,7 +246,7 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { )); // Verify era 78 staker pot has been funded (DAP drips into general pot, staking snapshots). let staker_pot_78 = - ::EraPots::era_pot_account(78, EraPotType::StakerRewards); + ::RewardPots::pot_account(RewardPot::Era(78, RewardKind::StakerRewards)); let ideal_validator_payout = validator_payout_for(time_per_era()); assert_eq!(Balances::balance(&staker_pot_78), ideal_validator_payout); // All eras from 1 to current still present @@ -297,7 +297,7 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { let expected_per_era = validator_payout_for(time_per_era()); for era in 79..=81 { let staker_pot = - ::EraPots::era_pot_account(era, EraPotType::StakerRewards); + ::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards)); assert_eq!( Balances::balance(&staker_pot), expected_per_era, @@ -510,7 +510,7 @@ fn era_pot_cleanup_after_history_depth() { let _ = staking_events_since_last_call(); // Verify era-1 staker pot was funded with expected amount. - let staker_pot_1 = ::EraPots::era_pot_account(1, EraPotType::StakerRewards); + let staker_pot_1 = ::RewardPots::pot_account(RewardPot::Era(1, RewardKind::StakerRewards)); let expected_per_era = validator_payout_for(time_per_era()); assert_eq!(Balances::balance(&staker_pot_1), expected_per_era); @@ -527,7 +527,7 @@ fn era_pot_cleanup_after_history_depth() { // THEN: Verify era-1 staker pot has been cleaned up let staker_pot = - ::EraPots::era_pot_account(cleanup_era, EraPotType::StakerRewards); + ::RewardPots::pot_account(RewardPot::Era(cleanup_era, RewardKind::StakerRewards)); assert_eq!(Balances::balance(&staker_pot), 0, "Staker pot should have zero balance"); assert_eq!(System::providers(&staker_pot), 0, "Staker pot should have no providers"); diff --git a/substrate/frame/staking-async/src/tests/payout_stakers.rs b/substrate/frame/staking-async/src/tests/payout_stakers.rs index d51618544e435..b186c9b080853 100644 --- a/substrate/frame/staking-async/src/tests/payout_stakers.rs +++ b/substrate/frame/staking-async/src/tests/payout_stakers.rs @@ -16,7 +16,7 @@ // limitations under the License. use super::*; -use crate::{session_rotation::Eras, EraPotType}; +use crate::{session_rotation::Eras, RewardKind, RewardPot}; use frame_support::dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}; use sp_runtime::{bounded_btree_map, traits::Dispatchable}; @@ -1747,7 +1747,7 @@ fn payout_fails_when_pot_missing_and_minting_disabled() { assert!(DisableMintingGuard::::get().is_some()); // GIVEN: era pot exists in DAP mode — destroy it to simulate corruption. - let pot = SequentialTest::era_pot_account(era, EraPotType::StakerRewards); + let pot = SequentialTest::pot_account(RewardPot::Era(era, RewardKind::StakerRewards)); assert!(crate::reward::EraRewardManager::::has_staker_rewards_pot(era)); frame_system::Account::::remove::<&AccountId>(&pot); assert!(!crate::reward::EraRewardManager::::has_staker_rewards_pot(era)); From 27941b49860ecb0a9a20fe5434889f948f077a44 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 15:43:08 +0200 Subject: [PATCH 80/87] fmt --- .../integration-tests/src/ah/test.rs | 8 +++++-- substrate/frame/staking-async/src/lib.rs | 5 +++-- .../frame/staking-async/src/pallet/impls.rs | 7 +++---- .../staking-async/src/tests/era_rotation.rs | 21 ++++++++++++------- 4 files changed, 26 insertions(+), 15 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 796e431776997..5065ae019db6d 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -2138,7 +2138,10 @@ fn legacy_to_dap_era_payout_e2e() { // THEN: legacy mode — no pots, no guard. assert_eq!(DisableMintingGuard::::get(), None); assert_eq!( - System::providers(&SequentialTest::pot_account(RewardPot::Era(0, RewardKind::StakerRewards))), + System::providers(&SequentialTest::pot_account(RewardPot::Era( + 0, + RewardKind::StakerRewards + ))), 0 ); @@ -2220,7 +2223,8 @@ fn legacy_to_dap_era_payout_e2e() { // Initialize DAP and fund general staker pot with ED. pallet_dap::LastIssuanceTimestamp::::put(MockTime::get()); - let general_pot = SequentialTest::pot_account(RewardPot::General(RewardKind::StakerRewards)); + let general_pot = + SequentialTest::pot_account(RewardPot::General(RewardKind::StakerRewards)); Balances::mint_into(&general_pot, 1).unwrap(); // WHEN: payout era 1 (legacy era) while already in DAP mode. diff --git a/substrate/frame/staking-async/src/lib.rs b/substrate/frame/staking-async/src/lib.rs index 763f079d46280..cb39a1b0df234 100644 --- a/substrate/frame/staking-async/src/lib.rs +++ b/substrate/frame/staking-async/src/lib.rs @@ -615,8 +615,9 @@ where fn pot_account(pot: RewardPot) -> AccountId { match pot { RewardPot::General(RewardKind::StakerRewards) => AccountId::from(200_000u64), - RewardPot::Era(era, RewardKind::StakerRewards) => - AccountId::from(100_000 + (era as u64 * 10)), + RewardPot::Era(era, RewardKind::StakerRewards) => { + AccountId::from(100_000 + (era as u64 * 10)) + }, } } } diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index 8bb5f8498c87f..d244e63796f85 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -24,10 +24,9 @@ use crate::{ session_rotation::{self, Eras, Rotator}, slashing::OffenceRecord, weights::WeightInfo, - BalanceOf, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, PotAccountProvider, - Nominations, NominationsQuota, PositiveImbalanceOf, RewardDestination, RewardKind, RewardPot, - SnapshotStatus, - StakingLedger, ValidatorPrefs, STAKING_ID, + BalanceOf, Exposure, Forcing, LedgerIntegrityState, MaxNominationsOf, Nominations, + NominationsQuota, PositiveImbalanceOf, PotAccountProvider, RewardDestination, RewardKind, + RewardPot, SnapshotStatus, StakingLedger, ValidatorPrefs, STAKING_ID, }; use alloc::{boxed::Box, vec, vec::Vec}; use frame_election_provider_support::{ diff --git a/substrate/frame/staking-async/src/tests/era_rotation.rs b/substrate/frame/staking-async/src/tests/era_rotation.rs index a1d1fda1bf038..af56126a80a9f 100644 --- a/substrate/frame/staking-async/src/tests/era_rotation.rs +++ b/substrate/frame/staking-async/src/tests/era_rotation.rs @@ -245,8 +245,10 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { ] )); // Verify era 78 staker pot has been funded (DAP drips into general pot, staking snapshots). - let staker_pot_78 = - ::RewardPots::pot_account(RewardPot::Era(78, RewardKind::StakerRewards)); + let staker_pot_78 = ::RewardPots::pot_account(RewardPot::Era( + 78, + RewardKind::StakerRewards, + )); let ideal_validator_payout = validator_payout_for(time_per_era()); assert_eq!(Balances::balance(&staker_pot_78), ideal_validator_payout); // All eras from 1 to current still present @@ -296,8 +298,10 @@ fn era_cleanup_history_depth_works_with_prune_era_step_extrinsic() { // Verify eras 79-81 staker pots were funded with expected amount. let expected_per_era = validator_payout_for(time_per_era()); for era in 79..=81 { - let staker_pot = - ::RewardPots::pot_account(RewardPot::Era(era, RewardKind::StakerRewards)); + let staker_pot = ::RewardPots::pot_account(RewardPot::Era( + era, + RewardKind::StakerRewards, + )); assert_eq!( Balances::balance(&staker_pot), expected_per_era, @@ -510,7 +514,8 @@ fn era_pot_cleanup_after_history_depth() { let _ = staking_events_since_last_call(); // Verify era-1 staker pot was funded with expected amount. - let staker_pot_1 = ::RewardPots::pot_account(RewardPot::Era(1, RewardKind::StakerRewards)); + let staker_pot_1 = + ::RewardPots::pot_account(RewardPot::Era(1, RewardKind::StakerRewards)); let expected_per_era = validator_payout_for(time_per_era()); assert_eq!(Balances::balance(&staker_pot_1), expected_per_era); @@ -526,8 +531,10 @@ fn era_pot_cleanup_after_history_depth() { // Verify rewards were allocated for the eras we advanced through. // THEN: Verify era-1 staker pot has been cleaned up - let staker_pot = - ::RewardPots::pot_account(RewardPot::Era(cleanup_era, RewardKind::StakerRewards)); + let staker_pot = ::RewardPots::pot_account(RewardPot::Era( + cleanup_era, + RewardKind::StakerRewards, + )); assert_eq!(Balances::balance(&staker_pot), 0, "Staker pot should have zero balance"); assert_eq!(System::providers(&staker_pot), 0, "Staker pot should have no providers"); From 7c0fd96b454bf975c1b67babf7d8a05e0824caaa Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 15:52:58 +0200 Subject: [PATCH 81/87] update rustdoc for force apply commission --- substrate/frame/staking-async/src/pallet/mod.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 3ddb0b6976d23..973be10ede1df 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -2577,9 +2577,10 @@ pub mod pallet { Ok(()) } - /// Force a validator to have at least the minimum commission. This will not affect a - /// validator who already has a commission greater than or equal to the minimum. Any account - /// can call this. + /// Clamps a validator's commission to the `[MinCommission, MaxCommission]` range. + /// + /// Named `force_apply_min_commission` for legacy reasons — it also enforces the + /// maximum. Any account can call this. #[pallet::call_index(24)] #[pallet::weight(T::WeightInfo::force_apply_min_commission())] pub fn force_apply_min_commission( From dcf973b75208c774f5b7baf6e42ac68292a2cffe Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 15:58:25 +0200 Subject: [PATCH 82/87] emit missing payee in legacy path --- .../frame/staking-async/src/pallet/impls.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/substrate/frame/staking-async/src/pallet/impls.rs b/substrate/frame/staking-async/src/pallet/impls.rs index d244e63796f85..24d68d26d66c5 100644 --- a/substrate/frame/staking-async/src/pallet/impls.rs +++ b/substrate/frame/staking-async/src/pallet/impls.rs @@ -453,6 +453,7 @@ impl Pallet { } Self::payout_legacy_mint( + era, &stash, validator_staker_payout_for_page, &exposure, @@ -506,6 +507,7 @@ impl Pallet { /// Legacy mint-based payout for pre-upgrade eras. fn payout_legacy_mint( + era: EraIndex, stash: &T::AccountId, validator_payout: BalanceOf, exposure: &crate::PagedExposure>, @@ -515,7 +517,7 @@ impl Pallet { let mut nominator_payout_count: u32 = 0; let mut total_imbalance = PositiveImbalanceOf::::zero(); - if let Some((imbalance, dest)) = Self::make_payout_legacy(stash, validator_payout) { + if let Some((imbalance, dest)) = Self::make_payout_legacy(era, stash, validator_payout) { Self::deposit_event(Event::::Rewarded { stash: stash.clone(), dest, @@ -532,7 +534,7 @@ impl Pallet { nominator_exposure_part.mul_floor(total_nominator_payout); if let Some((imbalance, dest)) = - Self::make_payout_legacy(&nominator.who, nominator_reward) + Self::make_payout_legacy(era, &nominator.who, nominator_reward) { nominator_payout_count.saturating_inc(); Self::deposit_event(Event::::Rewarded { @@ -622,13 +624,24 @@ impl Pallet { /// Legacy: make a payment to a staker by minting new tokens. fn make_payout_legacy( + era: EraIndex, stash: &T::AccountId, amount: BalanceOf, ) -> Option<(PositiveImbalanceOf, RewardDestination)> { if amount.is_zero() { return None; } - let dest = Self::payee(StakingAccount::Stash(stash.clone()))?; + let dest = match Self::payee(StakingAccount::Stash(stash.clone())) { + Some(d) => d, + None => { + defensive!("Staker missing payee"); + Self::deposit_event(Event::::Unexpected(UnexpectedKind::MissingPayee { + era, + stash: stash.clone(), + })); + return None; + }, + }; let maybe_imbalance = match dest { RewardDestination::Stash => asset::mint_into_existing::(stash, amount), From 7715bb381a06ddce0f32ad810c8f044803338488 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 16:03:13 +0200 Subject: [PATCH 83/87] update ErasValidatorReward rustdoc --- .../parachains/runtimes/assets/asset-hub-westend/src/lib.rs | 2 +- substrate/frame/staking-async/src/pallet/mod.rs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs index 94f7a6861227a..1ebfc3bc55da0 100644 --- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs +++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs @@ -1838,7 +1838,7 @@ pub type Migrations = ( // unreleased // PSM: initialize first external asset (USDT) with fees and ceiling weight. pallet_psm::migrations::v1::MigrateToV1, - pallet_dap::migrations::MigrateV1ToV2, + pallet_dap::migrations::MigrateV1ToV2, ); /// Asset Hub Westend has some undecodable storage, delete it. diff --git a/substrate/frame/staking-async/src/pallet/mod.rs b/substrate/frame/staking-async/src/pallet/mod.rs index 973be10ede1df..c8867ed4c7103 100644 --- a/substrate/frame/staking-async/src/pallet/mod.rs +++ b/substrate/frame/staking-async/src/pallet/mod.rs @@ -818,9 +818,11 @@ pub mod pallet { ValueQuery, >; - /// The total validator era payout for the last [`Config::HistoryDepth`] eras. + /// The total staker reward budget for each era within [`Config::HistoryDepth`]. /// - /// Eras that haven't finished yet or has been removed doesn't have reward. + /// Set at era finalization: + /// - in non-minting mode this is the snapshot of the era pot balance before any payouts. + /// - in legacy mode it comes from `EraPayout`, with rewards minted on the fly. #[pallet::storage] pub type ErasValidatorReward = StorageMap<_, Twox64Concat, EraIndex, BalanceOf>; From 769339d1d94ba08556a1583211eb7b746f799983 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 16:35:03 +0200 Subject: [PATCH 84/87] fix test delegate stake mock --- .../frame/nomination-pools/test-delegate-stake/src/mock.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs index d673857ef8f8b..abe697f387659 100644 --- a/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs +++ b/substrate/frame/nomination-pools/test-delegate-stake/src/mock.rs @@ -128,8 +128,7 @@ impl pallet_staking_async::Config for Runtime { type EraPayout = TestEraPayout; type DisableMinting = ConstBool; type BondingDuration = BondingDuration; - type GeneralPots = pallet_staking_async::SequentialTest; - type EraPots = pallet_staking_async::SequentialTest; + type RewardPots = pallet_staking_async::SequentialTest; type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, (), ())>; type VoterList = VoterList; From 7975c744f05be897ff2e70a9c49a50f370887a3d Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 18:19:09 +0200 Subject: [PATCH 85/87] fix rustdoc --- substrate/frame/staking-async/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/substrate/frame/staking-async/src/lib.rs b/substrate/frame/staking-async/src/lib.rs index cb39a1b0df234..2a26e88ce5498 100644 --- a/substrate/frame/staking-async/src/lib.rs +++ b/substrate/frame/staking-async/src/lib.rs @@ -36,7 +36,7 @@ //! ### Non-minting mode (`DisableMinting = true`) //! //! Staking does **not** mint tokens. It expects an external source (e.g. `pallet-dap`) to -//! fund the general staker reward pot ([`GeneralPotAccountProvider`]). At each era boundary, +//! fund the general staker reward pot ([`PotAccountProvider`]). At each era boundary, //! staking snapshots the accumulated balance into an era-specific pot via //! [`EraRewardManager`](reward::EraRewardManager). Payouts transfer from the era pot. //! From a3a6045c5ad2ec25afd6f1f497af4694a74445d8 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 18:21:09 +0200 Subject: [PATCH 86/87] dap mode in integration test --- .../integration-tests/src/ah/mock.rs | 28 ++++++++++ .../integration-tests/src/ah/test.rs | 56 ++++++++++--------- .../integration-tests/src/ah/weights.rs | 5 +- 3 files changed, 61 insertions(+), 28 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index 796d5a0a936dd..b325863df31da 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -23,9 +23,12 @@ use frame_election_provider_support::{ }; use frame_support::{ sp_runtime::testing::TestXt, + traits::fungible::Mutate, weights::{Weight, WeightMeter}, }; use pallet_election_provider_multi_block as multi_block; +use pallet_staking_async::PotAccountProvider; +use sp_staking::budget::BudgetRecipient; use pallet_election_provider_multi_block::{Event as ElectionEvent, Phase}; use pallet_staking_async::{ActiveEra, CurrentEra, Forcing}; use pallet_staking_async_rc_client::{ @@ -894,6 +897,10 @@ impl ExtBuilder { let mut state: TestState = t.into(); state.execute_with(|| { + if !UseLegacyEraPayout::get() { + setup_dap(); + } + // initialises events roll_next(); }); @@ -902,6 +909,27 @@ impl ExtBuilder { } } +/// Set up DAP infrastructure: budget allocation, timestamp, fund pots with ED. +pub(crate) fn setup_dap() { + let staker_key = + as + BudgetRecipient>::budget_key(); + let buffer_key = + as BudgetRecipient>::budget_key(); + let mut budget = pallet_dap::BudgetAllocationMap::new(); + budget.try_insert(staker_key, Perbill::from_percent(50)).unwrap(); + budget.try_insert(buffer_key, Perbill::from_percent(50)).unwrap(); + pallet_dap::BudgetAllocation::::put(budget); + + pallet_dap::LastIssuanceTimestamp::::put(MockTime::get()); + + // Fund general staker pot with ED to keep it alive. + let general_pot = pallet_staking_async::SequentialTest::pot_account( + pallet_staking_async::RewardPot::General(pallet_staking_async::RewardKind::StakerRewards), + ); + Balances::mint_into(&general_pot, 1).unwrap(); +} + parameter_types! { static StakingEventsIndex: usize = 0; static ElectionEventsIndex: usize = 0; diff --git a/substrate/frame/staking-async/integration-tests/src/ah/test.rs b/substrate/frame/staking-async/integration-tests/src/ah/test.rs index 5065ae019db6d..89af0f2afbb04 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/test.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/test.rs @@ -666,21 +666,21 @@ fn on_offence_current_era() { roll_next(); assert_eq!( staking_events_since_last_call(), - vec![staking_async::Event::SlashComputed { + vec![StakingEvent::SlashComputed { offence_era: 1, slash_era: 3, offender: 5, - page: 0 - },] + page: 0, + }] ); roll_next(); assert_eq!( staking_events_since_last_call(), - vec![staking_async::Event::SlashComputed { + vec![StakingEvent::SlashComputed { offence_era: 1, slash_era: 3, offender: 3, - page: 0 + page: 0, }] ); @@ -721,12 +721,9 @@ fn on_offence_current_era_instant_apply() { // flush the events. let _ = staking_events_since_last_call(); - // Record initial state for DAP verification let dap_buffer = as sp_staking::budget::BudgetRecipient< AccountId, >>::pot_account(); - let initial_dap_balance = Balances::free_balance(&dap_buffer); - let initial_total_issuance = Balances::total_issuance(); assert_ok!(rc_client::Pallet::::relay_new_offence_paged( RuntimeOrigin::root(), @@ -766,43 +763,48 @@ fn on_offence_current_era_instant_apply() { ] ); + // Capture state before slash application (after DAP drips have occurred). + let initial_dap_balance = Balances::free_balance(&dap_buffer); + let initial_total_issuance = Balances::total_issuance(); + // 2 blocks to process these offences, and they are applied on the spot. roll_next(); assert_eq!( staking_events_since_last_call(), vec![ - staking_async::Event::SlashComputed { + StakingEvent::SlashComputed { offence_era: 1, slash_era: 1, offender: 5, - page: 0 + page: 0, }, - staking_async::Event::Slashed { staker: 5, amount: 50 }, - staking_async::Event::Slashed { staker: 110, amount: 50 } + StakingEvent::Slashed { staker: 5, amount: 50 }, + StakingEvent::Slashed { staker: 110, amount: 50 }, ] ); roll_next(); assert_eq!( staking_events_since_last_call(), vec![ - staking_async::Event::SlashComputed { + StakingEvent::SlashComputed { offence_era: 1, slash_era: 1, offender: 3, - page: 0 + page: 0, }, - staking_async::Event::Slashed { staker: 3, amount: 50 } + StakingEvent::Slashed { staker: 3, amount: 50 }, ] ); - // DAP verification: slashed funds (50 + 50 + 50 = 150) should go to buffer + // DAP verification: each block drips 12_000ms of inflation. Budget is 50/50, + // so buffer gets 6_000 per block. Two slash-processing blocks = 12_000 drip. + // Plus 150 from slashes (50 + 50 + 50). let final_dap_balance = Balances::free_balance(&dap_buffer); - let final_total_issuance = Balances::total_issuance(); + assert_eq!(final_dap_balance - initial_dap_balance, 12_000 + 150); - // DAP buffer should have received all slashed funds - assert_eq!(final_dap_balance, initial_dap_balance + 150); - // Total issuance should be preserved (funds not burned) - assert_eq!(final_total_issuance, initial_total_issuance); + // Slashing redistributes (no burn), DAP mints 2 * 12_000 = 24_000. + let final_total_issuance = Balances::total_issuance(); + assert_eq!(final_total_issuance, initial_total_issuance + 24_000); }); } @@ -912,12 +914,12 @@ fn on_offence_previous_era() { roll_next(); assert_eq!( staking_events_since_last_call(), - vec![staking_async::Event::SlashComputed { + vec![StakingEvent::SlashComputed { offence_era: 2, slash_era: 4, offender: 3, - page: 0 - },] + page: 0, + }] ); // roll to the next era. @@ -987,13 +989,13 @@ fn on_offence_previous_era_instant_apply() { assert_eq!( staking_events_since_last_call(), vec![ - staking_async::Event::SlashComputed { + StakingEvent::SlashComputed { offence_era: 1, slash_era: 1, offender: 3, - page: 0 + page: 0, }, - staking_async::Event::Slashed { staker: 3, amount: 50 } + StakingEvent::Slashed { staker: 3, amount: 50 }, ] ); diff --git a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs index 8e8b07acf6c9d..58443915ca1b5 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/weights.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/weights.rs @@ -208,7 +208,10 @@ impl pallet_staking_async::WeightInfo for StakingAsyncWeightInfo { Default::default() } fn process_offence_queue() -> Weight { - >::get().max_block + let max_block = + >::get().max_block; + // Reserve headroom for DAP drip weight which also runs in on_initialize. + max_block.saturating_sub(<() as pallet_dap::weights::WeightInfo>::drip_issuance()) } fn rc_on_offence(_: u32) -> Weight { Default::default() From 282a851e83a845b56bc7f993e74da25effd4adf3 Mon Sep 17 00:00:00 2001 From: Ankan Date: Mon, 13 Apr 2026 19:30:28 +0200 Subject: [PATCH 87/87] fmt --- .../integration-tests/src/ah/mock.rs | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs index b325863df31da..b479abed1c1a7 100644 --- a/substrate/frame/staking-async/integration-tests/src/ah/mock.rs +++ b/substrate/frame/staking-async/integration-tests/src/ah/mock.rs @@ -27,14 +27,12 @@ use frame_support::{ weights::{Weight, WeightMeter}, }; use pallet_election_provider_multi_block as multi_block; -use pallet_staking_async::PotAccountProvider; -use sp_staking::budget::BudgetRecipient; use pallet_election_provider_multi_block::{Event as ElectionEvent, Phase}; -use pallet_staking_async::{ActiveEra, CurrentEra, Forcing}; +use pallet_staking_async::{ActiveEra, CurrentEra, Forcing, PotAccountProvider}; use pallet_staking_async_rc_client::{ OutgoingValidatorSet, SendKeysError, SendOperationError, SessionReport, ValidatorSetReport, }; -use sp_staking::SessionIndex; +use sp_staking::{budget::BudgetRecipient, SessionIndex}; use xcm::latest::{prelude::*, Asset, AssetId, Assets, Fungibility, Junction, Location}; use xcm_builder::{FungibleAdapter, IsConcrete}; use xcm_executor::{ @@ -911,11 +909,10 @@ impl ExtBuilder { /// Set up DAP infrastructure: budget allocation, timestamp, fund pots with ED. pub(crate) fn setup_dap() { - let staker_key = - as - BudgetRecipient>::budget_key(); - let buffer_key = - as BudgetRecipient>::budget_key(); + let staker_key = as BudgetRecipient>::budget_key(); + let buffer_key = as BudgetRecipient>::budget_key(); let mut budget = pallet_dap::BudgetAllocationMap::new(); budget.try_insert(staker_key, Perbill::from_percent(50)).unwrap(); budget.try_insert(buffer_key, Perbill::from_percent(50)).unwrap();