diff --git a/substrate/core/sr-primitives/src/lib.rs b/substrate/core/sr-primitives/src/lib.rs index 0a8a60b254..c51b92b4d6 100644 --- a/substrate/core/sr-primitives/src/lib.rs +++ b/substrate/core/sr-primitives/src/lib.rs @@ -95,9 +95,9 @@ pub struct Permill(u32); // TODO: impl Mul for N where N: As impl Permill { - pub fn times + ::rstd::ops::Mul + ::rstd::ops::Div>(self, b: N) -> N { + pub fn times + ::rstd::ops::Mul + ::rstd::ops::Div>(self, b: N) -> N { // TODO: handle overflows - b * >::sa(self.0 as usize) / >::sa(1000000) + b * >::sa(self.0 as u64) / >::sa(1000000) } pub fn from_millionths(x: u32) -> Permill { Permill(x) } @@ -122,6 +122,61 @@ impl From for Permill { } } +/// Perbill is parts-per-billion. 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 Perbill(u32); + +// TODO: impl Mul for N where N: As +impl Perbill { + /// Attenuate `b` by self. + pub fn times + ::rstd::ops::Mul + ::rstd::ops::Div>(self, b: N) -> N { + // TODO: handle overflows + b * >::sa(self.0 as u64) / >::sa(1_000_000_000) + } + + /// Nothing. + pub fn zero() -> Perbill { Perbill(0) } + + /// `true` if this is nothing. + pub fn is_zero(&self) -> bool { self.0 == 0 } + + /// Everything. + pub fn one() -> Perbill { Perbill(1_000_000_000) } + + /// Construct new instance where `x` is in billionths. Value equivalent to `x / 1,000,000,000`. + pub fn from_billionths(x: u32) -> Perbill { Perbill(x.min(1_000_000_000)) } + + /// Construct new instance where `x` is in millionths. Value equivalent to `x / 1,000,000`. + pub fn from_millionths(x: u32) -> Perbill { Perbill(x.min(1_000_000) * 1000) } + + /// Construct new instance where `x` is a percent. Value equivalent to `x%`. + pub fn from_percent(x: u32) -> Perbill { Perbill(x.min(100) * 10_000_000) } + + #[cfg(feature = "std")] + /// Construct new instance whose value is equal to `x` (between 0 and 1). + pub fn from_fraction(x: f64) -> Perbill { Perbill((x.max(0.0).min(1.0) * 1_000_000_000.0) as u32) } + + #[cfg(feature = "std")] + /// Construct new instance whose value is equal to `n / d` (between 0 and 1). + pub fn from_rational(n: f64, d: f64) -> Perbill { Perbill(((n / d).max(0.0).min(1.0) * 1_000_000_000.0) as u32) } +} + +#[cfg(feature = "std")] +impl From for Perbill { + fn from(x: f64) -> Perbill { + Perbill::from_fraction(x) + } +} + +#[cfg(feature = "std")] +impl From for Perbill { + fn from(x: f32) -> Perbill { + Perbill::from_fraction(x as f64) + } +} + /// Ed25519 signature verify. #[derive(Eq, PartialEq, Clone, Default, Encode, Decode)] #[cfg_attr(feature = "std", derive(Debug, Serialize, Deserialize))] diff --git a/substrate/node/executor/src/lib.rs b/substrate/node/executor/src/lib.rs index 5acd4da135..25c9c45aeb 100644 --- a/substrate/node/executor/src/lib.rs +++ b/substrate/node/executor/src/lib.rs @@ -54,7 +54,7 @@ mod tests { ed25519::{Public, Pair}}; use node_primitives::{Hash, BlockNumber, AccountId}; use runtime_primitives::traits::{Header as HeaderT, Digest as DigestT}; - use runtime_primitives::{generic, generic::Era, ApplyOutcome, ApplyError, ApplyResult}; + use runtime_primitives::{generic, generic::Era, ApplyOutcome, ApplyError, ApplyResult, Perbill}; use {balances, staking, session, system, consensus, timestamp, treasury}; use system::{EventRecord, Phase}; use node_runtime::{Header, Block, UncheckedExtrinsic, CheckedExtrinsic, Call, Runtime, Balances, @@ -238,8 +238,10 @@ mod tests { validator_count: 3, minimum_validator_count: 0, bonding_duration: 0, - offline_slash: 0, - session_reward: 0, + offline_slash: Perbill::zero(), + session_reward: Perbill::zero(), + current_offline_slash: 0, + current_session_reward: 0, offline_slash_grace: 0, }), democracy: Some(Default::default()), @@ -284,9 +286,9 @@ mod tests { 1, GENESIS_HASH.into(), if support_changes_trie { - hex!("1755be7303767b4d3855694b4f0ebd9d64b7011124d0ec1ad3e17c2a0d65e245").into() + hex!("6ed28a339a836a106e244dd78a2952606169cdeef3f72e29fa61c6ab827626c1").into() } else { - hex!("1f058f699ad3187bcf7e9ed8e44464d7a5added0cd912d2679b9dab2e7a04053").into() + hex!("fc51c67ef0f8b2b91db40e6d8757e12ee670b9775479312f3d25bab66b1bec35").into() }, if support_changes_trie { Some(hex!("d7ff76d7fbb9b613e8d140da6f1d561b4928785d4e4818ed959bd1bd35abc7e8").into()) @@ -310,7 +312,7 @@ mod tests { construct_block( 2, block1(false).1, - hex!("0acf8b3c169ce8f16faf5610f646f371681dcc3b544d3dd05036dbae7890e399").into(), + hex!("4611bee90608d504e0e66773391361a1f9084adbb15732b82206f381d27d64d0").into(), None, vec![ CheckedExtrinsic { @@ -333,7 +335,7 @@ mod tests { construct_block( 1, GENESIS_HASH.into(), - hex!("fe0e07c7b054fe186387461d455d536860e9c71d6979fd9dbf755e96ce070d04").into(), + hex!("681893f1698410a9b378ff7b69c7a0d8f7cc417a274583f6217d041b84c3cbae").into(), None, vec![ CheckedExtrinsic { diff --git a/substrate/node/runtime/src/lib.rs b/substrate/node/runtime/src/lib.rs index 27e8bc6d4e..e63a904797 100644 --- a/substrate/node/runtime/src/lib.rs +++ b/substrate/node/runtime/src/lib.rs @@ -74,7 +74,7 @@ use council::{motions as council_motions, voting as council_voting}; pub use runtime_primitives::BuildStorage; pub use consensus::Call as ConsensusCall; pub use timestamp::Call as TimestampCall; -pub use runtime_primitives::Permill; +pub use runtime_primitives::{Permill, Perbill}; #[cfg(any(feature = "std", test))] pub use checked_block::CheckedBlock; diff --git a/substrate/node/service/src/chain_spec.rs b/substrate/node/service/src/chain_spec.rs index bf502cdbd1..f0806c0d3e 100644 --- a/substrate/node/service/src/chain_spec.rs +++ b/substrate/node/service/src/chain_spec.rs @@ -19,7 +19,7 @@ use primitives::{AuthorityId, ed25519}; use node_runtime::{GenesisConfig, ConsensusConfig, CouncilConfig, DemocracyConfig, SessionConfig, StakingConfig, TimestampConfig, BalancesConfig, TreasuryConfig, - ContractConfig, Permill}; + ContractConfig, Permill, Perbill}; use service::ChainSpec; const STAGING_TELEMETRY_URL: &str = "wss://telemetry.polkadot.io/submit/"; @@ -61,8 +61,10 @@ fn staging_testnet_config_genesis() -> GenesisConfig { staking: Some(StakingConfig { current_era: 0, intentions: initial_authorities.iter().cloned().map(Into::into).collect(), - offline_slash: 10000, - session_reward: 100, + offline_slash: Perbill::from_millionths(1000), + session_reward: Perbill::from_billionths(60), + current_offline_slash: 0, + current_session_reward: 0, validator_count: 12, sessions_per_era: 12, // 1 hour per era bonding_duration: 24 * 60 * 12, // 1 day per bond. @@ -156,8 +158,10 @@ fn testnet_genesis(initial_authorities: Vec) -> GenesisConfig { validator_count: 2, sessions_per_era: 5, bonding_duration: 2 * 60 * 12, - offline_slash: 0, - session_reward: 0, + offline_slash: Perbill::zero(), + session_reward: Perbill::zero(), + current_offline_slash: 0, + current_session_reward: 0, offline_slash_grace: 0, }), democracy: Some(DemocracyConfig { diff --git a/substrate/srml/staking/src/genesis_config.rs b/substrate/srml/staking/src/genesis_config.rs index ac51ccd7e2..88d6cad359 100644 --- a/substrate/srml/staking/src/genesis_config.rs +++ b/substrate/srml/staking/src/genesis_config.rs @@ -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 { 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 Default for GenesisConfig { 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 primitives::BuildStorage for GenesisConfig { Self::hash(>::key()).to_vec() => self.current_era.encode(), Self::hash(>::key()).to_vec() => self.session_reward.encode(), Self::hash(>::key()).to_vec() => self.offline_slash.encode(), + Self::hash(>::key()).to_vec() => self.current_session_reward.encode(), + Self::hash(>::key()).to_vec() => self.current_offline_slash.encode(), Self::hash(>::key()).to_vec() => self.offline_slash_grace.encode() ]) } diff --git a/substrate/srml/staking/src/lib.rs b/substrate/srml/staking/src/lib.rs index 11758e6576..35b34b77d4 100644 --- a/substrate/srml/staking/src/lib.rs +++ b/substrate/srml/staking/src/lib.rs @@ -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 ]; /// Nominators for a particular account that is in action right now. pub CurrentNominatorsFor get(current_nominators_for): default map [ T::AccountId => Vec ]; + + /// 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 Module { fn this_session_reward(actual_elapsed: T::Moment) -> T::Balance { let ideal_elapsed = >::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 Module { } Self::deposit_event(RawEvent::Reward(reward)); let total_minted = reward * >::sa(validators.len()); - let total_rewarded_stake = Self::stake_range().0 * >::sa(validators.len()); + let total_rewarded_stake = Self::stake_range().1 * >::sa(validators.len()); T::OnRewardMinted::on_dilution(total_minted, total_rewarded_stake); } @@ -485,14 +491,14 @@ impl Module { intentions.sort_unstable_by(|&(ref b1, _), &(ref b2, _)| b2.cmp(&b1)); let desired_validator_count = >::get() as usize; - >::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()) + }; + >::put(&stake_range); + let vals = &intentions.into_iter() .map(|(_, v)| v) .take(desired_validator_count) @@ -508,6 +514,10 @@ impl Module { >::insert(v, Self::nominators_for(v)); } >::set_validators(vals); + + // Update the balances for slashing/rewarding according to the stakes. + >::put(Self::offline_slash().times(stake_range.1)); + >::put(Self::session_reward().times(stake_range.1)); } } @@ -547,7 +557,7 @@ impl consensus::OnOfflineValidator for Module { 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 diff --git a/substrate/srml/staking/src/mock.rs b/substrate/srml/staking/src/mock.rs index ecad0a9e48..92152ee518 100644 --- a/substrate/srml/staking/src/mock.rs +++ b/substrate/srml/staking/src/mock.rs @@ -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::{ diff --git a/substrate/srml/staking/src/tests.rs b/substrate/srml/staking/src/tests.rs index 9fd07e02b7..1c3287edf5 100644 --- a/substrate/srml/staking/src/tests.rs +++ b/substrate/srml/staking/src/tests.rs @@ -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. }); }