Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Staking rate targeting and specific rewards. #2882

Merged
merged 43 commits into from
Jul 24, 2019
Merged
Show file tree
Hide file tree
Changes from 35 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
987ae8a
PNPoS implementation
thiolliere Jun 5, 2019
eaf08c9
wip: change staking api
thiolliere Jun 7, 2019
1adc045
code readibility
thiolliere Jun 18, 2019
b47a60e
fix overflow
thiolliere Jun 18, 2019
a441bcc
comment
thiolliere Jun 18, 2019
0b854e7
license
thiolliere Jun 18, 2019
156deff
doc
thiolliere Jun 19, 2019
6cbb21d
reorganize a bit
thiolliere Jun 19, 2019
bf65044
rename to proper english + doc
thiolliere Jun 19, 2019
9cbae9d
address comments
thiolliere Jun 27, 2019
242aa96
refactor unused mock
thiolliere Jun 28, 2019
9663a1c
fix add_point
thiolliere Jun 28, 2019
5186a59
update tests
thiolliere Jun 28, 2019
2ffcb85
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 1, 2019
1c61c25
add not equalize to ci
thiolliere Jul 1, 2019
6e1d525
Revert "add not equalize to ci"
thiolliere Jul 1, 2019
7c750e5
bring test back
thiolliere Jul 1, 2019
22ed0d2
update locks
thiolliere Jul 1, 2019
a8162c0
fix genesis config
thiolliere Jul 1, 2019
9b24d2f
add authorship event handler + test
thiolliere Jul 1, 2019
0fe56c9
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 2, 2019
5559966
uncouple timestamp from staking
thiolliere Jul 2, 2019
b5796ed
use on finalize instead
thiolliere Jul 2, 2019
04803aa
remove todo
thiolliere Jul 2, 2019
720c69f
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 2, 2019
e87eb25
simplify mock
thiolliere Jul 3, 2019
a979385
address comment
thiolliere Jul 3, 2019
0d2dfa9
doc
thiolliere Jul 7, 2019
4d18ab0
Merge remote-tracking branch 'origin/master' into HEAD
thiolliere Jul 8, 2019
1b2c9b6
merge test
thiolliere Jul 8, 2019
a9474de
fmt
thiolliere Jul 8, 2019
cfbab16
remove todo todo
thiolliere Jul 9, 2019
936ab12
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 17, 2019
2454458
move add_reward_point to regular function
thiolliere Jul 17, 2019
ced4a07
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 19, 2019
e9505cd
doc
thiolliere Jul 19, 2019
1c350eb
doc
thiolliere Jul 19, 2019
0f2a52b
Merge remote-tracking branch 'origin/master' into gui-reward-inflation
thiolliere Jul 20, 2019
9026a7d
increase version
thiolliere Jul 20, 2019
3d416c7
Merge branch 'master' into gui-reward-inflation
thiolliere Jul 23, 2019
8431c44
doc and fmt
thiolliere Jul 24, 2019
a711a1b
Update srml/staking/src/inflation.rs
thiolliere Jul 24, 2019
6d3754b
Fix some doc typos
Jul 24, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions node/cli/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -255,8 +253,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(),
Expand Down
2 changes: 0 additions & 2 deletions node/executor/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,8 +340,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()],
}),
Expand Down
1 change: 1 addition & 0 deletions node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ parameter_types! {

impl staking::Trait for Runtime {
type Currency = Balances;
type Time = Timestamp;
type CurrencyToVote = CurrencyToVoteHandler;
type OnRewardMinted = Treasury;
type Event = Event;
Expand Down
4 changes: 3 additions & 1 deletion srml/staking/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand All @@ -37,4 +38,5 @@ std = [
"primitives/std",
"session/std",
"system/std",
"authorship/std",
]
326 changes: 326 additions & 0 deletions srml/staking/src/inflation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,326 @@
// Copyright 2017-2019 Parity Technologies (UK) Ltd.
thiolliere marked this conversation as resolved.
Show resolved Hide resolved
// 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 })
]
};

/// PNPoS: the target total payout to all validators (and their nominators) per era.
///
/// The value take into consideration desired interest rate and inflation rate (see module doc)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// The value take into consideration desired interest rate and inflation rate (see module doc)
/// The values taken into consideration are desired interest rate and inflation rate (see module doc)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(maybe my suggestion, but the sentence is ambiguous)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I improved a bit, actually it meant that I_NPoS function is defined using some constant which are the desired interest rate on ideal inflation rate and desired inflation rate

/// and is defined as such:
///
/// for x the staking rate in NPoS: `PNPoS(x) = INPoS(x) * current_total_token / era_per_year`
/// i.e. `PNPoS(x) = INPoS(x) * current_total_token * era_duration / year_duration`
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 = 31_557_600u32.into();
thiolliere marked this conversation as resolved.
Show resolved Hide resolved
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());
}
thiolliere marked this conversation as resolved.
Show resolved Hide resolved

/// 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,
);
}
}
Loading