Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

staking inflation #232

Merged
merged 25 commits into from
Feb 9, 2021
Merged
Show file tree
Hide file tree
Changes from 23 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
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