Pass max-total to RewardRemainder on end_era (#5697)

* Pass max-total to RewardRemainder on end_era

* add test and event

* add doc

Co-authored-by: thiolliere <gui.thiolliere@gmail.com>
This commit is contained in:
Jaco Greeff
2020-04-20 13:13:45 +02:00
committed by GitHub
parent 4b1f7d187f
commit 8c52a2dae6
4 changed files with 62 additions and 6 deletions
+3 -2
View File
@@ -21,10 +21,11 @@
use sp_runtime::{Perbill, traits::AtLeast32Bit, curve::PiecewiseLinear};
/// The total payout to all validators (and their nominators) per era.
/// The total payout to all validators (and their nominators) per era and maximum payout.
///
/// Defined as such:
/// `payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year`
/// `staker-payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year`
/// `maximum-payout = max_yearly_inflation * total_tokens / era_per_year`
///
/// `era_duration` is expressed in millisecond.
pub fn compute_total_payout<N>(
+28 -3
View File
@@ -172,6 +172,22 @@
//!
//! ## Implementation Details
//!
//! ### Era payout
//!
//! The era payout is computed using yearly inflation curve defined at
//! [`T::RewardCurve`](./trait.Trait.html#associatedtype.RewardCurve) as such:
//!
//! ```nocompile
//! staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year
//! ```
//! This payout is used to reward stakers as defined in next section
//!
//! ```nocompile
//! remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout
//! ```
//! The remaining reward is send to the configurable end-point
//! [`T::RewardRemainder`](./trait.Trait.html#associatedtype.RewardRemainder).
//!
//! ### Reward Calculation
//!
//! Validators and nominators are rewarded at the end of each era. The total reward of an era is
@@ -744,6 +760,7 @@ pub trait Trait: frame_system::Trait {
type CurrencyToVote: Convert<BalanceOf<Self>, VoteWeight> + Convert<u128, BalanceOf<Self>>;
/// Tokens have been minted and are unused for validator-reward.
/// See [Era payout](./index.html#era-payout).
type RewardRemainder: OnUnbalanced<NegativeImbalanceOf<Self>>;
/// The overarching event type.
@@ -772,7 +789,8 @@ pub trait Trait: frame_system::Trait {
/// Interface for interacting with a session module.
type SessionInterface: self::SessionInterface<Self::AccountId>;
/// The NPoS reward curve to use.
/// The NPoS reward curve used to define yearly inflation.
/// See [Era payout](./index.html#era-payout).
type RewardCurve: Get<&'static PiecewiseLinear<'static>>;
/// Something that can estimate the next session change, accurately or as a best effort guess.
@@ -1059,6 +1077,9 @@ decl_storage! {
decl_event!(
pub enum Event<T> where Balance = BalanceOf<T>, <T as frame_system::Trait>::AccountId {
/// The era payout has been set; the first balance is the validator-payout; the second is
/// the remainder from the maximum amount of reward.
EraPayout(EraIndex, Balance, Balance),
/// The staker has been rewarded by this amount. `AccountId` is the stash account.
Reward(AccountId, Balance),
/// One validator (and its nominators) has been slashed by the given amount.
@@ -2570,16 +2591,20 @@ impl<T: Trait> Module<T> {
let now_as_millis_u64 = T::UnixTime::now().as_millis().saturated_into::<u64>();
let era_duration = now_as_millis_u64 - active_era_start;
let (total_payout, _max_payout) = inflation::compute_total_payout(
let (validator_payout, max_payout) = inflation::compute_total_payout(
&T::RewardCurve::get(),
Self::eras_total_stake(&active_era.index),
T::Currency::total_issuance(),
// Duration of era; more than u64::MAX is rewarded as u64::MAX.
era_duration.saturated_into::<u64>(),
);
let rest = max_payout.saturating_sub(validator_payout);
Self::deposit_event(RawEvent::EraPayout(active_era.index, validator_payout, rest));
// Set ending era reward.
<ErasValidatorReward<T>>::insert(&active_era.index, total_payout);
<ErasValidatorReward<T>>::insert(&active_era.index, validator_payout);
T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));
}
}
+26 -1
View File
@@ -277,11 +277,26 @@ parameter_types! {
pub const UnsignedPriority: u64 = 1 << 20;
}
thread_local! {
pub static REWARD_REMAINDER_UNBALANCED: RefCell<u128> = RefCell::new(0);
}
pub struct RewardRemainderMock;
impl OnUnbalanced<NegativeImbalanceOf<Test>> for RewardRemainderMock {
fn on_nonzero_unbalanced(amount: NegativeImbalanceOf<Test>) {
REWARD_REMAINDER_UNBALANCED.with(|v| {
*v.borrow_mut() += amount.peek();
});
drop(amount);
}
}
impl Trait for Test {
type Currency = Balances;
type UnixTime = Timestamp;
type CurrencyToVote = CurrencyToVoteHandler;
type RewardRemainder = ();
type RewardRemainder = RewardRemainderMock;
type Event = MetaEvent;
type Slash = ();
type Reward = ();
@@ -976,3 +991,13 @@ macro_rules! assert_session_era {
);
};
}
pub(crate) fn staking_events() -> Vec<Event<Test>> {
System::events().into_iter().map(|r| r.event).filter_map(|e| {
if let MetaEvent::staking(inner) = e {
Some(inner)
} else {
None
}
}).collect()
}
+5
View File
@@ -152,6 +152,7 @@ fn rewards_should_work() {
// should check that:
// * rewards get recorded per session
// * rewards get paid per Era
// * `RewardRemainder::on_unbalanced` is called
// * Check that nominators are also rewarded
ExtBuilder::default().nominate(true).build_and_execute(|| {
let init_balance_10 = Balances::total_balance(&10);
@@ -197,6 +198,8 @@ fn rewards_should_work() {
start_session(3);
assert_eq!(Staking::active_era().unwrap().index, 1);
assert_eq!(mock::REWARD_REMAINDER_UNBALANCED.with(|v| *v.borrow()), 7050);
assert_eq!(*mock::staking_events().last().unwrap(), RawEvent::EraPayout(0, 2350, 7050));
mock::make_all_reward_payment(0);
assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + part_for_10 * total_payout_0*2/3, 2);
@@ -220,6 +223,8 @@ fn rewards_should_work() {
assert!(total_payout_1 > 10); // Test is meaningful if reward something
mock::start_era(2);
assert_eq!(mock::REWARD_REMAINDER_UNBALANCED.with(|v| *v.borrow()), 7050*2);
assert_eq!(*mock::staking_events().last().unwrap(), RawEvent::EraPayout(1, 2350, 7050));
mock::make_all_reward_payment(1);
assert_eq_error_rate!(Balances::total_balance(&10), init_balance_10 + part_for_10 * (total_payout_0 * 2/3 + total_payout_1), 2);