-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[Staking] Add issuance and budget traits in prep for Budget Split #11513
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1d17819
c3dc7e8
6f2742b
14e3aa9
ddd01ec
e14a388
187aa7f
5075904
4571f1f
946ff6a
bb15c4e
e0b4282
2df7d71
c6bbb0f
a30d654
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| title: Add issuance/budget traits | ||
| doc: | ||
| - audience: Runtime Dev | ||
| description: |- | ||
| Moves `EraPayout` trait from `pallet-staking` and `pallet-staking-async` to `sp-staking`, | ||
| eliminating duplicate definitions. Adds a new `budget` module with stake independent | ||
| traits for issuance computation (`IssuanceCurve`) and budget distribution | ||
| (`BudgetRecipient`, `BudgetRecipientList`). Also adds `StakerRewardCalculator` trait for | ||
| customizing validator incentive weights and staker reward splits. | ||
|
|
||
| No behavior changes. Existing `EraPayout` re-exports from both staking pallets are preserved. | ||
| crates: | ||
| - name: sp-staking | ||
| bump: minor | ||
| - name: pallet-staking | ||
| bump: patch | ||
| - name: pallet-staking-async | ||
| bump: patch | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| // 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. | ||
|
|
||
| //! Traits for inflation issuance and budget distribution. | ||
| //! | ||
| //! These traits define how new tokens are minted and distributed among budget recipients | ||
| //! (e.g. staker rewards, validator incentives). | ||
|
|
||
| use alloc::vec::Vec; | ||
| use sp_runtime::BoundedVec; | ||
|
|
||
| /// Maximum length of a budget key identifier. | ||
| pub const MAX_BUDGET_KEY_LEN: u32 = 32; | ||
|
|
||
| /// Identifier for a budget category in the inflation distribution system. | ||
| /// | ||
| /// Each budget recipient (e.g. staker rewards, validator incentive) is identified | ||
| /// by a unique key. Keys are bounded to [`MAX_BUDGET_KEY_LEN`] bytes. | ||
| pub type BudgetKey = BoundedVec<u8, sp_core::ConstU32<MAX_BUDGET_KEY_LEN>>; | ||
|
|
||
| /// Computes new token issuance for a given time period. | ||
| /// | ||
| /// Unlike [`super::EraPayout`], this trait does not depend on staking state. Issuance is | ||
| /// purely a function of total supply and elapsed time. | ||
| pub trait IssuanceCurve<Balance> { | ||
| /// Compute how much new tokens to mint for the given period. | ||
| fn issue(total_issuance: Balance, elapsed_millis: u64) -> Balance; | ||
| } | ||
|
|
||
| /// A recipient of inflation budget. | ||
| /// | ||
| /// Pallets that want a share of inflation implement this trait, providing a unique key | ||
| /// and a pot account where minted funds are deposited. | ||
| pub trait BudgetRecipient<AccountId> { | ||
| /// Unique identifier for this budget category. | ||
| fn budget_key() -> BudgetKey; | ||
| /// The account that receives minted inflation funds. | ||
| fn pot_account() -> AccountId; | ||
| } | ||
|
|
||
| /// Aggregates multiple [`BudgetRecipient`]s into a list. | ||
| /// | ||
| /// Implemented for tuples of `BudgetRecipient` types, allowing runtime configuration like: | ||
| /// ```ignore | ||
| /// type BudgetRecipients = (StakerRewardRecipient, ValidatorIncentiveRecipient); | ||
| /// ``` | ||
| pub trait BudgetRecipientList<AccountId> { | ||
| /// Collect all registered recipients as `(key, account)` pairs. | ||
| fn recipients() -> Vec<(BudgetKey, AccountId)>; | ||
| } | ||
|
|
||
| impl<AccountId> BudgetRecipientList<AccountId> for () { | ||
| fn recipients() -> Vec<(BudgetKey, AccountId)> { | ||
| Vec::new() | ||
| } | ||
| } | ||
|
|
||
| #[impl_trait_for_tuples::impl_for_tuples(1, 10)] | ||
| #[tuple_types_custom_trait_bound(BudgetRecipient<AccountId>)] | ||
| impl<AccountId> BudgetRecipientList<AccountId> for Tuple { | ||
| fn recipients() -> Vec<(BudgetKey, AccountId)> { | ||
| let mut v = Vec::new(); | ||
| for_tuples!( #( v.push((Tuple::budget_key(), Tuple::pot_account())); )* ); | ||
|
Ank4n marked this conversation as resolved.
|
||
| debug_assert!( | ||
| { | ||
| let mut keys: Vec<_> = v.iter().map(|(k, _)| k.clone()).collect(); | ||
| keys.sort(); | ||
| keys.windows(2).all(|w| w[0] != w[1]) | ||
| }, | ||
| "Duplicate BudgetRecipient key detected" | ||
| ); | ||
| v | ||
| } | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| struct RecipientA; | ||
| impl BudgetRecipient<u64> for RecipientA { | ||
| fn budget_key() -> BudgetKey { | ||
| BudgetKey::truncate_from(b"alpha".to_vec()) | ||
| } | ||
| fn pot_account() -> u64 { | ||
| 1 | ||
| } | ||
| } | ||
|
|
||
| struct RecipientB; | ||
| impl BudgetRecipient<u64> for RecipientB { | ||
| fn budget_key() -> BudgetKey { | ||
| BudgetKey::truncate_from(b"beta".to_vec()) | ||
| } | ||
| fn pot_account() -> u64 { | ||
| 2 | ||
| } | ||
| } | ||
|
|
||
| // Duplicate key: same as RecipientA. | ||
| struct RecipientDuplicate; | ||
| impl BudgetRecipient<u64> for RecipientDuplicate { | ||
| fn budget_key() -> BudgetKey { | ||
| BudgetKey::truncate_from(b"alpha".to_vec()) | ||
| } | ||
| fn pot_account() -> u64 { | ||
| 3 | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| fn unique_keys_work() { | ||
| let recipients = <(RecipientA, RecipientB) as BudgetRecipientList<u64>>::recipients(); | ||
| assert_eq!(recipients.len(), 2); | ||
| } | ||
|
|
||
| #[test] | ||
| #[should_panic(expected = "Duplicate BudgetRecipient key detected")] | ||
| fn duplicate_keys_panics() { | ||
| let _ = <(RecipientA, RecipientDuplicate) as BudgetRecipientList<u64>>::recipients(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -32,6 +32,7 @@ use sp_runtime::{ | |
| Debug, DispatchError, DispatchResult, Perbill, Saturating, | ||
| }; | ||
|
|
||
| pub mod budget; | ||
| pub mod offence; | ||
|
|
||
| pub mod currency_to_vote; | ||
|
|
@@ -726,6 +727,67 @@ pub trait DelegationMigrator { | |
| fn force_kill_agent(agent: Agent<Self::AccountId>); | ||
| } | ||
|
|
||
| /// Handler for determining how much of a balance should be paid out on the current era. | ||
| /// | ||
| /// [`budget::IssuanceCurve`] is the successor to this trait, decoupling issuance computation | ||
| /// from staking state. | ||
| pub trait EraPayout<Balance> { | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will be deprecated in the following PR |
||
| /// Determine the payout for this era. | ||
| /// | ||
| /// Returns the amount to be paid to stakers in this era, as well as whatever else should be | ||
| /// paid out ("the rest"). | ||
| fn era_payout( | ||
| total_staked: Balance, | ||
| total_issuance: Balance, | ||
| era_duration_millis: u64, | ||
| ) -> (Balance, Balance); | ||
| } | ||
|
|
||
| impl<Balance: Default> EraPayout<Balance> for () { | ||
| fn era_payout( | ||
| _total_staked: Balance, | ||
| _total_issuance: Balance, | ||
| _era_duration_millis: u64, | ||
| ) -> (Balance, Balance) { | ||
| (Default::default(), Default::default()) | ||
| } | ||
| } | ||
|
|
||
| /// Result of splitting a validator's staking reward between the validator and their nominators. | ||
| /// | ||
| /// Produced by [`StakerRewardCalculator::calculate_staker_reward`]. | ||
| #[derive(Debug, Clone, PartialEq, Eq)] | ||
| pub struct StakerRewardResult<Balance> { | ||
| /// Total payout for the validator (commission + proportional stake reward). | ||
| pub validator_payout: Balance, | ||
| /// Total payout for all nominators, to be split proportionally by the caller. | ||
| pub nominator_payout: Balance, | ||
| } | ||
|
|
||
| /// Handles two independent reward calculations: | ||
| /// | ||
| /// 1. **Staker reward split** ([`Self::calculate_staker_reward`]) — determines how a validator's | ||
| /// staking reward is divided between the validator and their nominators. | ||
| /// | ||
| /// 2. **Validator incentive weight** ([`Self::calculate_validator_incentive_weight`]) — determines | ||
| /// a validator's relative share of a separate validator incentive pot, based on self-stake. This | ||
| /// incentive pot is validator-only; nominators do not receive from it. | ||
| pub trait StakerRewardCalculator<Balance> { | ||
| /// Compute a weight for this validator's share of the validator incentive pot. | ||
| /// | ||
| /// Called once per validator during era planning. All validators' weights are summed, and | ||
| /// each validator's incentive payout is proportional to `their_weight / total_weight`. | ||
| fn calculate_validator_incentive_weight(self_stake: Balance) -> Balance; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Without any implementation yet, can you document how these two are meant to be used? I think I figured it out last time by looking at the code, but have forgotten again, and not intuitive enough (= I am not smart enough) to reverse-engineer 🙈 I am not sure what is this weight + how it related to the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. improved the doc, hopefully more now? 946ff6a This trait combines staker reward split and incentive weight calculation. Could argue to split it as well, but a runtime can always create a custom impl that delegates to different strategies internally, so I think this is fine. tldr for incentive weight: every validator gets reward proportional to their_weight / total_weight. |
||
|
|
||
| /// Split a validator's staking reward into validator and nominator portions. | ||
| fn calculate_staker_reward( | ||
| validator_total_reward: Balance, | ||
| validator_commission: Perbill, | ||
| validator_own_stake: Balance, | ||
| total_stake: Balance, | ||
| ) -> StakerRewardResult<Balance>; | ||
| } | ||
|
|
||
| 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"), $); | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we want to put
majorhere to make check-semver happier (E.g. https://github.com/paritytech/polkadot-sdk/actions/runs/23793615511/job/69335428077?pr=11513)?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
its moved to sp-staking and re-exported from the crates where it was removed. So should not break anything, right? Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe you're right but I don't think check-semver is too smart to catch it 😅 Now, it's not a mandatory job to merge, but will require some attention if (hopefully not) it doesn't make the cut for 2604. Do nothing or validate:false might be an option as well
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't wanna retrigger CI (it takes forever 😂 )
pallet_staking_asyncwill bump to major in the following PRs. But I can keep an eye on this.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
totally fine by me - let's all 🙏 that CI Gods are gentle with us and we can merge now finally 😄