Skip to content

[Staking] Move reward minting to DAP; payouts from drip-funded era pots#11616

Merged
Ank4n merged 98 commits intomasterfrom
ankn-staker-reward-from-pot
Apr 13, 2026
Merged

[Staking] Move reward minting to DAP; payouts from drip-funded era pots#11616
Ank4n merged 98 commits intomasterfrom
ankn-staker-reward-from-pot

Conversation

@Ank4n
Copy link
Copy Markdown
Contributor

@Ank4n Ank4n commented Apr 1, 2026

Extracted from #10844.

Overview

This PR introduces dual-mode era rewards for pallet-staking-async:

  • Non-minting mode (DisableMinting = true): Staking does not mint. An external source (e.g. pallet-dap) funds a general reward pot. Staking snapshots the pot into era-specific accounts at each era boundary. Payouts transfer from the era pot.
  • Legacy minting mode (DisableMinting = false): EraPayout computes inflation, tokens are minted on-the-fly during payout. Kept for Kusama compatibility where inflation depends on the staking ratio.

Switching from legacy to non-minting is a one-way migration.

How it works

Non-minting mode (Polkadot)

DAP drips inflation continuously into a general staker reward pot. At each era boundary, EraRewardManager::snapshot_era_rewards transfers the accumulated balance into an era-specific pot. Payouts transfer from that pot. When an era expires past HistoryDepth, unclaimed funds are returned via UnclaimedRewardHandler.

DisableMintingGuard is set on the first successful snapshot as a payout-side safety net -- prevents minting for eras that should have pots.

Runtime Changes

Staking Async

  • DisableMinting = ConstBool<true>
  • EraPayout = () (noop, never called)
  • RewardRemainder = (), MaxEraDuration = ()
  • GeneralPots / EraPots = Seed<StakingPotsPalletId>
  • UnclaimedRewardHandler = DAP (or any OnUnbalanced handler)

DAP

  • Implement IssuanceCurve -- same as EraPayout except it does not do the reward-treasury split and returns one single amount.
  • register DAP::buffer and Staking::StakerRewardRecipient in BudgetRecipients.
  • Run MigrateV1ToV2 to seed LastIssuanceTimestamp and BudgetAllocation
  • Pre-fund general staker pot with ED

Legacy minting mode (Kusama and other non-polkadot runtimes)

Same as before:
EraPayout::era_payout() computes inflation from total_staked, total_issuance, and era duration. MaxStakedRewards caps the staker portion. Remainder goes to RewardRemainder. Tokens minted on payout.

Runtime Changes

Staking Async

  • DisableMinting = ConstBool<false>
  • EraPayout = same as before
  • GeneralPots / EraPots = Seed<AnyPalletId> (required to compile, never called)

No DAP needed

Config Changes

Added:

  • DisableMinting: Get<bool>: compile-time constant controlling which mode. Irreversible once true.
  • UnclaimedRewardHandler: receives unclaimed era rewards during stale era cleanup.
  • GeneralPots / EraPots: pot account providers for era reward bookkeeping.
  • StakerRewardCalculator: Implementation for commission based reward-split between the validator and nominators.

Preserved for legacy mode (Kusama):

  • EraPayout, RewardRemainder, MaxEraDuration, MaxStakedRewards: used only when DisableMinting = false.

New storage:

  • MaxCommission: upper bound on validator commission (defaults to 100%).
  • DisableMintingGuard: safety guard: era from which legacy minting is permanently disabled on the payout side.

New extrinsic:

  • set_max_commission: callable via AdminOrigin (StakingAdmin).

WAH

  • PolkadotIssuanceCurve replaces old EraPayout impl.
  • DAP MigrateV1ToV2 migration.

TODO

  • Run benchmark for staking and dap pallets.
  • Create issue to pre fund general staking pots with ED post merge.

@Ank4n Ank4n requested a review from a team as a code owner April 1, 2026 20:13
@Ank4n Ank4n added the T2-pallets This PR/Issue is related to a particular pallet. label Apr 1, 2026
@Ank4n
Copy link
Copy Markdown
Contributor Author

Ank4n commented Apr 1, 2026

/cmd bench --pallet pallet_staking_async

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 1, 2026

Command "bench --pallet pallet_staking_async" has started 🚀 See logs here

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 2, 2026

Command "bench --pallet pallet_staking_async" has finished ✅ See logs here

Details

Subweight results:
File Extrinsic Old New Change [%]
cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs set_min_commission 102.96us 133.70us +29.86
cumulus/parachains/runtimes/assets/asset-hub-westend/src/weights/pallet_staking_async.rs force_apply_min_commission 187.29us 213.21us +13.84
Command output:

✅ Successful benchmarks of runtimes/pallets:
-- asset-hub-westend: ['pallet_staking_async']

