mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-07-22 21:55:45 +00:00
Rewards & slashes proportional to the least-slashable validator (#781)
* Initial stuff * Treasury now mints based on lowest staked. * Tests build * Fix tests * Fix hashes * Fix tests & grumble
This commit is contained in:
@@ -22,9 +22,10 @@ use rstd::prelude::*;
|
||||
use codec::Encode;
|
||||
use runtime_support::StorageValue;
|
||||
use primitives::traits::As;
|
||||
use primitives;
|
||||
use primitives::{self, Perbill};
|
||||
use super::{Trait, Intentions, CurrentEra, OfflineSlashGrace, MinimumValidatorCount,
|
||||
BondingDuration, SessionsPerEra, ValidatorCount, SessionReward, OfflineSlash};
|
||||
BondingDuration, SessionsPerEra, ValidatorCount, SessionReward, OfflineSlash,
|
||||
CurrentSessionReward, CurrentOfflineSlash};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
@@ -36,8 +37,10 @@ pub struct GenesisConfig<T: Trait> {
|
||||
pub validator_count: u32,
|
||||
pub minimum_validator_count: u32,
|
||||
pub bonding_duration: T::BlockNumber,
|
||||
pub session_reward: T::Balance,
|
||||
pub offline_slash: T::Balance,
|
||||
pub session_reward: Perbill,
|
||||
pub offline_slash: Perbill,
|
||||
pub current_session_reward: T::Balance,
|
||||
pub current_offline_slash: T::Balance,
|
||||
pub offline_slash_grace: u32,
|
||||
}
|
||||
|
||||
@@ -50,8 +53,10 @@ impl<T: Trait> Default for GenesisConfig<T> {
|
||||
validator_count: 0,
|
||||
minimum_validator_count: 0,
|
||||
bonding_duration: T::BlockNumber::sa(1000),
|
||||
session_reward: T::Balance::sa(0),
|
||||
offline_slash: T::Balance::sa(0),
|
||||
session_reward: Perbill::from_billionths(60),
|
||||
offline_slash: Perbill::from_fraction(0.001),
|
||||
current_session_reward: T::Balance::sa(0),
|
||||
current_offline_slash: T::Balance::sa(0),
|
||||
offline_slash_grace: 0,
|
||||
}
|
||||
}
|
||||
@@ -68,6 +73,8 @@ impl<T: Trait> primitives::BuildStorage for GenesisConfig<T> {
|
||||
Self::hash(<CurrentEra<T>>::key()).to_vec() => self.current_era.encode(),
|
||||
Self::hash(<SessionReward<T>>::key()).to_vec() => self.session_reward.encode(),
|
||||
Self::hash(<OfflineSlash<T>>::key()).to_vec() => self.offline_slash.encode(),
|
||||
Self::hash(<CurrentSessionReward<T>>::key()).to_vec() => self.current_session_reward.encode(),
|
||||
Self::hash(<CurrentOfflineSlash<T>>::key()).to_vec() => self.current_offline_slash.encode(),
|
||||
Self::hash(<OfflineSlashGrace<T>>::key()).to_vec() => self.offline_slash_grace.encode()
|
||||
])
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ use rstd::cmp;
|
||||
use runtime_support::{Parameter, StorageValue, StorageMap};
|
||||
use runtime_support::dispatch::Result;
|
||||
use session::OnSessionChange;
|
||||
use primitives::traits::{Zero, One, Bounded, OnFinalise, As};
|
||||
use primitives::{Perbill, traits::{Zero, One, Bounded, OnFinalise, As}};
|
||||
use balances::{address::Address, OnDilution};
|
||||
use system::ensure_signed;
|
||||
|
||||
@@ -143,9 +143,9 @@ decl_storage! {
|
||||
/// The length of a staking era in sessions.
|
||||
pub SessionsPerEra get(sessions_per_era): required T::BlockNumber;
|
||||
/// Maximum reward, per validator, that is provided per acceptable session.
|
||||
pub SessionReward get(session_reward): required T::Balance;
|
||||
pub SessionReward get(session_reward): required Perbill;
|
||||
/// Slash, per validator that is taken for the first time they are found to be offline.
|
||||
pub OfflineSlash get(offline_slash): required T::Balance;
|
||||
pub OfflineSlash get(offline_slash): required Perbill;
|
||||
/// Number of instances of offline reports before slashing begins for validators.
|
||||
pub OfflineSlashGrace get(offline_slash_grace): default u32;
|
||||
/// The length of the bonding duration in blocks.
|
||||
@@ -163,6 +163,12 @@ decl_storage! {
|
||||
pub NominatorsFor get(nominators_for): default map [ T::AccountId => Vec<T::AccountId> ];
|
||||
/// Nominators for a particular account that is in action right now.
|
||||
pub CurrentNominatorsFor get(current_nominators_for): default map [ T::AccountId => Vec<T::AccountId> ];
|
||||
|
||||
/// Maximum reward, per validator, that is provided per acceptable session.
|
||||
pub CurrentSessionReward get(current_session_reward): default T::Balance;
|
||||
/// Slash, per validator that is taken for the first time they are found to be offline.
|
||||
pub CurrentOfflineSlash get(current_offline_slash): default T::Balance;
|
||||
|
||||
/// The next value of sessions per era.
|
||||
pub NextSessionsPerEra get(next_sessions_per_era): T::BlockNumber;
|
||||
/// The session index at which the era length last changed.
|
||||
@@ -423,7 +429,7 @@ impl<T: Trait> Module<T> {
|
||||
fn this_session_reward(actual_elapsed: T::Moment) -> T::Balance {
|
||||
let ideal_elapsed = <session::Module<T>>::ideal_session_duration();
|
||||
let per65536: u64 = (T::Moment::sa(65536u64) * ideal_elapsed.clone() / actual_elapsed.max(ideal_elapsed)).as_();
|
||||
Self::session_reward() * T::Balance::sa(per65536) / T::Balance::sa(65536u64)
|
||||
Self::current_session_reward() * T::Balance::sa(per65536) / T::Balance::sa(65536u64)
|
||||
}
|
||||
|
||||
/// Session has just changed. We need to determine whether we pay a reward, slash and/or
|
||||
@@ -438,7 +444,7 @@ impl<T: Trait> Module<T> {
|
||||
}
|
||||
Self::deposit_event(RawEvent::Reward(reward));
|
||||
let total_minted = reward * <T::Balance as As<usize>>::sa(validators.len());
|
||||
let total_rewarded_stake = Self::stake_range().0 * <T::Balance as As<usize>>::sa(validators.len());
|
||||
let total_rewarded_stake = Self::stake_range().1 * <T::Balance as As<usize>>::sa(validators.len());
|
||||
T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake);
|
||||
}
|
||||
|
||||
@@ -485,14 +491,14 @@ impl<T: Trait> Module<T> {
|
||||
intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1));
|
||||
|
||||
let desired_validator_count = <ValidatorCount<T>>::get() as usize;
|
||||
<StakeRange<T>>::put(
|
||||
if !intentions.is_empty() {
|
||||
let n = cmp::min(desired_validator_count, intentions.len());
|
||||
(intentions[0].0, intentions[n - 1].0)
|
||||
} else {
|
||||
(Zero::zero(), Zero::zero())
|
||||
}
|
||||
);
|
||||
let stake_range = if !intentions.is_empty() {
|
||||
let n = cmp::min(desired_validator_count, intentions.len());
|
||||
(intentions[0].0, intentions[n - 1].0)
|
||||
} else {
|
||||
(Zero::zero(), Zero::zero())
|
||||
};
|
||||
<StakeRange<T>>::put(&stake_range);
|
||||
|
||||
let vals = &intentions.into_iter()
|
||||
.map(|(_, v)| v)
|
||||
.take(desired_validator_count)
|
||||
@@ -508,6 +514,10 @@ impl<T: Trait> Module<T> {
|
||||
<CurrentNominatorsFor<T>>::insert(v, Self::nominators_for(v));
|
||||
}
|
||||
<session::Module<T>>::set_validators(vals);
|
||||
|
||||
// Update the balances for slashing/rewarding according to the stakes.
|
||||
<CurrentOfflineSlash<T>>::put(Self::offline_slash().times(stake_range.1));
|
||||
<CurrentSessionReward<T>>::put(Self::session_reward().times(stake_range.1));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -547,7 +557,7 @@ impl<T: Trait> consensus::OnOfflineValidator for Module<T> {
|
||||
|
||||
let event = if slash_count >= grace {
|
||||
let instances = slash_count - grace;
|
||||
let slash = Self::offline_slash() << instances;
|
||||
let slash = Self::current_offline_slash() << instances;
|
||||
let next_slash = slash << 1u32;
|
||||
let _ = Self::slash_validator(&v, slash);
|
||||
if instances >= Self::validator_preferences(&v).unstake_threshold
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#![cfg(test)]
|
||||
|
||||
use primitives::BuildStorage;
|
||||
use primitives::traits::{Identity};
|
||||
use primitives::{Perbill, traits::Identity};
|
||||
use primitives::testing::{Digest, DigestItem, Header};
|
||||
use substrate_primitives::{H256, Blake2Hasher, RlpCodec};
|
||||
use runtime_io;
|
||||
@@ -117,8 +117,10 @@ pub fn new_test_ext(
|
||||
validator_count: 2,
|
||||
minimum_validator_count: 0,
|
||||
bonding_duration: sessions_per_era * session_length * 3,
|
||||
session_reward: reward,
|
||||
offline_slash: if monied { 20 } else { 0 },
|
||||
session_reward: Perbill::from_millionths((1000000 * reward / balance_factor) as u32),
|
||||
offline_slash: if monied { Perbill::from_percent(40) } else { Perbill::zero() },
|
||||
current_session_reward: reward,
|
||||
current_offline_slash: 20,
|
||||
offline_slash_grace: 0,
|
||||
}.build_storage().unwrap());
|
||||
t.extend(timestamp::GenesisConfig::<Test>{
|
||||
|
||||
@@ -316,10 +316,10 @@ fn nominating_and_rewards_should_work() {
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Staking::current_era(), 2);
|
||||
assert_eq!(Session::validators(), vec![3, 2]);
|
||||
assert_eq!(Balances::total_balance(&1), 12);
|
||||
assert_eq!(Balances::total_balance(&1), 16);
|
||||
assert_eq!(Balances::total_balance(&2), 20);
|
||||
assert_eq!(Balances::total_balance(&3), 40);
|
||||
assert_eq!(Balances::total_balance(&4), 48);
|
||||
assert_eq!(Balances::total_balance(&3), 60);
|
||||
assert_eq!(Balances::total_balance(&4), 64);
|
||||
|
||||
System::set_block_number(3);
|
||||
assert_ok!(Staking::stake(Origin::signed(4)));
|
||||
@@ -327,17 +327,17 @@ fn nominating_and_rewards_should_work() {
|
||||
assert_ok!(Staking::nominate(Origin::signed(3), 1.into()));
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Session::validators(), vec![1, 4]);
|
||||
assert_eq!(Balances::total_balance(&1), 12);
|
||||
assert_eq!(Balances::total_balance(&2), 30);
|
||||
assert_eq!(Balances::total_balance(&3), 50);
|
||||
assert_eq!(Balances::total_balance(&4), 48);
|
||||
assert_eq!(Balances::total_balance(&1), 16);
|
||||
assert_eq!(Balances::total_balance(&2), 40);
|
||||
assert_eq!(Balances::total_balance(&3), 80);
|
||||
assert_eq!(Balances::total_balance(&4), 64);
|
||||
|
||||
System::set_block_number(4);
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Balances::total_balance(&1), 13);
|
||||
assert_eq!(Balances::total_balance(&2), 30);
|
||||
assert_eq!(Balances::total_balance(&3), 58);
|
||||
assert_eq!(Balances::total_balance(&4), 58);
|
||||
assert_eq!(Balances::total_balance(&1), 26);
|
||||
assert_eq!(Balances::total_balance(&2), 40);
|
||||
assert_eq!(Balances::total_balance(&3), 133);
|
||||
assert_eq!(Balances::total_balance(&4), 128);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -357,9 +357,9 @@ fn rewards_with_off_the_table_should_work() {
|
||||
System::set_block_number(2);
|
||||
assert_ok!(Staking::register_preferences(Origin::signed(1), Staking::intentions().into_iter().position(|i| i == 1).unwrap() as u32, ValidatorPrefs { unstake_threshold: 3, validator_payment: 4 }));
|
||||
Session::check_rotate_session(System::block_number());
|
||||
assert_eq!(Balances::total_balance(&1), 16);
|
||||
assert_eq!(Balances::total_balance(&2), 24);
|
||||
assert_eq!(Balances::total_balance(&3), 40);
|
||||
assert_eq!(Balances::total_balance(&1), 22);
|
||||
assert_eq!(Balances::total_balance(&2), 37);
|
||||
assert_eq!(Balances::total_balance(&3), 60);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -393,10 +393,11 @@ fn nominating_slashes_should_work() {
|
||||
System::set_extrinsic_index(1);
|
||||
Staking::on_offline_validator(0);
|
||||
Staking::on_offline_validator(1);
|
||||
assert_eq!(Balances::total_balance(&1), 0);
|
||||
assert_eq!(Balances::total_balance(&2), 20);
|
||||
assert_eq!(Balances::total_balance(&3), 10);
|
||||
assert_eq!(Balances::total_balance(&4), 30);
|
||||
assert_eq!(Balances::total_balance(&1), 0); //slashed
|
||||
assert_eq!(Balances::total_balance(&2), 20); //not slashed
|
||||
assert_eq!(Balances::total_balance(&3), 10); //slashed
|
||||
assert_eq!(Balances::total_balance(&4), 30); //slashed
|
||||
// TODO: change slash % to something sensible.
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user