Staking rate targeting and specific rewards. (#2882)

* PNPoS implementation

* wip: change staking api

* code readibility

* fix overflow

* comment

* license

* doc

* reorganize a bit

* rename to proper english + doc

* address comments

* refactor unused mock

* fix add_point

* update tests

* add not equalize to ci

* Revert "add not equalize to ci"

This reverts commit 1c61c25874b04b1cdfadd7d353b75a189a59ad35.

* bring test back

* update locks

* fix genesis config

* add authorship event handler + test

* uncouple timestamp from staking

* use on finalize instead

* remove todo

* simplify mock

* address comment

* doc

* merge test

* fmt

* remove todo todo

* move add_reward_point to regular function

* doc

* doc

* increase version

* doc and fmt

* Update srml/staking/src/inflation.rs

Co-Authored-By: Bastian Köcher <bkchr@users.noreply.github.com>

* Fix some doc typos
This commit is contained in:
thiolliere
2019-07-24 19:25:43 +02:00
committed by Gavin Wood
parent 1c346d2190
commit 2b6f664caf
11 changed files with 711 additions and 206 deletions
+1
View File
@@ -3928,6 +3928,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",
-4
View File
@@ -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(),
-2
View File
@@ -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()],
}),
+1
View File
@@ -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;
+3 -1
View File
@@ -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",
]
+331
View File
@@ -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://www.gnu.org/licenses/>.
//! 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<N>(&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<N>(&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<N>(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,
);
}
}
+140 -37
View File
@@ -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<u32>,
}
/// Indicates the initial status of the staker.
#[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))]
pub enum StakerStatus<AccountId> {
@@ -434,6 +456,7 @@ type PositiveImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::PositiveImbalance;
type NegativeImbalanceOf<T> =
<<T as Trait>::Currency as Currency<<T as system::Trait>::AccountId>>::NegativeImbalance;
type MomentOf<T>= <<T as Trait>::Time as Time>::Moment;
type RawAssignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance);
type Assignment<T> = (<T as system::Trait>::AccountId, ExtendedBalance, BalanceOf<T>);
@@ -485,6 +508,9 @@ pub trait Trait: system::Trait {
/// The staking balance.
type Currency: LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
/// 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<T>;
/// The start of the current era.
pub CurrentEraStart get(current_era_start): MomentOf<T>;
/// 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<T>;
/// 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<T>() = default;
fn on_finalize() {
// Set the start of the first era.
if !<CurrentEraStart<T>>::exists() {
<CurrentEraStart<T>>::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<T: Trait> Module<T> {
/// 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<T>) {
fn reward_validator(stash: &T::AccountId, reward: BalanceOf<T>) -> PositiveImbalanceOf<T> {
let off_the_table = reward.min(Self::validators(stash).validator_payment);
let reward = reward - off_the_table;
let mut imbalance = <PositiveImbalanceOf<T>>::zero();
@@ -1058,8 +1088,10 @@ impl<T: Trait> Module<T> {
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<T: Trait> Module<T> {
fn new_session(session_index: SessionIndex)
-> Option<(Vec<T::AccountId>, Vec<(T::AccountId, Exposure<T::AccountId, BalanceOf<T>>)>)>
{
// accumulate good session reward
let reward = Self::current_session_reward();
<CurrentEraReward<T>>::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<T: Trait> Module<T> {
/// get a chance to set their session keys.
fn new_era(start_session_index: SessionIndex) -> Option<Vec<T::AccountId>> {
// Payout
let reward = <CurrentEraReward<T>>::take();
if !reward.is_zero() {
let rewards = CurrentEraRewards::take();
let now = T::Time::now();
let previous_era_start = <CurrentEraStart<T>>::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<T> = (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.
<BalanceOf<T>>::from(era_duration.saturated_into::<u32>()),
);
let mut total_imbalance = <PositiveImbalanceOf<T>>::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<T> = 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<T: Trait> Module<T> {
}
// Reassign all Stakers.
let (slot_stake, maybe_new_validators) = Self::select_validators();
// Update the balances for rewarding according to the stakes.
<CurrentSessionReward<T>>::put(Self::session_reward() * slot_stake);
let (_slot_stake, maybe_new_validators) = Self::select_validators();
maybe_new_validators
}
@@ -1331,6 +1377,24 @@ impl<T: Trait> Module<T> {
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) {
<Module<T>>::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<T: Trait> session::OnSessionEnding<T::AccountId> for Module<T> {
@@ -1353,6 +1417,45 @@ impl<T: Trait> OnFreeBalanceZero<T::AccountId> for Module<T> {
}
}
/// 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<T: Trait + authorship::Trait> authorship::EventHandler<T::AccountId, T::BlockNumber> for Module<T> {
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(<authorship::Module<T>>::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<N>(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::<u32>();
// 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<T>(rstd::marker::PhantomData<T>);
+43 -10
View File
@@ -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<u64> for Author11 {
fn find_author<'a, I>(_digests: I) -> Option<u64>
where I: 'a + IntoIterator<Item=(srml_support::ConsensusEngineId, &'a [u8])>
{
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<Test, Staking>;
@@ -148,7 +160,12 @@ impl session::historical::Trait for Test {
type FullIdentification = crate::Exposure<AccountId, Balance>;
type FullIdentificationOf = crate::ExposureOf<Test>;
}
impl authorship::Trait for Test {
type FindAuthor = Author11;
type UncleGenerations = UncleGenerations;
type FilterUncle = ();
type EventHandler = Module<Test>;
}
parameter_types! {
pub const MinimumPeriod: u64 = 5;
}
@@ -163,6 +180,7 @@ parameter_types! {
}
impl Trait for Test {
type Currency = balances::Module<Self>;
type Time = timestamp::Module<Self>;
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<u64>) {
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(
<Module<Test>>::slot_stake()*2,
Balances::total_issuance(),
duration,
);
res
}
pub fn add_reward_points_to_all_elected() {
for v in <Module<Test>>::current_elected() {
<Module<Test>>::add_reward_points_to_validator(v, 1);
}
}
pub fn validator_controllers() -> Vec<AccountId> {
Session::validators().into_iter().map(|s| Staking::bonded(&s).expect("no controller for validator")).collect()
}
+175 -151
View File
@@ -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.
<Validators<Test>>::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.
<Stakers<Test>>::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);
<Module<Test>>::add_reward_points_to_validator(11, 50);
<Module<Test>>::add_reward_points_to_validator(11, 50);
// This is the second validator of the current elected set.
<Module<Test>>::add_reward_points_to_validator(21, 50);
// This must be no-op as it is not an elected validator.
<Module<Test>>::add_reward_points_to_validator(1001, 10_000);
// 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 = 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));
// 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!(<Module<Test>>::slot_stake());
<Module<Test>>::add_reward_points_to_validator(11, 1);
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);
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);
assert_eq!(Staking::current_era(), 1);
assert_eq!(Balances::total_balance(&10), init_balance_10 + total_payout_0);
// 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);
// 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
<Module<Test>>::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
<Module<Test>>::add_reward_points_to_validator(41, 1);
<Module<Test>>::add_reward_points_to_validator(31, 1);
<Module<Test>>::add_reward_points_to_validator(21, 10); // must be no-op
<Module<Test>>::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
<Module<Test>>::add_reward_points_to_validator(41, 10); // must be no-op
<Module<Test>>::add_reward_points_to_validator(31, 10); // must be no-op
<Module<Test>>::add_reward_points_to_validator(21, 2);
<Module<Test>>::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
<Module<Test>>::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
<Module<Test>>::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
<Payee<Test>>::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
<Module<Test>>::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
<Module<Test>>::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);
// 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
<Module<Test>>::add_reward_points_to_validator(11, 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);
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);
<Ledger<Test>>::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
<Module<Test>>::add_reward_points_to_validator(11, 1);
<Module<Test>>::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() {
<Stakers<Test>>::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!(<authorship::Module<Test>>::author(), 11);
<Module<Test>>::note_author(11);
<Module<Test>>::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.
<Module<Test>>::note_uncle(31, 1);
// Not mandatory but must be coherent with rewards
assert_eq!(<CurrentElected<Test>>::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);
})
}
+6
View File
@@ -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 {
+11 -1
View File
@@ -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<T: Trait> ProvideInherent for Module<T> {
}
}
impl<T: Trait> Time for Module<T> {
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::*;