diff --git a/Cargo.lock b/Cargo.lock index 48edf4fa283ef..95e0cd46a5f66 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3919,6 +3919,7 @@ dependencies = [ "sr-io 2.0.0", "sr-primitives 2.0.0", "sr-std 2.0.0", + "srml-authorship 0.1.0", "srml-balances 2.0.0", "srml-session 2.0.0", "srml-support 2.0.0", diff --git a/node/cli/src/chain_spec.rs b/node/cli/src/chain_spec.rs index fc07ae4cea4d9..681be0325ec37 100644 --- a/node/cli/src/chain_spec.rs +++ b/node/cli/src/chain_spec.rs @@ -121,8 +121,6 @@ fn staging_testnet_config_genesis() -> GenesisConfig { staking: Some(StakingConfig { current_era: 0, offline_slash: Perbill::from_parts(1_000_000), - session_reward: Perbill::from_parts(2_065), - current_session_reward: 0, validator_count: 7, offline_slash_grace: 4, minimum_validator_count: 4, @@ -259,8 +257,6 @@ pub fn testnet_genesis( minimum_validator_count: 1, validator_count: 2, offline_slash: Perbill::zero(), - session_reward: Perbill::zero(), - current_session_reward: 0, offline_slash_grace: 0, stakers: initial_authorities.iter().map(|x| (x.0.clone(), x.1.clone(), STASH, StakerStatus::Validator)).collect(), invulnerables: initial_authorities.iter().map(|x| x.0.clone()).collect(), diff --git a/node/executor/src/lib.rs b/node/executor/src/lib.rs index b55461492625d..975b689919bef 100644 --- a/node/executor/src/lib.rs +++ b/node/executor/src/lib.rs @@ -361,8 +361,6 @@ mod tests { validator_count: 3, minimum_validator_count: 0, offline_slash: Perbill::zero(), - session_reward: Perbill::zero(), - current_session_reward: 0, offline_slash_grace: 0, invulnerables: vec![alice(), bob(), charlie()], }), diff --git a/node/runtime/src/lib.rs b/node/runtime/src/lib.rs index 71af5d9417e81..7cfc18470956b 100644 --- a/node/runtime/src/lib.rs +++ b/node/runtime/src/lib.rs @@ -75,8 +75,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // and set impl_version to equal spec_version. If only runtime // implementation changes and behavior does not, then leave spec_version as // is and increment impl_version. - spec_version: 117, - impl_version: 117, + spec_version: 118, + impl_version: 118, apis: RUNTIME_API_VERSIONS, }; @@ -236,6 +236,7 @@ parameter_types! { impl staking::Trait for Runtime { type Currency = Balances; + type Time = Timestamp; type CurrencyToVote = CurrencyToVoteHandler; type OnRewardMinted = Treasury; type Event = Event; diff --git a/srml/staking/Cargo.toml b/srml/staking/Cargo.toml index 74384495315cc..e9a660ebe06a7 100644 --- a/srml/staking/Cargo.toml +++ b/srml/staking/Cargo.toml @@ -15,11 +15,12 @@ primitives = { package = "sr-primitives", path = "../../core/sr-primitives", def srml-support = { path = "../support", default-features = false } system = { package = "srml-system", path = "../system", default-features = false } session = { package = "srml-session", path = "../session", default-features = false, features = ["historical"] } +authorship = { package = "srml-authorship", path = "../authorship", default-features = false } [dev-dependencies] substrate-primitives = { path = "../../core/primitives" } -timestamp = { package = "srml-timestamp", path = "../timestamp" } balances = { package = "srml-balances", path = "../balances" } +timestamp = { package = "srml-timestamp", path = "../timestamp" } rand = "0.6.5" [features] @@ -37,4 +38,5 @@ std = [ "primitives/std", "session/std", "system/std", + "authorship/std", ] diff --git a/srml/staking/src/inflation.rs b/srml/staking/src/inflation.rs new file mode 100644 index 0000000000000..503a20a629c22 --- /dev/null +++ b/srml/staking/src/inflation.rs @@ -0,0 +1,331 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! http://research.web3.foundation/en/latest/polkadot/Token%20Economics/#inflation-model + +use primitives::{Perbill, traits::SimpleArithmetic}; + +/// Linear function truncated to positive part `y = max(0, b [+ or -] a*x)` for PNPoS usage +#[derive(Debug, PartialEq, Eq, Clone, Copy)] +struct Linear { + negative_a: bool, + // Perbill + a: u32, + // Perbill + b: u32, +} + +impl Linear { + fn calculate_for_fraction_times_denominator(&self, n: N, d: N) -> N + where + N: SimpleArithmetic + Clone + { + if self.negative_a { + (Perbill::from_parts(self.b) * d).saturating_sub(Perbill::from_parts(self.a) * n) + } else { + (Perbill::from_parts(self.b) * d).saturating_add(Perbill::from_parts(self.a) * n) + } + } +} + +/// Piecewise Linear function for PNPoS usage +#[derive(Debug, PartialEq, Eq)] +struct PiecewiseLinear { + /// Array of tuple of Abscisse in Perbill and Linear. + /// + /// Each piece start with at the abscisse up to the abscisse of the next piece. + pieces: [(u32, Linear); 20], +} + +impl PiecewiseLinear { + fn calculate_for_fraction_times_denominator(&self, n: N, d: N) -> N + where + N: SimpleArithmetic + Clone + { + let part = self.pieces.iter() + .take_while(|(abscisse, _)| n > Perbill::from_parts(*abscisse) * d.clone()) + .last() + .unwrap_or(&self.pieces[0]); + + part.1.calculate_for_fraction_times_denominator(n, d) + } +} + +// Piecewise linear approximation of I_NPoS. +const I_NPOS: PiecewiseLinear = PiecewiseLinear { + pieces: [ + (0, Linear { negative_a: false, a: 150000000, b: 25000000 }), + (500000000, Linear { negative_a: true, a: 986493987, b: 593246993 }), + (507648979, Linear { negative_a: true, a: 884661327, b: 541551747 }), + (515726279, Linear { negative_a: true, a: 788373842, b: 491893761 }), + (524282719, Linear { negative_a: true, a: 697631517, b: 444319128 }), + (533378749, Linear { negative_a: true, a: 612434341, b: 398876765 }), + (543087019, Linear { negative_a: true, a: 532782338, b: 355618796 }), + (553495919, Linear { negative_a: true, a: 458675508, b: 314600968 }), + (564714479, Linear { negative_a: true, a: 390113843, b: 275883203 }), + (576879339, Linear { negative_a: true, a: 327097341, b: 239530285 }), + (590164929, Linear { negative_a: true, a: 269626004, b: 205612717 }), + (604798839, Linear { negative_a: true, a: 217699848, b: 174207838 }), + (621085859, Linear { negative_a: true, a: 171318873, b: 145401271 }), + (639447429, Linear { negative_a: true, a: 130483080, b: 119288928 }), + (660489879, Linear { negative_a: true, a: 95192479, b: 95979842 }), + (685131379, Linear { negative_a: true, a: 65447076, b: 75600334 }), + (714860569, Linear { negative_a: true, a: 41246910, b: 58300589 }), + (752334749, Linear { negative_a: true, a: 22592084, b: 44265915 }), + (803047659, Linear { negative_a: true, a: 9482996, b: 33738693 }), + (881691659, Linear { negative_a: true, a: 2572702, b: 27645944 }) + ] +}; + +/// Second per year for the Julian year (365.25 days) +const SECOND_PER_YEAR: u32 = 3600*24*36525/100; + +/// The total payout to all validators (and their nominators) per era. +/// +/// Named P_NPoS in the [paper](http://research.web3.foundation/en/latest/polkadot/Token%20Ec +/// onomics/#inflation-model). +/// +/// For x the staking rate in NPoS: `P_NPoS(x) = I_NPoS(x) * current_total_token / era_per_year` +/// i.e. `P_NPoS(x) = I_NPoS(x) * current_total_token * era_duration / year_duration` +/// +/// I_NPoS is the desired yearly inflation rate for nominated proof of stake. +pub fn compute_total_payout(npos_token_staked: N, total_tokens: N, era_duration: N) -> N +where + N: SimpleArithmetic + Clone +{ + let year_duration: N = SECOND_PER_YEAR.into(); + I_NPOS.calculate_for_fraction_times_denominator(npos_token_staked, total_tokens) + * era_duration / year_duration +} + +#[allow(non_upper_case_globals, non_snake_case)] // To stick with paper notations +#[cfg(test)] +mod test_inflation { + use std::convert::TryInto; + + // Function `y = a*x + b` using float used for testing precision of Linear + #[derive(Debug)] + struct LinearFloat { + a: f64, + b: f64, + } + + impl LinearFloat { + fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Self { + LinearFloat { + a: (y1 - y0) / (x1 - x0), + b: (x0*y1 - x1*y0) / (x0 - x1), + } + } + + fn compute(&self, x: f64) -> f64 { + self.a*x + self.b + } + } + + #[test] + fn linear_float_works() { + assert_eq!(LinearFloat::new(1.0, 2.0, 4.0, 3.0).compute(7.0), 4.0); + } + + // Constants defined in paper + const I_0: f64 = 0.025; + const i_ideal: f64 = 0.2; + const x_ideal: f64 = 0.5; + const d: f64 = 0.05; + + // Part left to 0.5 + fn I_left(x: f64) -> f64 { + I_0 + x * (i_ideal - I_0/x_ideal) + } + + // Part right to 0.5 + fn I_right(x: f64) -> f64 { + I_0 + (i_ideal*x_ideal - I_0) * 2_f64.powf((x_ideal-x)/d) + } + + // Definition of I_NPoS in float + fn I_full(x: f64) -> f64 { + if x <= 0.5 { + I_left(x) + } else { + I_right(x) + } + } + + // Compute approximation of I_NPoS into piecewise linear function + fn I_NPoS_points() -> super::PiecewiseLinear { + let mut points = vec![]; + + // Points for left part + points.push((0.0, I_0)); + points.push((0.5, I_left(0.5))); + + // Approximation for right part. + // + // We start from 0.5 (x0) and we try to find the next point (x1) for which the linear + // approximation of (x0, x1) doesn't deviate from float definition by an error of + // GEN_ERROR. + + // When computing deviation between linear approximation and float definition we iterate + // over all points with this step precision. + const STEP_PRECISION: f64 = 0.000_000_1; + // Max error used for generating points. + const GEN_ERROR: f64 = 0.000_1; + + let mut x0: f64 = 0.5; + let mut x1: f64 = x0; + + // This is just a step used to find next x1: + // if x1 + step result in a not enought precise approximation we reduce step and try again. + // we stop as soon as step is less than STEP_PRECISION. + let mut step: f64 = 0.1; + + loop { + let next_x1 = x1 + step; + + if next_x1 >= 1.0 { + points.push((1.0, I_right(1.0))); + break; + } + + let y0 = I_right(x0); + let next_y1 = I_right(next_x1); + + let mut error_overflowed = false; + + // Test error is not overflowed + + // Quick test on one point + if (I_right((x0 + next_x1)/2.0) - (y0 + next_y1)/2.0).abs() > GEN_ERROR { + error_overflowed = true; + } + + // Long test on all points + if !error_overflowed { + let linear = LinearFloat::new(x0, y0, next_x1, next_y1); + let mut cursor = x0; + while cursor < x1 { + if (I_right(cursor) - linear.compute(cursor)).abs() > GEN_ERROR { + error_overflowed = true; + } + cursor += STEP_PRECISION; + } + } + + if error_overflowed { + if step <= STEP_PRECISION { + points.push((x1, I_right(x1))); + x0 = x1; + step = 0.1; + } else { + step /= 10.0; + } + } else { + x1 = next_x1; + } + } + + // Convert points to piecewise linear definition + let pieces: Vec<(u32, super::Linear)> = (0..points.len()-1) + .map(|i| { + let p0 = points[i]; + let p1 = points[i+1]; + + let linear = LinearFloat::new(p0.0, p0.1, p1.0, p1.1); + + // Needed if we want to use a Perbill later + assert!(linear.a.abs() <= 1.0); + // Needed if we want to use a Perbill later + assert!(linear.b.abs() <= 1.0); + // Needed to stick with our restrictive definition of linear + assert!(linear.b.signum() == 1.0); + + ( + (p0.0 * 1_000_000_000.0) as u32, + super::Linear { + negative_a: linear.a.signum() < 0.0, + a: (linear.a.abs() * 1_000_000_000.0) as u32, + b: (linear.b.abs() * 1_000_000_000.0) as u32, + } + ) + }) + .collect(); + + println!("Generated pieces: {:?}", pieces); + assert_eq!(pieces.len(), 20); + + super::PiecewiseLinear { pieces: (&pieces[..]).try_into().unwrap() } + } + + /// This test is only useful to generate a new set of points for the definition of I_NPoS. + #[test] + fn generate_I_NPOS() { + assert_eq!(super::I_NPOS, I_NPoS_points()); + } + + /// This test ensure that i_npos piecewise linear approximation is close to the actual function. + /// It does compare the result from a computation in integer of different capcity and in f64. + #[test] + fn i_npos_precision() { + const STEP_PRECISION: f64 = 0.000_001; + const ERROR: f64 = 0.000_2; + + macro_rules! test_for_value { + ($($total_token:expr => $type:ty,)*) => { + let mut x = 0.1; + while x <= 1.0 { + let expected = I_full(x); + $({ + let result = super::I_NPOS.calculate_for_fraction_times_denominator( + (x * $total_token as f64) as $type, + $total_token, + ) as f64; + let expected = expected * $total_token as f64; + let error = (ERROR * $total_token as f64).max(2.0); + + let diff = (result - expected).abs(); + if diff >= error { + println!("total_token: {}", $total_token); + println!("x: {}", x); + println!("diff: {}", diff); + println!("error: {}", error); + panic!("error overflowed"); + } + })* + x += STEP_PRECISION + } + } + } + + test_for_value!( + 1_000u32 => u32, + 1_000_000u32 => u32, + 1_000_000_000u32 => u32, + 1_000_000_000_000u64 => u64, + 1_000_000_000_000_000u64 => u64, + 1_000_000_000_000_000_000u64 => u64, + 1_000_000_000_000_000_000_000u128 => u128, + 1_000_000_000_000_000_000_000_000u128 => u128, + 1_000_000_000_000_000_000_000_000_000u128 => u128, + 1_000_000_000_000_000_000_000_000_000_000u128 => u128, + 1_000_000_000_000_000_000_000_000_000_000_000_000u128 => u128, + u32::max_value() => u32, + u64::max_value() => u64, + u128::max_value() => u128, + ); + } +} diff --git a/srml/staking/src/lib.rs b/srml/staking/src/lib.rs index c72aa36e8bc8c..7a2d0dbc08109 100644 --- a/srml/staking/src/lib.rs +++ b/srml/staking/src/lib.rs @@ -167,20 +167,30 @@ //! //! ### Reward Calculation //! -//! Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each -//! session is calculated at the end of the session based on the timeliness of the session, then -//! accumulated to be paid later. The value of the new _per-session-reward_ is calculated at the end -//! of each era by multiplying `SlotStake` and `SessionReward` (`SessionReward` is the -//! multiplication factor, represented by a number between 0 and 1). Once a new era is triggered, -//! rewards are paid to the validators and their associated nominators. +//! Validators and nominators are rewarded at the end of each era. The total reward of an era is +//! calculated using the era duration and the staking rate (the total amount of tokens staked by +//! nominators and validators, divided by the total token supply). It aims to incentivise toward a +//! defined staking rate. The full specification can be found +//! [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics/#inflation-model). +//! +//! Total reward is split among validators and their nominators depending on the number of points +//! they received during the era. Points are added to a validator using +//! [`add_reward_points_to_validator`](./enum.Call.html#variant.add_reward_points_to_validator). +//! +//! [`Module`](./struct.Module.html) implements +//! [`authorship::EventHandler`](../srml_authorship/trait.EventHandler.html) to add reward points +//! to block producer and block producer of referenced uncles. +//! +//! The validator and its nominator split their reward as following: //! //! The validator can declare an amount, named //! [`validator_payment`](./struct.ValidatorPrefs.html#structfield.validator_payment), that does not //! get shared with the nominators at each reward payout through its //! [`ValidatorPrefs`](./struct.ValidatorPrefs.html). This value gets deducted from the total reward -//! that can be paid. The remaining portion is split among the validator and all of the nominators -//! that nominated the validator, proportional to the value staked behind this validator (_i.e._ -//! dividing the [`own`](./struct.Exposure.html#structfield.own) or +//! that is paid to the validator and its nominators. The remaining portion is split among the +//! validator and all of the nominators that nominated the validator, proportional to the value +//! staked behind this validator (_i.e._ dividing the +//! [`own`](./struct.Exposure.html#structfield.own) or //! [`others`](./struct.Exposure.html#structfield.others) by //! [`total`](./struct.Exposure.html#structfield.total) in [`Exposure`](./struct.Exposure.html)). //! @@ -266,6 +276,7 @@ mod mock; mod tests; mod phragmen; +mod inflation; #[cfg(all(feature = "bench", test))] mod benches; @@ -278,13 +289,14 @@ use srml_support::{ StorageValue, StorageMap, EnumerableStorageMap, decl_module, decl_event, decl_storage, ensure, traits::{ Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, - WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get + WithdrawReasons, WithdrawReason, OnUnbalanced, Imbalance, Get, Time } }; use session::{historical::OnSessionEnding, SelectInitialValidators, SessionIndex}; use primitives::Perbill; use primitives::traits::{ Convert, Zero, One, StaticLookup, CheckedSub, CheckedShl, Saturating, Bounded, + SaturatedConversion, SimpleArithmetic }; #[cfg(feature = "std")] use primitives::{Serialize, Deserialize}; @@ -302,6 +314,16 @@ const STAKING_ID: LockIdentifier = *b"staking "; /// Counter for the number of eras that have passed. pub type EraIndex = u32; +/// Reward points of an era. Used to split era total payout between validators. +#[derive(Encode, Decode, Default)] +pub struct EraRewards { + /// Total number of points. Equals the sum of reward points for each validator. + total: u32, + /// Reward at one index correspond to reward for validator in current_elected of this index. + /// Thus this reward vec is only valid for one elected set. + rewards: Vec, +} + /// Indicates the initial status of the staker. #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] pub enum StakerStatus { @@ -434,6 +456,7 @@ type PositiveImbalanceOf = <::Currency as Currency<::AccountId>>::PositiveImbalance; type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; +type MomentOf= <::Time as Time>::Moment; type RawAssignment = (::AccountId, ExtendedBalance); type Assignment = (::AccountId, ExtendedBalance, BalanceOf); @@ -485,6 +508,9 @@ pub trait Trait: system::Trait { /// The staking balance. type Currency: LockableCurrency; + /// Time used for computing era duration. + type Time: Time; + /// Convert a balance into a number used for election calculation. /// This must fit into a `u64` but is allowed to be sensibly lossy. /// TODO: #1377 @@ -522,8 +548,6 @@ decl_storage! { /// Minimum number of staking participants before emergency conditions are imposed. pub MinimumValidatorCount get(minimum_validator_count) config(): u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT; - /// Maximum reward, per validator, that is provided per acceptable session. - pub SessionReward get(session_reward) config(): Perbill = Perbill::from_parts(60); /// Slash, per validator that is taken for the first time they are found to be offline. pub OfflineSlash get(offline_slash) config(): Perbill = Perbill::from_millionths(1000); /// Number of instances of offline reports before slashing begins for validators. @@ -561,12 +585,11 @@ decl_storage! { /// The current era index. pub CurrentEra get(current_era) config(): EraIndex; - /// Maximum reward, per validator, that is provided per acceptable session. - pub CurrentSessionReward get(current_session_reward) config(): BalanceOf; + /// The start of the current era. + pub CurrentEraStart get(current_era_start): MomentOf; - /// The accumulated reward for the current era. Reset to zero at the beginning of the era - /// and increased for every successfully finished session. - pub CurrentEraReward get(current_era_reward): BalanceOf; + /// Rewards for the current era. Using indices of current elected set. + pub CurrentEraRewards: EraRewards; /// The amount of balance actively at stake for each validator slot, currently. /// @@ -650,6 +673,13 @@ decl_module! { fn deposit_event() = default; + fn on_finalize() { + // Set the start of the first era. + if !>::exists() { + >::put(T::Time::now()); + } + } + /// Take the origin account as a stash and lock up `value` of its balance. `controller` will /// be the account that controls it. /// @@ -1040,7 +1070,7 @@ impl Module { /// Reward a given validator by a specific amount. Add the reward to the validator's, and its /// nominators' balance, pro-rata based on their exposure, after having removed the validator's /// pre-payout cut. - fn reward_validator(stash: &T::AccountId, reward: BalanceOf) { + fn reward_validator(stash: &T::AccountId, reward: BalanceOf) -> PositiveImbalanceOf { let off_the_table = reward.min(Self::validators(stash).validator_payment); let reward = reward - off_the_table; let mut imbalance = >::zero(); @@ -1058,8 +1088,10 @@ impl Module { let per_u64 = Perbill::from_rational_approximation(exposure.own, total); per_u64 * reward }; + imbalance.maybe_subsume(Self::make_payout(stash, validator_cut + off_the_table)); - T::Reward::on_unbalanced(imbalance); + + imbalance } /// Session has just ended. Provide the validator set for the next session if it's an era-end, along @@ -1067,10 +1099,6 @@ impl Module { fn new_session(session_index: SessionIndex) -> Option<(Vec, Vec<(T::AccountId, Exposure>)>)> { - // accumulate good session reward - let reward = Self::current_session_reward(); - >::mutate(|r| *r += reward); - if ForceNewEra::take() || session_index % T::SessionsPerEra::get() == 0 { let validators = T::SessionInterface::validators(); let prior = validators.into_iter() @@ -1089,18 +1117,39 @@ impl Module { /// get a chance to set their session keys. fn new_era(start_session_index: SessionIndex) -> Option> { // Payout - let reward = >::take(); - if !reward.is_zero() { + let rewards = CurrentEraRewards::take(); + let now = T::Time::now(); + let previous_era_start = >::mutate(|v| { + rstd::mem::replace(v, now.clone()) + }); + let era_duration = now - previous_era_start; + if !era_duration.is_zero() { let validators = Self::current_elected(); - for v in validators.iter() { - Self::reward_validator(v, reward); + + let validator_len: BalanceOf = (validators.len() as u32).into(); + let total_rewarded_stake = Self::slot_stake() * validator_len; + + let total_payout = inflation::compute_total_payout( + total_rewarded_stake.clone(), + T::Currency::total_issuance(), + // Era of duration more than u32::MAX is rewarded as u32::MAX. + >::from(era_duration.saturated_into::()), + ); + + let mut total_imbalance = >::zero(); + + let total_points = rewards.total; + for (v, points) in validators.iter().zip(rewards.rewards.into_iter()) { + if points != 0 { + let reward = multiply_by_rational(total_payout, points, total_points); + total_imbalance.subsume(Self::reward_validator(v, reward)); + } } - Self::deposit_event(RawEvent::Reward(reward)); - let len = validators.len() as u32; // validators length can never overflow u64 - let len: BalanceOf = len.into(); - let total_minted = reward * len; - let total_rewarded_stake = Self::slot_stake() * len; - T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake); + + let total_reward = total_imbalance.peek(); + Self::deposit_event(RawEvent::Reward(total_reward)); + T::Reward::on_unbalanced(total_imbalance); + T::OnRewardMinted::on_dilution(total_reward, total_rewarded_stake); } // Increment current era. @@ -1126,10 +1175,7 @@ impl Module { } // Reassign all Stakers. - let (slot_stake, maybe_new_validators) = Self::select_validators(); - - // Update the balances for rewarding according to the stakes. - >::put(Self::session_reward() * slot_stake); + let (_slot_stake, maybe_new_validators) = Self::select_validators(); maybe_new_validators } @@ -1331,6 +1377,24 @@ impl Module { Self::deposit_event(event); } } + + /// Add reward points to validator. + /// + /// At the end of the era each the total payout will be distributed among validator + /// relatively to their points. + fn add_reward_points_to_validator(validator: T::AccountId, points: u32) { + >::current_elected().iter() + .position(|elected| *elected == validator) + .map(|index| { + CurrentEraRewards::mutate(|rewards| { + if let Some(new_total) = rewards.total.checked_add(points) { + rewards.total = new_total; + rewards.rewards.resize((index + 1).max(rewards.rewards.len()), 0); + rewards.rewards[index] += points; // Addition is less than total + } + }); + }); + } } impl session::OnSessionEnding for Module { @@ -1353,6 +1417,45 @@ impl OnFreeBalanceZero for Module { } } +/// Add reward points to block authors: +/// * 20 points to the block producer for producing a (non-uncle) block in the relay chain, +/// * 2 points to the block producer for each reference to a previously unreferenced uncle, and +/// * 1 point to the producer of each referenced uncle block. +impl authorship::EventHandler for Module { + fn note_author(author: T::AccountId) { + Self::add_reward_points_to_validator(author, 20); + } + fn note_uncle(author: T::AccountId, _age: T::BlockNumber) { + Self::add_reward_points_to_validator(>::author(), 2); + Self::add_reward_points_to_validator(author, 1); + } +} + +// This is guarantee not to overflow on whatever values. +// `num` must be inferior to `den` otherwise it will be reduce to `den`. +fn multiply_by_rational(value: N, num: u32, den: u32) -> N + where N: SimpleArithmetic + Clone +{ + let num = num.min(den); + + let result_divisor_part = value.clone() / den.into() * num.into(); + + let result_remainder_part = { + let rem = value % den.into(); + + // Fits into u32 because den is u32 and remainder < den + let rem_u32 = rem.saturated_into::(); + + // Multiplication fits into u64 as both term are u32 + let rem_part = rem_u32 as u64 * num as u64 / den as u64; + + // Result fits into u32 as num < total_points + (rem_part as u32).into() + }; + + result_divisor_part + result_remainder_part +} + /// A `Convert` implementation that finds the stash of the given controller account, /// if any. pub struct StashOf(rstd::marker::PhantomData); diff --git a/srml/staking/src/mock.rs b/srml/staking/src/mock.rs index f0151bf915499..a1548a00807d6 100644 --- a/srml/staking/src/mock.rs +++ b/srml/staking/src/mock.rs @@ -23,9 +23,10 @@ use primitives::testing::{Header, UintAuthorityId}; use substrate_primitives::{H256, Blake2Hasher}; use runtime_io; use srml_support::{assert_ok, impl_outer_origin, parameter_types, EnumerableStorageMap}; -use srml_support::traits::{Currency, Get}; -use crate::{EraIndex, GenesisConfig, Module, Trait, StakerStatus, - ValidatorPrefs, RewardDestination, Nominators +use srml_support::traits::{Currency, Get, FindAuthor}; +use crate::{ + EraIndex, GenesisConfig, Module, Trait, StakerStatus, ValidatorPrefs, RewardDestination, + Nominators, inflation }; /// The AccountId alias in this test module. @@ -86,6 +87,16 @@ impl_outer_origin!{ pub enum Origin for Test {} } +/// Author of block is always 11 +pub struct Author11; +impl FindAuthor for Author11 { + fn find_author<'a, I>(_digests: I) -> Option + where I: 'a + IntoIterator + { + Some(11) + } +} + // Workaround for https://github.com/rust-lang/rust/issues/26925 . Remove when sorted. #[derive(Clone, PartialEq, Eq, Debug)] pub struct Test; @@ -97,7 +108,7 @@ parameter_types! { impl system::Trait for Test { type Origin = Origin; type Index = u64; - type BlockNumber = u64; + type BlockNumber = BlockNumber; type Hash = H256; type Hashing = ::primitives::traits::BlakeTwo256; type AccountId = AccountId; @@ -132,6 +143,7 @@ impl balances::Trait for Test { parameter_types! { pub const Period: BlockNumber = 1; pub const Offset: BlockNumber = 0; + pub const UncleGenerations: u64 = 0; } impl session::Trait for Test { type OnSessionEnding = session::historical::NoteHistoricalRoot; @@ -148,7 +160,12 @@ impl session::historical::Trait for Test { type FullIdentification = crate::Exposure; type FullIdentificationOf = crate::ExposureOf; } - +impl authorship::Trait for Test { + type FindAuthor = Author11; + type UncleGenerations = UncleGenerations; + type FilterUncle = (); + type EventHandler = Module; +} parameter_types! { pub const MinimumPeriod: u64 = 5; } @@ -163,6 +180,7 @@ parameter_types! { } impl Trait for Test { type Currency = balances::Module; + type Time = timestamp::Module; type CurrencyToVote = CurrencyToVoteHandler; type OnRewardMinted = (); type Event = (); @@ -175,7 +193,6 @@ impl Trait for Test { pub struct ExtBuilder { existential_deposit: u64, - reward: u64, validator_pool: bool, nominate: bool, validator_count: u32, @@ -188,7 +205,6 @@ impl Default for ExtBuilder { fn default() -> Self { Self { existential_deposit: 0, - reward: 10, validator_pool: false, nominate: true, validator_count: 2, @@ -261,6 +277,8 @@ impl ExtBuilder { (41, balance_factor * 2000), (100, 2000 * balance_factor), (101, 2000 * balance_factor), + // This allow us to have a total_payout different from 0. + (999, 1_000_000_000_000), ], vesting: vec![], }.assimilate_storage(&mut t, &mut c); @@ -285,9 +303,7 @@ impl ExtBuilder { ], validator_count: self.validator_count, minimum_validator_count: self.minimum_validator_count, - session_reward: Perbill::from_millionths((1000000 * self.reward / balance_factor) as u32), offline_slash: Perbill::from_percent(5), - current_session_reward: self.reward, offline_slash_grace: 0, invulnerables: vec![], }.assimilate_storage(&mut t, &mut c); @@ -378,8 +394,9 @@ pub fn bond_nominator(acc: u64, val: u64, target: Vec) { pub fn start_session(session_index: session::SessionIndex) { // Compensate for session delay let session_index = session_index + 1; - for i in 0..(session_index - Session::current_index()) { + for i in Session::current_index()..session_index { System::set_block_number((i + 1).into()); + Timestamp::set_timestamp(System::block_number()); Session::on_initialize(System::block_number()); } @@ -391,6 +408,22 @@ pub fn start_era(era_index: EraIndex) { assert_eq!(Staking::current_era(), era_index); } +pub fn current_total_payout_for_duration(duration: u64) -> u64 { + let res = inflation::compute_total_payout( + >::slot_stake()*2, + Balances::total_issuance(), + duration, + ); + + res +} + +pub fn add_reward_points_to_all_elected() { + for v in >::current_elected() { + >::add_reward_points_to_validator(v, 1); + } +} + pub fn validator_controllers() -> Vec { Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect() } diff --git a/srml/staking/src/tests.rs b/srml/staking/src/tests.rs index 5987d9800d963..39409c2c39d97 100644 --- a/srml/staking/src/tests.rs +++ b/srml/staking/src/tests.rs @@ -82,11 +82,6 @@ fn basic_setup_works() { // Initial Era and session assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 0); - - // initial rewards - assert_eq!(Staking::current_session_reward(), 10); - // initial slash_count of validators assert_eq!(Staking::slash_count(&11), 0); @@ -301,6 +296,7 @@ fn slashing_does_not_cause_underflow() { assert_eq!(Staking::offline_slash_grace(), 0); // Set validator preference so that 2^unstake_threshold would cause overflow (greater than 64) + // FIXME: that doesn't overflow. >::insert(11, ValidatorPrefs { unstake_threshold: 10, validator_payment: 0, @@ -316,7 +312,6 @@ fn slashing_does_not_cause_underflow() { }); } - #[test] fn rewards_should_work() { // should check that: @@ -324,12 +319,16 @@ fn rewards_should_work() { // * rewards get paid per Era // * Check that nominators are also rewarded with_externalities(&mut ExtBuilder::default() - .build(), + .nominate(false) + .build(), || { + // Init some balances + let _ = Balances::make_free_balance_be(&2, 500); + let delay = 1; - // this test is only in the scope of one era. Since this variable changes - // at the last block/new era, we'll save it. - let session_reward = 10; + let init_balance_2 = Balances::total_balance(&2); + let init_balance_10 = Balances::total_balance(&10); + let init_balance_11 = Balances::total_balance(&11); // Set payee to controller assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); @@ -337,17 +336,13 @@ fn rewards_should_work() { // Initial config should be correct assert_eq!(Staking::current_era(), 0); assert_eq!(Session::current_index(), 0); - assert_eq!(Staking::current_session_reward(), 10); - // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&11), 1000); - // and the nominator (to-be) - let _ = Balances::make_free_balance_be(&2, 500); - assert_eq!(Balances::total_balance(&2), 500); - - // add a dummy nominator. + // Add a dummy nominator. + // + // Equal division indicates that the reward will be equally divided among validator and + // nominator. >::insert(&11, Exposure { - own: 500, // equal division indicates that the reward will be equally divided among validator and nominator. + own: 500, total: 1000, others: vec![IndividualExposure {who: 2, value: 500 }] }); @@ -362,10 +357,21 @@ fn rewards_should_work() { Session::on_initialize(System::block_number()); assert_eq!(Staking::current_era(), 0); assert_eq!(Session::current_index(), 1); - - // session triggered: the reward value stashed should be 10 -- defined in ExtBuilder genesis. - assert_eq!(Staking::current_session_reward(), session_reward); - assert_eq!(Staking::current_era_reward(), session_reward); + >::add_reward_points_to_validator(11, 50); + >::add_reward_points_to_validator(11, 50); + // This is the second validator of the current elected set. + >::add_reward_points_to_validator(21, 50); + // This must be no-op as it is not an elected validator. + >::add_reward_points_to_validator(1001, 10_000); + + // Compute total payout now for whole duration as other parameter won't change + let total_payout = current_total_payout_for_duration(9*5); + assert!(total_payout > 10); // Test is meaningfull if reward something + + // No reward yet + assert_eq!(Balances::total_balance(&2), init_balance_2); + assert_eq!(Balances::total_balance(&10), init_balance_10); + assert_eq!(Balances::total_balance(&11), init_balance_11); block = 6; // Block 6 => Session 2 => Era 0 System::set_block_number(block); @@ -374,11 +380,6 @@ fn rewards_should_work() { assert_eq!(Staking::current_era(), 0); assert_eq!(Session::current_index(), 2); - // session reward is the same, - assert_eq!(Staking::current_session_reward(), session_reward); - // though 2 will be deducted while stashed in the era reward due to delay - assert_eq!(Staking::current_era_reward(), 2*session_reward); // - delay); - block = 9; // Block 9 => Session 3 => Era 1 System::set_block_number(block); Timestamp::set_timestamp(block*5); // back to being on time. no delays @@ -386,8 +387,10 @@ fn rewards_should_work() { assert_eq!(Staking::current_era(), 1); assert_eq!(Session::current_index(), 3); - assert_eq!(Balances::total_balance(&10), 1 + (3*session_reward)/2); - assert_eq!(Balances::total_balance(&2), 500 + (3*session_reward)/2); + // 11 validator has 2/3 of the total rewards and half half for it and its nominator + assert_eq!(Balances::total_balance(&2), init_balance_2 + total_payout/3); + assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout/3); + assert_eq!(Balances::total_balance(&11), init_balance_11); }); } @@ -400,49 +403,36 @@ fn multi_era_reward_should_work() { .nominate(false) .build(), || { - let session_reward = 10; - - // This is set by the test config builder. - assert_eq!(Staking::current_session_reward(), session_reward); - - // check the balance of a validator accounts. - assert_eq!(Balances::total_balance(&10), 1); + let init_balance_10 = Balances::total_balance(&10); // Set payee to controller assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - start_session(0); - - // session triggered: the reward value stashed should be 10 - assert_eq!(Staking::current_session_reward(), session_reward); - assert_eq!(Staking::current_era_reward(), session_reward); + // Compute now as other parameter won't change + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 10); // Test is meaningfull if reward something + dbg!(>::slot_stake()); + >::add_reward_points_to_validator(11, 1); + start_session(0); start_session(1); - - assert_eq!(Staking::current_session_reward(), session_reward); - assert_eq!(Staking::current_era_reward(), 2*session_reward); - start_session(2); + start_session(3); - // 1 + sum of of the session rewards accumulated - let recorded_balance = 1 + 3*session_reward; - assert_eq!(Balances::total_balance(&10), recorded_balance); - - // the reward for next era will be: session_reward * slot_stake - let new_session_reward = Staking::session_reward() * Staking::slot_stake(); - assert_eq!(Staking::current_session_reward(), new_session_reward); + assert_eq!(Staking::current_era(), 1); + assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout_0); - // fast forward to next era: start_session(4); - // intermediate test. - assert_eq!(Staking::current_era_reward(), 2*new_session_reward); + let total_payout_1 = current_total_payout_for_duration(3); + assert!(total_payout_1 > 10); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 101); // new era is triggered here. start_session(5); // pay time - assert_eq!(Balances::total_balance(&10), 3*new_session_reward + recorded_balance); + assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout_0 + total_payout_1); }); } @@ -457,6 +447,8 @@ fn staking_should_work() { .fair(false) // to give 20 more staked value .build(), || { + Timestamp::set_timestamp(1); // Initialize time. + // remember + compare this along with the test. assert_eq_uvec!(validator_controllers(), vec![20, 10]); @@ -622,10 +614,6 @@ fn nominating_and_rewards_should_work() { assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller)); assert_ok!(Staking::set_payee(Origin::signed(40), RewardDestination::Controller)); - // default reward for the first session. - let session_reward = 10; - assert_eq!(Staking::current_session_reward(), session_reward); - // give the man some money let initial_balance = 1000; for i in [1, 2, 3, 4, 5, 10, 11, 20, 21].iter() { @@ -640,14 +628,22 @@ fn nominating_and_rewards_should_work() { assert_ok!(Staking::bond(Origin::signed(3), 4, 1000, RewardDestination::Controller)); assert_ok!(Staking::nominate(Origin::signed(4), vec![11, 21, 41])); + // the total reward for era 0 + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(41, 1); + >::add_reward_points_to_validator(31, 1); + >::add_reward_points_to_validator(21, 10); // must be no-op + >::add_reward_points_to_validator(11, 10); // must be no-op + start_era(1); // 10 and 20 have more votes, they will be chosen by phragmen. assert_eq_uvec!(validator_controllers(), vec![20, 10]); // OLD validators must have already received some rewards. - assert_eq!(Balances::total_balance(&40), 1 + 3 * session_reward); - assert_eq!(Balances::total_balance(&30), 1 + 3 * session_reward); + assert_eq!(Balances::total_balance(&40), 1 + total_payout_0/2); + assert_eq!(Balances::total_balance(&30), 1 + total_payout_0/2); // ------ check the staked value of all parties. @@ -707,35 +703,41 @@ fn nominating_and_rewards_should_work() { assert_eq!(Staking::stakers(31).total, 0); assert_eq!(Staking::stakers(41).total, 0); + // the total reward for era 1 + let total_payout_1 = current_total_payout_for_duration(3); + assert!(total_payout_1 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(41, 10); // must be no-op + >::add_reward_points_to_validator(31, 10); // must be no-op + >::add_reward_points_to_validator(21, 2); + >::add_reward_points_to_validator(11, 1); start_era(2); - // next session reward. - let new_session_reward = Staking::session_reward() * 3 * Staking::slot_stake(); - // NOTE: some addition or substraction (-2, -3, +1) are due to arithmetic approximations + // nothing else will happen, era ends and rewards are paid again, + // it is expected that nominators will also be paid. See below + + let payout_for_10 = total_payout_1/3; + let payout_for_20 = 2*total_payout_1/3; if cfg!(feature = "equalize") { - // Both have: has [400/2000 ~ 1/5 from 10] + [600/2000 ~ 3/10 from 20]'s reward. ==> 1/5 + 3/10 = 1/2 - assert_eq!(Balances::total_balance(&2), initial_balance + new_session_reward/2 - 3); - assert_eq!(Balances::total_balance(&4), initial_balance + new_session_reward/2 - 3); - // Rest for validators. - assert_eq!(Balances::total_balance(&10), initial_balance + new_session_reward/2 + 1); - assert_eq!(Balances::total_balance(&20), initial_balance + new_session_reward/2 + 1); + // Nominator 2: has [400/2000 ~ 1/5 from 10] + [600/2000 ~ 3/10 from 20]'s reward. + assert_eq!(Balances::total_balance(&2), initial_balance + payout_for_10/5 + payout_for_20*3/10 - 1); + // Nominator 4: has [400/2000 ~ 1/5 from 20] + [600/2000 ~ 3/10 from 10]'s reward. + assert_eq!(Balances::total_balance(&4), initial_balance + payout_for_20/5 + payout_for_10*3/10); + + // Validator 10: got 1000 / 2000 external stake. + assert_eq!(Balances::total_balance(&10), initial_balance + payout_for_10/2); + // Validator 20: got 1000 / 2000 external stake. + assert_eq!(Balances::total_balance(&20), initial_balance + payout_for_20/2); } else { // Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 - assert_eq!( - Balances::total_balance(&2), - initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11) - 2 - ); + assert_eq!(Balances::total_balance(&2), initial_balance + (2*payout_for_10/9 + 3*payout_for_20/11) - 2); // Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11 - assert_eq!( - Balances::total_balance(&4), - initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11) - 2 - ); + assert_eq!(Balances::total_balance(&4), initial_balance + (2*payout_for_10/9 + 3*payout_for_20/11) - 2); - // 10 got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 - assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9 - 1); - // 10 got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11 - assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11 + 2); + // Validator 10: got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9 + assert_eq!(Balances::total_balance(&10), initial_balance + 5*payout_for_10/9 - 1); + // Validator 20: got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11 + assert_eq!(Balances::total_balance(&20), initial_balance + 5*payout_for_20/11); } check_exposure_all(); @@ -768,6 +770,10 @@ fn nominators_also_get_slashed() { assert_ok!(Staking::bond(Origin::signed(1), 2, nominator_stake, RewardDestination::default())); assert_ok!(Staking::nominate(Origin::signed(2), vec![20, 10])); + let total_payout = current_total_payout_for_duration(3); + assert!(total_payout > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); + // new era, pay rewards, start_era(1); @@ -783,7 +789,7 @@ fn nominators_also_get_slashed() { let nominator_slash = nominator_stake.min(total_slash - validator_slash); // initial + first era reward + slash - assert_eq!(Balances::total_balance(&10), initial_balance + 30 - validator_slash); + assert_eq!(Balances::total_balance(&10), initial_balance + total_payout - validator_slash); assert_eq!(Balances::total_balance(&2), initial_balance - nominator_slash); check_exposure_all(); check_nominator_all(); @@ -957,45 +963,47 @@ fn reward_destination_works() { active: 1000, unlocking: vec![], })); - // Check current session reward is 10 - let session_reward0 = 3 * Staking::current_session_reward(); // 10 - // Move forward the system for payment - Timestamp::set_timestamp(5); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); + start_era(1); // Check that RewardDestination is Staked (default) assert_eq!(Staking::payee(&11), RewardDestination::Staked); // Check that reward went to the stash account of validator - assert_eq!(Balances::free_balance(&11), 1000 + session_reward0); + assert_eq!(Balances::free_balance(&11), 1000 + total_payout_0); // Check that amount at stake increased accordingly assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, - total: 1000 + session_reward0, - active: 1000 + session_reward0, + total: 1000 + total_payout_0, + active: 1000 + total_payout_0, unlocking: vec![], })); - // Update current session reward - let session_reward1 = 3 * Staking::current_session_reward(); // 1010 (1* slot_stake) //Change RewardDestination to Stash >::insert(&11, RewardDestination::Stash); - // Move forward the system for payment - Timestamp::set_timestamp(10); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_1 = current_total_payout_for_duration(3); + assert!(total_payout_1 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); + start_era(2); // Check that RewardDestination is Stash assert_eq!(Staking::payee(&11), RewardDestination::Stash); // Check that reward went to the stash account - assert_eq!(Balances::free_balance(&11), 1000 + session_reward0 + session_reward1); + assert_eq!(Balances::free_balance(&11), 1000 + total_payout_0 + total_payout_1); // Record this value - let recorded_stash_balance = 1000 + session_reward0 + session_reward1; + let recorded_stash_balance = 1000 + total_payout_0 + total_payout_1; // Check that amount at stake is NOT increased assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, - total: 1000 + session_reward0, - active: 1000 + session_reward0, + total: 1000 + total_payout_0, + active: 1000 + total_payout_0, unlocking: vec![], })); @@ -1005,20 +1013,22 @@ fn reward_destination_works() { // Check controller balance assert_eq!(Balances::free_balance(&10), 1); - // Move forward the system for payment - Timestamp::set_timestamp(15); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_2 = current_total_payout_for_duration(3); + assert!(total_payout_2 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); + start_era(3); - let session_reward2 = 3 * Staking::current_session_reward(); // 1010 (1* slot_stake) // Check that RewardDestination is Controller assert_eq!(Staking::payee(&11), RewardDestination::Controller); // Check that reward went to the controller account - assert_eq!(Balances::free_balance(&10), 1 + session_reward2); + assert_eq!(Balances::free_balance(&10), 1 + total_payout_2); // Check that amount at stake is NOT increased assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, - total: 1000 + session_reward0, - active: 1000 + session_reward0, + total: 1000 + total_payout_0, + active: 1000 + total_payout_0, unlocking: vec![], })); // Check that amount in staked account is NOT increased. @@ -1035,10 +1045,8 @@ fn validator_payment_prefs_work() { .build(), || { // Initial config - let session_reward = 10; let validator_cut = 5; let stash_initial_balance = Balances::total_balance(&11); - assert_eq!(Staking::current_session_reward(), session_reward); // check the balance of a validator accounts. assert_eq!(Balances::total_balance(&10), 1); @@ -1059,35 +1067,15 @@ fn validator_payment_prefs_work() { validator_payment: validator_cut }); - // ------------ Fast forward - // Block 3 => Session 1 => Era 0 - let mut block = 3; - System::set_block_number(block); - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 1); - - // session triggered: the reward value stashed should be 10 -- defined in ExtBuilder genesis. - assert_eq!(Staking::current_session_reward(), session_reward); - assert_eq!(Staking::current_era_reward(), session_reward); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); - block = 6; // Block 6 => Session 2 => Era 0 - System::set_block_number(block); - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 0); - assert_eq!(Session::current_index(), 2); - - assert_eq!(Staking::current_session_reward(), session_reward); - assert_eq!(Staking::current_era_reward(), 2*session_reward); - - block = 9; // Block 9 => Session 3 => Era 1 - System::set_block_number(block); - Session::on_initialize(System::block_number()); - assert_eq!(Staking::current_era(), 1); - assert_eq!(Session::current_index(), 3); + start_era(1); // whats left to be shared is the sum of 3 rounds minus the validator's cut. - let shared_cut = 3 * session_reward - validator_cut; + let shared_cut = total_payout_0 - validator_cut; // Validator's payee is Staked account, 11, reward will be paid here. assert_eq!(Balances::total_balance(&11), stash_initial_balance + shared_cut/2 + validator_cut); // Controller account will not get any reward. @@ -1165,7 +1153,6 @@ fn bond_extra_and_withdraw_unbonded_works() { // Initial config should be correct assert_eq!(Staking::current_era(), 0); assert_eq!(Session::current_index(), 0); - assert_eq!(Staking::current_session_reward(), 10); // check the balance of a validator accounts. assert_eq!(Balances::total_balance(&10), 1); @@ -1294,22 +1281,31 @@ fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment( assert_eq!(Staking::stakers(&21).total, 69); >::insert(&20, StakingLedger { stash: 22, total: 69, active: 69, unlocking: vec![] }); + // Compute total payout now for whole duration as other parameter won't change + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 100); // Test is meaningfull if reward something + >::add_reward_points_to_validator(11, 1); + >::add_reward_points_to_validator(21, 1); + // New era --> rewards are paid --> stakes are changed start_era(1); // -- new balances + reward - assert_eq!(Staking::stakers(&11).total, 1000 + 30); - assert_eq!(Staking::stakers(&21).total, 69 + 30); + assert_eq!(Staking::stakers(&11).total, 1000 + total_payout_0/2); + assert_eq!(Staking::stakers(&21).total, 69 + total_payout_0/2); + + let _11_balance = Balances::free_balance(&11); + assert_eq!(_11_balance, 1000 + total_payout_0/2); // -- slot stake should also be updated. - assert_eq!(Staking::slot_stake(), 69 + 30); + assert_eq!(Staking::slot_stake(), 69 + total_payout_0/2); // If 10 gets slashed now, it will be slashed by 5% of exposure.total * 2.pow(unstake_thresh) Staking::on_offline_validator(10, 4); // Confirm user has been reported assert_eq!(Staking::slash_count(&11), 4); // check the balance of 10 (slash will be deducted from free balance.) - assert_eq!(Balances::free_balance(&11), 1000 + 30 - 51 /*5% of 1030*/ * 8 /*2**3*/); + assert_eq!(Balances::free_balance(&11), _11_balance - _11_balance*5/100 * 2u64.pow(3)); check_exposure_all(); check_nominator_all(); @@ -1621,6 +1617,8 @@ fn switching_roles() { .nominate(false) .build(), || { + Timestamp::set_timestamp(1); // Initialize time. + // Reset reward destination for i in &[10, 20] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); } @@ -1718,7 +1716,7 @@ fn bond_with_no_staked_value() { .nominate(false) .minimum_validator_count(1) .build(), || { - // Can't bond with 1 + // Can't bond with 1 assert_noop!( Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller), "can not bond with value less than minimum balance" @@ -1753,7 +1751,6 @@ fn bond_with_no_staked_value() { assert_ok!(Staking::withdraw_unbonded(Origin::signed(2))); assert!(Staking::ledger(2).is_none()); assert_eq!(Balances::locks(&1).len(), 0); - }); } @@ -1771,13 +1768,16 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() { // setup assert_ok!(Staking::chill(Origin::signed(30))); assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller)); - let initial_balance_2 = Balances::free_balance(&2); - let initial_balance_10 = Balances::free_balance(&10); + let init_balance_2 = Balances::free_balance(&2); + let init_balance_10 = Balances::free_balance(&10); // Stingy validator. assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller)); assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default())); + let total_payout_0 = current_total_payout_for_duration(3); + assert!(total_payout_0 > 100); // Test is meaningfull if reward something + add_reward_points_to_all_elected(); start_era(1); // 2 is elected. @@ -1786,26 +1786,25 @@ fn bond_with_little_staked_value_bounded_by_slot_stake() { assert_eq!(Staking::slot_stake(), 1); // Old ones are rewarded. - assert_eq!(Balances::free_balance(&10), initial_balance_10 + 30); + assert_eq!(Balances::free_balance(&10), init_balance_10 + total_payout_0/3); // no rewards paid to 2. This was initial election. - assert_eq!(Balances::free_balance(&2), initial_balance_2); + assert_eq!(Balances::free_balance(&2), init_balance_2); + let total_payout_1 = current_total_payout_for_duration(3); + assert!(total_payout_1 > 100); // Test is meaningfull if reward something + add_reward_points_to_all_elected(); start_era(2); assert_eq_uvec!(validator_controllers(), vec![20, 10, 2]); assert_eq!(Staking::slot_stake(), 1); - let reward = Staking::current_session_reward(); - // 2 will not get the full reward, practically 1 - assert_eq!(Balances::free_balance(&2), initial_balance_2 + reward.max(3)); - // same for 10 - assert_eq!(Balances::free_balance(&10), initial_balance_10 + 30 + reward.max(3)); + assert_eq!(Balances::free_balance(&2), init_balance_2 + total_payout_1/3); + assert_eq!(Balances::free_balance(&10), init_balance_10 + total_payout_0/3 + total_payout_1/3); check_exposure_all(); check_nominator_all(); }); } - #[cfg(feature = "equalize")] #[test] fn phragmen_linear_worse_case_equalize() { @@ -2060,7 +2059,7 @@ fn reward_validator_slashing_validator_doesnt_overflow() { >::insert(&11, Exposure { total: stake, own: stake, others: vec![] }); // Check reward - Staking::reward_validator(&11, reward_slash); + let _ = Staking::reward_validator(&11, reward_slash); assert_eq!(Balances::total_balance(&11), stake * 2); // Set staker @@ -2076,3 +2075,28 @@ fn reward_validator_slashing_validator_doesnt_overflow() { assert_eq!(Balances::total_balance(&2), 1); }) } + +#[test] +fn reward_from_authorship_event_handler_works() { + with_externalities(&mut ExtBuilder::default() + .build(), + || { + use authorship::EventHandler; + + assert_eq!(>::author(), 11); + + >::note_author(11); + >::note_uncle(21, 1); + // An uncle author that is not currently elected doesn't get rewards, + // but the block producer does get reward for referencing it. + >::note_uncle(31, 1); + + // Not mandatory but must be coherent with rewards + assert_eq!(>::get(), vec![21, 11]); + + // 21 is rewarded as an uncle procuder + // 11 is rewarded as a block procuder and unclde referencer + assert_eq!(CurrentEraRewards::get().rewards, vec![1, 20+2*2]); + assert_eq!(CurrentEraRewards::get().total, 25); + }) +} diff --git a/srml/support/src/traits.rs b/srml/support/src/traits.rs index 0b35eca659ba0..aca056eb50a8e 100644 --- a/srml/support/src/traits.rs +++ b/srml/support/src/traits.rs @@ -615,6 +615,12 @@ bitmask! { } } +pub trait Time { + type Moment: SimpleArithmetic + Codec + Clone + Default; + + fn now() -> Self::Moment; +} + impl WithdrawReasons { /// Choose all variants except for `one`. pub fn except(one: WithdrawReason) -> WithdrawReasons { diff --git a/srml/timestamp/src/lib.rs b/srml/timestamp/src/lib.rs index 43aa29e04fd24..ca7245664e034 100644 --- a/srml/timestamp/src/lib.rs +++ b/srml/timestamp/src/lib.rs @@ -96,7 +96,8 @@ use parity_codec::Encode; use parity_codec::Decode; #[cfg(feature = "std")] use inherents::ProvideInherentData; -use srml_support::{StorageValue, Parameter, decl_storage, decl_module, for_each_tuple, traits::Get}; +use srml_support::{StorageValue, Parameter, decl_storage, decl_module, for_each_tuple}; +use srml_support::traits::{Time, Get}; use runtime_primitives::traits::{SimpleArithmetic, Zero, SaturatedConversion}; use system::ensure_none; use inherents::{RuntimeString, InherentIdentifier, ProvideInherent, IsFatalError, InherentData}; @@ -321,6 +322,15 @@ impl ProvideInherent for Module { } } +impl Time for Module { + type Moment = T::Moment; + + /// Before the first set of now with inherent the value returned is zero. + fn now() -> Self::Moment { + Self::now() + } +} + #[cfg(test)] mod tests { use super::*;