mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-11 23:31:07 +00:00
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:
Generated
+1
@@ -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",
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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()],
|
||||
}),
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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>);
|
||||
|
||||
@@ -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
@@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
Reference in New Issue
Block a user