Skip to content

Commit

Permalink
staking inflation (paritytech#232)
Browse files Browse the repository at this point in the history
* init

* convert annual inflation rate to round issuance

* mv all inflation logic inside stake pallet

* pass unit tests but needs more

* genesis

* rename InflationSchedule to Range type

* Update pallets/stake/src/lib.rs

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* Update pallets/stake/src/lib.rs

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* Update pallets/stake/src/lib.rs

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>

* rename MonetaryPolicy to SetMonetaryPolicyOrigin

* make InflationSchedule to prevent unintended compounding inflation

* set round duration to one hour

* simplify

* logistics

* fmt

* clean

* fmt

* fmt

* make tests pass with issuance 0

* fmt

* bump spec version

* update runtime comment

* remove issuance integration test

Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
  • Loading branch information
4meta5 and JoshOrndorff authored Feb 9, 2021
1 parent 69476e0 commit 6573a0c
Show file tree
Hide file tree
Showing 10 changed files with 366 additions and 79 deletions.
3 changes: 2 additions & 1 deletion Cargo.lock

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

1 change: 1 addition & 0 deletions node/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ moonbeam-runtime = { path = "../runtime" }
moonbeam-rpc-txpool = { path = "../client/rpc/txpool" }
moonbeam-rpc-primitives-txpool = { path = "../primitives/rpc/txpool" }
author-inherent = { path = "../pallets/author-inherent"}
stake = { path = "../pallets/stake"}

# Substrate dependencies
sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
Expand Down
26 changes: 25 additions & 1 deletion node/src/chain_spec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ use moonbeam_runtime::{
use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup};
use sc_service::ChainType;
use serde::{Deserialize, Serialize};
use sp_runtime::Perbill;
use stake::{InflationInfo, Range};
use std::{collections::BTreeMap, str::FromStr};

/// Specialized `ChainSpec`. This is a specialization of the general Substrate ChainSpec type.
Expand Down Expand Up @@ -60,6 +62,7 @@ pub fn development_chain_spec() -> ChainSpec {
None,
100_000 * GLMR,
)],
moonbeam_inflation_config(),
vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()],
Default::default(), // para_id
1281, //ChainId
Expand Down Expand Up @@ -92,6 +95,7 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec {
None,
100_000 * GLMR,
)],
moonbeam_inflation_config(),
vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()],
para_id,
1280, //ChainId
Expand All @@ -108,9 +112,26 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec {
)
}

pub fn moonbeam_inflation_config() -> InflationInfo<Balance> {
InflationInfo {
expect: Range {
min: 100_000 * GLMR,
ideal: 200_000 * GLMR,
max: 500_000 * GLMR,
},
// 8766 rounds (hours) in a year
round: Range {
min: Perbill::from_parts(Perbill::from_percent(4).deconstruct() / 8766),
ideal: Perbill::from_parts(Perbill::from_percent(5).deconstruct() / 8766),
max: Perbill::from_parts(Perbill::from_percent(5).deconstruct() / 8766),
},
}
}

fn testnet_genesis(
root_key: AccountId,
stakers: Vec<(AccountId, Option<AccountId>, Balance)>,
inflation_config: InflationInfo<Balance>,
endowed_accounts: Vec<AccountId>,
para_id: ParaId,
chain_id: u64,
Expand Down Expand Up @@ -140,6 +161,9 @@ fn testnet_genesis(
pallet_ethereum: Some(EthereumConfig {}),
pallet_democracy: Some(DemocracyConfig {}),
pallet_scheduler: Some(SchedulerConfig {}),
stake: Some(StakeConfig { stakers }),
stake: Some(StakeConfig {
stakers,
inflation_config,
}),
}
}
4 changes: 2 additions & 2 deletions pallets/stake/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stake"
version = "0.1.1"
version = "0.1.2"
authors = ["PureStake"]
edition = "2018"
description = "staking pallet for validator selection and rewards"
Expand Down Expand Up @@ -29,7 +29,7 @@ std = [
"pallet-balances/std",
"pallet-staking/std",
"parity-scale-codec/std",
"serde/std",
"serde",
"sp-std/std",
"sp-runtime/std",
]
185 changes: 185 additions & 0 deletions pallets/stake/src/inflation.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
// Copyright 2019-2020 PureStake Inc.
// This file is part of Moonbeam.

// Moonbeam 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.

// Moonbeam 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 Moonbeam. If not, see <http://www.gnu.org/licenses/>.

//! Helper methods for computing issuance based on inflation
use crate::{BalanceOf, Config};
use frame_support::traits::{Currency, Get};
use parity_scale_codec::{Decode, Encode};
#[cfg(feature = "std")]
use serde::{Deserialize, Serialize};
use sp_runtime::{Perbill, RuntimeDebug};

