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

Refactor out MaxPossibleReward, fix staking arithmetic #4041

Merged
merged 2 commits into from
Nov 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 5 additions & 2 deletions core/sr-primitives/src/curve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ use core::ops::Sub;
#[derive(PartialEq, Eq, primitives::RuntimeDebug)]
pub struct PiecewiseLinear<'a> {
/// Array of points. Must be in order from the lowest abscissas to the highest.
pub points: &'a [(Perbill, Perbill)]
pub points: &'a [(Perbill, Perbill)],
/// The maximum value that can be returned.
pub maximum: Perbill,
}

fn abs_sub<N: Ord + Sub<Output=N> + Clone>(a: N, b: N) -> N where {
Expand Down Expand Up @@ -135,7 +137,8 @@ fn test_calculate_for_fraction_times_denominator() {
(Perbill::from_parts(0_000_000_000), Perbill::from_parts(0_500_000_000)),
(Perbill::from_parts(0_500_000_000), Perbill::from_parts(1_000_000_000)),
(Perbill::from_parts(1_000_000_000), Perbill::from_parts(0_000_000_000)),
]
],
maximum: Perbill::from_parts(1_000_000_000),
};

pub fn formal_calculate_for_fraction_times_denominator(n: u64, d: u64) -> u64 {
Expand Down
5 changes: 1 addition & 4 deletions node/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ impl session::historical::Trait for Runtime {
srml_staking_reward_curve::build! {
const REWARD_CURVE: PiecewiseLinear<'static> = curve!(
min_inflation: 0_025_000,
max_inflation: 0_100_000, // 10% - must be equal to MaxReward below.
max_inflation: 0_100_000,
ideal_stake: 0_500_000,
falloff: 0_050_000,
max_piece_count: 40,
Expand All @@ -253,8 +253,6 @@ parameter_types! {
pub const SessionsPerEra: sr_staking_primitives::SessionIndex = 6;
pub const BondingDuration: staking::EraIndex = 24 * 28;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE;
pub const MaxReward: Perbill = Perbill::from_percent(10);
// ^^^ 10% - must be equal to max_inflation, above.
}

impl staking::Trait for Runtime {
Expand All @@ -269,7 +267,6 @@ impl staking::Trait for Runtime {
type BondingDuration = BondingDuration;
type SessionInterface = Self;
type RewardCurve = RewardCurve;
type MaxPossibleReward = MaxReward;
}

parameter_types! {
Expand Down
9 changes: 9 additions & 0 deletions srml/staking/reward-curve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,14 @@ fn compute_points(input: &INposInput) -> Vec<(u32, u32)> {
fn generate_piecewise_linear(points: Vec<(u32, u32)>) -> TokenStream2 {
let mut points_tokens = quote!();

let max = points.iter()
.map(|&(_, x)| x)
.max()
.unwrap_or(0)
.checked_mul(1_000)
// clip at 1.0 for sanity only since it'll panic later if too high.
.unwrap_or(1_000_000_000);

for (x, y) in points {
let error = || panic!(format!(
"Generated reward curve approximation doesn't fit into [0, 1] -> [0, 1] \
Expand All @@ -346,6 +354,7 @@ fn generate_piecewise_linear(points: Vec<(u32, u32)>) -> TokenStream2 {
quote!(
_sr_primitives::curve::PiecewiseLinear::<'static> {
points: & [ #points_tokens ],
maximum: _sr_primitives::Perbill::from_parts(#max),
}
)
}
Expand Down
49 changes: 29 additions & 20 deletions srml/staking/src/inflation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,17 @@ pub fn compute_total_payout<N>(
npos_token_staked: N,
total_tokens: N,
era_duration: u64
) -> N where N: SimpleArithmetic + Clone
{
) -> (N, N) where N: SimpleArithmetic + Clone {
// Milliseconds per year for the Julian year (365.25 days).
const MILLISECONDS_PER_YEAR: u64 = 1000 * 3600 * 24 * 36525 / 100;

Perbill::from_rational_approximation(era_duration as u64, MILLISECONDS_PER_YEAR)
* yearly_inflation.calculate_for_fraction_times_denominator(npos_token_staked, total_tokens)
let portion = Perbill::from_rational_approximation(era_duration as u64, MILLISECONDS_PER_YEAR);
let payout = portion * yearly_inflation.calculate_for_fraction_times_denominator(
npos_token_staked,
total_tokens.clone(),
);
let maximum = portion * (yearly_inflation.maximum * total_tokens);
(payout, maximum)
}

#[cfg(test)]
Expand All @@ -59,26 +63,31 @@ mod test {
#[test]
fn npos_curve_is_sensible() {
const YEAR: u64 = 365 * 24 * 60 * 60 * 1000;

// check maximum inflation.
// not 10_000 due to rounding error.
assert_eq!(super::compute_total_payout(&I_NPOS, 0, 100_000u64, YEAR).1, 9_993);

//super::I_NPOS.calculate_for_fraction_times_denominator(25, 100)
assert_eq!(super::compute_total_payout(&I_NPOS, 0, 100_000u64, YEAR), 2_498);
assert_eq!(super::compute_total_payout(&I_NPOS, 5_000, 100_000u64, YEAR), 3_248);
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, YEAR), 6_246);
assert_eq!(super::compute_total_payout(&I_NPOS, 40_000, 100_000u64, YEAR), 8_494);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, YEAR), 9_993);
assert_eq!(super::compute_total_payout(&I_NPOS, 60_000, 100_000u64, YEAR), 4_379);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, YEAR), 2_733);
assert_eq!(super::compute_total_payout(&I_NPOS, 95_000, 100_000u64, YEAR), 2_513);
assert_eq!(super::compute_total_payout(&I_NPOS, 100_000, 100_000u64, YEAR), 2_505);
assert_eq!(super::compute_total_payout(&I_NPOS, 0, 100_000u64, YEAR).0, 2_498);
assert_eq!(super::compute_total_payout(&I_NPOS, 5_000, 100_000u64, YEAR).0, 3_248);
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, YEAR).0, 6_246);
assert_eq!(super::compute_total_payout(&I_NPOS, 40_000, 100_000u64, YEAR).0, 8_494);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, YEAR).0, 9_993);
assert_eq!(super::compute_total_payout(&I_NPOS, 60_000, 100_000u64, YEAR).0, 4_379);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, YEAR).0, 2_733);
assert_eq!(super::compute_total_payout(&I_NPOS, 95_000, 100_000u64, YEAR).0, 2_513);
assert_eq!(super::compute_total_payout(&I_NPOS, 100_000, 100_000u64, YEAR).0, 2_505);

