-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[Staking] Self stake incentive for Validators #11651
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
base: ankn-staker-reward-from-pot
Are you sure you want to change the base?
Changes from 11 commits
15ab79b
7d496e0
0d19e7b
63c712f
a283929
a6633b5
243f46d
56d5e5a
86948fa
8eb0861
32880aa
671f0b4
a573167
8343456
3ec1beb
43e930e
d2a08c3
d270e11
1f2f4bd
9340a31
c01a450
7c36219
b47df30
a9dc064
f1274cb
50185a8
c032dac
74138ba
55aa179
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,17 @@ | ||
| title: "Validator self-stake incentive curve (non-vested)" | ||
| doc: | ||
| - audience: Runtime Dev | ||
| description: |- | ||
| Adds a separate validator incentive reward track funded from a second DAP budget pot. | ||
| Each validator's share is determined by a sqrt-based piecewise weight function of their | ||
| self-stake, with governance-configurable parameters (optimum, cap, slope factor). | ||
| Payout is a direct liquid transfer from the era incentive pot. | ||
|
|
||
| New extrinsic: `set_validator_self_stake_incentive_config` (AdminOrigin). | ||
| crates: | ||
| - name: pallet-staking-async | ||
| bump: major | ||
| - name: asset-hub-westend-runtime | ||
| bump: patch | ||
| - name: pallet-ahm-test | ||
| bump: patch |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,8 +38,8 @@ use frame_support::{ | |
| dispatch::WithPostDispatchInfo, | ||
| pallet_prelude::*, | ||
| traits::{ | ||
| Defensive, DefensiveSaturating, Get, Imbalance, InspectLockableCurrency, LockableCurrency, | ||
| OnUnbalanced, | ||
| fungible::Mutate as FunMutate, tokens::Preservation, Defensive, DefensiveSaturating, Get, | ||
| Imbalance, InspectLockableCurrency, LockableCurrency, OnUnbalanced, | ||
| }, | ||
| weights::Weight, | ||
| StorageDoubleMap, | ||
|
|
@@ -425,6 +425,14 @@ impl<T: Config> Pallet<T> { | |
| let validator_staker_payout_for_page = | ||
| page_stake_part.mul_floor(reward_split.validator_payout); | ||
|
|
||
| // Pay validator incentive bonus from the separate incentive pot. | ||
| // Emits `ValidatorIncentivePaid` event inside `transfer_validator_incentive`. | ||
| if let Some(incentive) = | ||
| Self::calculate_validator_incentive_for_page(era, &stash, page_stake_part) | ||
| { | ||
| Self::transfer_validator_incentive(era, &stash, incentive); | ||
| } | ||
|
|
||
| Self::deposit_event(Event::<T>::PayoutStarted { | ||
| era_index: era, | ||
| validator_stash: stash.clone(), | ||
|
|
@@ -568,8 +576,6 @@ impl<T: Config> Pallet<T> { | |
| stash: &T::AccountId, | ||
| amount: BalanceOf<T>, | ||
| ) -> Option<(BalanceOf<T>, RewardDestination<T::AccountId>)> { | ||
| use frame_support::traits::{fungible::Mutate as FunMutate, tokens::Preservation}; | ||
|
|
||
| if amount.is_zero() { | ||
| return None; | ||
| } | ||
|
|
@@ -654,6 +660,103 @@ impl<T: Config> Pallet<T> { | |
| maybe_imbalance.map(|imbalance| (imbalance, dest)) | ||
| } | ||
|
|
||
| /// Calculate the validator incentive amount for a single page. | ||
| fn calculate_validator_incentive_for_page( | ||
| era: EraIndex, | ||
| stash: &T::AccountId, | ||
| page_stake_part: Perbill, | ||
| ) -> Option<BalanceOf<T>> { | ||
| let era_incentive_budget = Eras::<T>::get_validator_incentive_budget(era); | ||
| if era_incentive_budget.is_zero() { | ||
| return None; | ||
| } | ||
|
|
||
| let (validator_weight, total_weight) = match ( | ||
| ErasValidatorIncentiveWeight::<T>::get(era, stash), | ||
| ErasSumValidatorIncentiveWeight::<T>::get(era), | ||
| ) { | ||
| (Some(w), t) => (w, t), | ||
| _ => return None, | ||
| }; | ||
|
|
||
| if total_weight.is_zero() { | ||
| log!(warn, "Total validator weight is zero but pot allocation exists for era {}", era); | ||
| Self::deposit_event(Event::<T>::Unexpected( | ||
| UnexpectedKind::ValidatorIncentiveWeightMismatch { era }, | ||
| )); | ||
| return None; | ||
| } | ||
|
|
||
| if validator_weight.is_zero() { | ||
| return None; | ||
| } | ||
|
|
||
| let validator_weight_part = Perbill::from_rational(validator_weight, total_weight); | ||
| let validator_total_incentive = validator_weight_part.mul_floor(era_incentive_budget); | ||
| let validator_incentive_for_page = page_stake_part.mul_floor(validator_total_incentive); | ||
|
|
||
| if validator_incentive_for_page.is_zero() { | ||
| return None; | ||
| } | ||
|
|
||
| Some(validator_incentive_for_page) | ||
| } | ||
|
|
||
| /// Transfer validator incentive from era pot to the validator's payout account. | ||
| /// | ||
| /// This is a direct liquid transfer. Future PRs may introduce vesting via a trait. | ||
| fn transfer_validator_incentive( | ||
| era: EraIndex, | ||
| stash: &T::AccountId, | ||
| amount: BalanceOf<T>, | ||
| ) -> BalanceOf<T> { | ||
| let (dest, payout_account) = match Self::payee(Stash(stash.clone())) { | ||
| Some(d) if !matches!(d, RewardDestination::None) => { | ||
| match Self::payout_account_for_dest(stash, &d) { | ||
| Some(account) => (d, account), | ||
| None => { | ||
| defensive!("Unable to determine payout account for destination"); | ||
| return Zero::zero(); | ||
| }, | ||
| } | ||
| }, | ||
| _ => { | ||
| defensive!("Validator missing payee"); | ||
| Self::deposit_event(Event::<T>::Unexpected( | ||
| UnexpectedKind::ValidatorMissingPayee { era }, | ||
|
||
| )); | ||
| return Zero::zero(); | ||
| }, | ||
| }; | ||
|
|
||
| let incentive_pot = T::EraPots::era_pot_account(era, crate::EraPotType::ValidatorSelfStake); | ||
|
|
||
| match T::Currency::transfer( | ||
Ank4n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| &incentive_pot, | ||
| &payout_account, | ||
| amount, | ||
| Preservation::Expendable, | ||
| ) { | ||
| Ok(_) => { | ||
| Self::deposit_event(Event::<T>::ValidatorIncentivePaid { | ||
| era, | ||
| validator_stash: stash.clone(), | ||
| dest, | ||
| amount, | ||
| }); | ||
| amount | ||
| }, | ||
| Err(e) => { | ||
| log!(warn, "Failed to transfer liquid incentive: {:?}", e); | ||
| defensive!("Validator incentive liquid transfer failed"); | ||
| Self::deposit_event(Event::<T>::Unexpected( | ||
| UnexpectedKind::ValidatorIncentiveTransferFailed { era }, | ||
Ank4n marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| )); | ||
| Zero::zero() | ||
| }, | ||
| } | ||
| } | ||
|
|
||
| /// Chill a stash account. | ||
| pub(crate) fn chill_stash(stash: &T::AccountId) { | ||
| let chilled_as_validator = Self::do_remove_validator(stash); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.