const SECONDS_PER_YEAR: u32 = 31557600;
const SECONDS_PER_BLOCK: u32 = 6;
const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK;

fn rounds_per_year<T: Config>() -> u32 {
BLOCKS_PER_YEAR / T::BlocksPerRound::get()
}

#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)]
pub struct Range<T> {
pub min: T,
pub ideal: T,
pub max: T,
}

impl<T: Ord> Range<T> {
pub fn is_valid(&self) -> bool {
self.max >= self.ideal && self.ideal >= self.min
}
}

impl<T: Ord + Copy> From<T> for Range<T> {
fn from(other: T) -> Range<T> {
Range {
min: other,
ideal: other,
max: other,
}
}
}

/// Convert annual inflation rate range to round inflation range
pub fn annual_to_round<T: Config>(annual: Range<Perbill>) -> Range<Perbill> {
let periods = rounds_per_year::<T>();
Range {
min: Perbill::from_parts(annual.min.deconstruct() / periods),
ideal: Perbill::from_parts(annual.ideal.deconstruct() / periods),
max: Perbill::from_parts(annual.max.deconstruct() / periods),
}
}

/// Compute round issuance range from round inflation range and current total issuance
pub fn round_issuance_range<T: Config>(round: Range<Perbill>) -> Range<BalanceOf<T>> {
let circulating = T::Currency::total_issuance();
Range {
min: round.min * circulating,
ideal: round.ideal * circulating,
max: round.max * circulating,
}
}

#[cfg_attr(feature = "std", derive(Serialize, Deserialize))]
#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)]
pub struct InflationInfo<Balance> {
/// Staking expectations
pub expect: Range<Balance>,
/// Round inflation range
pub round: Range<Perbill>,
}

impl<Balance> InflationInfo<Balance> {
pub fn new<T: Config>(
annual: Range<Perbill>,
expect: Range<Balance>,
) -> InflationInfo<Balance> {
InflationInfo {
expect,
round: annual_to_round::<T>(annual),
}
}
/// Set round inflation range according to input annual inflation range
pub fn set_annual_rate<T: Config>(&mut self, new: Range<Perbill>) {
self.round = annual_to_round::<T>(new);
}
/// Set staking expectations
pub fn set_expectations(&mut self, expect: Range<Balance>) {
self.expect = expect;
}
}

#[cfg(test)]
mod tests {
use super::*;
fn mock_annual_to_round(annual: Range<Perbill>, rounds_per_year: u32) -> Range<Perbill> {
Range {
min: Perbill::from_parts(annual.min.deconstruct() / rounds_per_year),
ideal: Perbill::from_parts(annual.ideal.deconstruct() / rounds_per_year),
max: Perbill::from_parts(annual.max.deconstruct() / rounds_per_year),
}
}
fn mock_round_issuance_range(
// Total circulating before minting
circulating: u128,
// Round inflation range
round: Range<Perbill>,
) -> Range<u128> {
Range {
min: round.min * circulating,
ideal: round.ideal * circulating,
max: round.max * circulating,
}
}
#[test]
fn simple_issuance_conversion() {
// 5% inflation for 10_000_0000 = 500,000 minted over the year
// let's assume there are 10 periods in a year
// => mint 500_000 over 10 periods => 50_000 minted per period
let expected_round_issuance_range: Range<u128> = Range {
min: 50_000,
ideal: 50_000,
max: 50_000,
};
let schedule = Range {
min: Perbill::from_percent(5),
ideal: Perbill::from_percent(5),
max: Perbill::from_percent(5),
};
assert_eq!(
expected_round_issuance_range,
mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
);
}
#[test]
fn range_issuance_conversion() {
// 3-5% inflation for 10_000_0000 = 300_000-500,000 minted over the year
// let's assume there are 10 periods in a year
// => mint 300_000-500_000 over 10 periods => 30_000-50_000 minted per period
let expected_round_issuance_range: Range<u128> = Range {
min: 30_000,
ideal: 40_000,
max: 50_000,
};
let schedule = Range {
min: Perbill::from_percent(3),
ideal: Perbill::from_percent(4),
max: Perbill::from_percent(5),
};
assert_eq!(
expected_round_issuance_range,
mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10))
);
}
#[test]
fn expected_parameterization() {
let expected_round_schedule: Range<u128> = Range {
min: 46,
ideal: 57,
max: 57,
};
let schedule = Range {
min: Perbill::from_percent(4),
ideal: Perbill::from_percent(5),
max: Perbill::from_percent(5),
};
assert_eq!(
expected_round_schedule,
mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 8766))
);
}
}
Loading

0 comments on commit 6573a0c

Please sign in to comment.