From f66c97bfc1e76cc885919f75edf26914ff279c30 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Wed, 3 Feb 2021 14:15:49 -0800 Subject: [PATCH 01/23] init --- Cargo.lock | 16 +++++++++++ pallets/inflation/Cargo.toml | 29 ++++++++++++++++++++ pallets/inflation/src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++ pallets/stake/Cargo.toml | 2 ++ pallets/stake/src/lib.rs | 45 +++++++++++++++++++++++-------- runtime/Cargo.toml | 2 ++ 6 files changed, 135 insertions(+), 11 deletions(-) create mode 100644 pallets/inflation/Cargo.toml create mode 100644 pallets/inflation/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 0319138656..44111efa78 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,6 +2761,20 @@ dependencies = [ "serde", ] +[[package]] +name = "inflation" +version = "0.1.1" +dependencies = [ + "frame-support", + "frame-system", + "parity-scale-codec", + "serde", + "sp-core", + "sp-io", + "sp-runtime", + "sp-std", +] + [[package]] name = "instant" version = "0.1.9" @@ -4114,6 +4128,7 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "hex-literal", + "inflation", "log", "moonbeam-rpc-primitives-txpool", "pallet-aura", @@ -9451,6 +9466,7 @@ dependencies = [ "author-inherent", "frame-support", "frame-system", + "inflation", "pallet-balances", "pallet-staking", "parity-scale-codec", diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml new file mode 100644 index 0000000000..509c6771b9 --- /dev/null +++ b/pallets/inflation/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "inflation" +version = "0.1.1" +authors = ["PureStake"] +edition = "2018" +description = "pallet for setting inflation schedule" + +[dependencies] +frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +parity-scale-codec = { version = "1.0.0", default-features = false, features = ["derive"] } +serde = { version = "1.0.101", optional = true } +sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[dev-dependencies] +sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } +sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } + +[features] +default = ["std"] +std = [ + "frame-support/std", + "frame-system/std", + "parity-scale-codec/std", + "serde/std", + "sp-std/std", + "sp-runtime/std", +] diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs new file mode 100644 index 0000000000..213da4b190 --- /dev/null +++ b/pallets/inflation/src/lib.rs @@ -0,0 +1,52 @@ +// 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 . + +//! Pallet that sets inflation schedule, used by stake + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_support::{decl_module, decl_storage}; +use frame_system::{Config as System}; +use parity_scale_codec::{Decode, Encode}; +use sp_runtime::{DispatchResult, Perbill, RuntimeDebug}; + +#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] +pub struct InflationSchedule { + max: T, + min: T, + ideal: T, +} + +pub trait Config: System {} + +decl_storage! { + trait Store for Module as Inflation { + /// Annual inflation targets + Schedule: InflationSchedule; + } +} + +decl_module! { + pub struct Module for enum Call where origin: T::Origin { + + #[weight = 0] + fn set_schedule(origin, schedule: InflationSchedule) -> DispatchResult { + //ensure_root(origin)?; + ::put(schedule); + Ok(()) + } + } +} diff --git a/pallets/stake/Cargo.toml b/pallets/stake/Cargo.toml index 4dc61ab212..d9ffdee211 100644 --- a/pallets/stake/Cargo.toml +++ b/pallets/stake/Cargo.toml @@ -7,6 +7,7 @@ description = "staking pallet for validator selection and rewards" [dependencies] author-inherent = { path = "../author-inherent", default-features = false } +inflation = { path = "../inflation", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -24,6 +25,7 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default = ["std"] std = [ "author-inherent/std", + "inflation/std", "frame-support/std", "frame-system/std", "pallet-balances/std", diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index acdebd1a5c..0343f964b3 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -52,6 +52,7 @@ use frame_support::{ traits::{Currency, Get, Imbalance, ReservableCurrency}, }; use frame_system::{ensure_signed, Config as System}; +use inflation::{Config as Inflation, InflationSchedule}; use pallet_staking::{Exposure, IndividualExposure}; use parity_scale_codec::{Decode, Encode, HasCompact}; use set::OrderedSet; @@ -417,7 +418,7 @@ type RewardPoint = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type Candidate = Validator<::AccountId, BalanceOf>; -pub trait Config: System { +pub trait Config: System + Inflation { /// The overarching event type type Event: From> + Into<::Event>; /// The currency type @@ -432,8 +433,6 @@ pub trait Config: System { type MaxNominatorsPerValidator: Get; /// Maximum validators per nominator type MaxValidatorsPerNominator: Get; - /// Balance issued as rewards per round (constant issuance) - type IssuancePerRound: Get>; /// Maximum fee for any validator type MaxFee: Get; /// Minimum stake for any registered on-chain account to become a validator @@ -530,6 +529,11 @@ decl_storage! { AtStake: double_map hasher(blake2_128_concat) RoundIndex, hasher(blake2_128_concat) T::AccountId => Exposure>; + /// Total staked by validators selected per round + Staked: map + hasher(blake2_128_concat) RoundIndex => BalanceOf; + /// Staking expectations + StakeExpectations: InflationSchedule>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -564,6 +568,7 @@ decl_storage! { let (v_count, total_staked) = >::best_candidates_become_validators(1u32); // start Round 1 at Block 0 ::put(1u32); + >::insert(1u32, total_staked); >::deposit_event( RawEvent::NewRound(T::BlockNumber::zero(), 1u32, v_count, total_staked) ); @@ -850,6 +855,7 @@ decl_module! { let (validator_count, total_staked) = Self::best_candidates_become_validators(next); // start next round ::put(next); + >::insert(next, total_staked); Self::deposit_event(RawEvent::NewRound(n, next, validator_count, total_staked)); } } @@ -972,7 +978,8 @@ impl Module { if next > duration { let round_to_payout = next - duration; let total = ::get(round_to_payout); - let issuance = T::IssuancePerRound::get(); + let total_staked = >::get(round_to_payout); + let issuance = Self::compute_issuance(total_staked); for (val, pts) in >::drain_prefix(round_to_payout) { let pct_due = Perbill::from_rational_approximation(pts, total); let mut amt_due = pct_due * issuance; @@ -1080,9 +1087,8 @@ impl Module { /// Add reward points to block authors: /// * 20 points to the block producer for producing a block in the chain -impl author_inherent::EventHandler for Module -where - T: Config + author_inherent::Config, +impl author_inherent::EventHandler + for Module { fn note_author(author: T::AccountId) { let now = ::get(); @@ -1092,11 +1098,28 @@ where } } -impl author_inherent::CanAuthor for Module -where - T: Config + author_inherent::Config, -{ +impl author_inherent::CanAuthor for Module { fn can_author(account: &T::AccountId) -> bool { Self::is_validator(account) } } + +pub trait ComputeInflation { + /// Set expectations of staked amount eg min, max, ideal count + fn set_expectations(expect: InflationSchedule); + /// Compute target issuance based on amount staked + fn compute_issuance(staked: Balance) -> Balance; +} + +impl ComputeInflation> for Module { + // TODO: add runtime method, only accessible by sudo + fn set_expectations(expect: InflationSchedule>) { + >::put(expect); + } + fn compute_issuance(staked: BalanceOf) -> BalanceOf { + let expectations = >::get(); + todo!() + } +} +// TODO: snapshot total stake per round of all validators +// set stake expectations diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 5204887ec0..dd0f9aeca8 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -17,6 +17,7 @@ precompiles = { path = "precompiles/", default-features = false } account = { path = "account/", default-features = false } pallet-ethereum-chain-id = { path = "../pallets/ethereum-chain-id", default-features = false } stake = { path = "../pallets/stake", default-features = false } +inflation = { path = "../pallets/inflation", default-features = false } # Substrate dependencies pallet-aura = { git = "https://github.com/paritytech/substrate.git", default-features = false, branch = "master", optional = true } @@ -108,6 +109,7 @@ std = [ "cumulus-primitives/std", "account/std", "stake/std", + "inflation/std", ] # Will be enabled by the `wasm-builder` when building the runtime for WASM. From d990b00a19fa2f7a5be98bc8516048f41c2c0b70 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Wed, 3 Feb 2021 22:45:14 -0800 Subject: [PATCH 02/23] convert annual inflation rate to round issuance --- pallets/inflation/src/lib.rs | 42 +++++++++++++++++++------- pallets/stake/src/issuance.rs | 40 +++++++++++++++++++++++++ pallets/stake/src/lib.rs | 56 +++++++++++++++++++---------------- pallets/stake/src/mock.rs | 1 - 4 files changed, 103 insertions(+), 36 deletions(-) create mode 100644 pallets/stake/src/issuance.rs diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs index 213da4b190..c7c0d041fb 100644 --- a/pallets/inflation/src/lib.rs +++ b/pallets/inflation/src/lib.rs @@ -18,24 +18,44 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_support::{decl_module, decl_storage}; -use frame_system::{Config as System}; +use frame_support::{decl_error, decl_module, decl_storage, ensure}; +use frame_system::Config as System; use parity_scale_codec::{Decode, Encode}; use sp_runtime::{DispatchResult, Perbill, RuntimeDebug}; #[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct InflationSchedule { - max: T, - min: T, - ideal: T, +pub struct InflationSchedule { + pub max: T, + pub min: T, + pub ideal: T, } -pub trait Config: System {} +impl InflationSchedule { + pub fn valid(&self) -> bool { + self.max > self.ideal && self.ideal > self.min + } +} + +pub trait UpdateInflation { + fn update_inflation(schedule: InflationSchedule); +} + +pub trait Config: System { + /// For updating dependent storage items + type Handler: UpdateInflation; +} + +decl_error! { + pub enum Error for Module { + /// Inflation schedule not formed correctly (max <= ideal || min >= ideal) + InvalidSchedule, + } +} decl_storage! { trait Store for Module as Inflation { - /// Annual inflation targets - Schedule: InflationSchedule; + /// Annual inflation targets + pub Schedule get(fn schedule): InflationSchedule; } } @@ -45,7 +65,9 @@ decl_module! { #[weight = 0] fn set_schedule(origin, schedule: InflationSchedule) -> DispatchResult { //ensure_root(origin)?; - ::put(schedule); + ensure!(schedule.valid(), Error::::InvalidSchedule); + ::put(schedule.clone()); + T::Handler::update_inflation(schedule); Ok(()) } } diff --git a/pallets/stake/src/issuance.rs b/pallets/stake/src/issuance.rs new file mode 100644 index 0000000000..12f6ba7051 --- /dev/null +++ b/pallets/stake/src/issuance.rs @@ -0,0 +1,40 @@ +//! Helper methods for computing issuance based on inflation +use crate::{BalanceOf, Config, Inflation}; +use frame_support::traits::{Currency, Get}; +use inflation::InflationSchedule; +use sp_runtime::Perbill; + +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() -> u32 { + BLOCKS_PER_YEAR / T::BlocksPerRound::get() +} + +/// Convert annual inflation schedule to round issuance settings +/// - called whenever the annual inflation schedule is changed to update round issuance +pub fn per_round( + schedule: InflationSchedule, +) -> InflationSchedule> { + let rounds_per_year = rounds_per_year::(); + let total_issuance = T::Currency::total_issuance(); + let ideal_annual_issuance = schedule.ideal * total_issuance; + let max_annual_issuance = schedule.max * total_issuance; + let min_annual_issuance = schedule.min * total_issuance; + let (max, min, ideal): (BalanceOf, BalanceOf, BalanceOf) = ( + max_annual_issuance / rounds_per_year.into(), + min_annual_issuance / rounds_per_year.into(), + ideal_annual_issuance / rounds_per_year.into(), + ); + InflationSchedule { max, min, ideal } +} + +#[cfg(test)] +mod tests { + // TODO: write a mock function with similar logic and test the conversion + #[test] + fn round_issuance_conversion() { + assert!(true); + } +} diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 0343f964b3..4f99dbaa94 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -45,14 +45,19 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] +mod issuance; +#[cfg(test)] +pub(crate) mod mock; mod set; +#[cfg(test)] +mod tests; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, storage::IterableStorageDoubleMap, traits::{Currency, Get, Imbalance, ReservableCurrency}, }; use frame_system::{ensure_signed, Config as System}; -use inflation::{Config as Inflation, InflationSchedule}; +use inflation::{Config as Inflation, InflationSchedule, UpdateInflation}; use pallet_staking::{Exposure, IndividualExposure}; use parity_scale_codec::{Decode, Encode, HasCompact}; use set::OrderedSet; @@ -61,10 +66,6 @@ use sp_runtime::{ DispatchResult, Perbill, RuntimeDebug, }; use sp_std::{cmp::Ordering, prelude::*}; -#[cfg(test)] -pub(crate) mod mock; -#[cfg(test)] -mod tests; #[derive(Default, Clone, Encode, Decode, RuntimeDebug)] pub struct Bond { @@ -423,8 +424,8 @@ pub trait Config: System + Inflation { type Event: From> + Into<::Event>; /// The currency type type Currency: Currency + ReservableCurrency; - /// Blocks per round - type BlocksPerRound: Get; + /// Number of blocks per round + type BlocksPerRound: Get; /// Number of rounds that validators remain bonded before exit request is executed type BondDuration: Get; /// Maximum validators per round @@ -534,6 +535,8 @@ decl_storage! { hasher(blake2_128_concat) RoundIndex => BalanceOf; /// Staking expectations StakeExpectations: InflationSchedule>; + /// Issuance per round + RoundIssuance: InflationSchedule>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -845,7 +848,7 @@ decl_module! { Ok(()) } fn on_finalize(n: T::BlockNumber) { - if (n % T::BlocksPerRound::get()).is_zero() { + if (n % T::BlocksPerRound::get().into()).is_zero() { let next = ::get() + 1; // pay all stakers for T::BondDuration rounds ago Self::pay_stakers(next); @@ -882,6 +885,22 @@ impl Module { }); >::put(candidates); } + // calculate total issuance based on total staked by validators selected for the round + fn compute_issuance(staked: BalanceOf) -> BalanceOf { + let expectation = >::get(); + let issuance = >::get(); + if staked < expectation.min { + return issuance.min; + } else if staked > expectation.max { + return issuance.max; + } else { + // TODO: split up into 3 branches + // 1. min < staked < ideal + // 2. ideal < staked < max + // 3. staked == ideal + return issuance.ideal; + } + } fn nominator_joins_validator( nominator: T::AccountId, amount: BalanceOf, @@ -1104,22 +1123,9 @@ impl author_inherent::CanAuthor { - /// Set expectations of staked amount eg min, max, ideal count - fn set_expectations(expect: InflationSchedule); - /// Compute target issuance based on amount staked - fn compute_issuance(staked: Balance) -> Balance; -} - -impl ComputeInflation> for Module { - // TODO: add runtime method, only accessible by sudo - fn set_expectations(expect: InflationSchedule>) { - >::put(expect); - } - fn compute_issuance(staked: BalanceOf) -> BalanceOf { - let expectations = >::get(); - todo!() +impl UpdateInflation for Module { + fn update_inflation(inflation: InflationSchedule) { + let issuance = issuance::per_round::(inflation); + >::put(issuance); } } -// TODO: snapshot total stake per round of all validators -// set stake expectations diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 3b3b0ee2f6..56f4fdb2b0 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -100,7 +100,6 @@ parameter_types! { pub const MaxValidators: u32 = 5; pub const MaxNominatorsPerValidator: usize = 4; pub const MaxValidatorsPerNominator: usize = 4; - pub const IssuancePerRound: u128 = 10; pub const MaxFee: Perbill = Perbill::from_percent(50); pub const MinValidatorStk: u128 = 10; pub const MinNominatorStk: u128 = 5; From 5cfa880fecc89944c1c0bfec4900c488e5b47b31 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Thu, 4 Feb 2021 22:11:15 -0800 Subject: [PATCH 03/23] mv all inflation logic inside stake pallet --- Cargo.lock | 16 ---- pallets/inflation/Cargo.toml | 29 -------- pallets/inflation/src/lib.rs | 74 ------------------- pallets/stake/Cargo.toml | 2 - .../stake/src/{issuance.rs => inflation.rs} | 31 +++++--- pallets/stake/src/lib.rs | 58 ++++++++++++--- runtime/Cargo.toml | 2 - 7 files changed, 69 insertions(+), 143 deletions(-) delete mode 100644 pallets/inflation/Cargo.toml delete mode 100644 pallets/inflation/src/lib.rs rename pallets/stake/src/{issuance.rs => inflation.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index 44111efa78..0319138656 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2761,20 +2761,6 @@ dependencies = [ "serde", ] -[[package]] -name = "inflation" -version = "0.1.1" -dependencies = [ - "frame-support", - "frame-system", - "parity-scale-codec", - "serde", - "sp-core", - "sp-io", - "sp-runtime", - "sp-std", -] - [[package]] name = "instant" version = "0.1.9" @@ -4128,7 +4114,6 @@ dependencies = [ "frame-system", "frame-system-rpc-runtime-api", "hex-literal", - "inflation", "log", "moonbeam-rpc-primitives-txpool", "pallet-aura", @@ -9466,7 +9451,6 @@ dependencies = [ "author-inherent", "frame-support", "frame-system", - "inflation", "pallet-balances", "pallet-staking", "parity-scale-codec", diff --git a/pallets/inflation/Cargo.toml b/pallets/inflation/Cargo.toml deleted file mode 100644 index 509c6771b9..0000000000 --- a/pallets/inflation/Cargo.toml +++ /dev/null @@ -1,29 +0,0 @@ -[package] -name = "inflation" -version = "0.1.1" -authors = ["PureStake"] -edition = "2018" -description = "pallet for setting inflation schedule" - -[dependencies] -frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -parity-scale-codec = { version = "1.0.0", default-features = false, features = ["derive"] } -serde = { version = "1.0.101", optional = true } -sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } - -[dev-dependencies] -sp-io = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } -sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } - -[features] -default = ["std"] -std = [ - "frame-support/std", - "frame-system/std", - "parity-scale-codec/std", - "serde/std", - "sp-std/std", - "sp-runtime/std", -] diff --git a/pallets/inflation/src/lib.rs b/pallets/inflation/src/lib.rs deleted file mode 100644 index c7c0d041fb..0000000000 --- a/pallets/inflation/src/lib.rs +++ /dev/null @@ -1,74 +0,0 @@ -// 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 . - -//! Pallet that sets inflation schedule, used by stake - -#![cfg_attr(not(feature = "std"), no_std)] - -use frame_support::{decl_error, decl_module, decl_storage, ensure}; -use frame_system::Config as System; -use parity_scale_codec::{Decode, Encode}; -use sp_runtime::{DispatchResult, Perbill, RuntimeDebug}; - -#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct InflationSchedule { - pub max: T, - pub min: T, - pub ideal: T, -} - -impl InflationSchedule { - pub fn valid(&self) -> bool { - self.max > self.ideal && self.ideal > self.min - } -} - -pub trait UpdateInflation { - fn update_inflation(schedule: InflationSchedule); -} - -pub trait Config: System { - /// For updating dependent storage items - type Handler: UpdateInflation; -} - -decl_error! { - pub enum Error for Module { - /// Inflation schedule not formed correctly (max <= ideal || min >= ideal) - InvalidSchedule, - } -} - -decl_storage! { - trait Store for Module as Inflation { - /// Annual inflation targets - pub Schedule get(fn schedule): InflationSchedule; - } -} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - - #[weight = 0] - fn set_schedule(origin, schedule: InflationSchedule) -> DispatchResult { - //ensure_root(origin)?; - ensure!(schedule.valid(), Error::::InvalidSchedule); - ::put(schedule.clone()); - T::Handler::update_inflation(schedule); - Ok(()) - } - } -} diff --git a/pallets/stake/Cargo.toml b/pallets/stake/Cargo.toml index d9ffdee211..4dc61ab212 100644 --- a/pallets/stake/Cargo.toml +++ b/pallets/stake/Cargo.toml @@ -7,7 +7,6 @@ description = "staking pallet for validator selection and rewards" [dependencies] author-inherent = { path = "../author-inherent", default-features = false } -inflation = { path = "../inflation", default-features = false } frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } @@ -25,7 +24,6 @@ sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default = ["std"] std = [ "author-inherent/std", - "inflation/std", "frame-support/std", "frame-system/std", "pallet-balances/std", diff --git a/pallets/stake/src/issuance.rs b/pallets/stake/src/inflation.rs similarity index 66% rename from pallets/stake/src/issuance.rs rename to pallets/stake/src/inflation.rs index 12f6ba7051..47dc78d64f 100644 --- a/pallets/stake/src/issuance.rs +++ b/pallets/stake/src/inflation.rs @@ -1,20 +1,33 @@ //! Helper methods for computing issuance based on inflation -use crate::{BalanceOf, Config, Inflation}; +use crate::{BalanceOf, Config}; use frame_support::traits::{Currency, Get}; -use inflation::InflationSchedule; -use sp_runtime::Perbill; +use parity_scale_codec::{Decode, Encode}; +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; +#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] +pub struct InflationSchedule { + pub min: T, + pub ideal: T, + pub max: T, +} + +impl InflationSchedule { + pub fn valid(&self) -> bool { + self.max >= self.ideal && self.ideal >= self.min + } +} + fn rounds_per_year() -> u32 { BLOCKS_PER_YEAR / T::BlocksPerRound::get() } /// Convert annual inflation schedule to round issuance settings /// - called whenever the annual inflation schedule is changed to update round issuance -pub fn per_round( +pub fn per_round( schedule: InflationSchedule, ) -> InflationSchedule> { let rounds_per_year = rounds_per_year::(); @@ -32,9 +45,9 @@ pub fn per_round( #[cfg(test)] mod tests { - // TODO: write a mock function with similar logic and test the conversion - #[test] - fn round_issuance_conversion() { - assert!(true); - } + // TODO: write a mock function with similar logic and test the conversion + #[test] + fn round_issuance_conversion() { + assert!(true); + } } diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 4f99dbaa94..e8a2f28d5c 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -45,7 +45,8 @@ #![recursion_limit = "256"] #![cfg_attr(not(feature = "std"), no_std)] -mod issuance; +mod inflation; +use inflation::InflationSchedule; #[cfg(test)] pub(crate) mod mock; mod set; @@ -54,10 +55,9 @@ mod tests; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, storage::IterableStorageDoubleMap, - traits::{Currency, Get, Imbalance, ReservableCurrency}, + traits::{Currency, EnsureOrigin, Get, Imbalance, ReservableCurrency}, }; use frame_system::{ensure_signed, Config as System}; -use inflation::{Config as Inflation, InflationSchedule, UpdateInflation}; use pallet_staking::{Exposure, IndividualExposure}; use parity_scale_codec::{Decode, Encode, HasCompact}; use set::OrderedSet; @@ -419,11 +419,13 @@ type RewardPoint = u32; type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; type Candidate = Validator<::AccountId, BalanceOf>; -pub trait Config: System + Inflation { +pub trait Config: System { /// The overarching event type type Event: From> + Into<::Event>; /// The currency type type Currency: Currency + ReservableCurrency; + /// The origin for setting inflation + type MonetaryPolicy: EnsureOrigin; /// Number of blocks per round type BlocksPerRound: Get; /// Number of rounds that validators remain bonded before exit request is executed @@ -481,7 +483,12 @@ decl_event!( ValidatorNominated(AccountId, Balance, AccountId, Balance), /// Nominator, Validator, Amount Unstaked, New Total Amt Staked for Validator NominatorLeftValidator(AccountId, AccountId, Balance, Balance), + /// Paid the account (nominator or validator) the balance as liquid rewards Rewarded(AccountId, Balance), + /// Inflation schedule set with the provided ideal issuance + InflationScheduleSet(Balance, Balance, Balance), + /// Staking expectations set + StakeExpectationsSet(Balance, Balance, Balance), } ); @@ -506,6 +513,7 @@ decl_error! { NominationDNE, Underflow, CannotSwitchToSameNomination, + InvalidSchedule, } } @@ -584,6 +592,41 @@ decl_module! { type Error = Error; fn deposit_event() = default; + #[weight = 0] + fn set_staking_expectations( + origin, + expectations: InflationSchedule>, + ) -> DispatchResult { + T::MonetaryPolicy::ensure_origin(origin)?; + ensure!(expectations.valid(), Error::::InvalidSchedule); + Self::deposit_event( + RawEvent::InflationScheduleSet( + expectations.min, + expectations.ideal, + expectations.max + ) + ); + >::put(expectations); + Ok(()) + } + #[weight = 0] + fn set_inflation( + origin, + schedule: InflationSchedule + ) -> DispatchResult { + T::MonetaryPolicy::ensure_origin(origin)?; + ensure!(schedule.valid(), Error::::InvalidSchedule); + let round_issuance = inflation::per_round::(schedule); + Self::deposit_event( + RawEvent::InflationScheduleSet( + round_issuance.min, + round_issuance.ideal, + round_issuance.max, + ) + ); + >::put(round_issuance); + Ok(()) + } #[weight = 0] fn join_candidates( origin, @@ -1122,10 +1165,3 @@ impl author_inherent::CanAuthor UpdateInflation for Module { - fn update_inflation(inflation: InflationSchedule) { - let issuance = issuance::per_round::(inflation); - >::put(issuance); - } -} diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index dd0f9aeca8..5204887ec0 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -17,7 +17,6 @@ precompiles = { path = "precompiles/", default-features = false } account = { path = "account/", default-features = false } pallet-ethereum-chain-id = { path = "../pallets/ethereum-chain-id", default-features = false } stake = { path = "../pallets/stake", default-features = false } -inflation = { path = "../pallets/inflation", default-features = false } # Substrate dependencies pallet-aura = { git = "https://github.com/paritytech/substrate.git", default-features = false, branch = "master", optional = true } @@ -109,7 +108,6 @@ std = [ "cumulus-primitives/std", "account/std", "stake/std", - "inflation/std", ] # Will be enabled by the `wasm-builder` when building the runtime for WASM. From b4d09363ab235ddd0958d98ab3c7816d2893415d Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Fri, 5 Feb 2021 01:34:58 -0800 Subject: [PATCH 04/23] pass unit tests but needs more --- pallets/stake/src/inflation.rs | 49 ++++++++++++++++++++++----- pallets/stake/src/lib.rs | 9 ++--- pallets/stake/src/mock.rs | 62 +++++++++++++++++++++++++++++++--- 3 files changed, 104 insertions(+), 16 deletions(-) diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index 47dc78d64f..7e11e46f05 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -25,8 +25,7 @@ fn rounds_per_year() -> u32 { BLOCKS_PER_YEAR / T::BlocksPerRound::get() } -/// Convert annual inflation schedule to round issuance settings -/// - called whenever the annual inflation schedule is changed to update round issuance +/// Converts annual inflation schedule to round issuance settings pub fn per_round( schedule: InflationSchedule, ) -> InflationSchedule> { @@ -35,19 +34,53 @@ pub fn per_round( let ideal_annual_issuance = schedule.ideal * total_issuance; let max_annual_issuance = schedule.max * total_issuance; let min_annual_issuance = schedule.min * total_issuance; - let (max, min, ideal): (BalanceOf, BalanceOf, BalanceOf) = ( - max_annual_issuance / rounds_per_year.into(), + let (min, ideal, max): (BalanceOf, BalanceOf, BalanceOf) = ( min_annual_issuance / rounds_per_year.into(), ideal_annual_issuance / rounds_per_year.into(), + max_annual_issuance / rounds_per_year.into(), ); - InflationSchedule { max, min, ideal } + InflationSchedule { min, ideal, max } } #[cfg(test)] mod tests { - // TODO: write a mock function with similar logic and test the conversion + use super::*; + fn mock_periodic_issuance( + // Annual inflation schedule + schedule: InflationSchedule, + // Total current issuance at time of minting + total_issuance: u128, + // Total number of periods + periods: u128, + ) -> InflationSchedule { + let ideal_issuance = schedule.ideal * total_issuance; + let max_issuance = schedule.max * total_issuance; + let min_issuance = schedule.min * total_issuance; + let (min, ideal, max): (u128, u128, u128) = ( + min_issuance / periods, + ideal_issuance / periods, + max_issuance / periods, + ); + InflationSchedule { min, ideal, max } + } #[test] - fn round_issuance_conversion() { - assert!(true); + fn periodic_issuance_conversion() { + // 5% inflation for 10_000_0000 = 500,000 minted over the year + // let's assume there are 10 periods in a year for the sake of simplicity + // => mint 500_000 over 10 periods => 50_000 minted per period + let expected_round_schedule: InflationSchedule = InflationSchedule { + min: 50_000, + ideal: 50_000, + max: 50_000, + }; + let schedule = InflationSchedule { + min: Perbill::from_percent(5), + ideal: Perbill::from_percent(5), + max: Perbill::from_percent(5), + }; + assert_eq!( + expected_round_schedule, + mock_periodic_issuance(schedule, 10_000_000, 10) + ); } } diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index e8a2f28d5c..39692007ed 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -542,9 +542,9 @@ decl_storage! { Staked: map hasher(blake2_128_concat) RoundIndex => BalanceOf; /// Staking expectations - StakeExpectations: InflationSchedule>; + StakeExpectations get(fn stake_expectations) config(): InflationSchedule>; /// Issuance per round - RoundIssuance: InflationSchedule>; + RoundIssuance get(fn round_issuance) config(): InflationSchedule>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -560,7 +560,7 @@ decl_storage! { for &(ref actor, ref opt_val, balance) in &config.stakers { assert!( T::Currency::free_balance(&actor) >= balance, - "Stash does not have enough balance to bond." + "Account does not have enough balance to bond." ); let _ = if let Some(nominated_val) = opt_val { >::join_nominators( @@ -681,6 +681,7 @@ decl_module! { ensure!(state.is_active(),Error::::AlreadyOffline); state.go_offline(); let mut candidates = >::get(); + // TODO: investigate possible bug in this next line if candidates.remove(&Bond::from_owner(validator.clone())) { >::put(candidates); } @@ -928,7 +929,7 @@ impl Module { }); >::put(candidates); } - // calculate total issuance based on total staked by validators selected for the round + // calculate total issuance based on total staked for the given round fn compute_issuance(staked: BalanceOf) -> BalanceOf { let expectation = >::get(); let issuance = >::get(); diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 56f4fdb2b0..53f9785463 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -108,12 +108,12 @@ parameter_types! { impl Config for Test { type Event = MetaEvent; type Currency = Balances; + type MonetaryPolicy = frame_system::EnsureRoot; type BlocksPerRound = BlocksPerRound; type BondDuration = BondDuration; type MaxValidators = MaxValidators; type MaxNominatorsPerValidator = MaxNominatorsPerValidator; type MaxValidatorsPerNominator = MaxValidatorsPerNominator; - type IssuancePerRound = IssuancePerRound; type MaxFee = MaxFee; type MinValidatorStk = MinValidatorStk; type MinNominatorStk = MinNominatorStk; @@ -126,15 +126,21 @@ pub type Sys = frame_system::Module; fn genesis( balances: Vec<(AccountId, Balance)>, stakers: Vec<(AccountId, Option, Balance)>, + stake_expectations: InflationSchedule, + round_issuance: InflationSchedule, ) -> sp_io::TestExternalities { let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); let genesis = pallet_balances::GenesisConfig:: { balances }; genesis.assimilate_storage(&mut storage).unwrap(); - GenesisConfig:: { stakers } - .assimilate_storage(&mut storage) - .unwrap(); + GenesisConfig:: { + stakers, + stake_expectations, + round_issuance, + } + .assimilate_storage(&mut storage) + .unwrap(); let mut ext = sp_io::TestExternalities::from(storage); ext.execute_with(|| Sys::set_block_number(1)); ext @@ -163,6 +169,18 @@ pub(crate) fn two_validators_four_nominators() -> sp_io::TestExternalities { (5, Some(2), 100), (6, Some(2), 100), ], + // staking expectations + InflationSchedule { + min: 700, + ideal: 700, + max: 700, + }, + // round issuance + InflationSchedule { + min: 10, + ideal: 10, + max: 10, + }, ) } @@ -188,6 +206,18 @@ pub(crate) fn five_validators_no_nominators() -> sp_io::TestExternalities { (5, None, 60), (6, None, 50), ], + // staking expectations + InflationSchedule { + min: 700, + ideal: 700, + max: 700, + }, + // round issuance + InflationSchedule { + min: 10, + ideal: 10, + max: 10, + }, ) } @@ -219,6 +249,18 @@ pub(crate) fn five_validators_five_nominators() -> sp_io::TestExternalities { (9, Some(2), 10), (10, Some(1), 10), ], + // staking expectations + InflationSchedule { + min: 700, + ideal: 700, + max: 700, + }, + // round issuance + InflationSchedule { + min: 10, + ideal: 10, + max: 10, + }, ) } @@ -232,6 +274,18 @@ pub(crate) fn one_validator_two_nominators() -> sp_io::TestExternalities { (2, Some(1), 10), (3, Some(1), 10), ], + // staking expectations + InflationSchedule { + min: 700, + ideal: 700, + max: 700, + }, + // round issuance + InflationSchedule { + min: 10, + ideal: 10, + max: 10, + }, ) } From e9836f7a37160cc8e395c956527d31833dbd9c41 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Fri, 5 Feb 2021 03:23:04 -0800 Subject: [PATCH 05/23] genesis --- Cargo.lock | 1 + node/Cargo.toml | 1 + node/src/chain_spec.rs | 9 +++- pallets/stake/Cargo.toml | 2 +- pallets/stake/src/inflation.rs | 93 +++++++++++++++++++++++++++++++--- pallets/stake/src/lib.rs | 6 +-- pallets/stake/src/mock.rs | 60 ++++------------------ runtime/src/lib.rs | 4 +- 8 files changed, 110 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0319138656..d4f8c3108b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4045,6 +4045,7 @@ dependencies = [ "sp-timestamp", "sp-transaction-pool", "sp-trie", + "stake", "structopt", "substrate-build-script-utils", "substrate-frame-rpc-system", diff --git a/node/Cargo.toml b/node/Cargo.toml index 797287f597..7941e9b53d 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -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" } diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 9b2be89bee..f252b4ab3f 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -22,6 +22,7 @@ use moonbeam_runtime::{ use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; +use stake::InflationSchedule; use std::collections::BTreeMap; use std::str::FromStr; @@ -60,6 +61,8 @@ pub fn development_chain_spec() -> ChainSpec { None, 100_000 * GLMR, )], + (100_000 * GLMR).into(), + (10 * GLMR).into(), vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()], Default::default(), // para_id 1281, //ChainId @@ -92,6 +95,8 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { None, 100_000 * GLMR, )], + (100_000 * GLMR).into(), + (10 * GLMR).into(), vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()], para_id, 1280, //ChainId @@ -111,6 +116,8 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { fn testnet_genesis( root_key: AccountId, stakers: Vec<(AccountId, Option, Balance)>, + stake_expectations: InflationSchedule, + round_issuance: InflationSchedule, endowed_accounts: Vec, para_id: ParaId, chain_id: u64, @@ -138,6 +145,6 @@ fn testnet_genesis( accounts: BTreeMap::new(), }), pallet_ethereum: Some(EthereumConfig {}), - stake: Some(StakeConfig { stakers }), + stake: Some(StakeConfig { stakers, stake_expectations, round_issuance }), } } diff --git a/pallets/stake/Cargo.toml b/pallets/stake/Cargo.toml index 4dc61ab212..fe0cee17fb 100644 --- a/pallets/stake/Cargo.toml +++ b/pallets/stake/Cargo.toml @@ -29,7 +29,7 @@ std = [ "pallet-balances/std", "pallet-staking/std", "parity-scale-codec/std", - "serde/std", + "serde", "sp-std/std", "sp-runtime/std", ] diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index 7e11e46f05..2d2a4dcf94 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -2,12 +2,15 @@ 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; +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] pub struct InflationSchedule { pub min: T, @@ -16,9 +19,22 @@ pub struct InflationSchedule { } impl InflationSchedule { - pub fn valid(&self) -> bool { + pub fn is_valid(&self) -> bool { self.max >= self.ideal && self.ideal >= self.min } + pub fn is_one(&self) -> bool { + self.max == self.ideal && self.ideal == self.min + } +} + +impl From for InflationSchedule { + fn from(other: T) -> InflationSchedule { + InflationSchedule { + min: other, + ideal: other, + max: other, + } + } } fn rounds_per_year() -> u32 { @@ -48,14 +64,14 @@ mod tests { fn mock_periodic_issuance( // Annual inflation schedule schedule: InflationSchedule, - // Total current issuance at time of minting - total_issuance: u128, + // Total circulating before minting + pre_issuance_circulating: u128, // Total number of periods periods: u128, ) -> InflationSchedule { - let ideal_issuance = schedule.ideal * total_issuance; - let max_issuance = schedule.max * total_issuance; - let min_issuance = schedule.min * total_issuance; + let ideal_issuance = schedule.ideal * pre_issuance_circulating; + let max_issuance = schedule.max * pre_issuance_circulating; + let min_issuance = schedule.min * pre_issuance_circulating; let (min, ideal, max): (u128, u128, u128) = ( min_issuance / periods, ideal_issuance / periods, @@ -64,9 +80,9 @@ mod tests { InflationSchedule { min, ideal, max } } #[test] - fn periodic_issuance_conversion() { + 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 for the sake of simplicity + // let's assume there are 10 periods in a year // => mint 500_000 over 10 periods => 50_000 minted per period let expected_round_schedule: InflationSchedule = InflationSchedule { min: 50_000, @@ -83,4 +99,65 @@ mod tests { mock_periodic_issuance(schedule, 10_000_000, 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_schedule: InflationSchedule = InflationSchedule { + min: 30_000, + ideal: 40_000, + max: 50_000, + }; + let schedule = InflationSchedule { + min: Perbill::from_percent(3), + ideal: Perbill::from_percent(4), + max: Perbill::from_percent(5), + }; + assert_eq!( + expected_round_schedule, + mock_periodic_issuance(schedule, 10_000_000, 10) + ); + } + #[test] + fn current_parameterization() { + let expected_round_schedule: InflationSchedule = InflationSchedule { + min: 1, + ideal: 1, + max: 2, + }; + let schedule = InflationSchedule { + min: Perbill::from_percent(4), + ideal: Perbill::from_percent(5), + max: Perbill::from_percent(6), + }; + assert_eq!( + expected_round_schedule, + mock_periodic_issuance(schedule, 10_000_000, 262980) + ); + } + // this shows us that we need to increase the `BlocksPerRound` in order for the `stake` pallet to work + // what is the minimum `BlocksPerRound`? + #[test] + fn proposed_parameterization() { + // 4-6% annual inflation + // 10_000_000 total circulating pre issuance + // RoundsPerYear = BLOCKS_PER_YEAR / BLOCKS_PER_ROUND = (31557600 / 6) / X = 10000 + // solve for X = 525.96 ~= 526 BLOCKS_PER_ROUND => ROUND is 52.596 hours = 2.17 days + // 400_000-600_000 minted + let expected_round_schedule: InflationSchedule = InflationSchedule { + min: 40, + ideal: 50, + max: 60, + }; + let schedule = InflationSchedule { + min: Perbill::from_percent(4), + ideal: Perbill::from_percent(5), + max: Perbill::from_percent(6), + }; + assert_eq!( + expected_round_schedule, + mock_periodic_issuance(schedule, 10_000_000, 10000) + ); + } } diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 39692007ed..d2e6bbdef6 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -46,7 +46,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod inflation; -use inflation::InflationSchedule; +pub use inflation::InflationSchedule; #[cfg(test)] pub(crate) mod mock; mod set; @@ -598,7 +598,7 @@ decl_module! { expectations: InflationSchedule>, ) -> DispatchResult { T::MonetaryPolicy::ensure_origin(origin)?; - ensure!(expectations.valid(), Error::::InvalidSchedule); + ensure!(expectations.is_valid(), Error::::InvalidSchedule); Self::deposit_event( RawEvent::InflationScheduleSet( expectations.min, @@ -615,7 +615,7 @@ decl_module! { schedule: InflationSchedule ) -> DispatchResult { T::MonetaryPolicy::ensure_origin(origin)?; - ensure!(schedule.valid(), Error::::InvalidSchedule); + ensure!(schedule.is_valid(), Error::::InvalidSchedule); let round_issuance = inflation::per_round::(schedule); Self::deposit_event( RawEvent::InflationScheduleSet( diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 53f9785463..63357bf037 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -126,9 +126,17 @@ pub type Sys = frame_system::Module; fn genesis( balances: Vec<(AccountId, Balance)>, stakers: Vec<(AccountId, Option, Balance)>, - stake_expectations: InflationSchedule, - round_issuance: InflationSchedule, ) -> sp_io::TestExternalities { + let stake_expectations: InflationSchedule = InflationSchedule { + min: 700, + ideal: 700, + max: 700, + }; + let round_issuance: InflationSchedule = InflationSchedule { + min: 10, + ideal: 10, + max: 10, + }; let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -169,18 +177,6 @@ pub(crate) fn two_validators_four_nominators() -> sp_io::TestExternalities { (5, Some(2), 100), (6, Some(2), 100), ], - // staking expectations - InflationSchedule { - min: 700, - ideal: 700, - max: 700, - }, - // round issuance - InflationSchedule { - min: 10, - ideal: 10, - max: 10, - }, ) } @@ -206,18 +202,6 @@ pub(crate) fn five_validators_no_nominators() -> sp_io::TestExternalities { (5, None, 60), (6, None, 50), ], - // staking expectations - InflationSchedule { - min: 700, - ideal: 700, - max: 700, - }, - // round issuance - InflationSchedule { - min: 10, - ideal: 10, - max: 10, - }, ) } @@ -249,18 +233,6 @@ pub(crate) fn five_validators_five_nominators() -> sp_io::TestExternalities { (9, Some(2), 10), (10, Some(1), 10), ], - // staking expectations - InflationSchedule { - min: 700, - ideal: 700, - max: 700, - }, - // round issuance - InflationSchedule { - min: 10, - ideal: 10, - max: 10, - }, ) } @@ -274,18 +246,6 @@ pub(crate) fn one_validator_two_nominators() -> sp_io::TestExternalities { (2, Some(1), 10), (3, Some(1), 10), ], - // staking expectations - InflationSchedule { - min: 700, - ideal: 700, - max: 700, - }, - // round issuance - InflationSchedule { - min: 10, - ideal: 10, - max: 10, - }, ) } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 63241138cc..81b3a5f808 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -312,8 +312,6 @@ parameter_types! { pub const MaxNominatorsPerValidator: usize = 10; /// Maximum 8 validators per nominator (same as MaxValidators) pub const MaxValidatorsPerNominator: usize = 8; - /// Issue 49 new tokens as rewards to validators every 2 minutes (round) - pub const IssuancePerRound: u128 = 49 * GLMR; /// The maximum percent a validator can take off the top of its rewards is 50% pub const MaxFee: Perbill = Perbill::from_percent(50); /// Minimum stake required to be reserved to be a validator is 5 @@ -324,12 +322,12 @@ parameter_types! { impl stake::Config for Runtime { type Event = Event; type Currency = Balances; + type MonetaryPolicy = frame_system::EnsureRoot; type BlocksPerRound = BlocksPerRound; type BondDuration = BondDuration; type MaxValidators = MaxValidators; type MaxNominatorsPerValidator = MaxNominatorsPerValidator; type MaxValidatorsPerNominator = MaxValidatorsPerNominator; - type IssuancePerRound = IssuancePerRound; type MaxFee = MaxFee; type MinValidatorStk = MinValidatorStk; type MinNomination = MinNominatorStk; From 532645d726eb2cff3f62ff66fd789271ece355d3 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Fri, 5 Feb 2021 06:38:49 -0800 Subject: [PATCH 06/23] rename InflationSchedule to Range type --- node/src/chain_spec.rs | 6 +++--- pallets/stake/src/inflation.rs | 38 ++++++++++++++++------------------ pallets/stake/src/lib.rs | 18 +++++++++------- pallets/stake/src/mock.rs | 4 ++-- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index f252b4ab3f..38ec64f675 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -22,7 +22,7 @@ use moonbeam_runtime::{ use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; -use stake::InflationSchedule; +use stake::Range; use std::collections::BTreeMap; use std::str::FromStr; @@ -116,8 +116,8 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { fn testnet_genesis( root_key: AccountId, stakers: Vec<(AccountId, Option, Balance)>, - stake_expectations: InflationSchedule, - round_issuance: InflationSchedule, + stake_expectations: Range, + round_issuance: Range, endowed_accounts: Vec, para_id: ParaId, chain_id: u64, diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index 2d2a4dcf94..d74cf38634 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -12,13 +12,13 @@ const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct InflationSchedule { +pub struct Range { pub min: T, pub ideal: T, pub max: T, } -impl InflationSchedule { +impl Range { pub fn is_valid(&self) -> bool { self.max >= self.ideal && self.ideal >= self.min } @@ -27,9 +27,9 @@ impl InflationSchedule { } } -impl From for InflationSchedule { - fn from(other: T) -> InflationSchedule { - InflationSchedule { +impl From for Range { + fn from(other: T) -> Range { + Range { min: other, ideal: other, max: other, @@ -42,9 +42,7 @@ fn rounds_per_year() -> u32 { } /// Converts annual inflation schedule to round issuance settings -pub fn per_round( - schedule: InflationSchedule, -) -> InflationSchedule> { +pub fn per_round(schedule: Range) -> Range> { let rounds_per_year = rounds_per_year::(); let total_issuance = T::Currency::total_issuance(); let ideal_annual_issuance = schedule.ideal * total_issuance; @@ -55,7 +53,7 @@ pub fn per_round( ideal_annual_issuance / rounds_per_year.into(), max_annual_issuance / rounds_per_year.into(), ); - InflationSchedule { min, ideal, max } + Range { min, ideal, max } } #[cfg(test)] @@ -63,12 +61,12 @@ mod tests { use super::*; fn mock_periodic_issuance( // Annual inflation schedule - schedule: InflationSchedule, + schedule: Range, // Total circulating before minting pre_issuance_circulating: u128, // Total number of periods periods: u128, - ) -> InflationSchedule { + ) -> Range { let ideal_issuance = schedule.ideal * pre_issuance_circulating; let max_issuance = schedule.max * pre_issuance_circulating; let min_issuance = schedule.min * pre_issuance_circulating; @@ -77,19 +75,19 @@ mod tests { ideal_issuance / periods, max_issuance / periods, ); - InflationSchedule { min, ideal, max } + Range { min, ideal, max } } #[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_schedule: InflationSchedule = InflationSchedule { + let expected_round_schedule: Range = Range { min: 50_000, ideal: 50_000, max: 50_000, }; - let schedule = InflationSchedule { + let schedule = Range { min: Perbill::from_percent(5), ideal: Perbill::from_percent(5), max: Perbill::from_percent(5), @@ -104,12 +102,12 @@ mod tests { // 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_schedule: InflationSchedule = InflationSchedule { + let expected_round_schedule: Range = Range { min: 30_000, ideal: 40_000, max: 50_000, }; - let schedule = InflationSchedule { + let schedule = Range { min: Perbill::from_percent(3), ideal: Perbill::from_percent(4), max: Perbill::from_percent(5), @@ -121,12 +119,12 @@ mod tests { } #[test] fn current_parameterization() { - let expected_round_schedule: InflationSchedule = InflationSchedule { + let expected_round_schedule: Range = Range { min: 1, ideal: 1, max: 2, }; - let schedule = InflationSchedule { + let schedule = Range { min: Perbill::from_percent(4), ideal: Perbill::from_percent(5), max: Perbill::from_percent(6), @@ -145,12 +143,12 @@ mod tests { // RoundsPerYear = BLOCKS_PER_YEAR / BLOCKS_PER_ROUND = (31557600 / 6) / X = 10000 // solve for X = 525.96 ~= 526 BLOCKS_PER_ROUND => ROUND is 52.596 hours = 2.17 days // 400_000-600_000 minted - let expected_round_schedule: InflationSchedule = InflationSchedule { + let expected_round_schedule: Range = Range { min: 40, ideal: 50, max: 60, }; - let schedule = InflationSchedule { + let schedule = Range { min: Perbill::from_percent(4), ideal: Perbill::from_percent(5), max: Perbill::from_percent(6), diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index d2e6bbdef6..f22bfd042b 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -46,7 +46,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod inflation; -pub use inflation::InflationSchedule; +pub use inflation::Range; #[cfg(test)] pub(crate) mod mock; mod set; @@ -538,13 +538,13 @@ decl_storage! { AtStake: double_map hasher(blake2_128_concat) RoundIndex, hasher(blake2_128_concat) T::AccountId => Exposure>; - /// Total staked by validators selected per round + /// Snapshot of total staked in this round Staked: map hasher(blake2_128_concat) RoundIndex => BalanceOf; /// Staking expectations - StakeExpectations get(fn stake_expectations) config(): InflationSchedule>; + StakeExpectations get(fn stake_expectations) config(): Range>; /// Issuance per round - RoundIssuance get(fn round_issuance) config(): InflationSchedule>; + RoundIssuance get(fn round_issuance) config(): Range>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -579,7 +579,8 @@ decl_storage! { let (v_count, total_staked) = >::best_candidates_become_validators(1u32); // start Round 1 at Block 0 ::put(1u32); - >::insert(1u32, total_staked); + // snapshot total stake + >::insert(1u32, >::get()); >::deposit_event( RawEvent::NewRound(T::BlockNumber::zero(), 1u32, v_count, total_staked) ); @@ -595,7 +596,7 @@ decl_module! { #[weight = 0] fn set_staking_expectations( origin, - expectations: InflationSchedule>, + expectations: Range>, ) -> DispatchResult { T::MonetaryPolicy::ensure_origin(origin)?; ensure!(expectations.is_valid(), Error::::InvalidSchedule); @@ -612,7 +613,7 @@ decl_module! { #[weight = 0] fn set_inflation( origin, - schedule: InflationSchedule + schedule: Range ) -> DispatchResult { T::MonetaryPolicy::ensure_origin(origin)?; ensure!(schedule.is_valid(), Error::::InvalidSchedule); @@ -902,7 +903,8 @@ decl_module! { let (validator_count, total_staked) = Self::best_candidates_become_validators(next); // start next round ::put(next); - >::insert(next, total_staked); + // snapshot total stake + >::insert(next, >::get()); Self::deposit_event(RawEvent::NewRound(n, next, validator_count, total_staked)); } } diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 63357bf037..06ce49fc59 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -127,12 +127,12 @@ fn genesis( balances: Vec<(AccountId, Balance)>, stakers: Vec<(AccountId, Option, Balance)>, ) -> sp_io::TestExternalities { - let stake_expectations: InflationSchedule = InflationSchedule { + let stake_expectations: Range = Range { min: 700, ideal: 700, max: 700, }; - let round_issuance: InflationSchedule = InflationSchedule { + let round_issuance: Range = Range { min: 10, ideal: 10, max: 10, From b6380e3b320fa66d76a99602931b9c96561afa88 Mon Sep 17 00:00:00 2001 From: Amar Singh Date: Fri, 5 Feb 2021 06:56:33 -0800 Subject: [PATCH 07/23] Update pallets/stake/src/lib.rs Co-authored-by: Joshy Orndorff --- pallets/stake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index f22bfd042b..0eec1dd7e7 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -425,7 +425,7 @@ pub trait Config: System { /// The currency type type Currency: Currency + ReservableCurrency; /// The origin for setting inflation - type MonetaryPolicy: EnsureOrigin; + type SetMonetaryPolicyOrigin: EnsureOrigin; /// Number of blocks per round type BlocksPerRound: Get; /// Number of rounds that validators remain bonded before exit request is executed From 372e870fea0a4b69bb96b8eb1badb25755a43218 Mon Sep 17 00:00:00 2001 From: Amar Singh Date: Fri, 5 Feb 2021 06:56:48 -0800 Subject: [PATCH 08/23] Update pallets/stake/src/lib.rs Co-authored-by: Joshy Orndorff --- pallets/stake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 0eec1dd7e7..57ca4674c0 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -1152,7 +1152,7 @@ impl Module { /// Add reward points to block authors: /// * 20 points to the block producer for producing a block in the chain -impl author_inherent::EventHandler +impl author_inherent::EventHandler for Module { fn note_author(author: T::AccountId) { From bc2b2f8f75e425865b5e724b5bd4e15af976d9ae Mon Sep 17 00:00:00 2001 From: Amar Singh Date: Fri, 5 Feb 2021 06:56:53 -0800 Subject: [PATCH 09/23] Update pallets/stake/src/lib.rs Co-authored-by: Joshy Orndorff --- pallets/stake/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 57ca4674c0..5cc1fd938b 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -1163,7 +1163,7 @@ impl author_inherent::EventHandler } } -impl author_inherent::CanAuthor for Module { +impl author_inherent::CanAuthor for Module { fn can_author(account: &T::AccountId) -> bool { Self::is_validator(account) } From ce55b8060dc604ac544c843fad748a93cdb8a46e Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Fri, 5 Feb 2021 06:59:43 -0800 Subject: [PATCH 10/23] rename MonetaryPolicy to SetMonetaryPolicyOrigin --- pallets/stake/src/lib.rs | 8 +++----- pallets/stake/src/mock.rs | 2 +- runtime/src/lib.rs | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 5cc1fd938b..105a1cf5a4 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -598,7 +598,7 @@ decl_module! { origin, expectations: Range>, ) -> DispatchResult { - T::MonetaryPolicy::ensure_origin(origin)?; + T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; ensure!(expectations.is_valid(), Error::::InvalidSchedule); Self::deposit_event( RawEvent::InflationScheduleSet( @@ -615,7 +615,7 @@ decl_module! { origin, schedule: Range ) -> DispatchResult { - T::MonetaryPolicy::ensure_origin(origin)?; + T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; ensure!(schedule.is_valid(), Error::::InvalidSchedule); let round_issuance = inflation::per_round::(schedule); Self::deposit_event( @@ -1152,9 +1152,7 @@ impl Module { /// Add reward points to block authors: /// * 20 points to the block producer for producing a block in the chain -impl author_inherent::EventHandler - for Module -{ +impl author_inherent::EventHandler for Module { fn note_author(author: T::AccountId) { let now = ::get(); let score_plus_20 = >::get(now, &author) + 20; diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 06ce49fc59..3c2e279f8c 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -108,7 +108,7 @@ parameter_types! { impl Config for Test { type Event = MetaEvent; type Currency = Balances; - type MonetaryPolicy = frame_system::EnsureRoot; + type SetMonetaryPolicyOrigin = frame_system::EnsureRoot; type BlocksPerRound = BlocksPerRound; type BondDuration = BondDuration; type MaxValidators = MaxValidators; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 81b3a5f808..9fa2d6f554 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -322,7 +322,7 @@ parameter_types! { impl stake::Config for Runtime { type Event = Event; type Currency = Balances; - type MonetaryPolicy = frame_system::EnsureRoot; + type SetMonetaryPolicyOrigin = frame_system::EnsureRoot; type BlocksPerRound = BlocksPerRound; type BondDuration = BondDuration; type MaxValidators = MaxValidators; From 10afdaed0f177fbfe2bf95c9a22a38f90dd6df7b Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Sat, 6 Feb 2021 15:32:08 -0800 Subject: [PATCH 11/23] make InflationSchedule to prevent unintended compounding inflation --- node/src/chain_spec.rs | 5 +- pallets/stake/src/inflation.rs | 97 ++++++++++++++++++++++++++-------- pallets/stake/src/lib.rs | 79 ++++++++++++++++++--------- pallets/stake/src/mock.rs | 13 +++-- 4 files changed, 140 insertions(+), 54 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 38ec64f675..68b3e132fd 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -116,8 +116,7 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { fn testnet_genesis( root_key: AccountId, stakers: Vec<(AccountId, Option, Balance)>, - stake_expectations: Range, - round_issuance: Range, + inflation_config: InflationSchedule, endowed_accounts: Vec, para_id: ParaId, chain_id: u64, @@ -145,6 +144,6 @@ fn testnet_genesis( accounts: BTreeMap::new(), }), pallet_ethereum: Some(EthereumConfig {}), - stake: Some(StakeConfig { stakers, stake_expectations, round_issuance }), + stake: Some(StakeConfig { stakers, inflation_config }), } } diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index d74cf38634..016d5f052a 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -1,10 +1,11 @@ //! Helper methods for computing issuance based on inflation -use crate::{BalanceOf, Config}; -use frame_support::traits::{Currency, Get}; +use crate::Config; +use frame_support::traits::Get; use parity_scale_codec::{Decode, Encode}; #[cfg(feature = "std")] use serde::{Deserialize, Serialize}; -use sp_runtime::{Perbill, RuntimeDebug}; +use sp_runtime::{traits::AtLeast32BitUnsigned, Perbill, RuntimeDebug}; +use sp_std::ops::{Div, Mul}; const SECONDS_PER_YEAR: u32 = 31557600; const SECONDS_PER_BLOCK: u32 = 6; @@ -12,7 +13,78 @@ const BLOCKS_PER_YEAR: u32 = SECONDS_PER_YEAR / SECONDS_PER_BLOCK; #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] #[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct Range { +pub struct InflationSchedule { + /// Pre-issuance circulating supply + pub base: Balance, + /// Annual inflation rate + pub annual: Range, + /// Staking expectations + pub expect: Range, + /// Round issuance + pub round: Range, +} + +impl + Div + AtLeast32BitUnsigned> + InflationSchedule +{ + pub fn new( + base: Balance, + annual: Range, + expect: Range, + ) -> InflationSchedule { + let rounds_per_year = rounds_per_year::(); + let ideal_annual_issuance = annual.ideal * base; + let max_annual_issuance = annual.max * base; + let min_annual_issuance = annual.min * base; + let round = Range { + min: min_annual_issuance / rounds_per_year.into(), + ideal: ideal_annual_issuance / rounds_per_year.into(), + max: max_annual_issuance / rounds_per_year.into(), + }; + InflationSchedule { + base, + annual, + expect, + round, + } + } + /// Set annual inflation rate without changing the base amount + pub fn set_rate(&mut self, new: Range) { + let rounds_per_year = rounds_per_year::(); + let ideal_annual_issuance = new.ideal * self.base; + let max_annual_issuance = new.max * self.base; + let min_annual_issuance = new.min * self.base; + let round = Range { + min: min_annual_issuance / rounds_per_year.into(), + ideal: ideal_annual_issuance / rounds_per_year.into(), + max: max_annual_issuance / rounds_per_year.into(), + }; + self.round = round; + self.annual = new; + } + /// Set staking expectations + pub fn set_expectations(&mut self, expect: Range) { + self.expect = expect; + } + /// Set base using T::Currency::total_issuance upon a new year; updates round issuance as well + pub fn set_base(&mut self, circulating: Balance) { + let rounds_per_year = rounds_per_year::(); + let ideal_annual_issuance = self.annual.ideal * circulating; + let max_annual_issuance = self.annual.max * circulating; + let min_annual_issuance = self.annual.min * circulating; + let round = Range { + min: min_annual_issuance / rounds_per_year.into(), + ideal: ideal_annual_issuance / rounds_per_year.into(), + max: max_annual_issuance / rounds_per_year.into(), + }; + self.base = circulating; + self.round = round; + } +} + +#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] +#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] +pub struct Range { pub min: T, pub ideal: T, pub max: T, @@ -41,21 +113,6 @@ fn rounds_per_year() -> u32 { BLOCKS_PER_YEAR / T::BlocksPerRound::get() } -/// Converts annual inflation schedule to round issuance settings -pub fn per_round(schedule: Range) -> Range> { - let rounds_per_year = rounds_per_year::(); - let total_issuance = T::Currency::total_issuance(); - let ideal_annual_issuance = schedule.ideal * total_issuance; - let max_annual_issuance = schedule.max * total_issuance; - let min_annual_issuance = schedule.min * total_issuance; - let (min, ideal, max): (BalanceOf, BalanceOf, BalanceOf) = ( - min_annual_issuance / rounds_per_year.into(), - ideal_annual_issuance / rounds_per_year.into(), - max_annual_issuance / rounds_per_year.into(), - ); - Range { min, ideal, max } -} - #[cfg(test)] mod tests { use super::*; @@ -134,8 +191,6 @@ mod tests { mock_periodic_issuance(schedule, 10_000_000, 262980) ); } - // this shows us that we need to increase the `BlocksPerRound` in order for the `stake` pallet to work - // what is the minimum `BlocksPerRound`? #[test] fn proposed_parameterization() { // 4-6% annual inflation diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 105a1cf5a4..30c8ab9643 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -46,7 +46,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod inflation; -pub use inflation::Range; +pub use inflation::{InflationSchedule, Range}; #[cfg(test)] pub(crate) mod mock; mod set; @@ -538,13 +538,11 @@ decl_storage! { AtStake: double_map hasher(blake2_128_concat) RoundIndex, hasher(blake2_128_concat) T::AccountId => Exposure>; - /// Snapshot of total staked in this round + /// Snapshot of total staked in this round; used to determine round issuance Staked: map hasher(blake2_128_concat) RoundIndex => BalanceOf; - /// Staking expectations - StakeExpectations get(fn stake_expectations) config(): Range>; - /// Issuance per round - RoundIssuance get(fn round_issuance) config(): Range>; + /// Inflation parameterization, which contains round issuance and stake expectations + InflationConfig get(fn inflation_config) config(): InflationSchedule>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -576,10 +574,11 @@ decl_storage! { ) }; } + // Choose top `MaxValidator`s from validator candidates let (v_count, total_staked) = >::best_candidates_become_validators(1u32); - // start Round 1 at Block 0 + // Start Round 1 at Block 0 ::put(1u32); - // snapshot total stake + // Snapshot total stake >::insert(1u32, >::get()); >::deposit_event( RawEvent::NewRound(T::BlockNumber::zero(), 1u32, v_count, total_staked) @@ -593,6 +592,8 @@ decl_module! { type Error = Error; fn deposit_event() = default; + /// Set the expectations for total staked. These expectations determine the issuance for + /// the round according to logic in `fn compute_issuance` #[weight = 0] fn set_staking_expectations( origin, @@ -600,16 +601,20 @@ decl_module! { ) -> DispatchResult { T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; ensure!(expectations.is_valid(), Error::::InvalidSchedule); + let mut config = >::get(); + config.set_expectations(expectations); Self::deposit_event( - RawEvent::InflationScheduleSet( - expectations.min, - expectations.ideal, - expectations.max + RawEvent::StakeExpectationsSet( + config.expect.min, + config.expect.ideal, + config.expect.max ) ); - >::put(expectations); + >::put(config); Ok(()) } + /// (Re)set the annual inflation and update round issuance accordingly + /// NOTE: does not update config.base because that would lead to _compounding_ inflation #[weight = 0] fn set_inflation( origin, @@ -617,17 +622,37 @@ decl_module! { ) -> DispatchResult { T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; ensure!(schedule.is_valid(), Error::::InvalidSchedule); - let round_issuance = inflation::per_round::(schedule); + let mut config = >::get(); + config.set_rate::(schedule); + Self::deposit_event( + RawEvent::InflationScheduleSet( + config.round.min, + config.round.ideal, + config.round.max, + ) + ); + >::put(config); + Ok(()) + } + /// Must be called upon a new year to update the round issuance based on new circulating + /// WARNING: if called more than once per year, will lead to _compounding_ inflation + #[weight = 0] + fn update_inflation_base(origin) -> DispatchResult { + T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; + let mut config = >::get(); + config.set_base::(T::Currency::total_issuance()); Self::deposit_event( RawEvent::InflationScheduleSet( - round_issuance.min, - round_issuance.ideal, - round_issuance.max, + config.round.min, + config.round.ideal, + config.round.max, ) ); - >::put(round_issuance); + >::put(config); Ok(()) } + /// Join the set of validator candidates by bonding at least `MinValidatorStk` and + /// setting commission fee below the `MaxFee` #[weight = 0] fn join_candidates( origin, @@ -653,6 +678,9 @@ decl_module! { Self::deposit_event(RawEvent::JoinedValidatorCandidates(acc, bond, new_total)); Ok(()) } + /// Request to leave the set of candidates. If successful, the account is immediately + /// removed from the candidate pool to prevent selection as a validator, but unbonding is + /// executed with a delay of `BondDuration` rounds. #[weight = 0] fn leave_candidates(origin) -> DispatchResult { let validator = ensure_signed(origin)?; @@ -931,20 +959,19 @@ impl Module { }); >::put(candidates); } - // calculate total issuance based on total staked for the given round + // Calculate total issuance based on total staked for the given round fn compute_issuance(staked: BalanceOf) -> BalanceOf { - let expectation = >::get(); - let issuance = >::get(); - if staked < expectation.min { - return issuance.min; - } else if staked > expectation.max { - return issuance.max; + let config = >::get(); + if staked < config.expect.min { + return config.round.min; + } else if staked > config.expect.max { + return config.round.max; } else { // TODO: split up into 3 branches // 1. min < staked < ideal // 2. ideal < staked < max // 3. staked == ideal - return issuance.ideal; + return config.round.ideal; } } fn nominator_joins_validator( diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 3c2e279f8c..944f6ef150 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -127,16 +127,22 @@ fn genesis( balances: Vec<(AccountId, Balance)>, stakers: Vec<(AccountId, Option, Balance)>, ) -> sp_io::TestExternalities { - let stake_expectations: Range = Range { + let expect: Range = Range { min: 700, ideal: 700, max: 700, }; - let round_issuance: Range = Range { + let round: Range = Range { min: 10, ideal: 10, max: 10, }; + let inflation_config: InflationSchedule = InflationSchedule { + base: 0, // not used in tests + annual: Perbill::zero().into(), // not used in tests + expect, + round, + }; let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); @@ -144,8 +150,7 @@ fn genesis( genesis.assimilate_storage(&mut storage).unwrap(); GenesisConfig:: { stakers, - stake_expectations, - round_issuance, + inflation_config, } .assimilate_storage(&mut storage) .unwrap(); From 7a2eb80fa33562e185ac07f0c418df2f6d2f5fd6 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Sun, 7 Feb 2021 16:24:54 -0800 Subject: [PATCH 12/23] set round duration to one hour --- node/src/chain_spec.rs | 27 +++++++++++++++++++++++---- runtime/src/lib.rs | 4 ++-- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 68b3e132fd..d0f292deb9 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -61,8 +61,7 @@ pub fn development_chain_spec() -> ChainSpec { None, 100_000 * GLMR, )], - (100_000 * GLMR).into(), - (10 * GLMR).into(), + moonbeam_inflation_config(), vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()], Default::default(), // para_id 1281, //ChainId @@ -95,8 +94,7 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { None, 100_000 * GLMR, )], - (100_000 * GLMR).into(), - (10 * GLMR).into(), + moonbeam_inflation_config(), vec![AccountId::from_str("6Be02d1d3665660d22FF9624b7BE0551ee1Ac91b").unwrap()], para_id, 1280, //ChainId @@ -113,6 +111,27 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { ) } +/// TODO: make a separate test network inflation config? +pub fn moonbeam_inflation_config() -> InflationSchedule { + // InflationSchedule { + // base: 10_000_000 * GLMR, + // annual: Range { + // min: Perbill::from_percent(4), + // ideal: Perbill::from_percent(5), + // max: Perbill::from_percent(5), + // }, + // expect: Range { + // min: 100_000 * GLMR, + // ideal: 500_000 * GLMR, + // max: 1_000_000 * GLMR, + // }, + // round: Range { + // min: + // }, + // } + todo!() +} + fn testnet_genesis( root_key: AccountId, stakers: Vec<(AccountId, Option, Balance)>, diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 9fa2d6f554..9b73896248 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -302,8 +302,8 @@ impl parachain_info::Config for Runtime {} pub const GLMR: Balance = 1_000_000_000_000_000_000; parameter_types! { - /// Moonbeam starts a new round every 2 minutes (20 * block_time) - pub const BlocksPerRound: u32 = 20; + /// Moonbeam starts a new round every hour (600 * block_time) + pub const BlocksPerRound: u32 = 600; /// Reward payments and validator exit requests are delayed by 4 minutes (2 * 20 * block_time) pub const BondDuration: u32 = 2; /// Maximum 8 valid block authors at any given time From bff3ee73ad45eb067f2dfbfaf00ce8711adb7e7c Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 00:23:02 -0800 Subject: [PATCH 13/23] simplify --- node/src/chain_spec.rs | 38 +++--- pallets/stake/src/inflation.rs | 207 +++++++++++++-------------------- pallets/stake/src/lib.rs | 52 ++++----- pallets/stake/src/mock.rs | 16 +-- pallets/stake/src/tests.rs | 30 ++--- 5 files changed, 142 insertions(+), 201 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index d0f292deb9..94bf1110a2 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -19,10 +19,11 @@ use moonbeam_runtime::{ AccountId, Balance, BalancesConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, GenesisConfig, ParachainInfoConfig, StakeConfig, SudoConfig, SystemConfig, GLMR, WASM_BINARY, }; +use sp_runtime::Perbill; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; -use stake::Range; +use stake::{Range, InflationInfo}; use std::collections::BTreeMap; use std::str::FromStr; @@ -111,31 +112,26 @@ pub fn get_chain_spec(para_id: ParaId) -> ChainSpec { ) } -/// TODO: make a separate test network inflation config? -pub fn moonbeam_inflation_config() -> InflationSchedule { - // InflationSchedule { - // base: 10_000_000 * GLMR, - // annual: Range { - // min: Perbill::from_percent(4), - // ideal: Perbill::from_percent(5), - // max: Perbill::from_percent(5), - // }, - // expect: Range { - // min: 100_000 * GLMR, - // ideal: 500_000 * GLMR, - // max: 1_000_000 * GLMR, - // }, - // round: Range { - // min: - // }, - // } - todo!() +pub fn moonbeam_inflation_config() -> InflationInfo { + InflationInfo { + expect: Range { + min: 100_000 * GLMR, + ideal: 500_000 * GLMR, + max: 1_000_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, Balance)>, - inflation_config: InflationSchedule, + inflation_config: InflationInfo, endowed_accounts: Vec, para_id: ParaId, chain_id: u64, diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index 016d5f052a..95df8118c4 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -1,85 +1,17 @@ //! Helper methods for computing issuance based on inflation -use crate::Config; -use frame_support::traits::Get; +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::{traits::AtLeast32BitUnsigned, Perbill, RuntimeDebug}; -use sp_std::ops::{Div, Mul}; +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; -#[cfg_attr(feature = "std", derive(Serialize, Deserialize))] -#[derive(Eq, PartialEq, Clone, Encode, Decode, Default, RuntimeDebug)] -pub struct InflationSchedule { - /// Pre-issuance circulating supply - pub base: Balance, - /// Annual inflation rate - pub annual: Range, - /// Staking expectations - pub expect: Range, - /// Round issuance - pub round: Range, -} - -impl + Div + AtLeast32BitUnsigned> - InflationSchedule -{ - pub fn new( - base: Balance, - annual: Range, - expect: Range, - ) -> InflationSchedule { - let rounds_per_year = rounds_per_year::(); - let ideal_annual_issuance = annual.ideal * base; - let max_annual_issuance = annual.max * base; - let min_annual_issuance = annual.min * base; - let round = Range { - min: min_annual_issuance / rounds_per_year.into(), - ideal: ideal_annual_issuance / rounds_per_year.into(), - max: max_annual_issuance / rounds_per_year.into(), - }; - InflationSchedule { - base, - annual, - expect, - round, - } - } - /// Set annual inflation rate without changing the base amount - pub fn set_rate(&mut self, new: Range) { - let rounds_per_year = rounds_per_year::(); - let ideal_annual_issuance = new.ideal * self.base; - let max_annual_issuance = new.max * self.base; - let min_annual_issuance = new.min * self.base; - let round = Range { - min: min_annual_issuance / rounds_per_year.into(), - ideal: ideal_annual_issuance / rounds_per_year.into(), - max: max_annual_issuance / rounds_per_year.into(), - }; - self.round = round; - self.annual = new; - } - /// Set staking expectations - pub fn set_expectations(&mut self, expect: Range) { - self.expect = expect; - } - /// Set base using T::Currency::total_issuance upon a new year; updates round issuance as well - pub fn set_base(&mut self, circulating: Balance) { - let rounds_per_year = rounds_per_year::(); - let ideal_annual_issuance = self.annual.ideal * circulating; - let max_annual_issuance = self.annual.max * circulating; - let min_annual_issuance = self.annual.min * circulating; - let round = Range { - min: min_annual_issuance / rounds_per_year.into(), - ideal: ideal_annual_issuance / rounds_per_year.into(), - max: max_annual_issuance / rounds_per_year.into(), - }; - self.base = circulating; - self.round = round; - } +fn rounds_per_year() -> u32 { + BLOCKS_PER_YEAR / T::BlocksPerRound::get() } #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] @@ -94,9 +26,6 @@ impl Range { pub fn is_valid(&self) -> bool { self.max >= self.ideal && self.ideal >= self.min } - pub fn is_one(&self) -> bool { - self.max == self.ideal && self.ideal == self.min - } } impl From for Range { @@ -109,37 +38,83 @@ impl From for Range { } } -fn rounds_per_year() -> u32 { - BLOCKS_PER_YEAR / T::BlocksPerRound::get() +/// Convert annual inflation rate range to round inflation range +pub fn annual_to_round(annual: Range) -> Range { + let periods = rounds_per_year::(); + 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(round: Range) -> Range> { + 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 { + /// Staking expectations + pub expect: Range, + /// Round inflation range + pub round: Range, +} + +impl InflationInfo { + pub fn new( + annual: Range, + expect: Range, + ) -> InflationInfo { + InflationInfo { + expect, + round: annual_to_round::(annual), + } + } + /// Set round inflation range according to input annual inflation range + pub fn set_annual_rate(&mut self, new: Range) { + self.round = annual_to_round::(new); + } + /// Set staking expectations + pub fn set_expectations(&mut self, expect: Range) { + self.expect = expect; + } } #[cfg(test)] mod tests { use super::*; - fn mock_periodic_issuance( - // Annual inflation schedule - schedule: Range, + fn mock_annual_to_round(annual: Range, rounds_per_year: u32) -> Range { + 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 - pre_issuance_circulating: u128, - // Total number of periods - periods: u128, + circulating: u128, + // Round inflation range + round: Range, ) -> Range { - let ideal_issuance = schedule.ideal * pre_issuance_circulating; - let max_issuance = schedule.max * pre_issuance_circulating; - let min_issuance = schedule.min * pre_issuance_circulating; - let (min, ideal, max): (u128, u128, u128) = ( - min_issuance / periods, - ideal_issuance / periods, - max_issuance / periods, - ); - Range { min, ideal, max } + 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_schedule: Range = Range { + let expected_round_issuance_range: Range = Range { min: 50_000, ideal: 50_000, max: 50_000, @@ -150,8 +125,8 @@ mod tests { max: Perbill::from_percent(5), }; assert_eq!( - expected_round_schedule, - mock_periodic_issuance(schedule, 10_000_000, 10) + expected_round_issuance_range, + mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10)) ); } #[test] @@ -159,7 +134,7 @@ mod tests { // 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_schedule: Range = Range { + let expected_round_issuance_range: Range = Range { min: 30_000, ideal: 40_000, max: 50_000, @@ -170,47 +145,25 @@ mod tests { max: Perbill::from_percent(5), }; assert_eq!( - expected_round_schedule, - mock_periodic_issuance(schedule, 10_000_000, 10) + expected_round_issuance_range, + mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 10)) ); } #[test] - fn current_parameterization() { + fn expected_parameterization() { let expected_round_schedule: Range = Range { - min: 1, - ideal: 1, - max: 2, + min: 46, + ideal: 57, + max: 57, }; let schedule = Range { min: Perbill::from_percent(4), ideal: Perbill::from_percent(5), - max: Perbill::from_percent(6), - }; - assert_eq!( - expected_round_schedule, - mock_periodic_issuance(schedule, 10_000_000, 262980) - ); - } - #[test] - fn proposed_parameterization() { - // 4-6% annual inflation - // 10_000_000 total circulating pre issuance - // RoundsPerYear = BLOCKS_PER_YEAR / BLOCKS_PER_ROUND = (31557600 / 6) / X = 10000 - // solve for X = 525.96 ~= 526 BLOCKS_PER_ROUND => ROUND is 52.596 hours = 2.17 days - // 400_000-600_000 minted - let expected_round_schedule: Range = Range { - min: 40, - ideal: 50, - max: 60, - }; - let schedule = Range { - min: Perbill::from_percent(4), - ideal: Perbill::from_percent(5), - max: Perbill::from_percent(6), + max: Perbill::from_percent(5), }; assert_eq!( expected_round_schedule, - mock_periodic_issuance(schedule, 10_000_000, 10000) + mock_round_issuance_range(10_000_000, mock_annual_to_round(schedule, 8766)) ); } } diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 30c8ab9643..9e35b5bb59 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -46,7 +46,7 @@ #![cfg_attr(not(feature = "std"), no_std)] mod inflation; -pub use inflation::{InflationSchedule, Range}; +pub use inflation::{InflationInfo, Range}; #[cfg(test)] pub(crate) mod mock; mod set; @@ -485,8 +485,8 @@ decl_event!( NominatorLeftValidator(AccountId, AccountId, Balance, Balance), /// Paid the account (nominator or validator) the balance as liquid rewards Rewarded(AccountId, Balance), - /// Inflation schedule set with the provided ideal issuance - InflationScheduleSet(Balance, Balance, Balance), + /// Round inflation range set with the provided annual inflation range + RoundInflationSet(Perbill, Perbill, Perbill), /// Staking expectations set StakeExpectationsSet(Balance, Balance, Balance), } @@ -542,7 +542,7 @@ decl_storage! { Staked: map hasher(blake2_128_concat) RoundIndex => BalanceOf; /// Inflation parameterization, which contains round issuance and stake expectations - InflationConfig get(fn inflation_config) config(): InflationSchedule>; + InflationConfig get(fn inflation_config) config(): InflationInfo>; /// Total points awarded in this round Points: map hasher(blake2_128_concat) RoundIndex => RewardPoint; @@ -623,9 +623,9 @@ decl_module! { T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; ensure!(schedule.is_valid(), Error::::InvalidSchedule); let mut config = >::get(); - config.set_rate::(schedule); + config.set_annual_rate::(schedule); Self::deposit_event( - RawEvent::InflationScheduleSet( + RawEvent::RoundInflationSet( config.round.min, config.round.ideal, config.round.max, @@ -634,24 +634,7 @@ decl_module! { >::put(config); Ok(()) } - /// Must be called upon a new year to update the round issuance based on new circulating - /// WARNING: if called more than once per year, will lead to _compounding_ inflation - #[weight = 0] - fn update_inflation_base(origin) -> DispatchResult { - T::SetMonetaryPolicyOrigin::ensure_origin(origin)?; - let mut config = >::get(); - config.set_base::(T::Currency::total_issuance()); - Self::deposit_event( - RawEvent::InflationScheduleSet( - config.round.min, - config.round.ideal, - config.round.max, - ) - ); - >::put(config); - Ok(()) - } - /// Join the set of validator candidates by bonding at least `MinValidatorStk` and + /// Join the set of validator candidates by bonding at least `MinValidatorStk` and /// setting commission fee below the `MaxFee` #[weight = 0] fn join_candidates( @@ -703,6 +686,7 @@ decl_module! { Self::deposit_event(RawEvent::ValidatorScheduledExit(now,validator,when)); Ok(()) } + /// Temporarily leave the set of validator candidates without unbonding #[weight = 0] fn go_offline(origin) -> DispatchResult { let validator = ensure_signed(origin)?; @@ -718,6 +702,7 @@ decl_module! { Self::deposit_event(RawEvent::ValidatorWentOffline(::get(),validator)); Ok(()) } + /// Rejoin the set of validator candidates if previously had called `go_offline` #[weight = 0] fn go_online(origin) -> DispatchResult { let validator = ensure_signed(origin)?; @@ -735,6 +720,7 @@ decl_module! { Self::deposit_event(RawEvent::ValidatorBackOnline(::get(),validator)); Ok(()) } + /// Bond more for validator candidates #[weight = 0] fn candidate_bond_more(origin, more: BalanceOf) -> DispatchResult { let validator = ensure_signed(origin)?; @@ -751,6 +737,7 @@ decl_module! { Self::deposit_event(RawEvent::ValidatorBondedMore(validator, before, after)); Ok(()) } + /// Bond less for validator candidates #[weight = 0] fn candidate_bond_less(origin, less: BalanceOf) -> DispatchResult { let validator = ensure_signed(origin)?; @@ -767,6 +754,7 @@ decl_module! { Self::deposit_event(RawEvent::ValidatorBondedLess(validator, before, after)); Ok(()) } + /// Join the set of nominators #[weight = 0] fn join_nominators( origin, @@ -782,6 +770,7 @@ decl_module! { Self::deposit_event(RawEvent::NominatorJoined(acc, amount)); Ok(()) } + /// Leave the set of nominators and, by implication, revoke all ongoing nominations #[weight = 0] fn leave_nominators(origin) -> DispatchResult { let acc = ensure_signed(origin)?; @@ -793,6 +782,7 @@ decl_module! { Self::deposit_event(RawEvent::NominatorLeft(acc, nominator.total)); Ok(()) } + /// Nominate a new validator candidate if already nominating #[weight = 0] fn nominate_new( origin, @@ -838,6 +828,8 @@ decl_module! { )); Ok(()) } + /// Swap an old nomination with a new nomination. If the new nomination exists, it + /// updates the existing nomination by adding the balance of the old nomination #[weight = 0] fn switch_nomination(origin, old: T::AccountId, new: T::AccountId) -> DispatchResult { let acc = ensure_signed(origin)?; @@ -865,10 +857,12 @@ decl_module! { Self::deposit_event(RawEvent::NominationSwapped(acc, swapped_amt, old, new)); Ok(()) } + /// Revoke an existing nomination #[weight = 0] fn revoke_nomination(origin, validator: T::AccountId) -> DispatchResult { Self::nominator_revokes_validator(ensure_signed(origin)?, validator) } + /// Bond more for nominators with respect to a specific validator candidate #[weight = 0] fn nominator_bond_more( origin, @@ -893,6 +887,7 @@ decl_module! { Self::deposit_event(RawEvent::NominationIncreased(nominator, candidate, before, after)); Ok(()) } + /// Bond less for nominators with respect to a specific nominator candidate #[weight = 0] fn nominator_bond_less( origin, @@ -959,19 +954,20 @@ impl Module { }); >::put(candidates); } - // Calculate total issuance based on total staked for the given round + // Calculate round issuance based on total staked for the given round fn compute_issuance(staked: BalanceOf) -> BalanceOf { let config = >::get(); + let round_issuance = inflation::round_issuance_range::(config.round); if staked < config.expect.min { - return config.round.min; + return round_issuance.min; } else if staked > config.expect.max { - return config.round.max; + return round_issuance.max; } else { // TODO: split up into 3 branches // 1. min < staked < ideal // 2. ideal < staked < max // 3. staked == ideal - return config.round.ideal; + return round_issuance.ideal; } } fn nominator_joins_validator( diff --git a/pallets/stake/src/mock.rs b/pallets/stake/src/mock.rs index 944f6ef150..776b00a9a2 100644 --- a/pallets/stake/src/mock.rs +++ b/pallets/stake/src/mock.rs @@ -132,17 +132,13 @@ fn genesis( ideal: 700, max: 700, }; - let round: Range = Range { - min: 10, - ideal: 10, - max: 10, - }; - let inflation_config: InflationSchedule = InflationSchedule { - base: 0, // not used in tests - annual: Perbill::zero().into(), // not used in tests - expect, - round, + // very unrealistic test parameterization, would be dumb to have per-round inflation this high + let round: Range = Range { + min: Perbill::from_percent(5), + ideal: Perbill::from_percent(5), + max: Perbill::from_percent(5), }; + let inflation_config: InflationInfo = InflationInfo { expect, round }; let mut storage = frame_system::GenesisConfig::default() .build_storage::() .unwrap(); diff --git a/pallets/stake/src/tests.rs b/pallets/stake/src/tests.rs index 5249b05cc9..173840d6fd 100644 --- a/pallets/stake/src/tests.rs +++ b/pallets/stake/src/tests.rs @@ -362,7 +362,7 @@ fn payout_distribution_to_solo_validators() { RawEvent::ValidatorChosen(3, 4, 70), RawEvent::ValidatorChosen(3, 5, 60), RawEvent::NewRound(10, 3, 5, 400), - RawEvent::Rewarded(1, 10), + RawEvent::Rewarded(1, 305), RawEvent::ValidatorChosen(4, 1, 100), RawEvent::ValidatorChosen(4, 2, 90), RawEvent::ValidatorChosen(4, 3, 80), @@ -385,8 +385,8 @@ fn payout_distribution_to_solo_validators() { RawEvent::ValidatorChosen(5, 4, 70), RawEvent::ValidatorChosen(5, 5, 60), RawEvent::NewRound(20, 5, 5, 400), - RawEvent::Rewarded(1, 6), - RawEvent::Rewarded(2, 4), + RawEvent::Rewarded(1, 192), + RawEvent::Rewarded(2, 128), RawEvent::ValidatorChosen(6, 1, 100), RawEvent::ValidatorChosen(6, 2, 90), RawEvent::ValidatorChosen(6, 3, 80), @@ -411,11 +411,11 @@ fn payout_distribution_to_solo_validators() { RawEvent::ValidatorChosen(7, 4, 70), RawEvent::ValidatorChosen(7, 5, 60), RawEvent::NewRound(30, 7, 5, 400), - RawEvent::Rewarded(5, 2), - RawEvent::Rewarded(3, 2), - RawEvent::Rewarded(1, 2), - RawEvent::Rewarded(4, 2), - RawEvent::Rewarded(2, 2), + RawEvent::Rewarded(5, 67), + RawEvent::Rewarded(3, 67), + RawEvent::Rewarded(1, 67), + RawEvent::Rewarded(4, 67), + RawEvent::Rewarded(2, 67), RawEvent::ValidatorChosen(8, 1, 100), RawEvent::ValidatorChosen(8, 2, 90), RawEvent::ValidatorChosen(8, 3, 80), @@ -465,10 +465,10 @@ fn payout_distribution_to_nominators() { RawEvent::ValidatorChosen(3, 3, 20), RawEvent::ValidatorChosen(3, 5, 10), RawEvent::NewRound(10, 3, 5, 140), - RawEvent::Rewarded(1, 4), - RawEvent::Rewarded(6, 2), - RawEvent::Rewarded(7, 2), - RawEvent::Rewarded(10, 2), + RawEvent::Rewarded(1, 20), + RawEvent::Rewarded(6, 10), + RawEvent::Rewarded(7, 10), + RawEvent::Rewarded(10, 10), RawEvent::ValidatorChosen(4, 1, 50), RawEvent::ValidatorChosen(4, 2, 40), RawEvent::ValidatorChosen(4, 4, 20), @@ -526,9 +526,9 @@ fn pays_validator_commission() { RawEvent::ValidatorChosen(4, 4, 40), RawEvent::ValidatorChosen(4, 1, 40), RawEvent::NewRound(15, 4, 2, 80), - RawEvent::Rewarded(4, 6), - RawEvent::Rewarded(5, 2), - RawEvent::Rewarded(6, 2), + RawEvent::Rewarded(4, 18), + RawEvent::Rewarded(5, 6), + RawEvent::Rewarded(6, 6), RawEvent::ValidatorChosen(5, 4, 40), RawEvent::ValidatorChosen(5, 1, 40), RawEvent::NewRound(20, 5, 2, 80), From 93ca5185177436e63f98fca02de3d5d7f4fb2a1c Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 02:21:44 -0800 Subject: [PATCH 14/23] logistics --- pallets/stake/Cargo.toml | 2 +- pallets/stake/src/inflation.rs | 16 ++++++++++++++++ runtime/src/lib.rs | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pallets/stake/Cargo.toml b/pallets/stake/Cargo.toml index fe0cee17fb..301f6d49c1 100644 --- a/pallets/stake/Cargo.toml +++ b/pallets/stake/Cargo.toml @@ -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" diff --git a/pallets/stake/src/inflation.rs b/pallets/stake/src/inflation.rs index 95df8118c4..e4d3c32e6c 100644 --- a/pallets/stake/src/inflation.rs +++ b/pallets/stake/src/inflation.rs @@ -1,3 +1,19 @@ +// 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 . + //! Helper methods for computing issuance based on inflation use crate::{BalanceOf, Config}; use frame_support::traits::{Currency, Get}; diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index c9af079542..ccef317c99 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -17,7 +17,7 @@ //! The Moonbeam Runtime. //! //! Primary features of this runtime include: -//! * Ethereum compatability +//! * Ethereum compatibility //! * Moonbeam tokenomics #![cfg_attr(not(feature = "std"), no_std)] @@ -109,13 +109,13 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("moonbase-alphanet"), impl_name: create_runtime_str!("moonbase-alphanet"), authoring_version: 3, - spec_version: 17, + spec_version: 18, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, }; -/// The version infromation used to identify this runtime when compiled natively. +/// The version information used to identify this runtime when compiled natively. #[cfg(feature = "std")] pub fn native_version() -> NativeVersion { NativeVersion { From 7b2948d22c441d9a91df77aab83e1e09c30797c9 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 02:23:05 -0800 Subject: [PATCH 15/23] fmt --- pallets/stake/src/lib.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index e6fa45ea8a..9147b22448 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -591,8 +591,8 @@ decl_module! { pub struct Module for enum Call where origin: T::Origin { type Error = Error; fn deposit_event() = default; - - /// A new round chooses a new validator set. Runtime config is 20 so every 2 minutes. + + /// A new round chooses a new validator set. Runtime config is 20 so every 2 minutes. const BlocksPerRound: T::BlockNumber = T::BlocksPerRound::get(); /// Number of rounds that validators remain bonded before exit request is executed const BondDuration: RoundIndex = T::BondDuration::get(); @@ -612,7 +612,7 @@ decl_module! { const MinNomination: BalanceOf = T::MinNomination::get(); /// Minimum stake for any registered on-chain account to become a nominator const MinNominatorStk: BalanceOf = T::MinNominatorStk::get(); - + /// Set the expectations for total staked. These expectations determine the issuance for /// the round according to logic in `fn compute_issuance` #[weight = 0] @@ -653,8 +653,8 @@ decl_module! { ); >::put(config); Ok(()) - } - /// Join the set of validator candidates by bonding at least `MinValidatorStk` and + } + /// Join the set of validator candidates by bonding at least `MinValidatorStk` and /// setting commission fee below the `MaxFee` #[weight = 0] fn join_candidates( From 5e526f878b68febdb5ad3cb25a91afabbb1598b3 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 02:34:53 -0800 Subject: [PATCH 16/23] clean --- Cargo.lock | 2 +- pallets/stake/src/lib.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d4f8c3108b..28c139979a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9447,7 +9447,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "stake" -version = "0.1.1" +version = "0.1.2" dependencies = [ "author-inherent", "frame-support", diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index 9147b22448..ff0981a15b 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -592,8 +592,8 @@ decl_module! { type Error = Error; fn deposit_event() = default; - /// A new round chooses a new validator set. Runtime config is 20 so every 2 minutes. - const BlocksPerRound: T::BlockNumber = T::BlocksPerRound::get(); + /// A new round chooses a new validator set. Runtime config is 20 so every 2 minutes. + const BlocksPerRound: u32 = T::BlocksPerRound::get(); /// Number of rounds that validators remain bonded before exit request is executed const BondDuration: RoundIndex = T::BondDuration::get(); /// Maximum validators per round. @@ -602,12 +602,10 @@ decl_module! { const MaxNominatorsPerValidator: u32 = T::MaxNominatorsPerValidator::get(); /// Maximum validators per nominator const MaxValidatorsPerNominator: u32 = T::MaxValidatorsPerNominator::get(); - /// Balance issued as rewards per round (constant issuance) - const IssuancePerRound: BalanceOf = T::IssuancePerRound::get(); /// Maximum fee for any validator const MaxFee: Perbill = T::MaxFee::get(); /// Minimum stake for any registered on-chain account to become a validator - const MinValidatorStk: BalanceOf = T::MinNominatorStk::get(); + const MinValidatorStk: BalanceOf = T::MinValidatorStk::get(); /// Minimum stake for any registered on-chain account to nominate const MinNomination: BalanceOf = T::MinNomination::get(); /// Minimum stake for any registered on-chain account to become a nominator From b9b613b96e3ea105fea43af638ef5aa002049915 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 02:36:27 -0800 Subject: [PATCH 17/23] fmt --- node/src/chain_spec.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 94bf1110a2..f10d697722 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -19,11 +19,11 @@ use moonbeam_runtime::{ AccountId, Balance, BalancesConfig, EVMConfig, EthereumChainIdConfig, EthereumConfig, GenesisConfig, ParachainInfoConfig, StakeConfig, SudoConfig, SystemConfig, GLMR, WASM_BINARY, }; -use sp_runtime::Perbill; use sc_chain_spec::{ChainSpecExtension, ChainSpecGroup}; use sc_service::ChainType; use serde::{Deserialize, Serialize}; -use stake::{Range, InflationInfo}; +use sp_runtime::Perbill; +use stake::{InflationInfo, Range}; use std::collections::BTreeMap; use std::str::FromStr; @@ -117,9 +117,9 @@ pub fn moonbeam_inflation_config() -> InflationInfo { expect: Range { min: 100_000 * GLMR, ideal: 500_000 * GLMR, - max: 1_000_000 * GLMR, + max: 1_000_000 * GLMR, }, - // 8766 rounds (hours) in a year + // 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), @@ -159,6 +159,9 @@ fn testnet_genesis( accounts: BTreeMap::new(), }), pallet_ethereum: Some(EthereumConfig {}), - stake: Some(StakeConfig { stakers, inflation_config }), + stake: Some(StakeConfig { + stakers, + inflation_config, + }), } } From 443e4d60d4935788d661dec15fa9e95581ea231c Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 02:43:32 -0800 Subject: [PATCH 18/23] fmt --- pallets/stake/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pallets/stake/src/lib.rs b/pallets/stake/src/lib.rs index ff0981a15b..86e39b674d 100644 --- a/pallets/stake/src/lib.rs +++ b/pallets/stake/src/lib.rs @@ -651,8 +651,8 @@ decl_module! { ); >::put(config); Ok(()) - } - /// Join the set of validator candidates by bonding at least `MinValidatorStk` and + } + /// Join the set of validator candidates by bonding at least `MinValidatorStk` and /// setting commission fee below the `MaxFee` #[weight = 0] fn join_candidates( From 71872370a2815979d721af44fd083be153de0592 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 08:43:22 -0800 Subject: [PATCH 19/23] make tests pass with issuance 0 --- node/src/chain_spec.rs | 4 ++-- tests/tests/test-stake.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index f10d697722..55d120c381 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -116,8 +116,8 @@ pub fn moonbeam_inflation_config() -> InflationInfo { InflationInfo { expect: Range { min: 100_000 * GLMR, - ideal: 500_000 * GLMR, - max: 1_000_000 * GLMR, + ideal: 200_000 * GLMR, + max: 500_000 * GLMR, }, // 8766 rounds (hours) in a year round: Range { diff --git a/tests/tests/test-stake.ts b/tests/tests/test-stake.ts index 88442c122d..8e800570f0 100644 --- a/tests/tests/test-stake.ts +++ b/tests/tests/test-stake.ts @@ -18,7 +18,7 @@ describeWithMoonbeam("Moonbeam RPC (Stake)", `simple-specs.json`, (context) => { }); step("issuance minted to the sole validator for authoring blocks", async function () { - const issuanceEveryRound = 49n * GLMR; + const issuanceEveryRound = 0n * GLMR; // payment transfer is delayed by two rounds const balanceAfterBlock40 = BigInt(GENESIS_ACCOUNT_BALANCE) + issuanceEveryRound; const balanceAfterBlock60 = balanceAfterBlock40 + issuanceEveryRound; From de80750ee715b159fb76adf56ee0668fe783316c Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 08:52:49 -0800 Subject: [PATCH 20/23] fmt --- node/src/chain_spec.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/node/src/chain_spec.rs b/node/src/chain_spec.rs index 2b0574c167..ba848f01e8 100644 --- a/node/src/chain_spec.rs +++ b/node/src/chain_spec.rs @@ -159,7 +159,7 @@ fn testnet_genesis( accounts: BTreeMap::new(), }), pallet_ethereum: Some(EthereumConfig {}), - pallet_democracy: Some(DemocracyConfig {}), + pallet_democracy: Some(DemocracyConfig {}), pallet_scheduler: Some(SchedulerConfig {}), stake: Some(StakeConfig { stakers, From 61249a8f780db604e84dc9cd3cf01611379ddb46 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 09:13:03 -0800 Subject: [PATCH 21/23] bump spec version --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index d79f5c2089..954a224e43 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -110,7 +110,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { spec_name: create_runtime_str!("moonbase-alphanet"), impl_name: create_runtime_str!("moonbase-alphanet"), authoring_version: 3, - spec_version: 18, + spec_version: 19, impl_version: 1, apis: RUNTIME_API_VERSIONS, transaction_version: 2, From 10a7fbd380f172fa04eb4334b2cb474f4d21c081 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 09:26:16 -0800 Subject: [PATCH 22/23] update runtime comment --- runtime/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 954a224e43..4c2ca07adc 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -366,7 +366,7 @@ pub const GLMR: Balance = 1_000_000_000_000_000_000; parameter_types! { /// Moonbeam starts a new round every hour (600 * block_time) pub const BlocksPerRound: u32 = 600; - /// Reward payments and validator exit requests are delayed by 4 minutes (2 * 20 * block_time) + /// Reward payments and validator exit requests are delayed by 2 hours (2 * 600 * block_time) pub const BondDuration: u32 = 2; /// Maximum 8 valid block authors at any given time pub const MaxValidators: u32 = 8; From dd66bc9c96d21c058637335db63bf6cc9e764208 Mon Sep 17 00:00:00 2001 From: 4meta5 Date: Mon, 8 Feb 2021 12:41:59 -0800 Subject: [PATCH 23/23] remove issuance integration test --- tests/tests/test-stake.ts | 22 ---------------------- 1 file changed, 22 deletions(-) diff --git a/tests/tests/test-stake.ts b/tests/tests/test-stake.ts index 8e800570f0..5348032295 100644 --- a/tests/tests/test-stake.ts +++ b/tests/tests/test-stake.ts @@ -17,28 +17,6 @@ describeWithMoonbeam("Moonbeam RPC (Stake)", `simple-specs.json`, (context) => { expect((validators[0] as Buffer).toString("hex").toLowerCase()).equal(GENESIS_ACCOUNT); }); - step("issuance minted to the sole validator for authoring blocks", async function () { - const issuanceEveryRound = 0n * GLMR; - // payment transfer is delayed by two rounds - const balanceAfterBlock40 = BigInt(GENESIS_ACCOUNT_BALANCE) + issuanceEveryRound; - const balanceAfterBlock60 = balanceAfterBlock40 + issuanceEveryRound; - - var block = await context.web3.eth.getBlockNumber(); - while (block < 40) { - await createAndFinalizeBlock(context.polkadotApi); - block = await context.web3.eth.getBlockNumber(); - } - expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal( - balanceAfterBlock40.toString() - ); - while (block < 60) { - await createAndFinalizeBlock(context.polkadotApi); - block = await context.web3.eth.getBlockNumber(); - } - expect(await context.web3.eth.getBalance(GENESIS_ACCOUNT)).to.equal( - balanceAfterBlock60.toString() - ); - }); it("candidates set in genesis", async function () { const candidates = await context.polkadotApi.query.stake.candidates(GENESIS_ACCOUNT); expect((candidates.toHuman() as any).id.toLowerCase()).equal(GENESIS_ACCOUNT);