Extended Balance Type for Staking's Election (#2134)

* First draft of extended balance type

* Test cleanup.

* Update staking docs.

* Add a good failing test case for quintill

* Bring back saturating.

* Some final fixes

* A few more.

* Update wasm; Bump spec;

* Re-bump.

* Custom lossy conversion from currency to vote

* remove print

* Fix reverse conversion issue.

* void. Re-trigger ci.
This commit is contained in:
Kian Peymani
2019-03-29 20:00:15 +04:30
committed by Gav Wood
parent 086d55397b
commit 958cc7efea
10 changed files with 474 additions and 389 deletions
+18 -52
View File
@@ -254,82 +254,48 @@ impl From<codec::Compact<Perbill>> for Perbill {
}
}
/// Perquintill is parts-per-quintillion. It stores a value between 0 and 1 in fixed point and
/// PerU128 is parts-per-u128-max-value. It stores a value between 0 and 1 in fixed point and
/// provides a means to multiply some other value by that.
#[cfg_attr(feature = "std", derive(Serialize, Deserialize, Debug))]
#[derive(Encode, Decode, Default, Copy, Clone, PartialEq, Eq)]
pub struct Perquintill(u64);
pub struct PerU128(u128);
const QUINTILLION: u64 = 1_000_000_000_000_000_000;
const U128: u128 = u128::max_value();
impl Perquintill {
impl PerU128 {
/// Nothing.
pub fn zero() -> Self { Self(0) }
/// Everything.
pub fn one() -> Self { Self(QUINTILLION) }
pub fn one() -> Self { Self(U128) }
/// Construct new instance where `x` is in quintillionths. Value equivalent to `x / 1,000,000,000,000,000,000`.
pub fn from_quintillionths(x: u64) -> Self { Self(x.min(QUINTILLION)) }
/// Construct new instance where `x` is in billionths. Value equivalent to `x / 1,000,000,000`.
pub fn from_billionths(x: u64) -> Self { Self(x.min(1_000_000_000) * 1_000_000_000 ) }
/// Construct new instance where `x` is in millionths. Value equivalent to `x / 1,000,000`.
pub fn from_millionths(x: u64) -> Self { Self(x.min(1_000_000) * 1000_000_000_000) }
/// Construct new instance where `x` is parts in u128::max_value. Equal to x/U128::max_value.
pub fn from_max_value(x: u128) -> Self { Self(x) }
/// Construct new instance where `x` is denominator and the nominator is 1.
pub fn from_xth(x: u64) -> Self { Self(QUINTILLION / x.min(QUINTILLION)) }
#[cfg(feature = "std")]
/// Construct new instance whose value is equal to `x` (between 0 and 1).
pub fn from_fraction(x: f64) -> Self { Self((x.max(0.0).min(1.0) * QUINTILLION as f64) as u64) }
pub fn from_xth(x: u128) -> Self { Self(U128/x.max(1)) }
}
impl ::rstd::ops::Deref for Perquintill {
type Target = u64;
impl ::rstd::ops::Deref for PerU128 {
type Target = u128;
fn deref(&self) -> &u64 {
fn deref(&self) -> &u128 {
&self.0
}
}
impl<N> ::rstd::ops::Mul<N> for Perquintill
where
N: traits::As<u64>
{
type Output = N;
fn mul(self, b: N) -> Self::Output {
<N as traits::As<u64>>::sa(b.as_().saturating_mul(self.0) / QUINTILLION)
}
}
#[cfg(feature = "std")]
impl From<f64> for Perquintill {
fn from(x: f64) -> Perquintill {
Perquintill::from_fraction(x)
}
}
#[cfg(feature = "std")]
impl From<f32> for Perquintill {
fn from(x: f32) -> Perquintill {
Perquintill::from_fraction(x as f64)
}
}
impl codec::CompactAs for Perquintill {
type As = u64;
fn encode_as(&self) -> &u64 {
impl codec::CompactAs for PerU128 {
type As = u128;
fn encode_as(&self) -> &u128 {
&self.0
}
fn decode_from(x: u64) -> Perquintill {
Perquintill(x)
fn decode_from(x: u128) -> PerU128 {
Self(x)
}
}
impl From<codec::Compact<Perquintill>> for Perquintill {
fn from(x: codec::Compact<Perquintill>) -> Perquintill {
impl From<codec::Compact<PerU128>> for PerU128 {
fn from(x: codec::Compact<PerU128>) -> PerU128 {
x.0
}
}
@@ -152,6 +152,35 @@ impl<A, B: Default> Convert<A, B> for () {
fn convert(_: A) -> B { Default::default() }
}
/// A structure that converts the currency type into a lossy u64
/// And back from u128
pub struct CurrencyToVoteHandler;
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 {
if x >> 96 == 0 {
// Remove dust; divide by 2^32
(x >> 32) as u64
} else {
u64::max_value()
}
}
}
impl Convert<u128, u128> for CurrencyToVoteHandler {
fn convert(x: u128) -> u128 {
// if it practically fits in u64
if x >> 64 == 0 {
// Add zero dust; multiply by 2^32
x << 32
}
else {
// 0000_0000_FFFF_FFFF_FFFF_FFFF_0000_0000
(u64::max_value() << 32) as u128
}
}
}
/// A structure that performs identity conversion.
pub struct Identity;
impl<T> Convert<T, T> for Identity {
+4 -3
View File
@@ -34,7 +34,7 @@ use client::{
use runtime_primitives::{ApplyResult, generic, create_runtime_str};
use runtime_primitives::transaction_validity::TransactionValidity;
use runtime_primitives::traits::{
BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup,
BlakeTwo256, Block as BlockT, DigestFor, NumberFor, StaticLookup, CurrencyToVoteHandler
};
use version::RuntimeVersion;
use council::{motions as council_motions, voting as council_voting};
@@ -58,8 +58,8 @@ pub const VERSION: RuntimeVersion = RuntimeVersion {
spec_name: create_runtime_str!("node"),
impl_name: create_runtime_str!("substrate-node"),
authoring_version: 10,
spec_version: 51,
impl_version: 51,
spec_version: 52,
impl_version: 52,
apis: RUNTIME_API_VERSIONS,
};
@@ -129,6 +129,7 @@ impl session::Trait for Runtime {
impl staking::Trait for Runtime {
type Currency = balances::Module<Self>;
type CurrencyToVote = CurrencyToVoteHandler;
type OnRewardMinted = Treasury;
type Event = Event;
type Slash = ();
+118 -78
View File
@@ -15,12 +15,13 @@
// along with Substrate. If not, see <http://www.gnu.org/licenses/>.
//! # Staking Module
//!
//! <!-- Original author of paragraph: @gavofyork -->
//! The staking module is the means by which a set of network maintainers (known as "authorities" in some contexts and "validators" in others)
//!
//! The staking module is the means by which a set of network maintainers (known as _authorities_ in some contexts and _validators_ in others)
//! are chosen based upon those who voluntarily place funds under deposit. Under deposit, those funds are rewarded under
//! normal operation but are held at pain of "slash" (expropriation) should the staked maintainer be found not to be
//! normal operation but are held at pain of _slash_ (expropriation) should the staked maintainer be found not to be
//! discharging their duties properly.
//!
//! You can start using the Staking module by implementing the staking [`Trait`].
//!
//! ## Overview
@@ -34,12 +35,13 @@
//! - Stash account: The account holding an owner's funds used for staking.
//! - Controller account: The account which controls an owner's funds for staking.
//! - Era: A (whole) number of sessions, which is the period that the validator set (and each validator's active nominator set) is recalculated and where rewards are paid out.
//! - Slash: The punishment of a staker by reducing their funds ([reference](#references)).
//! - Slash: The punishment of a staker by reducing their funds.
//!
//! ### Goals
//! <!-- Original author of paragraph: @gavofyork -->
//!
//! The staking system in Substrate NPoS is designed to achieve three goals:
//!
//! - It should be possible to stake funds that are controlled by a cold wallet.
//! - It should be possible to withdraw some, or deposit more, funds without interrupting the role of an entity.
//! - It should be possible to switch between roles (nominator, validator, idle) with minimal overhead.
@@ -48,59 +50,62 @@
//!
//! #### Staking
//!
//! Almost any interaction with the staking module requires at least one account to become **bonded**, also known as
//! being a **staker**. For this, all that it is needed is a secondary _**stash account**_ which will hold the staked funds.
//! Henceforth, the former account that initiated the interest is called the **controller** and the latter, holding the
//! funds, is named the **stash**. Also, note that this implies that entering the staking process requires an _account
//! pair_, one to take the role of the controller and one to be the frozen stash account (any value locked in
//! stash cannot be used, hence called _frozen_). This process in the public API is mostly referred to as _bonding_ via
//! the `bond()` function.
//! Almost any interaction with the staking module requires a process of _**bonding**_ (also known as
//! being a _staker_). To become *bonded* a fund-holding account known as the _stash account_, which holds some of all of the
//! funds that become frozen in place as part of the staking process, is paired with an active **controller** account which issues
//! instructions on how they shall be used.
//!
//! Any account pair successfully placed at stake can accept three possible roles, namely: `validate`, `nominate` or
//! simply `chill`. Note that during the process of accepting these roles, the _controller_ account is always responsible
//! for declaring interest and the _stash_ account stays untouched, without directly interacting in any operation.
//! An account pair can become bonded using the [`bond`](./enum.Call.html#variant.bond) call.
//!
//! Stash accounts can change their associated controller using the [`set_controller`](./enum.Call.html#variant.set_controller) call.
//!
//! There are three possible roles that any staked account pair can be in: `Validator`, `Nominator` and `Idle` (defined in [`StakerStatus`]). There are
//! three corresponding instructions to change between roles, namely:
//! [`validate`](./enum.Call.html#variant.validate), [`nominate`](./enum.Call.html#variant.nominate) and [`chill`](./enum.Call.html#variant.chill).
//!
//! #### Validating
//!
//! A **validator** takes the role of either validating blocks or ensuring their finality, maintaining the veracity of
//! the network. A validator should avoid both any sort of malicious misbehavior and going offline.
//! Bonded accounts that state interest in being a validator do NOT get immediately chosen as a validator. Instead, they
//! are declared as a _candidate_ and they _might_ get elected at the _next **era**_ as a validator. The result of the
//! election is determined by nominators and their votes. An account can become a validator via the `validate()` call.
//! are declared as a _candidate_ and they _might_ get elected at the _next era_ as a validator. The result of the
//! election is determined by nominators and their votes.
//!
//! An account can become a validator candidate via the [`validate`](./enum.Call.html#variant.validate) call.
//!
//! #### Nomination
//!
//! A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on a set of validators
//! to be elected. Once interest in nomination is stated by an account, it takes effect _immediately_, meaning that its
//! votes will be taken into account at the next election round. As mentioned above, a nominator must also place some
//! funds in a stash account, essentially indicating the _weight_ of its vote. In some sense, the nominator bets on the
//! honesty of a set of validators by voting for them, with the goal of having a share of the reward granted to them.
//! Any rewards given to a validator is shared among that validator and all of the nominators that voted for it. The
//! same logic applies to the slash of a validator; if a validator misbehaves all of its nominators also get slashed.
//! to be elected. Once interest in nomination is stated by an account, it takes effect at the next election round. The
//! funds in the nominator's stash account indicate the _weight_ of its vote.
//! Both the rewards and any punishment that a validator earns are shared between the validator and its nominators.
//! This rule incentivizes the nominators to NOT vote for the misbehaving/offline validators as much as possible, simply
//! because the nominators will also lose funds if they vote poorly. An account can become a nominator via the
//! `nominate()` call.
//! because the nominators will also lose funds if they vote poorly.
//!
//! An account can become a nominator via the [`nominate`](enum.Call.html#variant.nominate) call.
//!
//! #### Rewards and Slash
//!
//! The **reward and slashing** procedure are the core of the staking module, attempting to _embrace valid behavior_
//! while _punishing any misbehavior or lack of availability_. Slashing can occur at any point in time, once
//! misbehavior is reported. One such misbehavior is a validator being detected as offline more than a certain number of
//! times. Once slashing is determined, a value is deducted from the balance of the validator and all the nominators who
//! voted for this validator. Same rules apply to the rewards in the sense of being shared among a validator and its
//! associated nominators.
//! The **reward and slashing** procedure is the core of the staking module, attempting to _embrace valid behavior_
//! while _punishing any misbehavior or lack of availability_.
//!
//! Slashing can occur at any point in time, once misbehavior is reported. Once slashing is determined, a value is
//! deducted from the balance of the validator and all the nominators who voted for this validator (values are deducted from the _stash_ account of the slashed entity).
//!
//! Similar to slashing, rewards are also shared among a validator and its associated nominators.
//! Yet, the reward funds are not always transferred to the stash account and can be configured. See [Reward Calculation](#reward-calculation)
//! for more details.
//!
//! #### Chilling
//!
//! Finally, any of the roles above can choose to step back temporarily and just chill for a while. This means that if
//! they are a nominator, they will not be considered as voters anymore and if they are validators, they will no longer
//! be a candidate for the next election (again, both effects apply at the beginning of the next era). An account can
//! step back via the `chill()` call.
//! be a candidate for the next election.
//!
//! An account can step back via the [`chill`](enum.Call.html#variant.chill) call.
//!
//! ## Interface
//!
//! ### Types
//!
//! - `Currency`: Used as the measurement means of staking and funds management.
//!
//! ### Dispatchable
//!
//! The Dispatchable functions of the staking module enable the steps needed for entities to accept and change their
@@ -110,7 +115,7 @@
//!
//! ### Public
//! The staking module contains many public storage items and (im)mutable functions. Please refer to the [struct list](#structs)
//! below and the [`Module`](https://crates.parity.io/srml_staking/struct.Module.html) struct definition for more details.
//! below and the [`Module`] struct definition for more details.
//!
//! ## Usage
//!
@@ -134,8 +139,6 @@
//! Staking::validate(Origin::signed(4), ValidatorPrefs::default());
//! ```
//!
//! Note that, as mentioned, the stash account is transparent in such calls and only the controller initiates the function calls.
//!
//! Similarly, to state desire in nominating:
//!
//! ```rust,ignore
@@ -149,59 +152,96 @@
//! Staking::chill(Origin::signed(4));
//! ```
//!
//! You can find the equivalent of the above calls in your [Substrate UI](https://substrate-ui.parity.io).
//! ### Snippet: Reporting Misbehavior
//!
//! ```
//! use srml_support::{decl_module, dispatch::Result};
//! use system::ensure_signed;
//! use srml_staking::{self as staking};
//!
//! pub trait Trait: staking::Trait {}
//!
//! decl_module! {
//! pub struct Module<T: Trait> for enum Call where origin: T::Origin {
//! /// Report whoever calls this function as offline once.
//! pub fn report_sender(origin) -> Result {
//! let reported = ensure_signed(origin)?;
//! <staking::Module<T>>::on_offline_validator(reported, 1);
//! Ok(())
//! }
//! }
//! }
//! # fn main() { }
//! ```
//!
//! ## Implementation Details
//!
//! ### Slot Stake
//!
//! The term `slot_stake` will be used throughout this section. It refers to a value calculated at the end of each era,
//! containing the _minimum value at stake among all validators._
//! The term [`SlotStake`] will be used throughout this section. It refers to a value calculated at the end of each era,
//! containing the _minimum value at stake among all validators._ Note that a validator's value at stake might be a combination of
//! The validator's own stake and the votes it received. See [`Exposure`] for more details.
//!
//! ### Reward Calculation
//!
//! - Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each session is calculated at
//! the end of the session based on the timeliness of the session, then accumulated to be paid later. The value of
//! the new _per-session-reward_ is calculated at the end of each era by multiplying `slot_stake` and a configuration
//! storage item named `SessionReward`.
//! - Once a new era is triggered, rewards are paid to the validators and the associated nominators.
//! - The validator can declare an amount, named `validator_payment`, that does not get shared with the nominators at
//! each reward payout through their `ValidatorPrefs`. This value gets deducted from the total reward that can be paid.
//! The remaining portion is split among the validator and all of the nominators who had a vote for this validator,
//! proportional to their staked value.
//! - All entities who receive a reward have the option to choose their reward destination, through the `Payee` storage item (see `set_payee()`), to be one of the following:
//! - Controller account.
//! Rewards are recorded **per-session** and paid **per-era**. The value of the reward for each session is calculated at
//! the end of the session based on the timeliness of the session, then accumulated to be paid later. The value of
//! the new _per-session-reward_ is calculated at the end of each era by multiplying [`SlotStake`] and [`SessionReward`]
//! (`SessionReward` is the multiplication factor, represented by a number between 0 and 1).
//! Once a new era is triggered, rewards are paid to the validators and the associated nominators.
//!
//! The validator can declare an amount, named [`validator_payment`](./struct.ValidatorPrefs.html#structfield.validator_payment), that does not get shared with the nominators at
//! each reward payout through their [`ValidatorPrefs`]. This value gets deducted from the total reward that can be paid.
//! The remaining portion is split among the validator and all of the nominators that nominated the validator,
//! proportional to the value staked behind this validator
//! (_i.e._ dividing the [`own`](./struct.Exposure.html#structfield.own) or [`others`](./struct.Exposure.html#structfield.others) by [`total`](./struct.Exposure.html#structfield.total) in [`Exposure`]).
//!
//! All entities who receive a reward have the option to choose their reward destination,
//! through the [`Payee`] storage item (see [`set_payee`](enum.Call.html#variant.set_payee)), to be one of the following:
//!
//! - Controller account, (obviously) not increasing the staked value.
//! - Stash account, not increasing the staked value.
//! - Stash account, also increasing the staked value.
//!
//! ### Slashing details
//!
//! - A validator can be _reported_ to be offline at any point via `on_offline_validator` public function.
//! - Each validator declares how many times it can be _reported_ before it actually gets slashed via the
//! `unstake_threshold` in `ValidatorPrefs`. On top of this, the module also introduces an `OfflineSlashGrace`,
//! which applies to all validators and prevents them from getting immediately slashed.
//! - Similar to the reward value, the slash value is updated at the end of each era by multiplying `slot_stake` and a
//! configuration storage item, `OfflineSlash`.
//! - Once a validator has been reported a sufficient number of times, the actual value that gets deducted from that
//! validator, and every single nominator that voted for it is calculated by multiplying the result of the above point
//! by `2.pow(unstake_threshold)`.
//! - If the previous overflows, then `slot_stake` is used.
//! - If the previous is more than what the validator/nominator has in stake, all of its stake is slashed (`.max(total_stake)`).
//! A validator can be _reported_ to be offline at any point via [`on_offline_validator`](enum.Call.html#variant.on_offline_validator) public function.
//! Each validator declares how many times it can be _reported_ before it actually gets slashed via their
//! `unstake_threshold` in [`ValidatorPrefs`].
//!
//! On top of this, staking module also introduces an [`OfflineSlashGrace`], which applies to all validators and prevents
//! them from getting immediately slashed.
//!
//! Essentially, a validator gets slashed once they have been reported more than [`OfflineSlashGrace`] + [`unstake_threshold`](./struct.ValidatorPrefs.html#structfield.unstake_threshold) times.
//! Getting slashed due to offline report always leads to being _unstaked_ (_i.e._ removed as a validator candidate) as the consequence.
//!
//! The base slash value is computed _per slash-event_ by multiplying [`OfflineSlash`] and the `total` [`Exposure`]. This value
//! is then multiplied by `2.pow(unstake_threshold)` to obtain the final slash value.
//! All individual accounts' punishments are capped at their total stake (NOTE: This cap should never come into force in a correctly implemented, non-corrupted, well-configured system).
//!
//! ### Additional Fund Management Operations
//!
//! Any funds already placed into stash can be the target of the following operations:
//!
//! - The controller account can free a portion (or all) of the funds using the `unbond()` call. Note that the funds
//! are not immediately accessible, instead, a duration denoted by `BondingDuration` (in number of eras) must pass until the funds can actually be removed.
//! - To actually remove the funds, once the bonding duration is over, `withdraw_unbonded()` can be used.
//! - As opposed to the above, additional funds can be added to the stash account via the `bond_extra()` transaction call.
//! The controller account can free a portion (or all) of the funds using the [`unbond`](enum.Call.html#variant.unbond) call.
//! Note that the funds are not immediately accessible. Instead, a duration denoted by [`BondingDuration`] (in number of eras)
//! must pass until the funds can actually be removed. Once the [`BondingDuration`] is over the [`withdraw_unbonded`]((enum.Call.html#variant.withdraw_unbonded)) call can be used
//! to actually withdraw the funds.
//!
//! ### Election algorithm details.
//!### Election Algorithm
//!
//! The current election algorithm is implemented based on Phragmén.
//! The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS).
//!
//! The election algorithm, aside from electing the validators with the most stake value and votes, tries to divide the nominator votes
//! among candidates in an equal manner. To further assure this, an optional post-processing can be applied that iteratively normalizes the nominator staked values
//! until the total difference among votes of a particular nominator are less than a threshold.
//!
//! The current election algorithm is implemented based on Phragmén. The reference implementation can be found [here](https://github.com/w3f/consensus/tree/master/NPoS).
//!
//! ## GenesisConfig
//!
//! See the [`GensisConfig`] for a list of attributes that can be provided.
//! See the [`GenesisConfig`] for a list of attributes that can be provided.
//!
//! ## Related Modules
//!
@@ -209,10 +249,6 @@
//! - [**Sessions**](https://crates.parity.io/srml_session/index.html): Used to manage sessions. Also, a list of new validators is also stored in the sessions module's `Validators` at the end of each era.
//! - [**System**](https://crates.parity.io/srml_system/index.html): Used to obtain block number and time, among other details.
//!
//! # References
//!
//! 1. This document is written as a more verbose version of the original [Staking.md](../Staking.md) file. Some sections, are taken directly from the aforementioned document.
#![cfg_attr(not(feature = "std"), no_std)]
@@ -224,11 +260,11 @@ use srml_support::{StorageValue, StorageMap, EnumerableStorageMap, dispatch::Res
use srml_support::{decl_module, decl_event, decl_storage, ensure};
use srml_support::traits::{
Currency, OnFreeBalanceZero, OnDilution, LockIdentifier, LockableCurrency, WithdrawReasons,
OnUnbalanced, Imbalance
OnUnbalanced, Imbalance,
};
use session::OnSessionChange;
use primitives::Perbill;
use primitives::traits::{Zero, One, As, StaticLookup, CheckedSub, Saturating, Bounded};
use primitives::traits::{Convert, Zero, One, As, StaticLookup, CheckedSub, Saturating, Bounded};
#[cfg(feature = "std")]
use primitives::{Serialize, Deserialize};
use system::ensure_signed;
@@ -249,7 +285,7 @@ const MAX_UNSTAKE_THRESHOLD: u32 = 10;
pub enum StakerStatus<AccountId> {
/// Chilling.
Idle,
/// Declared state in validating or already participating in it.
/// Declared desire in validating or already participating in it.
Validator,
/// Nominating for a group of other stakers.
Nominator(Vec<AccountId>),
@@ -381,6 +417,10 @@ pub trait Trait: system::Trait + session::Trait {
Currency<Self::AccountId> +
LockableCurrency<Self::AccountId, Moment=Self::BlockNumber>;
/// Convert a balance into a number used for election calculation.
/// This must fit into a `u64` but is allowed to be sensibly lossy.
type CurrencyToVote: Convert<BalanceOf<Self>, u64> + Convert<u128, BalanceOf<Self>>;
/// Some tokens minted.
type OnRewardMinted: OnDilution<BalanceOf<Self>>;
@@ -520,7 +560,7 @@ decl_module! {
/// Take the origin account as a stash and lock up `value` of its balance. `controller` will be the
/// account that controls it.
///
/// The dispatch origin for this call must be _Signed_.
/// The dispatch origin for this call must be _Signed_ by the stash account.
fn bond(origin, controller: <T::Lookup as StaticLookup>::Source, #[compact] value: BalanceOf<T>, payee: RewardDestination) {
let stash = ensure_signed(origin)?;
+14 -3
View File
@@ -18,16 +18,27 @@
#![cfg(test)]
use primitives::{traits::IdentityLookup, BuildStorage, Perbill};
use primitives::{traits::{IdentityLookup, Convert}, BuildStorage, Perbill};
use primitives::testing::{Digest, DigestItem, Header, UintAuthorityId, ConvertUintAuthorityId};
use substrate_primitives::{H256, Blake2Hasher};
use runtime_io;
use srml_support::impl_outer_origin;
use crate::{GenesisConfig, Module, Trait, StakerStatus};
// The AccountId alias in this test module.
/// The AccountId alias in this test module.
pub type AccountIdType = u64;
/// Simple structure that exposes how u64 currency can be represented as... u64.
pub struct CurrencyToVoteHandler;
impl Convert<u64, u64> for CurrencyToVoteHandler {
fn convert(x: u64) -> u64 { x }
}
impl Convert<u128, u64> for CurrencyToVoteHandler {
fn convert(x: u128) -> u64 {
x as u64
}
}
impl_outer_origin!{
pub enum Origin for Test {}
}
@@ -73,6 +84,7 @@ impl timestamp::Trait for Test {
}
impl Trait for Test {
type Currency = balances::Module<Self>;
type CurrencyToVote = CurrencyToVoteHandler;
type OnRewardMinted = ();
type Event = ();
type Slash = ();
@@ -131,7 +143,6 @@ impl ExtBuilder {
self
}
pub fn nominate(mut self, nominate: bool) -> Self {
// NOTE: this only sets a dummy nominator for tests that want 10 and 20 (default validators) to be chosen by default.
self.nominate = nominate;
self
}
+68 -55
View File
@@ -17,11 +17,13 @@
//! Rust implementation of the Phragmén election algorithm.
use rstd::prelude::*;
use primitives::Perquintill;
use primitives::traits::{Zero, As, Bounded, CheckedMul, Saturating};
use primitives::PerU128;
use primitives::traits::{Zero, Saturating, Convert};
use parity_codec::{HasCompact, Encode, Decode};
use crate::{Exposure, BalanceOf, Trait, ValidatorPrefs, IndividualExposure};
type Fraction = PerU128;
type ExtendedBalance = u128;
/// Configure the behavior of the Phragmen election.
/// Might be deprecated.
@@ -43,39 +45,39 @@ pub struct Candidate<AccountId, Balance: HasCompact> {
/// Exposure struct, holding info about the value that the validator has in stake.
pub exposure: Exposure<AccountId, Balance>,
/// Intermediary value used to sort candidates.
pub score: Perquintill,
pub score: Fraction,
/// Accumulator of the stake of this candidate based on received votes.
approval_stake: Balance,
approval_stake: ExtendedBalance,
/// Flag for being elected.
elected: bool,
/// This is most often equal to `Exposure.total` but not always. Needed for [`equalize`]
backing_stake: Balance
backing_stake: ExtendedBalance
}
/// Wrapper around the nomination info of a single nominator for a group of validators.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Nominator<AccountId, Balance: HasCompact> {
pub struct Nominator<AccountId> {
/// The nominator's account.
who: AccountId,
/// List of validators proposed by this nominator.
edges: Vec<Edge<AccountId, Balance>>,
edges: Vec<Edge<AccountId>>,
/// the stake amount proposed by the nominator as a part of the vote.
budget: Balance,
budget: ExtendedBalance,
/// Incremented each time a nominee that this nominator voted for has been elected.
load: Perquintill,
load: Fraction,
}
/// Wrapper around a nominator vote and the load of that vote.
#[derive(Clone, Encode, Decode, Default)]
#[cfg_attr(feature = "std", derive(Debug))]
pub struct Edge<AccountId, Balance: HasCompact> {
pub struct Edge<AccountId> {
/// Account being voted for
who: AccountId,
/// Load of this vote.
load: Perquintill,
load: Fraction,
/// Final backing stake of this vote.
backing_stake: Balance,
backing_stake: ExtendedBalance,
/// Index of the candidate stored in the 'candidates' vector
candidate_index: usize,
/// Index of the candidate stored in the 'elected_candidates' vector. Used only with equalize.
@@ -107,6 +109,8 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
>>,
for <'r> FS: Fn(&'r T::AccountId) -> BalanceOf<T>,
{
let expand = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let shrink = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let rounds = get_rounds();
let mut elected_candidates;
@@ -121,14 +125,14 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
}).collect::<Vec<Candidate<T::AccountId, BalanceOf<T>>>>();
// 1.1- Add phantom votes.
let mut nominators: Vec<Nominator<T::AccountId, BalanceOf<T>>> = Vec::with_capacity(candidates.len());
let mut nominators: Vec<Nominator<T::AccountId>> = Vec::with_capacity(candidates.len());
candidates.iter_mut().enumerate().for_each(|(idx, c)| {
c.approval_stake += c.exposure.total;
c.approval_stake += expand(c.exposure.total);
nominators.push(Nominator {
who: c.who.clone(),
edges: vec![ Edge { who: c.who.clone(), candidate_index: idx, ..Default::default() }],
budget: c.exposure.total,
load: Perquintill::zero(),
budget: expand(c.exposure.total),
load: Fraction::zero(),
})
});
@@ -136,10 +140,11 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
// Also collect approval stake along the way.
nominators.extend(get_nominators().map(|(who, nominees)| {
let nominator_stake = stash_of(&who);
let mut edges: Vec<Edge<T::AccountId, BalanceOf<T>>> = Vec::with_capacity(nominees.len());
let mut edges: Vec<Edge<T::AccountId>> = Vec::with_capacity(nominees.len());
for n in &nominees {
if let Some(idx) = candidates.iter_mut().position(|i| i.who == *n) {
candidates[idx].approval_stake = candidates[idx].approval_stake.saturating_add(nominator_stake);
candidates[idx].approval_stake = candidates[idx].approval_stake
.saturating_add(expand(nominator_stake));
edges.push(Edge { who: n.clone(), candidate_index: idx, ..Default::default() });
}
}
@@ -147,15 +152,15 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
Nominator {
who,
edges: edges,
budget: nominator_stake,
load: Perquintill::zero(),
budget: expand(nominator_stake),
load: Fraction::zero(),
}
}));
// 3- optimization:
// Candidates who have 0 stake => have no votes or all null-votes. Kick them out not.
let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > BalanceOf::<T>::zero())
let mut candidates = candidates.into_iter().filter(|c| c.approval_stake > 0)
.collect::<Vec<Candidate<T::AccountId, BalanceOf<T>>>>();
// 4- If we have more candidates then needed, run Phragmén.
@@ -166,7 +171,7 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
// Loop 1: initialize score
for c in &mut candidates {
if !c.elected {
c.score = Perquintill::from_xth(c.approval_stake.as_());
c.score = Fraction::from_xth(c.approval_stake);
}
}
// Loop 2: increment score.
@@ -174,10 +179,8 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
for e in &n.edges {
let c = &mut candidates[e.candidate_index];
if !c.elected {
// Note: This seems to never overflow, ok to be safe though
let temp = n.budget.as_().saturating_mul(*n.load) / c.approval_stake.as_();
// Note: This seems to never overflow, ok to be safe though
c.score = Perquintill::from_quintillionths((*c.score).saturating_add(temp));
let temp = n.budget.saturating_mul(*n.load) / c.approval_stake;
c.score = Fraction::from_max_value((*c.score).saturating_add(temp));
}
}
}
@@ -194,7 +197,7 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
for n in &mut nominators {
for e in &mut n.edges {
if e.who == winner.who {
e.load = Perquintill::from_quintillionths(*winner.score - *n.load);
e.load = Fraction::from_max_value(*winner.score - *n.load);
n.load = winner.score;
}
}
@@ -209,14 +212,14 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
// if the target of this vote is among the winners, otherwise let go.
if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who) {
e.elected = true;
// NOTE: always divide last to avoid collapse to zero.
e.backing_stake = <BalanceOf<T>>::sa(n.budget.as_().saturating_mul(*e.load) / *n.load);
// NOTE: for now, always divide last to avoid collapse to zero.
e.backing_stake = n.budget.saturating_mul(*e.load) / *n.load;
c.backing_stake = c.backing_stake.saturating_add(e.backing_stake);
if c.who != n.who {
// Only update the exposure if this vote is from some other account.
c.exposure.total = c.exposure.total.saturating_add(e.backing_stake);
c.exposure.total = c.exposure.total.saturating_add(shrink(e.backing_stake));
c.exposure.others.push(
IndividualExposure { who: n.who.clone(), value: e.backing_stake }
IndividualExposure { who: n.who.clone(), value: shrink(e.backing_stake) }
);
}
}
@@ -250,7 +253,6 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
}
}
}
} else {
if candidates.len() > minimum_validator_count {
// if we don't have enough candidates, just choose all that have some vote.
@@ -259,9 +261,9 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
let nominator = n.who.clone();
for e in &mut n.edges {
if let Some(c) = elected_candidates.iter_mut().find(|c| c.who == e.who && c.who != nominator) {
c.exposure.total = c.exposure.total.saturating_add(n.budget);
c.exposure.total = c.exposure.total.saturating_add(shrink(n.budget));
c.exposure.others.push(
IndividualExposure { who: n.who.clone(), value: n.budget }
IndividualExposure { who: n.who.clone(), value: shrink(n.budget) }
);
}
}
@@ -274,29 +276,40 @@ pub fn elect<T: Trait + 'static, FR, FN, FV, FS>(
Some(elected_candidates)
}
/// Performs equalize post-processing to the output of the election algorithm
/// This function mutates the input parameters, most noticeably it updates the exposure of
/// the elected candidates.
/// The return value is to tolerance at which the function has stopped.
pub fn equalize<T: Trait + 'static>(
nominator: &mut Nominator<T::AccountId, BalanceOf<T>>,
nominator: &mut Nominator<T::AccountId>,
elected_candidates: &mut Vec<Candidate<T::AccountId, BalanceOf<T>>>,
tolerance: BalanceOf<T>
_tolerance: BalanceOf<T>
) -> BalanceOf<T> {
let expand = |b: BalanceOf<T>| <T::CurrencyToVote as Convert<BalanceOf<T>, u64>>::convert(b) as ExtendedBalance;
let shrink = |b: ExtendedBalance| <T::CurrencyToVote as Convert<ExtendedBalance, BalanceOf<T>>>::convert(b);
let tolerance = expand(_tolerance);
let mut elected_edges = nominator.edges
.iter_mut()
.filter(|e| e.elected)
.collect::<Vec<&mut Edge<T::AccountId, BalanceOf<T>>>>();
if elected_edges.len() == 0 { return <BalanceOf<T>>::zero(); }
.collect::<Vec<&mut Edge<T::AccountId>>>();
if elected_edges.len() == 0 {
return <BalanceOf<T>>::zero();
}
let stake_used = elected_edges
.iter()
.fold(<BalanceOf<T>>::zero(), |s, e| s.saturating_add(e.backing_stake));
.fold(0, |s, e| s.saturating_add(e.backing_stake));
let backed_stakes = elected_edges
.iter()
.map(|e| elected_candidates[e.elected_idx].backing_stake)
.collect::<Vec<BalanceOf<T>>>();
.collect::<Vec<ExtendedBalance>>();
let backing_backed_stake = elected_edges
.iter()
.filter(|e| e.backing_stake > <BalanceOf<T>>::zero())
.filter(|e| e.backing_stake > 0)
.map(|e| elected_candidates[e.elected_idx].backing_stake)
.collect::<Vec<BalanceOf<T>>>();
.collect::<Vec<ExtendedBalance>>();
let mut difference;
if backing_backed_stake.len() > 0 {
@@ -311,7 +324,7 @@ pub fn equalize<T: Trait + 'static>(
difference = max_stake.saturating_sub(min_stake);
difference = difference.saturating_add(nominator.budget.saturating_sub(stake_used));
if difference < tolerance {
return difference;
return shrink(difference);
}
} else {
difference = nominator.budget;
@@ -322,18 +335,19 @@ pub fn equalize<T: Trait + 'static>(
// NOTE: no assertions in the runtime, but this should nonetheless be indicative.
//assert_eq!(elected_candidates[e.elected_idx].who, e.who);
elected_candidates[e.elected_idx].backing_stake -= e.backing_stake;
elected_candidates[e.elected_idx].exposure.total -= e.backing_stake;
e.backing_stake = <BalanceOf<T>>::zero();
elected_candidates[e.elected_idx].exposure.total -= shrink(e.backing_stake);
e.backing_stake = 0;
});
elected_edges.sort_unstable_by_key(|e| elected_candidates[e.elected_idx].backing_stake);
let mut cumulative_stake = <BalanceOf<T>>::zero();
let mut cumulative_stake: ExtendedBalance = 0;
let mut last_index = elected_edges.len() - 1;
let budget = nominator.budget;
elected_edges.iter_mut().enumerate().for_each(|(idx, e)| {
let stake = elected_candidates[e.elected_idx].backing_stake;
let stake_mul = stake.checked_mul(&<BalanceOf<T>>::sa(idx as u64)).unwrap_or(<BalanceOf<T>>::max_value());
let stake_mul = stake.saturating_mul(idx as ExtendedBalance);
let stake_sub = stake_mul.saturating_sub(cumulative_stake);
if stake_sub > budget {
last_index = idx.checked_sub(1).unwrap_or(0);
@@ -346,19 +360,18 @@ pub fn equalize<T: Trait + 'static>(
let split_ways = last_index + 1;
let excess = nominator.budget
.saturating_add(cumulative_stake)
.saturating_sub(
last_stake.checked_mul(&<BalanceOf<T>>::sa(split_ways as u64))
.unwrap_or(<BalanceOf<T>>::max_value())
);
.saturating_sub(last_stake.saturating_mul(split_ways as ExtendedBalance));
let nominator_address = nominator.who.clone();
elected_edges.iter_mut().take(split_ways).for_each(|e| {
let c = &mut elected_candidates[e.elected_idx];
e.backing_stake = (excess / <BalanceOf<T>>::sa(split_ways as u64)).saturating_add(last_stake) - c.backing_stake;
c.exposure.total = c.exposure.total.saturating_add(e.backing_stake);
e.backing_stake = (excess / split_ways as ExtendedBalance)
.saturating_add(last_stake)
.saturating_sub(c.backing_stake);
c.exposure.total = c.exposure.total.saturating_add(shrink(e.backing_stake));
c.backing_stake = c.backing_stake.saturating_add(e.backing_stake);
if let Some(i_expo) = c.exposure.others.iter_mut().find(|i| i.who == nominator_address) {
i_expo.value = e.backing_stake;
i_expo.value = shrink(e.backing_stake);
}
});
difference
shrink(difference)
}
+182 -198
View File
@@ -21,8 +21,8 @@
use super::*;
use runtime_io::with_externalities;
use phragmen;
use primitives::Perquintill;
use srml_support::{assert_ok, assert_noop, EnumerableStorageMap};
use primitives::PerU128;
use srml_support::{assert_ok, assert_noop, assert_eq_uvec, EnumerableStorageMap};
use mock::{Balances, Session, Staking, System, Timestamp, Test, ExtBuilder, Origin};
use srml_support::traits::{Currency, ReservableCurrency};
@@ -54,7 +54,7 @@ fn basic_setup_works() {
assert_eq!(Staking::nominators(101), vec![11, 21]);
// Account 10 is exposed by 1000 * balance_factor from their own stash in account 11 + the default nominator vote
assert_eq!(Staking::stakers(11), Exposure { total: 1125, own: 1000, others: vec![ IndividualExposure { who: 101, value: 125 }] });
assert_eq!(Staking::stakers(11), Exposure { total: 1124, own: 1000, others: vec![ IndividualExposure { who: 101, value: 124 }] });
// Account 20 is exposed by 1000 * balance_factor from their own stash in account 21 + the default nominator vote
assert_eq!(Staking::stakers(21), Exposure { total: 1375, own: 1000, others: vec![ IndividualExposure { who: 101, value: 375 }] });
@@ -69,8 +69,7 @@ fn basic_setup_works() {
assert_eq!(Staking::current_session_reward(), 10);
// initial slot_stake
assert_eq!(Staking::slot_stake(), 1125); // Naive
// assert_eq!(Staking::slot_stake(), 1250); // Post-process
assert_eq!(Staking::slot_stake(), 1124); // Naive
// initial slash_count of validators
assert_eq!(Staking::slash_count(&11), 0);
@@ -173,7 +172,7 @@ fn offline_grace_should_delay_slashing() {
assert_ok!(Staking::set_offline_slash_grace(offline_slash_grace));
assert_eq!(Staking::offline_slash_grace(), 1);
// Check unstaked_threshold is 3 (default)
// Check unstake_threshold is 3 (default)
let default_unstake_threshold = 3;
assert_eq!(Staking::validators(&11), ValidatorPrefs { unstake_threshold: default_unstake_threshold, validator_payment: 0 });
@@ -192,7 +191,7 @@ fn offline_grace_should_delay_slashing() {
Staking::on_offline_validator(10, 1);
assert_eq!(Staking::slash_count(&11), 5);
// User gets slashed
assert_eq!(Balances::free_balance(&11), 0);
assert!(Balances::free_balance(&11) < 70);
// New era is forced
assert!(Staking::forcing_new_era().is_some());
});
@@ -440,7 +439,7 @@ fn staking_should_work() {
.build(),
|| {
// remember + compare this along with the test.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
assert_ok!(Staking::set_bonding_duration(2));
assert_eq!(Staking::bonding_duration(), 2);
@@ -458,7 +457,7 @@ fn staking_should_work() {
assert_ok!(Staking::validate(Origin::signed(4), ValidatorPrefs::default()));
// No effects will be seen so far.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// --- Block 2:
System::set_block_number(2);
@@ -466,7 +465,7 @@ fn staking_should_work() {
assert_eq!(Staking::current_era(), 0);
// No effects will be seen so far. Era has not been yet triggered.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// --- Block 3: the validators will now change.
@@ -475,7 +474,7 @@ fn staking_should_work() {
// 2 only voted for 4 and 20
assert_eq!(Session::validators().len(), 2);
assert_eq!(Session::validators(), vec![20, 4]);
assert_eq_uvec!(Session::validators(), vec![20, 4]);
assert_eq!(Staking::current_era(), 1);
@@ -487,14 +486,14 @@ fn staking_should_work() {
Staking::chill(Origin::signed(4)).unwrap();
// nothing should be changed so far.
assert_eq!(Session::validators(), vec![20, 4]);
assert_eq_uvec!(Session::validators(), vec![20, 4]);
assert_eq!(Staking::current_era(), 1);
// --- Block 5: nothing. 4 is still there.
System::set_block_number(5);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![20, 4]);
assert_eq_uvec!(Session::validators(), vec![20, 4]);
assert_eq!(Staking::current_era(), 1);
@@ -503,7 +502,7 @@ fn staking_should_work() {
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 2);
assert_eq!(Session::validators().contains(&4), false);
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Note: the stashed value of 4 is still lock
assert_eq!(Staking::ledger(&4), Some(StakingLedger { stash: 3, total: 1500, active: 1500, unlocking: vec![] }));
@@ -528,7 +527,7 @@ fn less_than_needed_candidates_works() {
assert_eq!(Staking::minimum_validator_count(), 1);
// initial validators
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// 10 and 20 are now valid candidates.
// trigger era
@@ -537,7 +536,7 @@ fn less_than_needed_candidates_works() {
assert_eq!(Staking::current_era(), 1);
// both validators will be chosen again. NO election algorithm is even executed.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// But the exposure is updated in a simple way. No external votes exists. This is purely self-vote.
assert_eq!(Staking::stakers(10).others.iter().map(|e| e.who).collect::<Vec<BalanceOf<Test>>>(), vec![]);
@@ -560,7 +559,7 @@ fn no_candidate_emergency_condition() {
assert_eq!(Staking::validator_count(), 15);
// initial validators
assert_eq!(Session::validators(), vec![10, 20, 30, 40]);
assert_eq_uvec!(Session::validators(), vec![10, 20, 30, 40]);
// trigger era
System::set_block_number(1);
@@ -568,15 +567,12 @@ fn no_candidate_emergency_condition() {
assert_eq!(Staking::current_era(), 1);
// No one nominates => no one has a proper vote => no change
assert_eq!(Session::validators(), vec![10, 20, 30, 40]);
assert_eq_uvec!(Session::validators(), vec![10, 20, 30, 40]);
});
}
#[test]
fn nominating_and_rewards_should_work() {
// For now it tests a functionality which somehow overlaps with other tests:
// the fact that the nominator is rewarded properly.
//
// PHRAGMEN OUTPUT: running this test with the reference impl gives:
//
// Votes [('10', 1000, ['10']), ('20', 1000, ['20']), ('30', 1000, ['30']), ('40', 1000, ['40']), ('2', 1000, ['10', '20', '30']), ('4', 1000, ['10', '20', '40'])]
@@ -620,7 +616,7 @@ fn nominating_and_rewards_should_work() {
.build(),
|| {
// initial validators -- everyone is actually even.
assert_eq!(Session::validators(), vec![40, 30]);
assert_eq_uvec!(Session::validators(), vec![40, 30]);
// Set payee to controller
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
@@ -638,9 +634,6 @@ fn nominating_and_rewards_should_work() {
let _ = Balances::make_free_balance_be(i, initial_balance);
}
// record their balances.
for i in 1..5 { assert_eq!(Balances::total_balance(&i), initial_balance); }
// bond two account pairs and state interest in nomination.
// 2 will nominate for 10, 20, 30
assert_ok!(Staking::bond(Origin::signed(1), 2, 1000, RewardDestination::Controller));
@@ -654,7 +647,7 @@ fn nominating_and_rewards_should_work() {
assert_eq!(Staking::current_era(), 1);
// 10 and 20 have more votes, they will be chosen by phragmen.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// OLD validators must have already received some rewards.
assert_eq!(Balances::total_balance(&40), 1 + session_reward);
@@ -664,9 +657,9 @@ fn nominating_and_rewards_should_work() {
// total expo of 10, with 1200 coming from nominators (externals), according to phragmen.
assert_eq!(Staking::stakers(11).own, 1000);
assert_eq!(Staking::stakers(11).total, 1000 + 800);
assert_eq!(Staking::stakers(11).total, 1000 + 798);
// 2 and 4 supported 10, each with stake 600, according to phragmen.
assert_eq!(Staking::stakers(11).others.iter().map(|e| e.value).collect::<Vec<BalanceOf<Test>>>(), vec![400, 400]);
assert_eq!(Staking::stakers(11).others.iter().map(|e| e.value).collect::<Vec<BalanceOf<Test>>>(), vec![399, 399]);
assert_eq!(Staking::stakers(11).others.iter().map(|e| e.who).collect::<Vec<BalanceOf<Test>>>(), vec![3, 1]);
// total expo of 20, with 500 coming from nominators (externals), according to phragmen.
assert_eq!(Staking::stakers(21).own, 1000);
@@ -687,13 +680,13 @@ fn nominating_and_rewards_should_work() {
// nothing else will happen, era ends and rewards are paid again,
// it is expected that nominators will also be paid. See below
// Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11
// Nominator 2: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11
assert_eq!(Balances::total_balance(&2), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11));
// Nominator 4: has [400/1800 ~ 2/9 from 10] + [600/2200 ~ 3/11 from 20]'s reward. ==> 2/9 + 3/11
assert_eq!(Balances::total_balance(&4), initial_balance + (2*new_session_reward/9 + 3*new_session_reward/11));
// 10 got 800 / 1800 external stake => 8/18 =? 4/9 => Validator's share = 5/9
assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9) ;
assert_eq!(Balances::total_balance(&10), initial_balance + 5*new_session_reward/9 + 2) ;
// 10 got 1200 / 2200 external stake => 12/22 =? 6/11 => Validator's share = 5/11
assert_eq!(Balances::total_balance(&20), initial_balance + 5*new_session_reward/11);
});
@@ -710,7 +703,7 @@ fn nominators_also_get_slashed() {
// Account 10 has not been reported offline
assert_eq!(Staking::slash_count(&10), 0);
// initial validators
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
<OfflineSlash<Test>>::put(Perbill::from_percent(12));
// Set payee to controller
@@ -753,8 +746,7 @@ fn nominators_also_get_slashed() {
#[test]
fn double_staking_should_fail() {
// should test (in the same order):
// * an account already bonded as controller CAN be reused as the controller of another account.
// * an account already bonded as stash cannot be the controller of another account.
// * an account already bonded as stash cannot be be stashed again.
// * an account already bonded as stash cannot nominate.
// * an account already bonded as controller can nominate.
with_externalities(&mut ExtBuilder::default()
@@ -762,7 +754,6 @@ fn double_staking_should_fail() {
.build(),
|| {
let arbitrary_value = 5;
System::set_block_number(1);
// 2 = controller, 1 stashed => ok
assert_ok!(Staking::bond(Origin::signed(1), 2, arbitrary_value, RewardDestination::default()));
// 4 = not used so far, 1 stashed => not allowed.
@@ -777,16 +768,12 @@ fn double_staking_should_fail() {
#[test]
fn double_controlling_should_fail() {
// should test (in the same order):
// * an account already bonded as controller CAN be reused as the controller of another account.
// * an account already bonded as stash cannot be the controller of another account.
// * an account already bonded as stash cannot nominate.
// * an account already bonded as controller can nominate.
// * an account already bonded as controller CANNOT be reused as the controller of another account.
with_externalities(&mut ExtBuilder::default()
.sessions_per_era(2)
.build(),
|| {
let arbitrary_value = 5;
System::set_block_number(1);
// 2 = controller, 1 stashed => ok
assert_ok!(Staking::bond(Origin::signed(1), 2, arbitrary_value, RewardDestination::default()));
// 2 = controller, 3 stashed (Note that 2 is reused.) => no-op
@@ -888,7 +875,7 @@ fn cannot_transfer_staked_balance() {
#[test]
fn cannot_transfer_staked_balance_2() {
// Tests that a stash account cannot transfer funds
// Same test as above but with 20
// Same test as above but with 20, and more accurate.
// 21 has 2000 free balance but 1000 at stake
with_externalities(&mut ExtBuilder::default()
.nominate(false)
@@ -916,7 +903,7 @@ fn cannot_reserve_staked_balance() {
// Confirm account 11 has some free balance
assert_eq!(Balances::free_balance(&11), 1000);
// Confirm account 11 (via controller 10) is totally staked
assert_eq!(Staking::stakers(&11).total, 1125);
assert_eq!(Staking::stakers(&11).own, 1000);
// Confirm account 11 cannot transfer as a result
assert_noop!(Balances::reserve(&11, 1), "account liquidity restrictions prevent withdrawal");
@@ -931,20 +918,16 @@ fn cannot_reserve_staked_balance() {
fn reward_destination_works() {
// Rewards go to the correct destination as determined in Payee
with_externalities(&mut ExtBuilder::default().nominate(false).build(), || {
// Check that account 11 is a validator
assert!(<Validators<Test>>::exists(11));
// Check that account 11 is a validator
assert!(Staking::current_elected().contains(&11));
// Check the balance of the validator account
assert_eq!(Balances::free_balance(&10), 1);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(&11), 1000);
// Check these two accounts are bonded
assert_eq!(Staking::bonded(&11), Some(10));
// Check how much is at stake
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
// Track current session reward
let mut current_session_reward = Staking::current_session_reward();
// Check current session reward is 10
let session_reward0 = Staking::current_session_reward(); // 10
// Move forward the system for payment
System::set_block_number(1);
@@ -953,14 +936,12 @@ fn reward_destination_works() {
// Check that RewardDestination is Staked (default)
assert_eq!(Staking::payee(&11), RewardDestination::Staked);
// Check current session reward is 10
assert_eq!(current_session_reward, 10);
// Check that reward went to the stash account of validator
assert_eq!(Balances::free_balance(&11), 1000 + current_session_reward);
assert_eq!(Balances::free_balance(&11), 1000 + session_reward0);
// Check that amount at stake increased accordingly
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] }));
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
// Update current session reward
current_session_reward = Staking::current_session_reward(); // 1010 (1* slot_stake)
let session_reward1 = Staking::current_session_reward(); // 1010 (1* slot_stake)
//Change RewardDestination to Stash
<Payee<Test>>::insert(&11, RewardDestination::Stash);
@@ -973,12 +954,11 @@ fn reward_destination_works() {
// Check that RewardDestination is Stash
assert_eq!(Staking::payee(&11), RewardDestination::Stash);
// Check that reward went to the stash account
assert_eq!(Balances::free_balance(&11), 1000 + 10 + current_session_reward);
assert_eq!(Balances::free_balance(&11), 1000 + session_reward0 + session_reward1);
// Record this value
let recorded_stash_balance = 1000 + 10 + current_session_reward;
let recorded_stash_balance = 1000 + session_reward0 + session_reward1;
// Check that amount at stake is NOT increased
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] }));
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
// Change RewardDestination to Controller
<Payee<Test>>::insert(&11, RewardDestination::Controller);
@@ -990,13 +970,14 @@ fn reward_destination_works() {
System::set_block_number(3);
Timestamp::set_timestamp(15);
Session::check_rotate_session(System::block_number());
let session_reward2 = Staking::current_session_reward(); // 1010 (1* slot_stake)
// Check that RewardDestination is Controller
assert_eq!(Staking::payee(&11), RewardDestination::Controller);
// Check that reward went to the controller account
assert_eq!(Balances::free_balance(&10), 1 + 1010);
assert_eq!(Balances::free_balance(&10), 1 + session_reward2);
// Check that amount at stake is NOT increased
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 10, active: 1000 + 10, unlocking: vec![] }));
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + session_reward0, active: 1000 + session_reward0, unlocking: vec![] }));
// Check that amount in staked account is NOT increased.
assert_eq!(Balances::free_balance(&11), recorded_stash_balance);
});
@@ -1012,27 +993,20 @@ fn validator_payment_prefs_work() {
.sessions_per_era(3)
.build(),
|| {
// Initial config
let session_reward = 10;
let validator_cut = 5;
let validator_initial_balance = Balances::total_balance(&11);
// Initial config should be correct
assert_eq!(Staking::era_length(), 9);
assert_eq!(Staking::sessions_per_era(), 3);
assert_eq!(Staking::last_era_length_change(), 0);
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 0);
let stash_initial_balance = Balances::total_balance(&11);
assert_eq!(Staking::current_session_reward(), session_reward);
// check the balance of a validator accounts.
assert_eq!(Balances::total_balance(&10), 1);
// check the balance of a validator's stash accounts.
assert_eq!(Balances::total_balance(&11), validator_initial_balance);
assert_eq!(Balances::total_balance(&11), stash_initial_balance);
// and the nominator (to-be)
let _ = Balances::make_free_balance_be(&2, 500);
// add a dummy nominator.
// NOTE: this nominator is being added 'manually', use '.nominate()' to do it realistically.
<Stakers<Test>>::insert(&11, Exposure {
own: 500, // equal division indicates that the reward will be equally divided among validator and nominator.
total: 1000,
@@ -1045,10 +1019,9 @@ fn validator_payment_prefs_work() {
});
// ------------ Fast forward
let mut block = 3;
// Block 3 => Session 1 => Era 0
let mut block = 3;
System::set_block_number(block);
Timestamp::set_timestamp(block*5); // on time.
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 1);
@@ -1059,7 +1032,6 @@ fn validator_payment_prefs_work() {
block = 6; // Block 6 => Session 2 => Era 0
System::set_block_number(block);
Timestamp::set_timestamp(block*5); // a little late.
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 0);
assert_eq!(Session::current_index(), 2);
@@ -1069,7 +1041,6 @@ fn validator_payment_prefs_work() {
block = 9; // Block 9 => Session 3 => Era 1
System::set_block_number(block);
Timestamp::set_timestamp(block*5);
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::current_index(), 3);
@@ -1077,7 +1048,7 @@ fn validator_payment_prefs_work() {
// whats left to be shared is the sum of 3 rounds minus the validator's cut.
let shared_cut = 3 * session_reward - validator_cut;
// Validator's payee is Staked account, 11, reward will be paid here.
assert_eq!(Balances::total_balance(&11), validator_initial_balance + shared_cut/2 + validator_cut);
assert_eq!(Balances::total_balance(&11), stash_initial_balance + shared_cut/2 + validator_cut);
// Controller account will not get any reward.
assert_eq!(Balances::total_balance(&10), 1);
// Rest of the reward will be shared and paid to the nominator in stake.
@@ -1112,7 +1083,6 @@ fn bond_extra_works() {
assert_ok!(Staking::bond_extra(Origin::signed(11), u64::max_value()));
// The full amount of the funds should now be in the total and active
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000000, active: 1000000, unlocking: vec![] }));
});
}
@@ -1147,14 +1117,7 @@ fn bond_extra_and_withdraw_unbonded_works() {
// confirm that 10 is a normal validator and gets paid at the end of the era.
System::set_block_number(1);
Timestamp::set_timestamp(5);
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 1);
assert_eq!(Session::current_index(), 1);
// NOTE: despite having .nominate() in extBuilder, 20 doesn't have a share since
// rewards are paid before election in new_era()
assert_eq!(Balances::total_balance(&10), 1 + 10);
// Initial state of 10
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
@@ -1176,13 +1139,12 @@ fn bond_extra_and_withdraw_unbonded_works() {
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000 + 100, active: 1000 + 100, unlocking: vec![] }));
// Exposure is now updated.
assert_eq!(Staking::stakers(&11), Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] });
// Note that by this point 10 also have received more rewards, but we don't care now.
// assert_eq!(Balances::total_balance(&10), 1 + 10 + MORE_REWARD);
// Unbond almost all of the funds in stash.
Staking::unbond(Origin::signed(10), 1000).unwrap();
assert_eq!(Staking::ledger(&10), Some(StakingLedger {
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] }));
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] })
);
// Attempting to free the balances now will fail. 2 eras need to pass.
Staking::withdraw_unbonded(Origin::signed(10)).unwrap();
@@ -1190,7 +1152,9 @@ fn bond_extra_and_withdraw_unbonded_works() {
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] }));
// trigger next era.
System::set_block_number(3);Timestamp::set_timestamp(15);Session::check_rotate_session(System::block_number());
System::set_block_number(3);
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 3);
assert_eq!(Session::current_index(), 3);
@@ -1200,7 +1164,8 @@ fn bond_extra_and_withdraw_unbonded_works() {
stash: 11, total: 1000 + 100, active: 100, unlocking: vec![UnlockChunk{ value: 1000, era: 2 + 2}] }));
// trigger next era.
System::set_block_number(4);Timestamp::set_timestamp(20);Session::check_rotate_session(System::block_number());
System::set_block_number(4);
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 4);
assert_eq!(Session::current_index(), 4);
@@ -1212,17 +1177,14 @@ fn bond_extra_and_withdraw_unbonded_works() {
}
#[test]
fn slot_stake_is_least_staked_validator_and_limits_maximum_punishment() {
fn slot_stake_is_least_staked_validator_and_exposure_defines_maximum_punishment() {
// Test that slot_stake is determined by the least staked validator
// Test that slot_stake is the maximum punishment that can happen to a validator
// Note that rewardDestination is the stash account by default
// Note that unlike reward slash will affect free_balance, not the stash account.
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.fare(false)
.build(),
|| {
// Give the man some money.
// Confirm validator count is 2
assert_eq!(Staking::validator_count(), 2);
// Confirm account 10 and 20 are validators
@@ -1245,26 +1207,21 @@ fn slot_stake_is_least_staked_validator_and_limits_maximum_punishment() {
// New era --> rewards are paid --> stakes are changed
System::set_block_number(1);
Timestamp::set_timestamp(5);
Session::check_rotate_session(System::block_number());
assert_eq!(Staking::current_era(), 1);
// -- new balances + reward
assert_eq!(Staking::stakers(&11).total, 1000 + 10);
assert_eq!(Staking::stakers(&21).total, 69 + 10);
// -- Note that rewards are going directly to stash, not as free balance.
assert_eq!(Balances::free_balance(&10), 1000);
assert_eq!(Balances::free_balance(&20), 1000);
// -- slot stake should also be updated.
assert_eq!(Staking::slot_stake(), 79);
// // If 10 gets slashed now, despite having +1000 in stash, it will be slashed byt 79, which is the slot stake
// If 10 gets slashed now, it will be slashed by 5% of exposure.total * 2.pow(unstake_thresh)
Staking::on_offline_validator(10, 4);
// // Confirm user has been reported
// Confirm user has been reported
assert_eq!(Staking::slash_count(&11), 4);
// // check the balance of 10 (slash will be deducted from free balance.)
// check the balance of 10 (slash will be deducted from free balance.)
assert_eq!(Balances::free_balance(&11), 1000 + 10 - 50 /*5% of 1000*/ * 8 /*2**3*/);
});
}
@@ -1277,8 +1234,6 @@ fn on_free_balance_zero_stash_removes_validator() {
.existential_deposit(10)
.build(),
|| {
// Check that account 10 is a validator
assert!(<Validators<Test>>::exists(11));
// Check the balance of the validator account
assert_eq!(Balances::free_balance(&10), 256);
// Check the balance of the stash account
@@ -1345,13 +1300,10 @@ fn on_free_balance_zero_stash_removes_nominator() {
assert_eq!(Balances::free_balance(&10), 256);
// Check the balance of the stash account
assert_eq!(Balances::free_balance(&11), 256000);
// Check these two accounts are bonded
assert_eq!(Staking::bonded(&11), Some(10));
// Set payee information
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Stash));
// Check storage items that should be cleaned up
assert!(<Ledger<Test>>::exists(&10));
assert!(<Bonded<Test>>::exists(&11));
@@ -1442,13 +1394,9 @@ fn phragmen_poc_works() {
.build(),
|| {
// We don't really care about this. At this point everything is even.
// assert_eq!(Session::validators(), vec![40, 30]);
assert_eq!(Staking::ledger(&10), Some(StakingLedger { stash: 11, total: 1000, active: 1000, unlocking: vec![] }));
assert_eq!(Staking::ledger(&20), Some(StakingLedger { stash: 21, total: 1000, active: 1000, unlocking: vec![] }));
assert_eq!(Staking::ledger(&30), Some(StakingLedger { stash: 31, total: 1000, active: 1000, unlocking: vec![] }));
assert_eq!(Staking::ledger(&40), Some(StakingLedger { stash: 41, total: 1000, active: 1000, unlocking: vec![] }));
assert_eq_uvec!(Session::validators(), vec![40, 30]);
// Set payees to Controller
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller));
assert_ok!(Staking::set_payee(Origin::signed(30), RewardDestination::Controller));
@@ -1471,7 +1419,7 @@ fn phragmen_poc_works() {
System::set_block_number(1);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// with stake 1666 and 1333 respectively
assert_eq!(Staking::stakers(11).own, 1000);
@@ -1491,22 +1439,55 @@ fn phragmen_poc_works() {
}
#[test]
fn phragmen_election_works_example_2() {
fn phragmen_election_works_with_post_processing() {
// tests the encapsulated phragmen::elect function.
// Votes [
// ('10', 1000, ['10']),
// ('20', 1000, ['20']),
// ('30', 1000, ['30']),
// ('2', 50, ['10', '20']),
// ('4', 1000, ['10', '30'])
// ]
// Sequential Phragmén gives
// 10 is elected with stake 1705.7377049180327 and score 0.0004878048780487805
// 30 is elected with stake 1344.2622950819673 and score 0.0007439024390243903
// 10 has load 0.0004878048780487805 and supported
// 10 with stake 1000.0
// 20 has load 0 and supported
// 20 with stake 0
// 30 has load 0.0007439024390243903 and supported
// 30 with stake 1000.0
// 2 has load 0.0004878048780487805 and supported
// 10 with stake 50.0 20 with stake 0.0
// 4 has load 0.0007439024390243903 and supported
// 10 with stake 655.7377049180328 30 with stake 344.26229508196724
// Sequential Phragmén with post processing gives
// 10 is elected with stake 1525.0 and score 0.0004878048780487805
// 30 is elected with stake 1525.0 and score 0.0007439024390243903
// 10 has load 0.0004878048780487805 and supported
// 10 with stake 1000.0
// 20 has load 0 and supported
// 20 with stake 0
// 30 has load 0.0007439024390243903 and supported
// 30 with stake 1000.0
// 2 has load 0.0004878048780487805 and supported
// 10 with stake 50.0 20 with stake 0.0
// 4 has load 0.0007439024390243903 and supported
// 10 with stake 475.0 30 with stake 525.0
with_externalities(&mut ExtBuilder::default().nominate(false).build(), || {
// initial setup of 10 and 20, both validators
assert_eq!(Session::validators(), vec![20, 10]);
// no one is a nominator
assert_eq!(<Nominators<Test>>::enumerate().count(), 0 as usize);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Bond [30, 31] as the third validator
assert_ok!(Staking::bond(Origin::signed(31), 30, 1000, RewardDestination::default()));
assert_ok!(Staking::validate(Origin::signed(30), ValidatorPrefs::default()));
// bond [2,1](A), [4,3](B), as 2 nominators
// Give all of them some balance to be able to bond properly.
for i in &[1, 3] { let _ = Balances::deposit_creating(i, 2000); }
assert_ok!(Staking::bond(Origin::signed(1), 2, 50, RewardDestination::default()));
assert_ok!(Staking::nominate(Origin::signed(2), vec![11, 21]));
@@ -1539,62 +1520,21 @@ fn phragmen_election_works_example_2() {
let winner_10 = winners.iter().filter(|w| w.who == 11).nth(0).unwrap();
let winner_30 = winners.iter().filter(|w| w.who == 31).nth(0).unwrap();
// python implementation output:
/*
Votes [
('10', 1000, ['10']),
('20', 1000, ['20']),
('30', 1000, ['30']),
('2', 50, ['10', '20']),
('4', 1000, ['10', '30'])
]
Sequential Phragmén gives
10 is elected with stake 1705.7377049180327 and score 0.0004878048780487805
30 is elected with stake 1344.2622950819673 and score 0.0007439024390243903
10 has load 0.0004878048780487805 and supported
10 with stake 1000.0
20 has load 0 and supported
20 with stake 0
30 has load 0.0007439024390243903 and supported
30 with stake 1000.0
2 has load 0.0004878048780487805 and supported
10 with stake 50.0 20 with stake 0.0
4 has load 0.0007439024390243903 and supported
10 with stake 655.7377049180328 30 with stake 344.26229508196724
Sequential Phragmén with post processing gives
10 is elected with stake 1525.0 and score 0.0004878048780487805
30 is elected with stake 1525.0 and score 0.0007439024390243903
10 has load 0.0004878048780487805 and supported
10 with stake 1000.0
20 has load 0 and supported
20 with stake 0
30 has load 0.0007439024390243903 and supported
30 with stake 1000.0
2 has load 0.0004878048780487805 and supported
10 with stake 50.0 20 with stake 0.0
4 has load 0.0007439024390243903 and supported
10 with stake 475.0 30 with stake 525.0
*/
// Check exposures
assert_eq!(winner_10.exposure.total, 1000 + 525);
assert_eq!(winner_10.score, Perquintill::from_quintillionths(487804878048780));
assert_eq!(winner_10.score, PerU128::from_max_value(165991398498018762665060784113057664));
assert_eq!(winner_10.exposure.others[0].value, 475);
assert_eq!(winner_10.exposure.others[1].value, 50);
assert_eq!(winner_30.exposure.total, 1000 + 525);
assert_eq!(winner_30.score, Perquintill::from_quintillionths(743902439024390));
assert_eq!(winner_30.score, PerU128::from_max_value(253136882709478613064217695772412937));
assert_eq!(winner_30.exposure.others[0].value, 525);
})
}
#[test]
fn switching_roles() {
// Show: It should be possible to switch between roles (nominator, validator, idle) with minimal overhead.
// Test that it should be possible to switch between roles (nominator, validator, idle) with minimal overhead.
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.sessions_per_era(3)
@@ -1603,7 +1543,7 @@ fn switching_roles() {
// Reset reward destination
for i in &[10, 20] { assert_ok!(Staking::set_payee(Origin::signed(*i), RewardDestination::Controller)); }
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// put some money in account that we'll use.
for i in 1..7 { let _ = Balances::deposit_creating(&i, 5000); }
@@ -1624,42 +1564,43 @@ fn switching_roles() {
Session::check_rotate_session(System::block_number());
// no change
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// new block
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
// no change
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// new block --> ne era --> new validators
System::set_block_number(3);
Session::check_rotate_session(System::block_number());
// with current nominators 10 and 5 have the most stake
assert_eq!(Session::validators(), vec![6, 10]);
assert_eq_uvec!(Session::validators(), vec![6, 10]);
// 2 decides to be a validator. Consequences:
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
// new stakes:
// 10: 1000 self vote
// 6: 1000 self vote
// 20: 1000 self vote + 500 vote
// 2: 2000 self vote + 500 vote.
assert_ok!(Staking::validate(Origin::signed(2), ValidatorPrefs::default()));
// 6 : 1000 self vote
// 2 : 2000 self vote + 500 vote.
// Winners: 20 and 2
System::set_block_number(4);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![6, 10]);
assert_eq_uvec!(Session::validators(), vec![6, 10]);
System::set_block_number(5);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![6, 10]);
assert_eq_uvec!(Session::validators(), vec![6, 10]);
// ne era
System::set_block_number(6);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![2, 20]);
assert_eq_uvec!(Session::validators(), vec![2, 20]);
});
}
@@ -1670,7 +1611,7 @@ fn wrong_vote_is_null() {
.validator_pool(true)
.build(),
|| {
assert_eq!(Session::validators(), vec![40, 30]);
assert_eq_uvec!(Session::validators(), vec![40, 30]);
// put some money in account that we'll use.
for i in 1..3 { let _ = Balances::deposit_creating(&i, 5000); }
@@ -1686,7 +1627,7 @@ fn wrong_vote_is_null() {
System::set_block_number(1);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
});
}
@@ -1701,13 +1642,12 @@ fn bond_with_no_staked_value() {
.build(), || {
// setup
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller));
let _ = Balances::deposit_creating(&3, 1000);
let initial_balance_2 = Balances::free_balance(&2);
let initial_balance_4 = Balances::free_balance(&4);
// initial validators
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Stingy validator.
assert_ok!(Staking::bond(Origin::signed(1), 2, 0, RewardDestination::Controller));
@@ -1717,7 +1657,7 @@ fn bond_with_no_staked_value() {
Session::check_rotate_session(System::block_number());
// Not elected even though we want 3.
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// min of 10 and 20.
assert_eq!(Staking::slot_stake(), 1000);
@@ -1726,17 +1666,17 @@ fn bond_with_no_staked_value() {
assert_ok!(Staking::bond(Origin::signed(3), 4, 500, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(4), vec![1]));
assert_eq!(Staking::ledger(4), Some(StakingLedger { stash: 3, active: 500, total: 500, unlocking: vec![]}));
// no rewards paid to 2 and 4 yet
assert_eq!(Balances::free_balance(&2), initial_balance_2);
assert_eq!(Balances::free_balance(&4), initial_balance_4);
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![20, 10, 2]);
// Stingy one is selected
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
assert_eq!(Staking::stakers(1), Exposure { own: 0, total: 500, others: vec![IndividualExposure { who: 3, value: 500}]});
// New slot stake.
assert_eq!(Staking::slot_stake(), 500);
// no rewards paid to 2 and 4 yet
@@ -1750,13 +1690,12 @@ fn bond_with_no_staked_value() {
// 2 will not get any reward
// 4 will get all the reward share
assert_eq!(Balances::free_balance(&2), initial_balance_2);
// assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward);
assert_eq!(Balances::free_balance(&4), initial_balance_4 + reward);
});
}
#[test]
fn bond_with_little_staked_value() {
fn bond_with_little_staked_value_bounded_by_slot_stake() {
// Behavior when someone bonds with little staked value.
// Particularly when she votes and the candidate is elected.
with_externalities(&mut ExtBuilder::default()
@@ -1767,11 +1706,11 @@ fn bond_with_little_staked_value() {
|| {
// setup
assert_ok!(Staking::set_payee(Origin::signed(10), RewardDestination::Controller));
assert_ok!(Staking::set_payee(Origin::signed(20), RewardDestination::Controller));
let initial_balance_2 = Balances::free_balance(&2);
let initial_balance_10 = Balances::free_balance(&10);
// initial validators
assert_eq!(Session::validators(), vec![20, 10]);
assert_eq_uvec!(Session::validators(), vec![20, 10]);
// Stingy validator.
assert_ok!(Staking::bond(Origin::signed(1), 2, 1, RewardDestination::Controller));
@@ -1782,24 +1721,25 @@ fn bond_with_little_staked_value() {
// 2 is elected.
// and fucks up the slot stake.
assert_eq!(Session::validators(), vec![20, 10, 2]);
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
assert_eq!(Staking::slot_stake(), 1);
// Old ones are rewarded.
assert_eq!(Balances::free_balance(&10), 1 + 10);
assert_eq!(Balances::free_balance(&20), 1 + 10);
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 10);
// no rewards paid to 2. This was initial election.
assert_eq!(Balances::free_balance(&2), initial_balance_2);
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![20, 10, 2]);
assert_eq_uvec!(Session::validators(), vec![20, 10, 2]);
assert_eq!(Staking::slot_stake(), 1);
let reward = Staking::current_session_reward();
// 2 will not get the full reward, practically 1
assert_eq!(Balances::free_balance(&2), initial_balance_2 + reward.max(1));
// same for 10
assert_eq!(Balances::free_balance(&10), initial_balance_10 + 10 + reward.max(1));
});
}
@@ -1838,13 +1778,13 @@ fn phragmen_linear_worse_case_equalize() {
bond_nominator(112, 1000, vec![51, 61]);
bond_nominator(114, 1000, vec![61, 71]);
assert_eq!(Session::validators(), vec![40, 30]);
assert_eq_uvec!(Session::validators(), vec![40, 30]);
assert_ok!(Staking::set_validator_count(7));
System::set_block_number(1);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![10, 60, 40, 20, 50, 30, 70]);
assert_eq_uvec!(Session::validators(), vec![10, 60, 40, 20, 50, 30, 70]);
// Sequential Phragmén with post processing gives
// 10 is elected with stake 3000.0 and score 0.00025
@@ -1866,7 +1806,7 @@ fn phragmen_linear_worse_case_equalize() {
}
#[test]
fn phragmen_chooses_correct_validators() {
fn phragmen_chooses_correct_number_of_validators() {
with_externalities(&mut ExtBuilder::default()
.nominate(true)
.validator_pool(true)
@@ -1874,8 +1814,6 @@ fn phragmen_chooses_correct_validators() {
.validator_count(1)
.build(),
|| {
// 4 validator candidates
// self vote + default account 100 is nominator.
assert_eq!(Staking::validator_count(), 1);
assert_eq!(Session::validators().len(), 1);
@@ -1886,6 +1824,34 @@ fn phragmen_chooses_correct_validators() {
})
}
#[test]
fn phragmen_score_should_be_accurate_on_large_stakes() {
with_externalities(&mut ExtBuilder::default()
.nominate(false)
.build()
, || {
let bond_validator = |a, b| {
assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller));
assert_ok!(Staking::validate(Origin::signed(a), ValidatorPrefs::default()));
};
for i in 1..=8 {
let _ = Balances::make_free_balance_be(&i, u64::max_value());
}
bond_validator(2, u64::max_value());
bond_validator(4, u64::max_value());
bond_validator(6, u64::max_value()-1);
bond_validator(8, u64::max_value()-2);
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![4, 2]);
})
}
#[test]
fn phragmen_should_not_overflow_validators() {
with_externalities(&mut ExtBuilder::default()
@@ -1900,6 +1866,10 @@ fn phragmen_should_not_overflow_validators() {
assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(a), v));
};
let check_exposure = |a| {
let expo = Staking::stakers(&a);
assert_eq!(expo.total, expo.own + expo.others.iter().map(|e| e.value).sum::<u64>());
};
for i in 1..=8 {
let _ = Balances::make_free_balance_be(&i, u64::max_value());
@@ -1917,7 +1887,9 @@ fn phragmen_should_not_overflow_validators() {
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![4, 2]);
assert_eq_uvec!(Session::validators(), vec![4, 2]);
check_exposure(4);
check_exposure(2);
})
}
@@ -1935,6 +1907,10 @@ fn phragmen_should_not_overflow_nominators() {
assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(a), v));
};
let check_exposure = |a| {
let expo = Staking::stakers(&a);
assert_eq!(expo.total, expo.own + expo.others.iter().map(|e| e.value).sum::<u64>());
};
let _ = Staking::chill(Origin::signed(10));
let _ = Staking::chill(Origin::signed(20));
@@ -1952,7 +1928,9 @@ fn phragmen_should_not_overflow_nominators() {
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![4, 2]);
assert_eq_uvec!(Session::validators(), vec![4, 2]);
check_exposure(4);
check_exposure(2);
})
}
@@ -1970,6 +1948,10 @@ fn phragmen_should_not_overflow_ultimate() {
assert_ok!(Staking::bond(Origin::signed(a-1), a, b, RewardDestination::Controller));
assert_ok!(Staking::nominate(Origin::signed(a), v));
};
let check_exposure = |a| {
let expo = Staking::stakers(&a);
assert_eq!(expo.total, expo.own + expo.others.iter().map(|e| e.value).sum::<u64>());
};
for i in 1..=8 {
let _ = Balances::make_free_balance_be(&i, u64::max_value());
@@ -1984,6 +1966,8 @@ fn phragmen_should_not_overflow_ultimate() {
System::set_block_number(2);
Session::check_rotate_session(System::block_number());
assert_eq!(Session::validators(), vec![4, 2]);
assert_eq_uvec!(Session::validators(), vec![4, 2]);
check_exposure(4);
check_exposure(2);
})
}
}
+41
View File
@@ -110,6 +110,47 @@ macro_rules! assert_ok {
}
}
/// Panic when the vectors are different, without taking the order into account.
///
/// # Examples
///
/// ```rust
/// #[macro_use]
/// # extern crate srml_support;
/// # use srml_support::{assert_eq_uvec};
/// # fn main() {
/// assert_eq_uvec!(vec![1,2], vec![2,1]);
/// # }
/// ```
///
/// ```rust,should_panic
/// #[macro_use]
/// # extern crate srml_support;
/// # use srml_support::{assert_eq_uvec};
/// # fn main() {
/// assert_eq_uvec!(vec![1,2,3], vec![2,1]);
/// # }
/// ```
#[macro_export]
#[cfg(feature = "std")]
macro_rules! assert_eq_uvec {
( $x:expr, $y:expr ) => {
$crate::__assert_eq_uvec!($x, $y);
$crate::__assert_eq_uvec!($y, $x);
}
}
#[macro_export]
#[doc(hidden)]
#[cfg(feature = "std")]
macro_rules! __assert_eq_uvec {
( $x:expr, $y:expr ) => {
$x.iter().for_each(|e| {
if !$y.contains(e) { panic!(format!("vectors not equal: {:?} != {:?}", $x, $y)); }
});
}
}
/// The void type - it cannot exist.
// Oh rust, you crack me up...
#[derive(Clone, Eq, PartialEq)]