mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-06-12 19:21:13 +00:00
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:
@@ -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 {
|
||||
|
||||
BIN
Binary file not shown.
@@ -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 = ();
|
||||
|
||||
BIN
Binary file not shown.
@@ -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)?;
|
||||
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
@@ -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);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user