From c8d38b8070f93f0524ea16e19a867132654f2aa6 Mon Sep 17 00:00:00 2001 From: Mazdak Farrokhzad Date: Thu, 17 Sep 2020 14:02:56 +0200 Subject: [PATCH] mesh-1255: copy-in staking/src --- pallets/runtime/develop/src/runtime.rs | 1 + pallets/runtime/testnet/src/runtime.rs | 1 + pallets/runtime/tests/src/staking/mock.rs | 1 + pallets/runtime/tests/src/staking/mod.rs | 30 +-- pallets/staking/src/benchmarking.rs | 56 ++-- pallets/staking/src/inflation.rs | 3 +- pallets/staking/src/lib.rs | 309 ++++++++++++++++------ pallets/staking/src/testing_utils.rs | 47 ++-- 8 files changed, 297 insertions(+), 151 deletions(-) diff --git a/pallets/runtime/develop/src/runtime.rs b/pallets/runtime/develop/src/runtime.rs index 9d33bc0ce0..4d4243d6ac 100644 --- a/pallets/runtime/develop/src/runtime.rs +++ b/pallets/runtime/develop/src/runtime.rs @@ -396,6 +396,7 @@ impl pallet_staking::Trait for Runtime { type MinSolutionScoreBump = MinSolutionScoreBump; type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; type UnsignedPriority = StakingUnsignedPriority; + type WeightInfo = (); type RequiredAddOrigin = frame_system::EnsureRoot; type RequiredRemoveOrigin = frame_system::EnsureRoot; type RequiredComplianceOrigin = frame_system::EnsureRoot; diff --git a/pallets/runtime/testnet/src/runtime.rs b/pallets/runtime/testnet/src/runtime.rs index 0efc8e90fd..426430a796 100644 --- a/pallets/runtime/testnet/src/runtime.rs +++ b/pallets/runtime/testnet/src/runtime.rs @@ -400,6 +400,7 @@ impl pallet_staking::Trait for Runtime { type RequiredComplianceOrigin = frame_system::EnsureRoot; type RequiredCommissionOrigin = frame_system::EnsureRoot; type RequiredChangeHistoryDepthOrigin = frame_system::EnsureRoot; + type WeightInfo = (); } parameter_types! { diff --git a/pallets/runtime/tests/src/staking/mock.rs b/pallets/runtime/tests/src/staking/mock.rs index fed684e10d..5718b4cf16 100644 --- a/pallets/runtime/tests/src/staking/mock.rs +++ b/pallets/runtime/tests/src/staking/mock.rs @@ -597,6 +597,7 @@ impl Trait for Test { type RequiredComplianceOrigin = frame_system::EnsureRoot; type RequiredCommissionOrigin = frame_system::EnsureRoot; type RequiredChangeHistoryDepthOrigin = frame_system::EnsureRoot; + type WeightInfo = (); } impl frame_system::offchain::SendTransactionTypes for Test diff --git a/pallets/runtime/tests/src/staking/mod.rs b/pallets/runtime/tests/src/staking/mod.rs index 8c6476e481..0c5538bf30 100644 --- a/pallets/runtime/tests/src/staking/mod.rs +++ b/pallets/runtime/tests/src/staking/mod.rs @@ -3697,7 +3697,7 @@ mod offchain_phragmen { current_era(), ElectionSize::default(), ), - Error::::PhragmenEarlySubmission, + Error::::OffchainElectionEarlySubmission, Some(::DbWeight::get().reads(1)), ); }) @@ -3723,7 +3723,7 @@ mod offchain_phragmen { let (compact, winners, score) = horrible_phragmen_with_post_processing(false); assert_err_with_weight!( submit_solution(Origin::signed(10), winners.clone(), compact.clone(), score,), - Error::::PhragmenWeakSubmission, + Error::::OffchainElectionWeakSubmission, Some(::DbWeight::get().reads(3)) ); }) @@ -3894,7 +3894,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusWinnerCount, + Error::::OffchainElectionBogusWinnerCount, ); }) } @@ -3918,7 +3918,7 @@ mod offchain_phragmen { current_era(), ElectionSize::default(), ), - Error::::PhragmenBogusElectionSize, + Error::::OffchainElectionBogusElectionSize, ); }) } @@ -3943,7 +3943,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusWinnerCount, + Error::::OffchainElectionBogusWinnerCount, ); }) } @@ -3991,7 +3991,7 @@ mod offchain_phragmen { // The error type sadly cannot be more specific now. assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusCompact, + Error::::OffchainElectionBogusCompact, ); }) } @@ -4018,7 +4018,7 @@ mod offchain_phragmen { // The error type sadly cannot be more specific now. assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusCompact, + Error::::OffchainElectionBogusCompact, ); }) } @@ -4044,7 +4044,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusWinner, + Error::::OffchainElectionBogusWinner, ); }) } @@ -4074,7 +4074,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusEdge, + Error::::OffchainElectionBogusEdge, ); }) } @@ -4104,7 +4104,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusSelfVote, + Error::::OffchainElectionBogusSelfVote, ); }) } @@ -4134,7 +4134,7 @@ mod offchain_phragmen { // This raises score issue. assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusSelfVote, + Error::::OffchainElectionBogusSelfVote, ); }) } @@ -4163,7 +4163,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusCompact, + Error::::OffchainElectionBogusCompact, ); }) } @@ -4199,7 +4199,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusNomination, + Error::::OffchainElectionBogusNomination, ); }) } @@ -4264,7 +4264,7 @@ mod offchain_phragmen { // is rejected. assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenSlashedNomination, + Error::::OffchainElectionSlashedNomination, ); }) } @@ -4286,7 +4286,7 @@ mod offchain_phragmen { assert_noop!( submit_solution(Origin::signed(10), winners, compact, score,), - Error::::PhragmenBogusScore, + Error::::OffchainElectionBogusScore, ); }) } diff --git a/pallets/staking/src/benchmarking.rs b/pallets/staking/src/benchmarking.rs index 5ea0cf9ca4..b095cdc0e4 100644 --- a/pallets/staking/src/benchmarking.rs +++ b/pallets/staking/src/benchmarking.rs @@ -21,7 +21,7 @@ use super::*; use crate::Module as Staking; use testing_utils::*; -pub use frame_benchmarking::{account, benchmarks}; +pub use frame_benchmarking::{account, benchmarks, whitelisted_caller}; use frame_system::RawOrigin; use sp_runtime::traits::One; const SEED: u32 = 0; @@ -57,16 +57,11 @@ pub fn create_validator_with_nominators( let mut points_total = 0; let mut points_individual = Vec::new(); - MinimumValidatorCount::put(0); - let (v_stash, v_controller) = create_stash_controller::(0, 100)?; let validator_prefs = ValidatorPrefs { commission: Perbill::from_percent(50), }; - Staking::::validate( - RawOrigin::Signed(v_controller.clone()).into(), - validator_prefs, - )?; + Staking::::validate(RawOrigin::Signed(v_controller).into(), validator_prefs)?; let stash_lookup: ::Source = T::Lookup::unlookup(v_stash.clone()); points_total += 10; @@ -104,7 +99,7 @@ pub fn create_validator_with_nominators( ErasRewardPoints::::insert(current_era, reward); // Create reward pool - let total_payout = get_minimum_balance::() * 1000.into(); + let total_payout = T::Currency::minimum_balance() * 1000.into(); >::insert(current_era, total_payout); Ok(v_stash) @@ -122,7 +117,7 @@ benchmarks! { let controller = create_funded_user::("controller", u, 100); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); let reward_destination = RewardDestination::Staked; - let amount = get_minimum_balance::() * 10.into(); + let amount = T::Currency::minimum_balance() * 10.into(); }: _(RawOrigin::Signed(stash.clone()), controller_lookup, amount, reward_destination) verify { assert!(Bonded::::contains_key(stash)); @@ -132,7 +127,7 @@ benchmarks! { bond_extra { let u in ...; let (stash, controller) = create_stash_controller::(u, 100)?; - let max_additional = get_minimum_balance::() * 10.into(); + let max_additional = T::Currency::minimum_balance() * 10.into(); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; }: _(RawOrigin::Signed(stash), max_additional) @@ -145,7 +140,7 @@ benchmarks! { unbond { let u in ...; let (_, controller) = create_stash_controller::(u, 100)?; - let amount = get_minimum_balance::() * 10.into(); + let amount = T::Currency::minimum_balance() * 10.into(); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; let original_bonded: BalanceOf = ledger.active; }: _(RawOrigin::Signed(controller.clone()), amount) @@ -161,7 +156,7 @@ benchmarks! { let s in 0 .. MAX_SPANS; let (stash, controller) = create_stash_controller::(0, 100)?; add_slashing_spans::(&stash, s); - let amount = get_minimum_balance::() * 5.into(); // Half of total + let amount = T::Currency::minimum_balance() * 5.into(); // Half of total Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; CurrentEra::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; @@ -179,7 +174,7 @@ benchmarks! { let s in 0 .. MAX_SPANS; let (stash, controller) = create_stash_controller::(0, 100)?; add_slashing_spans::(&stash, s); - let amount = get_minimum_balance::() * 10.into(); + let amount = T::Currency::minimum_balance() * 10.into(); Staking::::unbond(RawOrigin::Signed(controller.clone()).into(), amount)?; CurrentEra::put(EraIndex::max_value()); let ledger = Ledger::::get(&controller).ok_or("ledger not created before")?; @@ -290,7 +285,7 @@ benchmarks! { let validator = create_validator_with_nominators::(n, T::MaxNominatorRewardedPerValidator::get() as u32, true)?; let current_era = CurrentEra::get().unwrap(); - let caller = account("caller", 0, SEED); + let caller = whitelisted_caller(); let balance_before = T::Currency::free_balance(&validator); }: _(RawOrigin::Signed(caller), validator.clone(), current_era) verify { @@ -304,7 +299,7 @@ benchmarks! { let validator = create_validator_with_nominators::(n, T::MaxNominatorRewardedPerValidator::get() as u32, false)?; let current_era = CurrentEra::get().unwrap(); - let caller = account("caller", 0, SEED); + let caller = whitelisted_caller(); let balance_before = T::Currency::free_balance(&validator); }: payout_stakers(RawOrigin::Signed(caller), validator.clone(), current_era) verify { @@ -364,7 +359,7 @@ benchmarks! { new_era { let v in 1 .. 10; let n in 1 .. 100; - MinimumValidatorCount::put(0); + create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; let session_index = SessionIndex::one(); }: { @@ -383,8 +378,8 @@ benchmarks! { for _ in 0 .. l { staking_ledger.unlocking.push(unlock_chunk.clone()) } - Ledger::::insert(controller.clone(), staking_ledger.clone()); - let slash_amount = get_minimum_balance::() * 10.into(); + Ledger::::insert(controller, staking_ledger); + let slash_amount = T::Currency::minimum_balance() * 10.into(); let balance_before = T::Currency::free_balance(&stash); }: { crate::slashing::do_slash::( @@ -401,7 +396,6 @@ benchmarks! { payout_all { let v in 1 .. 10; let n in 1 .. 100; - MinimumValidatorCount::put(0); create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; // Start a new Era let new_validators = Staking::::new_era(SessionIndex::one()).unwrap(); @@ -427,10 +421,10 @@ benchmarks! { ErasRewardPoints::::insert(current_era, reward); // Create reward pool - let total_payout = get_minimum_balance::() * 1000.into(); + let total_payout = T::Currency::minimum_balance() * 1000.into(); >::insert(current_era, total_payout); - let caller: T::AccountId = account("caller", 0, SEED); + let caller: T::AccountId = whitelisted_caller(); }: { for arg in payout_calls_arg { >::payout_stakers(RawOrigin::Signed(caller.clone()).into(), arg.0, arg.1)?; @@ -482,6 +476,10 @@ benchmarks! { let era = >::current_era().unwrap_or(0); let caller: T::AccountId = account("caller", n, SEED); + + // Whitelist caller account from further DB operations. + let caller_key = frame_system::Account::::hashed_key_for(&caller); + frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); }: { let result = >::submit_election_solution( RawOrigin::Signed(caller.clone()).into(), @@ -543,6 +541,10 @@ benchmarks! { let era = >::current_era().unwrap_or(0); let caller: T::AccountId = account("caller", n, SEED); + // Whitelist caller account from further DB operations. + let caller_key = frame_system::Account::::hashed_key_for(&caller); + frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + // submit a very bad solution on-chain { // this is needed to fool the chain to accept this solution. @@ -585,7 +587,6 @@ benchmarks! { // number of nominator intent let n in 1000 .. 2000; - MinimumValidatorCount::put(0); create_validators_with_nominators_for_era::(v, n, MAX_NOMINATIONS, false, None)?; // needed for the solution to be generates. @@ -596,6 +597,10 @@ benchmarks! { let caller: T::AccountId = account("caller", n, SEED); let era = >::current_era().unwrap_or(0); + // Whitelist caller account from further DB operations. + let caller_key = frame_system::Account::::hashed_key_for(&caller); + frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into()); + // submit a seq-phragmen with all the good stuff on chain. { let (winners, compact, score, size) = get_seq_phragmen_solution::(true); @@ -637,7 +642,6 @@ mod tests { use frame_support::assert_ok; #[test] - #[ignore = "Will fix when benchmarks get fixed"] fn create_validators_with_nominators_for_era_works() { ExtBuilder::default() .has_stakers(false) @@ -664,7 +668,6 @@ mod tests { } #[test] - #[ignore = "Will fix when benchmarks get fixed"] fn create_validator_with_nominators_works() { ExtBuilder::default() .has_stakers(false) @@ -694,7 +697,6 @@ mod tests { } #[test] - #[ignore = "Will fix when benchmarks get fixed"] fn add_slashing_spans_works() { ExtBuilder::default() .has_stakers(false) @@ -732,7 +734,6 @@ mod tests { } #[test] - #[ignore = "Will fix when benchmarks get fixed"] fn test_payout_all() { ExtBuilder::default() .has_stakers(false) @@ -758,7 +759,6 @@ mod tests { } #[test] - #[ignore = "Will fix when benchmarks get fixed"] fn test_benchmarks() { ExtBuilder::default() .has_stakers(false) @@ -795,7 +795,7 @@ mod tests { } #[test] - #[ignore = "Will fix when benchmarks get fixed"] + #[ignore] fn test_benchmarks_offchain() { ExtBuilder::default() .has_stakers(false) diff --git a/pallets/staking/src/inflation.rs b/pallets/staking/src/inflation.rs index 64d38eea2f..11acb50564 100644 --- a/pallets/staking/src/inflation.rs +++ b/pallets/staking/src/inflation.rs @@ -20,7 +20,6 @@ //! The staking rate in NPoS is the total amount of tokens staked by nominators and validators, //! divided by the total token supply. -use sp_arithmetic::traits::Unsigned; use sp_runtime::{curve::PiecewiseLinear, traits::AtLeast32BitUnsigned, Perbill}; /// The total payout to all validators (and their nominators) per era and maximum payout. @@ -37,7 +36,7 @@ pub fn compute_total_payout( era_duration: u64, ) -> (N, N) where - N: AtLeast32BitUnsigned + Unsigned + Clone, + N: AtLeast32BitUnsigned + Clone, { // Milliseconds per year for the Julian year (365.25 days). const MILLISECONDS_PER_YEAR: u64 = 1000 * 3600 * 24 * 36525 / 100; diff --git a/pallets/staking/src/lib.rs b/pallets/staking/src/lib.rs index 3b82512b85..a7a5dec075 100644 --- a/pallets/staking/src/lib.rs +++ b/pallets/staking/src/lib.rs @@ -166,7 +166,7 @@ //! //! ``` //! use frame_support::{decl_module, dispatch}; -//! use frame_system::{self as system, ensure_signed}; +//! use frame_system::ensure_signed; //! use pallet_staking::{self as staking}; //! //! pub trait Trait: staking::Trait {} @@ -316,7 +316,7 @@ use frame_system::{ use pallet_identity as identity; use pallet_session::historical; use polymesh_common_utilities::{identity::Trait as IdentityTrait, Context}; -use polymesh_primitives::{traits::BlockRewardsReserveCurrency, IdentityId}; +use polymesh_primitives::IdentityId; use sp_npos_elections::{ build_support_map, evaluate_support, generate_solution_type, is_score_better, seq_phragmen, Assignment, ElectionResult as PrimitiveElectionResult, ElectionScore, ExtendedBalance, @@ -348,7 +348,6 @@ use sp_std::{ result, }; -const DEFAULT_MINIMUM_VALIDATOR_COUNT: u32 = 4; const STAKING_ID: LockIdentifier = *b"staking "; pub const MAX_UNLOCKING_CHUNKS: usize = 32; pub const MAX_NOMINATIONS: usize = ::LIMIT; @@ -392,18 +391,6 @@ generate_solution_type!( pub struct CompactAssignments::(16) ); -/// Information regarding the active era (era in used in session). -#[derive(Encode, Decode, RuntimeDebug)] -pub struct ActiveEraInfo { - /// Index of era. - pub index: EraIndex, - /// Moment of start expressed as millisecond from `$UNIX_EPOCH`. - /// - /// Start can be none if start hasn't been set for the era yet, - /// Start is set on the first on_finalize of the era to guarantee usage of `Time`. - start: Option, -} - /// Accuracy used for on-chain election. pub type ChainAccuracy = Perbill; @@ -436,6 +423,18 @@ type PositiveImbalanceOf = pub type NegativeImbalanceOf = <::Currency as Currency<::AccountId>>::NegativeImbalance; +/// Information regarding the active era (era in used in session). +#[derive(Encode, Decode, RuntimeDebug)] +pub struct ActiveEraInfo { + /// Index of era. + pub index: EraIndex, + /// Moment of start expressed as millisecond from `$UNIX_EPOCH`. + /// + /// Start can be none if start hasn't been set for the era yet, + /// Start is set on the first on_finalize of the era to guarantee usage of `Time`. + start: Option, +} + /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. @@ -650,7 +649,10 @@ pub struct Nominations { /// /// Except for initial nominations which are considered submitted at era 0. pub submitted_in: EraIndex, - /// Whether the nominations have been suppressed. + /// Whether the nominations have been suppressed. This can happen due to slashing of the + /// validators, or other events that might invalidate the nomination. + /// + /// NOTE: this for future proofing and is thus far not used. pub suppressed: bool, } @@ -690,7 +692,7 @@ pub struct UnappliedSlash { /// Reporters of the offence; bounty payout recipients. reporters: Vec, /// The amount of payout. - payout: Balance, + pub payout: Balance, } /// Indicate how an election round was computed. @@ -873,12 +875,129 @@ pub mod weight { } } +pub trait WeightInfo { + fn bond(u: u32) -> Weight; + fn bond_extra(u: u32) -> Weight; + fn unbond(u: u32) -> Weight; + fn withdraw_unbonded_update(s: u32) -> Weight; + fn withdraw_unbonded_kill(s: u32) -> Weight; + fn validate(u: u32) -> Weight; + fn nominate(n: u32) -> Weight; + fn chill(u: u32) -> Weight; + fn set_payee(u: u32) -> Weight; + fn set_controller(u: u32) -> Weight; + fn set_validator_count(c: u32) -> Weight; + fn force_no_eras(i: u32) -> Weight; + fn force_new_era(i: u32) -> Weight; + fn force_new_era_always(i: u32) -> Weight; + fn set_invulnerables(v: u32) -> Weight; + fn force_unstake(s: u32) -> Weight; + fn cancel_deferred_slash(s: u32) -> Weight; + fn payout_stakers(n: u32) -> Weight; + fn payout_stakers_alive_controller(n: u32) -> Weight; + fn rebond(l: u32) -> Weight; + fn set_history_depth(e: u32) -> Weight; + fn reap_stash(s: u32) -> Weight; + fn new_era(v: u32, n: u32) -> Weight; + fn do_slash(l: u32) -> Weight; + fn payout_all(v: u32, n: u32) -> Weight; + fn submit_solution_initial(v: u32, n: u32, a: u32, w: u32) -> Weight; + fn submit_solution_better(v: u32, n: u32, a: u32, w: u32) -> Weight; + fn submit_solution_weaker(v: u32, n: u32) -> Weight; +} + +impl WeightInfo for () { + fn bond(_u: u32) -> Weight { + 1_000_000_000 + } + fn bond_extra(_u: u32) -> Weight { + 1_000_000_000 + } + fn unbond(_u: u32) -> Weight { + 1_000_000_000 + } + fn withdraw_unbonded_update(_s: u32) -> Weight { + 1_000_000_000 + } + fn withdraw_unbonded_kill(_s: u32) -> Weight { + 1_000_000_000 + } + fn validate(_u: u32) -> Weight { + 1_000_000_000 + } + fn nominate(_n: u32) -> Weight { + 1_000_000_000 + } + fn chill(_u: u32) -> Weight { + 1_000_000_000 + } + fn set_payee(_u: u32) -> Weight { + 1_000_000_000 + } + fn set_controller(_u: u32) -> Weight { + 1_000_000_000 + } + fn set_validator_count(_c: u32) -> Weight { + 1_000_000_000 + } + fn force_no_eras(_i: u32) -> Weight { + 1_000_000_000 + } + fn force_new_era(_i: u32) -> Weight { + 1_000_000_000 + } + fn force_new_era_always(_i: u32) -> Weight { + 1_000_000_000 + } + fn set_invulnerables(_v: u32) -> Weight { + 1_000_000_000 + } + fn force_unstake(_s: u32) -> Weight { + 1_000_000_000 + } + fn cancel_deferred_slash(_s: u32) -> Weight { + 1_000_000_000 + } + fn payout_stakers(_n: u32) -> Weight { + 1_000_000_000 + } + fn payout_stakers_alive_controller(_n: u32) -> Weight { + 1_000_000_000 + } + fn rebond(_l: u32) -> Weight { + 1_000_000_000 + } + fn set_history_depth(_e: u32) -> Weight { + 1_000_000_000 + } + fn reap_stash(_s: u32) -> Weight { + 1_000_000_000 + } + fn new_era(_v: u32, _n: u32) -> Weight { + 1_000_000_000 + } + fn do_slash(_l: u32) -> Weight { + 1_000_000_000 + } + fn payout_all(_v: u32, _n: u32) -> Weight { + 1_000_000_000 + } + fn submit_solution_initial(_v: u32, _n: u32, _a: u32, _w: u32) -> Weight { + 1_000_000_000 + } + fn submit_solution_better(_v: u32, _n: u32, _a: u32, _w: u32) -> Weight { + 1_000_000_000 + } + fn submit_solution_weaker(_v: u32, _n: u32) -> Weight { + 1_000_000_000 + } +} + pub trait Trait: frame_system::Trait + SendTransactionTypes> + pallet_babe::Trait + IdentityTrait { /// The staking balance. - type Currency: LockableCurrency - + BlockRewardsReserveCurrency, NegativeImbalanceOf>; + type Currency: LockableCurrency; /// Time used for computing era duration. /// @@ -963,6 +1082,9 @@ pub trait Trait: /// multiple pallets send unsigned transactions. type UnsignedPriority: Get; + /// Weight information for extrinsics in this pallet. + type WeightInfo: WeightInfo; + /// Required origin for adding a potential validator (can always be Root). type RequiredAddOrigin: EnsureOrigin; @@ -999,6 +1121,23 @@ impl Default for Forcing { } } +// A value placed in storage that represents the current version of the Staking storage. This value +// is used by the `on_runtime_upgrade` logic to determine whether we run storage migration logic. +// This should match directly with the semantic versions of the Rust crate. +#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug)] +enum Releases { + V1_0_0Ancient, + V2_0_0, + V3_0_0, + V4_0_0, +} + +impl Default for Releases { + fn default() -> Self { + Releases::V4_0_0 + } +} + decl_storage! { trait Store for Module as Staking { /// Number of eras to keep in history. @@ -1014,8 +1153,7 @@ decl_storage! { pub ValidatorCount get(fn validator_count) config(): u32; /// Minimum number of staking participants before emergency conditions are imposed. - pub MinimumValidatorCount get(fn minimum_validator_count) config(): - u32 = DEFAULT_MINIMUM_VALIDATOR_COUNT; + pub MinimumValidatorCount get(fn minimum_validator_count) config(): u32; /// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're /// easy to initialize and the performance hit is minimal (we expect no more than four @@ -1186,6 +1324,12 @@ decl_storage! { /// The minimum amount with which a validator can bond. pub MinimumBondThreshold get(fn min_bond_threshold) config(): BalanceOf; + + /// True if network has been upgraded to this version. + /// Storage version of the pallet. + /// + /// This is set to v3.0.0 for new networks. + StorageVersion build(|_: &GenesisConfig| Releases::V4_0_0): Releases; } add_extra_genesis { config(stakers): @@ -1233,36 +1377,36 @@ decl_event!( pub enum Event where Balance = BalanceOf, ::AccountId { /// The era payout has been set; the first balance is the validator-payout; the second is /// the remainder from the maximum amount of reward. + /// [era_index, validator_payout, remainder] EraPayout(EraIndex, Balance, Balance), - /// The staker has been rewarded by this amount. `AccountId` is the stash account. + /// The staker has been rewarded by this amount. [stash, amount] Reward(AccountId, Balance), /// One validator (and its nominators) has been slashed by the given amount. + /// [validator, amount] Slash(AccountId, Balance), /// An old slashing report from a prior era was discarded because it could - /// not be processed. + /// not be processed. [session_index] OldSlashingReportDiscarded(SessionIndex), - /// A new set of stakers was elected with the given computation method. + /// A new set of stakers was elected with the given [compute]. StakingElection(ElectionCompute), - /// A new solution for the upcoming election has been stored. + /// A new solution for the upcoming election has been stored. [compute] SolutionStored(ElectionCompute), - /// An account has bonded this amount. + /// An account has bonded this amount. [did, stash, amount] /// /// NOTE: This event is only emitted when funds are bonded via a dispatchable. Notably, /// it will not be emitted for staking rewards when they are added to stake. Bonded(IdentityId, AccountId, Balance), - /// User has unbonded their funds + /// An account has unbonded this amount. [did, stash, amount] Unbonded(IdentityId, AccountId, Balance), /// User has updated their nominations Nominated(IdentityId, AccountId, Vec), /// An account has called `withdraw_unbonded` and removed unbonding chunks worth `Balance` - /// from the unlocking queue. + /// from the unlocking queue. [stash, amount] Withdrawn(AccountId, Balance), /// An entity has issued a candidacy. See the transaction for who. PermissionedValidatorAdded(Option, AccountId), /// The given member was removed. See the transaction for who. PermissionedValidatorRemoved(Option, AccountId), - /// The given member was removed. See the transaction for who. - PermissionedValidatorStatusChanged(IdentityId, AccountId), /// Remove the nominators from the valid nominators when there CDD expired. /// Caller, Stash accountId of nominators InvalidatedNominators(IdentityId, AccountId, Vec), @@ -1310,34 +1454,34 @@ decl_error! { /// Rewards for this era have already been claimed for this validator. AlreadyClaimed, /// The submitted result is received out of the open window. - PhragmenEarlySubmission, + OffchainElectionEarlySubmission, /// The submitted result is not as good as the one stored on chain. - PhragmenWeakSubmission, + OffchainElectionWeakSubmission, /// The snapshot data of the current window is missing. SnapshotUnavailable, /// Incorrect number of winners were presented. - PhragmenBogusWinnerCount, + OffchainElectionBogusWinnerCount, /// One of the submitted winners is not an active candidate on chain (index is out of range /// in snapshot). - PhragmenBogusWinner, + OffchainElectionBogusWinner, /// Error while building the assignment type from the compact. This can happen if an index /// is invalid, or if the weights _overflow_. - PhragmenBogusCompact, + OffchainElectionBogusCompact, /// One of the submitted nominators is not an active nominator on chain. - PhragmenBogusNominator, + OffchainElectionBogusNominator, /// One of the submitted nominators has an edge to which they have not voted on chain. - PhragmenBogusNomination, + OffchainElectionBogusNomination, /// One of the submitted nominators has an edge which is submitted before the last non-zero /// slash of the target. - PhragmenSlashedNomination, + OffchainElectionSlashedNomination, /// A self vote must only be originated from a validator to ONLY themselves. - PhragmenBogusSelfVote, + OffchainElectionBogusSelfVote, /// The submitted result has unknown edges that are not among the presented winners. - PhragmenBogusEdge, + OffchainElectionBogusEdge, /// The claimed score does not match with the one computed from the data. - PhragmenBogusScore, + OffchainElectionBogusScore, /// The election size is invalid. - PhragmenBogusElectionSize, + OffchainElectionBogusElectionSize, /// The call is not allowed at the given time due to restrictions of election period. CallNotAllowed, /// Incorrect previous history depth input provided. @@ -1477,6 +1621,17 @@ decl_module! { // `on_finalize` weight is tracked in `on_initialize` } + fn integrity_test() { + sp_io::TestExternalities::new_empty().execute_with(|| + assert!( + T::SlashDeferDuration::get() < T::BondingDuration::get() || T::BondingDuration::get() == 0, + "As per documentation, slash defer duration ({}) should be less than bonding duration ({}).", + T::SlashDeferDuration::get(), + T::BondingDuration::get(), + ) + ); + } + /// Take the origin account as a stash and lock up `value` of its balance. `controller` will /// be the account that controls it. /// @@ -1510,11 +1665,9 @@ decl_module! { payee: RewardDestination, ) { let stash = ensure_signed(origin)?; - ensure!(!>::contains_key(&stash), Error::::AlreadyBonded); let controller = T::Lookup::lookup(controller)?; - ensure!(!>::contains_key(&controller), Error::::AlreadyPaired); // Reject a bond which is considered to be _dust_. @@ -1787,13 +1940,11 @@ decl_module! { if let Some(nominate_identity) = >::get_identity(stash) { let leeway = Self::get_bonding_duration_period() as u32; - let is_cdded = >::fetch_cdd(nominate_identity, leeway.into()).is_some(); - - if is_cdded { + if >::fetch_cdd(nominate_identity, leeway.into()).is_some() { let targets = targets.into_iter() - .take(MAX_NOMINATIONS) - .map(T::Lookup::lookup) - .collect::, _>>()?; + .take(MAX_NOMINATIONS) + .map(T::Lookup::lookup) + .collect::, _>>()?; let nominations = Nominations { targets: targets.clone(), @@ -1803,7 +1954,7 @@ decl_module! { }; >::remove(stash); - >::insert(stash.clone(), &nominations); + >::insert(stash, &nominations); Self::deposit_event(RawEvent::Nominated(nominate_identity, stash.clone(), targets)); } } @@ -1880,7 +2031,6 @@ decl_module! { let old_controller = Self::bonded(&stash).ok_or(Error::::NotStash)?; let controller = T::Lookup::lookup(controller)?; ensure!(!>::contains_key(&controller), Error::::AlreadyPaired); - if controller != old_controller { >::insert(&stash, &controller); if let Some(l) = >::take(&old_controller) { @@ -2168,7 +2318,7 @@ decl_module! { /// Cancel enactment of a deferred slash. /// - /// Can be called by either the root origin or the `T::SlashCancelOrigin`. + /// Can be called by the `T::SlashCancelOrigin`. /// /// Parameters: era and indices of the slashes for that era to kill. /// @@ -2269,11 +2419,8 @@ decl_module! { let ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; ensure!(!ledger.unlocking.is_empty(), Error::::NoUnlockChunk); - let initial_bonded = ledger.active; let ledger = ledger.rebond(value); Self::update_ledger(&controller, &ledger); - let did = Context::current_identity::().unwrap_or_default(); - Self::deposit_event(RawEvent::Bonded(did, ledger.stash.clone(), ledger.active - initial_bonded)); Ok(Some( 35 * WEIGHT_PER_MICROS + 50 * WEIGHT_PER_NANOS * (ledger.unlocking.len() as Weight) @@ -2648,7 +2795,7 @@ impl Module { } // Lets now calculate how this is split to the nominators. - // Sort nominators by highest to lowest exposure, but only keep `max_nominator_payouts` of them. + // Reward only the clipped exposures. Note this is not necessarily sorted. for nominator in exposure.others.iter() { let nominator_exposure_part = Perbill::from_rational_approximation(nominator.value, exposure.total); @@ -2664,8 +2811,9 @@ impl Module { Ok(()) } - /// Update the ledger for a controller. This will also update the stash lock. The lock will - /// will lock the entire funds except paying for further transactions. + /// Update the ledger for a controller. + /// + /// This will also update the stash lock. fn update_ledger( controller: &T::AccountId, ledger: &StakingLedger>, @@ -2754,14 +2902,15 @@ impl Module { // check window open ensure!( Self::era_election_status().is_open(), - Error::::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(1)), + Error::::OffchainElectionEarlySubmission.with_weight(T::DbWeight::get().reads(1)), ); // check current era. if let Some(current_era) = Self::current_era() { ensure!( current_era == era, - Error::::PhragmenEarlySubmission.with_weight(T::DbWeight::get().reads(2)), + Error::::OffchainElectionEarlySubmission + .with_weight(T::DbWeight::get().reads(2)), ) } @@ -2769,7 +2918,7 @@ impl Module { if let Some(queued_score) = Self::queued_score() { ensure!( is_score_better(score, queued_score, T::MinSolutionScoreBump::get()), - Error::::PhragmenWeakSubmission.with_weight(T::DbWeight::get().reads(3)), + Error::::OffchainElectionWeakSubmission.with_weight(T::DbWeight::get().reads(3)), ) } @@ -2806,7 +2955,7 @@ impl Module { // size of the solution must be correct. ensure!( snapshot_validators_length == u32::from(election_size.validators), - Error::::PhragmenBogusElectionSize, + Error::::OffchainElectionBogusElectionSize, ); // check the winner length only here and when we know the length of the snapshot validators @@ -2814,7 +2963,7 @@ impl Module { let desired_winners = Self::validator_count().min(snapshot_validators_length); ensure!( winners.len() as u32 == desired_winners, - Error::::PhragmenBogusWinnerCount + Error::::OffchainElectionBogusWinnerCount ); let snapshot_nominators_len = >::decode_len() @@ -2824,7 +2973,7 @@ impl Module { // rest of the size of the solution must be correct. ensure!( snapshot_nominators_len == election_size.nominators, - Error::::PhragmenBogusElectionSize, + Error::::OffchainElectionBogusElectionSize, ); // decode snapshot validators. @@ -2841,7 +2990,7 @@ impl Module { snapshot_validators .get(widx as usize) .cloned() - .ok_or(Error::::PhragmenBogusWinner) + .ok_or(Error::::OffchainElectionBogusWinner) }) .collect::, Error>>()?; @@ -2863,7 +3012,7 @@ impl Module { .map_err(|e| { // log the error since it is not propagated into the runtime error. log!(warn, "💸 un-compacting solution failed due to {:?}", e); - Error::::PhragmenBogusCompact + Error::::OffchainElectionBogusCompact })?; // check all nominators actually including the claimed vote. Also check correct self votes. @@ -2882,7 +3031,7 @@ impl Module { "💸 detected an error in the staking locking and snapshot." ); // abort. - return Err(Error::::PhragmenBogusNominator.into()); + return Err(Error::::OffchainElectionBogusNominator.into()); } if !is_validator { @@ -2899,24 +3048,30 @@ impl Module { // each target in the provided distribution must be actually nominated by the // nominator after the last non-zero slash. if nomination.targets.iter().find(|&tt| tt == t).is_none() { - return Err(Error::::PhragmenBogusNomination.into()); + return Err(Error::::OffchainElectionBogusNomination.into()); } if ::SlashingSpans::get(&t).map_or(false, |spans| { nomination.submitted_in < spans.last_nonzero_slash() }) { - return Err(Error::::PhragmenSlashedNomination.into()); + return Err(Error::::OffchainElectionSlashedNomination.into()); } } } else { // a self vote - ensure!(distribution.len() == 1, Error::::PhragmenBogusSelfVote); - ensure!(distribution[0].0 == *who, Error::::PhragmenBogusSelfVote); + ensure!( + distribution.len() == 1, + Error::::OffchainElectionBogusSelfVote + ); + ensure!( + distribution[0].0 == *who, + Error::::OffchainElectionBogusSelfVote + ); // defensive only. A compact assignment of length one does NOT encode the weight and // it is always created to be 100%. ensure!( distribution[0].1 == OffchainAccuracy::one(), - Error::::PhragmenBogusSelfVote, + Error::::OffchainElectionBogusSelfVote, ); } } @@ -2933,13 +3088,13 @@ impl Module { let (supports, num_error) = build_support_map::(&winners, &staked_assignments); // This technically checks that all targets in all nominators were among the winners. - ensure!(num_error == 0, Error::::PhragmenBogusEdge); + ensure!(num_error == 0, Error::::OffchainElectionBogusEdge); // Check if the score is the same as the claimed one. let submitted_score = evaluate_support(&supports); ensure!( submitted_score == claimed_score, - Error::::PhragmenBogusScore + Error::::OffchainElectionBogusScore ); // At last, alles Ok. Exposures and store the result. @@ -3129,7 +3284,7 @@ impl Module { if exposure_clipped.others.len() > clipped_max_len { exposure_clipped .others - .sort_unstable_by(|a, b| a.value.cmp(&b.value).reverse()); + .sort_by(|a, b| a.value.cmp(&b.value).reverse()); exposure_clipped.others.truncate(clipped_max_len); } >::insert(¤t_era, &stash, exposure_clipped); @@ -3463,8 +3618,8 @@ impl Module { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); ledger.unlocking.push(UnlockChunk { value, era }); - let did = Context::current_identity::().unwrap_or_default(); Self::update_ledger(&controller, &ledger); + let did = Context::current_identity::().unwrap_or_default(); Self::deposit_event(RawEvent::Unbonded(did, ledger.stash.clone(), value)); } } diff --git a/pallets/staking/src/testing_utils.rs b/pallets/staking/src/testing_utils.rs index 224bb2dd53..c17e935805 100644 --- a/pallets/staking/src/testing_utils.rs +++ b/pallets/staking/src/testing_utils.rs @@ -21,10 +21,7 @@ use crate::Module as Staking; use crate::*; use frame_benchmarking::account; -use frame_support::traits::Currency; use frame_system::RawOrigin; -use polymesh_common_utilities::traits::identity::LinkedKeyInfo; -use primitives::{IdentityId, Signatory}; use rand_chacha::{ rand_core::{RngCore, SeedableRng}, ChaChaRng, @@ -33,12 +30,6 @@ use sp_io::hashing::blake2_256; use sp_npos_elections::*; const SEED: u32 = 0; -pub type Balance = - <::Currency as Currency<::AccountId>>::Balance; - -pub fn get_minimum_balance() -> Balance { - return 100.into(); -} /// Grab a funded user. pub fn create_funded_user( @@ -47,26 +38,24 @@ pub fn create_funded_user( balance_factor: u32, ) -> T::AccountId { let user = account(string, n, SEED); - let balance = get_minimum_balance::() * balance_factor.into(); + let balance = T::Currency::minimum_balance() * balance_factor.into(); T::Currency::make_free_balance_be(&user, balance); // ensure T::CurrencyToVote will work correctly. T::Currency::issue(balance); user } -/// Create a stash and controller pair, where the controller is dead, and payouts go to controller. -/// This is used to test worst case payout scenarios. -pub fn create_stash_and_dead_controller( +/// Create a stash and controller pair. +pub fn create_stash_controller( n: u32, balance_factor: u32, ) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); - // controller has no funds - let controller = create_funded_user::("controller", n, 0); + let controller = create_funded_user::("controller", n, balance_factor); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); - let reward_destination = RewardDestination::Controller; - let amount = get_minimum_balance::() * (balance_factor / 10).max(1).into(); + let reward_destination = RewardDestination::Staked; + let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), controller_lookup, @@ -76,17 +65,19 @@ pub fn create_stash_and_dead_controller( return Ok((stash, controller)); } -/// Create a stash and controller pair. -pub fn create_stash_controller( +/// Create a stash and controller pair, where the controller is dead, and payouts go to controller. +/// This is used to test worst case payout scenarios. +pub fn create_stash_and_dead_controller( n: u32, balance_factor: u32, ) -> Result<(T::AccountId, T::AccountId), &'static str> { let stash = create_funded_user::("stash", n, balance_factor); - let controller = create_funded_user::("controller", n, balance_factor); + // controller has no funds + let controller = create_funded_user::("controller", n, 0); let controller_lookup: ::Source = T::Lookup::unlookup(controller.clone()); - let reward_destination = RewardDestination::Staked; - let amount = get_minimum_balance::() * (balance_factor / 10).max(1).into(); + let reward_destination = RewardDestination::Controller; + let amount = T::Currency::minimum_balance() * (balance_factor / 10).max(1).into(); Staking::::bond( RawOrigin::Signed(stash.clone()).into(), controller_lookup, @@ -205,8 +196,9 @@ pub fn get_weak_solution( // self stake >::iter().for_each(|(who, _p)| { - *backing_stake_of.entry(who.clone()).or_insert(Zero::zero()) += - >::slashable_balance_of(&who) + *backing_stake_of + .entry(who.clone()) + .or_insert_with(|| Zero::zero()) += >::slashable_balance_of(&who) }); // elect winners. We chose the.. least backed ones. @@ -263,11 +255,8 @@ pub fn get_weak_solution( }; // convert back to ratio assignment. This takes less space. - let low_accuracy_assignment: Vec> = - staked_assignments - .into_iter() - .map(|sa| sa.into_assignment(true)) - .collect(); + let low_accuracy_assignment = + assignment_staked_to_ratio_normalized(staked_assignments).expect("Failed to normalize"); // re-calculate score based on what the chain will decode. let score = {