const DAY: u64 = 24 * 60 * 60 * 1000;
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, DAY), 17);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, DAY), 27);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, DAY), 7);
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, DAY).0, 17);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, DAY).0, 27);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, DAY).0, 7);

const SIX_HOURS: u64 = 6 * 60 * 60 * 1000;
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, SIX_HOURS), 4);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, SIX_HOURS), 7);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, SIX_HOURS), 2);
assert_eq!(super::compute_total_payout(&I_NPOS, 25_000, 100_000u64, SIX_HOURS).0, 4);
assert_eq!(super::compute_total_payout(&I_NPOS, 50_000, 100_000u64, SIX_HOURS).0, 7);
assert_eq!(super::compute_total_payout(&I_NPOS, 75_000, 100_000u64, SIX_HOURS).0, 2);

const HOUR: u64 = 60 * 60 * 1000;
assert_eq!(
Expand All @@ -87,7 +96,7 @@ mod test {
2_500_000_000_000_000_000_000_000_000u128,
5_000_000_000_000_000_000_000_000_000u128,
HOUR
),
).0,
57_038_500_000_000_000_000_000
);
}
Expand Down
18 changes: 6 additions & 12 deletions srml/staking/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -524,10 +524,6 @@ pub trait Trait: system::Trait {

/// The NPoS reward curve to use.
type RewardCurve: Get<&'static PiecewiseLinear<'static>>;

/// The maximum possible reward (in proportion of total issued tokens) that can be paid in one
/// reward cycle.
type MaxPossibleReward: Get<Perbill>;
}

/// Mode of era-forcing.
Expand Down Expand Up @@ -1203,7 +1199,7 @@ impl<T: Trait> Module<T> {
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(
let (total_payout, max_payout) = inflation::compute_total_payout(
&T::RewardCurve::get(),
total_rewarded_stake.clone(),
T::Currency::total_issuance(),
Expand All @@ -1220,16 +1216,14 @@ impl<T: Trait> Module<T> {
}
}

let total_reward = total_imbalance.peek();
// assert!(total_reward <= total_payout)

let max_reward = T::MaxPossibleReward::get() * T::Currency::total_issuance();
let rest_reward = max_reward.saturating_sub(total_reward);
// assert!(total_imbalance.peek() == total_payout)
let total_payout = total_imbalance.peek();

Self::deposit_event(RawEvent::Reward(total_reward, rest_reward));
let rest = max_payout.saturating_sub(total_payout);
Self::deposit_event(RawEvent::Reward(total_payout, rest));

T::Reward::on_unbalanced(total_imbalance);
T::RewardRemainder::on_unbalanced(T::Currency::issue(rest_reward));
T::RewardRemainder::on_unbalanced(T::Currency::issue(rest));
}

// Increment current era.
Expand Down
8 changes: 2 additions & 6 deletions srml/staking/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,6 @@ parameter_types! {
pub const SessionsPerEra: SessionIndex = 3;
pub const BondingDuration: EraIndex = 3;
pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS;
pub const MaxReward: Perbill = Perbill::from_percent(10);
}
impl Trait for Test {
type Currency = balances::Module<Self>;
Expand All @@ -206,7 +205,6 @@ impl Trait for Test {
type BondingDuration = BondingDuration;
type SessionInterface = Self;
type RewardCurve = RewardCurve;
type MaxPossibleReward = MaxReward;
}

pub struct ExtBuilder {
Expand Down Expand Up @@ -434,14 +432,12 @@ pub fn start_era(era_index: EraIndex) {
}

pub fn current_total_payout_for_duration(duration: u64) -> u64 {
let res = inflation::compute_total_payout(
inflation::compute_total_payout(
<Test as Trait>::RewardCurve::get(),
<Module<Test>>::slot_stake() * 2,
Balances::total_issuance(),
duration,
);

res
).0
}

pub fn reward_all_elected() {
Expand Down