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::*;