From 2f0a91449d79381d28eb5ee4d6789c0cc5951433 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 5 Jan 2023 15:51:12 +0100 Subject: [PATCH 01/88] [Feature] StakeTracker P1 - VotersList --- Cargo.lock | 1 + frame/bags-list/fuzzer/src/main.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 1 + frame/bags-list/remote-tests/src/snapshot.rs | 2 +- frame/bags-list/remote-tests/src/try_state.rs | 2 +- frame/bags-list/src/lib.rs | 24 +- frame/bags-list/src/list/tests.rs | 2 +- frame/election-provider-support/src/lib.rs | 40 +- frame/staking/src/benchmarking.rs | 9 +- frame/staking/src/migrations.rs | 425 +++++++++--------- frame/staking/src/pallet/impls.rs | 138 +++--- frame/staking/src/pallet/mod.rs | 29 +- frame/support/src/traits/voting.rs | 74 +-- primitives/staking/Cargo.toml | 2 + primitives/staking/src/currency_to_vote.rs | 86 ++++ primitives/staking/src/lib.rs | 59 ++- 16 files changed, 480 insertions(+), 416 deletions(-) create mode 100644 primitives/staking/src/currency_to_vote.rs diff --git a/Cargo.lock b/Cargo.lock index f82343e442a89..2e521d6fc4093 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9485,6 +9485,7 @@ version = "4.0.0-dev" dependencies = [ "parity-scale-codec", "scale-info", + "sp-arithmetic", "sp-core", "sp-runtime", "sp-std", diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 9f7ca464cc2b8..9d31a0884871b 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -27,7 +27,7 @@ //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; use honggfuzz::fuzz; use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder}; diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 759906a4ef479..c49898dd856da 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -17,6 +17,7 @@ //! Test to check the migration of the voter bag. use crate::{RuntimeT, LOG_TARGET}; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::traits::PalletInfoAccess; use pallet_staking::Nominators; use remote_externalities::{Builder, Mode, OnlineConfig}; diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 0163ca200a15d..fe6982a2f69f8 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -16,7 +16,7 @@ //! Test to execute the snapshot using the voter bag. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index 514c80d72ab67..49f68bb6563af 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -16,7 +16,7 @@ //! Test to execute the sanity-check of the voter bag. -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{ storage::generator::StorageMap, traits::{Get, PalletInfoAccess}, diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 14f8a613eb798..e5e8899a55a87 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -54,7 +54,9 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::FullCodec; -use frame_election_provider_support::{ScoreProvider, SortedListProvider}; +use frame_election_provider_support::{ + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, +}; use frame_system::ensure_signed; use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded, StaticLookup}; use sp_std::prelude::*; @@ -299,7 +301,7 @@ impl, I: 'static> Pallet { } } -impl, I: 'static> SortedListProvider for Pallet { +impl, I: 'static> ReadOnlySortedListProvider for Pallet { type Error = ListError; type Score = T::Score; @@ -321,15 +323,19 @@ impl, I: 'static> SortedListProvider for Pallet fn contains(id: &T::AccountId) -> bool { List::::contains(id) } + fn get_score(id: &T::AccountId) -> Result { + List::::get_score(id) + } + fn try_state() -> Result<(), &'static str> { + List::::try_state() + } +} +impl, I: 'static> SortedListProvider for Pallet { fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { List::::insert(id, score) } - fn get_score(id: &T::AccountId) -> Result { - List::::get_score(id) - } - fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { Pallet::::do_rebag(id, new_score).map(|_| ()) } @@ -348,10 +354,6 @@ impl, I: 'static> SortedListProvider for Pallet List::::unsafe_regenerate(all, score_of) } - fn try_state() -> Result<(), &'static str> { - List::::try_state() - } - fn unsafe_clear() { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. // I.e. because it can lead to many storage accesses. @@ -384,7 +386,7 @@ impl, I: 'static> SortedListProvider for Pallet } impl, I: 'static> ScoreProvider for Pallet { - type Score = as SortedListProvider>::Score; + type Score = as ReadOnlySortedListProvider>::Score; fn score(id: &T::AccountId) -> T::Score { Node::::get(id).map(|node| node.score()).unwrap_or_default() diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index 966ea1a74c71c..e7512c600f580 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -20,7 +20,7 @@ use crate::{ mock::{test_utils::*, *}, ListBags, ListNodes, }; -use frame_election_provider_support::{SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; fn node( diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 9d5d6c018e5e1..6d424b29b4467 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -471,16 +471,13 @@ where } } -/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. -/// -/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other -/// entity. +/// A trait that defines a set of functions that allow read access to the underlying storage +/// and the types those functions depend upon. /// -/// The scores (see [`Self::Score`]) are ascending, the higher, the better. -/// -/// Something that implements this trait will do a best-effort sort over ids, and thus can be -/// used on the implementing side of [`ElectionDataProvider`]. -pub trait SortedListProvider { +/// Initially a part of [`SortedListProvider`], it allows for restricting a consumer to read-only +/// operations. This is particularly useful in case the list is populated by one entity and +/// read by another. +pub trait ReadOnlySortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; @@ -501,6 +498,25 @@ pub trait SortedListProvider { /// Return true if the list already contains `id`. fn contains(id: &AccountId) -> bool; + /// Get the score of `id`. + fn get_score(id: &AccountId) -> Result; + + /// Check internal state of list. Only meant for debugging. + fn try_state() -> Result<(), &'static str>; +} + +/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. +/// +/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other +/// entity. +/// +/// The scores (see [`Self::Score`]) are ascending, the higher, the better. +/// +/// Something that implements this trait will do a best-effort sort over ids, and thus can be +/// used on the implementing side of [`ElectionDataProvider`]. +/// +/// Inherits [`ReadOnlySortedListProvider`], which provides basic types and read-only methods. +pub trait SortedListProvider: ReadOnlySortedListProvider { /// Hook for inserting a new id. /// /// Implementation should return an error if duplicate item is being inserted. @@ -513,9 +529,6 @@ pub trait SortedListProvider { /// Returns `Ok(())` iff it successfully updates an item, an `Err(_)` otherwise. fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error>; - /// Get the score of `id`. - fn get_score(id: &AccountId) -> Result; - /// Same as `on_update`, but incorporate some increased score. fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> { let old_score = Self::get_score(id)?; @@ -562,9 +575,6 @@ pub trait SortedListProvider { /// unbounded amount of storage accesses. fn unsafe_clear(); - /// Check internal state of list. Only meant for debugging. - fn try_state() -> Result<(), &'static str>; - /// If `who` changes by the returned amount they are guaranteed to have a worst case change /// in their list position. #[cfg(feature = "runtime-benchmarks")] diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index b3d32b26ec1f7..c6dd47301f99c 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{ConfigOp, Pallet as Staking}; use testing_utils::*; use codec::Decode; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; use frame_support::{ dispatch::UnfilteredDispatchable, pallet_prelude::*, @@ -189,8 +189,11 @@ impl ListScenario { )?; // find a destination weight that will trigger the worst case scenario - let dest_weight_as_vote = - T::VoterList::score_update_worst_case(&origin_stash1, is_increase); + // TODO: This is a hack, might be great to implement this differently. + let dest_weight_as_vote = >::score_update_worst_case( + &origin_stash1, + is_increase, + ); let total_issuance = T::Currency::total_issuance(); diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 6253c3feed17d..f42cb8f3445c7 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -288,214 +288,217 @@ pub mod v10 { } } -pub mod v9 { - use super::*; - #[cfg(feature = "try-runtime")] - use frame_support::codec::{Decode, Encode}; - #[cfg(feature = "try-runtime")] - use sp_std::vec::Vec; - - /// Migration implementation that injects all validators into sorted list. - /// - /// This is only useful for chains that started their `VoterList` just based on nominators. - pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); - impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { - fn on_runtime_upgrade() -> Weight { - if StorageVersion::::get() == ObsoleteReleases::V8_0_0 { - let prev_count = T::VoterList::count(); - let weight_of_cached = Pallet::::weight_of_fn(); - for (v, _) in Validators::::iter() { - let weight = weight_of_cached(&v); - let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { - log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) - }); - } - - log!( - info, - "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", - Validators::::count(), - prev_count, - T::VoterList::count(), - ); - - StorageVersion::::put(ObsoleteReleases::V9_0_0); - T::BlockWeights::get().max_block - } else { - log!( - warn, - "InjectValidatorsIntoVoterList being executed on the wrong storage \ - version, expected ObsoleteReleases::V8_0_0" - ); - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - fn pre_upgrade() -> Result, &'static str> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V8_0_0, - "must upgrade linearly" - ); - - let prev_count = T::VoterList::count(); - Ok(prev_count.encode()) - } - - #[cfg(feature = "try-runtime")] - fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { - let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( - "the state parameter should be something that was generated by pre_upgrade", - ); - let post_count = T::VoterList::count(); - let validators = Validators::::count(); - assert!(post_count == prev_count + validators); - - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V9_0_0, - "must upgrade " - ); - Ok(()) - } - } -} - -pub mod v8 { - use super::*; - use crate::{Config, Nominators, Pallet, Weight}; - use frame_election_provider_support::SortedListProvider; - use frame_support::traits::Get; - - #[cfg(feature = "try-runtime")] - pub fn pre_migrate() -> Result<(), &'static str> { - frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V7_0_0, - "must upgrade linearly" - ); - - crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); - Ok(()) - } - - /// Migration to sorted `VoterList`. - pub fn migrate() -> Weight { - if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { - crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); - - let migrated = T::VoterList::unsafe_regenerate( - Nominators::::iter().map(|(id, _)| id), - Pallet::::weight_of_fn(), - ); - debug_assert_eq!(T::VoterList::try_state(), Ok(())); - - StorageVersion::::put(ObsoleteReleases::V8_0_0); - crate::log!( - info, - "👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated", - migrated, - ); - - T::BlockWeights::get().max_block - } else { - T::DbWeight::get().reads(1) - } - } - - #[cfg(feature = "try-runtime")] - pub fn post_migrate() -> Result<(), &'static str> { - T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; - crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); - Ok(()) - } -} - -pub mod v7 { - use super::*; - use frame_support::storage_alias; - - #[storage_alias] - type CounterForValidators = StorageValue, u32>; - #[storage_alias] - type CounterForNominators = StorageValue, u32>; - - pub fn pre_migrate() -> Result<(), &'static str> { - assert!( - CounterForValidators::::get().unwrap().is_zero(), - "CounterForValidators already set." - ); - assert!( - CounterForNominators::::get().unwrap().is_zero(), - "CounterForNominators already set." - ); - assert!(Validators::::count().is_zero(), "Validators already set."); - assert!(Nominators::::count().is_zero(), "Nominators already set."); - assert!(StorageVersion::::get() == ObsoleteReleases::V6_0_0); - Ok(()) - } - - pub fn migrate() -> Weight { - log!(info, "Migrating staking to ObsoleteReleases::V7_0_0"); - let validator_count = Validators::::iter().count() as u32; - let nominator_count = Nominators::::iter().count() as u32; - - CounterForValidators::::put(validator_count); - CounterForNominators::::put(nominator_count); - - StorageVersion::::put(ObsoleteReleases::V7_0_0); - log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0"); - - T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) - } -} - -pub mod v6 { - use super::*; - use frame_support::{storage_alias, traits::Get, weights::Weight}; - - // NOTE: value type doesn't matter, we just set it to () here. - #[storage_alias] - type SnapshotValidators = StorageValue, ()>; - #[storage_alias] - type SnapshotNominators = StorageValue, ()>; - #[storage_alias] - type QueuedElected = StorageValue, ()>; - #[storage_alias] - type QueuedScore = StorageValue, ()>; - #[storage_alias] - type EraElectionStatus = StorageValue, ()>; - #[storage_alias] - type IsCurrentSessionFinal = StorageValue, ()>; - - /// check to execute prior to migration. - pub fn pre_migrate() -> Result<(), &'static str> { - // these may or may not exist. - log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); - log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); - log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); - log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); - // these must exist. - assert!( - IsCurrentSessionFinal::::exists(), - "IsCurrentSessionFinal storage item not found!" - ); - assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); - Ok(()) - } - - /// Migrate storage to v6. - pub fn migrate() -> Weight { - log!(info, "Migrating staking to ObsoleteReleases::V6_0_0"); - - SnapshotValidators::::kill(); - SnapshotNominators::::kill(); - QueuedElected::::kill(); - QueuedScore::::kill(); - EraElectionStatus::::kill(); - IsCurrentSessionFinal::::kill(); - - StorageVersion::::put(ObsoleteReleases::V6_0_0); - - log!(info, "Done."); - T::DbWeight::get().writes(6 + 1) - } -} +// These migrations have been commented out as they are no longer compatible with the current +// codebase. +// +// pub mod v9 { +// use super::*; +// #[cfg(feature = "try-runtime")] +// use frame_support::codec::{Decode, Encode}; +// #[cfg(feature = "try-runtime")] +// use sp_std::vec::Vec; +// +// /// Migration implementation that injects all validators into sorted list. +// /// +// /// This is only useful for chains that started their `VoterList` just based on nominators. +// pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); +// impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { +// fn on_runtime_upgrade() -> Weight { +// if StorageVersion::::get() == ObsoleteReleases::V8_0_0 { +// let prev_count = T::VoterList::count(); +// let weight_of_cached = Pallet::::weight_of_fn(); +// for (v, _) in Validators::::iter() { +// let weight = weight_of_cached(&v); +// let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { +// log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) +// }); +// } +// +// log!( +// info, +// "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", +// Validators::::count(), +// prev_count, +// T::VoterList::count(), +// ); +// +// StorageVersion::::put(ObsoleteReleases::V9_0_0); +// T::BlockWeights::get().max_block +// } else { +// log!( +// warn, +// "InjectValidatorsIntoVoterList being executed on the wrong storage \ +// version, expected ObsoleteReleases::V8_0_0" +// ); +// T::DbWeight::get().reads(1) +// } +// } +// +// #[cfg(feature = "try-runtime")] +// fn pre_upgrade() -> Result, &'static str> { +// frame_support::ensure!( +// StorageVersion::::get() == ObsoleteReleases::V8_0_0, +// "must upgrade linearly" +// ); +// +// let prev_count = T::VoterList::count(); +// Ok(prev_count.encode()) +// } +// +// #[cfg(feature = "try-runtime")] +// fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { +// let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( +// "the state parameter should be something that was generated by pre_upgrade", +// ); +// let post_count = T::VoterList::count(); +// let validators = Validators::::count(); +// assert!(post_count == prev_count + validators); +// +// frame_support::ensure!( +// StorageVersion::::get() == ObsoleteReleases::V9_0_0, +// "must upgrade " +// ); +// Ok(()) +// } +// } +// } +// +// pub mod v8 { +// use super::*; +// use crate::{Config, Nominators, Pallet, Weight}; +// use frame_election_provider_support::SortedListProvider; +// use frame_support::traits::Get; +// +// #[cfg(feature = "try-runtime")] +// pub fn pre_migrate() -> Result<(), &'static str> { +// frame_support::ensure!( +// StorageVersion::::get() == ObsoleteReleases::V7_0_0, +// "must upgrade linearly" +// ); +// +// crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); +// Ok(()) +// } +// +// /// Migration to sorted `VoterList`. +// pub fn migrate() -> Weight { +// if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { +// crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); +// +// let migrated = T::VoterList::unsafe_regenerate( +// Nominators::::iter().map(|(id, _)| id), +// Pallet::::weight_of_fn(), +// ); +// debug_assert_eq!(T::VoterList::try_state(), Ok(())); +// +// StorageVersion::::put(ObsoleteReleases::V8_0_0); +// crate::log!( +// info, +// "👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated", +// migrated, +// ); +// +// T::BlockWeights::get().max_block +// } else { +// T::DbWeight::get().reads(1) +// } +// } +// +// #[cfg(feature = "try-runtime")] +// pub fn post_migrate() -> Result<(), &'static str> { +// T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; +// crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); +// Ok(()) +// } +// } +// +// pub mod v7 { +// use super::*; +// use frame_support::storage_alias; +// +// #[storage_alias] +// type CounterForValidators = StorageValue, u32>; +// #[storage_alias] +// type CounterForNominators = StorageValue, u32>; +// +// pub fn pre_migrate() -> Result<(), &'static str> { +// assert!( +// CounterForValidators::::get().unwrap().is_zero(), +// "CounterForValidators already set." +// ); +// assert!( +// CounterForNominators::::get().unwrap().is_zero(), +// "CounterForNominators already set." +// ); +// assert!(Validators::::count().is_zero(), "Validators already set."); +// assert!(Nominators::::count().is_zero(), "Nominators already set."); +// assert!(StorageVersion::::get() == ObsoleteReleases::V6_0_0); +// Ok(()) +// } +// +// pub fn migrate() -> Weight { +// log!(info, "Migrating staking to ObsoleteReleases::V7_0_0"); +// let validator_count = Validators::::iter().count() as u32; +// let nominator_count = Nominators::::iter().count() as u32; +// +// CounterForValidators::::put(validator_count); +// CounterForNominators::::put(nominator_count); +// +// StorageVersion::::put(ObsoleteReleases::V7_0_0); +// log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0"); +// +// T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) +// } +// } +// +// pub mod v6 { +// use super::*; +// use frame_support::{storage_alias, traits::Get, weights::Weight}; +// +// // NOTE: value type doesn't matter, we just set it to () here. +// #[storage_alias] +// type SnapshotValidators = StorageValue, ()>; +// #[storage_alias] +// type SnapshotNominators = StorageValue, ()>; +// #[storage_alias] +// type QueuedElected = StorageValue, ()>; +// #[storage_alias] +// type QueuedScore = StorageValue, ()>; +// #[storage_alias] +// type EraElectionStatus = StorageValue, ()>; +// #[storage_alias] +// type IsCurrentSessionFinal = StorageValue, ()>; +// +// /// check to execute prior to migration. +// pub fn pre_migrate() -> Result<(), &'static str> { +// // these may or may not exist. +// log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); +// log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); +// log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); +// log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); +// // these must exist. +// assert!( +// IsCurrentSessionFinal::::exists(), +// "IsCurrentSessionFinal storage item not found!" +// ); +// assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); +// Ok(()) +// } +// +// /// Migrate storage to v6. +// pub fn migrate() -> Weight { +// log!(info, "Migrating staking to ObsoleteReleases::V6_0_0"); +// +// SnapshotValidators::::kill(); +// SnapshotNominators::::kill(); +// QueuedElected::::kill(); +// QueuedScore::::kill(); +// EraElectionStatus::::kill(); +// IsCurrentSessionFinal::::kill(); +// +// StorageVersion::::put(ObsoleteReleases::V6_0_0); +// +// log!(info, "Done."); +// T::DbWeight::get().writes(6 + 1) +// } +// } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index db9aeba6fb58e..bc321dac3a6cf 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,8 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, - SortedListProvider, VoteWeight, VoterOf, + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ dispatch::WithPostDispatchInfo, @@ -38,7 +38,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - EraIndex, SessionIndex, Stake, StakingInterface, + EraIndex, OnStakingUpdate, SessionIndex, Stake, StakingInterface, }; use sp_std::prelude::*; @@ -269,8 +269,14 @@ impl Pallet { /// /// This will also update the stash lock. pub(crate) fn update_ledger(controller: &T::AccountId, ledger: &StakingLedger) { + let prev_ledger = Self::ledger(controller).map(|l| Stake { + stash: l.stash, + total: l.total, + active: l.active, + }); T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); + T::EventListener::on_update_ledger(&ledger.stash, prev_ledger); } /// Chill a stash account. @@ -662,6 +668,7 @@ impl Pallet { Self::do_remove_nominator(stash); frame_system::Pallet::::dec_consumers(stash); + T::EventListener::on_reaped(stash); Ok(()) } @@ -871,17 +878,9 @@ impl Pallet { /// to `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - if !Nominators::::contains_key(who) { - // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); - } + let prev_nominations = Self::nominations(who); Nominators::::insert(who, nominations); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); + T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()); } /// This function will remove a nominator from the `Nominators` storage map, @@ -890,23 +889,14 @@ impl Pallet { /// Returns true if `who` was removed from `Nominators`, otherwise false. /// /// NOTE: you must ALWAYS use this function to remove a nominator from the system. Any access to - /// `Nominators` or `VoterList` outside of this function is almost certainly - /// wrong. + /// `Nominators` outside of this function is almost certainly wrong. pub fn do_remove_nominator(who: &T::AccountId) -> bool { - let outcome = if Nominators::::contains_key(who) { + if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); - let _ = T::VoterList::on_remove(who).defensive(); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome + T::EventListener::on_nominator_remove(who, nominations); + return true + } + false } /// This function will add a validator to the `Validators` storage map. @@ -918,16 +908,9 @@ impl Pallet { /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { - // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::weight_of(who)) - .defensive_unwrap_or_default(); + T::EventListener::on_validator_add(who); } Validators::::insert(who, prefs); - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); } /// This function will remove a validator from the `Validators` storage map. @@ -935,23 +918,14 @@ impl Pallet { /// Returns true if `who` was removed from `Validators`, otherwise false. /// /// NOTE: you must ALWAYS use this function to remove a validator from the system. Any access to - /// `Validators` or `VoterList` outside of this function is almost certainly - /// wrong. + /// `Validators` outside of this function is almost certainly wrong. pub fn do_remove_validator(who: &T::AccountId) -> bool { - let outcome = if Validators::::contains_key(who) { + if Validators::::contains_key(who) { Validators::::remove(who); - let _ = T::VoterList::on_remove(who).defensive(); - true - } else { - false - }; - - debug_assert_eq!( - Nominators::::count() + Validators::::count(), - T::VoterList::count() - ); - - outcome + T::EventListener::on_validator_remove(who); + return true + } + false } /// Register some amount of weight directly with the system pallet. @@ -1081,8 +1055,8 @@ impl ElectionDataProvider for Pallet { >::remove_all(); #[allow(deprecated)] >::remove_all(); - - T::VoterList::unsafe_clear(); + // TODO: sort it out some other way + // T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] @@ -1408,9 +1382,9 @@ impl ScoreProvider for Pallet { /// does not provide validators in sorted order. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list`]. pub struct UseValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseValidatorsMap { - type Score = BalanceOf; +impl ReadOnlySortedListProvider for UseValidatorsMap { type Error = (); + type Score = BalanceOf; /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { @@ -1432,13 +1406,20 @@ impl SortedListProvider for UseValidatorsMap { fn contains(id: &T::AccountId) -> bool { Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id).into()) + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } +} + +impl SortedListProvider for UseValidatorsMap { fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id).into()) - } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1454,9 +1435,7 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { - Ok(()) - } + fn unsafe_clear() { #[allow(deprecated)] Validators::::remove_all(); @@ -1472,7 +1451,8 @@ impl SortedListProvider for UseValidatorsMap { /// does not provided nominators in sorted ordered. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list]. pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); -impl SortedListProvider for UseNominatorsAndValidatorsMap { + +impl ReadOnlySortedListProvider for UseNominatorsAndValidatorsMap { type Error = (); type Score = VoteWeight; @@ -1506,13 +1486,20 @@ impl SortedListProvider for UseNominatorsAndValidatorsM fn contains(id: &T::AccountId) -> bool { Nominators::::contains_key(id) || Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id)) + } + fn try_state() -> Result<(), &'static str> { + Ok(()) + } +} + +impl SortedListProvider for UseNominatorsAndValidatorsMap { fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id)) - } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1528,9 +1515,6 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do upon regenerate. 0 } - fn try_state() -> Result<(), &'static str> { - Ok(()) - } fn unsafe_clear() { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a @@ -1593,7 +1577,9 @@ impl StakingInterface for Pallet { Self::current_era().unwrap_or(Zero::zero()) } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake( + who: &Self::AccountId, + ) -> Result, DispatchError> { Self::bonded(who) .and_then(|c| Self::ledger(c)) .map(|l| Stake { stash: l.stash, total: l.total, active: l.active }) @@ -1647,11 +1633,11 @@ impl StakingInterface for Pallet { Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } - sp_staking::runtime_benchmarks_enabled! { - fn nominations(who: Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) - } + fn nominations(who: &Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } + sp_staking::runtime_benchmarks_enabled! { fn add_era_stakers( current_era: &EraIndex, stash: &T::AccountId, @@ -1669,6 +1655,12 @@ impl StakingInterface for Pallet { CurrentEra::::put(era); } } + + type CurrencyToVote = T::CurrencyToVote; + + fn is_validator(who: &Self::AccountId) -> bool { + Validators::::contains_key(who) + } } #[cfg(any(test, feature = "try-runtime"))] diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 92502949ef1a0..e981f06d74b16 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -58,7 +58,7 @@ pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { - use frame_election_provider_support::ElectionDataProvider; + use frame_election_provider_support::{ElectionDataProvider, ReadOnlySortedListProvider}; use crate::BenchmarkingConfig; @@ -225,7 +225,10 @@ pub mod pallet { /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. /// /// Invariant: what comes out of this list will always be a nominator. - type VoterList: SortedListProvider; + /// + /// NOTE: Staking does not maintain this list, it merely reads from it. The list is + /// maintained by `EventListener` implementors. + type VoterList: ReadOnlySortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going /// to be introduced in a follow-up PR. @@ -249,6 +252,13 @@ pub mod pallet { /// VALIDATOR. type TargetList: SortedListProvider>; + /// Something that listens to staking updates and performs actions based on the data it + /// receives. + /// NOTE: Once we have more implementors of EventListener - this could be done similarly to + /// `OnRuntimeUpdate` - just a tuple of all sorts of pallets implementing the trait that all + /// react to the interface methods being called or ignore them. + type EventListener: sp_staking::OnStakingUpdate>; + /// The maximum number of `unlocking` chunks a [`StakingLedger`] can /// have. Effectively determines how many unique eras a staker may be /// unbonding in. @@ -933,11 +943,6 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&stash) { - let _ = - T::VoterList::on_update(&stash, Self::weight_of(&ledger.stash)).defensive(); - } Self::deposit_event(Event::::Bonded { stash, amount: extra }); } @@ -1037,12 +1042,6 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - // update this staker in the sorted list, if they exist in it. - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } - Self::deposit_event(Event::::Unbonded { stash: ledger.stash, amount: value }); } @@ -1543,10 +1542,6 @@ pub mod pallet { // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); - if T::VoterList::contains(&ledger.stash) { - let _ = T::VoterList::on_update(&ledger.stash, Self::weight_of(&ledger.stash)) - .defensive(); - } let removed_chunks = 1u32 // for the case where the last iterated chunk is not removed .saturating_add(initial_unlocking) diff --git a/frame/support/src/traits/voting.rs b/frame/support/src/traits/voting.rs index 49ae3163d0cd1..f2f9f5e522146 100644 --- a/frame/support/src/traits/voting.rs +++ b/frame/support/src/traits/voting.rs @@ -20,81 +20,11 @@ use crate::dispatch::{DispatchError, Parameter}; use codec::{HasCompact, MaxEncodedLen}; -use sp_arithmetic::{ - traits::{SaturatedConversion, UniqueSaturatedFrom, UniqueSaturatedInto}, - Perbill, -}; +use sp_arithmetic::Perbill; use sp_runtime::traits::Member; +pub use sp_staking::currency_to_vote::*; use sp_std::prelude::*; -/// A trait similar to `Convert` to convert values from `B` an abstract balance type -/// into u64 and back from u128. (This conversion is used in election and other places where complex -/// calculation over balance type is needed) -/// -/// Total issuance of the currency is passed in, but an implementation of this trait may or may not -/// use it. -/// -/// # WARNING -/// -/// the total issuance being passed in implies that the implementation must be aware of the fact -/// that its values can affect the outcome. This implies that if the vote value is dependent on the -/// total issuance, it should never ber written to storage for later re-use. -pub trait CurrencyToVote { - /// Convert balance to u64. - fn to_vote(value: B, issuance: B) -> u64; - - /// Convert u128 to balance. - fn to_currency(value: u128, issuance: B) -> B; -} - -/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. -/// -/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the -/// important cases: -/// -/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that -/// the factor will not have any effect. In this case, any account's balance is also less. Thus, -/// both of the conversions are basically an `as`; Any balance can fit in u64. -/// -/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and -/// divided upon conversion. -pub struct U128CurrencyToVote; - -impl U128CurrencyToVote { - fn factor(issuance: u128) -> u128 { - (issuance / u64::MAX as u128).max(1) - } -} - -impl CurrencyToVote for U128CurrencyToVote { - fn to_vote(value: u128, issuance: u128) -> u64 { - (value / Self::factor(issuance)).saturated_into() - } - - fn to_currency(value: u128, issuance: u128) -> u128 { - value.saturating_mul(Self::factor(issuance)) - } -} - -/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. -/// -/// # Warning -/// -/// This is designed to be used mostly for testing. Use with care, and think about the consequences. -pub struct SaturatingCurrencyToVote; - -impl + UniqueSaturatedFrom> CurrencyToVote - for SaturatingCurrencyToVote -{ - fn to_vote(value: B, _: B) -> u64 { - value.unique_saturated_into() - } - - fn to_currency(value: u128, _: B) -> B { - B::unique_saturated_from(value) - } -} - pub trait VoteTally { fn new(_: Class) -> Self; fn ayes(&self, class: Class) -> Votes; diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 35feae43ebb8c..26eafb3cac408 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -18,6 +18,7 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } +sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } [features] default = ["std"] @@ -27,5 +28,6 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", + "sp-arithmetic/std" ] runtime-benchmarks = [] diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs new file mode 100644 index 0000000000000..d04f651ae3068 --- /dev/null +++ b/primitives/staking/src/currency_to_vote.rs @@ -0,0 +1,86 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use sp_arithmetic::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; +use sp_runtime::SaturatedConversion; +/// A trait similar to `Convert` to convert values from `B` an abstract balance type +/// into u64 and back from u128. (This conversion is used in election and other places where complex +/// calculation over balance type is needed) +/// +/// Total issuance of the currency is passed in, but an implementation of this trait may or may not +/// use it. +/// +/// # WARNING +/// +/// the total issuance being passed in implies that the implementation must be aware of the fact +/// that its values can affect the outcome. This implies that if the vote value is dependent on the +/// total issuance, it should never ber written to storage for later re-use. +pub trait CurrencyToVote { + /// Convert balance to u64. + fn to_vote(value: B, issuance: B) -> u64; + + /// Convert u128 to balance. + fn to_currency(value: u128, issuance: B) -> B; +} + +/// An implementation of `CurrencyToVote` tailored for chain's that have a balance type of u128. +/// +/// The factor is the `(total_issuance / u64::MAX).max(1)`, represented as u64. Let's look at the +/// important cases: +/// +/// If the chain's total issuance is less than u64::MAX, this will always be 1, which means that +/// the factor will not have any effect. In this case, any account's balance is also less. Thus, +/// both of the conversions are basically an `as`; Any balance can fit in u64. +/// +/// If the chain's total issuance is more than 2*u64::MAX, then a factor might be multiplied and +/// divided upon conversion. +pub struct U128CurrencyToVote; + +impl U128CurrencyToVote { + fn factor(issuance: u128) -> u128 { + (issuance / u64::MAX as u128).max(1) + } +} + +impl CurrencyToVote for U128CurrencyToVote { + fn to_vote(value: u128, issuance: u128) -> u64 { + (value / Self::factor(issuance)).saturated_into() + } + + fn to_currency(value: u128, issuance: u128) -> u128 { + value.saturating_mul(Self::factor(issuance)) + } +} + +/// A naive implementation of `CurrencyConvert` that simply saturates all conversions. +/// +/// # Warning +/// +/// This is designed to be used mostly for testing. Use with care, and think about the consequences. +pub struct SaturatingCurrencyToVote; + +impl + UniqueSaturatedFrom> CurrencyToVote + for SaturatingCurrencyToVote +{ + fn to_vote(value: B, _: B) -> u64 { + value.unique_saturated_into() + } + + fn to_currency(value: u128, _: B) -> B { + B::unique_saturated_from(value) + } +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 9eb4a4890cdf8..42564611fb46a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -20,11 +20,16 @@ //! A crate which contains primitives that are useful for implementation that uses staking //! approaches in general. Definitions related to sessions, slashing, etc go here. -use sp_runtime::{DispatchError, DispatchResult}; -use sp_std::{collections::btree_map::BTreeMap, vec::Vec}; +use crate::currency_to_vote::CurrencyToVote; +use codec::{Encode, EncodeLike, FullCodec, MaxEncodedLen}; +use scale_info::TypeInfo; +use sp_runtime::{DispatchError, DispatchResult, Saturating}; +use sp_std::{collections::btree_map::BTreeMap, ops::Sub, vec::Vec}; pub mod offence; +pub mod currency_to_vote; + /// Simple index type with which we can count sessions. pub type SessionIndex = u32; @@ -57,9 +62,10 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. -pub struct Stake { +#[derive(Default)] +pub struct Stake { /// The stash account whose balance is actually locked and at stake. - pub stash: T::AccountId, + pub stash: AccountId, /// The total stake that `stash` has in the staking system. This includes the /// `active` stake, and any funds currently in the process of unbonding via /// [`StakingInterface::unbond`]. @@ -69,10 +75,29 @@ pub struct Stake { /// This is only guaranteed to reflect the amount locked by the staking system. If there are /// non-staking locks on the bonded pair's balance this amount is going to be larger in /// reality. - pub total: T::Balance, + pub total: Balance, /// The total amount of the stash's balance that will be at stake in any forthcoming /// rounds. - pub active: T::Balance, + pub active: Balance, +} + +/// A generic staking event listener. Currently used for implementations involved in stake tracking. +/// Note that the interface is designed in a way that the events are fired post-action, so any +/// pre-action data that is needed needs to be passed to interface methods. +/// The rest of the data can be retrieved by using `StakingInterface`. +pub trait OnStakingUpdate { + /// Track ledger updates. + fn on_update_ledger(who: &AccountId, prev_stake: Option>); + /// Track nominators, those reinstated and also new ones. + fn on_nominator_add(who: &AccountId, prev_nominations: Vec); + /// Track validators, those reinstated and new. + fn on_validator_add(who: &AccountId); + /// Track removed validators. Either chilled or those that became nominators instead. + fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator + /// Track removed nominators. + fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator + /// Track those participants of staking system that are kicked out for whatever reason. + fn on_reaped(who: &AccountId); // -> basically `kill_stash` } /// A generic representation of a staking implementation. @@ -81,11 +106,22 @@ pub struct Stake { /// implementations as well. pub trait StakingInterface { /// Balance type used by the staking system. - type Balance: PartialEq; + type Balance: Sub + + Ord + + PartialEq + + Default + + Copy + + MaxEncodedLen + + FullCodec + + TypeInfo + + Saturating; /// AccountId type used by the staking system type AccountId; + /// whatever + type CurrencyToVote: CurrencyToVote; + /// The minimum amount required to bond in order to set nomination intentions. This does not /// necessarily mean the nomination will be counted in an election, but instead just enough to /// be stored as a nominator. In other words, this is the minimum amount to register the @@ -112,7 +148,8 @@ pub trait StakingInterface { fn current_era() -> EraIndex; /// Returns the stake of `who`. - fn stake(who: &Self::AccountId) -> Result, DispatchError>; + fn stake(who: &Self::AccountId) + -> Result, DispatchError>; fn total_stake(who: &Self::AccountId) -> Result { Self::stake(who).map(|s| s.total) @@ -176,9 +213,11 @@ pub trait StakingInterface { /// Checks whether an account `staker` has been exposed in an era. fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool; + /// Checks whether or not this is a validator account. + fn is_validator(who: &Self::AccountId) -> bool; + /// Get the nominations of a stash, if they are a nominator, `None` otherwise. - #[cfg(feature = "runtime-benchmarks")] - fn nominations(who: Self::AccountId) -> Option>; + fn nominations(who: &Self::AccountId) -> Option>; #[cfg(feature = "runtime-benchmarks")] fn add_era_stakers( From 29e1f665d3b969e2e4f54afc264b392d0688c590 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 5 Jan 2023 16:27:14 +0100 Subject: [PATCH 02/88] add stake-tracker --- Cargo.lock | 22 +++++++ Cargo.toml | 1 + frame/stake-tracker/Cargo.toml | 67 +++++++++++++++++++ frame/stake-tracker/src/lib.rs | 116 +++++++++++++++++++++++++++++++++ 4 files changed, 206 insertions(+) create mode 100644 frame/stake-tracker/Cargo.toml create mode 100644 frame/stake-tracker/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 2e521d6fc4093..2472839ef2e63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5851,6 +5851,28 @@ dependencies = [ "sp-std", ] +[[package]] +name = "pallet-stake-tracker" +version = "4.0.0-dev" +dependencies = [ + "frame-benchmarking", + "frame-election-provider-support", + "frame-support", + "frame-system", + "log", + "pallet-balances", + "pallet-staking", + "pallet-timestamp", + "parity-scale-codec", + "scale-info", + "sp-io", + "sp-runtime", + "sp-staking", + "sp-std", + "sp-tracing", + "substrate-test-utils", +] + [[package]] name = "pallet-staking" version = "4.0.0-dev" diff --git a/Cargo.toml b/Cargo.toml index 8f55d8e527ecd..10b32bf0302ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,6 +139,7 @@ members = [ "frame/session/benchmarking", "frame/society", "frame/staking", + "frame/stake-tracker", "frame/staking/reward-curve", "frame/staking/reward-fn", "frame/state-trie-migration", diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml new file mode 100644 index 0000000000000..171984b594d42 --- /dev/null +++ b/frame/stake-tracker/Cargo.toml @@ -0,0 +1,67 @@ +[package] +name = "pallet-stake-tracker" +version = "4.0.0-dev" +authors = ["Parity Technologies "] +edition = "2021" +license = "Unlicense" +homepage = "https://substrate.io" +repository = "https://github.com/paritytech/substrate/" +description = "FRAME stake tracker pallet" +readme = "README.md" + +[package.metadata.docs.rs] +targets = ["x86_64-unknown-linux-gnu"] + +[dependencies] +codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false } +log = { version = "0.4.17", default-features = false } +scale-info = { version = "2.1.1", default-features = false, features = ["derive"] } + +frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" } +frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" } + +sp-io = { version = "7.0.0", default-features = false, path = "../../primitives/io" } +sp-runtime = { version = "7.0.0", default-features = false, path = "../../primitives/runtime" } +sp-std = { version = "5.0.0", default-features = false, path = "../../primitives/std" } +sp-staking = { version = "4.0.0-dev", default-features = false, path = "../../primitives/staking" } + +frame-election-provider-support = { default-features = false, path = "../election-provider-support" } + +frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" } + +[dev-dependencies] +substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } +sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } +pallet-staking = { path = "../staking" } +pallet-balances = { path = "../balances" } +pallet-timestamp = { path = "../timestamp" } + + + + +[features] +default = ["std"] +std = [ + "codec/std", + "log/std", + "scale-info/std", + + "frame-support/std", + "frame-system/std", + + "sp-io/std", + "sp-staking/std", + "sp-runtime/std", + "sp-std/std", + + "frame-election-provider-support/std", + + "frame-benchmarking/std", +] +runtime-benchmarks = [ + "frame-benchmarking/runtime-benchmarks", + "frame-system/runtime-benchmarks", + "sp-staking/runtime-benchmarks", + "pallet-staking/runtime-benchmarks" +] +try-runtime = ["frame-support/try-runtime"] diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs new file mode 100644 index 0000000000000..9f65e16a4f5f9 --- /dev/null +++ b/frame/stake-tracker/src/lib.rs @@ -0,0 +1,116 @@ +// This file is part of Substrate. + +// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#![cfg_attr(not(feature = "std"), no_std)] + +use frame_election_provider_support::{ + ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, +}; +use frame_support::traits::{Currency, CurrencyToVote, Defensive}; +pub use pallet::*; +use sp_runtime::Saturating; +use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; + +use sp_std::vec::Vec; + +/// The balance type of this pallet. +pub type BalanceOf = <::Staking as StakingInterface>::Balance; + +#[frame_support::pallet] +pub mod pallet { + use crate::*; + use frame_election_provider_support::{SortedListProvider, VoteWeight}; + use frame_support::{pallet_prelude::*, Twox64Concat}; + + use sp_staking::StakingInterface; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); + + #[pallet::pallet] + #[pallet::generate_store(pub(super) trait Store)] + #[pallet::storage_version(STORAGE_VERSION)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + /// This has to come from Staking::Currency + type Currency: Currency>; + + type Staking: StakingInterface; + + type VoterList: SortedListProvider; + } +} + +impl Pallet { + /// The total balance that can be slashed from a stash account as of right now. + pub fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { + // Weight note: consider making the stake accessible through stash. + T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() + } + + pub(crate) fn to_vote(balance: BalanceOf) -> VoteWeight { + let total_issuance = T::Currency::total_issuance(); + ::CurrencyToVote::to_vote(balance, total_issuance) + } +} + +impl OnStakingUpdate> for Pallet { + fn on_update_ledger(who: &T::AccountId, _: Option>>) { + let current_stake = T::Staking::stake(who).unwrap(); + let current_active = current_stake.active; + + // if this is a nominator + if let Some(_) = T::Staking::nominations(¤t_stake.stash) { + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof("any nominator should have an entry in the voter list."); + } + + if T::Staking::is_validator(¤t_stake.stash) { + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof("any validator should have an entry in the voter list."); + } + } + + fn on_nominator_add(who: &T::AccountId, _prev_nominations: Vec) { + let _ = + T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))) + .defensive(); + } + + /// This should only be called if that stash isn't already a validator. Note, that if we want to + /// properly track ApprovalStake here - we need to make sure we subtract the validator stash + /// balance when they chill? + /// Why? Because we don't remove ApprovalStake when a validator chills and we need to make sure + /// their self-stake is up-to-date and not applied twice. + fn on_validator_add(who: &T::AccountId) { + let self_stake = Self::slashable_balance_of(who); + // maybe update sorted list. + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)).defensive(); + } + + fn on_validator_remove(who: &T::AccountId) { + let _ = T::VoterList::on_remove(who).defensive(); + } + + fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { + let _ = T::VoterList::on_remove(who).defensive(); + } + + fn on_reaped(_who: &T::AccountId) {} +} From 71ad9ab7ec1afdc0a8a9065b99f4a03a52f97789 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 5 Jan 2023 16:29:18 +0100 Subject: [PATCH 03/88] fmt --- frame/stake-tracker/src/lib.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 9f65e16a4f5f9..59840bd911c04 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -17,12 +17,10 @@ #![cfg_attr(not(feature = "std"), no_std)] -use frame_election_provider_support::{ - ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, -}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote, Defensive}; pub use pallet::*; -use sp_runtime::Saturating; + use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; use sp_std::vec::Vec; @@ -34,7 +32,7 @@ pub type BalanceOf = <::Staking as StakingInterface>::Balance; pub mod pallet { use crate::*; use frame_election_provider_support::{SortedListProvider, VoteWeight}; - use frame_support::{pallet_prelude::*, Twox64Concat}; + use frame_support::pallet_prelude::*; use sp_staking::StakingInterface; @@ -108,7 +106,7 @@ impl OnStakingUpdate> for Pallet { let _ = T::VoterList::on_remove(who).defensive(); } - fn on_nominator_remove(who: &T::AccountId, nominations: Vec) { + fn on_nominator_remove(who: &T::AccountId, _nominations: Vec) { let _ = T::VoterList::on_remove(who).defensive(); } From c4c5248cdedc329e9b79e74e5441a884eb220e51 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 5 Jan 2023 21:19:55 +0100 Subject: [PATCH 04/88] make staking tests work with stake-tracker --- Cargo.lock | 1 + frame/stake-tracker/src/lib.rs | 3 +-- frame/staking/Cargo.toml | 1 + frame/staking/src/mock.rs | 8 ++++++++ frame/staking/src/testing_utils.rs | 10 ++++++---- frame/staking/src/tests.rs | 6 +++++- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2472839ef2e63..5f26cbbe426f0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5886,6 +5886,7 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-session", + "pallet-stake-tracker", "pallet-staking-reward-curve", "pallet-timestamp", "parity-scale-codec", diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 59840bd911c04..43c04e365e233 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -87,8 +87,7 @@ impl OnStakingUpdate> for Pallet { fn on_nominator_add(who: &T::AccountId, _prev_nominations: Vec) { let _ = - T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))) - .defensive(); + T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))); } /// This should only be called if that stash isn't already a validator. Note, that if we want to diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index f6b3b95d0beb9..1a9e5cf7e4242 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -48,6 +48,7 @@ substrate-test-utils = { version = "4.0.0-dev", path = "../../test-utils" } frame-benchmarking = { version = "4.0.0-dev", path = "../benchmarking" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } rand_chacha = { version = "0.2" } +pallet-stake-tracker = { version = "4.0.0-dev", path = "../stake-tracker" } [features] default = ["std"] diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 843e452125a6e..de3fe13277bb5 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -99,6 +99,7 @@ frame_support::construct_runtime!( Session: pallet_session::{Pallet, Call, Storage, Event, Config}, Historical: pallet_session::historical::{Pallet, Storage}, VoterBagsList: pallet_bags_list::::{Pallet, Call, Storage, Event}, + StakeTracker: pallet_stake_tracker::{Pallet, Storage}, } ); @@ -310,6 +311,13 @@ impl crate::pallet::pallet::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = StakeTracker; +} + +impl pallet_stake_tracker::Config for Test { + type Currency = Balances; + type Staking = Staking; + type VoterList = VoterBagsList; } pub(crate) type StakingCall = crate::Call; diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 0e0ac76523471..9490306e5c014 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -34,8 +34,10 @@ use sp_std::prelude::*; const SEED: u32 = 0; +pub trait Runtime: Config + pallet_stake_tracker::Config {} + /// This function removes all validators and nominators from storage. -pub fn clear_validators_and_nominators() { +pub fn clear_validators_and_nominators() { #[allow(deprecated)] Validators::::remove_all(); @@ -44,7 +46,7 @@ pub fn clear_validators_and_nominators() { Nominators::::remove_all(); // NOTE: safe to call outside block production - T::VoterList::unsafe_clear(); + ::VoterList::unsafe_clear(); } /// Grab a funded user. @@ -130,7 +132,7 @@ pub fn create_stash_and_dead_controller( } /// create `max` validators. -pub fn create_validators( +pub fn create_validators( max: u32, balance_factor: u32, ) -> Result>, &'static str> { @@ -171,7 +173,7 @@ pub fn create_validators_with_seed( /// them are considered and `edge_per_nominator` random validators are voted for. /// /// Return the validators chosen to be nominated. -pub fn create_validators_with_nominators_for_era( +pub fn create_validators_with_nominators_for_era( validators: u32, nominators: u32, edge_per_nominator: usize, diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 46c3c97441938..bb5babc77a78f 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,7 +18,9 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; -use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; +use frame_election_provider_support::{ + ElectionProvider, ReadOnlySortedListProvider, SortedListProvider, Support, +}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, @@ -5140,6 +5142,8 @@ fn change_of_max_nominations() { // or they can be chilled by any account. assert!(Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); + // TODO: This test now fails, because we don't want to be checking for un-decodables + // anymore as previously discussed. Shall we remove it? assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100)); assert!(!Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); From 58bfb70fd911f3d4c80d0154c4ca252edcdc3e50 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 10:35:24 +0100 Subject: [PATCH 05/88] ignore obsolete test --- frame/staking/src/tests.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index bb5babc77a78f..f4089fbb9852f 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5055,6 +5055,9 @@ fn min_commission_works() { } #[test] +#[ignore] +// TODO: This test should go away as we've decided that we don't cater for max_nominations decrease. +// If this ever happens it should be accompanied by a migration that fixes the storage. fn change_of_max_nominations() { use frame_election_provider_support::ElectionDataProvider; ExtBuilder::default() From 66d6f5adc64e87d11c56b2b04d34571b939a3898 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 10:37:23 +0100 Subject: [PATCH 06/88] clarification --- frame/stake-tracker/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 43c04e365e233..8f54cc3808912 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -86,6 +86,8 @@ impl OnStakingUpdate> for Pallet { } fn on_nominator_add(who: &T::AccountId, _prev_nominations: Vec) { + // NOTE: We ignore the result here, because this method can be called when the nominator is + // already in the list, just changing their nominations. let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))); } From 0de90d4f1bcc0035b89bb32bc60a032d5fa15e74 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 11:59:18 +0100 Subject: [PATCH 07/88] fix tests/benches feature-gated --- frame/bags-list/src/lib.rs | 60 +++++++++++----------- frame/election-provider-support/src/lib.rs | 28 +++++----- frame/nomination-pools/src/mock.rs | 10 +++- frame/staking/src/benchmarking.rs | 6 +-- frame/staking/src/pallet/impls.rs | 50 +++++++++--------- frame/staking/src/testing_utils.rs | 16 +++--- 6 files changed, 89 insertions(+), 81 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index e5e8899a55a87..7c01c349f7e88 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -329,6 +329,36 @@ impl, I: 'static> ReadOnlySortedListProvider for Pall fn try_state() -> Result<(), &'static str> { List::::try_state() } + + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + fn unsafe_clear() { + // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. + // I.e. because it can lead to many storage accesses. + // So it is ok to call it as caller must ensure the conditions. + List::::unsafe_clear() + } + + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + use frame_support::traits::Get as _; + let thresholds = T::BagThresholds::get(); + let node = list::Node::::get(who).unwrap(); + let current_bag_idx = thresholds + .iter() + .chain(sp_std::iter::once(&T::Score::max_value())) + .position(|w| w == &node.bag_upper) + .unwrap(); + + if is_increase { + let next_threshold_idx = current_bag_idx + 1; + assert!(thresholds.len() > next_threshold_idx); + thresholds[next_threshold_idx] + } else { + assert!(current_bag_idx != 0); + let prev_threshold_idx = current_bag_idx - 1; + thresholds[prev_threshold_idx] + } + } + } } impl, I: 'static> SortedListProvider for Pallet { @@ -353,36 +383,6 @@ impl, I: 'static> SortedListProvider for Pallet // So it is ok to call it as caller must ensure the conditions. List::::unsafe_regenerate(all, score_of) } - - fn unsafe_clear() { - // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. - // I.e. because it can lead to many storage accesses. - // So it is ok to call it as caller must ensure the conditions. - List::::unsafe_clear() - } - - frame_election_provider_support::runtime_benchmarks_enabled! { - fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { - use frame_support::traits::Get as _; - let thresholds = T::BagThresholds::get(); - let node = list::Node::::get(who).unwrap(); - let current_bag_idx = thresholds - .iter() - .chain(sp_std::iter::once(&T::Score::max_value())) - .position(|w| w == &node.bag_upper) - .unwrap(); - - if is_increase { - let next_threshold_idx = current_bag_idx + 1; - assert!(thresholds.len() > next_threshold_idx); - thresholds[next_threshold_idx] - } else { - assert!(current_bag_idx != 0); - let prev_threshold_idx = current_bag_idx - 1; - thresholds[prev_threshold_idx] - } - } - } } impl, I: 'static> ScoreProvider for Pallet { diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 6d424b29b4467..288c223c5a6b0 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -503,6 +503,20 @@ pub trait ReadOnlySortedListProvider { /// Check internal state of list. Only meant for debugging. fn try_state() -> Result<(), &'static str>; + + /// Remove all items from the list. + /// + /// ## WARNING + /// + /// This function should never be called in production settings because it can lead to an + /// unbounded amount of storage accesses. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn unsafe_clear(); + + /// If `who` changes by the returned amount they are guaranteed to have a worst case change + /// in their list position. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; } /// A utility trait for something to implement `ElectionDataProvider` in a sensible way. @@ -566,19 +580,6 @@ pub trait SortedListProvider: ReadOnlySortedListProvider { all: impl IntoIterator, score_of: Box Self::Score>, ) -> u32; - - /// Remove all items from the list. - /// - /// ## WARNING - /// - /// This function should never be called in production settings because it can lead to an - /// unbounded amount of storage accesses. - fn unsafe_clear(); - - /// If `who` changes by the returned amount they are guaranteed to have a worst case change - /// in their list position. - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; } /// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and @@ -684,3 +685,4 @@ pub type BoundedSupportsOf = BoundedSupports< sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $); sp_core::generate_feature_enabled_macro!(runtime_benchmarks_or_fuzz_enabled, any(feature = "runtime-benchmarks", feature = "fuzzing"), $); +sp_core::generate_feature_enabled_macro!(runtime_benchmarks_or_test_enabled, any(feature = "runtime-benchmarks", test), $); diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 99d521df3241b..796ee22127eef 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -47,6 +47,7 @@ impl StakingMock { impl sp_staking::StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { StakingMinBond::get() @@ -107,7 +108,6 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - #[cfg(feature = "runtime-benchmarks")] fn nominations(_: Self::AccountId) -> Option> { Nominations::get() } @@ -116,7 +116,9 @@ impl sp_staking::StakingInterface for StakingMock { unimplemented!("method currently not used in testing") } - fn stake(who: &Self::AccountId) -> Result, DispatchError> { + fn stake( + who: &Self::AccountId, + ) -> Result, DispatchError> { match ( UnbondingBalanceMap::get().get(who).map(|v| *v), BondedBalanceMap::get().get(who).map(|v| *v), @@ -153,6 +155,10 @@ impl sp_staking::StakingInterface for StakingMock { fn set_current_era(_era: EraIndex) { unimplemented!("method currently not used in testing") } + + fn is_validator(who: &Self::AccountId) -> bool { + unimplemented!("method currently not used in testing") + } } impl frame_system::Config for Runtime { diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index c6dd47301f99c..0e5d567c60289 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -190,10 +190,8 @@ impl ListScenario { // find a destination weight that will trigger the worst case scenario // TODO: This is a hack, might be great to implement this differently. - let dest_weight_as_vote = >::score_update_worst_case( - &origin_stash1, - is_increase, - ); + let dest_weight_as_vote = + T::VoterList::score_update_worst_case(&origin_stash1, is_increase); let total_issuance = T::Currency::total_issuance(); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index bc321dac3a6cf..bf4824f10114b 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1412,6 +1412,17 @@ impl ReadOnlySortedListProvider for UseValidatorsMap fn try_state() -> Result<(), &'static str> { Ok(()) } + + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + fn unsafe_clear() { + #[allow(deprecated)] + Validators::::remove_all(); + } + + fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { + unimplemented!() + } + } } impl SortedListProvider for UseValidatorsMap { @@ -1435,16 +1446,6 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do upon regenerate. 0 } - - fn unsafe_clear() { - #[allow(deprecated)] - Validators::::remove_all(); - } - - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { - unimplemented!() - } } /// A simple voter list implementation that does not require any additional pallets. Note, this @@ -1492,6 +1493,21 @@ impl ReadOnlySortedListProvider for UseNominatorsAndVal fn try_state() -> Result<(), &'static str> { Ok(()) } + + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + fn unsafe_clear() { + // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a + // condition of SortedListProvider::unsafe_clear. + #[allow(deprecated)] + Nominators::::remove_all(); + #[allow(deprecated)] + Validators::::remove_all(); + } + + fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { + unimplemented!() + } + } } impl SortedListProvider for UseNominatorsAndValidatorsMap { @@ -1515,20 +1531,6 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // nothing to do upon regenerate. 0 } - - fn unsafe_clear() { - // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a - // condition of SortedListProvider::unsafe_clear. - #[allow(deprecated)] - Nominators::::remove_all(); - #[allow(deprecated)] - Validators::::remove_all(); - } - - #[cfg(feature = "runtime-benchmarks")] - fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { - unimplemented!() - } } // NOTE: in this entire impl block, the assumption is that `who` is a stash account. diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 9490306e5c014..5508c8beed4b8 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,17 +27,15 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; const SEED: u32 = 0; -pub trait Runtime: Config + pallet_stake_tracker::Config {} - /// This function removes all validators and nominators from storage. -pub fn clear_validators_and_nominators() { +pub fn clear_validators_and_nominators() { #[allow(deprecated)] Validators::::remove_all(); @@ -45,8 +43,10 @@ pub fn clear_validators_and_nominators() { #[allow(deprecated)] Nominators::::remove_all(); - // NOTE: safe to call outside block production - ::VoterList::unsafe_clear(); + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + // NOTE: safe to call outside block production + T::VoterList::unsafe_clear(); + } } /// Grab a funded user. @@ -132,7 +132,7 @@ pub fn create_stash_and_dead_controller( } /// create `max` validators. -pub fn create_validators( +pub fn create_validators( max: u32, balance_factor: u32, ) -> Result>, &'static str> { @@ -173,7 +173,7 @@ pub fn create_validators_with_seed( /// them are considered and `edge_per_nominator` random validators are voted for. /// /// Return the validators chosen to be nominated. -pub fn create_validators_with_nominators_for_era( +pub fn create_validators_with_nominators_for_era( validators: u32, nominators: u32, edge_per_nominator: usize, From ad54a956c4557447c5cbd177935d4769e332e442 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 12:18:52 +0100 Subject: [PATCH 08/88] StakeTracker to node --- Cargo.lock | 1 + bin/node/runtime/Cargo.toml | 1 + bin/node/runtime/src/lib.rs | 10 +++++++++- 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 5f26cbbe426f0..0cf8ed20d6a08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3127,6 +3127,7 @@ dependencies = [ "pallet-session", "pallet-session-benchmarking", "pallet-society", + "pallet-stake-tracker", "pallet-staking", "pallet-staking-reward-curve", "pallet-state-trie-migration", diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 201e3a85f8941..68ab1f94e1a8d 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -95,6 +95,7 @@ pallet-root-testing = { version = "1.0.0-dev", default-features = false, path = pallet-session = { version = "4.0.0-dev", features = [ "historical" ], path = "../../../frame/session", default-features = false } pallet-session-benchmarking = { version = "4.0.0-dev", path = "../../../frame/session/benchmarking", default-features = false, optional = true } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking" } +pallet-stake-tracker = { version = "4.0.0-dev", default-features = false, path = "../../../frame/stake-tracker" } pallet-staking-reward-curve = { version = "4.0.0-dev", default-features = false, path = "../../../frame/staking/reward-curve" } pallet-state-trie-migration = { version = "4.0.0-dev", default-features = false, path = "../../../frame/state-trie-migration" } pallet-scheduler = { version = "4.0.0-dev", default-features = false, path = "../../../frame/scheduler" } diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index a9e93a16f0713..c64a74adf2e48 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -578,7 +578,7 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; - type VoterList = VoterList; + type VoterList = ::VoterList; // This a placeholder, to be introduced in the next PR as an instance of bags-list type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; @@ -586,6 +586,13 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; + type EventListener = StakeTracker; +} + +impl pallet_stake_tracker::Config for Runtime { + type Currency = Balances; + type Staking = Staking; + type VoterList = VoterList; } impl pallet_fast_unstake::Config for Runtime { @@ -1710,6 +1717,7 @@ construct_runtime!( AssetTxPayment: pallet_asset_tx_payment, ElectionProviderMultiPhase: pallet_election_provider_multi_phase, Staking: pallet_staking, + StakeTracker: pallet_stake_tracker, Session: pallet_session, Democracy: pallet_democracy, Council: pallet_collective::, From 3b1d926565f740c7864a862ab97fac137790d7af Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 13:27:30 +0100 Subject: [PATCH 09/88] more fixes --- Cargo.lock | 1 + frame/babe/src/mock.rs | 1 + frame/bags-list/remote-tests/Cargo.toml | 1 + frame/bags-list/remote-tests/src/lib.rs | 12 ++++++-- frame/bags-list/remote-tests/src/migration.rs | 2 +- frame/fast-unstake/src/mock.rs | 1 + frame/grandpa/src/mock.rs | 1 + .../nomination-pools/benchmarking/src/mock.rs | 1 + frame/nomination-pools/src/mock.rs | 2 +- .../nomination-pools/test-staking/src/mock.rs | 1 + frame/offences/benchmarking/src/mock.rs | 1 + frame/root-offences/src/mock.rs | 1 + frame/session/benchmarking/src/mock.rs | 1 + primitives/staking/src/lib.rs | 28 ++++++++++++++++++- 14 files changed, 48 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0cf8ed20d6a08..579bd691db8ed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4898,6 +4898,7 @@ dependencies = [ "frame-system", "log", "pallet-bags-list", + "pallet-stake-tracker", "pallet-staking", "sp-core", "sp-runtime", diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index dbeb588c1830e..cb912b30da547 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -211,6 +211,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } impl pallet_offences::Config for Test { diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index 9fb6d0154b302..3cdf92c04030d 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -15,6 +15,7 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # frame pallet-staking = { path = "../../staking", version = "4.0.0-dev" } +pallet-stake-tracker = { path = "../../stake-tracker", version = "4.0.0-dev" } pallet-bags-list = { path = "../../bags-list", version = "4.0.0-dev" } frame-election-provider-support = { path = "../../election-provider-support", version = "4.0.0-dev" } frame-system = { path = "../../system", version = "4.0.0-dev" } diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index fc25e3b65ddb1..1642835b7dfb2 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -17,7 +17,7 @@ //! Utilities for remote-testing pallet-bags-list. -use frame_election_provider_support::ScoreProvider; +use frame_election_provider_support::{ReadOnlySortedListProvider, ScoreProvider}; use pallet_bags_list::Instance1; use sp_std::prelude::*; @@ -32,12 +32,18 @@ pub mod try_state; /// /// For example, this can be the `Runtime` type of the Polkadot runtime. pub trait RuntimeT: - pallet_staking::Config + pallet_bags_list::Config + frame_system::Config + pallet_staking::Config + + pallet_stake_tracker::Config + + pallet_bags_list::Config + + frame_system::Config { } impl< I: 'static, - T: pallet_staking::Config + pallet_bags_list::Config + frame_system::Config, + T: pallet_staking::Config + + pallet_stake_tracker::Config + + pallet_bags_list::Config + + frame_system::Config, > RuntimeT for T { } diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index c49898dd856da..17d72164f803d 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -51,7 +51,7 @@ pub async fn execute( use frame_election_provider_support::SortedListProvider; // run the actual migration - let moved = ::VoterList::unsafe_regenerate( + let moved = ::VoterList::unsafe_regenerate( pallet_staking::Nominators::::iter().map(|(n, _)| n), pallet_staking::Pallet::::weight_of_fn(), ); diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 3f974e5e1a9d6..9392d147f6634 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -156,6 +156,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } pub struct BalanceToU256; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 131f6cafcd179..190ebd77044cf 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -215,6 +215,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } impl pallet_offences::Config for Test { diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 06a66838594c7..c55a6f0c0126b 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -119,6 +119,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } parameter_types! { diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index 796ee22127eef..eeb1d61e26e55 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -108,7 +108,7 @@ impl sp_staking::StakingInterface for StakingMock { Ok(()) } - fn nominations(_: Self::AccountId) -> Option> { + fn nominations(_: &Self::AccountId) -> Option> { Nominations::get() } diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index c67aec0134b07..6b2d5742da08c 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -133,6 +133,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } parameter_types! { diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 223f551a6e5c1..4c48ff28ea34b 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -189,6 +189,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } impl pallet_im_online::Config for Test { diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index e1d9ee14ee2fc..97ff970cb20e5 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -200,6 +200,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } impl pallet_session::historical::Config for Test { diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index 0699640bc092a..c73c35b5e25cc 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -183,6 +183,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListener = (); } impl crate::Config for Test {} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 42564611fb46a..e9d903b7afa7a 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -21,7 +21,7 @@ //! approaches in general. Definitions related to sessions, slashing, etc go here. use crate::currency_to_vote::CurrencyToVote; -use codec::{Encode, EncodeLike, FullCodec, MaxEncodedLen}; +use codec::{FullCodec, MaxEncodedLen}; use scale_info::TypeInfo; use sp_runtime::{DispatchError, DispatchResult, Saturating}; use sp_std::{collections::btree_map::BTreeMap, ops::Sub, vec::Vec}; @@ -100,6 +100,32 @@ pub trait OnStakingUpdate { fn on_reaped(who: &AccountId); // -> basically `kill_stash` } +impl OnStakingUpdate for () { + fn on_update_ledger(_: &AccountId, _: Option>) { + // stub + } + + fn on_nominator_add(_: &AccountId, _: Vec) { + // stub + } + + fn on_validator_add(_: &AccountId) { + // stub + } + + fn on_validator_remove(_: &AccountId) { + // stub + } + + fn on_nominator_remove(_: &AccountId, _: Vec) { + // stub + } + + fn on_reaped(_: &AccountId) { + // stub + } +} + /// A generic representation of a staking implementation. /// /// This interface uses the terminology of NPoS, but it is aims to be generic enough to cover other From 16bd6b17c87509d02655cc0e090ad78d34eda655 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 13:44:46 +0100 Subject: [PATCH 10/88] more fixes --- frame/bags-list/src/lib.rs | 2 +- frame/nomination-pools/src/mock.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 7c01c349f7e88..0008dd6c6f122 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -271,7 +271,7 @@ pub mod pallet { #[cfg(feature = "try-runtime")] fn try_state(_: BlockNumberFor) -> Result<(), &'static str> { - >::try_state() + >::try_state() } } } diff --git a/frame/nomination-pools/src/mock.rs b/frame/nomination-pools/src/mock.rs index eeb1d61e26e55..083ca91e041f1 100644 --- a/frame/nomination-pools/src/mock.rs +++ b/frame/nomination-pools/src/mock.rs @@ -156,7 +156,7 @@ impl sp_staking::StakingInterface for StakingMock { unimplemented!("method currently not used in testing") } - fn is_validator(who: &Self::AccountId) -> bool { + fn is_validator(_: &Self::AccountId) -> bool { unimplemented!("method currently not used in testing") } } From 938c4dfcae3199cdce0e8b39d70be70b2aa014e7 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 6 Jan 2023 13:56:24 +0100 Subject: [PATCH 11/88] more fixes --- frame/bags-list/remote-tests/src/lib.rs | 1 - frame/bags-list/src/list/tests.rs | 2 +- frame/nomination-pools/benchmarking/src/lib.rs | 6 +++--- frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/migrations.rs | 1 - frame/staking/src/pallet/mod.rs | 2 +- frame/staking/src/testing_utils.rs | 2 +- frame/staking/src/tests.rs | 5 +---- 8 files changed, 8 insertions(+), 13 deletions(-) diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index 1642835b7dfb2..ea6b08d5cf653 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -57,7 +57,6 @@ pub fn display_and_check_bags>( currency_unit: u64, currency_name: &'static str, ) { - use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; let min_nominator_bond = >::get(); diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index e7512c600f580..9c81ed8c12351 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -20,7 +20,7 @@ use crate::{ mock::{test_utils::*, *}, ListBags, ListNodes, }; -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; +use frame_election_provider_support::{ReadOnlySortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; fn node( diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 9b063539152b7..79c4ffe679d27 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -24,7 +24,7 @@ mod mock; use frame_benchmarking::{account, frame_support::traits::Currency, vec, whitelist_account, Vec}; -use frame_election_provider_support::SortedListProvider; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{assert_ok, ensure, traits::Get}; use frame_system::RawOrigin as RuntimeOrigin; use pallet_nomination_pools::{ @@ -644,12 +644,12 @@ frame_benchmarking::benchmarks! { .collect(); assert_ok!(T::Staking::nominate(&pool_account, validators)); - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_some()); + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_some()); whitelist_account!(depositor); }:_(RuntimeOrigin::Signed(depositor.clone()), 1) verify { - assert!(T::Staking::nominations(Pools::::create_bonded_account(1)).is_none()); + assert!(T::Staking::nominations(&Pools::::create_bonded_account(1)).is_none()); } impl_benchmark_test_suite!( diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 0e5d567c60289..b583c4296419b 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{ConfigOp, Pallet as Staking}; use testing_utils::*; use codec::Decode; -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{ dispatch::UnfilteredDispatchable, pallet_prelude::*, diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index f42cb8f3445c7..5c8b336ee3bd6 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -17,7 +17,6 @@ //! Storage migrations for the Staking pallet. use super::*; -use frame_election_provider_support::SortedListProvider; use frame_support::{ dispatch::GetStorageVersion, pallet_prelude::ValueQuery, storage_alias, traits::OnRuntimeUpgrade, diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index e981f06d74b16..7e058967c36f4 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -24,7 +24,7 @@ use frame_support::{ dispatch::Codec, pallet_prelude::*, traits::{ - Currency, CurrencyToVote, Defensive, DefensiveResult, DefensiveSaturating, EnsureOrigin, + Currency, CurrencyToVote, DefensiveResult, DefensiveSaturating, EnsureOrigin, EstimateNextNewSession, Get, LockIdentifier, LockableCurrency, OnUnbalanced, TryCollect, UnixTime, }, diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 5508c8beed4b8..34779f222a2a7 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,7 +27,7 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index f4089fbb9852f..7efe4977eb46e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,9 +18,7 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; -use frame_election_provider_support::{ - ElectionProvider, ReadOnlySortedListProvider, SortedListProvider, Support, -}; +use frame_election_provider_support::{ElectionProvider, ReadOnlySortedListProvider, Support}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, @@ -5155,7 +5153,6 @@ fn change_of_max_nominations() { mod sorted_list_provider { use super::*; - use frame_election_provider_support::SortedListProvider; #[test] fn re_nominate_does_not_change_counters_or_list() { From 74a2a48e4ea4cb855311163752c834470b867a14 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 8 Jan 2023 11:49:06 +0100 Subject: [PATCH 12/88] fix some build errs --- bin/node/runtime/Cargo.toml | 2 ++ frame/election-provider-support/src/lib.rs | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/bin/node/runtime/Cargo.toml b/bin/node/runtime/Cargo.toml index 68ab1f94e1a8d..433baf5277e16 100644 --- a/bin/node/runtime/Cargo.toml +++ b/bin/node/runtime/Cargo.toml @@ -175,6 +175,7 @@ std = [ "sp-runtime/std", "sp-staking/std", "pallet-staking/std", + "pallet-stake-tracker/std", "pallet-state-trie-migration/std", "sp-session/std", "pallet-sudo/std", @@ -304,6 +305,7 @@ try-runtime = [ "pallet-root-testing/try-runtime", "pallet-session/try-runtime", "pallet-staking/try-runtime", + "pallet-stake-tracker/try-runtime", "pallet-state-trie-migration/try-runtime", "pallet-scheduler/try-runtime", "pallet-society/try-runtime", diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 288c223c5a6b0..548c31d3b30f6 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -474,6 +474,8 @@ where /// A trait that defines a set of functions that allow read access to the underlying storage /// and the types those functions depend upon. /// +/// The scores (see [`Self::Score`]) are ascending, the higher, the better. +/// /// Initially a part of [`SortedListProvider`], it allows for restricting a consumer to read-only /// operations. This is particularly useful in case the list is populated by one entity and /// read by another. @@ -524,8 +526,6 @@ pub trait ReadOnlySortedListProvider { /// This is generic over `AccountId` and it can represent a validator, a nominator, or any other /// entity. /// -/// The scores (see [`Self::Score`]) are ascending, the higher, the better. -/// /// Something that implements this trait will do a best-effort sort over ids, and thus can be /// used on the implementing side of [`ElectionDataProvider`]. /// From d829b919f16f9ec9d9a15ae190bd37a5c37ec8ed Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 8 Jan 2023 12:03:53 +0100 Subject: [PATCH 13/88] doc fixes --- frame/bags-list/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 0008dd6c6f122..ea0c0c9666215 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -20,7 +20,7 @@ //! A semi-sorted list, where items hold an `AccountId` based on some `Score`. The //! `AccountId` (`id` for short) might be synonym to a `voter` or `nominator` in some context, and //! `Score` signifies the chance of each id being included in the final -//! [`SortedListProvider::iter`]. +//! [`ReadOnlySortedListProvider::iter`]. //! //! It implements [`frame_election_provider_support::SortedListProvider`] to provide a semi-sorted //! list of accounts to another pallet. It needs some other pallet to give it some information about From 2ea7d05921eb13b1a2b0f6f231801ad5b7b8e1d7 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 8 Jan 2023 17:51:52 +0100 Subject: [PATCH 14/88] fix nom-pools benches --- Cargo.lock | 1 + frame/nomination-pools/benchmarking/Cargo.toml | 1 + frame/nomination-pools/benchmarking/src/mock.rs | 11 +++++++++-- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 579bd691db8ed..643cba4569aa1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5499,6 +5499,7 @@ dependencies = [ "pallet-bags-list", "pallet-balances", "pallet-nomination-pools", + "pallet-stake-tracker", "pallet-staking", "pallet-staking-reward-curve", "pallet-timestamp", diff --git a/frame/nomination-pools/benchmarking/Cargo.toml b/frame/nomination-pools/benchmarking/Cargo.toml index 74b71a353fe7f..1c8699b015ed7 100644 --- a/frame/nomination-pools/benchmarking/Cargo.toml +++ b/frame/nomination-pools/benchmarking/Cargo.toml @@ -24,6 +24,7 @@ frame-support = { version = "4.0.0-dev", default-features = false, path = "../.. frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" } pallet-bags-list = { version = "4.0.0-dev", default-features = false, path = "../../bags-list" } pallet-staking = { version = "4.0.0-dev", default-features = false, path = "../../staking" } +pallet-stake-tracker = { version = "4.0.0-dev", default-features = false, path = "../../stake-tracker" } pallet-nomination-pools = { version = "1.0.0", default-features = false, path = "../" } # Substrate Primitives diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index c55a6f0c0126b..2e69166662e68 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -112,14 +112,20 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = VoterList; + type VoterList = ::VoterList; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListener = StakeTracker; +} + +impl pallet_stake_tracker::Config for Runtime { + type Currency = Balances; + type Staking = Staking; + type VoterList = VoterList; } parameter_types! { @@ -185,6 +191,7 @@ frame_support::construct_runtime!( Staking: pallet_staking::{Pallet, Call, Config, Storage, Event}, VoterList: pallet_bags_list::::{Pallet, Call, Storage, Event}, Pools: pallet_nomination_pools::{Pallet, Call, Storage, Event}, + StakeTracker: pallet_stake_tracker::{Pallet, Storage}, } ); From 1344c244d29e268deef656bddb1c18ee5868716c Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 14:44:40 +0100 Subject: [PATCH 15/88] Update primitives/staking/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- primitives/staking/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index e9d903b7afa7a..5219a575859b6 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -100,6 +100,7 @@ pub trait OnStakingUpdate { fn on_reaped(who: &AccountId); // -> basically `kill_stash` } +#[cfg(feature = "std")] impl OnStakingUpdate for () { fn on_update_ledger(_: &AccountId, _: Option>) { // stub From 32f50d2bf50cbb2f935eba8fde41009f03624452 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 14:45:23 +0100 Subject: [PATCH 16/88] Update primitives/staking/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- primitives/staking/src/lib.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 5219a575859b6..37cad4fc448f1 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -81,7 +81,8 @@ pub struct Stake { pub active: Balance, } -/// A generic staking event listener. Currently used for implementations involved in stake tracking. +/// A generic staking event listener. +/// /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. From c3a7bd6cb5ef7a0b97b9d6dfbccb06f13bca8799 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 14:47:54 +0100 Subject: [PATCH 17/88] Update primitives/staking/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- primitives/staking/src/lib.rs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 37cad4fc448f1..01d25a3b59a67 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -87,18 +87,22 @@ pub struct Stake { /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { - /// Track ledger updates. - fn on_update_ledger(who: &AccountId, prev_stake: Option>); - /// Track nominators, those reinstated and also new ones. - fn on_nominator_add(who: &AccountId, prev_nominations: Vec); - /// Track validators, those reinstated and new. + /// Fired when the stake amount of someone updates. + /// + /// Also called when someone stakes for the first time. (TODO: is it? this is why we need unit tests for this pallet alone). + /// + /// This is effectively any changes to the bond amount, such as bonding more funds, and unbonding. + fn on_stake_update(who: &AccountId, prev_stake: Option>); + /// Fired when someone sets their intention to nominate, either new, or existing one. + fn on_nominator_update(who: &AccountId, prev_nominations: Vec); + /// Fired when someone sets their intention to validate, either new, or existing one. fn on_validator_add(who: &AccountId); - /// Track removed validators. Either chilled or those that became nominators instead. + /// Fired when someone removes their intention to validate, either due to chill or nominating. fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator - /// Track removed nominators. + /// Fired when someone removes their intention to nominate, either due to chill or validating. fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator - /// Track those participants of staking system that are kicked out for whatever reason. - fn on_reaped(who: &AccountId); // -> basically `kill_stash` + /// fired when someone is fully unstakes. + fn on_unstake(who: &AccountId); // -> basically `kill_stash` } #[cfg(feature = "std")] From 4ce30ed93da250f7ec309d02ad2183df98657fb6 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 14:56:50 +0100 Subject: [PATCH 18/88] Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/staking/src/pallet/mod.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 7e058967c36f4..04c165e0c15d7 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -225,9 +225,8 @@ pub mod pallet { /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. /// /// Invariant: what comes out of this list will always be a nominator. - /// - /// NOTE: Staking does not maintain this list, it merely reads from it. The list is - /// maintained by `EventListener` implementors. + // NOTE: Staking does not maintain this list, it merely reads from it. The list is + // maintained by `EventListener` implementors. type VoterList: ReadOnlySortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going From 2d282173d17b8247e7f634ba9fafb1a2a8555843 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 15:03:03 +0100 Subject: [PATCH 19/88] Update frame/staking/src/pallet/mod.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/staking/src/pallet/mod.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 04c165e0c15d7..823abefc0eeca 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -253,9 +253,6 @@ pub mod pallet { /// Something that listens to staking updates and performs actions based on the data it /// receives. - /// NOTE: Once we have more implementors of EventListener - this could be done similarly to - /// `OnRuntimeUpdate` - just a tuple of all sorts of pallets implementing the trait that all - /// react to the interface methods being called or ignore them. type EventListener: sp_staking::OnStakingUpdate>; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can From 44b318f6bc0407074b975f8680937036c55c4016 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 15:03:50 +0100 Subject: [PATCH 20/88] Update primitives/staking/src/currency_to_vote.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- primitives/staking/src/currency_to_vote.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs index d04f651ae3068..038394971ea56 100644 --- a/primitives/staking/src/currency_to_vote.rs +++ b/primitives/staking/src/currency_to_vote.rs @@ -17,6 +17,7 @@ use sp_arithmetic::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; use sp_runtime::SaturatedConversion; + /// A trait similar to `Convert` to convert values from `B` an abstract balance type /// into u64 and back from u128. (This conversion is used in election and other places where complex /// calculation over balance type is needed) From 8838a07145643e727d0b6166df0bae36128b0355 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 9 Jan 2023 16:06:54 +0100 Subject: [PATCH 21/88] fix OnStakingUpdate occurrences --- frame/stake-tracker/src/lib.rs | 6 +++--- frame/staking/src/pallet/impls.rs | 6 +++--- primitives/staking/src/lib.rs | 20 +++++++++++--------- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 8f54cc3808912..ecd9f2836b73b 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -69,7 +69,7 @@ impl Pallet { } impl OnStakingUpdate> for Pallet { - fn on_update_ledger(who: &T::AccountId, _: Option>>) { + fn on_stake_update(who: &T::AccountId, _: Option>>) { let current_stake = T::Staking::stake(who).unwrap(); let current_active = current_stake.active; @@ -85,7 +85,7 @@ impl OnStakingUpdate> for Pallet { } } - fn on_nominator_add(who: &T::AccountId, _prev_nominations: Vec) { + fn on_nominator_update(who: &T::AccountId, _prev_nominations: Vec) { // NOTE: We ignore the result here, because this method can be called when the nominator is // already in the list, just changing their nominations. let _ = @@ -111,5 +111,5 @@ impl OnStakingUpdate> for Pallet { let _ = T::VoterList::on_remove(who).defensive(); } - fn on_reaped(_who: &T::AccountId) {} + fn on_unstake(_who: &T::AccountId) {} } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index bf4824f10114b..56f8e29aa1663 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -276,7 +276,7 @@ impl Pallet { }); T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); - T::EventListener::on_update_ledger(&ledger.stash, prev_ledger); + T::EventListener::on_stake_update(&ledger.stash, prev_ledger); } /// Chill a stash account. @@ -668,7 +668,7 @@ impl Pallet { Self::do_remove_nominator(stash); frame_system::Pallet::::dec_consumers(stash); - T::EventListener::on_reaped(stash); + T::EventListener::on_unstake(stash); Ok(()) } @@ -880,7 +880,7 @@ impl Pallet { pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { let prev_nominations = Self::nominations(who); Nominators::::insert(who, nominations); - T::EventListener::on_nominator_add(who, prev_nominations.unwrap_or_default()); + T::EventListener::on_nominator_update(who, prev_nominations.unwrap_or_default()); } /// This function will remove a nominator from the `Nominators` storage map, diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 01d25a3b59a67..ae33161df25a6 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -81,21 +81,23 @@ pub struct Stake { pub active: Balance, } -/// A generic staking event listener. -/// +/// A generic staking event listener. +/// /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. /// - /// Also called when someone stakes for the first time. (TODO: is it? this is why we need unit tests for this pallet alone). - /// - /// This is effectively any changes to the bond amount, such as bonding more funds, and unbonding. + /// Also called when someone stakes for the first time. (TODO: is it? this is why we need unit + /// tests for this pallet alone). + /// + /// This is effectively any changes to the bond amount, such as bonding more funds, and + /// unbonding. fn on_stake_update(who: &AccountId, prev_stake: Option>); /// Fired when someone sets their intention to nominate, either new, or existing one. fn on_nominator_update(who: &AccountId, prev_nominations: Vec); - /// Fired when someone sets their intention to validate, either new, or existing one. + /// Fired when someone sets their intention to validate, either new, or existing one. fn on_validator_add(who: &AccountId); /// Fired when someone removes their intention to validate, either due to chill or nominating. fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator @@ -107,11 +109,11 @@ pub trait OnStakingUpdate { #[cfg(feature = "std")] impl OnStakingUpdate for () { - fn on_update_ledger(_: &AccountId, _: Option>) { + fn on_stake_update(_: &AccountId, _: Option>) { // stub } - fn on_nominator_add(_: &AccountId, _: Vec) { + fn on_nominator_update(_: &AccountId, _: Vec) { // stub } @@ -127,7 +129,7 @@ impl OnStakingUpdate for () { // stub } - fn on_reaped(_: &AccountId) { + fn on_unstake(_: &AccountId) { // stub } } From 7d36dc911f7f3902f17d9a2c06d08376d4425053 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 11 Jan 2023 12:25:06 +0100 Subject: [PATCH 22/88] add mock --- Cargo.lock | 2 + frame/stake-tracker/Cargo.toml | 7 ++ frame/stake-tracker/src/lib.rs | 3 + frame/stake-tracker/src/mock.rs | 210 ++++++++++++++++++++++++++++++++ primitives/staking/src/lib.rs | 2 +- 5 files changed, 223 insertions(+), 1 deletion(-) create mode 100644 frame/stake-tracker/src/mock.rs diff --git a/Cargo.lock b/Cargo.lock index 904a3879b65b7..bb4b66e96f5ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6358,12 +6358,14 @@ dependencies = [ "frame-support", "frame-system", "log", + "pallet-bags-list", "pallet-balances", "pallet-staking", "pallet-timestamp", "parity-scale-codec", "scale-info", "sp-io", + "sp-npos-elections", "sp-runtime", "sp-staking", "sp-std", diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index 171984b594d42..fb2c10e966677 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -35,6 +35,9 @@ sp-tracing = { version = "6.0.0", path = "../../primitives/tracing" } pallet-staking = { path = "../staking" } pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } +sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } +pallet-bags-list = { version = "4.0.0-dev", path = "../bags-list" } + @@ -58,10 +61,14 @@ std = [ "frame-benchmarking/std", ] + runtime-benchmarks = [ "frame-benchmarking/runtime-benchmarks", + "frame-election-provider-support/runtime-benchmarks", "frame-system/runtime-benchmarks", "sp-staking/runtime-benchmarks", "pallet-staking/runtime-benchmarks" ] + + try-runtime = ["frame-support/try-runtime"] diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index ecd9f2836b73b..d48b892c4e67d 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -17,6 +17,9 @@ #![cfg_attr(not(feature = "std"), no_std)] +#[cfg(test)] +mod mock; + use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote, Defensive}; pub use pallet::*; diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs new file mode 100644 index 0000000000000..986a5e2e385fe --- /dev/null +++ b/frame/stake-tracker/src/mock.rs @@ -0,0 +1,210 @@ +use crate::{self as pallet_stake_tracker, *}; +use frame_election_provider_support::{ScoreProvider, VoteWeight}; +use frame_support::{parameter_types, weights::constants::RocksDbWeight}; +use sp_runtime::{ + testing::{Header, H256}, + traits::IdentityLookup, + DispatchError, DispatchResult, +}; +use sp_staking::{EraIndex, Stake, StakingInterface}; + +pub(crate) type AccountId = u64; +pub(crate) type AccountIndex = u64; +pub(crate) type BlockNumber = u64; +pub(crate) type Balance = u128; + +type Block = frame_system::mocking::MockBlock; +type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; + +frame_support::construct_runtime!( + pub enum Runtime where + Block = Block, + NodeBlock = Block, + UncheckedExtrinsic = UncheckedExtrinsic, + { + System: frame_system::{Pallet, Call, Config, Storage, Event}, + Balances: pallet_balances::{Pallet, Call, Storage, Config, Event}, + VoterBagsList: pallet_bags_list::::{Pallet, Call, Storage, Event}, + StakeTracker: pallet_stake_tracker::{Pallet, Storage}, + } +); + +parameter_types! { + pub static ExistentialDeposit: Balance = 1; +} + +impl frame_system::Config for Runtime { + type BaseCallFilter = frame_support::traits::Everything; + type BlockWeights = (); + type BlockLength = (); + type DbWeight = RocksDbWeight; + type RuntimeOrigin = RuntimeOrigin; + type Index = AccountIndex; + type BlockNumber = BlockNumber; + type RuntimeCall = RuntimeCall; + type Hash = H256; + type Hashing = ::sp_runtime::traits::BlakeTwo256; + type AccountId = AccountId; + type Lookup = IdentityLookup; + type Header = Header; + type RuntimeEvent = RuntimeEvent; + type BlockHashCount = frame_support::traits::ConstU64<250>; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = pallet_balances::AccountData; + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = frame_support::traits::ConstU32<16>; +} + +impl pallet_balances::Config for Runtime { + type Balance = Balance; + type DustRemoval = (); + type RuntimeEvent = RuntimeEvent; + type ExistentialDeposit = ExistentialDeposit; + type AccountStore = System; + type WeightInfo = (); + type MaxLocks = frame_support::traits::ConstU32<1024>; + type MaxReserves = (); + type ReserveIdentifier = [u8; 8]; +} + +impl pallet_stake_tracker::Config for Runtime { + type Currency = Balances; + type Staking = StakingMock; + type VoterList = VoterBagsList; +} +const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = + [10, 20, 30, 40, 50, 60, 1_000, 2_000, 10_000]; + +parameter_types! { + pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; +} + +type VoterBagsListInstance = pallet_bags_list::Instance1; +impl pallet_bags_list::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type WeightInfo = (); + // Staking is the source of truth for voter bags list, since they are not kept up to date. + type ScoreProvider = StakingMock; + type BagThresholds = BagThresholds; + type Score = VoteWeight; +} + +pub struct StakingMock {} + +// We don't really care about this yet in the context of testing stake-tracker logic. +impl ScoreProvider for StakingMock { + type Score = VoteWeight; + + fn score(_id: &AccountId) -> Self::Score { + VoteWeight::default() + } +} + +impl StakingInterface for StakingMock { + type Balance = Balance; + type AccountId = AccountId; + type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; + + fn minimum_nominator_bond() -> Self::Balance { + unimplemented!("Currently not used.") + } + + fn minimum_validator_bond() -> Self::Balance { + unimplemented!("Currently not used.") + } + + fn stash_by_ctrl(controller: &Self::AccountId) -> Result { + unimplemented!("Currently not used.") + } + + fn bonding_duration() -> EraIndex { + unimplemented!("Currently not used.") + } + + fn current_era() -> EraIndex { + unimplemented!("Currently not used.") + } + + // TODO: Impl + fn stake( + who: &Self::AccountId, + ) -> Result, DispatchError> { + unimplemented!("Currently not used.") + } + + fn bond( + who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn nominate(who: &Self::AccountId, validators: Vec) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn chill(who: &Self::AccountId) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn unbond(stash: &Self::AccountId, value: Self::Balance) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn withdraw_unbonded( + stash: Self::AccountId, + num_slashing_spans: u32, + ) -> Result { + unimplemented!("Currently not used.") + } + + fn desired_validator_count() -> u32 { + unimplemented!("Currently not used.") + } + + fn election_ongoing() -> bool { + unimplemented!("Currently not used.") + } + + fn force_unstake(who: Self::AccountId) -> DispatchResult { + unimplemented!("Currently not used.") + } + + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + unimplemented!("Currently not used.") + } + + // TODO: implement + fn is_validator(who: &Self::AccountId) -> bool { + unimplemented!("Currently not used.") + } + + // TODO: implement + fn nominations(who: &Self::AccountId) -> Option> { + unimplemented!("Currently not used.") + } + + #[cfg(feature = "runtime-benchmarks")] + fn add_era_stakers( + current_era: &EraIndex, + stash: &Self::AccountId, + exposures: Vec<(Self::AccountId, Self::Balance)>, + ) { + unimplemented!("Currently not used.") + } + + #[cfg(feature = "runtime-benchmarks")] + fn set_current_era(era: EraIndex) { + unimplemented!("Currently not used.") + } +} diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index ae33161df25a6..fc074e0da639e 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -103,7 +103,7 @@ pub trait OnStakingUpdate { fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator /// Fired when someone removes their intention to nominate, either due to chill or validating. fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator - /// fired when someone is fully unstakes. + /// fired when someone is fully unstaked. fn on_unstake(who: &AccountId); // -> basically `kill_stash` } From 3446f0eca857b0d84361a0f6299760c48fc2c34d Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 11 Jan 2023 12:37:34 +0100 Subject: [PATCH 23/88] fixes --- frame/stake-tracker/Cargo.toml | 1 + frame/stake-tracker/src/mock.rs | 39 ++++++++++++++++----------------- frame/staking/Cargo.toml | 3 +++ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index fb2c10e966677..2ed0d1e41aaa0 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -37,6 +37,7 @@ pallet-balances = { path = "../balances" } pallet-timestamp = { path = "../timestamp" } sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elections" } pallet-bags-list = { version = "4.0.0-dev", path = "../bags-list" } +frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 986a5e2e385fe..21234079764f1 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -103,6 +103,12 @@ impl ScoreProvider for StakingMock { fn score(_id: &AccountId) -> Self::Score { VoteWeight::default() } + + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + fn set_score_of(_: &AccountId, _: Self::Score) { + // not use yet. + } + } } impl StakingInterface for StakingMock { @@ -118,7 +124,7 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } - fn stash_by_ctrl(controller: &Self::AccountId) -> Result { + fn stash_by_ctrl(_: &Self::AccountId) -> Result { unimplemented!("Currently not used.") } @@ -137,34 +143,27 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } - fn bond( - who: &Self::AccountId, - value: Self::Balance, - payee: &Self::AccountId, - ) -> DispatchResult { + fn bond(_: &Self::AccountId, _: Self::Balance, _: &Self::AccountId) -> DispatchResult { unimplemented!("Currently not used.") } - fn nominate(who: &Self::AccountId, validators: Vec) -> DispatchResult { + fn nominate(_: &Self::AccountId, _: Vec) -> DispatchResult { unimplemented!("Currently not used.") } - fn chill(who: &Self::AccountId) -> DispatchResult { + fn chill(_: &Self::AccountId) -> DispatchResult { unimplemented!("Currently not used.") } - fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + fn bond_extra(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { unimplemented!("Currently not used.") } - fn unbond(stash: &Self::AccountId, value: Self::Balance) -> DispatchResult { + fn unbond(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { unimplemented!("Currently not used.") } - fn withdraw_unbonded( - stash: Self::AccountId, - num_slashing_spans: u32, - ) -> Result { + fn withdraw_unbonded(_: Self::AccountId, _: u32) -> Result { unimplemented!("Currently not used.") } @@ -176,11 +175,11 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } - fn force_unstake(who: Self::AccountId) -> DispatchResult { + fn force_unstake(_: Self::AccountId) -> DispatchResult { unimplemented!("Currently not used.") } - fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + fn is_exposed_in_era(_: &Self::AccountId, _: &EraIndex) -> bool { unimplemented!("Currently not used.") } @@ -196,15 +195,15 @@ impl StakingInterface for StakingMock { #[cfg(feature = "runtime-benchmarks")] fn add_era_stakers( - current_era: &EraIndex, - stash: &Self::AccountId, - exposures: Vec<(Self::AccountId, Self::Balance)>, + _: &EraIndex, + _: &Self::AccountId, + _: Vec<(Self::AccountId, Self::Balance)>, ) { unimplemented!("Currently not used.") } #[cfg(feature = "runtime-benchmarks")] - fn set_current_era(era: EraIndex) { + fn set_current_era(_: EraIndex) { unimplemented!("Currently not used.") } } diff --git a/frame/staking/Cargo.toml b/frame/staking/Cargo.toml index 1a9e5cf7e4242..5e342416fe444 100644 --- a/frame/staking/Cargo.toml +++ b/frame/staking/Cargo.toml @@ -75,4 +75,7 @@ runtime-benchmarks = [ "rand_chacha", "sp-staking/runtime-benchmarks", ] +fuzz = [ + "frame-election-provider-support/fuzz" +] try-runtime = ["frame-support/try-runtime"] From ead8b818f3d0bdbf1ba9d6a5d9cb4e60a56ff5f9 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 12 Jan 2023 18:38:04 +0100 Subject: [PATCH 24/88] remove defenisive, s, some tests --- frame/stake-tracker/src/lib.rs | 16 ++++---- frame/stake-tracker/src/mock.rs | 65 +++++++++++++++++++++++++++++--- frame/stake-tracker/src/tests.rs | 52 +++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 14 deletions(-) create mode 100644 frame/stake-tracker/src/tests.rs diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index d48b892c4e67d..605cc0aef8c0b 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -18,7 +18,9 @@ #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] -mod mock; +pub(crate) mod mock; +#[cfg(test)] +mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote, Defensive}; @@ -78,13 +80,11 @@ impl OnStakingUpdate> for Pallet { // if this is a nominator if let Some(_) = T::Staking::nominations(¤t_stake.stash) { - let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) - .defensive_proof("any nominator should have an entry in the voter list."); + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); } if T::Staking::is_validator(¤t_stake.stash) { - let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) - .defensive_proof("any validator should have an entry in the voter list."); + let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); } } @@ -103,15 +103,15 @@ impl OnStakingUpdate> for Pallet { fn on_validator_add(who: &T::AccountId) { let self_stake = Self::slashable_balance_of(who); // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)).defensive(); + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)); } fn on_validator_remove(who: &T::AccountId) { - let _ = T::VoterList::on_remove(who).defensive(); + let _ = T::VoterList::on_remove(who); } fn on_nominator_remove(who: &T::AccountId, _nominations: Vec) { - let _ = T::VoterList::on_remove(who).defensive(); + let _ = T::VoterList::on_remove(who); } fn on_unstake(_who: &T::AccountId) {} diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 21234079764f1..d38364fb87137 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -7,6 +7,7 @@ use sp_runtime::{ DispatchError, DispatchResult, }; use sp_staking::{EraIndex, Stake, StakingInterface}; +use Currency; pub(crate) type AccountId = u64; pub(crate) type AccountIndex = u64; @@ -136,11 +137,15 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } - // TODO: Impl fn stake( who: &Self::AccountId, ) -> Result, DispatchError> { - unimplemented!("Currently not used.") + let stake = ::Currency::total_balance(who); + Ok(Stake { + stash: *who, + active: stake.saturating_sub(ExistentialDeposit::get()), + total: stake, + }) } fn bond(_: &Self::AccountId, _: Self::Balance, _: &Self::AccountId) -> DispatchResult { @@ -183,14 +188,16 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } - // TODO: implement fn is_validator(who: &Self::AccountId) -> bool { - unimplemented!("Currently not used.") + *who >= 10 && *who <= 14 } - // TODO: implement fn nominations(who: &Self::AccountId) -> Option> { - unimplemented!("Currently not used.") + if *who >= 20 && *who <= 24 { + Some(Vec::new()) + } else { + None + } } #[cfg(feature = "runtime-benchmarks")] @@ -207,3 +214,49 @@ impl StakingInterface for StakingMock { unimplemented!("Currently not used.") } } + +#[derive(Default)] +pub struct ExtBuilder {} + +impl ExtBuilder { + fn build(self) -> sp_io::TestExternalities { + sp_tracing::try_init_simple(); + + let mut storage = + frame_system::GenesisConfig::default().build_storage::().unwrap(); + + let _ = pallet_balances::GenesisConfig:: { + balances: vec![ + // Random users, used to test some edge-cases, where we don't want the user to be + // neither a nominator nor validator. + (1, 10), + (2, 20), + (3, 30), + // Validator stashes, for simplicity we assume stash == controller as StakeTracker + // really does not care. + (10, 10), + (11, 20), + (12, 30), + (13, 40), + (14, 50), + // nominators + (20, 10), + (21, 20), + (22, 30), + (23, 40), + (24, 50), + ], + } + .assimilate_storage(&mut storage); + + let mut ext = sp_io::TestExternalities::from(storage); + + ext + } + + pub fn build_and_execute(self, test: impl FnOnce() -> ()) { + sp_tracing::try_init_simple(); + let mut ext = self.build(); + ext.execute_with(test); + } +} diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs new file mode 100644 index 0000000000000..5affbaf818f0d --- /dev/null +++ b/frame/stake-tracker/src/tests.rs @@ -0,0 +1,52 @@ +use super::{mock::*, pallet::*}; +use crate as pallet_stake_tracker; +use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; +use frame_support::assert_storage_noop; +use sp_staking::{OnStakingUpdate, StakingInterface}; + +type VoterList = ::VoterList; +type Staking = ::Staking; + +mod on_stake_update { + use super::*; + #[test] + fn noop_when_not_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + // usual user + assert_storage_noop!(StakeTracker::on_stake_update(&1, None)); + // validator + assert_storage_noop!(StakeTracker::on_stake_update(&10, None)); + // nominator + assert_storage_noop!(StakeTracker::on_stake_update(&20, None)); + }); + } + + #[test] + fn noop_when_not_validator_or_nominator() { + ExtBuilder::default().build_and_execute(|| { + VoterList::on_insert(1, 10000).unwrap(); + // usual user + assert_storage_noop!(StakeTracker::on_stake_update(&1, None)); + }); + } + + #[test] + fn works_for_validators_and_nominators() { + ExtBuilder::default().build_and_execute(|| { + let score = 1000; + assert_eq!(VoterList::count(), 0); + // validator, nominator + for (idx, id) in [10, 20].iter().enumerate() { + let _ = VoterList::on_insert(*id, score).unwrap(); + assert_eq!(VoterList::count() as usize, idx + 1); + assert_eq!(VoterList::get_score(id).unwrap(), score); + let _ = StakeTracker::on_stake_update(id, None); + assert_eq!( + VoterList::get_score(id).unwrap(), + Pallet::::to_vote(Staking::stake(id).map(|s| s.active).unwrap()) + ); + } + }); + } +} From 387b7b4eb7a4f5cd538307acab81145657cdae28 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 12 Jan 2023 19:09:55 +0100 Subject: [PATCH 25/88] basic tests finished --- frame/stake-tracker/src/mock.rs | 3 + frame/stake-tracker/src/tests.rs | 180 +++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+) diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index d38364fb87137..9b007050e47ae 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -140,6 +140,9 @@ impl StakingInterface for StakingMock { fn stake( who: &Self::AccountId, ) -> Result, DispatchError> { + if *who >= 30 { + return Err(DispatchError::Other("not bonded")) + } let stake = ::Currency::total_balance(who); Ok(Stake { stash: *who, diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 5affbaf818f0d..c522a1fb12e8a 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -22,6 +22,16 @@ mod on_stake_update { }); } + #[test] + #[should_panic] + fn panics_when_not_bonded() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + // user without stake + assert_storage_noop!(StakeTracker::on_stake_update(&30, None)); + }); + } + #[test] fn noop_when_not_validator_or_nominator() { ExtBuilder::default().build_and_execute(|| { @@ -50,3 +60,173 @@ mod on_stake_update { }); } } + +mod on_nominator_update { + use super::*; + #[test] + fn noop_when_in_the_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for (idx, id) in [1, 10, 20].iter().enumerate() { + let _ = VoterList::on_insert(*id, 1000); + assert_storage_noop!(StakeTracker::on_nominator_update(id, Vec::new())); + } + }); + } + + #[test] + #[should_panic] + fn panics_when_not_bonded() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + // user without stake + assert_storage_noop!(StakeTracker::on_nominator_update(&30, Vec::new())); + }); + } + + #[test] + // It is the caller's problem to make sure `on_nominator_update` is called in the right context. + fn works_for_everyone() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for id in [1, 10, 20] { + StakeTracker::on_nominator_update(&id, Vec::new()); + assert_eq!( + VoterList::get_score(&id).unwrap(), + Pallet::::to_vote(Staking::stake(&id).map(|s| s.active).unwrap()) + ); + } + }); + } +} + +mod on_validator_add { + use super::*; + #[test] + fn noop_when_in_the_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for id in [1, 10, 20] { + let _ = VoterList::on_insert(id, 1000); + assert_storage_noop!(StakeTracker::on_validator_add(&id)); + } + }); + } + + #[test] + #[should_panic] + fn panics_when_not_bonded() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + // user without stake + assert_storage_noop!(StakeTracker::on_validator_add(&30)); + }); + } + + #[test] + // It is the caller's problem to make sure `on_validator_add` is called in the right context. + fn works_for_everyone() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for id in [1, 10, 20] { + StakeTracker::on_validator_add(&id); + assert_eq!( + VoterList::get_score(&id).unwrap(), + Pallet::::to_vote(Staking::stake(&id).map(|s| s.active).unwrap()) + ); + } + }); + } +} + +mod on_validator_remove { + use super::*; + #[test] + fn noop_when_not_in_the_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator, not bonded + for id in [1, 10, 20, 30] { + assert_storage_noop!(StakeTracker::on_validator_remove(&id)); + } + }); + } + + #[test] + // It is the caller's problem to make sure `on_validator_remove` is called in the right context. + fn works_for_everyone_also_unbonded() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for id in [1, 10, 20, 30] { + let _ = VoterList::on_insert(id, 100); + assert_eq!(VoterList::count(), 1); + StakeTracker::on_validator_remove(&id); + assert_eq!(VoterList::count(), 0); + } + }); + } +} + +mod on_nominator_remove { + use super::*; + #[test] + fn noop_when_not_in_the_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator, not bonded + for id in [1, 10, 20, 30] { + assert_storage_noop!(StakeTracker::on_nominator_remove(&id, Vec::new())); + } + }); + } + + #[test] + // It is the caller's problem to make sure `on_nominator_remove` is called in the right context. + fn works_for_everyone_also_unbonded() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator + for id in [1, 10, 20, 30] { + let _ = VoterList::on_insert(id, 100); + assert_eq!(VoterList::count(), 1); + StakeTracker::on_nominator_remove(&id, Vec::new()); + assert_eq!(VoterList::count(), 0); + } + }); + } +} + +mod on_unstake { + use super::*; + + #[test] + fn noop() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // usual user, validator, nominator, not bonded + for id in [1, 10, 20, 30] { + assert_storage_noop!(StakeTracker::on_unstake(&id)); + } + + // usual user, validator, nominator, not bonded + for id in [1, 10, 20, 30] { + VoterList::on_insert(id, 100); + assert_storage_noop!(StakeTracker::on_unstake(&id)); + } + }); + } +} From 9e561e9f06080d7200a334dba318d60136e2c0bb Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 13 Jan 2023 17:35:02 +0100 Subject: [PATCH 26/88] still unsure what to do with un-decodables --- frame/staking/src/pallet/mod.rs | 2 ++ frame/staking/src/tests.rs | 7 +++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index aecf7f2da0432..a6d1e3e87ec56 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -1730,6 +1730,8 @@ pub mod pallet { // // Otherwise, if caller is the same as the controller, this is just like `chill`. + // TODO: This no longer works, we need nominations to be decodable, because we pass + // nominations to EventHandler for processing. if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { Self::chill_stash(&stash); return Ok(()) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 7efe4977eb46e..d523d5450262a 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5054,8 +5054,8 @@ fn min_commission_works() { #[test] #[ignore] -// TODO: This test should go away as we've decided that we don't cater for max_nominations decrease. -// If this ever happens it should be accompanied by a migration that fixes the storage. +// TODO: We can't chill un-decodable nominators as we need to take into account every validator they +// were backing. fn change_of_max_nominations() { use frame_election_provider_support::ElectionDataProvider; ExtBuilder::default() @@ -5143,8 +5143,7 @@ fn change_of_max_nominations() { // or they can be chilled by any account. assert!(Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); - // TODO: This test now fails, because we don't want to be checking for un-decodables - // anymore as previously discussed. Shall we remove it? + assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100)); assert!(!Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); From 363eb6f39b85c64ee571e75f1d29f5f9c57c0eb4 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 13 Jan 2023 17:40:57 +0100 Subject: [PATCH 27/88] more comments --- frame/staking/src/tests.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d523d5450262a..a3cbf37545cfa 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5131,6 +5131,9 @@ fn change_of_max_nominations() { assert!(Nominators::::get(60).is_some()); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); + // TODO: This has to be fixed, we can't re-nominate without reporting previous + // nominations to the EventHandler. + // now one of them can revive themselves by re-nominating to a proper value. assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1])); assert_eq!( @@ -5140,6 +5143,7 @@ fn change_of_max_nominations() { vec![(70, 1), (60, 1)] ); + // TODO: This part has to be removed, we need to be able to decode old nominations. // or they can be chilled by any account. assert!(Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); From 2c6275e91d188ccc236ec85760f05ba0d3aff01a Mon Sep 17 00:00:00 2001 From: kianenigma Date: Sat, 14 Jan 2023 22:31:39 -0300 Subject: [PATCH 28/88] fix migrations --- frame/staking/src/migrations.rs | 519 ++++++++++++++++---------------- 1 file changed, 267 insertions(+), 252 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 5c8b336ee3bd6..bc3cea9aebc01 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -22,34 +22,38 @@ use frame_support::{ traits::OnRuntimeUpgrade, }; -/// Used for release versioning upto v12. -/// -/// Obsolete from v13. Keeping around to make encoding/decoding of old migration code easier. -#[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] -enum ObsoleteReleases { - V1_0_0Ancient, - V2_0_0, - V3_0_0, - V4_0_0, - V5_0_0, // blockable validators. - V6_0_0, // removal of all storage associated with offchain phragmen. - V7_0_0, // keep track of number of nominators / validators in map - V8_0_0, // populate `VoterList`. - V9_0_0, // inject validators into `VoterList` as well. - V10_0_0, // remove `EarliestUnappliedSlash`. - V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList - V12_0_0, // remove `HistoryDepth`. -} +mod obsolete { + use super::*; + /// Used for release versioning upto v12. + /// + /// Obsolete from v13. Keeping around to make encoding/decoding of old migration code easier. + #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] + pub(super) enum Releases { + V1_0_0Ancient, + V2_0_0, + V3_0_0, + V4_0_0, + V5_0_0, // blockable validators. + V6_0_0, // removal of all storage associated with offchain phragmen. + V7_0_0, // keep track of number of nominators / validators in map + V8_0_0, // populate `VoterList`. + V9_0_0, // inject validators into `VoterList` as well. + V10_0_0, // remove `EarliestUnappliedSlash`. + V11_0_0, // Move pallet storage prefix, e.g. BagsList -> VoterBagsList + V12_0_0, // remove `HistoryDepth`. + } -impl Default for ObsoleteReleases { - fn default() -> Self { - ObsoleteReleases::V12_0_0 + impl Default for Releases { + fn default() -> Self { + Releases::V12_0_0 + } } + + /// Alias to the old storage item used for release versioning. Obsolete since v13. + #[storage_alias] + pub(super) type StorageVersion = StorageValue, Releases, ValueQuery>; } -/// Alias to the old storage item used for release versioning. Obsolete since v13. -#[storage_alias] -type StorageVersion = StorageValue, ObsoleteReleases, ValueQuery>; pub mod v13 { use super::*; @@ -59,7 +63,7 @@ pub mod v13 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V12_0_0, + StorageVersion::::get() == obsolete::Releases::V12_0_0, "Required v12 before upgrading to v13" ); @@ -68,10 +72,10 @@ pub mod v13 { fn on_runtime_upgrade() -> Weight { let current = Pallet::::current_storage_version(); - let onchain = StorageVersion::::get(); + let onchain = obsolete::StorageVersion::::get(); - if current == 13 && onchain == ObsoleteReleases::V12_0_0 { - StorageVersion::::kill(); + if current == 13 && onchain == obsolete::Releases::V12_0_0 { + obsolete::StorageVersion::::kill(); current.put::>(); log!(info, "v13 applied successfully"); @@ -115,7 +119,7 @@ pub mod v12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V11_0_0, + StorageVersion::::get() == obsolete::Releases::V11_0_0, "Expected v11 before upgrading to v12" ); @@ -132,9 +136,9 @@ pub mod v12 { } fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == ObsoleteReleases::V11_0_0 { + if obsolete::StorageVersion::::get() == obsolete::Releases::V11_0_0 { HistoryDepth::::kill(); - StorageVersion::::put(ObsoleteReleases::V12_0_0); + obsolete::StorageVersion::::put(obsolete::Releases::V12_0_0); log!(info, "v12 applied successfully"); T::DbWeight::get().reads_writes(1, 2) @@ -147,7 +151,7 @@ pub mod v12 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V12_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V12_0_0, "v12 not applied" ); Ok(()) @@ -171,7 +175,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V10_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V10_0_0, "must upgrade linearly" ); let old_pallet_prefix = twox_128(N::get().as_bytes()); @@ -196,9 +200,9 @@ pub mod v11 { let old_pallet_name = N::get(); let new_pallet_name =

::name(); - if StorageVersion::::get() == ObsoleteReleases::V10_0_0 { + if obsolete::StorageVersion::::get() == obsolete::Releases::V10_0_0 { // bump version anyway, even if we don't need to move the prefix - StorageVersion::::put(ObsoleteReleases::V11_0_0); + obsolete::StorageVersion::::put(obsolete::Releases::V11_0_0); if new_pallet_name == old_pallet_name { log!( warn, @@ -218,7 +222,7 @@ pub mod v11 { #[cfg(feature = "try-runtime")] fn post_upgrade(_state: Vec) -> Result<(), &'static str> { frame_support::ensure!( - StorageVersion::::get() == ObsoleteReleases::V11_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V11_0_0, "wrong version after the upgrade" ); @@ -263,7 +267,7 @@ pub mod v10 { pub struct MigrateToV10(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { + if obsolete::StorageVersion::::get() == obsolete::Releases::V9_0_0 { let pending_slashes = as Store>::UnappliedSlashes::iter().take(512); for (era, slashes) in pending_slashes { for slash in slashes { @@ -275,7 +279,7 @@ pub mod v10 { } EarliestUnappliedSlash::::kill(); - StorageVersion::::put(ObsoleteReleases::V10_0_0); + obsolete::StorageVersion::::put(obsolete::Releases::V10_0_0); log!(info, "MigrateToV10 executed successfully"); T::DbWeight::get().reads_writes(1, 1) @@ -287,217 +291,228 @@ pub mod v10 { } } -// These migrations have been commented out as they are no longer compatible with the current -// codebase. -// -// pub mod v9 { -// use super::*; -// #[cfg(feature = "try-runtime")] -// use frame_support::codec::{Decode, Encode}; -// #[cfg(feature = "try-runtime")] -// use sp_std::vec::Vec; -// -// /// Migration implementation that injects all validators into sorted list. -// /// -// /// This is only useful for chains that started their `VoterList` just based on nominators. -// pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); -// impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { -// fn on_runtime_upgrade() -> Weight { -// if StorageVersion::::get() == ObsoleteReleases::V8_0_0 { -// let prev_count = T::VoterList::count(); -// let weight_of_cached = Pallet::::weight_of_fn(); -// for (v, _) in Validators::::iter() { -// let weight = weight_of_cached(&v); -// let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { -// log!(warn, "failed to insert {:?} into VoterList: {:?}", v, err) -// }); -// } -// -// log!( -// info, -// "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", -// Validators::::count(), -// prev_count, -// T::VoterList::count(), -// ); -// -// StorageVersion::::put(ObsoleteReleases::V9_0_0); -// T::BlockWeights::get().max_block -// } else { -// log!( -// warn, -// "InjectValidatorsIntoVoterList being executed on the wrong storage \ -// version, expected ObsoleteReleases::V8_0_0" -// ); -// T::DbWeight::get().reads(1) -// } -// } -// -// #[cfg(feature = "try-runtime")] -// fn pre_upgrade() -> Result, &'static str> { -// frame_support::ensure!( -// StorageVersion::::get() == ObsoleteReleases::V8_0_0, -// "must upgrade linearly" -// ); -// -// let prev_count = T::VoterList::count(); -// Ok(prev_count.encode()) -// } -// -// #[cfg(feature = "try-runtime")] -// fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { -// let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( -// "the state parameter should be something that was generated by pre_upgrade", -// ); -// let post_count = T::VoterList::count(); -// let validators = Validators::::count(); -// assert!(post_count == prev_count + validators); -// -// frame_support::ensure!( -// StorageVersion::::get() == ObsoleteReleases::V9_0_0, -// "must upgrade " -// ); -// Ok(()) -// } -// } -// } -// -// pub mod v8 { -// use super::*; -// use crate::{Config, Nominators, Pallet, Weight}; -// use frame_election_provider_support::SortedListProvider; -// use frame_support::traits::Get; -// -// #[cfg(feature = "try-runtime")] -// pub fn pre_migrate() -> Result<(), &'static str> { -// frame_support::ensure!( -// StorageVersion::::get() == ObsoleteReleases::V7_0_0, -// "must upgrade linearly" -// ); -// -// crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); -// Ok(()) -// } -// -// /// Migration to sorted `VoterList`. -// pub fn migrate() -> Weight { -// if StorageVersion::::get() == ObsoleteReleases::V7_0_0 { -// crate::log!(info, "migrating staking to ObsoleteReleases::V8_0_0"); -// -// let migrated = T::VoterList::unsafe_regenerate( -// Nominators::::iter().map(|(id, _)| id), -// Pallet::::weight_of_fn(), -// ); -// debug_assert_eq!(T::VoterList::try_state(), Ok(())); -// -// StorageVersion::::put(ObsoleteReleases::V8_0_0); -// crate::log!( -// info, -// "👜 completed staking migration to ObsoleteReleases::V8_0_0 with {} voters migrated", -// migrated, -// ); -// -// T::BlockWeights::get().max_block -// } else { -// T::DbWeight::get().reads(1) -// } -// } -// -// #[cfg(feature = "try-runtime")] -// pub fn post_migrate() -> Result<(), &'static str> { -// T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; -// crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); -// Ok(()) -// } -// } -// -// pub mod v7 { -// use super::*; -// use frame_support::storage_alias; -// -// #[storage_alias] -// type CounterForValidators = StorageValue, u32>; -// #[storage_alias] -// type CounterForNominators = StorageValue, u32>; -// -// pub fn pre_migrate() -> Result<(), &'static str> { -// assert!( -// CounterForValidators::::get().unwrap().is_zero(), -// "CounterForValidators already set." -// ); -// assert!( -// CounterForNominators::::get().unwrap().is_zero(), -// "CounterForNominators already set." -// ); -// assert!(Validators::::count().is_zero(), "Validators already set."); -// assert!(Nominators::::count().is_zero(), "Nominators already set."); -// assert!(StorageVersion::::get() == ObsoleteReleases::V6_0_0); -// Ok(()) -// } -// -// pub fn migrate() -> Weight { -// log!(info, "Migrating staking to ObsoleteReleases::V7_0_0"); -// let validator_count = Validators::::iter().count() as u32; -// let nominator_count = Nominators::::iter().count() as u32; -// -// CounterForValidators::::put(validator_count); -// CounterForNominators::::put(nominator_count); -// -// StorageVersion::::put(ObsoleteReleases::V7_0_0); -// log!(info, "Completed staking migration to ObsoleteReleases::V7_0_0"); -// -// T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) -// } -// } -// -// pub mod v6 { -// use super::*; -// use frame_support::{storage_alias, traits::Get, weights::Weight}; -// -// // NOTE: value type doesn't matter, we just set it to () here. -// #[storage_alias] -// type SnapshotValidators = StorageValue, ()>; -// #[storage_alias] -// type SnapshotNominators = StorageValue, ()>; -// #[storage_alias] -// type QueuedElected = StorageValue, ()>; -// #[storage_alias] -// type QueuedScore = StorageValue, ()>; -// #[storage_alias] -// type EraElectionStatus = StorageValue, ()>; -// #[storage_alias] -// type IsCurrentSessionFinal = StorageValue, ()>; -// -// /// check to execute prior to migration. -// pub fn pre_migrate() -> Result<(), &'static str> { -// // these may or may not exist. -// log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); -// log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); -// log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); -// log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); -// // these must exist. -// assert!( -// IsCurrentSessionFinal::::exists(), -// "IsCurrentSessionFinal storage item not found!" -// ); -// assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); -// Ok(()) -// } -// -// /// Migrate storage to v6. -// pub fn migrate() -> Weight { -// log!(info, "Migrating staking to ObsoleteReleases::V6_0_0"); -// -// SnapshotValidators::::kill(); -// SnapshotNominators::::kill(); -// QueuedElected::::kill(); -// QueuedScore::::kill(); -// EraElectionStatus::::kill(); -// IsCurrentSessionFinal::::kill(); -// -// StorageVersion::::put(ObsoleteReleases::V6_0_0); -// -// log!(info, "Done."); -// T::DbWeight::get().writes(6 + 1) -// } -// } +pub mod v9 { + use super::*; + #[cfg(feature = "try-runtime")] + use frame_support::codec::{Decode, Encode}; + #[cfg(feature = "try-runtime")] + use sp_std::vec::Vec; + use frame_election_provider_support::{SortedListProvider, ReadOnlySortedListProvider}; + + pub trait MigrationConfig { + type Config: Config; + type VoterList: frame_election_provider_support::SortedListProvider< + ::AccountId, + Score = u64 + >; + } + + /// Migration implementation that injects all validators into sorted list. + /// + /// This is only useful for chains that started their `VoterList` just based on nominators. + pub struct InjectValidatorsIntoVoterList(sp_std::marker::PhantomData); + impl OnRuntimeUpgrade for InjectValidatorsIntoVoterList { + fn on_runtime_upgrade() -> Weight { + if obsolete::StorageVersion::::get() == obsolete::Releases::V8_0_0 { + let prev_count = T::VoterList::count(); + let weight_of_cached = Pallet::::weight_of_fn(); + for (v, _) in Validators::::iter() { + let weight = weight_of_cached(&v); + let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { + frame_support::log::warn!("failed to insert {:?} into VoterList: {:?}", v, err) + }); + } + + frame_support::log::info!( + "injected a total of {} new voters, prev count: {} next count: {}, updating to version 9", + Validators::::count(), + prev_count, + T::VoterList::count(), + ); + + obsolete::StorageVersion::::put(obsolete::Releases::V9_0_0); + ::BlockWeights::get().max_block + } else { + frame_support::log::warn!( + "InjectValidatorsIntoVoterList being executed on the wrong storage \ + version, expected obsolete::Releases::V8_0_0" + ); + ::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, &'static str> { + frame_support::ensure!( + obsolete::StorageVersion::::get() == obsolete::Releases::V8_0_0, + "must upgrade linearly" + ); + + let prev_count = T::VoterList::count(); + Ok(prev_count.encode()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(prev_count: Vec) -> Result<(), &'static str> { + let prev_count: u32 = Decode::decode(&mut prev_count.as_slice()).expect( + "the state parameter should be something that was generated by pre_upgrade", + ); + let post_count = T::VoterList::count(); + let validators = Validators::::count(); + assert!(post_count == prev_count + validators); + + frame_support::ensure!( + obsolete::StorageVersion::::get() == obsolete::Releases::V9_0_0, + "must upgrade " + ); + Ok(()) + } + } +} + +pub mod v8 { + use super::*; + use crate::{Config, Nominators, Pallet, Weight}; + use frame_election_provider_support::{SortedListProvider, ReadOnlySortedListProvider}; + use frame_support::traits::Get; + + pub trait MigrationConfig { + type Config: Config; + type VoterList: frame_election_provider_support::SortedListProvider< + ::AccountId, + Score = u64 + >; + } + + #[cfg(feature = "try-runtime")] + pub fn pre_migrate() -> Result<(), &'static str> { + frame_support::ensure!( + obsolete::StorageVersion::::get() == obsolete::Releases::V7_0_0, + "must upgrade linearly" + ); + + crate::log!(info, "👜 staking bags-list migration passes PRE migrate checks ✅",); + Ok(()) + } + + /// Migration to sorted `VoterList`. + pub fn migrate() -> Weight { + if obsolete::StorageVersion::::get() == obsolete::Releases::V7_0_0 { + frame_support::log::info!("migrating staking to obsolete::Releases::V8_0_0"); + + let migrated = T::VoterList::unsafe_regenerate( + Nominators::::iter().map(|(id, _)| id), + Pallet::::weight_of_fn(), + ); + debug_assert_eq!(T::VoterList::try_state(), Ok(())); + + obsolete::StorageVersion::::put(obsolete::Releases::V8_0_0); + frame_support::log::info!( + "👜 completed staking migration to obsolete::Releases::V8_0_0 with {} voters migrated", + migrated, + ); + + ::BlockWeights::get().max_block + } else { + ::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + pub fn post_migrate() -> Result<(), &'static str> { + T::VoterList::try_state().map_err(|_| "VoterList is not in a sane state.")?; + crate::log!(info, "👜 staking bags-list migration passes POST migrate checks ✅",); + Ok(()) + } +} + +pub mod v7 { + use super::*; + use frame_support::storage_alias; + + #[storage_alias] + type CounterForValidators = StorageValue, u32>; + #[storage_alias] + type CounterForNominators = StorageValue, u32>; + + pub fn pre_migrate() -> Result<(), &'static str> { + assert!( + CounterForValidators::::get().unwrap().is_zero(), + "CounterForValidators already set." + ); + assert!( + CounterForNominators::::get().unwrap().is_zero(), + "CounterForNominators already set." + ); + assert!(Validators::::count().is_zero(), "Validators already set."); + assert!(Nominators::::count().is_zero(), "Nominators already set."); + assert!(obsolete::StorageVersion::::get() == obsolete::Releases::V6_0_0); + Ok(()) + } + + pub fn migrate() -> Weight { + log!(info, "Migrating staking to obsolete::Releases::V7_0_0"); + let validator_count = Validators::::iter().count() as u32; + let nominator_count = Nominators::::iter().count() as u32; + + CounterForValidators::::put(validator_count); + CounterForNominators::::put(nominator_count); + + obsolete::StorageVersion::::put(obsolete::Releases::V7_0_0); + log!(info, "Completed staking migration to obsolete::Releases::V7_0_0"); + + T::DbWeight::get().reads_writes(validator_count.saturating_add(nominator_count).into(), 2) + } +} + +pub mod v6 { + use super::*; + use frame_support::{storage_alias, traits::Get, weights::Weight}; + + // NOTE: value type doesn't matter, we just set it to () here. + #[storage_alias] + type SnapshotValidators = StorageValue, ()>; + #[storage_alias] + type SnapshotNominators = StorageValue, ()>; + #[storage_alias] + type QueuedElected = StorageValue, ()>; + #[storage_alias] + type QueuedScore = StorageValue, ()>; + #[storage_alias] + type EraElectionStatus = StorageValue, ()>; + #[storage_alias] + type IsCurrentSessionFinal = StorageValue, ()>; + + /// check to execute prior to migration. + pub fn pre_migrate() -> Result<(), &'static str> { + // these may or may not exist. + log!(info, "SnapshotValidators.exits()? {:?}", SnapshotValidators::::exists()); + log!(info, "SnapshotNominators.exits()? {:?}", SnapshotNominators::::exists()); + log!(info, "QueuedElected.exits()? {:?}", QueuedElected::::exists()); + log!(info, "QueuedScore.exits()? {:?}", QueuedScore::::exists()); + // these must exist. + assert!( + IsCurrentSessionFinal::::exists(), + "IsCurrentSessionFinal storage item not found!" + ); + assert!(EraElectionStatus::::exists(), "EraElectionStatus storage item not found!"); + Ok(()) + } + + /// Migrate storage to v6. + pub fn migrate() -> Weight { + log!(info, "Migrating staking to obsolete::Releases::V6_0_0"); + + SnapshotValidators::::kill(); + SnapshotNominators::::kill(); + QueuedElected::::kill(); + QueuedScore::::kill(); + EraElectionStatus::::kill(); + IsCurrentSessionFinal::::kill(); + + obsolete::StorageVersion::::put(obsolete::Releases::V6_0_0); + + log!(info, "Done."); + T::DbWeight::get().writes(6 + 1) + } +} From fc142029972facd9831c96331b69916a1bde08b3 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 16 Jan 2023 11:49:55 +0100 Subject: [PATCH 29/88] Add NotDecodable error and tests --- frame/staking/src/pallet/mod.rs | 27 ++++++++++++++++++--------- frame/staking/src/tests.rs | 27 +++++++++------------------ 2 files changed, 27 insertions(+), 27 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index a6d1e3e87ec56..a16c91f6157b9 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -778,6 +778,10 @@ pub mod pallet { CommissionTooLow, /// Some bound is not met. BoundNotMet, + /// Nominations are not decodable. This means that `Config::MaxNominations` has been + /// decreased without a migration. A nominator is then stuck until it's fixed, because we + /// can't forgo the bookkeeping. + NotDecodable, } #[pallet::hooks] @@ -1146,8 +1150,18 @@ pub mod pallet { ensure!(ledger.active >= MinNominatorBond::::get(), Error::::InsufficientBond); let stash = &ledger.stash; + let is_nominator = Nominators::::contains_key(stash); + + // If the nominator is not decodable - throw an error. The only reason for that could be + // a decrease of `MaxNominatorsCount`, which should be accompanied by a migration that + // fixes those nominations. Otherwise the Staking pallet ends up in an inconsistent + // state, because we cannot do proper bookeeping. + if is_nominator && Nominators::::get(stash).is_none() { + Err(Error::::NotDecodable)? + } + // Only check limits if they are not already a nominator. - if !Nominators::::contains_key(stash) { + if !is_nominator { // If this error is reached, we need to adjust the `MinNominatorBond` and start // calling `chill_other`. Until then, we explicitly block new nominators to protect // the runtime. @@ -1716,10 +1730,6 @@ pub mod pallet { // In order for one user to chill another user, the following conditions must be met: // - // * `controller` belongs to a nominator who has become non-decodable, - // - // Or - // // * A `ChillThreshold` is set which defines how close to the max nominators or // validators we must reach before users can start chilling one-another. // * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close @@ -1730,11 +1740,10 @@ pub mod pallet { // // Otherwise, if caller is the same as the controller, this is just like `chill`. - // TODO: This no longer works, we need nominations to be decodable, because we pass - // nominations to EventHandler for processing. + // If the validator is not decodable - a migration needs to be executed to fix the + // number of nominations. We can't chill nominators without knowing their nominations. if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { - Self::chill_stash(&stash); - return Ok(()) + return Err(Error::::NotDecodable)? } if caller != controller { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index a3cbf37545cfa..ba32a3dd98bf1 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5053,9 +5053,6 @@ fn min_commission_works() { } #[test] -#[ignore] -// TODO: We can't chill un-decodable nominators as we need to take into account every validator they -// were backing. fn change_of_max_nominations() { use frame_election_provider_support::ElectionDataProvider; ExtBuilder::default() @@ -5131,26 +5128,20 @@ fn change_of_max_nominations() { assert!(Nominators::::get(60).is_some()); assert_eq!(Staking::electing_voters(None).unwrap().len(), 3 + 1); - // TODO: This has to be fixed, we can't re-nominate without reporting previous - // nominations to the EventHandler. - - // now one of them can revive themselves by re-nominating to a proper value. - assert_ok!(Staking::nominate(RuntimeOrigin::signed(71), vec![1])); - assert_eq!( - Nominators::::iter() - .map(|(k, n)| (k, n.targets.len())) - .collect::>(), - vec![(70, 1), (60, 1)] + // Impossible to re-nominate when not decodable. + assert_noop!( + Staking::nominate(RuntimeOrigin::signed(71), vec![1]), + Error::::NotDecodable ); - // TODO: This part has to be removed, we need to be able to decode old nominations. - // or they can be chilled by any account. assert!(Nominators::::contains_key(101)); assert!(Nominators::::get(101).is_none()); - assert_ok!(Staking::chill_other(RuntimeOrigin::signed(70), 100)); - assert!(!Nominators::::contains_key(101)); - assert!(Nominators::::get(101).is_none()); + // Impossible to chill_other when not deodable. + assert_noop!( + Staking::chill_other(RuntimeOrigin::signed(70), 100), + Error::::NotDecodable + ); }) } From a0db07f7fb4075323653275491a73e0d40f21484 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 17 Jan 2023 10:17:42 +0100 Subject: [PATCH 30/88] fix try-runtime migration build --- frame/staking/src/migrations.rs | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index bc3cea9aebc01..b28f7e45f7451 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -54,7 +54,6 @@ mod obsolete { pub(super) type StorageVersion = StorageValue, Releases, ValueQuery>; } - pub mod v13 { use super::*; @@ -63,7 +62,7 @@ pub mod v13 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == obsolete::Releases::V12_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V12_0_0, "Required v12 before upgrading to v13" ); @@ -94,7 +93,7 @@ pub mod v13 { ); frame_support::ensure!( - !StorageVersion::::exists(), + !obsolete::StorageVersion::::exists(), "Storage version not migrated correctly" ); @@ -119,7 +118,7 @@ pub mod v12 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - StorageVersion::::get() == obsolete::Releases::V11_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V11_0_0, "Expected v11 before upgrading to v12" ); @@ -293,17 +292,17 @@ pub mod v10 { pub mod v9 { use super::*; + use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; #[cfg(feature = "try-runtime")] use frame_support::codec::{Decode, Encode}; #[cfg(feature = "try-runtime")] use sp_std::vec::Vec; - use frame_election_provider_support::{SortedListProvider, ReadOnlySortedListProvider}; pub trait MigrationConfig { type Config: Config; type VoterList: frame_election_provider_support::SortedListProvider< ::AccountId, - Score = u64 + Score = u64, >; } @@ -319,7 +318,11 @@ pub mod v9 { for (v, _) in Validators::::iter() { let weight = weight_of_cached(&v); let _ = T::VoterList::on_insert(v.clone(), weight).map_err(|err| { - frame_support::log::warn!("failed to insert {:?} into VoterList: {:?}", v, err) + frame_support::log::warn!( + "failed to insert {:?} into VoterList: {:?}", + v, + err + ) }); } @@ -344,7 +347,7 @@ pub mod v9 { #[cfg(feature = "try-runtime")] fn pre_upgrade() -> Result, &'static str> { frame_support::ensure!( - obsolete::StorageVersion::::get() == obsolete::Releases::V8_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V8_0_0, "must upgrade linearly" ); @@ -358,11 +361,11 @@ pub mod v9 { "the state parameter should be something that was generated by pre_upgrade", ); let post_count = T::VoterList::count(); - let validators = Validators::::count(); - assert!(post_count == prev_count + validators); + let validators = Validators::::count(); + assert_eq!(post_count, prev_count + validators); frame_support::ensure!( - obsolete::StorageVersion::::get() == obsolete::Releases::V9_0_0, + obsolete::StorageVersion::::get() == obsolete::Releases::V9_0_0, "must upgrade " ); Ok(()) @@ -373,14 +376,14 @@ pub mod v9 { pub mod v8 { use super::*; use crate::{Config, Nominators, Pallet, Weight}; - use frame_election_provider_support::{SortedListProvider, ReadOnlySortedListProvider}; + use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; use frame_support::traits::Get; pub trait MigrationConfig { type Config: Config; type VoterList: frame_election_provider_support::SortedListProvider< ::AccountId, - Score = u64 + Score = u64, >; } From 504695e3c383905dd9832e83a443d52947e03239 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 17 Jan 2023 10:50:43 +0100 Subject: [PATCH 31/88] unused import fix --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 605cc0aef8c0b..8f7dce25302f0 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -23,7 +23,7 @@ pub(crate) mod mock; mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::traits::{Currency, CurrencyToVote, Defensive}; +use frame_support::traits::{Currency, CurrencyToVote}; pub use pallet::*; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; From 5361a955567fb9bd7eddb793d6216b2c12966331 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 17 Jan 2023 10:58:18 +0100 Subject: [PATCH 32/88] test fixes --- frame/stake-tracker/src/mock.rs | 2 +- frame/stake-tracker/src/tests.rs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 9b007050e47ae..c8469e3ef79fe 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -252,7 +252,7 @@ impl ExtBuilder { } .assimilate_storage(&mut storage); - let mut ext = sp_io::TestExternalities::from(storage); + let ext = sp_io::TestExternalities::from(storage); ext } diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index c522a1fb12e8a..3060e22324998 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -69,7 +69,7 @@ mod on_nominator_update { assert_eq!(VoterList::count(), 0); // usual user, validator, nominator - for (idx, id) in [1, 10, 20].iter().enumerate() { + for id in [1, 10, 20] { let _ = VoterList::on_insert(*id, 1000); assert_storage_noop!(StakeTracker::on_nominator_update(id, Vec::new())); } @@ -224,7 +224,7 @@ mod on_unstake { // usual user, validator, nominator, not bonded for id in [1, 10, 20, 30] { - VoterList::on_insert(id, 100); + assert_ok!(VoterList::on_insert(id, 100)); assert_storage_noop!(StakeTracker::on_unstake(&id)); } }); From 78094f2ce60439a6a8d72d854cdcd751246d55d9 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 17 Jan 2023 11:06:12 +0100 Subject: [PATCH 33/88] some more fixes --- frame/stake-tracker/src/tests.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 3060e22324998..6671d845d0b9b 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -1,7 +1,7 @@ use super::{mock::*, pallet::*}; use crate as pallet_stake_tracker; use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; -use frame_support::assert_storage_noop; +use frame_support::{assert_ok, assert_storage_noop}; use sp_staking::{OnStakingUpdate, StakingInterface}; type VoterList = ::VoterList; @@ -70,8 +70,8 @@ mod on_nominator_update { // usual user, validator, nominator for id in [1, 10, 20] { - let _ = VoterList::on_insert(*id, 1000); - assert_storage_noop!(StakeTracker::on_nominator_update(id, Vec::new())); + let _ = VoterList::on_insert(id, 1000); + assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); } }); } From 561eb2bc4bbc4e10315679f69a5ed75a26e2ad6a Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 19 Jan 2023 09:36:21 +0100 Subject: [PATCH 34/88] fix unused --- frame/staking/src/testing_utils.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 34779f222a2a7..790750c18fc1a 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,7 +27,6 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; -use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; From 1fffe7f3a9b498271b027da41341711ade4f456f Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Thu, 19 Jan 2023 11:14:18 +0100 Subject: [PATCH 35/88] Revert "fix unused" This reverts commit 561eb2bc4bbc4e10315679f69a5ed75a26e2ad6a. --- frame/staking/src/testing_utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 790750c18fc1a..34779f222a2a7 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,6 +27,7 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; +use frame_election_provider_support::ReadOnlySortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; From 200253783605548400ed3764935f2125e729cc3d Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 31 Jan 2023 14:54:17 +0100 Subject: [PATCH 36/88] Update frame/stake-tracker/src/mock.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/src/mock.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index c8469e3ef79fe..ab9abfddd92bb 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -118,7 +118,7 @@ impl StakingInterface for StakingMock { type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { - unimplemented!("Currently not used.") + unreachable!(); } fn minimum_validator_bond() -> Self::Balance { From 6108362cab56af7633006507ff6f6ea6219fa124 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 1 Feb 2023 11:40:50 +0100 Subject: [PATCH 37/88] Update frame/stake-tracker/Cargo.toml Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index 2ed0d1e41aaa0..14ef0145e2fa8 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -71,5 +71,4 @@ runtime-benchmarks = [ "pallet-staking/runtime-benchmarks" ] - try-runtime = ["frame-support/try-runtime"] From f92dfb7b43ace528e18f01920c51130bf7c6a8e3 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 1 Feb 2023 11:41:27 +0100 Subject: [PATCH 38/88] Update frame/stake-tracker/Cargo.toml Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/Cargo.toml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/frame/stake-tracker/Cargo.toml b/frame/stake-tracker/Cargo.toml index 14ef0145e2fa8..6c648937a9b24 100644 --- a/frame/stake-tracker/Cargo.toml +++ b/frame/stake-tracker/Cargo.toml @@ -39,10 +39,6 @@ sp-npos-elections = { version = "4.0.0-dev", path = "../../primitives/npos-elect pallet-bags-list = { version = "4.0.0-dev", path = "../bags-list" } frame-election-provider-support = { version = "4.0.0-dev", path = "../election-provider-support" } - - - - [features] default = ["std"] std = [ From d6069c3491271bf39e5f52fa1a0a2a67441eb9e4 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 10:39:27 +0100 Subject: [PATCH 39/88] fmt --- frame/election-provider-support/src/lib.rs | 2 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/impls.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 424818efb91c5..6150bd455db6a 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -504,7 +504,7 @@ pub trait ReadOnlySortedListProvider { fn get_score(id: &AccountId) -> Result; /// Check internal state of list. Only meant for debugging. - #[cfg(feature = "try-runtime")] + #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str>; /// Remove all items from the list. diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 43a0a99c4371f..497125f171306 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -99,7 +99,7 @@ frame_support::construct_runtime!( Session: pallet_session, Historical: pallet_session::historical, VoterBagsList: pallet_bags_list::, - StakeTracker: pallet_stake_tracker, + StakeTracker: pallet_stake_tracker, } ); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 59b3d30cce07a..2024256b7eeea 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1406,7 +1406,7 @@ impl ReadOnlySortedListProvider for UseValidatorsMap fn get_score(id: &T::AccountId) -> Result { Ok(Pallet::::weight_of(id).into()) } - #[cfg(feature = "try-runtime")] + #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str> { Ok(()) } From edbcf89f5d6b2b999978b26c697539d89755a626 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 11:22:02 +0100 Subject: [PATCH 40/88] get rid of ReadOnlySortedListProvider --- frame/bags-list/fuzzer/src/main.rs | 2 +- frame/bags-list/remote-tests/src/lib.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 2 +- frame/bags-list/remote-tests/src/snapshot.rs | 2 +- frame/bags-list/remote-tests/src/try_state.rs | 2 +- frame/bags-list/src/lib.rs | 12 ++- frame/bags-list/src/list/tests.rs | 2 +- frame/election-provider-support/src/lib.rs | 50 ++++++------- frame/nis/src/lib.rs | 2 +- .../nomination-pools/benchmarking/src/lib.rs | 2 +- frame/stake-tracker/src/tests.rs | 2 +- frame/staking/src/benchmarking.rs | 2 +- frame/staking/src/migrations.rs | 4 +- frame/staking/src/pallet/impls.rs | 75 +++++++++++-------- frame/staking/src/pallet/mod.rs | 4 +- frame/staking/src/testing_utils.rs | 2 +- frame/staking/src/tests.rs | 2 +- 17 files changed, 86 insertions(+), 83 deletions(-) diff --git a/frame/bags-list/fuzzer/src/main.rs b/frame/bags-list/fuzzer/src/main.rs index 0fc52e853fcad..c78e2a13076d5 100644 --- a/frame/bags-list/fuzzer/src/main.rs +++ b/frame/bags-list/fuzzer/src/main.rs @@ -27,7 +27,7 @@ //! More information about `honggfuzz` can be found //! [here](https://docs.rs/honggfuzz/). -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider, VoteWeight}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use honggfuzz::fuzz; use pallet_bags_list::mock::{AccountId, BagsList, ExtBuilder}; diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index ea6b08d5cf653..b9ed2efdbdad2 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -17,7 +17,7 @@ //! Utilities for remote-testing pallet-bags-list. -use frame_election_provider_support::{ReadOnlySortedListProvider, ScoreProvider}; +use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use pallet_bags_list::Instance1; use sp_std::prelude::*; diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 17d72164f803d..3dd36c83fed17 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -17,7 +17,7 @@ //! Test to check the migration of the voter bag. use crate::{RuntimeT, LOG_TARGET}; -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use pallet_staking::Nominators; use remote_externalities::{Builder, Mode, OnlineConfig}; diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index fe6982a2f69f8..0163ca200a15d 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -16,7 +16,7 @@ //! Test to execute the snapshot using the voter bag. -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::traits::PalletInfoAccess; use remote_externalities::{Builder, Mode, OnlineConfig}; use sp_runtime::{traits::Block as BlockT, DeserializeOwned}; diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index 9ea04d5177b39..2979e5cc8196f 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -16,7 +16,7 @@ //! Test to execute the sanity-check of the voter bag. -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::{ storage::generator::StorageMap, traits::{Get, PalletInfoAccess}, diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index d3eb754059256..8a86153800214 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -20,7 +20,7 @@ //! A semi-sorted list, where items hold an `AccountId` based on some `Score`. The //! `AccountId` (`id` for short) might be synonym to a `voter` or `nominator` in some context, and //! `Score` signifies the chance of each id being included in the final -//! [`ReadOnlySortedListProvider::iter`]. +//! [`SortedListProvider::iter`]. //! //! It implements [`frame_election_provider_support::SortedListProvider`] to provide a semi-sorted //! list of accounts to another pallet. It needs some other pallet to give it some information about @@ -54,9 +54,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::FullCodec; -use frame_election_provider_support::{ - ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, -}; +use frame_election_provider_support::{ScoreProvider, SortedListProvider}; use frame_system::ensure_signed; use sp_runtime::traits::{AtLeast32BitUnsigned, Bounded, StaticLookup}; use sp_std::prelude::*; @@ -271,7 +269,7 @@ pub mod pallet { #[cfg(feature = "try-runtime")] fn try_state(_: BlockNumberFor) -> Result<(), &'static str> { - >::try_state() + >::try_state() } } } @@ -308,7 +306,7 @@ impl, I: 'static> Pallet { } } -impl, I: 'static> ReadOnlySortedListProvider for Pallet { +impl, I: 'static> SortedListProvider for Pallet { type Error = ListError; type Score = T::Score; @@ -395,7 +393,7 @@ impl, I: 'static> SortedListProvider for Pallet } impl, I: 'static> ScoreProvider for Pallet { - type Score = as ReadOnlySortedListProvider>::Score; + type Score = as SortedListProvider>::Score; fn score(id: &T::AccountId) -> T::Score { Node::::get(id).map(|node| node.score()).unwrap_or_default() diff --git a/frame/bags-list/src/list/tests.rs b/frame/bags-list/src/list/tests.rs index cbd1f3dcaed8c..3c4aa7c86634d 100644 --- a/frame/bags-list/src/list/tests.rs +++ b/frame/bags-list/src/list/tests.rs @@ -20,7 +20,7 @@ use crate::{ mock::{test_utils::*, *}, ListBags, ListNodes, }; -use frame_election_provider_support::{ReadOnlySortedListProvider, VoteWeight}; +use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{assert_ok, assert_storage_noop}; fn node( diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index 6150bd455db6a..be341fd17afe5 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -471,15 +471,20 @@ where } } -/// A trait that defines a set of functions that allow read access to the underlying storage -/// and the types those functions depend upon. +/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. +/// +/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other +/// entity. +/// +/// Something that implements this trait will do a best-effort sort over ids, and thus can be +/// used on the implementing side of [`ElectionDataProvider`]. /// /// The scores (see [`Self::Score`]) are ascending, the higher, the better. /// /// Initially a part of [`SortedListProvider`], it allows for restricting a consumer to read-only /// operations. This is particularly useful in case the list is populated by one entity and /// read by another. -pub trait ReadOnlySortedListProvider { +pub trait SortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; @@ -507,31 +512,6 @@ pub trait ReadOnlySortedListProvider { #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str>; - /// Remove all items from the list. - /// - /// ## WARNING - /// - /// This function should never be called in production settings because it can lead to an - /// unbounded amount of storage accesses. - #[cfg(any(feature = "runtime-benchmarks", test))] - fn unsafe_clear(); - - /// If `who` changes by the returned amount they are guaranteed to have a worst case change - /// in their list position. - #[cfg(any(feature = "runtime-benchmarks", test))] - fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; -} - -/// A utility trait for something to implement `ElectionDataProvider` in a sensible way. -/// -/// This is generic over `AccountId` and it can represent a validator, a nominator, or any other -/// entity. -/// -/// Something that implements this trait will do a best-effort sort over ids, and thus can be -/// used on the implementing side of [`ElectionDataProvider`]. -/// -/// Inherits [`ReadOnlySortedListProvider`], which provides basic types and read-only methods. -pub trait SortedListProvider: ReadOnlySortedListProvider { /// Hook for inserting a new id. /// /// Implementation should return an error if duplicate item is being inserted. @@ -581,6 +561,20 @@ pub trait SortedListProvider: ReadOnlySortedListProvider { all: impl IntoIterator, score_of: Box Self::Score>, ) -> u32; + + /// Remove all items from the list. + /// + /// ## WARNING + /// + /// This function should never be called in production settings because it can lead to an + /// unbounded amount of storage accesses. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn unsafe_clear(); + + /// If `who` changes by the returned amount they are guaranteed to have a worst case change + /// in their list position. + #[cfg(any(feature = "runtime-benchmarks", test))] + fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; } /// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs index fa0163fb60bfe..aef0c3141ef1c 100644 --- a/frame/nis/src/lib.rs +++ b/frame/nis/src/lib.rs @@ -1,4 +1,4 @@ -// This file is part of Substrate. +/// This file is part of Substrate. // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 diff --git a/frame/nomination-pools/benchmarking/src/lib.rs b/frame/nomination-pools/benchmarking/src/lib.rs index 0b7eef5d70295..820e4d6a9fb38 100644 --- a/frame/nomination-pools/benchmarking/src/lib.rs +++ b/frame/nomination-pools/benchmarking/src/lib.rs @@ -26,7 +26,7 @@ mod mock; use frame_benchmarking::v1::{ account, frame_support::traits::Currency, vec, whitelist_account, Vec, }; -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::{assert_ok, ensure, traits::Get}; use frame_system::RawOrigin as RuntimeOrigin; use pallet_nomination_pools::{ diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 6671d845d0b9b..8fbf3c875d097 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -1,6 +1,6 @@ use super::{mock::*, pallet::*}; use crate as pallet_stake_tracker; -use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; +use frame_election_provider_support::SortedListProvider; use frame_support::{assert_ok, assert_storage_noop}; use sp_staking::{OnStakingUpdate, StakingInterface}; diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 92c9317d50cac..963766d78bc5d 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -22,7 +22,7 @@ use crate::{ConfigOp, Pallet as Staking}; use testing_utils::*; use codec::Decode; -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::{ dispatch::UnfilteredDispatchable, pallet_prelude::*, diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index 4597fe7d9eda1..348917c290b0e 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -292,7 +292,7 @@ pub mod v10 { pub mod v9 { use super::*; - use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; + use frame_election_provider_support::SortedListProvider; #[cfg(feature = "try-runtime")] use frame_support::codec::{Decode, Encode}; #[cfg(feature = "try-runtime")] @@ -376,7 +376,7 @@ pub mod v9 { pub mod v8 { use super::*; use crate::{Config, Nominators, Pallet, Weight}; - use frame_election_provider_support::{ReadOnlySortedListProvider, SortedListProvider}; + use frame_election_provider_support::SortedListProvider; use frame_support::traits::Get; pub trait MigrationConfig { diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 2024256b7eeea..18e8795c71f08 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -18,8 +18,8 @@ //! Implementations for the Staking FRAME Pallet. use frame_election_provider_support::{ - data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, - ReadOnlySortedListProvider, ScoreProvider, SortedListProvider, VoteWeight, VoterOf, + data_provider, BoundedSupportsOf, ElectionDataProvider, ElectionProvider, ScoreProvider, + SortedListProvider, VoteWeight, VoterOf, }; use frame_support::{ dispatch::WithPostDispatchInfo, @@ -1379,7 +1379,8 @@ impl ScoreProvider for Pallet { /// does not provide validators in sorted order. If you desire nominators in a sorted order take /// a look at [`pallet-bags-list`]. pub struct UseValidatorsMap(sp_std::marker::PhantomData); -impl ReadOnlySortedListProvider for UseValidatorsMap { + +impl SortedListProvider for UseValidatorsMap { type Error = (); type Score = BalanceOf; @@ -1387,6 +1388,7 @@ impl ReadOnlySortedListProvider for UseValidatorsMap fn iter() -> Box> { Box::new(Validators::::iter().map(|(v, _)| v)) } + fn iter_from( start: &T::AccountId, ) -> Result>, Self::Error> { @@ -1397,33 +1399,24 @@ impl ReadOnlySortedListProvider for UseValidatorsMap Err(()) } } + fn count() -> u32 { Validators::::count() } + fn contains(id: &T::AccountId) -> bool { Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { Ok(Pallet::::weight_of(id).into()) } + #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str> { Ok(()) } - frame_election_provider_support::runtime_benchmarks_or_test_enabled! { - fn unsafe_clear() { - #[allow(deprecated)] - Validators::::remove_all(); - } - - fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { - unimplemented!() - } - } -} - -impl SortedListProvider for UseValidatorsMap { fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) @@ -1433,10 +1426,23 @@ impl SortedListProvider for UseValidatorsMap { // nothing to do on update. Ok(()) } + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { // nothing to do on remove. Ok(()) } + + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + fn unsafe_clear() { + #[allow(deprecated)] + Validators::::remove_all(); + } + + fn score_update_worst_case(_who: &T::AccountId, _is_increase: bool) -> Self::Score { + unimplemented!() + } + } + fn unsafe_regenerate( _: impl IntoIterator, _: Box Self::Score>, @@ -1451,7 +1457,7 @@ impl SortedListProvider for UseValidatorsMap { /// a look at [`pallet-bags-list]. pub struct UseNominatorsAndValidatorsMap(sp_std::marker::PhantomData); -impl ReadOnlySortedListProvider for UseNominatorsAndValidatorsMap { +impl SortedListProvider for UseNominatorsAndValidatorsMap { type Error = (); type Score = VoteWeight; @@ -1462,6 +1468,7 @@ impl ReadOnlySortedListProvider for UseNominatorsAndVal .chain(Nominators::::iter().map(|(n, _)| n)), ) } + fn iter_from( start: &T::AccountId, ) -> Result>, Self::Error> { @@ -1479,20 +1486,39 @@ impl ReadOnlySortedListProvider for UseNominatorsAndVal Err(()) } } + fn count() -> u32 { Nominators::::count().saturating_add(Validators::::count()) } + fn contains(id: &T::AccountId) -> bool { Nominators::::contains_key(id) || Validators::::contains_key(id) } + fn get_score(id: &T::AccountId) -> Result { Ok(Pallet::::weight_of(id)) } + #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str> { Ok(()) } + fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on insert. + Ok(()) + } + + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { + // nothing to do on update. + Ok(()) + } + + fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { + // nothing to do on remove. + Ok(()) + } + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { fn unsafe_clear() { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a @@ -1507,22 +1533,7 @@ impl ReadOnlySortedListProvider for UseNominatorsAndVal unimplemented!() } } -} - -impl SortedListProvider for UseNominatorsAndValidatorsMap { - fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on insert. - Ok(()) - } - fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { - // nothing to do on update. - Ok(()) - } - fn on_remove(_: &T::AccountId) -> Result<(), Self::Error> { - // nothing to do on remove. - Ok(()) - } fn unsafe_regenerate( _: impl IntoIterator, _: Box Self::Score>, diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 5a6e8a85529fe..df6b9b7c83ded 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -58,7 +58,7 @@ pub(crate) const SPECULATIVE_NUM_SPANS: u32 = 32; #[frame_support::pallet] pub mod pallet { - use frame_election_provider_support::{ElectionDataProvider, ReadOnlySortedListProvider}; + use frame_election_provider_support::{ElectionDataProvider, SortedListProvider}; use crate::BenchmarkingConfig; @@ -227,7 +227,7 @@ pub mod pallet { /// Invariant: what comes out of this list will always be a nominator. // NOTE: Staking does not maintain this list, it merely reads from it. The list is // maintained by `EventListener` implementors. - type VoterList: ReadOnlySortedListProvider; + type VoterList: SortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going /// to be introduced in a follow-up PR. diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 34779f222a2a7..a7ce412bb6737 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,7 +27,7 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; -use frame_election_provider_support::ReadOnlySortedListProvider; +use frame_election_provider_support::SortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d13b8754f17c1..b278700ffd267 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -18,7 +18,7 @@ //! Tests for the module. use super::{ConfigOp, Event, *}; -use frame_election_provider_support::{ElectionProvider, ReadOnlySortedListProvider, Support}; +use frame_election_provider_support::{ElectionProvider, SortedListProvider, Support}; use frame_support::{ assert_noop, assert_ok, assert_storage_noop, bounded_vec, dispatch::{extract_actual_weight, GetDispatchInfo, WithPostDispatchInfo}, From 16671dac59a5e98486c42d2329e0dfb6b8b2e522 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 14:20:29 +0100 Subject: [PATCH 41/88] fix some errs --- frame/bags-list/src/lib.rs | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/frame/bags-list/src/lib.rs b/frame/bags-list/src/lib.rs index 8a86153800214..7e849cb588ce5 100644 --- a/frame/bags-list/src/lib.rs +++ b/frame/bags-list/src/lib.rs @@ -328,6 +328,7 @@ impl, I: 'static> SortedListProvider for Pallet fn contains(id: &T::AccountId) -> bool { List::::contains(id) } + fn get_score(id: &T::AccountId) -> Result { List::::get_score(id) } @@ -337,6 +338,18 @@ impl, I: 'static> SortedListProvider for Pallet Self::do_try_state() } + fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { + List::::insert(id, score) + } + + fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { + Pallet::::do_rebag(id, new_score).map(|_| ()) + } + + fn on_remove(id: &T::AccountId) -> Result<(), ListError> { + List::::remove(id) + } + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { fn unsafe_clear() { // NOTE: This call is unsafe for the same reason as SortedListProvider::unsafe_clear. @@ -366,20 +379,6 @@ impl, I: 'static> SortedListProvider for Pallet } } } -} - -impl, I: 'static> SortedListProvider for Pallet { - fn on_insert(id: T::AccountId, score: T::Score) -> Result<(), ListError> { - List::::insert(id, score) - } - - fn on_update(id: &T::AccountId, new_score: T::Score) -> Result<(), ListError> { - Pallet::::do_rebag(id, new_score).map(|_| ()) - } - - fn on_remove(id: &T::AccountId) -> Result<(), ListError> { - List::::remove(id) - } fn unsafe_regenerate( all: impl IntoIterator, From aff459912b35a6862547557da3acf99039c14376 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 14:28:23 +0100 Subject: [PATCH 42/88] revert weird random change --- frame/nis/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/nis/src/lib.rs b/frame/nis/src/lib.rs index aef0c3141ef1c..fa0163fb60bfe 100644 --- a/frame/nis/src/lib.rs +++ b/frame/nis/src/lib.rs @@ -1,4 +1,4 @@ -/// This file is part of Substrate. +// This file is part of Substrate. // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 From f15bd1c4db6bfce557e784e2fe358bfd873aff31 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 15:39:49 +0100 Subject: [PATCH 43/88] more redundant import removals --- frame/staking/src/pallet/mod.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index df6b9b7c83ded..0f843369d8d11 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -17,9 +17,7 @@ //! Staking FRAME Pallet. -use frame_election_provider_support::{ - ElectionProvider, ElectionProviderBase, SortedListProvider, VoteWeight, -}; +use frame_election_provider_support::{ElectionProvider, ElectionProviderBase, VoteWeight}; use frame_support::{ dispatch::Codec, pallet_prelude::*, From 85e863df4a2c052f60d6beb14dedba04c27f5054 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 20:15:38 +0100 Subject: [PATCH 44/88] more fixes --- bin/node/runtime/src/lib.rs | 2 +- frame/babe/src/mock.rs | 2 +- frame/bags-list/remote-tests/src/migration.rs | 1 - frame/bags-list/remote-tests/src/try_state.rs | 1 - frame/fast-unstake/src/mock.rs | 2 +- frame/grandpa/src/mock.rs | 2 +- frame/nomination-pools/benchmarking/src/mock.rs | 2 +- frame/nomination-pools/test-staking/src/mock.rs | 2 +- frame/offences/benchmarking/src/mock.rs | 2 +- frame/root-offences/src/mock.rs | 2 +- frame/session/benchmarking/src/mock.rs | 2 +- frame/staking/src/mock.rs | 2 +- frame/staking/src/pallet/mod.rs | 2 +- 13 files changed, 11 insertions(+), 13 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 0a820d885c472..defd8ce2c0c5d 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -580,7 +580,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = NominationPools; type WeightInfo = pallet_staking::weights::SubstrateWeight; type BenchmarkingConfig = StakingBenchmarkingConfig; - type EventListener = StakeTracker; + type EventListeners = StakeTracker; } impl pallet_stake_tracker::Config for Runtime { diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 4a54defd6abcd..36f2912ec545a 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -204,7 +204,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } impl pallet_offences::Config for Test { diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 3dd36c83fed17..3d6cc6f234c27 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -49,7 +49,6 @@ pub async fn execute( let pre_migrate_nominator_count = >::iter().count() as u32; log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count); - use frame_election_provider_support::SortedListProvider; // run the actual migration let moved = ::VoterList::unsafe_regenerate( pallet_staking::Nominators::::iter().map(|(n, _)| n), diff --git a/frame/bags-list/remote-tests/src/try_state.rs b/frame/bags-list/remote-tests/src/try_state.rs index 2979e5cc8196f..9ed877a43afe1 100644 --- a/frame/bags-list/remote-tests/src/try_state.rs +++ b/frame/bags-list/remote-tests/src/try_state.rs @@ -16,7 +16,6 @@ //! Test to execute the sanity-check of the voter bag. -use frame_election_provider_support::SortedListProvider; use frame_support::{ storage::generator::StorageMap, traits::{Get, PalletInfoAccess}, diff --git a/frame/fast-unstake/src/mock.rs b/frame/fast-unstake/src/mock.rs index 2fca22e97f65c..5b1ff3c09311e 100644 --- a/frame/fast-unstake/src/mock.rs +++ b/frame/fast-unstake/src/mock.rs @@ -156,7 +156,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } pub struct BalanceToU256; diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index ecc242ab48df3..5111411701bdf 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -208,7 +208,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } impl pallet_offences::Config for Test { diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 2e69166662e68..a7c7bffdf045c 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -119,7 +119,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = StakeTracker; + type EventListeners = StakeTracker; } impl pallet_stake_tracker::Config for Runtime { diff --git a/frame/nomination-pools/test-staking/src/mock.rs b/frame/nomination-pools/test-staking/src/mock.rs index 6b2d5742da08c..562fabb15d2b9 100644 --- a/frame/nomination-pools/test-staking/src/mock.rs +++ b/frame/nomination-pools/test-staking/src/mock.rs @@ -133,7 +133,7 @@ impl pallet_staking::Config for Runtime { type OnStakerSlash = Pools; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } parameter_types! { diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 813fea960a04d..5f19a664e8937 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -181,7 +181,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } impl pallet_im_online::Config for Test { diff --git a/frame/root-offences/src/mock.rs b/frame/root-offences/src/mock.rs index 40721499850d0..fd040d6647709 100644 --- a/frame/root-offences/src/mock.rs +++ b/frame/root-offences/src/mock.rs @@ -195,7 +195,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } impl pallet_session::historical::Config for Test { diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index c73c35b5e25cc..169ca9ef6f9c3 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -183,7 +183,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = (); + type EventListeners = (); } impl crate::Config for Test {} diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 497125f171306..6e6d049d1f0fd 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -305,7 +305,7 @@ impl crate::pallet::pallet::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); - type EventListener = StakeTracker; + type EventListeners = StakeTracker; } impl pallet_stake_tracker::Config for Test { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 0f843369d8d11..caa69e5352623 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -251,7 +251,7 @@ pub mod pallet { /// Something that listens to staking updates and performs actions based on the data it /// receives. - type EventListener: sp_staking::OnStakingUpdate>; + type EventListeners: sp_staking::OnStakingUpdate>; /// The maximum number of `unlocking` chunks a [`StakingLedger`] can /// have. Effectively determines how many unique eras a staker may be From 3b816b88832e5ffb4a2532764643700919285ed0 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 20:25:54 +0100 Subject: [PATCH 45/88] more fixes --- frame/staking/src/pallet/impls.rs | 12 ++++++------ frame/staking/src/pallet/mod.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 18e8795c71f08..0d59652d126e4 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -276,7 +276,7 @@ impl Pallet { }); T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); - T::EventListener::on_stake_update(&ledger.stash, prev_ledger); + T::EventListeners::on_stake_update(&ledger.stash, prev_ledger); } /// Chill a stash account. @@ -668,7 +668,7 @@ impl Pallet { Self::do_remove_nominator(stash); frame_system::Pallet::::dec_consumers(stash); - T::EventListener::on_unstake(stash); + T::EventListeners::on_unstake(stash); Ok(()) } @@ -887,7 +887,7 @@ impl Pallet { pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { let prev_nominations = Self::nominations(who); Nominators::::insert(who, nominations); - T::EventListener::on_nominator_update(who, prev_nominations.unwrap_or_default()); + T::EventListeners::on_nominator_update(who, prev_nominations.unwrap_or_default()); } /// This function will remove a nominator from the `Nominators` storage map, @@ -900,7 +900,7 @@ impl Pallet { pub fn do_remove_nominator(who: &T::AccountId) -> bool { if let Some(nominations) = Self::nominations(who) { Nominators::::remove(who); - T::EventListener::on_nominator_remove(who, nominations); + T::EventListeners::on_nominator_remove(who, nominations); return true } false @@ -915,7 +915,7 @@ impl Pallet { /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { - T::EventListener::on_validator_add(who); + T::EventListeners::on_validator_add(who); } Validators::::insert(who, prefs); } @@ -929,7 +929,7 @@ impl Pallet { pub fn do_remove_validator(who: &T::AccountId) -> bool { if Validators::::contains_key(who) { Validators::::remove(who); - T::EventListener::on_validator_remove(who); + T::EventListeners::on_validator_remove(who); return true } false diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index caa69e5352623..bf7e1f1b73cb7 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -224,7 +224,7 @@ pub mod pallet { /// /// Invariant: what comes out of this list will always be a nominator. // NOTE: Staking does not maintain this list, it merely reads from it. The list is - // maintained by `EventListener` implementors. + // maintained by `EventListeners` implementors. type VoterList: SortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going From 676a7a96c7982e64b74f95446b06ba75e2af5106 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 10 Feb 2023 21:21:48 +0100 Subject: [PATCH 46/88] more fixes --- frame/stake-tracker/src/lib.rs | 4 ++-- frame/stake-tracker/src/mock.rs | 17 +++++++++++++++++ frame/stake-tracker/src/tests.rs | 27 ++++++++++++++++++++++----- frame/staking/src/pallet/impls.rs | 2 +- primitives/staking/src/lib.rs | 4 ++-- 5 files changed, 44 insertions(+), 10 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 8f7dce25302f0..e6b4304ddc624 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -1,6 +1,6 @@ // This file is part of Substrate. -// Copyright (C) 2022 Parity Technologies (UK) Ltd. +// Copyright (C) 2023 Parity Technologies (UK) Ltd. // SPDX-License-Identifier: Apache-2.0 // Licensed under the Apache License, Version 2.0 (the "License"); @@ -100,7 +100,7 @@ impl OnStakingUpdate> for Pallet { /// balance when they chill? /// Why? Because we don't remove ApprovalStake when a validator chills and we need to make sure /// their self-stake is up-to-date and not applied twice. - fn on_validator_add(who: &T::AccountId) { + fn on_validator_update(who: &T::AccountId) { let self_stake = Self::slashable_balance_of(who); // maybe update sorted list. let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)); diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index ab9abfddd92bb..705fb6bb15f79 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use crate::{self as pallet_stake_tracker, *}; use frame_election_provider_support::{ScoreProvider, VoteWeight}; use frame_support::{parameter_types, weights::constants::RocksDbWeight}; diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 8fbf3c875d097..741c217206de1 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -1,3 +1,20 @@ +// This file is part of Substrate. + +// Copyright (C) 2023 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + use super::{mock::*, pallet::*}; use crate as pallet_stake_tracker; use frame_election_provider_support::SortedListProvider; @@ -104,7 +121,7 @@ mod on_nominator_update { } } -mod on_validator_add { +mod on_validator_update { use super::*; #[test] fn noop_when_in_the_list() { @@ -114,7 +131,7 @@ mod on_validator_add { // usual user, validator, nominator for id in [1, 10, 20] { let _ = VoterList::on_insert(id, 1000); - assert_storage_noop!(StakeTracker::on_validator_add(&id)); + assert_storage_noop!(StakeTracker::on_validator_update(&id)); } }); } @@ -125,19 +142,19 @@ mod on_validator_add { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); // user without stake - assert_storage_noop!(StakeTracker::on_validator_add(&30)); + assert_storage_noop!(StakeTracker::on_validator_update(&30)); }); } #[test] - // It is the caller's problem to make sure `on_validator_add` is called in the right context. + // It is the caller's problem to make sure `on_validator_update` is called in the right context. fn works_for_everyone() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); // usual user, validator, nominator for id in [1, 10, 20] { - StakeTracker::on_validator_add(&id); + StakeTracker::on_validator_update(&id); assert_eq!( VoterList::get_score(&id).unwrap(), Pallet::::to_vote(Staking::stake(&id).map(|s| s.active).unwrap()) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 0d59652d126e4..e645f43657d01 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -915,7 +915,7 @@ impl Pallet { /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { if !Validators::::contains_key(who) { - T::EventListeners::on_validator_add(who); + T::EventListeners::on_validator_update(who); } Validators::::insert(who, prefs); } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index fc074e0da639e..2d12223f6cf1f 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -98,7 +98,7 @@ pub trait OnStakingUpdate { /// Fired when someone sets their intention to nominate, either new, or existing one. fn on_nominator_update(who: &AccountId, prev_nominations: Vec); /// Fired when someone sets their intention to validate, either new, or existing one. - fn on_validator_add(who: &AccountId); + fn on_validator_update(who: &AccountId); /// Fired when someone removes their intention to validate, either due to chill or nominating. fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator /// Fired when someone removes their intention to nominate, either due to chill or validating. @@ -117,7 +117,7 @@ impl OnStakingUpdate for () { // stub } - fn on_validator_add(_: &AccountId) { + fn on_validator_update(_: &AccountId) { // stub } From 54a5a6adf2c0f0cad75bdf1cd2201de4b2ced829 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 10:13:42 +0100 Subject: [PATCH 47/88] more fixes --- Cargo.lock | 1 - frame/bags-list/remote-tests/Cargo.toml | 1 - frame/bags-list/remote-tests/src/lib.rs | 10 +---- frame/bags-list/remote-tests/src/migration.rs | 2 +- frame/stake-tracker/src/lib.rs | 37 ++++++++----------- frame/staking/src/benchmarking.rs | 1 - frame/staking/src/pallet/impls.rs | 3 +- primitives/staking/src/lib.rs | 1 - 8 files changed, 20 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 164a3e9f09626..fb89622ee6562 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5481,7 +5481,6 @@ dependencies = [ "frame-system", "log", "pallet-bags-list", - "pallet-stake-tracker", "pallet-staking", "sp-core", "sp-runtime", diff --git a/frame/bags-list/remote-tests/Cargo.toml b/frame/bags-list/remote-tests/Cargo.toml index a7c3c157f489f..26670c551f87d 100644 --- a/frame/bags-list/remote-tests/Cargo.toml +++ b/frame/bags-list/remote-tests/Cargo.toml @@ -15,7 +15,6 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] # frame pallet-staking = { path = "../../staking", version = "4.0.0-dev" } -pallet-stake-tracker = { path = "../../stake-tracker", version = "4.0.0-dev" } pallet-bags-list = { path = "../../bags-list", version = "4.0.0-dev", features = ["fuzz"] } frame-election-provider-support = { path = "../../election-provider-support", version = "4.0.0-dev" } diff --git a/frame/bags-list/remote-tests/src/lib.rs b/frame/bags-list/remote-tests/src/lib.rs index b9ed2efdbdad2..b5130c1c7820b 100644 --- a/frame/bags-list/remote-tests/src/lib.rs +++ b/frame/bags-list/remote-tests/src/lib.rs @@ -32,18 +32,12 @@ pub mod try_state; /// /// For example, this can be the `Runtime` type of the Polkadot runtime. pub trait RuntimeT: - pallet_staking::Config - + pallet_stake_tracker::Config - + pallet_bags_list::Config - + frame_system::Config + pallet_staking::Config + pallet_bags_list::Config + frame_system::Config { } impl< I: 'static, - T: pallet_staking::Config - + pallet_stake_tracker::Config - + pallet_bags_list::Config - + frame_system::Config, + T: pallet_staking::Config + pallet_bags_list::Config + frame_system::Config, > RuntimeT for T { } diff --git a/frame/bags-list/remote-tests/src/migration.rs b/frame/bags-list/remote-tests/src/migration.rs index 3d6cc6f234c27..9f1227316c974 100644 --- a/frame/bags-list/remote-tests/src/migration.rs +++ b/frame/bags-list/remote-tests/src/migration.rs @@ -50,7 +50,7 @@ pub async fn execute( log::info!(target: LOG_TARGET, "Nominator count: {}", pre_migrate_nominator_count); // run the actual migration - let moved = ::VoterList::unsafe_regenerate( + let moved = ::VoterList::unsafe_regenerate( pallet_staking::Nominators::::iter().map(|(n, _)| n), pallet_staking::Pallet::::weight_of_fn(), ); diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index e6b4304ddc624..3c8f8b75e981b 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -61,9 +61,7 @@ pub mod pallet { } impl Pallet { - /// The total balance that can be slashed from a stash account as of right now. - pub fn slashable_balance_of(who: &T::AccountId) -> BalanceOf { - // Weight note: consider making the stake accessible through stash. + pub fn active_stake_of(who: &T::AccountId) -> BalanceOf { T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() } @@ -75,33 +73,30 @@ impl Pallet { impl OnStakingUpdate> for Pallet { fn on_stake_update(who: &T::AccountId, _: Option>>) { - let current_stake = T::Staking::stake(who).unwrap(); - let current_active = current_stake.active; - - // if this is a nominator - if let Some(_) = T::Staking::nominations(¤t_stake.stash) { - let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); - } - - if T::Staking::is_validator(¤t_stake.stash) { - let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); + if let Ok(current_stake) = T::Staking::stake(who) { + let current_active = current_stake.active; + + // if this is a nominator + if let Some(_) = T::Staking::nominations(¤t_stake.stash) { + let _ = + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); + } + + if T::Staking::is_validator(¤t_stake.stash) { + let _ = + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); + } } } fn on_nominator_update(who: &T::AccountId, _prev_nominations: Vec) { // NOTE: We ignore the result here, because this method can be called when the nominator is // already in the list, just changing their nominations. - let _ = - T::VoterList::on_insert(who.clone(), Self::to_vote(Self::slashable_balance_of(who))); + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))); } - /// This should only be called if that stash isn't already a validator. Note, that if we want to - /// properly track ApprovalStake here - we need to make sure we subtract the validator stash - /// balance when they chill? - /// Why? Because we don't remove ApprovalStake when a validator chills and we need to make sure - /// their self-stake is up-to-date and not applied twice. fn on_validator_update(who: &T::AccountId) { - let self_stake = Self::slashable_balance_of(who); + let self_stake = Self::active_stake_of(who); // maybe update sorted list. let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)); } diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 963766d78bc5d..a4366bbd8aa9a 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -189,7 +189,6 @@ impl ListScenario { )?; // find a destination weight that will trigger the worst case scenario - // TODO: This is a hack, might be great to implement this differently. let dest_weight_as_vote = T::VoterList::score_update_worst_case(&origin_stash1, is_increase); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index e645f43657d01..4ace6c3e39cfc 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1062,8 +1062,7 @@ impl ElectionDataProvider for Pallet { >::remove_all(); #[allow(deprecated)] >::remove_all(); - // TODO: sort it out some other way - // T::VoterList::unsafe_clear(); + T::VoterList::unsafe_clear(); } #[cfg(feature = "runtime-benchmarks")] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 2d12223f6cf1f..15e6a48d319b4 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -82,7 +82,6 @@ pub struct Stake { } /// A generic staking event listener. -/// /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. From 7e042ac8e8da607d8c2dbdc68994cf7489235a1b Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 11:23:39 +0100 Subject: [PATCH 48/88] little fix --- frame/election-provider-support/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index be341fd17afe5..b341656ef1109 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -574,7 +574,7 @@ pub trait SortedListProvider { /// If `who` changes by the returned amount they are guaranteed to have a worst case change /// in their list position. #[cfg(any(feature = "runtime-benchmarks", test))] - fn score_update_worst_case(_who: &AccountId, _is_increase: bool) -> Self::Score; + fn score_update_worst_case(who: &AccountId, is_increase: bool) -> Self::Score; } /// Something that can provide the `Score` of an account. Similar to [`ElectionProvider`] and From 3295a298f30a0353cf16bca09e51f0b3da96fc06 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 17:11:54 +0100 Subject: [PATCH 49/88] no more panics --- frame/stake-tracker/src/lib.rs | 4 ++-- frame/stake-tracker/src/tests.rs | 41 ++++++++++---------------------- 2 files changed, 14 insertions(+), 31 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 3c8f8b75e981b..54c0cd12fcde6 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -90,8 +90,8 @@ impl OnStakingUpdate> for Pallet { } fn on_nominator_update(who: &T::AccountId, _prev_nominations: Vec) { - // NOTE: We ignore the result here, because this method can be called when the nominator is - // already in the list, just changing their nominations. + // NOTE: We ignore the result here, because this method can be called when the nominator + // is already in the list, just changing their nominations. let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))); } diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 741c217206de1..4a9bd2ffe8c4b 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -40,8 +40,7 @@ mod on_stake_update { } #[test] - #[should_panic] - fn panics_when_not_bonded() { + fn does_nothing_when_not_bonded() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); // user without stake @@ -93,28 +92,20 @@ mod on_nominator_update { }); } - #[test] - #[should_panic] - fn panics_when_not_bonded() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(VoterList::count(), 0); - // user without stake - assert_storage_noop!(StakeTracker::on_nominator_update(&30, Vec::new())); - }); - } - #[test] // It is the caller's problem to make sure `on_nominator_update` is called in the right context. fn works_for_everyone() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator - for id in [1, 10, 20] { + // usual user, validator, nominator, not bonded + for id in [1, 10, 20, 30] { StakeTracker::on_nominator_update(&id, Vec::new()); assert_eq!( VoterList::get_score(&id).unwrap(), - Pallet::::to_vote(Staking::stake(&id).map(|s| s.active).unwrap()) + Pallet::::to_vote( + Staking::stake(&id).map(|s| s.active).unwrap_or_default() + ) ); } }); @@ -136,28 +127,20 @@ mod on_validator_update { }); } - #[test] - #[should_panic] - fn panics_when_not_bonded() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(VoterList::count(), 0); - // user without stake - assert_storage_noop!(StakeTracker::on_validator_update(&30)); - }); - } - #[test] // It is the caller's problem to make sure `on_validator_update` is called in the right context. fn works_for_everyone() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator - for id in [1, 10, 20] { + // usual user, validator, nominator, no stake + for id in [1, 10, 20, 30] { StakeTracker::on_validator_update(&id); assert_eq!( VoterList::get_score(&id).unwrap(), - Pallet::::to_vote(Staking::stake(&id).map(|s| s.active).unwrap()) + Pallet::::to_vote( + Staking::stake(&id).map(|s| s.active).unwrap_or_default() + ) ); } }); @@ -215,7 +198,7 @@ mod on_nominator_remove { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator + // usual user, validator, nominator, bonded for id in [1, 10, 20, 30] { let _ = VoterList::on_insert(id, 100); assert_eq!(VoterList::count(), 1); From 3f6b5b97e43d56318f4396e50dd2d60c0f9992ce Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 17:37:09 +0100 Subject: [PATCH 50/88] implement OnStakingUpdate for tuples --- Cargo.lock | 1 + primitives/staking/Cargo.toml | 1 + primitives/staking/src/lib.rs | 36 +++++++++++++++++------------------ 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb89622ee6562..e09be709ac8c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10274,6 +10274,7 @@ dependencies = [ name = "sp-staking" version = "4.0.0-dev" dependencies = [ + "impl-trait-for-tuples", "parity-scale-codec", "scale-info", "sp-arithmetic", diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 3cc7a3bb04031..17da4e1582633 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -19,6 +19,7 @@ sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } +impl-trait-for-tuples = "0.2.2" [features] default = ["std"] diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 15e6a48d319b4..d5ad9f5473601 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -62,8 +62,8 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. -#[derive(Default)] -pub struct Stake { +#[derive(Default, Clone)] +pub struct Stake { /// The stash account whose balance is actually locked and at stake. pub stash: AccountId, /// The total stake that `stash` has in the staking system. This includes the @@ -85,7 +85,7 @@ pub struct Stake { /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. -pub trait OnStakingUpdate { +pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. /// /// Also called when someone stakes for the first time. (TODO: is it? this is why we need unit @@ -106,30 +106,30 @@ pub trait OnStakingUpdate { fn on_unstake(who: &AccountId); // -> basically `kill_stash` } -#[cfg(feature = "std")] -impl OnStakingUpdate for () { - fn on_stake_update(_: &AccountId, _: Option>) { - // stub +#[impl_trait_for_tuples::impl_for_tuples(10)] +impl OnStakingUpdate for Tuple { + fn on_stake_update(who: &AccountId, prev_stake: Option>) { + for_tuples!( #( Tuple::on_stake_update(who, prev_stake.clone()); )* ); } - fn on_nominator_update(_: &AccountId, _: Vec) { - // stub + fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { + for_tuples!( #( Tuple::on_nominator_update(who, prev_nominations.clone()); )* ); } - fn on_validator_update(_: &AccountId) { - // stub + fn on_validator_update(who: &AccountId) { + for_tuples!( #( Tuple::on_validator_update(who); )* ); } - fn on_validator_remove(_: &AccountId) { - // stub + fn on_validator_remove(who: &AccountId) { + for_tuples!( #( Tuple::on_validator_remove(who); )* ); } - fn on_nominator_remove(_: &AccountId, _: Vec) { - // stub + fn on_nominator_remove(who: &AccountId, nominations: Vec) { + for_tuples!( #( Tuple::on_nominator_remove(who, nominations.clone()); )* ); } - fn on_unstake(_: &AccountId) { - // stub + fn on_unstake(who: &AccountId) { + for_tuples!( #( Tuple::on_unstake(who); )* ); } } @@ -150,7 +150,7 @@ pub trait StakingInterface { + Saturating; /// AccountId type used by the staking system - type AccountId; + type AccountId: Clone; /// whatever type CurrencyToVote: CurrencyToVote; From 489b71c61c2111fdd04924b7e8bfafb941940923 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 17:41:44 +0100 Subject: [PATCH 51/88] minor fixes --- frame/stake-tracker/src/mock.rs | 34 ++++++++++++++++----------------- frame/staking/src/mock.rs | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 705fb6bb15f79..6abec8bd023c4 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -139,19 +139,19 @@ impl StakingInterface for StakingMock { } fn minimum_validator_bond() -> Self::Balance { - unimplemented!("Currently not used.") + unreachable!(); } fn stash_by_ctrl(_: &Self::AccountId) -> Result { - unimplemented!("Currently not used.") + unreachable!(); } fn bonding_duration() -> EraIndex { - unimplemented!("Currently not used.") + unreachable!(); } fn current_era() -> EraIndex { - unimplemented!("Currently not used.") + unreachable!(); } fn stake( @@ -160,7 +160,7 @@ impl StakingInterface for StakingMock { if *who >= 30 { return Err(DispatchError::Other("not bonded")) } - let stake = ::Currency::total_balance(who); + let stake = Balances::total_balance(who); Ok(Stake { stash: *who, active: stake.saturating_sub(ExistentialDeposit::get()), @@ -169,43 +169,43 @@ impl StakingInterface for StakingMock { } fn bond(_: &Self::AccountId, _: Self::Balance, _: &Self::AccountId) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn nominate(_: &Self::AccountId, _: Vec) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn chill(_: &Self::AccountId) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn bond_extra(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn unbond(_: &Self::AccountId, _: Self::Balance) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn withdraw_unbonded(_: Self::AccountId, _: u32) -> Result { - unimplemented!("Currently not used.") + unreachable!(); } fn desired_validator_count() -> u32 { - unimplemented!("Currently not used.") + unreachable!(); } fn election_ongoing() -> bool { - unimplemented!("Currently not used.") + unreachable!(); } fn force_unstake(_: Self::AccountId) -> DispatchResult { - unimplemented!("Currently not used.") + unreachable!(); } fn is_exposed_in_era(_: &Self::AccountId, _: &EraIndex) -> bool { - unimplemented!("Currently not used.") + unreachable!(); } fn is_validator(who: &Self::AccountId) -> bool { @@ -226,12 +226,12 @@ impl StakingInterface for StakingMock { _: &Self::AccountId, _: Vec<(Self::AccountId, Self::Balance)>, ) { - unimplemented!("Currently not used.") + unreachable!(); } #[cfg(feature = "runtime-benchmarks")] fn set_current_era(_: EraIndex) { - unimplemented!("Currently not used.") + unreachable!(); } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 6e6d049d1f0fd..5ff88513f7939 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -99,7 +99,7 @@ frame_support::construct_runtime!( Session: pallet_session, Historical: pallet_session::historical, VoterBagsList: pallet_bags_list::, - StakeTracker: pallet_stake_tracker, + StakeTracker: pallet_stake_tracker, } ); From aec88796ba6fd0eefb90b3bdc599e099c80f33b5 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 18:30:50 +0100 Subject: [PATCH 52/88] introduce a testing framework for OnStakingUpdate --- frame/staking/src/mock.rs | 52 +++++++++++++++++++++++++++++++-- frame/staking/src/pallet/mod.rs | 2 -- 2 files changed, 50 insertions(+), 4 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 5ff88513f7939..8693d26090a08 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -35,7 +35,10 @@ use sp_runtime::{ testing::{Header, UintAuthorityId}, traits::{IdentityLookup, Zero}, }; -use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; +use sp_staking::{ + offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, + OnStakingUpdate, Stake, +}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -234,6 +237,12 @@ parameter_types! { pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); + pub static OnStakeUpdate: Vec<(AccountId, Option>)> = Vec::new(); + pub static OnNominatorUpdate: Vec<(AccountId, Vec)> = Vec::new(); + pub static OnValidatorUpdate: Vec = Vec::new(); + pub static OnValidatorRemove: Vec = Vec::new(); + pub static OnNominatorRemove: Vec<(AccountId, Vec)> = Vec::new(); + pub static OnUnstake: Vec = Vec::new(); pub static MaxWinners: u32 = 100; } @@ -276,6 +285,45 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM } } +pub struct EventListenerMock; +impl OnStakingUpdate for EventListenerMock { + fn on_stake_update(who: &AccountId, prev_stake: Option>) { + let mut vec = OnStakeUpdate::get(); + vec.push((who.clone(), prev_stake)); + OnStakeUpdate::set(vec); + } + + fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { + let mut vec = OnNominatorUpdate::get(); + vec.push((who.clone(), prev_nominations)); + OnNominatorUpdate::set(vec); + } + + fn on_validator_update(who: &AccountId) { + let mut vec = OnValidatorUpdate::get(); + vec.push(who.clone()); + OnValidatorUpdate::set(vec); + } + + fn on_validator_remove(who: &AccountId) { + let mut vec = OnValidatorRemove::get(); + vec.push(who.clone()); + OnValidatorRemove::set(vec); + } + + fn on_nominator_remove(who: &AccountId, nominations: Vec) { + let mut vec = OnNominatorRemove::get(); + vec.push((who.clone(), nominations)); + OnNominatorRemove::set(vec); + } + + fn on_unstake(who: &AccountId) { + let mut vec = OnUnstake::get(); + vec.push(who.clone()); + OnUnstake::set(vec); + } +} + impl crate::pallet::pallet::Config for Test { type MaxNominations = MaxNominations; type Currency = Balances; @@ -305,7 +353,7 @@ impl crate::pallet::pallet::Config for Test { type OnStakerSlash = OnStakerSlashMock; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); - type EventListeners = StakeTracker; + type EventListeners = (StakeTracker, EventListenerMock); } impl pallet_stake_tracker::Config for Test { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index bf7e1f1b73cb7..8442f8cb2157e 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -223,8 +223,6 @@ pub mod pallet { /// staker. In case of `bags-list`, this always means using `rebag` and `putInFrontOf`. /// /// Invariant: what comes out of this list will always be a nominator. - // NOTE: Staking does not maintain this list, it merely reads from it. The list is - // maintained by `EventListeners` implementors. type VoterList: SortedListProvider; /// WIP: This is a noop as of now, the actual business logic that's described below is going From 6bf3fcb1318fbfd76ee46053e455039e0af8f3d6 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 11 Feb 2023 18:41:30 +0100 Subject: [PATCH 53/88] more small fixes --- frame/staking/src/pallet/impls.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 4ace6c3e39cfc..337d17ef804be 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1546,6 +1546,7 @@ impl SortedListProvider for UseNominatorsAndValidatorsM impl StakingInterface for Pallet { type AccountId = T::AccountId; type Balance = BalanceOf; + type CurrencyToVote = T::CurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { MinNominatorBond::::get() @@ -1667,8 +1668,6 @@ impl StakingInterface for Pallet { } } - type CurrencyToVote = T::CurrencyToVote; - fn is_validator(who: &Self::AccountId) -> bool { Validators::::contains_key(who) } From c04965378531a23fc7dfa0e39a71bc3cc9216126 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 13:24:56 +0100 Subject: [PATCH 54/88] re-order methods --- frame/staking/src/pallet/impls.rs | 90 +++++++++++++++---------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 337d17ef804be..147f7df6f4a97 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1544,8 +1544,8 @@ impl SortedListProvider for UseNominatorsAndValidatorsM // NOTE: in this entire impl block, the assumption is that `who` is a stash account. impl StakingInterface for Pallet { - type AccountId = T::AccountId; type Balance = BalanceOf; + type AccountId = T::AccountId; type CurrencyToVote = T::CurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { @@ -1556,31 +1556,12 @@ impl StakingInterface for Pallet { MinValidatorBond::::get() } - fn desired_validator_count() -> u32 { - ValidatorCount::::get() - } - - fn election_ongoing() -> bool { - T::ElectionProvider::ongoing() - } - - fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { - let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); - Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) - } - fn stash_by_ctrl(controller: &Self::AccountId) -> Result { Self::ledger(controller) .map(|l| l.stash) .ok_or(Error::::NotController.into()) } - fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { - ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *who || exposures.others.iter().any(|i| i.who == *who) - }) - } - fn bonding_duration() -> EraIndex { T::BondingDuration::get() } @@ -1598,15 +1579,23 @@ impl StakingInterface for Pallet { .ok_or(Error::::NotStash.into()) } - fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { - Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) + fn bond( + who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult { + Self::bond( + RawOrigin::Signed(who.clone()).into(), + T::Lookup::unlookup(who.clone()), + value, + RewardDestination::Account(payee.clone()), + ) } - fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { + fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - Self::unbond(RawOrigin::Signed(ctrl).into(), value) - .map_err(|with_post| with_post.error) - .map(|_| ()) + let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); + Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } fn chill(who: &Self::AccountId) -> DispatchResult { @@ -1616,6 +1605,17 @@ impl StakingInterface for Pallet { Self::chill(RawOrigin::Signed(ctrl).into()) } + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) + } + + fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + Self::unbond(RawOrigin::Signed(ctrl).into(), value) + .map_err(|with_post| with_post.error) + .map(|_| ()) + } + fn withdraw_unbonded( who: Self::AccountId, num_slashing_spans: u32, @@ -1626,27 +1626,27 @@ impl StakingInterface for Pallet { .map_err(|with_post| with_post.error) } - fn bond( - who: &Self::AccountId, - value: Self::Balance, - payee: &Self::AccountId, - ) -> DispatchResult { - Self::bond( - RawOrigin::Signed(who.clone()).into(), - T::Lookup::unlookup(who.clone()), - value, - RewardDestination::Account(payee.clone()), - ) + fn desired_validator_count() -> u32 { + ValidatorCount::::get() } - fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); - Self::nominate(RawOrigin::Signed(ctrl).into(), targets) + fn election_ongoing() -> bool { + T::ElectionProvider::ongoing() } - fn nominations(who: &Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) + fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { + let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); + Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) + } + + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { + validator == *who || exposures.others.iter().any(|i| i.who == *who) + }) + } + + fn is_validator(who: &Self::AccountId) -> bool { + Validators::::contains_key(who) } sp_staking::runtime_benchmarks_enabled! { @@ -1668,8 +1668,8 @@ impl StakingInterface for Pallet { } } - fn is_validator(who: &Self::AccountId) -> bool { - Validators::::contains_key(who) + fn nominations(who: &Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) } } From c987805e1e9842c728c734a18dca373f0f1b8315 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 13:34:31 +0100 Subject: [PATCH 55/88] integration tests for events --- frame/staking/src/mock.rs | 27 +++ frame/staking/src/testing_utils.rs | 1 - frame/staking/src/tests.rs | 317 ++++++++++++++++++++++++----- primitives/staking/src/lib.rs | 2 +- 4 files changed, 295 insertions(+), 52 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 8693d26090a08..682f6e5b4ea64 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -378,6 +378,7 @@ pub struct ExtBuilder { status: BTreeMap>, stakes: BTreeMap, stakers: Vec<(AccountId, AccountId, Balance, StakerStatus)>, + check_events: bool, } impl Default for ExtBuilder { @@ -395,6 +396,7 @@ impl Default for ExtBuilder { status: Default::default(), stakes: Default::default(), stakers: Default::default(), + check_events: false, } } } @@ -474,6 +476,10 @@ impl ExtBuilder { self.balance_factor = factor; self } + pub fn check_events(mut self, check: bool) -> Self { + self.check_events = check; + self + } fn build(self) -> sp_io::TestExternalities { sp_tracing::try_init_simple(); let mut storage = frame_system::GenesisConfig::default().build_storage::().unwrap(); @@ -599,8 +605,29 @@ impl ExtBuilder { } pub fn build_and_execute(self, test: impl FnOnce() -> ()) { sp_tracing::try_init_simple(); + let check_events = self.check_events; let mut ext = self.build(); + ext.execute_with(|| { + // Clean up all the events produced on init. + OnStakeUpdate::take(); + OnNominatorUpdate::take(); + OnValidatorUpdate::take(); + OnValidatorRemove::take(); + OnNominatorRemove::take(); + OnUnstake::take(); + }); ext.execute_with(test); + if check_events { + ext.execute_with(|| { + // Make sure we have checked all the events produced by the test. + assert!(OnStakeUpdate::get().is_empty(), "Unexpected OnStakeUpdate events"); + assert!(OnNominatorUpdate::get().is_empty(), "Unexpected OnNominatorUpdate events"); + assert!(OnValidatorUpdate::get().is_empty(), "Unexpected OnValidatorUpdate events"); + assert!(OnValidatorRemove::get().is_empty(), "Unexpected OnValidatorRemove events"); + assert!(OnNominatorRemove::get().is_empty(), "Unexpected OnNominatorRemove events"); + assert!(OnUnstake::get().is_empty(), "Unexpected OnUnstake events"); + }); + } ext.execute_with(|| { Staking::do_try_state(System::block_number()).unwrap(); }); diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index a7ce412bb6737..790750c18fc1a 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -27,7 +27,6 @@ use rand_chacha::{ }; use sp_io::hashing::blake2_256; -use frame_election_provider_support::SortedListProvider; use frame_support::{pallet_prelude::*, traits::Currency}; use sp_runtime::{traits::StaticLookup, Perbill}; use sp_std::prelude::*; diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index b278700ffd267..6d01849b6302c 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -34,7 +34,7 @@ use sp_runtime::{ }; use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, - SessionIndex, + SessionIndex, Stake, }; use sp_std::prelude::*; use substrate_test_utils::assert_eq_uvec; @@ -230,7 +230,7 @@ fn basic_setup_works() { #[test] fn change_controller_works() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().check_events(true).build_and_execute(|| { // 10 and 11 are bonded as stash controller. assert_eq!(Staking::bonded(&11), Some(10)); @@ -248,6 +248,9 @@ fn change_controller_works() { Error::::NotController, ); assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); + + assert_eq!(OnValidatorUpdate::take(), vec![11]); + assert_eq!(OnValidatorRemove::take(), vec![11]); }) } @@ -453,6 +456,7 @@ fn staking_should_work() { #[test] fn blocking_and_kicking_works() { ExtBuilder::default() + .check_events(true) .minimum_validator_count(1) .validator_count(4) .nominate(true) @@ -475,6 +479,7 @@ fn blocking_and_kicking_works() { Staking::nominate(RuntimeOrigin::signed(100), vec![11]), Error::::BadTarget ); + assert_eq!(OnNominatorUpdate::take(), vec![(101, vec![11, 21])]); }); } @@ -1159,7 +1164,7 @@ fn bond_extra_works() { // Tests that extra `free_balance` in the stash can be added to stake // NOTE: this tests only verifies `StakingLedger` for correct updates // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().check_events(true).build_and_execute(|| { // Check that account 10 is a validator assert!(>::contains_key(11)); // Check that account 10 is bonded to account 11 @@ -1206,6 +1211,14 @@ fn bond_extra_works() { claimed_rewards: bounded_vec![], }) ); + + assert_eq!( + OnStakeUpdate::take(), + vec![ + (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), + (11, Some(Stake { stash: 11, total: 1100, active: 1100 })) + ] + ) }); } @@ -1217,7 +1230,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // * It can add extra funds to the bonded account. // * it can unbond a portion of its funds from the stash account. // * Once the unbonding period is done, it can actually take the funds out of the stash. - ExtBuilder::default().nominate(false).build_and_execute(|| { + ExtBuilder::default().check_events(true).nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); @@ -1346,6 +1359,16 @@ fn bond_extra_and_withdraw_unbonded_works() { claimed_rewards: bounded_vec![], }), ); + assert_eq!( + OnStakeUpdate::take(), + vec![ + (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), + (11, Some(Stake { stash: 11, total: 1100, active: 1100 })), + (11, Some(Stake { stash: 11, total: 1100, active: 100 })), + (11, Some(Stake { stash: 11, total: 1100, active: 100 })), + (11, Some(Stake { stash: 11, total: 1100, active: 100 })) + ] + ); }) } @@ -1394,7 +1417,7 @@ fn many_unbond_calls_should_work() { #[test] fn auto_withdraw_may_not_unlock_all_chunks() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().check_events(true).build_and_execute(|| { // set `MaxUnlockingChunks` to a low number to test case when the unbonding period // is larger than the number of unlocking chunks available, which may result on a // `Error::NoMoreChunks`, even when the auto-withdraw tries to release locked chunks. @@ -1418,6 +1441,16 @@ fn auto_withdraw_may_not_unlock_all_chunks() { current_era += 10; mock::start_active_era(current_era); assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); + + assert_eq!( + OnStakeUpdate::take(), + vec![ + (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), + (11, Some(Stake { stash: 11, total: 1000, active: 999 })), + (11, Some(Stake { stash: 11, total: 1000, active: 999 })), + (11, Some(Stake { stash: 11, total: 999, active: 999 })) + ] + ); }) } @@ -1708,6 +1741,7 @@ fn rebond_emits_right_value_in_event() { #[test] fn reward_to_stake_works() { ExtBuilder::default() + .check_events(true) .nominate(false) .set_status(31, StakerStatus::Idle) .set_status(41, StakerStatus::Idle) @@ -1758,6 +1792,14 @@ fn reward_to_stake_works() { // -- new infos assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000 + total_payout_0 / 2); assert_eq!(Staking::eras_stakers(active_era(), 21).total, 69 + total_payout_0 / 2); + + assert_eq!( + OnStakeUpdate::take(), + [ + (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), + (21, Some(Stake { stash: 21, total: 69, active: 69 })) + ] + ); }); } @@ -1908,6 +1950,7 @@ fn bond_with_no_staked_value() { .balance_factor(5) .nominate(false) .minimum_validator_count(1) + .check_events(true) .build_and_execute(|| { // Can't bond with 1 assert_noop!( @@ -1950,6 +1993,15 @@ fn bond_with_no_staked_value() { assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); assert!(Staking::ledger(2).is_none()); assert_eq!(Balances::locks(&1).len(), 0); + assert_eq!( + OnStakeUpdate::take(), + vec![ + (1, None), + (1, Some(Stake { stash: 1, total: 5, active: 5 })), + (1, Some(Stake { stash: 1, total: 5, active: 0 })) + ] + ); + assert_eq!(OnUnstake::take(), vec![1]); }); } @@ -1959,6 +2011,7 @@ fn bond_with_little_staked_value_bounded() { .validator_count(3) .nominate(false) .minimum_validator_count(1) + .check_events(true) .build_and_execute(|| { // setup assert_ok!(Staking::chill(RuntimeOrigin::signed(30))); @@ -2024,6 +2077,18 @@ fn bond_with_little_staked_value_bounded() { init_balance_10 + total_payout_0 / 3 + total_payout_1 / 3, 2, ); + + assert_eq!( + OnStakeUpdate::take(), + vec![ + (1, None), + (21, Some(Stake { stash: 21, total: 1000, active: 1000 })), + (31, Some(Stake { stash: 31, total: 500, active: 500 })), + (21, Some(Stake { stash: 21, total: 4692, active: 4692 })) + ] + ); + assert_eq!(OnValidatorUpdate::take(), vec![1]); + assert_eq!(OnValidatorRemove::take(), vec![31]); }); } @@ -2034,6 +2099,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { .nominate(false) .minimum_validator_count(1) .set_stake(31, 1000) + .check_events(true) .build_and_execute(|| { // ensure all have equal stake. assert_eq!( @@ -2077,6 +2143,8 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }) ], ); + assert_eq!(OnStakeUpdate::take(), vec![(1, None), (3, None)]); + assert_eq!(OnNominatorUpdate::take(), vec![(1, vec![]), (3, vec![])]); }); } @@ -2088,6 +2156,7 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { .nominate(false) .set_stake(31, 1000) .minimum_validator_count(1) + .check_events(true) .build_and_execute(|| { // ensure all have equal stake. assert_eq!( @@ -2131,6 +2200,8 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }) ], ); + assert_eq!(OnStakeUpdate::take(), vec![(1, None), (3, None)]); + assert_eq!(OnNominatorUpdate::take(), vec![(1, vec![]), (3, vec![])]); }); } @@ -4060,51 +4131,57 @@ fn payout_stakers_handles_weight_refund() { #[test] fn bond_during_era_correctly_populates_claimed_rewards() { - ExtBuilder::default().has_stakers(false).build_and_execute(|| { - // Era = None - bond_validator(9, 8, 1000); - assert_eq!( - Staking::ledger(&8), - Some(StakingLedger { - stash: 9, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - mock::start_active_era(5); - bond_validator(11, 10, 1000); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (0..5).collect::>().try_into().unwrap(), - }) - ); + ExtBuilder::default() + .has_stakers(false) + .check_events(true) + .build_and_execute(|| { + // Era = None + bond_validator(9, 8, 1000); + assert_eq!( + Staking::ledger(&8), + Some(StakingLedger { + stash: 9, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: bounded_vec![], + }) + ); + mock::start_active_era(5); + bond_validator(11, 10, 1000); + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: (0..5).collect::>().try_into().unwrap(), + }) + ); - // make sure only era upto history depth is stored - let current_era = 99; - let last_reward_era = 99 - HistoryDepth::get(); - mock::start_active_era(current_era); - bond_validator(13, 12, 1000); - assert_eq!( - Staking::ledger(&12), - Some(StakingLedger { - stash: 13, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (last_reward_era..current_era) - .collect::>() - .try_into() - .unwrap(), - }) - ); - }); + // make sure only era upto history depth is stored + let current_era = 99; + let last_reward_era = 99 - HistoryDepth::get(); + mock::start_active_era(current_era); + bond_validator(13, 12, 1000); + assert_eq!( + Staking::ledger(&12), + Some(StakingLedger { + stash: 13, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: (last_reward_era..current_era) + .collect::>() + .try_into() + .unwrap(), + }) + ); + + assert_eq!(OnStakeUpdate::take(), vec![(9, None), (11, None), (13, None)]); + assert_eq!(OnValidatorUpdate::take(), vec![9, 11, 13]); + }); } #[test] @@ -4337,6 +4414,7 @@ fn cannot_rebond_to_lower_than_ed() { ExtBuilder::default() .existential_deposit(10) .balance_factor(10) + .check_events(true) .build_and_execute(|| { // initial stuff. assert_eq!( @@ -4369,6 +4447,11 @@ fn cannot_rebond_to_lower_than_ed() { Staking::rebond(RuntimeOrigin::signed(20), 5), Error::::InsufficientBond ); + assert_eq!( + OnStakeUpdate::take(), + vec![(21, Some(Stake { stash: 21, total: 10000, active: 10000 }))] + ); + assert_eq!(OnValidatorRemove::take(), [21]); }) } @@ -4377,6 +4460,7 @@ fn cannot_bond_extra_to_lower_than_ed() { ExtBuilder::default() .existential_deposit(10) .balance_factor(10) + .check_events(true) .build_and_execute(|| { // initial stuff. assert_eq!( @@ -4409,6 +4493,11 @@ fn cannot_bond_extra_to_lower_than_ed() { Staking::bond_extra(RuntimeOrigin::signed(21), 5), Error::::InsufficientBond, ); + assert_eq!( + OnStakeUpdate::take(), + vec![(21, Some(Stake { stash: 21, total: 10000, active: 10000 }))] + ); + assert_eq!(OnValidatorRemove::take(), vec![21]); }) } @@ -4762,6 +4851,7 @@ fn chill_other_works() { .balance_factor(100) .min_nominator_bond(1_000) .min_validator_bond(1_500) + .check_events(true) .build_and_execute(|| { let initial_validators = Validators::::count(); let initial_nominators = Nominators::::count(); @@ -4908,12 +4998,87 @@ fn chill_other_works() { // chill a validator. Limit is reached, chill-able. assert_eq!(Validators::::count(), 9); assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 3)); + + assert_eq!( + OnStakeUpdate::take(), + vec![ + (0, None), + (2, None), + (4, None), + (6, None), + (8, None), + (10, None), + (12, None), + (14, None), + (16, None), + (18, None), + (20, None), + (22, None), + (24, None), + (26, None), + (28, None), + (30, None), + (32, None), + (34, None), + (36, None), + (38, None), + (40, None), + (42, None), + (44, None), + (46, None), + (48, None), + (50, None), + (52, None), + (54, None), + (56, None), + (58, None), + ], + ); + assert_eq!( + OnNominatorUpdate::take(), + vec![ + (0, vec![]), + (4, vec![]), + (8, vec![]), + (12, vec![]), + (16, vec![]), + (20, vec![]), + (24, vec![]), + (28, vec![]), + (32, vec![]), + (36, vec![]), + (40, vec![]), + (44, vec![]), + (48, vec![]), + (52, vec![]), + (56, vec![]) + ] + ); + assert_eq!( + OnValidatorUpdate::take(), + vec![2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58] + ); + assert_eq!(OnValidatorRemove::take(), vec![26, 30, 34, 38, 42, 46, 50, 54, 58, 2]); + assert_eq!( + OnNominatorRemove::take(), + vec![ + (24, vec![1]), + (28, vec![1]), + (32, vec![1]), + (36, vec![1]), + (40, vec![1]), + (44, vec![1]), + (48, vec![1]), + (52, vec![1]), + (56, vec![1]), + ], + ); }) } #[test] fn capped_stakers_works() { - ExtBuilder::default().build_and_execute(|| { + ExtBuilder::default().check_events(true).build_and_execute(|| { let validator_count = Validators::::count(); assert_eq!(validator_count, 3); let nominator_count = Nominators::::count(); @@ -5008,6 +5173,58 @@ fn capped_stakers_works() { RuntimeOrigin::signed(last_validator), ValidatorPrefs::default() )); + assert_eq!( + OnStakeUpdate::take(), + vec![ + (12372621823874611106, None), + (17270956243860093491, None), + (5465328850787429409, None), + (17161747032660228993, None), + (15952342661998645977, None), + (3727006293154618313, None), + (1400879179532508248, None), + (1021376644067438394, None), + (4362915210409513192, None), + (777994016134154318, None), + (15963114285730158603, None), + (12891137879492301340, None), + (4753808362819695445, None), + (17018405332266509855, None), + (7651320429057838151, None), + (8398748157494047725, None), + (1473894167078292758, None), + (15979092350402807284, None) + ] + ); + assert_eq!( + OnNominatorUpdate::take(), + vec![ + (4362915210409513192, vec![]), + (777994016134154318, vec![]), + (15963114285730158603, vec![]), + (12891137879492301340, vec![]), + (4753808362819695445, vec![]), + (17018405332266509855, vec![]), + (7651320429057838151, vec![]), + (8398748157494047725, vec![]), + (1473894167078292758, vec![]), + (1473894167078292758, vec![1]), + (15979092350402807284, vec![]) + ] + ); + assert_eq!( + OnValidatorUpdate::take(), + vec![ + 12372621823874611106, + 17270956243860093491, + 5465328850787429409, + 17161747032660228993, + 15952342661998645977, + 3727006293154618313, + 1400879179532508248, + 1021376644067438394 + ] + ); }) } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index d5ad9f5473601..a72183a6e2d48 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -62,7 +62,7 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug, Eq, PartialEq)] pub struct Stake { /// The stash account whose balance is actually locked and at stake. pub stash: AccountId, From e6676f72ef5c2f8d6b0e9b122565452b8ad3077d Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 17:30:52 +0100 Subject: [PATCH 56/88] import fix --- frame/staking/src/testing_utils.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/staking/src/testing_utils.rs b/frame/staking/src/testing_utils.rs index 790750c18fc1a..dc6cb73223bbd 100644 --- a/frame/staking/src/testing_utils.rs +++ b/frame/staking/src/testing_utils.rs @@ -43,6 +43,7 @@ pub fn clear_validators_and_nominators() { Nominators::::remove_all(); frame_election_provider_support::runtime_benchmarks_or_test_enabled! { + use frame_election_provider_support::SortedListProvider; // NOTE: safe to call outside block production T::VoterList::unsafe_clear(); } From e3ae48f9560181bf64bdccc5c606f2d4d5927a32 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 17:39:17 +0100 Subject: [PATCH 57/88] fix clippy --- frame/staking/src/mock.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 682f6e5b4ea64..201b7947d3535 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -289,37 +289,37 @@ pub struct EventListenerMock; impl OnStakingUpdate for EventListenerMock { fn on_stake_update(who: &AccountId, prev_stake: Option>) { let mut vec = OnStakeUpdate::get(); - vec.push((who.clone(), prev_stake)); + vec.push((*who, prev_stake)); OnStakeUpdate::set(vec); } fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { let mut vec = OnNominatorUpdate::get(); - vec.push((who.clone(), prev_nominations)); + vec.push((*who, prev_nominations)); OnNominatorUpdate::set(vec); } fn on_validator_update(who: &AccountId) { let mut vec = OnValidatorUpdate::get(); - vec.push(who.clone()); + vec.push(*who); OnValidatorUpdate::set(vec); } fn on_validator_remove(who: &AccountId) { let mut vec = OnValidatorRemove::get(); - vec.push(who.clone()); + vec.push(*who); OnValidatorRemove::set(vec); } fn on_nominator_remove(who: &AccountId, nominations: Vec) { let mut vec = OnNominatorRemove::get(); - vec.push((who.clone(), nominations)); + vec.push((*who, nominations)); OnNominatorRemove::set(vec); } fn on_unstake(who: &AccountId) { let mut vec = OnUnstake::get(); - vec.push(who.clone()); + vec.push(*who); OnUnstake::set(vec); } } From 06cd68c2bff9267937225448af6ab83c2630e796 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 18:06:03 +0100 Subject: [PATCH 58/88] sort out read-only lists --- bin/node/runtime/src/lib.rs | 6 +- frame/election-provider-support/src/lib.rs | 4 -- frame/stake-tracker/src/lib.rs | 80 +++++++++++++++++++++- 3 files changed, 84 insertions(+), 6 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index defd8ce2c0c5d..601b3907e4a14 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -572,7 +572,11 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; - type VoterList = ::VoterList; + type VoterList = pallet_stake_tracker::TrackedList< + Self, + VoteWeight, + ::VoterList, + >; // This a placeholder, to be introduced in the next PR as an instance of bags-list type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index b341656ef1109..0b6d1f3f05a43 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -480,10 +480,6 @@ where /// used on the implementing side of [`ElectionDataProvider`]. /// /// The scores (see [`Self::Score`]) are ascending, the higher, the better. -/// -/// Initially a part of [`SortedListProvider`], it allows for restricting a consumer to read-only -/// operations. This is particularly useful in case the list is populated by one entity and -/// read by another. pub trait SortedListProvider { /// The list's error type. type Error: sp_std::fmt::Debug; diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 54c0cd12fcde6..f1a3641f464f7 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -25,10 +25,14 @@ mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::traits::{Currency, CurrencyToVote}; pub use pallet::*; +use sp_runtime::{ + traits::{Bounded, Zero}, + Saturating, +}; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; -use sp_std::vec::Vec; +use sp_std::{boxed::Box, vec::Vec}; /// The balance type of this pallet. pub type BalanceOf = <::Staking as StakingInterface>::Balance; @@ -111,3 +115,77 @@ impl OnStakingUpdate> for Pallet { fn on_unstake(_who: &T::AccountId) {} } + +/// A wrapper for a given `SortedListProvider` that disables insert/update/remove operations, +/// effectively rendering it read-only, except for unsafe operations. +pub struct TrackedList(sp_std::marker::PhantomData<(T, S, P)>); + +impl> + SortedListProvider for TrackedList +{ + type Error = P::Error; + type Score = P::Score; + fn iter() -> Box> { + P::iter() + } + + fn iter_from( + start: &T::AccountId, + ) -> Result>, Self::Error> { + P::iter_from(start) + } + + fn count() -> u32 { + P::count() + } + + fn contains(id: &T::AccountId) -> bool { + P::contains(id) + } + + fn get_score(id: &T::AccountId) -> Result { + P::get_score(id) + } + + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), &'static str> { + P::try_state() + } + + fn on_insert(_id: T::AccountId, _score: Self::Score) -> Result<(), Self::Error> { + unreachable!() + } + + fn on_update(_id: &T::AccountId, _score: Self::Score) -> Result<(), Self::Error> { + unreachable!() + } + + fn on_increase(_id: &T::AccountId, _additional: Self::Score) -> Result<(), Self::Error> { + unreachable!() + } + + fn on_decrease(_id: &T::AccountId, _decreased: Self::Score) -> Result<(), Self::Error> { + unreachable!() + } + + fn on_remove(_id: &T::AccountId) -> Result<(), Self::Error> { + unreachable!() + } + + fn unsafe_regenerate( + all: impl IntoIterator, + score_of: Box Self::Score>, + ) -> u32 { + P::unsafe_regenerate(all, score_of) + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn unsafe_clear() { + P::unsafe_clear() + } + + #[cfg(any(feature = "runtime-benchmarks", test))] + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + P::score_update_worst_case(who, is_increase) + } +} From 391b0b420b73b8abb53baa50f82badba96a28515 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 12 Feb 2023 18:20:19 +0100 Subject: [PATCH 59/88] fix feature gates --- frame/stake-tracker/src/lib.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index f1a3641f464f7..88af8e1a8228d 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -179,13 +179,13 @@ impl Self::Score { - P::score_update_worst_case(who, is_increase) + fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { + P::score_update_worst_case(who, is_increase) + } } } From 2d1fe570da3c9822470eca3a2acf7d805107bf63 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 17 Feb 2023 10:29:39 +0100 Subject: [PATCH 60/88] Update frame/stake-tracker/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 88af8e1a8228d..58f496491db76 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -29,7 +29,6 @@ use sp_runtime::{ traits::{Bounded, Zero}, Saturating, }; - use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; use sp_std::{boxed::Box, vec::Vec}; From 435d72b058e33acf69ff8cde6bb7cb50a187d535 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 17 Feb 2023 11:00:35 +0100 Subject: [PATCH 61/88] address some review comments --- Cargo.lock | 1 - frame/stake-tracker/src/lib.rs | 2 +- frame/staking/src/pallet/mod.rs | 13 ++++++------- primitives/staking/Cargo.toml | 2 -- primitives/staking/src/currency_to_vote.rs | 6 ++++-- primitives/staking/src/lib.rs | 9 +++++---- 6 files changed, 16 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8c3b347191bab..1d4a7f738b8c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10269,7 +10269,6 @@ dependencies = [ "impl-trait-for-tuples", "parity-scale-codec", "scale-info", - "sp-arithmetic", "sp-core", "sp-runtime", "sp-std", diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 58f496491db76..3de509a132ae4 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -65,7 +65,7 @@ pub mod pallet { impl Pallet { pub fn active_stake_of(who: &T::AccountId) -> BalanceOf { - T::Staking::stake(&who).map(|l| l.active).unwrap_or_default() + T::Staking::stake(&who).map(|s| s.active).unwrap_or_default() } pub(crate) fn to_vote(balance: BalanceOf) -> VoteWeight { diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 6d3537d637400..d3d4a5a611d1b 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -776,8 +776,7 @@ pub mod pallet { CommissionTooLow, /// Some bound is not met. BoundNotMet, - /// Nominations are not decodable. This means that `Config::MaxNominations` has been - /// decreased without a migration. A nominator is then stuck until it's fixed, because we + /// Nominations are not decodable. A nominator is then stuck until it's fixed, because we /// can't forgo the bookkeeping. NotDecodable, } @@ -1145,10 +1144,10 @@ pub mod pallet { let is_nominator = Nominators::::contains_key(stash); - // If the nominator is not decodable - throw an error. The only reason for that could be - // a decrease of `MaxNominatorsCount`, which should be accompanied by a migration that + // If the nominator is not decodable - throw an error. One of the reasons for that could + // be a decrease of `MaxNominations`, which should be accompanied by a migration that // fixes those nominations. Otherwise the Staking pallet ends up in an inconsistent - // state, because we cannot do proper bookeeping. + // state, because we cannot do proper bookkeeping. if is_nominator && Nominators::::get(stash).is_none() { Err(Error::::NotDecodable)? } @@ -1703,8 +1702,8 @@ pub mod pallet { // // Otherwise, if caller is the same as the controller, this is just like `chill`. - // If the validator is not decodable - a migration needs to be executed to fix the - // number of nominations. We can't chill nominators without knowing their nominations. + // If the nominator is not decodable - a migration needs to be executed to fix the + // storage. We can't chill nominators without knowing their nominations. if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { return Err(Error::::NotDecodable)? } diff --git a/primitives/staking/Cargo.toml b/primitives/staking/Cargo.toml index 17da4e1582633..37ef32e7d6e74 100644 --- a/primitives/staking/Cargo.toml +++ b/primitives/staking/Cargo.toml @@ -18,7 +18,6 @@ scale-info = { version = "2.1.1", default-features = false, features = ["derive" sp-core = { version = "7.0.0", default-features = false, path = "../core" } sp-runtime = { version = "7.0.0", default-features = false, path = "../runtime" } sp-std = { version = "5.0.0", default-features = false, path = "../std" } -sp-arithmetic = { version = "6.0.0", default-features = false, path = "../arithmetic" } impl-trait-for-tuples = "0.2.2" [features] @@ -29,6 +28,5 @@ std = [ "sp-core/std", "sp-runtime/std", "sp-std/std", - "sp-arithmetic/std" ] runtime-benchmarks = [] diff --git a/primitives/staking/src/currency_to_vote.rs b/primitives/staking/src/currency_to_vote.rs index 038394971ea56..e4eac76219c3f 100644 --- a/primitives/staking/src/currency_to_vote.rs +++ b/primitives/staking/src/currency_to_vote.rs @@ -15,8 +15,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use sp_arithmetic::traits::{UniqueSaturatedFrom, UniqueSaturatedInto}; -use sp_runtime::SaturatedConversion; +use sp_runtime::{ + traits::{UniqueSaturatedFrom, UniqueSaturatedInto}, + SaturatedConversion, +}; /// A trait similar to `Convert` to convert values from `B` an abstract balance type /// into u64 and back from u128. (This conversion is used in election and other places where complex diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index a72183a6e2d48..a40ebecde39b0 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -88,9 +88,6 @@ pub struct Stake { pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. /// - /// Also called when someone stakes for the first time. (TODO: is it? this is why we need unit - /// tests for this pallet alone). - /// /// This is effectively any changes to the bond amount, such as bonding more funds, and /// unbonding. fn on_stake_update(who: &AccountId, prev_stake: Option>); @@ -107,7 +104,11 @@ pub trait OnStakingUpdate { } #[impl_trait_for_tuples::impl_for_tuples(10)] -impl OnStakingUpdate for Tuple { +impl OnStakingUpdate for Tuple +where + AccountId: Clone, + Balance: Copy, +{ fn on_stake_update(who: &AccountId, prev_stake: Option>) { for_tuples!( #( Tuple::on_stake_update(who, prev_stake.clone()); )* ); } From 501243b74548e8d12eb3a742265d97831c810302 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 17 Feb 2023 12:08:34 +0100 Subject: [PATCH 62/88] add some docs --- frame/stake-tracker/src/lib.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 3de509a132ae4..bd36c272f1d3f 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -15,6 +15,23 @@ // See the License for the specific language governing permissions and // limitations under the License. +//! # Stake Tracker Pallet +//! +//! The Stake Tracker pallet is used to maintain a sorted list of voters by listening to the events +//! that Staking emits. +//! +//! - [`Config`] +//! - [`Pallet`] +//! +//! ## Overview +//! +//! The goal of Stake Tracker is to reduce the burden of Staking pallet by taking care of the +//! [`Config::VoterList`] maintenance. This pallet implements [`OnStakingUpdate`] interface in order +//! to be able to listen to the events that Staking emits and propagate the changes to the list +//! accordingly. It also exposes [`TrackedList`] that stubs a subset of [`SortedListProvider`] +//! methods out effectively disabling the user's ability to update the list. This wrapper should be +//! used to pass the tracked entity to the consumer. + #![cfg_attr(not(feature = "std"), no_std)] #[cfg(test)] @@ -59,6 +76,7 @@ pub mod pallet { type Staking: StakingInterface; + /// A sorted list of nominators and validators, by their stake and self-stake respectively. type VoterList: SortedListProvider; } } From 8360474ae6ab7aa7892fe38f060bf016ca9afd59 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 17 Feb 2023 13:11:44 +0100 Subject: [PATCH 63/88] revert reorder --- frame/staking/src/pallet/impls.rs | 152 +++++++++++++++--------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 147f7df6f4a97..a30335425b388 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -1380,8 +1380,8 @@ impl ScoreProvider for Pallet { pub struct UseValidatorsMap(sp_std::marker::PhantomData); impl SortedListProvider for UseValidatorsMap { - type Error = (); type Score = BalanceOf; + type Error = (); /// Returns iterator over voter list, which can have `take` called on it. fn iter() -> Box> { @@ -1407,20 +1407,15 @@ impl SortedListProvider for UseValidatorsMap { Validators::::contains_key(id) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id).into()) - } - - #[cfg(feature = "try-runtime")] - fn try_state() -> Result<(), &'static str> { - Ok(()) - } - fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id).into()) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1431,6 +1426,19 @@ impl SortedListProvider for UseValidatorsMap { Ok(()) } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box Self::Score>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), &'static str> { + Ok(()) + } + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { fn unsafe_clear() { #[allow(deprecated)] @@ -1441,14 +1449,6 @@ impl SortedListProvider for UseValidatorsMap { unimplemented!() } } - - fn unsafe_regenerate( - _: impl IntoIterator, - _: Box Self::Score>, - ) -> u32 { - // nothing to do upon regenerate. - 0 - } } /// A simple voter list implementation that does not require any additional pallets. Note, this @@ -1494,20 +1494,15 @@ impl SortedListProvider for UseNominatorsAndValidatorsM Nominators::::contains_key(id) || Validators::::contains_key(id) } - fn get_score(id: &T::AccountId) -> Result { - Ok(Pallet::::weight_of(id)) - } - - #[cfg(feature = "try-runtime")] - fn try_state() -> Result<(), &'static str> { - Ok(()) - } - fn on_insert(_: T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on insert. Ok(()) } + fn get_score(id: &T::AccountId) -> Result { + Ok(Pallet::::weight_of(id)) + } + fn on_update(_: &T::AccountId, _weight: Self::Score) -> Result<(), Self::Error> { // nothing to do on update. Ok(()) @@ -1518,6 +1513,19 @@ impl SortedListProvider for UseNominatorsAndValidatorsM Ok(()) } + fn unsafe_regenerate( + _: impl IntoIterator, + _: Box Self::Score>, + ) -> u32 { + // nothing to do upon regenerate. + 0 + } + + #[cfg(feature = "try-runtime")] + fn try_state() -> Result<(), &'static str> { + Ok(()) + } + frame_election_provider_support::runtime_benchmarks_or_test_enabled! { fn unsafe_clear() { // NOTE: Caller must ensure this doesn't lead to too many storage accesses. This is a @@ -1532,26 +1540,31 @@ impl SortedListProvider for UseNominatorsAndValidatorsM unimplemented!() } } - - fn unsafe_regenerate( - _: impl IntoIterator, - _: Box Self::Score>, - ) -> u32 { - // nothing to do upon regenerate. - 0 - } } // NOTE: in this entire impl block, the assumption is that `who` is a stash account. impl StakingInterface for Pallet { - type Balance = BalanceOf; type AccountId = T::AccountId; + type Balance = BalanceOf; type CurrencyToVote = T::CurrencyToVote; fn minimum_nominator_bond() -> Self::Balance { MinNominatorBond::::get() } + fn desired_validator_count() -> u32 { + ValidatorCount::::get() + } + + fn election_ongoing() -> bool { + T::ElectionProvider::ongoing() + } + + fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { + let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); + Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) + } + fn minimum_validator_bond() -> Self::Balance { MinValidatorBond::::get() } @@ -1562,6 +1575,12 @@ impl StakingInterface for Pallet { .ok_or(Error::::NotController.into()) } + fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { + ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { + validator == *who || exposures.others.iter().any(|i| i.who == *who) + }) + } + fn bonding_duration() -> EraIndex { T::BondingDuration::get() } @@ -1579,23 +1598,15 @@ impl StakingInterface for Pallet { .ok_or(Error::::NotStash.into()) } - fn bond( - who: &Self::AccountId, - value: Self::Balance, - payee: &Self::AccountId, - ) -> DispatchResult { - Self::bond( - RawOrigin::Signed(who.clone()).into(), - T::Lookup::unlookup(who.clone()), - value, - RewardDestination::Account(payee.clone()), - ) + fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { + Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) } - fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { + fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); - Self::nominate(RawOrigin::Signed(ctrl).into(), targets) + Self::unbond(RawOrigin::Signed(ctrl).into(), value) + .map_err(|with_post| with_post.error) + .map(|_| ()) } fn chill(who: &Self::AccountId) -> DispatchResult { @@ -1605,17 +1616,6 @@ impl StakingInterface for Pallet { Self::chill(RawOrigin::Signed(ctrl).into()) } - fn bond_extra(who: &Self::AccountId, extra: Self::Balance) -> DispatchResult { - Self::bond_extra(RawOrigin::Signed(who.clone()).into(), extra) - } - - fn unbond(who: &Self::AccountId, value: Self::Balance) -> DispatchResult { - let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; - Self::unbond(RawOrigin::Signed(ctrl).into(), value) - .map_err(|with_post| with_post.error) - .map(|_| ()) - } - fn withdraw_unbonded( who: Self::AccountId, num_slashing_spans: u32, @@ -1626,23 +1626,23 @@ impl StakingInterface for Pallet { .map_err(|with_post| with_post.error) } - fn desired_validator_count() -> u32 { - ValidatorCount::::get() - } - - fn election_ongoing() -> bool { - T::ElectionProvider::ongoing() - } - - fn force_unstake(who: Self::AccountId) -> sp_runtime::DispatchResult { - let num_slashing_spans = Self::slashing_spans(&who).map_or(0, |s| s.iter().count() as u32); - Self::force_unstake(RawOrigin::Root.into(), who.clone(), num_slashing_spans) + fn bond( + who: &Self::AccountId, + value: Self::Balance, + payee: &Self::AccountId, + ) -> DispatchResult { + Self::bond( + RawOrigin::Signed(who.clone()).into(), + T::Lookup::unlookup(who.clone()), + value, + RewardDestination::Account(payee.clone()), + ) } - fn is_exposed_in_era(who: &Self::AccountId, era: &EraIndex) -> bool { - ErasStakers::::iter_prefix(era).any(|(validator, exposures)| { - validator == *who || exposures.others.iter().any(|i| i.who == *who) - }) + fn nominate(who: &Self::AccountId, targets: Vec) -> DispatchResult { + let ctrl = Self::bonded(who).ok_or(Error::::NotStash)?; + let targets = targets.into_iter().map(T::Lookup::unlookup).collect::>(); + Self::nominate(RawOrigin::Signed(ctrl).into(), targets) } fn is_validator(who: &Self::AccountId) -> bool { From 355145e855befc39c3317acdfb51a79e72382525 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Fri, 17 Feb 2023 13:15:50 +0100 Subject: [PATCH 64/88] fix beefy tests --- frame/beefy/src/mock.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index 8d8b831950d8e..fa79c365bd42d 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -231,6 +231,7 @@ impl pallet_staking::Config for Test { type OnStakerSlash = (); type BenchmarkingConfig = pallet_staking::TestBenchmarkingConfig; type WeightInfo = (); + type EventListeners = (); } impl pallet_offences::Config for Test { From 84f8d8f2be3187057003f0b76bb6353659d13019 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 19 Feb 2023 11:59:17 +0100 Subject: [PATCH 65/88] address some review comments --- frame/stake-tracker/src/lib.rs | 30 +++++++++++++++++++----------- frame/staking/src/pallet/impls.rs | 13 ++++++------- frame/staking/src/pallet/mod.rs | 6 +++--- frame/staking/src/tests.rs | 4 ++-- 4 files changed, 30 insertions(+), 23 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index bd36c272f1d3f..605de718cfa2c 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -40,7 +40,10 @@ pub(crate) mod mock; mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; -use frame_support::traits::{Currency, CurrencyToVote}; +use frame_support::{ + defensive, + traits::{Currency, CurrencyToVote}, +}; pub use pallet::*; use sp_runtime::{ traits::{Bounded, Zero}, @@ -169,24 +172,29 @@ impl Result<(), Self::Error> { - unreachable!() + fn on_insert(id: T::AccountId, score: Self::Score) -> Result<(), Self::Error> { + defensive!("TrackedList on_insert should never be called"); + P::on_increase(id, score) } - fn on_update(_id: &T::AccountId, _score: Self::Score) -> Result<(), Self::Error> { - unreachable!() + fn on_update(id: &T::AccountId, score: Self::Score) -> Result<(), Self::Error> { + defensive!("TrackedList on_update should never be called"); + P::on_update(id, score) } - fn on_increase(_id: &T::AccountId, _additional: Self::Score) -> Result<(), Self::Error> { - unreachable!() + fn on_increase(id: &T::AccountId, additional: Self::Score) -> Result<(), Self::Error> { + defensive!("TrackedList on_increase should never be called"); + P::on_increase(id, additional) } - fn on_decrease(_id: &T::AccountId, _decreased: Self::Score) -> Result<(), Self::Error> { - unreachable!() + fn on_decrease(id: &T::AccountId, decreased: Self::Score) -> Result<(), Self::Error> { + defensive!("TrackedList on_decrease should never be called"); + P::on_decrease(id, decreased) } - fn on_remove(_id: &T::AccountId) -> Result<(), Self::Error> { - unreachable!() + fn on_remove(id: &T::AccountId) -> Result<(), Self::Error> { + defensive!("TrackedList on_remove should never be called"); + P::on_remove(id) } fn unsafe_regenerate( diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a30335425b388..80db0a11e165c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -914,9 +914,8 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { - if !Validators::::contains_key(who) { - T::EventListeners::on_validator_update(who); - } + T::EventListeners::on_validator_update(who); + Validators::::insert(who, prefs); } @@ -1649,6 +1648,10 @@ impl StakingInterface for Pallet { Validators::::contains_key(who) } + fn nominations(who: &Self::AccountId) -> Option> { + Nominators::::get(who).map(|n| n.targets.into_inner()) + } + sp_staking::runtime_benchmarks_enabled! { fn add_era_stakers( current_era: &EraIndex, @@ -1667,10 +1670,6 @@ impl StakingInterface for Pallet { CurrentEra::::put(era); } } - - fn nominations(who: &Self::AccountId) -> Option> { - Nominators::::get(who).map(|n| n.targets.into_inner()) - } } #[cfg(any(test, feature = "try-runtime"))] diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index d3d4a5a611d1b..fc8153f275bd2 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -778,7 +778,7 @@ pub mod pallet { BoundNotMet, /// Nominations are not decodable. A nominator is then stuck until it's fixed, because we /// can't forgo the bookkeeping. - NotDecodable, + NotDecodableNominator, } #[pallet::hooks] @@ -1149,7 +1149,7 @@ pub mod pallet { // fixes those nominations. Otherwise the Staking pallet ends up in an inconsistent // state, because we cannot do proper bookkeeping. if is_nominator && Nominators::::get(stash).is_none() { - Err(Error::::NotDecodable)? + Err(Error::::NotDecodableNominator)? } // Only check limits if they are not already a nominator. @@ -1705,7 +1705,7 @@ pub mod pallet { // If the nominator is not decodable - a migration needs to be executed to fix the // storage. We can't chill nominators without knowing their nominations. if Nominators::::contains_key(&stash) && Nominators::::get(&stash).is_none() { - return Err(Error::::NotDecodable)? + return Err(Error::::NotDecodableNominator)? } if caller != controller { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6d01849b6302c..ef1e28550727e 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5357,7 +5357,7 @@ fn change_of_max_nominations() { // Impossible to re-nominate when not decodable. assert_noop!( Staking::nominate(RuntimeOrigin::signed(71), vec![1]), - Error::::NotDecodable + Error::::NotDecodableNominator ); assert!(Nominators::::contains_key(101)); @@ -5366,7 +5366,7 @@ fn change_of_max_nominations() { // Impossible to chill_other when not deodable. assert_noop!( Staking::chill_other(RuntimeOrigin::signed(70), 100), - Error::::NotDecodable + Error::::NotDecodableNominator ); }) } From 17d0fbbee9db2e791db58a1faa5e74dcab376ca3 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 19 Feb 2023 12:24:35 +0100 Subject: [PATCH 66/88] adjust docs --- frame/stake-tracker/src/lib.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 605de718cfa2c..c10cfdffe8fd4 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -25,12 +25,12 @@ //! //! ## Overview //! -//! The goal of Stake Tracker is to reduce the burden of Staking pallet by taking care of the -//! [`Config::VoterList`] maintenance. This pallet implements [`OnStakingUpdate`] interface in order -//! to be able to listen to the events that Staking emits and propagate the changes to the list -//! accordingly. It also exposes [`TrackedList`] that stubs a subset of [`SortedListProvider`] -//! methods out effectively disabling the user's ability to update the list. This wrapper should be -//! used to pass the tracked entity to the consumer. +//! The goal of Stake Tracker is to maintain [`SortedListProvider`] sorted list implementations +//! based on [`SortedListProvider::Score`]. This pallet implements [`OnStakingUpdate`] interface in +//! order to be able to listen to the events that Staking emits and propagate the changes to said +//! lists accordingly. It also exposes [`TrackedList`] that adds defensive checks to a subset of +//! [`SortedListProvider`] methods in order to spot unexpected list updates on the consumer side. +//! This wrapper should beused to pass the tracked entity to the consumer. #![cfg_attr(not(feature = "std"), no_std)] @@ -136,8 +136,8 @@ impl OnStakingUpdate> for Pallet { fn on_unstake(_who: &T::AccountId) {} } -/// A wrapper for a given `SortedListProvider` that disables insert/update/remove operations, -/// effectively rendering it read-only, except for unsafe operations. +/// A wrapper for a given `SortedListProvider` that introduces defensive checks for insert, update +/// and remove operations, effectively suggesting that it's read-only, except for unsafe operations. pub struct TrackedList(sp_std::marker::PhantomData<(T, S, P)>); impl> From c2d8ad435401d89d6db8760b52c8f65f25df4364 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 19 Feb 2023 12:24:56 +0100 Subject: [PATCH 67/88] more doc updates --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index c10cfdffe8fd4..00772864d651b 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -137,7 +137,7 @@ impl OnStakingUpdate> for Pallet { } /// A wrapper for a given `SortedListProvider` that introduces defensive checks for insert, update -/// and remove operations, effectively suggesting that it's read-only, except for unsafe operations. +/// and remove operations, suggesting that it's read-only, except for unsafe operations. pub struct TrackedList(sp_std::marker::PhantomData<(T, S, P)>); impl> From 3f286e15005a2ac297a910a032a0495666cd4245 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 19 Feb 2023 12:35:32 +0100 Subject: [PATCH 68/88] fix typo --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 00772864d651b..5ea5a054be936 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -174,7 +174,7 @@ impl Result<(), Self::Error> { defensive!("TrackedList on_insert should never be called"); - P::on_increase(id, score) + P::on_insert(id, score) } fn on_update(id: &T::AccountId, score: Self::Score) -> Result<(), Self::Error> { From 0246d7bba48c693f4e069ffd64b95df8b5d3e935 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 19 Feb 2023 14:39:05 +0100 Subject: [PATCH 69/88] fix docs --- frame/stake-tracker/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 5ea5a054be936..88547c99ef986 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -17,8 +17,8 @@ //! # Stake Tracker Pallet //! -//! The Stake Tracker pallet is used to maintain a sorted list of voters by listening to the events -//! that Staking emits. +//! The Stake Tracker pallet is used to maintain sorted lists of [`Pallet::AccountId`] by listening +//! to the events that Staking emits. //! //! - [`Config`] //! - [`Pallet`] From d1b783c28c24c2cfb8c75608c52f98b075674023 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sun, 26 Feb 2023 12:07:31 +0100 Subject: [PATCH 70/88] Update frame/stake-tracker/src/lib.rs Co-authored-by: Kian Paimani <5588131+kianenigma@users.noreply.github.com> --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 88547c99ef986..695ab863b8d89 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -30,7 +30,7 @@ //! order to be able to listen to the events that Staking emits and propagate the changes to said //! lists accordingly. It also exposes [`TrackedList`] that adds defensive checks to a subset of //! [`SortedListProvider`] methods in order to spot unexpected list updates on the consumer side. -//! This wrapper should beused to pass the tracked entity to the consumer. +//! This wrapper should be used to pass the tracked entity to the consumer. #![cfg_attr(not(feature = "std"), no_std)] From 9332d75b91815097d986fbe4900ea5348fa00fdd Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 13 Mar 2023 11:24:36 -0300 Subject: [PATCH 71/88] address some of the PR comments --- frame/stake-tracker/src/lib.rs | 107 ++++++++++++++++-------------- frame/staking/src/mock.rs | 7 +- frame/staking/src/pallet/impls.rs | 14 +++- primitives/staking/src/lib.rs | 42 +++--------- 4 files changed, 82 insertions(+), 88 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 695ab863b8d89..699f2ddccb6ba 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -42,13 +42,9 @@ mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ defensive, - traits::{Currency, CurrencyToVote}, + traits::{Currency, CurrencyToVote, Defensive}, }; pub use pallet::*; -use sp_runtime::{ - traits::{Bounded, Zero}, - Saturating, -}; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; use sp_std::{boxed::Box, vec::Vec}; @@ -103,34 +99,49 @@ impl OnStakingUpdate> for Pallet { // if this is a nominator if let Some(_) = T::Staking::nominations(¤t_stake.stash) { let _ = - T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof( + "Unable to update a nominator, perhaps it does not exist?", + ); } + // if this is a validator if T::Staking::is_validator(¤t_stake.stash) { let _ = - T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)); + T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) + .defensive_proof( + "Unable to update a validator, perhaps it does not exist?", + ); } } } + fn on_nominator_add(who: &T::AccountId) { + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))) + .defensive_proof("Unable to insert a nominator, perhaps it already exists?"); + } - fn on_nominator_update(who: &T::AccountId, _prev_nominations: Vec) { - // NOTE: We ignore the result here, because this method can be called when the nominator - // is already in the list, just changing their nominations. - let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))); + fn on_nominator_update(_who: &T::AccountId, _prev_nominations: Vec) { + // Nothing to be done yet. } - fn on_validator_update(who: &T::AccountId) { + fn on_validator_add(who: &T::AccountId) { let self_stake = Self::active_stake_of(who); - // maybe update sorted list. - let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)); + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)) + .defensive_proof("Unable to insert a validator, perhaps it already exists?"); + } + + fn on_validator_update(_who: &T::AccountId) { + // Nothing to be done. } fn on_validator_remove(who: &T::AccountId) { - let _ = T::VoterList::on_remove(who); + let _ = T::VoterList::on_remove(who) + .defensive_proof("Unable to remove a validator, perhaps it does not exist?"); } fn on_nominator_remove(who: &T::AccountId, _nominations: Vec) { - let _ = T::VoterList::on_remove(who); + let _ = T::VoterList::on_remove(who) + .defensive_proof("Unable to remove a nominator, perhaps it does not exist?"); } fn on_unstake(_who: &T::AccountId) {} @@ -138,79 +149,77 @@ impl OnStakingUpdate> for Pallet { /// A wrapper for a given `SortedListProvider` that introduces defensive checks for insert, update /// and remove operations, suggesting that it's read-only, except for unsafe operations. -pub struct TrackedList(sp_std::marker::PhantomData<(T, S, P)>); +pub struct TrackedList(sp_std::marker::PhantomData<(AccountId, Inner)>); -impl> - SortedListProvider for TrackedList +impl> SortedListProvider + for TrackedList { - type Error = P::Error; - type Score = P::Score; - fn iter() -> Box> { - P::iter() + type Error = Inner::Error; + type Score = Inner::Score; + fn iter() -> Box> { + Inner::iter() } - fn iter_from( - start: &T::AccountId, - ) -> Result>, Self::Error> { - P::iter_from(start) + fn iter_from(start: &AccountId) -> Result>, Self::Error> { + Inner::iter_from(start) } fn count() -> u32 { - P::count() + Inner::count() } - fn contains(id: &T::AccountId) -> bool { - P::contains(id) + fn contains(id: &AccountId) -> bool { + Inner::contains(id) } - fn get_score(id: &T::AccountId) -> Result { - P::get_score(id) + fn get_score(id: &AccountId) -> Result { + Inner::get_score(id) } #[cfg(feature = "try-runtime")] fn try_state() -> Result<(), &'static str> { - P::try_state() + Inner::try_state() } - fn on_insert(id: T::AccountId, score: Self::Score) -> Result<(), Self::Error> { + fn on_insert(id: AccountId, score: Self::Score) -> Result<(), Self::Error> { defensive!("TrackedList on_insert should never be called"); - P::on_insert(id, score) + Inner::on_insert(id, score) } - fn on_update(id: &T::AccountId, score: Self::Score) -> Result<(), Self::Error> { + fn on_update(id: &AccountId, score: Self::Score) -> Result<(), Self::Error> { defensive!("TrackedList on_update should never be called"); - P::on_update(id, score) + Inner::on_update(id, score) } - fn on_increase(id: &T::AccountId, additional: Self::Score) -> Result<(), Self::Error> { + fn on_increase(id: &AccountId, additional: Self::Score) -> Result<(), Self::Error> { defensive!("TrackedList on_increase should never be called"); - P::on_increase(id, additional) + Inner::on_increase(id, additional) } - fn on_decrease(id: &T::AccountId, decreased: Self::Score) -> Result<(), Self::Error> { + fn on_decrease(id: &AccountId, decreased: Self::Score) -> Result<(), Self::Error> { defensive!("TrackedList on_decrease should never be called"); - P::on_decrease(id, decreased) + Inner::on_decrease(id, decreased) } - fn on_remove(id: &T::AccountId) -> Result<(), Self::Error> { + fn on_remove(id: &AccountId) -> Result<(), Self::Error> { defensive!("TrackedList on_remove should never be called"); - P::on_remove(id) + Inner::on_remove(id) } fn unsafe_regenerate( - all: impl IntoIterator, - score_of: Box Self::Score>, + all: impl IntoIterator, + score_of: Box Self::Score>, ) -> u32 { - P::unsafe_regenerate(all, score_of) + Inner::unsafe_regenerate(all, score_of) } frame_election_provider_support::runtime_benchmarks_or_test_enabled! { fn unsafe_clear() { - P::unsafe_clear() + Inner::unsafe_clear() } - fn score_update_worst_case(who: &T::AccountId, is_increase: bool) -> Self::Score { - P::score_update_worst_case(who, is_increase) + fn score_update_worst_case(who: &AccountId, is_increase: bool) -> Self::Score { + Inner::score_update_worst_case(who, is_increase) } } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index eb294fd9b11d3..e03e86ad6abb6 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -293,12 +293,16 @@ impl OnStakingUpdate for EventListenerMock { OnStakeUpdate::set(vec); } + fn on_nominator_add(who: &AccountId) {} + fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { let mut vec = OnNominatorUpdate::get(); vec.push((*who, prev_nominations)); OnNominatorUpdate::set(vec); } + fn on_validator_add(who: &AccountId) {} + fn on_validator_update(who: &AccountId) { let mut vec = OnValidatorUpdate::get(); vec.push(*who); @@ -345,8 +349,7 @@ impl crate::pallet::pallet::Config for Test { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainExecution; type GenesisElectionProvider = Self::ElectionProvider; - // NOTE: consider a macro and use `UseNominatorsAndValidatorsMap` as well. - type VoterList = VoterBagsList; + type VoterList = pallet_stake_tracker::TrackedList; type TargetList = UseValidatorsMap; type MaxUnlockingChunks = MaxUnlockingChunks; type HistoryDepth = HistoryDepth; diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 718859cd5a0c7..d34532a0b9ae3 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -885,9 +885,13 @@ impl Pallet { /// to `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - let prev_nominations = Self::nominations(who); + if Nominators::::contains_key(who) { + let prev_nominations = Self::nominations(who); + T::EventListeners::on_nominator_update(who, prev_nominations.unwrap_or_default()); + } else { + T::EventListeners::on_nominator_add(who); + } Nominators::::insert(who, nominations); - T::EventListeners::on_nominator_update(who, prev_nominations.unwrap_or_default()); } /// This function will remove a nominator from the `Nominators` storage map, @@ -914,7 +918,11 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { - T::EventListeners::on_validator_update(who); + if Validators::::contains_key(who) { + T::EventListeners::on_validator_update(who); + } else { + T::EventListeners::on_validator_add(who); + } Validators::::insert(who, prefs); } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 46e4de3be14be..cc7c69fb62727 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -63,7 +63,7 @@ impl OnStakerSlash for () { /// A struct that reflects stake that an account has in the staking system. Provides a set of /// methods to operate on it's properties. Aimed at making `StakingInterface` more concise. #[derive(Default, Clone, Debug, Eq, PartialEq)] -pub struct Stake { +pub struct Stake { /// The stash account whose balance is actually locked and at stake. pub stash: AccountId, /// The total stake that `stash` has in the staking system. This includes the @@ -85,15 +85,20 @@ pub struct Stake { /// Note that the interface is designed in a way that the events are fired post-action, so any /// pre-action data that is needed needs to be passed to interface methods. /// The rest of the data can be retrieved by using `StakingInterface`. +#[impl_trait_for_tuples::impl_for_tuples(10)] pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. /// /// This is effectively any changes to the bond amount, such as bonding more funds, and /// unbonding. fn on_stake_update(who: &AccountId, prev_stake: Option>); - /// Fired when someone sets their intention to nominate, either new, or existing one. + /// Fired when someone sets their intention to nominate. + fn on_nominator_add(who: &AccountId); + /// Fired when an existing nominator updates their nominations. fn on_nominator_update(who: &AccountId, prev_nominations: Vec); - /// Fired when someone sets their intention to validate, either new, or existing one. + /// Fired when someone sets their intention to validate. + fn on_validator_add(who: &AccountId); + /// Fired when an existing validator updates their preferences. fn on_validator_update(who: &AccountId); /// Fired when someone removes their intention to validate, either due to chill or nominating. fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator @@ -103,37 +108,6 @@ pub trait OnStakingUpdate { fn on_unstake(who: &AccountId); // -> basically `kill_stash` } -#[impl_trait_for_tuples::impl_for_tuples(10)] -impl OnStakingUpdate for Tuple -where - AccountId: Clone, - Balance: Copy, -{ - fn on_stake_update(who: &AccountId, prev_stake: Option>) { - for_tuples!( #( Tuple::on_stake_update(who, prev_stake.clone()); )* ); - } - - fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { - for_tuples!( #( Tuple::on_nominator_update(who, prev_nominations.clone()); )* ); - } - - fn on_validator_update(who: &AccountId) { - for_tuples!( #( Tuple::on_validator_update(who); )* ); - } - - fn on_validator_remove(who: &AccountId) { - for_tuples!( #( Tuple::on_validator_remove(who); )* ); - } - - fn on_nominator_remove(who: &AccountId, nominations: Vec) { - for_tuples!( #( Tuple::on_nominator_remove(who, nominations.clone()); )* ); - } - - fn on_unstake(who: &AccountId) { - for_tuples!( #( Tuple::on_unstake(who); )* ); - } -} - /// A generic representation of a staking implementation. /// /// This interface uses the terminology of NPoS, but it is aims to be generic enough to cover other From f1436536cc7bae8fddabaae72d0d815a4b62427e Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 13 Mar 2023 12:16:38 -0300 Subject: [PATCH 72/88] defensive unstake --- frame/stake-tracker/src/lib.rs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 699f2ddccb6ba..fca59b4c33958 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -42,7 +42,7 @@ mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ defensive, - traits::{Currency, CurrencyToVote, Defensive}, + traits::{Currency, CurrencyToVote, Defensive, DefensiveResult}, }; pub use pallet::*; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; @@ -144,7 +144,11 @@ impl OnStakingUpdate> for Pallet { .defensive_proof("Unable to remove a nominator, perhaps it does not exist?"); } - fn on_unstake(_who: &T::AccountId) {} + fn on_unstake(_who: &T::AccountId) { + if T::VoterList::contains(who) { + defensive!("The staker should have already been removed!") + } + } } /// A wrapper for a given `SortedListProvider` that introduces defensive checks for insert, update From 605816d02a22302657f77754fdf7797f6f48c7b8 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Mon, 13 Mar 2023 17:50:06 -0300 Subject: [PATCH 73/88] re-arrange testing events --- .../nomination-pools/benchmarking/src/mock.rs | 2 +- frame/stake-tracker/src/lib.rs | 6 +- frame/staking/src/mock.rs | 93 +++--- frame/staking/src/tests.rs | 315 +++--------------- 4 files changed, 107 insertions(+), 309 deletions(-) diff --git a/frame/nomination-pools/benchmarking/src/mock.rs b/frame/nomination-pools/benchmarking/src/mock.rs index 5ac3d9ab1229b..2eb405754d1c4 100644 --- a/frame/nomination-pools/benchmarking/src/mock.rs +++ b/frame/nomination-pools/benchmarking/src/mock.rs @@ -112,7 +112,7 @@ impl pallet_staking::Config for Runtime { type ElectionProvider = frame_election_provider_support::NoElection<(AccountId, BlockNumber, Staking, ())>; type GenesisElectionProvider = Self::ElectionProvider; - type VoterList = ::VoterList; + type VoterList = VoterList; type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; type HistoryDepth = ConstU32<84>; diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index fca59b4c33958..212347dfc27ac 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -42,7 +42,7 @@ mod tests; use frame_election_provider_support::{SortedListProvider, VoteWeight}; use frame_support::{ defensive, - traits::{Currency, CurrencyToVote, Defensive, DefensiveResult}, + traits::{Currency, CurrencyToVote, Defensive}, }; pub use pallet::*; use sp_staking::{OnStakingUpdate, Stake, StakingInterface}; @@ -144,9 +144,9 @@ impl OnStakingUpdate> for Pallet { .defensive_proof("Unable to remove a nominator, perhaps it does not exist?"); } - fn on_unstake(_who: &T::AccountId) { + fn on_unstake(who: &T::AccountId) { if T::VoterList::contains(who) { - defensive!("The staker should have already been removed!") + defensive!("The staker should have already been removed!"); } } } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index e03e86ad6abb6..fceb3e3c57a92 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -17,7 +17,14 @@ //! Test utilities -use crate::{self as pallet_staking, *}; +use crate::{ + self as pallet_staking, + mock::StakingEvent::{ + NominatorAdd, NominatorRemove, NominatorUpdate, StakeUpdate, Unstake, ValidatorAdd, + ValidatorRemove, ValidatorUpdate, + }, + *, +}; use frame_election_provider_support::{onchain, SequentialPhragmen, VoteWeight}; use frame_support::{ assert_ok, ord_parameter_types, parameter_types, @@ -237,12 +244,6 @@ parameter_types! { pub static MaxUnlockingChunks: u32 = 32; pub static RewardOnUnbalanceWasCalled: bool = false; pub static LedgerSlashPerEra: (BalanceOf, BTreeMap>) = (Zero::zero(), BTreeMap::new()); - pub static OnStakeUpdate: Vec<(AccountId, Option>)> = Vec::new(); - pub static OnNominatorUpdate: Vec<(AccountId, Vec)> = Vec::new(); - pub static OnValidatorUpdate: Vec = Vec::new(); - pub static OnValidatorRemove: Vec = Vec::new(); - pub static OnNominatorRemove: Vec<(AccountId, Vec)> = Vec::new(); - pub static OnUnstake: Vec = Vec::new(); pub static MaxWinners: u32 = 100; } @@ -285,46 +286,70 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM } } +#[derive(Clone)] +pub enum StakingEvent { + StakeUpdate(AccountId, Option>), + NominatorAdd(AccountId), + NominatorUpdate(AccountId, Vec), + ValidatorAdd(AccountId), + ValidatorUpdate(AccountId), + ValidatorRemove(AccountId), + NominatorRemove(AccountId, Vec), + Unstake(AccountId), +} + +parameter_types! { + pub static EmittedEvents: Vec = Vec::new(); +} + pub struct EventListenerMock; impl OnStakingUpdate for EventListenerMock { fn on_stake_update(who: &AccountId, prev_stake: Option>) { - let mut vec = OnStakeUpdate::get(); - vec.push((*who, prev_stake)); - OnStakeUpdate::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(StakeUpdate(*who, prev_stake)); + EmittedEvents::set(vec); } - fn on_nominator_add(who: &AccountId) {} + fn on_nominator_add(who: &AccountId) { + let mut vec = EmittedEvents::get(); + vec.push(NominatorAdd(*who)); + EmittedEvents::set(vec); + } fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { - let mut vec = OnNominatorUpdate::get(); - vec.push((*who, prev_nominations)); - OnNominatorUpdate::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(NominatorUpdate(*who, prev_nominations)); + EmittedEvents::set(vec); } - fn on_validator_add(who: &AccountId) {} + fn on_validator_add(who: &AccountId) { + let mut vec = EmittedEvents::get(); + vec.push(ValidatorAdd(*who)); + EmittedEvents::set(vec); + } fn on_validator_update(who: &AccountId) { - let mut vec = OnValidatorUpdate::get(); - vec.push(*who); - OnValidatorUpdate::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(ValidatorUpdate(*who)); + EmittedEvents::set(vec); } fn on_validator_remove(who: &AccountId) { - let mut vec = OnValidatorRemove::get(); - vec.push(*who); - OnValidatorRemove::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(ValidatorRemove(*who)); + EmittedEvents::set(vec); } fn on_nominator_remove(who: &AccountId, nominations: Vec) { - let mut vec = OnNominatorRemove::get(); - vec.push((*who, nominations)); - OnNominatorRemove::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(NominatorRemove(*who, nominations)); + EmittedEvents::set(vec); } fn on_unstake(who: &AccountId) { - let mut vec = OnUnstake::get(); - vec.push(*who); - OnUnstake::set(vec); + let mut vec = EmittedEvents::get(); + vec.push(Unstake(*who)); + EmittedEvents::set(vec); } } @@ -612,23 +637,13 @@ impl ExtBuilder { let mut ext = self.build(); ext.execute_with(|| { // Clean up all the events produced on init. - OnStakeUpdate::take(); - OnNominatorUpdate::take(); - OnValidatorUpdate::take(); - OnValidatorRemove::take(); - OnNominatorRemove::take(); - OnUnstake::take(); + EmittedEvents::take(); }); ext.execute_with(test); if check_events { ext.execute_with(|| { // Make sure we have checked all the events produced by the test. - assert!(OnStakeUpdate::get().is_empty(), "Unexpected OnStakeUpdate events"); - assert!(OnNominatorUpdate::get().is_empty(), "Unexpected OnNominatorUpdate events"); - assert!(OnValidatorUpdate::get().is_empty(), "Unexpected OnValidatorUpdate events"); - assert!(OnValidatorRemove::get().is_empty(), "Unexpected OnValidatorRemove events"); - assert!(OnNominatorRemove::get().is_empty(), "Unexpected OnNominatorRemove events"); - assert!(OnUnstake::get().is_empty(), "Unexpected OnUnstake events"); + assert!(EmittedEvents::get().is_empty(), "Encountered unchecked Staking events"); }); } ext.execute_with(|| { diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 6d891ff9f54d4..fa88ec5ebfc47 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -230,7 +230,7 @@ fn basic_setup_works() { #[test] fn change_controller_works() { - ExtBuilder::default().check_events(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { // 10 and 11 are bonded as stash controller. assert_eq!(Staking::bonded(&11), Some(10)); @@ -248,9 +248,6 @@ fn change_controller_works() { Error::::NotController, ); assert_ok!(Staking::validate(RuntimeOrigin::signed(5), ValidatorPrefs::default())); - - assert_eq!(OnValidatorUpdate::take(), vec![11]); - assert_eq!(OnValidatorRemove::take(), vec![11]); }) } @@ -456,7 +453,6 @@ fn staking_should_work() { #[test] fn blocking_and_kicking_works() { ExtBuilder::default() - .check_events(true) .minimum_validator_count(1) .validator_count(4) .nominate(true) @@ -479,7 +475,6 @@ fn blocking_and_kicking_works() { Staking::nominate(RuntimeOrigin::signed(100), vec![11]), Error::::BadTarget ); - assert_eq!(OnNominatorUpdate::take(), vec![(101, vec![11, 21])]); }); } @@ -1164,7 +1159,7 @@ fn bond_extra_works() { // Tests that extra `free_balance` in the stash can be added to stake // NOTE: this tests only verifies `StakingLedger` for correct updates // See `bond_extra_and_withdraw_unbonded_works` for more details and updates on `Exposure`. - ExtBuilder::default().check_events(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { // Check that account 10 is a validator assert!(>::contains_key(11)); // Check that account 10 is bonded to account 11 @@ -1211,14 +1206,6 @@ fn bond_extra_works() { claimed_rewards: bounded_vec![], }) ); - - assert_eq!( - OnStakeUpdate::take(), - vec![ - (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), - (11, Some(Stake { stash: 11, total: 1100, active: 1100 })) - ] - ) }); } @@ -1230,7 +1217,7 @@ fn bond_extra_and_withdraw_unbonded_works() { // * It can add extra funds to the bonded account. // * it can unbond a portion of its funds from the stash account. // * Once the unbonding period is done, it can actually take the funds out of the stash. - ExtBuilder::default().check_events(true).nominate(false).build_and_execute(|| { + ExtBuilder::default().nominate(false).build_and_execute(|| { // Set payee to controller. avoids confusion assert_ok!(Staking::set_payee(RuntimeOrigin::signed(10), RewardDestination::Controller)); @@ -1359,16 +1346,6 @@ fn bond_extra_and_withdraw_unbonded_works() { claimed_rewards: bounded_vec![], }), ); - assert_eq!( - OnStakeUpdate::take(), - vec![ - (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), - (11, Some(Stake { stash: 11, total: 1100, active: 1100 })), - (11, Some(Stake { stash: 11, total: 1100, active: 100 })), - (11, Some(Stake { stash: 11, total: 1100, active: 100 })), - (11, Some(Stake { stash: 11, total: 1100, active: 100 })) - ] - ); }) } @@ -1417,7 +1394,7 @@ fn many_unbond_calls_should_work() { #[test] fn auto_withdraw_may_not_unlock_all_chunks() { - ExtBuilder::default().check_events(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { // set `MaxUnlockingChunks` to a low number to test case when the unbonding period // is larger than the number of unlocking chunks available, which may result on a // `Error::NoMoreChunks`, even when the auto-withdraw tries to release locked chunks. @@ -1441,16 +1418,6 @@ fn auto_withdraw_may_not_unlock_all_chunks() { current_era += 10; mock::start_active_era(current_era); assert_ok!(Staking::unbond(RuntimeOrigin::signed(10), 1)); - - assert_eq!( - OnStakeUpdate::take(), - vec![ - (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), - (11, Some(Stake { stash: 11, total: 1000, active: 999 })), - (11, Some(Stake { stash: 11, total: 1000, active: 999 })), - (11, Some(Stake { stash: 11, total: 999, active: 999 })) - ] - ); }) } @@ -1741,7 +1708,6 @@ fn rebond_emits_right_value_in_event() { #[test] fn reward_to_stake_works() { ExtBuilder::default() - .check_events(true) .nominate(false) .set_status(31, StakerStatus::Idle) .set_status(41, StakerStatus::Idle) @@ -1792,14 +1758,6 @@ fn reward_to_stake_works() { // -- new infos assert_eq!(Staking::eras_stakers(active_era(), 11).total, 1000 + total_payout_0 / 2); assert_eq!(Staking::eras_stakers(active_era(), 21).total, 69 + total_payout_0 / 2); - - assert_eq!( - OnStakeUpdate::take(), - [ - (11, Some(Stake { stash: 11, total: 1000, active: 1000 })), - (21, Some(Stake { stash: 21, total: 69, active: 69 })) - ] - ); }); } @@ -1950,7 +1908,6 @@ fn bond_with_no_staked_value() { .balance_factor(5) .nominate(false) .minimum_validator_count(1) - .check_events(true) .build_and_execute(|| { // Can't bond with 1 assert_noop!( @@ -1993,15 +1950,6 @@ fn bond_with_no_staked_value() { assert_ok!(Staking::withdraw_unbonded(RuntimeOrigin::signed(2), 0)); assert!(Staking::ledger(2).is_none()); assert_eq!(Balances::locks(&1).len(), 0); - assert_eq!( - OnStakeUpdate::take(), - vec![ - (1, None), - (1, Some(Stake { stash: 1, total: 5, active: 5 })), - (1, Some(Stake { stash: 1, total: 5, active: 0 })) - ] - ); - assert_eq!(OnUnstake::take(), vec![1]); }); } @@ -2011,7 +1959,6 @@ fn bond_with_little_staked_value_bounded() { .validator_count(3) .nominate(false) .minimum_validator_count(1) - .check_events(true) .build_and_execute(|| { // setup assert_ok!(Staking::chill(RuntimeOrigin::signed(30))); @@ -2077,18 +2024,6 @@ fn bond_with_little_staked_value_bounded() { init_balance_10 + total_payout_0 / 3 + total_payout_1 / 3, 2, ); - - assert_eq!( - OnStakeUpdate::take(), - vec![ - (1, None), - (21, Some(Stake { stash: 21, total: 1000, active: 1000 })), - (31, Some(Stake { stash: 31, total: 500, active: 500 })), - (21, Some(Stake { stash: 21, total: 4692, active: 4692 })) - ] - ); - assert_eq!(OnValidatorUpdate::take(), vec![1]); - assert_eq!(OnValidatorRemove::take(), vec![31]); }); } @@ -2099,7 +2034,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { .nominate(false) .minimum_validator_count(1) .set_stake(31, 1000) - .check_events(true) .build_and_execute(|| { // ensure all have equal stake. assert_eq!( @@ -2143,8 +2077,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider() { (31, Support { total: 2200, voters: vec![(31, 1000), (1, 600), (3, 600)] }) ], ); - assert_eq!(OnStakeUpdate::take(), vec![(1, None), (3, None)]); - assert_eq!(OnNominatorUpdate::take(), vec![(1, vec![]), (3, vec![])]); }); } @@ -2156,7 +2088,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { .nominate(false) .set_stake(31, 1000) .minimum_validator_count(1) - .check_events(true) .build_and_execute(|| { // ensure all have equal stake. assert_eq!( @@ -2200,8 +2131,6 @@ fn bond_with_duplicate_vote_should_be_ignored_by_election_provider_elected() { (21, Support { total: 2500, voters: vec![(21, 1000), (1, 500), (3, 1000)] }) ], ); - assert_eq!(OnStakeUpdate::take(), vec![(1, None), (3, None)]); - assert_eq!(OnNominatorUpdate::take(), vec![(1, vec![]), (3, vec![])]); }); } @@ -4131,57 +4060,51 @@ fn payout_stakers_handles_weight_refund() { #[test] fn bond_during_era_correctly_populates_claimed_rewards() { - ExtBuilder::default() - .has_stakers(false) - .check_events(true) - .build_and_execute(|| { - // Era = None - bond_validator(9, 8, 1000); - assert_eq!( - Staking::ledger(&8), - Some(StakingLedger { - stash: 9, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: bounded_vec![], - }) - ); - mock::start_active_era(5); - bond_validator(11, 10, 1000); - assert_eq!( - Staking::ledger(&10), - Some(StakingLedger { - stash: 11, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (0..5).collect::>().try_into().unwrap(), - }) - ); - - // make sure only era upto history depth is stored - let current_era = 99; - let last_reward_era = 99 - HistoryDepth::get(); - mock::start_active_era(current_era); - bond_validator(13, 12, 1000); - assert_eq!( - Staking::ledger(&12), - Some(StakingLedger { - stash: 13, - total: 1000, - active: 1000, - unlocking: Default::default(), - claimed_rewards: (last_reward_era..current_era) - .collect::>() - .try_into() - .unwrap(), - }) - ); + ExtBuilder::default().has_stakers(false).build_and_execute(|| { + // Era = None + bond_validator(9, 8, 1000); + assert_eq!( + Staking::ledger(&8), + Some(StakingLedger { + stash: 9, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: bounded_vec![], + }) + ); + mock::start_active_era(5); + bond_validator(11, 10, 1000); + assert_eq!( + Staking::ledger(&10), + Some(StakingLedger { + stash: 11, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: (0..5).collect::>().try_into().unwrap(), + }) + ); - assert_eq!(OnStakeUpdate::take(), vec![(9, None), (11, None), (13, None)]); - assert_eq!(OnValidatorUpdate::take(), vec![9, 11, 13]); - }); + // make sure only era upto history depth is stored + let current_era = 99; + let last_reward_era = 99 - HistoryDepth::get(); + mock::start_active_era(current_era); + bond_validator(13, 12, 1000); + assert_eq!( + Staking::ledger(&12), + Some(StakingLedger { + stash: 13, + total: 1000, + active: 1000, + unlocking: Default::default(), + claimed_rewards: (last_reward_era..current_era) + .collect::>() + .try_into() + .unwrap(), + }) + ); + }); } #[test] @@ -4414,7 +4337,6 @@ fn cannot_rebond_to_lower_than_ed() { ExtBuilder::default() .existential_deposit(10) .balance_factor(10) - .check_events(true) .build_and_execute(|| { // initial stuff. assert_eq!( @@ -4447,11 +4369,6 @@ fn cannot_rebond_to_lower_than_ed() { Staking::rebond(RuntimeOrigin::signed(20), 5), Error::::InsufficientBond ); - assert_eq!( - OnStakeUpdate::take(), - vec![(21, Some(Stake { stash: 21, total: 10000, active: 10000 }))] - ); - assert_eq!(OnValidatorRemove::take(), [21]); }) } @@ -4460,7 +4377,6 @@ fn cannot_bond_extra_to_lower_than_ed() { ExtBuilder::default() .existential_deposit(10) .balance_factor(10) - .check_events(true) .build_and_execute(|| { // initial stuff. assert_eq!( @@ -4493,11 +4409,6 @@ fn cannot_bond_extra_to_lower_than_ed() { Staking::bond_extra(RuntimeOrigin::signed(21), 5), Error::::InsufficientBond, ); - assert_eq!( - OnStakeUpdate::take(), - vec![(21, Some(Stake { stash: 21, total: 10000, active: 10000 }))] - ); - assert_eq!(OnValidatorRemove::take(), vec![21]); }) } @@ -4851,7 +4762,6 @@ fn chill_other_works() { .balance_factor(100) .min_nominator_bond(1_000) .min_validator_bond(1_500) - .check_events(true) .build_and_execute(|| { let initial_validators = Validators::::count(); let initial_nominators = Nominators::::count(); @@ -4998,87 +4908,12 @@ fn chill_other_works() { // chill a validator. Limit is reached, chill-able. assert_eq!(Validators::::count(), 9); assert_ok!(Staking::chill_other(RuntimeOrigin::signed(1337), 3)); - - assert_eq!( - OnStakeUpdate::take(), - vec![ - (0, None), - (2, None), - (4, None), - (6, None), - (8, None), - (10, None), - (12, None), - (14, None), - (16, None), - (18, None), - (20, None), - (22, None), - (24, None), - (26, None), - (28, None), - (30, None), - (32, None), - (34, None), - (36, None), - (38, None), - (40, None), - (42, None), - (44, None), - (46, None), - (48, None), - (50, None), - (52, None), - (54, None), - (56, None), - (58, None), - ], - ); - assert_eq!( - OnNominatorUpdate::take(), - vec![ - (0, vec![]), - (4, vec![]), - (8, vec![]), - (12, vec![]), - (16, vec![]), - (20, vec![]), - (24, vec![]), - (28, vec![]), - (32, vec![]), - (36, vec![]), - (40, vec![]), - (44, vec![]), - (48, vec![]), - (52, vec![]), - (56, vec![]) - ] - ); - assert_eq!( - OnValidatorUpdate::take(), - vec![2, 6, 10, 14, 18, 22, 26, 30, 34, 38, 42, 46, 50, 54, 58] - ); - assert_eq!(OnValidatorRemove::take(), vec![26, 30, 34, 38, 42, 46, 50, 54, 58, 2]); - assert_eq!( - OnNominatorRemove::take(), - vec![ - (24, vec![1]), - (28, vec![1]), - (32, vec![1]), - (36, vec![1]), - (40, vec![1]), - (44, vec![1]), - (48, vec![1]), - (52, vec![1]), - (56, vec![1]), - ], - ); }) } #[test] fn capped_stakers_works() { - ExtBuilder::default().check_events(true).build_and_execute(|| { + ExtBuilder::default().build_and_execute(|| { let validator_count = Validators::::count(); assert_eq!(validator_count, 3); let nominator_count = Nominators::::count(); @@ -5173,58 +5008,6 @@ fn capped_stakers_works() { RuntimeOrigin::signed(last_validator), ValidatorPrefs::default() )); - assert_eq!( - OnStakeUpdate::take(), - vec![ - (12372621823874611106, None), - (17270956243860093491, None), - (5465328850787429409, None), - (17161747032660228993, None), - (15952342661998645977, None), - (3727006293154618313, None), - (1400879179532508248, None), - (1021376644067438394, None), - (4362915210409513192, None), - (777994016134154318, None), - (15963114285730158603, None), - (12891137879492301340, None), - (4753808362819695445, None), - (17018405332266509855, None), - (7651320429057838151, None), - (8398748157494047725, None), - (1473894167078292758, None), - (15979092350402807284, None) - ] - ); - assert_eq!( - OnNominatorUpdate::take(), - vec![ - (4362915210409513192, vec![]), - (777994016134154318, vec![]), - (15963114285730158603, vec![]), - (12891137879492301340, vec![]), - (4753808362819695445, vec![]), - (17018405332266509855, vec![]), - (7651320429057838151, vec![]), - (8398748157494047725, vec![]), - (1473894167078292758, vec![]), - (1473894167078292758, vec![1]), - (15979092350402807284, vec![]) - ] - ); - assert_eq!( - OnValidatorUpdate::take(), - vec![ - 12372621823874611106, - 17270956243860093491, - 5465328850787429409, - 17161747032660228993, - 15952342661998645977, - 3727006293154618313, - 1400879179532508248, - 1021376644067438394 - ] - ); }) } From a7250ddf88ab53c90fe9eda72b484066f1441c25 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 09:50:01 -0300 Subject: [PATCH 74/88] fixup stake-tracker tests --- frame/stake-tracker/src/lib.rs | 3 +- frame/stake-tracker/src/mock.rs | 20 +++-- frame/stake-tracker/src/tests.rs | 148 ++++++++++++++----------------- 3 files changed, 79 insertions(+), 92 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 212347dfc27ac..e1e63da3678af 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -125,8 +125,7 @@ impl OnStakingUpdate> for Pallet { } fn on_validator_add(who: &T::AccountId) { - let self_stake = Self::active_stake_of(who); - let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(self_stake)) + let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))) .defensive_proof("Unable to insert a validator, perhaps it already exists?"); } diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 6abec8bd023c4..e2182f3b1fd88 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -129,6 +129,17 @@ impl ScoreProvider for StakingMock { } } +parameter_types! { + pub static Nominators: Vec = vec![20, 21, 22, 23, 24]; + pub static Validators: Vec = vec![10, 11, 12, 13, 14]; +} + +pub(crate) fn stakers() -> Vec { + let mut stakers = Nominators::get(); + stakers.append(&mut Validators::get()); + stakers +} + impl StakingInterface for StakingMock { type Balance = Balance; type AccountId = AccountId; @@ -157,7 +168,7 @@ impl StakingInterface for StakingMock { fn stake( who: &Self::AccountId, ) -> Result, DispatchError> { - if *who >= 30 { + if !Nominators::get().contains(who) && !Validators::get().contains(who) { return Err(DispatchError::Other("not bonded")) } let stake = Balances::total_balance(who); @@ -213,7 +224,7 @@ impl StakingInterface for StakingMock { } fn nominations(who: &Self::AccountId) -> Option> { - if *who >= 20 && *who <= 24 { + if Nominators::get().contains(who) { Some(Vec::new()) } else { None @@ -247,11 +258,6 @@ impl ExtBuilder { let _ = pallet_balances::GenesisConfig:: { balances: vec![ - // Random users, used to test some edge-cases, where we don't want the user to be - // neither a nominator nor validator. - (1, 10), - (2, 20), - (3, 30), // Validator stashes, for simplicity we assume stash == controller as StakeTracker // really does not care. (10, 10), diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 4a9bd2ffe8c4b..9fa72f5a0d22c 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -18,26 +18,17 @@ use super::{mock::*, pallet::*}; use crate as pallet_stake_tracker; use frame_election_provider_support::SortedListProvider; -use frame_support::{assert_ok, assert_storage_noop}; +use frame_support::assert_storage_noop; use sp_staking::{OnStakingUpdate, StakingInterface}; type VoterList = ::VoterList; type Staking = ::Staking; +// It is the caller's problem to make sure each of events is emitted in the right context, therefore +// we test each event for all the stakers (validators + nominators). + mod on_stake_update { use super::*; - #[test] - fn noop_when_not_in_list() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(VoterList::count(), 0); - // usual user - assert_storage_noop!(StakeTracker::on_stake_update(&1, None)); - // validator - assert_storage_noop!(StakeTracker::on_stake_update(&10, None)); - // nominator - assert_storage_noop!(StakeTracker::on_stake_update(&20, None)); - }); - } #[test] fn does_nothing_when_not_bonded() { @@ -48,22 +39,13 @@ mod on_stake_update { }); } - #[test] - fn noop_when_not_validator_or_nominator() { - ExtBuilder::default().build_and_execute(|| { - VoterList::on_insert(1, 10000).unwrap(); - // usual user - assert_storage_noop!(StakeTracker::on_stake_update(&1, None)); - }); - } - #[test] fn works_for_validators_and_nominators() { ExtBuilder::default().build_and_execute(|| { let score = 1000; assert_eq!(VoterList::count(), 0); // validator, nominator - for (idx, id) in [10, 20].iter().enumerate() { + for (idx, id) in stakers().iter().enumerate() { let _ = VoterList::on_insert(*id, score).unwrap(); assert_eq!(VoterList::count() as usize, idx + 1); assert_eq!(VoterList::get_score(id).unwrap(), score); @@ -77,37 +59,70 @@ mod on_stake_update { } } +mod on_nominator_add { + use super::*; + + #[test] + fn works() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + + // nominators + validators + for id in stakers() { + StakeTracker::on_nominator_add(&id); + assert_eq!( + VoterList::get_score(&id).unwrap(), + StakeTracker::to_vote(StakeTracker::active_stake_of(&id)) + ); + } + + assert_eq!(VoterList::count(), stakers().len() as u32); + }); + } +} + mod on_nominator_update { use super::*; + #[test] - fn noop_when_in_the_list() { + fn always_noop() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator - for id in [1, 10, 20] { + // not in list + // validator, nominator + for id in stakers() { + assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); + } + + // in list + // validator, nominator + for id in stakers() { let _ = VoterList::on_insert(id, 1000); assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); } }); } +} + +mod on_validator_add { + use super::*; #[test] - // It is the caller's problem to make sure `on_nominator_update` is called in the right context. - fn works_for_everyone() { + fn works() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator, not bonded - for id in [1, 10, 20, 30] { - StakeTracker::on_nominator_update(&id, Vec::new()); + // nominators + validators + for id in stakers() { + StakeTracker::on_validator_add(&id); assert_eq!( VoterList::get_score(&id).unwrap(), - Pallet::::to_vote( - Staking::stake(&id).map(|s| s.active).unwrap_or_default() - ) + StakeTracker::to_vote(StakeTracker::active_stake_of(&id)) ); } + + assert_eq!(VoterList::count(), stakers().len() as u32); }); } } @@ -120,7 +135,7 @@ mod on_validator_update { assert_eq!(VoterList::count(), 0); // usual user, validator, nominator - for id in [1, 10, 20] { + for id in stakers() { let _ = VoterList::on_insert(id, 1000); assert_storage_noop!(StakeTracker::on_validator_update(&id)); } @@ -128,20 +143,13 @@ mod on_validator_update { } #[test] - // It is the caller's problem to make sure `on_validator_update` is called in the right context. - fn works_for_everyone() { + fn noop() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator, no stake - for id in [1, 10, 20, 30] { - StakeTracker::on_validator_update(&id); - assert_eq!( - VoterList::get_score(&id).unwrap(), - Pallet::::to_vote( - Staking::stake(&id).map(|s| s.active).unwrap_or_default() - ) - ); + // validators + nominators + for id in stakers() { + assert_storage_noop!(StakeTracker::on_validator_update(&id)); } }); } @@ -149,26 +157,15 @@ mod on_validator_update { mod on_validator_remove { use super::*; - #[test] - fn noop_when_not_in_the_list() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(VoterList::count(), 0); - - // usual user, validator, nominator, not bonded - for id in [1, 10, 20, 30] { - assert_storage_noop!(StakeTracker::on_validator_remove(&id)); - } - }); - } #[test] // It is the caller's problem to make sure `on_validator_remove` is called in the right context. - fn works_for_everyone_also_unbonded() { + fn works_for_everyone() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator - for id in [1, 10, 20, 30] { + // nominators + validators + for id in stakers() { let _ = VoterList::on_insert(id, 100); assert_eq!(VoterList::count(), 1); StakeTracker::on_validator_remove(&id); @@ -180,26 +177,15 @@ mod on_validator_remove { mod on_nominator_remove { use super::*; - #[test] - fn noop_when_not_in_the_list() { - ExtBuilder::default().build_and_execute(|| { - assert_eq!(VoterList::count(), 0); - - // usual user, validator, nominator, not bonded - for id in [1, 10, 20, 30] { - assert_storage_noop!(StakeTracker::on_nominator_remove(&id, Vec::new())); - } - }); - } #[test] // It is the caller's problem to make sure `on_nominator_remove` is called in the right context. - fn works_for_everyone_also_unbonded() { + fn works_for_everyone() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator, bonded - for id in [1, 10, 20, 30] { + // nominators + validators + for id in stakers() { let _ = VoterList::on_insert(id, 100); assert_eq!(VoterList::count(), 1); StakeTracker::on_nominator_remove(&id, Vec::new()); @@ -213,18 +199,14 @@ mod on_unstake { use super::*; #[test] - fn noop() { + // By the time this is called - staker has to already be removed from the list. Otherwise we hit + // the defensive path. + fn noop_when_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator, not bonded - for id in [1, 10, 20, 30] { - assert_storage_noop!(StakeTracker::on_unstake(&id)); - } - - // usual user, validator, nominator, not bonded - for id in [1, 10, 20, 30] { - assert_ok!(VoterList::on_insert(id, 100)); + // any staker + for id in stakers() { assert_storage_noop!(StakeTracker::on_unstake(&id)); } }); From 0cb32220ea2fb340fd7a4563ce67fa770e89f703 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 16:31:32 -0300 Subject: [PATCH 75/88] reinstate migration changes --- frame/staking/src/migrations.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/staking/src/migrations.rs b/frame/staking/src/migrations.rs index a55da2d9e52f9..5c638b7504f35 100644 --- a/frame/staking/src/migrations.rs +++ b/frame/staking/src/migrations.rs @@ -266,7 +266,7 @@ pub mod v10 { pub struct MigrateToV10(sp_std::marker::PhantomData); impl OnRuntimeUpgrade for MigrateToV10 { fn on_runtime_upgrade() -> frame_support::weights::Weight { - if StorageVersion::::get() == ObsoleteReleases::V9_0_0 { + if obsolete::StorageVersion::::get() == obsolete::Releases::V9_0_0 { let pending_slashes = UnappliedSlashes::::iter().take(512); for (era, slashes) in pending_slashes { for slash in slashes { From d79ac49cab234ea484d37778f3fe27ddba2bf92c Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 16:31:57 -0300 Subject: [PATCH 76/88] basic OnStakingUpdate staking tests --- frame/staking/src/mock.rs | 2 +- frame/staking/src/tests.rs | 97 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index fceb3e3c57a92..524b0a9cbab0a 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -286,7 +286,7 @@ impl sp_staking::OnStakerSlash for OnStakerSlashM } } -#[derive(Clone)] +#[derive(Clone, Debug, Eq, PartialEq)] pub enum StakingEvent { StakeUpdate(AccountId, Option>), NominatorAdd(AccountId), diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index 7407fdcb7bb29..497e8bd5bcfd2 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5823,3 +5823,100 @@ mod staking_interface { }); } } + +mod on_staking_update { + use super::*; + use crate::mock::StakingEvent::*; + + #[test] + fn on_validator_add() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 1500, + RewardDestination::Controller + )); + assert_ok!(Staking::validate(RuntimeOrigin::signed(4), ValidatorPrefs::default())); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(3, None), ValidatorAdd(3)]); + }); + } + + #[test] + fn on_validator_update() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::validate(RuntimeOrigin::signed(10), ValidatorPrefs::default())); + assert_eq!(EmittedEvents::take(), vec![ValidatorUpdate(11)]); + }); + } + + #[test] + fn on_stake_update() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(3), + 4, + 100, + RewardDestination::Controller + )); + + assert_ok!(Staking::bond_extra(RuntimeOrigin::signed(3), 500)); + assert_eq!( + EmittedEvents::take(), + vec![ + StakeUpdate(3, None), + StakeUpdate(3, Some(Stake { stash: 3, total: 100, active: 100 })) + ] + ); + }); + } + + #[test] + fn on_nominator_update() { + ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { + assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11])); + assert_eq!(EmittedEvents::take(), vec![NominatorUpdate(101, vec![11, 21])]); + }); + } + + #[test] + fn on_nominator_add() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 1000, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11, 21, 31])); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(1, None), NominatorAdd(1)]); + }); + } + + #[test] + fn on_nominator_remove() { + ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 101, 0)); + assert_eq!( + EmittedEvents::take(), + vec![NominatorRemove(101, vec![11, 21]), Unstake(101)] + ); + }); + } + + #[test] + fn on_validator_remove() { + ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 11, 0)); + assert_eq!(EmittedEvents::take(), vec![ValidatorRemove(11), Unstake(11)]); + }); + } + + #[test] + fn on_unstake() { + ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { + assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 11, 0)); + assert_eq!(EmittedEvents::take(), vec![ValidatorRemove(11), Unstake(11)]); + }); + } +} From 9eb705a98c3a9ede8a0e333d9479c4cc40f4f935 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 16:46:48 -0300 Subject: [PATCH 77/88] more fixes --- bin/node/runtime/src/lib.rs | 6 +----- frame/stake-tracker/src/lib.rs | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index e075df5a2dbd1..f6dfb0601aa9f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -572,11 +572,7 @@ impl pallet_staking::Config for Runtime { type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainExecution; - type VoterList = pallet_stake_tracker::TrackedList< - Self, - VoteWeight, - ::VoterList, - >; + type VoterList = pallet_stake_tracker::TrackedList; // This a placeholder, to be introduced in the next PR as an instance of bags-list type TargetList = pallet_staking::UseValidatorsMap; type MaxUnlockingChunks = ConstU32<32>; diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index e1e63da3678af..387c32717345f 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -70,9 +70,10 @@ pub mod pallet { #[pallet::config] pub trait Config: frame_system::Config { - /// This has to come from Staking::Currency + /// The same currency type that's used by Staking. type Currency: Currency>; + /// An interface to Staking. type Staking: StakingInterface; /// A sorted list of nominators and validators, by their stake and self-stake respectively. From 6a305f80f8adf6fb2218a531bde708bdd6310675 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 16:50:05 -0300 Subject: [PATCH 78/88] more fixes --- primitives/staking/src/lib.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index cc7c69fb62727..0544bbde0c5d0 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -81,12 +81,11 @@ pub struct Stake { pub active: Balance, } -/// A generic staking event listener. -/// Note that the interface is designed in a way that the events are fired post-action, so any -/// pre-action data that is needed needs to be passed to interface methods. -/// The rest of the data can be retrieved by using `StakingInterface`. +/// A generic staking event listener. Note that the interface is designed in a way that the events +/// are fired post-action, so any pre-action data that is needed needs to be passed to interface +/// methods. The rest of the data can be retrieved by using `StakingInterface`. #[impl_trait_for_tuples::impl_for_tuples(10)] -pub trait OnStakingUpdate { +pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. /// /// This is effectively any changes to the bond amount, such as bonding more funds, and From 20f02e9cfc2f569615bd0c4a33141489e2c7badc Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 16:58:57 -0300 Subject: [PATCH 79/88] rm deprecated --- frame/stake-tracker/src/lib.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 387c32717345f..3cb62c8584632 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -64,7 +64,6 @@ pub mod pallet { const STORAGE_VERSION: StorageVersion = StorageVersion::new(0); #[pallet::pallet] - #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); From 9f28619cff31bbfe63ff4bb923355595c07fb5dd Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 17:28:12 -0300 Subject: [PATCH 80/88] fix comment --- frame/stake-tracker/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 3cb62c8584632..748f7e3134a5c 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -17,7 +17,7 @@ //! # Stake Tracker Pallet //! -//! The Stake Tracker pallet is used to maintain sorted lists of [`Pallet::AccountId`] by listening +//! The Stake Tracker pallet is used to maintain sorted lists of [`Config::AccountId`] by listening //! to the events that Staking emits. //! //! - [`Config`] From 8bbfd2bac6aca459c9ba2d894cec7b58e76c96ff Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 17:37:14 -0300 Subject: [PATCH 81/88] on_stake_update defensive tests --- frame/stake-tracker/src/tests.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 9fa72f5a0d22c..651d3c8be0741 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -57,6 +57,28 @@ mod on_stake_update { } }); } + + #[test] + #[should_panic(expected = "Unable to update a nominator, perhaps it does not exist?")] + fn defensive_when_not_in_list_nominator() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Nominators::get() { + let _ = StakeTracker::on_stake_update(&id, None); + } + }); + } + + #[test] + #[should_panic(expected = "Unable to update a validator, perhaps it does not exist?")] + fn defensive_when_not_in_list_validator() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Validators::get() { + let _ = StakeTracker::on_stake_update(&id, None); + } + }); + } } mod on_nominator_add { From f8dabaab3ff1b925acf795f92fe78c5ea2bbc681 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 14 Mar 2023 17:47:27 -0300 Subject: [PATCH 82/88] more defensive tests --- frame/stake-tracker/src/tests.rs | 64 ++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 3 deletions(-) diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index 651d3c8be0741..fb3d871ae5ce4 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -49,7 +49,7 @@ mod on_stake_update { let _ = VoterList::on_insert(*id, score).unwrap(); assert_eq!(VoterList::count() as usize, idx + 1); assert_eq!(VoterList::get_score(id).unwrap(), score); - let _ = StakeTracker::on_stake_update(id, None); + StakeTracker::on_stake_update(id, None); assert_eq!( VoterList::get_score(id).unwrap(), Pallet::::to_vote(Staking::stake(id).map(|s| s.active).unwrap()) @@ -64,7 +64,7 @@ mod on_stake_update { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); for id in Nominators::get() { - let _ = StakeTracker::on_stake_update(&id, None); + StakeTracker::on_stake_update(&id, None); } }); } @@ -75,7 +75,7 @@ mod on_stake_update { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); for id in Validators::get() { - let _ = StakeTracker::on_stake_update(&id, None); + StakeTracker::on_stake_update(&id, None); } }); } @@ -101,6 +101,18 @@ mod on_nominator_add { assert_eq!(VoterList::count(), stakers().len() as u32); }); } + + #[test] + #[should_panic(expected = "Unable to insert a nominator, perhaps it already exists?")] + fn defensive_when_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Nominators::get() { + let _ = VoterList::on_insert(id, 100); + StakeTracker::on_nominator_add(&id); + } + }); + } } mod on_nominator_update { @@ -147,6 +159,18 @@ mod on_validator_add { assert_eq!(VoterList::count(), stakers().len() as u32); }); } + + #[test] + #[should_panic(expected = "Unable to insert a validator, perhaps it already exists?")] + fn defensive_when_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Validators::get() { + let _ = VoterList::on_insert(id, 100); + StakeTracker::on_validator_add(&id); + } + }); + } } mod on_validator_update { @@ -195,6 +219,17 @@ mod on_validator_remove { } }); } + + #[test] + #[should_panic(expected = "Unable to remove a validator, perhaps it does not exist?")] + fn defensive_when_not_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Validators::get() { + StakeTracker::on_validator_remove(&id); + } + }); + } } mod on_nominator_remove { @@ -215,6 +250,17 @@ mod on_nominator_remove { } }); } + + #[test] + #[should_panic(expected = "Unable to remove a nominator, perhaps it does not exist?")] + fn defensive_when_not_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in Nominators::get() { + StakeTracker::on_nominator_remove(&id, vec![]); + } + }); + } } mod on_unstake { @@ -233,4 +279,16 @@ mod on_unstake { } }); } + + #[test] + #[should_panic(expected = "The staker should have already been removed!")] + fn defensive_when_in_list() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + for id in stakers() { + let _ = VoterList::on_insert(id, 100); + StakeTracker::on_unstake(&id); + } + }); + } } From 32509adac2453cbdae9bed7b80d01a2996ba69af Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Wed, 15 Mar 2023 10:41:29 -0300 Subject: [PATCH 83/88] fix rustdoc again --- frame/stake-tracker/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 748f7e3134a5c..0e07c873591fb 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -17,8 +17,8 @@ //! # Stake Tracker Pallet //! -//! The Stake Tracker pallet is used to maintain sorted lists of [`Config::AccountId`] by listening -//! to the events that Staking emits. +//! The Stake Tracker pallet is used to maintain sorted lists of [`frame_system::Config::AccountId`] +//! by listening to the events that Staking emits. //! //! - [`Config`] //! - [`Pallet`] From 69e70f9048182abe53108e55109371116369ba3a Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 18 Mar 2023 18:44:43 -0300 Subject: [PATCH 84/88] fix balances --- frame/stake-tracker/src/mock.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index e2182f3b1fd88..1d341e153ceee 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -88,6 +88,10 @@ impl pallet_balances::Config for Runtime { type MaxLocks = frame_support::traits::ConstU32<1024>; type MaxReserves = (); type ReserveIdentifier = [u8; 8]; + type HoldIdentifier = (); + type FreezeIdentifier = (); + type MaxHolds = (); + type MaxFreezes = (); } impl pallet_stake_tracker::Config for Runtime { From 9ad762b07d24c9e66df6683900dadc13747a43fc Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Sat, 18 Mar 2023 20:32:01 -0300 Subject: [PATCH 85/88] address comments --- frame/stake-tracker/src/lib.rs | 40 +++---- frame/stake-tracker/src/mock.rs | 2 +- frame/stake-tracker/src/tests.rs | 187 ++++++++++++++++++------------- frame/staking/src/mock.rs | 32 ++---- frame/staking/src/tests.rs | 74 ++++++++++++ primitives/staking/src/lib.rs | 27 ++++- 6 files changed, 232 insertions(+), 130 deletions(-) diff --git a/frame/stake-tracker/src/lib.rs b/frame/stake-tracker/src/lib.rs index 0e07c873591fb..a12102dcd2b3f 100644 --- a/frame/stake-tracker/src/lib.rs +++ b/frame/stake-tracker/src/lib.rs @@ -81,7 +81,7 @@ pub mod pallet { } impl Pallet { - pub fn active_stake_of(who: &T::AccountId) -> BalanceOf { + pub(crate) fn active_stake_of(who: &T::AccountId) -> BalanceOf { T::Staking::stake(&who).map(|s| s.active).unwrap_or_default() } @@ -96,56 +96,56 @@ impl OnStakingUpdate> for Pallet { if let Ok(current_stake) = T::Staking::stake(who) { let current_active = current_stake.active; - // if this is a nominator + // If this is a nominator, update their position in the `VoterList`. if let Some(_) = T::Staking::nominations(¤t_stake.stash) { let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) - .defensive_proof( - "Unable to update a nominator, perhaps it does not exist?", - ); + .defensive_proof("Nominator's position in VoterList updated; qed"); } - // if this is a validator + // If this is a validator, update their position in the `VoterList`. if T::Staking::is_validator(¤t_stake.stash) { let _ = T::VoterList::on_update(¤t_stake.stash, Self::to_vote(current_active)) - .defensive_proof( - "Unable to update a validator, perhaps it does not exist?", - ); + .defensive_proof("Validator's position in VoterList updated; qed"); } } } fn on_nominator_add(who: &T::AccountId) { let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))) - .defensive_proof("Unable to insert a nominator, perhaps it already exists?"); + .defensive_proof("Nominator inserted into VoterList; qed"); } - fn on_nominator_update(_who: &T::AccountId, _prev_nominations: Vec) { - // Nothing to be done yet. + fn on_nominator_update(who: &T::AccountId, _prev_nominations: Vec) { + if !T::VoterList::contains(who) { + defensive!("Active nominator is in the VoterList; qed"); + } } fn on_validator_add(who: &T::AccountId) { let _ = T::VoterList::on_insert(who.clone(), Self::to_vote(Self::active_stake_of(who))) - .defensive_proof("Unable to insert a validator, perhaps it already exists?"); + .defensive_proof("Validator inserted into VoterList; qed"); } - fn on_validator_update(_who: &T::AccountId) { - // Nothing to be done. + fn on_validator_update(who: &T::AccountId) { + if !T::VoterList::contains(who) { + defensive!("Active validator is in the VoterList; qed"); + } } fn on_validator_remove(who: &T::AccountId) { - let _ = T::VoterList::on_remove(who) - .defensive_proof("Unable to remove a validator, perhaps it does not exist?"); + let _ = + T::VoterList::on_remove(who).defensive_proof("Validator removed from VoterList; qed"); } fn on_nominator_remove(who: &T::AccountId, _nominations: Vec) { - let _ = T::VoterList::on_remove(who) - .defensive_proof("Unable to remove a nominator, perhaps it does not exist?"); + let _ = + T::VoterList::on_remove(who).defensive_proof("Nominator removed from VoterList; qed"); } fn on_unstake(who: &T::AccountId) { if T::VoterList::contains(who) { - defensive!("The staker should have already been removed!"); + defensive!("The staker has already been removed; qed"); } } } diff --git a/frame/stake-tracker/src/mock.rs b/frame/stake-tracker/src/mock.rs index 1d341e153ceee..7a6473bc2596e 100644 --- a/frame/stake-tracker/src/mock.rs +++ b/frame/stake-tracker/src/mock.rs @@ -224,7 +224,7 @@ impl StakingInterface for StakingMock { } fn is_validator(who: &Self::AccountId) -> bool { - *who >= 10 && *who <= 14 + Validators::get().contains(who) } fn nominations(who: &Self::AccountId) -> Option> { diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index fb3d871ae5ce4..e99b6e48e58c4 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -15,14 +15,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{mock::*, pallet::*}; +use super::mock::*; use crate as pallet_stake_tracker; use frame_election_provider_support::SortedListProvider; -use frame_support::assert_storage_noop; -use sp_staking::{OnStakingUpdate, StakingInterface}; +use frame_support::{assert_storage_noop, traits::fungible::Mutate}; +use sp_staking::OnStakingUpdate; type VoterList = ::VoterList; -type Staking = ::Staking; // It is the caller's problem to make sure each of events is emitted in the right context, therefore // we test each event for all the stakers (validators + nominators). @@ -40,26 +39,50 @@ mod on_stake_update { } #[test] - fn works_for_validators_and_nominators() { + fn works() { ExtBuilder::default().build_and_execute(|| { - let score = 1000; + let balance_before: Balance = 1000; + let balance_after: Balance = 10; + let validator_id = 10; + let nominator_id = 20; assert_eq!(VoterList::count(), 0); - // validator, nominator - for (idx, id) in stakers().iter().enumerate() { - let _ = VoterList::on_insert(*id, score).unwrap(); - assert_eq!(VoterList::count() as usize, idx + 1); - assert_eq!(VoterList::get_score(id).unwrap(), score); - StakeTracker::on_stake_update(id, None); - assert_eq!( - VoterList::get_score(id).unwrap(), - Pallet::::to_vote(Staking::stake(id).map(|s| s.active).unwrap()) - ); - } + + // validator + Balances::set_balance(&validator_id, balance_before); + StakeTracker::on_validator_add(&validator_id); + assert_eq!( + VoterList::get_score(&validator_id).unwrap(), + StakeTracker::to_vote(StakeTracker::active_stake_of(&validator_id)) + ); + + Balances::set_balance(&validator_id, balance_after); + StakeTracker::on_stake_update(&validator_id, None); + assert_eq!( + VoterList::get_score(&validator_id).unwrap(), + StakeTracker::to_vote(StakeTracker::active_stake_of(&validator_id)) + ); + + // nominator + Balances::set_balance(&nominator_id, balance_before); + StakeTracker::on_nominator_add(&nominator_id); + assert_eq!( + VoterList::get_score(&nominator_id).unwrap(), + StakeTracker::to_vote(StakeTracker::active_stake_of(&nominator_id)) + ); + + Balances::set_balance(&nominator_id, balance_after); + StakeTracker::on_stake_update(&nominator_id, None); + assert_eq!( + VoterList::get_score(&nominator_id).unwrap(), + StakeTracker::to_vote(StakeTracker::active_stake_of(&nominator_id)) + ); + + assert_eq!(VoterList::count(), 2); }); } #[test] - #[should_panic(expected = "Unable to update a nominator, perhaps it does not exist?")] + #[should_panic(expected = "Nominator's position in VoterList updated; qed")] fn defensive_when_not_in_list_nominator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); @@ -70,7 +93,7 @@ mod on_stake_update { } #[test] - #[should_panic(expected = "Unable to update a validator, perhaps it does not exist?")] + #[should_panic(expected = "Validator's position in VoterList updated; qed")] fn defensive_when_not_in_list_validator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); @@ -89,8 +112,8 @@ mod on_nominator_add { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // nominators + validators - for id in stakers() { + // nominators + for id in Nominators::get() { StakeTracker::on_nominator_add(&id); assert_eq!( VoterList::get_score(&id).unwrap(), @@ -98,17 +121,17 @@ mod on_nominator_add { ); } - assert_eq!(VoterList::count(), stakers().len() as u32); + assert_eq!(VoterList::count(), Nominators::get().len() as u32); }); } #[test] - #[should_panic(expected = "Unable to insert a nominator, perhaps it already exists?")] + #[should_panic(expected = "Nominator inserted into VoterList; qed")] fn defensive_when_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); for id in Nominators::get() { - let _ = VoterList::on_insert(id, 100); + StakeTracker::on_nominator_add(&id); StakeTracker::on_nominator_add(&id); } }); @@ -119,22 +142,23 @@ mod on_nominator_update { use super::*; #[test] - fn always_noop() { + #[should_panic(expected = "Active nominator is in the VoterList; qed")] + fn defensive_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); + StakeTracker::on_nominator_update(&20, Vec::new()) + }); + } - // not in list - // validator, nominator - for id in stakers() { - assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); - } + #[test] + fn noop() { + ExtBuilder::default().build_and_execute(|| { + assert_eq!(VoterList::count(), 0); + let id = 20; - // in list - // validator, nominator - for id in stakers() { - let _ = VoterList::on_insert(id, 1000); - assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); - } + StakeTracker::on_nominator_add(&id); + assert_storage_noop!(StakeTracker::on_nominator_update(&id, Vec::new())); + assert_eq!(VoterList::count(), 1); }); } } @@ -147,8 +171,8 @@ mod on_validator_add { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // nominators + validators - for id in stakers() { + // validators + for id in Validators::get() { StakeTracker::on_validator_add(&id); assert_eq!( VoterList::get_score(&id).unwrap(), @@ -156,47 +180,44 @@ mod on_validator_add { ); } - assert_eq!(VoterList::count(), stakers().len() as u32); + assert_eq!(VoterList::count(), Validators::get().len() as u32); }); } #[test] - #[should_panic(expected = "Unable to insert a validator, perhaps it already exists?")] + #[should_panic(expected = "Validator inserted into VoterList; qed")] fn defensive_when_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Validators::get() { - let _ = VoterList::on_insert(id, 100); - StakeTracker::on_validator_add(&id); - } + let id = 10; + StakeTracker::on_validator_add(&id); + StakeTracker::on_validator_add(&id); }); } } mod on_validator_update { use super::*; + #[test] - fn noop_when_in_the_list() { + fn noop() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // usual user, validator, nominator - for id in stakers() { - let _ = VoterList::on_insert(id, 1000); - assert_storage_noop!(StakeTracker::on_validator_update(&id)); - } + let id = 10; + + StakeTracker::on_validator_add(&id); + assert_storage_noop!(StakeTracker::on_validator_update(&id)); + assert_eq!(VoterList::count(), 1); }); } #[test] - fn noop() { + #[should_panic(expected = "Active validator is in the VoterList; qed")] + fn defensive_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - - // validators + nominators - for id in stakers() { - assert_storage_noop!(StakeTracker::on_validator_update(&id)); - } + StakeTracker::on_validator_update(&10) }); } } @@ -205,29 +226,31 @@ mod on_validator_remove { use super::*; #[test] - // It is the caller's problem to make sure `on_validator_remove` is called in the right context. - fn works_for_everyone() { + fn works_for_validator_and_nominator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // nominators + validators - for id in stakers() { - let _ = VoterList::on_insert(id, 100); - assert_eq!(VoterList::count(), 1); - StakeTracker::on_validator_remove(&id); - assert_eq!(VoterList::count(), 0); - } + let validator_id = 10; + let nominator_id = 20; + + StakeTracker::on_validator_add(&validator_id); + StakeTracker::on_validator_remove(&validator_id); + + assert_eq!(VoterList::count(), 0); + + StakeTracker::on_nominator_add(&nominator_id); + StakeTracker::on_validator_remove(&nominator_id); + + assert_eq!(VoterList::count(), 0); }); } #[test] - #[should_panic(expected = "Unable to remove a validator, perhaps it does not exist?")] + #[should_panic(expected = "Validator removed from VoterList; qed")] fn defensive_when_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Validators::get() { - StakeTracker::on_validator_remove(&id); - } + StakeTracker::on_validator_remove(&10); }); } } @@ -236,23 +259,27 @@ mod on_nominator_remove { use super::*; #[test] - // It is the caller's problem to make sure `on_nominator_remove` is called in the right context. - fn works_for_everyone() { + fn works_for_nominator_and_validator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - // nominators + validators - for id in stakers() { - let _ = VoterList::on_insert(id, 100); - assert_eq!(VoterList::count(), 1); - StakeTracker::on_nominator_remove(&id, Vec::new()); - assert_eq!(VoterList::count(), 0); - } + let validator_id = 10; + let nominator_id = 20; + + StakeTracker::on_nominator_add(&nominator_id); + StakeTracker::on_nominator_remove(&nominator_id, Vec::new()); + + assert_eq!(VoterList::count(), 0); + + StakeTracker::on_validator_add(&validator_id); + StakeTracker::on_nominator_remove(&validator_id, Vec::new()); + + assert_eq!(VoterList::count(), 0); }); } #[test] - #[should_panic(expected = "Unable to remove a nominator, perhaps it does not exist?")] + #[should_panic(expected = "Nominator removed from VoterList; qed")] fn defensive_when_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); @@ -281,7 +308,7 @@ mod on_unstake { } #[test] - #[should_panic(expected = "The staker should have already been removed!")] + #[should_panic(expected = "The staker has already been removed; qed")] fn defensive_when_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index e41343bca5c0a..23f00749ae905 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -309,51 +309,35 @@ parameter_types! { pub struct EventListenerMock; impl OnStakingUpdate for EventListenerMock { fn on_stake_update(who: &AccountId, prev_stake: Option>) { - let mut vec = EmittedEvents::get(); - vec.push(StakeUpdate(*who, prev_stake)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(StakeUpdate(*who, prev_stake))) } fn on_nominator_add(who: &AccountId) { - let mut vec = EmittedEvents::get(); - vec.push(NominatorAdd(*who)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(NominatorAdd(*who))) } fn on_nominator_update(who: &AccountId, prev_nominations: Vec) { - let mut vec = EmittedEvents::get(); - vec.push(NominatorUpdate(*who, prev_nominations)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(NominatorUpdate(*who, prev_nominations))); } fn on_validator_add(who: &AccountId) { - let mut vec = EmittedEvents::get(); - vec.push(ValidatorAdd(*who)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(ValidatorAdd(*who))); } fn on_validator_update(who: &AccountId) { - let mut vec = EmittedEvents::get(); - vec.push(ValidatorUpdate(*who)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(ValidatorUpdate(*who))); } fn on_validator_remove(who: &AccountId) { - let mut vec = EmittedEvents::get(); - vec.push(ValidatorRemove(*who)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(ValidatorRemove(*who))); } fn on_nominator_remove(who: &AccountId, nominations: Vec) { - let mut vec = EmittedEvents::get(); - vec.push(NominatorRemove(*who, nominations)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(NominatorRemove(*who, nominations))); } fn on_unstake(who: &AccountId) { - let mut vec = EmittedEvents::get(); - vec.push(Unstake(*who)); - EmittedEvents::set(vec); + EmittedEvents::mutate(|x| x.push(Unstake(*who))); } } diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index a5ad2b44387e8..01821953a99ec 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -5845,6 +5845,7 @@ mod on_staking_update { #[test] fn on_validator_update() { ExtBuilder::default().check_events(true).build_and_execute(|| { + assert!(Validators::::contains_key(11)); assert_ok!(Staking::validate(RuntimeOrigin::signed(10), ValidatorPrefs::default())); assert_eq!(EmittedEvents::take(), vec![ValidatorUpdate(11)]); }); @@ -5874,6 +5875,7 @@ mod on_staking_update { #[test] fn on_nominator_update() { ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { + assert!(Nominators::::contains_key(101)); assert_ok!(Staking::nominate(RuntimeOrigin::signed(100), vec![11])); assert_eq!(EmittedEvents::take(), vec![NominatorUpdate(101, vec![11, 21])]); }); @@ -5901,6 +5903,25 @@ mod on_staking_update { EmittedEvents::take(), vec![NominatorRemove(101, vec![11, 21]), Unstake(101)] ); + + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 10, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11])); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(1, None), NominatorAdd(1)]); + + assert_ok!(Staking::chill(RuntimeOrigin::signed(2))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 10)); + assert_eq!( + EmittedEvents::take(), + [ + NominatorRemove(1, vec![11]), + StakeUpdate(1, Some(Stake { stash: 1, total: 10, active: 10 })) + ] + ); }); } @@ -5909,6 +5930,59 @@ mod on_staking_update { ExtBuilder::default().check_events(true).nominate(true).build_and_execute(|| { assert_ok!(Staking::force_unstake(RuntimeOrigin::root(), 11, 0)); assert_eq!(EmittedEvents::take(), vec![ValidatorRemove(11), Unstake(11)]); + + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 10, + RewardDestination::Controller + )); + assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(1, None), ValidatorAdd(1)]); + + assert_ok!(Staking::chill(RuntimeOrigin::signed(2))); + assert_ok!(Staking::unbond(RuntimeOrigin::signed(2), 10)); + assert_eq!( + EmittedEvents::take(), + [ + ValidatorRemove(1), + StakeUpdate(1, Some(Stake { stash: 1, total: 10, active: 10 })) + ] + ); + }); + } + + #[test] + fn validator_to_nominator() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 10, + RewardDestination::Controller + )); + assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(1, None), ValidatorAdd(1)]); + + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11])); + assert_eq!(EmittedEvents::take(), vec![ValidatorRemove(1), NominatorAdd(1)]); + }); + } + + #[test] + fn nominator_to_validator() { + ExtBuilder::default().check_events(true).build_and_execute(|| { + assert_ok!(Staking::bond( + RuntimeOrigin::signed(1), + 2, + 10, + RewardDestination::Controller + )); + assert_ok!(Staking::nominate(RuntimeOrigin::signed(2), vec![11])); + assert_eq!(EmittedEvents::take(), vec![StakeUpdate(1, None), NominatorAdd(1)]); + + assert_ok!(Staking::validate(RuntimeOrigin::signed(2), ValidatorPrefs::default())); + assert_eq!(EmittedEvents::take(), vec![NominatorRemove(1, vec![11]), ValidatorAdd(1)]); }); } diff --git a/primitives/staking/src/lib.rs b/primitives/staking/src/lib.rs index 0544bbde0c5d0..6101abac0cd46 100644 --- a/primitives/staking/src/lib.rs +++ b/primitives/staking/src/lib.rs @@ -81,9 +81,11 @@ pub struct Stake { pub active: Balance, } -/// A generic staking event listener. Note that the interface is designed in a way that the events -/// are fired post-action, so any pre-action data that is needed needs to be passed to interface -/// methods. The rest of the data can be retrieved by using `StakingInterface`. +/// A generic staking event listener. +/// +/// Note that the interface is designed in a way that the events are fired post-action, so any +/// pre-action data that is needed needs to be passed to interface methods. The rest of the data can +/// be retrieved by using `StakingInterface`. #[impl_trait_for_tuples::impl_for_tuples(10)] pub trait OnStakingUpdate { /// Fired when the stake amount of someone updates. @@ -91,18 +93,33 @@ pub trait OnStakingUpdate { /// This is effectively any changes to the bond amount, such as bonding more funds, and /// unbonding. fn on_stake_update(who: &AccountId, prev_stake: Option>); + /// Fired when someone sets their intention to nominate. fn on_nominator_add(who: &AccountId); + /// Fired when an existing nominator updates their nominations. + /// + /// Note that this is not fired when a nominator changes their stake. For that, + /// `on_stake_update` should be used, followed by querying whether `who` was a validator or a + /// nominator. fn on_nominator_update(who: &AccountId, prev_nominations: Vec); + /// Fired when someone sets their intention to validate. + /// + /// Note validator preference changes are not communicated, but could be added if needed. fn on_validator_add(who: &AccountId); + /// Fired when an existing validator updates their preferences. + /// + /// Note validator preference changes are not communicated, but could be added if needed. fn on_validator_update(who: &AccountId); + /// Fired when someone removes their intention to validate, either due to chill or nominating. fn on_validator_remove(who: &AccountId); // only fire this event when this is an actual Validator + /// Fired when someone removes their intention to nominate, either due to chill or validating. fn on_nominator_remove(who: &AccountId, nominations: Vec); // only fire this if this is an actual Nominator + /// fired when someone is fully unstaked. fn on_unstake(who: &AccountId); // -> basically `kill_stash` } @@ -123,10 +140,10 @@ pub trait StakingInterface { + TypeInfo + Saturating; - /// AccountId type used by the staking system + /// AccountId type used by the staking system. type AccountId: Clone; - /// whatever + /// Means of converting Currency to VoteWeight. type CurrencyToVote: CurrencyToVote; /// The minimum amount required to bond in order to set nomination intentions. This does not From 03154ecf8d1526ac74bd7109a96a385cc4f9122b Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 21 Mar 2023 11:42:09 -0300 Subject: [PATCH 86/88] remove unnecessary loops --- frame/stake-tracker/src/tests.rs | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/frame/stake-tracker/src/tests.rs b/frame/stake-tracker/src/tests.rs index e99b6e48e58c4..efd2229a61d41 100644 --- a/frame/stake-tracker/src/tests.rs +++ b/frame/stake-tracker/src/tests.rs @@ -86,9 +86,7 @@ mod on_stake_update { fn defensive_when_not_in_list_nominator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Nominators::get() { - StakeTracker::on_stake_update(&id, None); - } + StakeTracker::on_stake_update(&20, None); }); } @@ -97,9 +95,8 @@ mod on_stake_update { fn defensive_when_not_in_list_validator() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Validators::get() { - StakeTracker::on_stake_update(&id, None); - } + + StakeTracker::on_stake_update(&10, None); }); } } @@ -130,10 +127,8 @@ mod on_nominator_add { fn defensive_when_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Nominators::get() { - StakeTracker::on_nominator_add(&id); - StakeTracker::on_nominator_add(&id); - } + StakeTracker::on_nominator_add(&20); + StakeTracker::on_nominator_add(&20); }); } } @@ -283,9 +278,7 @@ mod on_nominator_remove { fn defensive_when_not_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in Nominators::get() { - StakeTracker::on_nominator_remove(&id, vec![]); - } + StakeTracker::on_nominator_remove(&20, vec![]); }); } } @@ -312,10 +305,8 @@ mod on_unstake { fn defensive_when_in_list() { ExtBuilder::default().build_and_execute(|| { assert_eq!(VoterList::count(), 0); - for id in stakers() { - let _ = VoterList::on_insert(id, 100); - StakeTracker::on_unstake(&id); - } + let _ = VoterList::on_insert(10, 100); + StakeTracker::on_unstake(&10); }); } } From ecf831e8c81453e7dd77588dd9ff6830a3f54c55 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 21 Mar 2023 18:25:38 -0300 Subject: [PATCH 87/88] fix some event-handlers to be fired post-action --- frame/staking/src/pallet/impls.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index a992abd777b52..27428e1bc4d1c 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -885,13 +885,15 @@ impl Pallet { /// to `Nominators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { - if Nominators::::contains_key(who) { + let nominator_exists = Nominators::::contains_key(who); + Nominators::::insert(who, nominations); + + if nominator_exists { let prev_nominations = Self::nominations(who); T::EventListeners::on_nominator_update(who, prev_nominations.unwrap_or_default()); } else { T::EventListeners::on_nominator_add(who); } - Nominators::::insert(who, nominations); } /// This function will remove a nominator from the `Nominators` storage map, @@ -918,13 +920,15 @@ impl Pallet { /// `Validators` or `VoterList` outside of this function is almost certainly /// wrong. pub fn do_add_validator(who: &T::AccountId, prefs: ValidatorPrefs) { - if Validators::::contains_key(who) { + let validator_exists = Validators::::contains_key(who); + + Validators::::insert(who, prefs); + + if validator_exists { T::EventListeners::on_validator_update(who); } else { T::EventListeners::on_validator_add(who); } - - Validators::::insert(who, prefs); } /// This function will remove a validator from the `Validators` storage map. From 4fd1c49a07c6dc569d267230dbb605f3e9c000c4 Mon Sep 17 00:00:00 2001 From: Roman Useinov Date: Tue, 21 Mar 2023 18:35:25 -0300 Subject: [PATCH 88/88] fix do_add_nominator --- frame/staking/src/pallet/impls.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 27428e1bc4d1c..d772bb04c733d 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -886,10 +886,12 @@ impl Pallet { /// wrong. pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { let nominator_exists = Nominators::::contains_key(who); + // Get previous nominations before the nominator is updated. + let prev_nominations = Self::nominations(who); + Nominators::::insert(who, nominations); if nominator_exists { - let prev_nominations = Self::nominations(who); T::EventListeners::on_nominator_update(who, prev_nominations.unwrap_or_default()); } else { T::EventListeners::on_nominator_add(who);