/// 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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
pub(crate) fn create(era: EraIndex, pot_type: EraPotType) -> T::AccountId {
fn create(era: EraIndex, pot_type: EraPotType) -> T::AccountId {

Should not be called anywhere else I presume?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Its called in benchmarks.

validator_total_reward: BalanceOf<T>,
validator_commission: Perbill,
validator_own_stake: BalanceOf<T>,
total_stake: BalanceOf<T>,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
total_stake: BalanceOf<T>,
total_exposure: BalanceOf<T>,

not to be mistaken with total stake that could mean something different (total % of DOTs staked)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

done

Comment thread substrate/frame/staking-async/src/reward.rs
Comment thread substrate/frame/staking-async/src/reward.rs Outdated
Comment thread substrate/frame/staking-async/src/lib.rs Outdated
Copy link
Copy Markdown
Contributor

@kianenigma kianenigma left a comment

Choose a reason for hiding this comment

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

In terms of review, I managed to get over this in one pass (to my own surprise)

What I think is missing is:

  • View functions for pots, enabling UIs to display them easier
  • Adding the budgets to the genesis of the test runtime, enabling more tests
    • I manually tested that if we let the chain run for 4 eras, the following are correct: Pot accounts are created and populated. The ErasValidatorRewards is the right snapshot. calling a payout works. But this can be codified in a test as well.
  • For now I am using 2 pallet ids to resolve conflicting account ids.

My half finish work while reviewing this is:
https://github.com/paritytech/polkadot-sdk/compare/ankn-staker-reward-from-pot...kiz-dap3-review?expand=1

I made a simple UI for this in https://github.com/kianenigma/apps/tree/kiz-dap

@Ank4n
Copy link
Copy Markdown
Contributor Author

Ank4n commented Apr 13, 2026

  • View functions for pots, enabling UIs to display them easier

Good point. I will add them in the following PR (#11651).

  • Adding the budgets to the genesis of the test runtime, enabling more tests

ahh very good point. I did it in unit tests but not in the integration tests. Let me do and see what fails.

  • I manually tested that if we let the chain run for 4 eras, the following are correct: Pot accounts are created and populated. The ErasValidatorRewards is the right snapshot. calling a payout works. But this can be codified in a test as well.
  • For now I am using 2 pallet ids to resolve conflicting account ids.

My half finish work while reviewing this is: https://github.com/paritytech/polkadot-sdk/compare/ankn-staker-reward-from-pot...kiz-dap3-review?expand=1

I made a simple UI for this in https://github.com/kianenigma/apps/tree/kiz-dap

There is this e2e drip test that runs for one era and verifies drips are continuous and in expected range, as well as the final snapshot reward is sensible.

@Ank4n Ank4n enabled auto-merge April 13, 2026 17:48
@Ank4n Ank4n added this pull request to the merge queue Apr 13, 2026
Merged via the queue into master with commit de22911 Apr 13, 2026
363 of 370 checks passed
@Ank4n Ank4n deleted the ankn-staker-reward-from-pot branch April 13, 2026 19:54
pull Bot pushed a commit to cshein45/polkadot-sdk that referenced this pull request Apr 21, 2026
Extracted from paritytech#10844.
Builds on paritytech#11616.
Specs: https://hackmd.io/@jonasW3F/rkN6BXE2ex

## Overview

Adds a separate validator self-stake incentive reward track. Validators
receive an additional bonus from a dedicated
incentive pot, proportional to their self-stake weight. Payout is flat
liquid. Vesting implementation will be in a follow-up PR.

## Incentive Weight Curve

Each validator's share of the incentive pot is determined by a piecewise
sqrt function:

- Below optimum: `w(s) = √s`
- Between optimum and cap: `w(s) = √(T + k² × (s - T))` (diminishing
returns)
- Above cap: plateau

Weights are calculated during era planning and stored per-validator. At
payout, each validator's incentive =
`(their_weight / total_weight) × era_incentive_budget`, prorated across
pages.

## Changes

**New storage:** `OptimumSelfStake`, `HardCapSelfStake`,
`SelfStakeSlopeFactor`,
`ErasValidatorIncentiveAllocation` (era budget snapshot),
`ErasTotalValidatorWeight`, `ErasValidatorIncentive` (per-validator
weights).

**New extrinsic:** `set_validator_self_stake_incentive_config`: sets
optimum, cap, and slope factor. Callable by StakingAdmin. Validates
optimum ≤ cap.

**New event:** `ValidatorIncentivePaid { era, validator_stash, dest,
amount }`

**Era lifecycle:** `end_era_dap` now snapshots both staker reward and
incentive pots. Era planning calculates and stores incentive weights for
each elected validator. Pruning cleans up `ErasValidatorIncentive`
storage.

**Payout:** `do_payout_stakers_by_page` calls
`pay_validator_incentive_for_page` after staker rewards. Direct
`Currency::transfer` from incentive era pot to payout account.


## TODO
- [x] Run staking async bench
- [x] Update integration test with self stake incentive setup.
- [x] View Functions for Staking Pots. 
- [ ] Test with and improve on [Kian's
UI](https://github.com/kianenigma/apps/tree/kiz-dap)

---------

Co-authored-by: cmd[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Paolo La Camera <paolo@parity.io>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

T2-pallets This PR/Issue is related to a particular pallet.

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

5 participants