diff --git a/Cargo.lock b/Cargo.lock index c30de516..d2e303af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5687,6 +5687,7 @@ dependencies = [ "frame-support", "frame-system", "impl-serde 0.3.2", + "log", "pallet-balances", "parity-scale-codec", "scale-info", diff --git a/pallets/bfc-offences/src/lib.rs b/pallets/bfc-offences/src/lib.rs index 357976fb..d3e36eaf 100644 --- a/pallets/bfc-offences/src/lib.rs +++ b/pallets/bfc-offences/src/lib.rs @@ -27,6 +27,22 @@ pub type NegativeImbalanceOf = <::Currency as Currency< ::AccountId, >>::NegativeImbalance; +pub(crate) const LOG_TARGET: &'static str = "runtime::bfc-offences"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +/// Used for release versioning upto v2_0_0. +/// +/// Obsolete from v3. Keeping around to make encoding/decoding of old migration code easier. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] /// A value that represents the current storage version of this pallet. /// diff --git a/pallets/bfc-offences/src/migrations.rs b/pallets/bfc-offences/src/migrations.rs index f29f95c7..cd1d16c0 100644 --- a/pallets/bfc-offences/src/migrations.rs +++ b/pallets/bfc-offences/src/migrations.rs @@ -1,11 +1,65 @@ use super::*; +use frame_support::{dispatch::Weight, pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; + +#[storage_alias] +pub type StorageVersion = StorageValue, Releases, ValueQuery>; + +pub mod v3 { + use super::*; + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; + + pub struct MigrateToV3(PhantomData); + + impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + + let current = Pallet::::current_storage_version(); + let onchain = StorageVersion::::get(); + + if current == 3 && onchain == Releases::V2_0_0 { + // migrate to new standard storage version + StorageVersion::::kill(); + current.put::>(); + + log!(info, "bfc-offences storage migration passes v3 update ✅"); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + } else { + log!(warn, "Skipping v3, should be removed"); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!( + StorageVersion::::get() == Releases::V2_0_0, + "Required v2_0_0 before upgrading to v3" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + ensure!(Pallet::::on_chain_storage_version() == 3, "v3 not applied"); + + ensure!(!StorageVersion::::exists(), "Storage version not migrated correctly"); + + Ok(()) + } + } +} pub mod v2 { use super::*; use frame_support::{pallet_prelude::Weight, traits::Get}; pub fn pre_migrate() -> Result<(), &'static str> { - frame_support::ensure!( + ensure!( StorageVersion::::get() == Releases::V1_0_0, "Storage version must match to v1.0.0", ); @@ -27,7 +81,7 @@ pub mod v1 { use frame_support::{pallet_prelude::Weight, traits::Get}; pub fn pre_migrate() -> Result<(), &'static str> { - frame_support::ensure!( + ensure!( StorageVersion::::get() == Releases::V1_0_0, "Storage version must match to v1.0.0", ); diff --git a/pallets/bfc-offences/src/pallet/impls.rs b/pallets/bfc-offences/src/pallet/impls.rs index d0c2402b..96adc2ef 100644 --- a/pallets/bfc-offences/src/pallet/impls.rs +++ b/pallets/bfc-offences/src/pallet/impls.rs @@ -61,33 +61,33 @@ impl OffenceHandler> for Pallet { slash_fraction: Perbill, bond: BalanceOf, ) -> BalanceOf { - let mut slash_amount = BalanceOf::::zero(); // slash bonds only if activated - if IsSlashActive::::get() { - slash_amount = slash_fraction * bond; + if Self::is_slash_active() { + let slash_amount = slash_fraction * bond; // slash the validator's reserved self bond // the slashed imbalance will be reserved to the treasury T::Slash::on_unbalanced(T::Currency::slash_reserved(stash, slash_amount).0); Self::deposit_event(Event::Slashed { who: who.clone(), amount: slash_amount }); + return slash_amount; } - slash_amount + BalanceOf::::zero() } fn refresh_offences(session_index: SessionIndex) { - for offences in ValidatorOffences::::iter() { + >::iter().for_each(|offences| { if (session_index - offences.1.latest_offence_session_index) - > OffenceExpirationInSessions::::get() + > Self::offence_expiration_in_sessions() { - ValidatorOffences::::remove(&offences.0); + >::remove(&offences.0); } - } + }); } fn is_offence_count_exceeds(count: u32, tier: TierType) -> bool { // if offence count exceeds the configured limit return match tier { - TierType::Full => count > FullMaximumOffenceCount::::get(), - _ => count > BasicMaximumOffenceCount::::get(), + TierType::Full => count > Self::full_maximum_offence_count(), + _ => count > Self::basic_maximum_offence_count(), }; } } diff --git a/pallets/bfc-offences/src/pallet/mod.rs b/pallets/bfc-offences/src/pallet/mod.rs index bb7f16f9..42c3a3c8 100644 --- a/pallets/bfc-offences/src/pallet/mod.rs +++ b/pallets/bfc-offences/src/pallet/mod.rs @@ -1,13 +1,13 @@ mod impls; use crate::{ - BalanceOf, NegativeImbalanceOf, OffenceCount, Releases, ValidatorOffenceInfo, WeightInfo, + migrations, BalanceOf, NegativeImbalanceOf, OffenceCount, ValidatorOffenceInfo, WeightInfo, }; use bp_staking::TierType; use frame_support::{ pallet_prelude::*, - traits::{Currency, OnUnbalanced, ReservableCurrency}, + traits::{Currency, OnRuntimeUpgrade, OnUnbalanced, ReservableCurrency, StorageVersion}, }; use frame_system::pallet_prelude::*; use sp_staking::SessionIndex; @@ -16,8 +16,12 @@ use sp_staking::SessionIndex; pub mod pallet { use super::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); + /// Pallet for bfc offences #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// Configuration trait of this pallet @@ -71,10 +75,6 @@ pub mod pallet { Slashed { who: T::AccountId, amount: BalanceOf }, } - #[pallet::storage] - /// Storage version of the pallet - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn validator_offences)] @@ -107,6 +107,13 @@ pub mod pallet { /// The current activation of validator slashing pub type IsSlashActive = StorageValue<_, bool, ValueQuery>; + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + migrations::v3::MigrateToV3::::on_runtime_upgrade() + } + } + #[pallet::genesis_config] pub struct GenesisConfig {} @@ -120,7 +127,6 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - StorageVersion::::put(Releases::V2_0_0); OffenceExpirationInSessions::::put(T::DefaultOffenceExpirationInSessions::get()); FullMaximumOffenceCount::::put(T::DefaultFullMaximumOffenceCount::get()); BasicMaximumOffenceCount::::put(T::DefaultBasicMaximumOffenceCount::get()); @@ -140,7 +146,7 @@ pub mod pallet { origin: OriginFor, new: SessionIndex, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(new > 0u32, Error::::CannotSetBelowMin); let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); @@ -159,7 +165,7 @@ pub mod pallet { new: OffenceCount, tier: TierType, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(new > 0u32, Error::::CannotSetBelowMin); match tier { TierType::Full => { @@ -207,7 +213,7 @@ pub mod pallet { origin: OriginFor, is_active: bool, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(is_active != >::get(), Error::::NoWritingSameValue); >::put(is_active); Self::deposit_event(Event::OffenceActivationSet { is_active }); @@ -223,7 +229,7 @@ pub mod pallet { origin: OriginFor, is_active: bool, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(is_active != >::get(), Error::::NoWritingSameValue); >::put(is_active); Self::deposit_event(Event::SlashActivationSet { is_active }); diff --git a/pallets/bfc-staking/src/lib.rs b/pallets/bfc-staking/src/lib.rs index 2af2150f..9d206042 100644 --- a/pallets/bfc-staking/src/lib.rs +++ b/pallets/bfc-staking/src/lib.rs @@ -40,7 +40,6 @@ pub mod weights; pub use inflation::{InflationInfo, Range}; pub use pallet::pallet::*; -pub use set::OrderedSet; use weights::WeightInfo; use parity_scale_codec::{Decode, Encode}; @@ -49,14 +48,18 @@ use scale_info::TypeInfo; use bp_staking::{RoundIndex, TierType}; use frame_support::{ pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, + traits::{tokens::Balance, Currency, Get, ReservableCurrency}, }; use sp_runtime::{ - traits::{Convert, One, Saturating, Zero}, - Perbill, RuntimeDebug, + traits::{Convert, MaybeDisplay, One, Saturating, Zero}, + FixedPointOperand, Perbill, RuntimeDebug, }; use sp_staking::SessionIndex; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::{ + collections::{btree_map::BTreeMap, btree_set::BTreeSet}, + fmt::Debug, + prelude::*, +}; pub type BalanceOf = <::Currency as Currency<::AccountId>>::Balance; @@ -79,6 +82,10 @@ macro_rules! log { }; } +/// Used for release versioning upto v3_0_0. +/// +/// Obsolete from v4. Keeping around to make encoding/decoding of old migration code easier. + #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] /// A value placed in storage that represents the current version of the Staking storage. This value /// is used by the `on_runtime_upgrade` logic to determine whether we run storage migration logic. @@ -372,7 +379,7 @@ impl DelayedControllerSet { } #[derive(Default, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] -/// Info needed to maked delayed commission sets after round end +/// Info needed to made delayed commission sets after round end pub struct DelayedCommissionSet { /// The bonded controller account pub who: AccountId, @@ -447,8 +454,8 @@ impl< > Nominations { /// Retrieve the nominator accounts as a vector - pub fn nominators(self) -> Vec { - self.nominations.into_iter().map(|n| n.owner).collect() + pub fn nominators(&self) -> Vec { + self.nominations.iter().map(|n| n.owner.clone()).collect() } pub fn count(&self) -> u32 { @@ -464,33 +471,18 @@ impl< /// nominations if the amount is the same pub fn insert_sorted_greatest_to_least(&mut self, nomination: Bond) { self.total = self.total.saturating_add(nomination.amount); - // if nominations nonempty && last_element == nomination.amount => push input and return - if !self.nominations.is_empty() { - // if last_element == nomination.amount => push the nomination and return early - if self.nominations[self.nominations.len() - 1].amount == nomination.amount { - self.nominations.push(nomination); - // early return - return; - } - } - // else binary search insertion - match self.nominations.binary_search_by(|x| nomination.amount.cmp(&x.amount)) { - // sorted insertion on sorted vec - // enforces first come first serve for equal bond amounts - Ok(i) => { - let mut new_index = i + 1; - while new_index <= (self.nominations.len() - 1) { - if self.nominations[new_index].amount == nomination.amount { - new_index += 1; - } else { - self.nominations.insert(new_index, nomination); - return; - } - } - self.nominations.push(nomination) - }, - Err(i) => self.nominations.insert(i, nomination), - } + + let insertion_index = + match self.nominations.binary_search_by(|x| nomination.amount.cmp(&x.amount)) { + // Find the next index where amount is not equal to the current nomination amount. + Ok(i) => self.nominations[i..] + .iter() + .position(|x| x.amount != nomination.amount) + .map_or(self.nominations.len(), |offset| i + offset), + Err(i) => i, + }; + + self.nominations.insert(insertion_index, nomination); } /// Return the capacity status for top nominations @@ -529,12 +521,10 @@ impl< nominator: &AccountId, value: Balance, ) -> bool { - for (i, bond) in &mut self.nominations.iter().enumerate() { + for bond in self.nominations.iter_mut() { if bond.owner == *nominator { - let new = Bond { owner: bond.owner.clone(), amount: bond.amount - value }; - self.nominations[i] = new; + bond.amount = bond.amount.saturating_sub(value); self.total = self.total.saturating_sub(value); - return true; } } @@ -860,7 +850,7 @@ impl< self.request = None; // update candidate pool value because it must change if self bond changes >::put(new_total_staked); - Pallet::::update_active(&controller, self.voting_power.into()); + Pallet::::update_active(&controller, self.voting_power.into())?; Pallet::::deposit_event(event); Ok(()) } @@ -886,7 +876,8 @@ impl< &mut self, candidate: T::AccountId, top_nominations: &Nominations>, - ) where + ) -> DispatchResult + where BalanceOf: Into + From, { self.lowest_top_nomination_amount = top_nominations.lowest_nomination_amount().into(); @@ -897,8 +888,10 @@ impl< // so we moved the update into this function to deduplicate code and patch a bug that // forgot to apply the update when increasing top nomination if old_voting_power != self.voting_power { - Pallet::::update_active(&candidate, self.voting_power.into()); + Pallet::::update_active(&candidate, self.voting_power.into())?; } + + Ok(()) } /// Reset bottom nominations metadata @@ -932,7 +925,7 @@ impl< // top is full, insert into top iff the lowest_top < amount if self.lowest_top_nomination_amount < nomination.amount.into() { // bumps lowest top to the bottom inside this function call - less_total_staked = self.add_top_nomination::(candidate, nomination); + less_total_staked = self.add_top_nomination::(candidate, nomination)?; NominatorAdded::AddedToTop { new_total: self.voting_power } } else { // if bottom is full, only insert if greater than lowest bottom (which will @@ -946,13 +939,13 @@ impl< less_total_staked = Some(self.lowest_bottom_nomination_amount); } // insert into bottom - self.add_bottom_nomination::(false, candidate, nomination); + self.add_bottom_nomination::(false, candidate, nomination)?; NominatorAdded::AddedToBottom } }, // top is either empty or partially full _ => { - self.add_top_nomination::(candidate, nomination); + self.add_top_nomination::(candidate, nomination)?; NominatorAdded::AddedToTop { new_total: self.voting_power } }, }; @@ -966,34 +959,35 @@ impl< &mut self, candidate: &T::AccountId, nomination: Bond>, - ) -> Option + ) -> Result, DispatchError> where BalanceOf: Into + From, { let mut less_total_staked = None; - let mut top_nominations = >::get(candidate) - .expect("CandidateInfo existence => TopNominations existence"); + let mut top_nominations = + >::get(candidate).ok_or(>::TopNominationDNE)?; let max_top_nominations_per_candidate = T::MaxTopNominationsPerCandidate::get(); if top_nominations.nominations.len() as u32 == max_top_nominations_per_candidate { // pop lowest top nomination - let new_bottom_nomination = top_nominations.nominations.pop().expect(""); + let new_bottom_nomination = + top_nominations.nominations.pop().ok_or(>::TopNominationDNE)?; top_nominations.total = top_nominations.total.saturating_sub(new_bottom_nomination.amount); if matches!(self.bottom_capacity, CapacityStatus::Full) { less_total_staked = Some(self.lowest_bottom_nomination_amount); } - self.add_bottom_nomination::(true, candidate, new_bottom_nomination); + self.add_bottom_nomination::(true, candidate, new_bottom_nomination)?; } // insert into top top_nominations.insert_sorted_greatest_to_least(nomination); // update candidate info - self.reset_top_data::(candidate.clone(), &top_nominations); + self.reset_top_data::(candidate.clone(), &top_nominations)?; if less_total_staked.is_none() { // only increment nomination count if we are not kicking a bottom nomination self.nomination_count += 1u32; } >::insert(&candidate, top_nominations); - less_total_staked + Ok(less_total_staked) } /// Add nomination to bottom nominations @@ -1005,20 +999,19 @@ impl< bumped_from_top: bool, candidate: &T::AccountId, nomination: Bond>, - ) where + ) -> DispatchResult + where BalanceOf: Into + From, { - let mut bottom_nominations = >::get(candidate) - .expect("CandidateInfo existence => BottomNominations existence"); + let mut bottom_nominations = + >::get(candidate).ok_or(>::BottomNominationDNE)?; // if bottom is full, kick the lowest bottom (which is expected to be lower than input // as per check) let increase_nomination_count = if bottom_nominations.nominations.len() as u32 == T::MaxBottomNominationsPerCandidate::get() { - let lowest_bottom_to_be_kicked = bottom_nominations - .nominations - .pop() - .expect("if at full capacity (>0), then >0 bottom nominations exist; qed"); + let lowest_bottom_to_be_kicked = + bottom_nominations.nominations.pop().ok_or(>::BottomNominationDNE)?; // EXPECT lowest_bottom_to_be_kicked.amount < nomination.amount enforced by caller // if lowest_bottom_to_be_kicked.amount == nomination.amount, we will still kick // the lowest bottom to enforce first come first served @@ -1033,17 +1026,10 @@ impl< // total staked is updated via propagation of lowest bottom nomination amount prior // to call let mut nominator_state = >::get(&lowest_bottom_to_be_kicked.owner) - .expect("Nomination existence => NominatorState existence"); - let leaving = nominator_state.nominations.0.len() == 1usize; + .ok_or(>::NominatorDNE)?; + let leaving = nominator_state.nominations.len() == 1usize; nominator_state.rm_nomination(candidate); - if let Some(request) = nominator_state.requests.requests.remove(&candidate) { - nominator_state.requests.less_total = - nominator_state.requests.less_total.saturating_sub(request.amount); - if matches!(request.action, NominationChange::Revoke) { - nominator_state.requests.revocations_count = - nominator_state.requests.revocations_count.saturating_sub(1u32); - } - } + nominator_state.requests.remove_request(&candidate); Pallet::::deposit_event(Event::NominationKicked { nominator: lowest_bottom_to_be_kicked.owner.clone(), candidate: candidate.clone(), @@ -1070,6 +1056,8 @@ impl< bottom_nominations.insert_sorted_greatest_to_least(nomination); self.reset_bottom_data::(&bottom_nominations); >::insert(candidate, bottom_nominations); + + Ok(()) } /// Remove nomination @@ -1115,8 +1103,8 @@ impl< { let old_voting_power = self.voting_power; // remove top nomination - let mut top_nominations = >::get(candidate) - .expect("CandidateInfo exists => TopNominations exists"); + let mut top_nominations = + >::get(candidate).ok_or(>::CandidateDNE)?; let mut actual_amount_option: Option> = None; top_nominations.nominations = top_nominations .nominations @@ -1136,7 +1124,7 @@ impl< // if bottom nonempty => bump top bottom to top if !matches!(self.bottom_capacity, CapacityStatus::Empty) { let mut bottom_nominations = - >::get(candidate).expect("bottom is nonempty as just checked"); + >::get(candidate).ok_or(>::BottomNominationDNE)?; // expect already stored greatest to least by bond amount let highest_bottom_nomination = bottom_nominations.nominations.remove(0); bottom_nominations.total = @@ -1147,7 +1135,7 @@ impl< top_nominations.insert_sorted_greatest_to_least(highest_bottom_nomination); } // update candidate info - self.reset_top_data::(candidate.clone(), &top_nominations); + self.reset_top_data::(candidate.clone(), &top_nominations)?; self.nomination_count = self.nomination_count.saturating_sub(1u32); >::insert(candidate, top_nominations); // return whether total counted changed @@ -1165,8 +1153,8 @@ impl< BalanceOf: Into, { // remove bottom nomination - let mut bottom_nominations = >::get(candidate) - .expect("CandidateInfo exists => BottomNominations exists"); + let mut bottom_nominations = + >::get(candidate).ok_or(>::BottomNominationDNE)?; let mut actual_amount_option: Option> = None; bottom_nominations.nominations = bottom_nominations .nominations @@ -1232,27 +1220,25 @@ impl< where BalanceOf: Into + From, { - let mut top_nominations = >::get(candidate) - .expect("CandidateInfo exists => TopNominations exists"); + let mut top_nominations = + >::get(candidate).ok_or(>::TopNominationDNE)?; let mut in_top = false; top_nominations.nominations = top_nominations .nominations - .clone() .into_iter() .map(|d| { - if d.owner != nominator { - d - } else { + if d.owner == nominator { in_top = true; - let new_amount = d.amount.saturating_add(more); - Bond { owner: d.owner, amount: new_amount } + Bond { owner: d.owner, amount: d.amount.saturating_add(more) } + } else { + d } }) .collect(); ensure!(in_top, Error::::NominationDNE); top_nominations.total = top_nominations.total.saturating_add(more); top_nominations.sort_greatest_to_least(); - self.reset_top_data::(candidate.clone(), &top_nominations); + self.reset_top_data::(candidate.clone(), &top_nominations)?; >::insert(candidate, top_nominations); Ok(true) } @@ -1271,8 +1257,7 @@ impl< let mut bottom_nominations = >::get(candidate).ok_or(Error::::CandidateDNE)?; let mut nomination_option: Option>> = None; - let in_top_after = if (bond.saturating_add(more)).into() > self.lowest_top_nomination_amount - { + let in_top_after = if bond.saturating_add(more).into() > self.lowest_top_nomination_amount { // bump it from bottom bottom_nominations.nominations = bottom_nominations .nominations @@ -1293,22 +1278,20 @@ impl< let nomination = nomination_option.ok_or(Error::::NominationDNE)?; bottom_nominations.total = bottom_nominations.total.saturating_sub(bond); // add it to top - let mut top_nominations = >::get(candidate) - .expect("CandidateInfo existence => TopNominations existence"); + let mut top_nominations = + >::get(candidate).ok_or(>::TopNominationDNE)?; // if top is full, pop lowest top if matches!(top_nominations.top_capacity::(), CapacityStatus::Full) { // pop lowest top nomination - let new_bottom_nomination = top_nominations - .nominations - .pop() - .expect("Top capacity full => Exists at least 1 top nomination"); + let new_bottom_nomination = + top_nominations.nominations.pop().ok_or(>::TopNominationDNE)?; top_nominations.total = top_nominations.total.saturating_sub(new_bottom_nomination.amount); bottom_nominations.insert_sorted_greatest_to_least(new_bottom_nomination); } // insert into top top_nominations.insert_sorted_greatest_to_least(nomination); - self.reset_top_data::(candidate.clone(), &top_nominations); + self.reset_top_data::(candidate.clone(), &top_nominations)?; >::insert(candidate, top_nominations); true } else { @@ -1316,14 +1299,13 @@ impl< // just increase the nomination bottom_nominations.nominations = bottom_nominations .nominations - .clone() .into_iter() .map(|d| { - if d.owner != nominator { - d - } else { + if d.owner == nominator { in_bottom = true; Bond { owner: d.owner, amount: d.amount.saturating_add(more) } + } else { + d } }) .collect(); @@ -1412,8 +1394,8 @@ impl< .collect(); let nomination = nomination_option.ok_or(Error::::NominationDNE)?; // pop highest bottom by reverse and popping - let mut bottom_nominations = >::get(candidate) - .expect("CandidateInfo existence => BottomNominations existence"); + let mut bottom_nominations = + >::get(candidate).ok_or(>::BottomNominationDNE)?; let highest_bottom_nomination = bottom_nominations.nominations.remove(0); bottom_nominations.total = bottom_nominations.total.saturating_sub(highest_bottom_nomination.amount); @@ -1429,14 +1411,13 @@ impl< let mut is_in_top = false; top_nominations.nominations = top_nominations .nominations - .clone() .into_iter() .map(|d| { - if d.owner != nominator { - d - } else { + if d.owner == nominator { is_in_top = true; Bond { owner: d.owner, amount: d.amount.saturating_sub(less) } + } else { + d } }) .collect(); @@ -1445,7 +1426,7 @@ impl< top_nominations.sort_greatest_to_least(); true }; - self.reset_top_data::(candidate.clone(), &top_nominations); + self.reset_top_data::(candidate.clone(), &top_nominations)?; >::insert(candidate, top_nominations); Ok(in_top_after) } @@ -1460,19 +1441,18 @@ impl< where BalanceOf: Into, { - let mut bottom_nominations = >::get(candidate) - .expect("CandidateInfo exists => BottomNominations exists"); + let mut bottom_nominations = + >::get(candidate).ok_or(>::BottomNominationDNE)?; let mut in_bottom = false; bottom_nominations.nominations = bottom_nominations .nominations - .clone() .into_iter() .map(|d| { - if d.owner != nominator { - d - } else { + if d.owner == nominator { in_bottom = true; Bond { owner: d.owner, amount: d.amount.saturating_sub(less) } + } else { + d } }) .collect(); @@ -1486,13 +1466,13 @@ impl< /// Convey relevant information describing if a nominator was added to the top or bottom /// Nominations added to the top yield a new total -#[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Clone, Copy, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum NominatorAdded { AddedToTop { new_total: B }, AddedToBottom, } -#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Clone, PartialEq, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum NominatorStatus { /// Active with no scheduled exit Active, @@ -1506,9 +1486,9 @@ pub struct Nominator { /// Nominator account pub id: AccountId, /// Current state of all nominations - pub nominations: OrderedSet>, + pub nominations: BTreeMap, /// Initial state of all nominations - pub initial_nominations: OrderedSet>, + pub initial_nominations: BTreeMap, /// Total balance locked for this nominator pub total: Balance, /// Requests to change nominations, relevant if active @@ -1520,28 +1500,7 @@ pub struct Nominator { /// The total amount of awarded tokens to this nominator pub awarded_tokens: Balance, /// The amount of awarded tokens to this nominator per candidate - pub awarded_tokens_per_candidate: OrderedSet>, -} - -// Temporary manual implementation for migration testing purposes -impl PartialEq for Nominator { - fn eq(&self, other: &Self) -> bool { - let must_be_true = self.id == other.id - && self.total == other.total - && self.requests == other.requests - && self.status == other.status; - if !must_be_true { - return false; - } - for (Bond { owner: o1, amount: a1 }, Bond { owner: o2, amount: a2 }) in - self.nominations.0.iter().zip(other.nominations.0.iter()) - { - if o1 != o2 || a1 != a2 { - return false; - } - } - true - } + pub awarded_tokens_per_candidate: BTreeMap, } impl< @@ -1557,19 +1516,22 @@ impl< > Nominator { pub fn new(id: AccountId, validator: AccountId, amount: Balance) -> Self { + let nominations: BTreeMap = + BTreeMap::from([(validator.clone(), amount)]); + let initial_nominations: BTreeMap = + BTreeMap::from([(validator.clone(), amount)]); + let awarded_tokens_per_candidate: BTreeMap = + BTreeMap::from([(validator.clone(), Zero::zero())]); Nominator { id, - nominations: OrderedSet::from(vec![Bond { owner: validator.clone(), amount }]), - initial_nominations: OrderedSet::from(vec![Bond { owner: validator.clone(), amount }]), + nominations, + initial_nominations, total: amount, requests: PendingNominationRequests::new(), status: NominatorStatus::Active, reward_dst: RewardDestination::default(), awarded_tokens: Zero::zero(), - awarded_tokens_per_candidate: OrderedSet::from(vec![Bond { - owner: validator.clone(), - amount: Zero::zero(), - }]), + awarded_tokens_per_candidate, } } @@ -1595,30 +1557,13 @@ impl< } pub fn replace_nominations(&mut self, old: &AccountId, new: &AccountId) { - self.nominations.0 = self - .nominations - .0 - .clone() - .into_iter() - .map(|mut n| { - if n.owner == *old { - n.owner = new.clone(); - } - n - }) - .collect(); - self.initial_nominations.0 = self - .initial_nominations - .0 - .clone() - .into_iter() - .map(|mut n| { - if n.owner == *old { - n.owner = new.clone(); - } - n - }) - .collect(); + if let Some(amount) = self.nominations.remove(old) { + self.nominations.insert(new.clone(), amount); + } + + if let Some(amount) = self.initial_nominations.remove(old) { + self.initial_nominations.insert(new.clone(), amount); + } } pub fn replace_requests(&mut self, old: &AccountId, new: &AccountId) { @@ -1638,11 +1583,8 @@ impl< } pub fn increment_awarded_tokens(&mut self, validator: &AccountId, tokens: Balance) { - for x in &mut self.awarded_tokens_per_candidate.0 { - if &x.owner == validator { - x.amount += tokens; - break; - } + if let Some(x) = self.awarded_tokens_per_candidate.get_mut(validator) { + *x += tokens; } self.awarded_tokens += tokens; } @@ -1651,7 +1593,7 @@ impl< /// - returns None if not in leaving state pub fn can_execute_leave(&self, nomination_weight_hint: u32) -> DispatchResult { ensure!( - nomination_weight_hint >= (self.nominations.0.len() as u32), + nomination_weight_hint >= (self.nominations.len() as u32), Error::::TooLowNominationCountToLeaveNominators ); if let NominatorStatus::Leaving(when) = self.status { @@ -1687,53 +1629,31 @@ impl< self.status = NominatorStatus::Active } - pub fn add_nomination(&mut self, bond: Bond) -> bool { - let amt = bond.amount; - if self.nominations.insert(bond.clone()) { - self.total += amt; - self.initial_nominations.insert(bond.clone()); - self.awarded_tokens_per_candidate - .insert(Bond { owner: bond.owner.clone(), amount: Zero::zero() }); - true + // pub fn add_nomination(&mut self, bond: Bond) -> bool { + pub fn add_nomination( + &mut self, + candidate: AccountId, + amount: Balance, + ) -> DispatchResult { + if self.nominations.contains_key(&candidate) { + Err(>::AlreadyNominatedCandidate.into()) } else { - false + self.nominations.insert(candidate.clone(), amount); + self.total += amount; + self.initial_nominations.insert(candidate.clone(), amount); + self.awarded_tokens_per_candidate.insert(candidate, Zero::zero()); + Ok(()) } } // Return Some(remaining balance), must be more than MinNominatorStk // Return None if nomination not found pub fn rm_nomination(&mut self, validator: &AccountId) -> Option { - let mut amt: Option = None; - let nominations = self - .nominations - .0 - .iter() - .filter_map(|x| { - if &x.owner == validator { - amt = Some(x.amount); - None - } else { - Some(x.clone()) - } - }) - .collect(); - let initial_nominations = self - .initial_nominations - .0 - .iter() - .filter_map(|x| if &x.owner == validator { None } else { Some(x.clone()) }) - .collect(); - let awarded_tokens_per_candidate = self - .awarded_tokens_per_candidate - .0 - .iter() - .filter_map(|x| if &x.owner == validator { None } else { Some(x.clone()) }) - .collect(); - if let Some(balance) = amt { - self.nominations = OrderedSet::from(nominations); - self.initial_nominations = OrderedSet::from(initial_nominations); - self.awarded_tokens_per_candidate = OrderedSet::from(awarded_tokens_per_candidate); - self.total = self.total.saturating_sub(balance); + if let Some(amount) = self.nominations.remove(validator) { + self.initial_nominations.remove(validator); + self.awarded_tokens_per_candidate.remove(validator); + + self.total = self.total.saturating_sub(amount); Some(self.total) } else { None @@ -1754,36 +1674,34 @@ impl< let candidate_id: T::AccountId = candidate.clone().into(); let balance_amt: BalanceOf = amount.into(); // increase nomination - for x in &mut self.nominations.0 { - if x.owner == candidate { - let before_amount: BalanceOf = x.amount.into(); - x.amount += amount; - self.total += amount; - // update validator state nomination - let mut validator_state = - >::get(&candidate_id).ok_or(Error::::CandidateDNE)?; - T::Currency::reserve(&self.id.clone().into(), balance_amt)?; - let in_top = validator_state.increase_nomination::( - &candidate_id, - nominator_id.clone(), - before_amount, - balance_amt, - )?; - let after = validator_state.voting_power; - Pallet::::update_active(&candidate_id, after); - let new_total_staked = >::get().saturating_add(balance_amt); - let nom_st: Nominator> = self.clone().into(); - >::put(new_total_staked); - >::insert(&candidate_id, validator_state); - >::insert(&nominator_id, nom_st); - Pallet::::deposit_event(Event::NominationIncreased { - nominator: nominator_id, - candidate: candidate_id, - amount: balance_amt, - in_top, - }); - return Ok(()); - } + if let Some(candidate_amount) = self.nominations.get_mut(&candidate) { + let before_amount = candidate_amount.clone(); + *candidate_amount += amount; + self.total += amount; + // update validator state nomination + let mut validator_state = + >::get(&candidate_id).ok_or(Error::::CandidateDNE)?; + T::Currency::reserve(&self.id.clone().into(), balance_amt)?; + let in_top = validator_state.increase_nomination::( + &candidate_id, + nominator_id.clone(), + before_amount.into(), + balance_amt, + )?; + let after = validator_state.voting_power; + Pallet::::update_active(&candidate_id, after)?; + let new_total_staked = >::get().saturating_add(balance_amt); + let nom_st: Nominator> = self.clone().into(); + >::put(new_total_staked); + >::insert(&candidate_id, validator_state); + >::insert(&nominator_id, nom_st); + Pallet::::deposit_event(Event::NominationIncreased { + nominator: nominator_id, + candidate: candidate_id, + amount: balance_amt, + in_top, + }); + return Ok(()); } Err(Error::::NominationDNE.into()) } @@ -1798,37 +1716,21 @@ impl< BalanceOf: Into + From, { // get nomination amount - let Bond { amount, .. } = self - .nominations - .0 - .iter() - .find(|b| b.owner == validator) - .ok_or(Error::::NominationDNE)?; - ensure!(*amount > less, Error::::NominatorBondBelowMin); - let expected_amt: BalanceOf = (*amount - less).into(); - ensure!(expected_amt >= T::MinNomination::get(), Error::::NominationBelowMin); - // Net Total is total after pending orders are executed - let net_total = self.total - self.requests.less_total; - // Net Total is always >= MinNominatorStk - let max_subtracted_amount = net_total - T::MinNominatorStk::get().into(); - ensure!(less <= max_subtracted_amount, Error::::NominatorBondBelowMin); - let when = >::get().current_round_index + T::NominationBondLessDelay::get(); - self.requests.bond_less::(validator, less, when)?; - Ok(when) - } - - /// Temporary function to migrate revocations - pub fn hotfix_set_revoke(&mut self, validator: AccountId, when: RoundIndex) { - // get nomination amount - let maybe_bond = self.nominations.0.iter().find(|b| b.owner == validator); - if let Some(Bond { amount, .. }) = maybe_bond { - // add revocation to pending requests - if let Err(e) = self.requests.revoke::(validator, *amount, when) { - log::warn!("Migrate revocation request failed with error: {:?}", e); - } + return if let Some(amount) = self.nominations.get(&validator) { + ensure!(*amount > less, Error::::NominatorBondBelowMin); + let expected_amt: BalanceOf = (*amount - less).into(); + ensure!(expected_amt >= T::MinNomination::get(), Error::::NominationBelowMin); + // Net Total is total after pending orders are executed + let net_total = self.total - self.requests.less_total; + // Net Total is always >= MinNominatorStk + let max_subtracted_amount = net_total - T::MinNominatorStk::get().into(); + ensure!(less <= max_subtracted_amount, Error::::NominatorBondBelowMin); + let when = >::get().current_round_index + T::NominationBondLessDelay::get(); + self.requests.bond_less::(validator, less, when)?; + Ok(when) } else { - log::warn!("Migrate revocation request failed because nomination DNE"); - } + Err(Error::::NominationDNE.into()) + }; } /// Schedule revocation for the given validator @@ -1840,17 +1742,15 @@ impl< BalanceOf: Into, { // get nomination amount - let Bond { amount, .. } = self - .nominations - .0 - .iter() - .find(|b| b.owner == validator) - .ok_or(Error::::NominationDNE)?; - let now = >::get().current_round_index; - let when = now + T::RevokeNominationDelay::get(); - // add revocation to pending requests - self.requests.revoke::(validator, *amount, when)?; - Ok((now, when)) + return if let Some(amount) = self.nominations.get(&validator) { + let now = >::get().current_round_index; + let when = now + T::RevokeNominationDelay::get(); + // add revocation to pending requests + self.requests.revoke::(validator, *amount, when)?; + Ok((now, when)) + } else { + Err(Error::::NominationDNE.into()) + }; } /// Execute pending nomination change request @@ -1872,7 +1772,7 @@ impl< match action { NominationChange::Revoke => { // revoking last nomination => leaving set of nominators - let leaving = if self.nominations.0.len() == 1usize { + let leaving = if self.nominations.len() == 1usize { true } else { ensure!( @@ -1914,50 +1814,49 @@ impl< // remove from pending requests self.requests.less_total = self.requests.less_total.saturating_sub(amount); // decrease nomination - for x in &mut self.nominations.0 { - if x.owner == candidate { - return if x.amount > amount { - let amount_before: BalanceOf = x.amount.into(); - x.amount = x.amount.saturating_sub(amount); - self.total = self.total.saturating_sub(amount); - let new_total: BalanceOf = self.total.into(); - ensure!( - new_total >= T::MinNomination::get(), - Error::::NominationBelowMin - ); - ensure!( - new_total >= T::MinNominatorStk::get(), - Error::::NominatorBondBelowMin - ); - let mut validator = >::get(&candidate_id) - .ok_or(Error::::CandidateDNE)?; - T::Currency::unreserve(&nominator_id, balance_amt); - // need to go into decrease_nomination - let in_top = validator.decrease_nomination::( - &candidate_id, - nominator_id.clone(), - amount_before, - balance_amt, - )?; - >::insert(&candidate_id, validator); - let new_total_staked = >::get().saturating_sub(balance_amt); - >::put(new_total_staked); - let nom_st: Nominator> = self.clone().into(); - >::insert(&nominator_id, nom_st); - Pallet::::deposit_event(Event::NominationDecreased { - nominator: nominator_id, - candidate: candidate_id, - amount: balance_amt, - in_top, - }); - Ok(()) - } else { - // must rm entire nomination if x.amount <= less or cancel request - Err(Error::::NominationBelowMin.into()) - }; - } - } - Err(Error::::NominationDNE.into()) + return if let Some(candidate_amount) = self.nominations.get_mut(&candidate) { + return if *candidate_amount > amount { + let amount_before = candidate_amount.clone(); + *candidate_amount = candidate_amount.saturating_sub(amount); + self.total = self.total.saturating_sub(amount); + let new_total: BalanceOf = self.total.into(); + ensure!( + new_total >= T::MinNomination::get(), + Error::::NominationBelowMin + ); + ensure!( + new_total >= T::MinNominatorStk::get(), + Error::::NominatorBondBelowMin + ); + let mut validator = >::get(&candidate_id) + .ok_or(Error::::CandidateDNE)?; + T::Currency::unreserve(&nominator_id, balance_amt); + // need to go into decrease_nomination + let in_top = validator.decrease_nomination::( + &candidate_id, + nominator_id.clone(), + amount_before.into(), + balance_amt, + )?; + >::insert(&candidate_id, validator); + let new_total_staked = >::get().saturating_sub(balance_amt); + >::put(new_total_staked); + let nom_st: Nominator> = self.clone().into(); + >::insert(&nominator_id, nom_st); + Pallet::::deposit_event(Event::NominationDecreased { + nominator: nominator_id, + candidate: candidate_id, + amount: balance_amt, + in_top, + }); + Ok(()) + } else { + // must rm entire nomination if x.amount <= less or cancel request + Err(Error::::NominationBelowMin.into()) + }; + } else { + Err(Error::::NominationDNE.into()) + }; }, } } @@ -2030,6 +1929,21 @@ impl Default for PendingNominationRequests { } } +impl< + A: Parameter + Member + MaybeSerializeDeserialize + Debug + MaybeDisplay + Ord + MaxEncodedLen, + B: Balance + MaybeSerializeDeserialize + Debug + MaxEncodedLen + FixedPointOperand, + > PendingNominationRequests +{ + pub fn remove_request(&mut self, address: &A) { + if let Some(request) = self.requests.remove(address) { + self.less_total = self.less_total.saturating_sub(request.amount); + if matches!(request.action, NominationChange::Revoke) { + self.revocations_count = self.revocations_count.saturating_sub(1u32); + } + } + } +} + impl< A: Ord + Clone, B: Zero diff --git a/pallets/bfc-staking/src/migrations.rs b/pallets/bfc-staking/src/migrations.rs index 9f8ab7ca..d8e64b65 100644 --- a/pallets/bfc-staking/src/migrations.rs +++ b/pallets/bfc-staking/src/migrations.rs @@ -1,4 +1,197 @@ use super::*; +use crate::set::OrderedSet; + +pub mod v4 { + use super::*; + use bp_staking::MAX_AUTHORITIES; + use frame_support::{ + storage_alias, traits::OnRuntimeUpgrade, BoundedBTreeMap, BoundedBTreeSet, + }; + + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; + + #[storage_alias] + pub type StorageVersion = StorageValue, Releases, ValueQuery>; + + #[storage_alias] + pub type MinTotalSelected = StorageValue, u32, ValueQuery>; + + #[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] + /// Nominator state + pub struct OrderedSetNominator { + pub id: AccountId, + pub nominations: OrderedSet>, + pub initial_nominations: OrderedSet>, + pub total: Balance, + pub requests: PendingNominationRequests, + pub status: NominatorStatus, + pub reward_dst: RewardDestination, + pub awarded_tokens: Balance, + pub awarded_tokens_per_candidate: OrderedSet>, + } + + pub struct MigrateToV4(PhantomData); + + impl OnRuntimeUpgrade for MigrateToV4 { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + + let current = Pallet::::current_storage_version(); + let onchain = StorageVersion::::get(); + + if current == 4 && onchain == Releases::V3_0_0 { + MinTotalSelected::::kill(); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(0, 1)); + + // translate `BoundedVec>, ConstU32>` to `BoundedBTreeMap, ConstU32>` + >::translate::< + BoundedVec>, ConstU32>, + _, + >(|old_pool| { + let new_pool = old_pool + .expect("") + .into_iter() + .map(|bond| (bond.owner, bond.amount)) + .collect::>>(); + + Some(BoundedBTreeMap::try_from(new_pool).expect("")) + }) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + // closure for translate `BoundedVec>` to `BoundedBTreeSet>` + let vec_to_bset = + |old: Option>>| { + let new: BoundedBTreeSet> = old + .expect("") + .into_iter() + .collect::>() + .try_into() + .expect(""); + Some(new) + }; + >::translate::< + BoundedVec>, + _, + >(vec_to_bset) + .expect(""); + >::translate::< + BoundedVec>, + _, + >(vec_to_bset) + .expect(""); + >::translate::< + BoundedVec>, + _, + >(vec_to_bset) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(3, 3)); + + // translate `Vec<(RoundIndex, Vec)>` to `BTreeMap>>` + >::translate::)>, _>( + |old| { + Some( + old.expect("") + .into_iter() + .map(|(round_index, candidates)| { + let bset: BoundedBTreeSet> = + candidates + .into_iter() + .collect::>() + .try_into() + .expect(""); + (round_index, bset) + }) + .collect(), + ) + }, + ) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + // translate old `Nominator` which using ordered set to new Nominator + >::translate( + |_, old: OrderedSetNominator>| { + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + let nominations: BTreeMap<_, _> = old + .nominations + .0 + .into_iter() + .map(|bond| (bond.owner, bond.amount)) + .collect(); + + let initial_nominations: BTreeMap<_, _> = old + .initial_nominations + .0 + .into_iter() + .map(|bond| (bond.owner, bond.amount)) + .collect(); + + let awarded_tokens_per_candidate: BTreeMap<_, _> = old + .awarded_tokens_per_candidate + .0 + .clone() + .iter() + .map(|bond| (bond.owner.clone(), bond.amount)) + .collect(); + + Some(Nominator { + id: old.id, + nominations, + initial_nominations, + total: old.total, + requests: old.requests, + status: old.status, + reward_dst: old.reward_dst, + awarded_tokens: old.awarded_tokens, + awarded_tokens_per_candidate, + }) + }, + ); + + // translate `Vec<(RoundIndex, u32)>` to `BTreeMap` + >::translate::, _>(|old| { + Some(old.expect("").into_iter().collect::>()) + }) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 1)); + + // migrate to new standard storage version + StorageVersion::::kill(); + current.put::>(); + + log!(info, "bfc-staking storage migration passes v4 update ✅"); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + } else { + log!(warn, "Skipping bfc-staking migration v4, should be removed"); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!( + StorageVersion::::get() == Releases::V3_0_0, + "Required v3_0_0 before upgrading to v4" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + ensure!(Pallet::::on_chain_storage_version() == 4, "v4 not applied"); + + ensure!(!StorageVersion::::exists(), "Storage version not migrated correctly"); + + Ok(()) + } + } +} pub mod v3 { use super::*; @@ -57,25 +250,25 @@ pub mod v2 { } pub fn migrate() -> Weight { - NominatorState::::translate(|_key, old: OldNominator>| { - let nominations = old.nominations.0.clone(); - let mut awarded_tokens_per_candidate = OrderedSet::new(); - for nomination in nominations { - awarded_tokens_per_candidate - .insert(Bond { owner: nomination.owner.clone(), amount: Zero::zero() }); - } - Some(Nominator { - id: old.id, - nominations: old.nominations, - initial_nominations: old.initial_nominations, - total: old.total, - requests: old.requests, - status: old.status, - reward_dst: old.reward_dst, - awarded_tokens: old.awarded_tokens, - awarded_tokens_per_candidate, - }) - }); + // NominatorState::::translate(|_key, old: OldNominator>| { + // let nominations = old.nominations.0.clone(); + // let mut awarded_tokens_per_candidate = OrderedSet::new(); + // for nomination in nominations { + // awarded_tokens_per_candidate + // .insert(Bond { owner: nomination.owner.clone(), amount: Zero::zero() }); + // } + // Some(Nominator { + // id: old.id, + // nominations: old.nominations, + // initial_nominations: old.initial_nominations, + // total: old.total, + // requests: old.requests, + // status: old.status, + // reward_dst: old.reward_dst, + // awarded_tokens: old.awarded_tokens, + // awarded_tokens_per_candidate, + // }) + // }); // StorageVersion::::put(Releases::V2_0_0); crate::log!(info, "bfc-staking migration passes Releases::V2_0_0 migrate checks ✅"); diff --git a/pallets/bfc-staking/src/pallet/impls.rs b/pallets/bfc-staking/src/pallet/impls.rs index ca644d27..1e316be9 100644 --- a/pallets/bfc-staking/src/pallet/impls.rs +++ b/pallets/bfc-staking/src/pallet/impls.rs @@ -10,7 +10,7 @@ use pallet_session::ShouldEndSession; use bp_staking::{ traits::{OffenceHandler, RelayManager}, - Offence, + Offence, MAX_AUTHORITIES, }; use sp_runtime::{ traits::{Convert, Saturating, Zero}, @@ -20,24 +20,25 @@ use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, SessionIndex, }; -use sp_std::{vec, vec::Vec}; +use sp_std::{collections::btree_set::BTreeSet, vec, vec::Vec}; use frame_support::{ pallet_prelude::*, traits::{Currency, EstimateNextSessionRotation, Get, Imbalance, ReservableCurrency}, weights::Weight, + BoundedBTreeSet, }; impl Pallet { /// Verifies if the given account is a nominator pub fn is_nominator(acc: &T::AccountId) -> bool { - >::get(acc).is_some() + Self::nominator_state(acc).is_some() } /// Verifies if the given account is a candidate pub fn is_candidate(acc: &T::AccountId, tier: TierType) -> bool { let mut is_candidate = false; - if let Some(state) = >::get(acc) { + if let Some(state) = Self::candidate_info(acc) { is_candidate = match tier { TierType::Full | TierType::Basic => state.tier == tier, TierType::All => true, @@ -49,19 +50,19 @@ impl Pallet { /// Verifies if the given account is a selected candidate for the current round pub fn is_selected_candidate(acc: &T::AccountId, tier: TierType) -> bool { let mut is_selected_candidate = false; - match >::get().binary_search(acc) { - Ok(_) => { + match Self::selected_candidates().contains(acc) { + true => { is_selected_candidate = Self::is_candidate(acc, tier); }, - Err(_) => (), + false => (), }; is_selected_candidate } /// Verifies if the given account has already requested for controller account update pub fn is_controller_set_requested(controller: T::AccountId) -> bool { - let round = >::get(); - let controller_sets = >::get(round.current_round_index); + let round = Self::round(); + let controller_sets = Self::delayed_controller_sets(round.current_round_index); if controller_sets.is_empty() { return false; } @@ -70,8 +71,8 @@ impl Pallet { /// Verifies if the given account has already requested for commission rate update pub fn is_commission_set_requested(who: &T::AccountId) -> bool { - let round = >::get(); - let commission_sets = >::get(round.current_round_index); + let round = Self::round(); + let commission_sets = Self::delayed_commission_sets(round.current_round_index); if commission_sets.is_empty() { return false; } @@ -79,127 +80,122 @@ impl Pallet { } /// Adds a new controller set request. The state reflection will be applied in the next round. - pub fn add_to_controller_sets(stash: T::AccountId, old: T::AccountId, new: T::AccountId) { - let round = >::get(); - let mut controller_sets = >::get(round.current_round_index); - controller_sets - .try_push(DelayedControllerSet::new(stash, old, new)) - .expect("DelayedControllerSets out of bound"); - >::insert(round.current_round_index, controller_sets); + pub fn add_to_controller_sets( + stash: T::AccountId, + old: T::AccountId, + new: T::AccountId, + ) -> DispatchResult { + let round = Self::round(); + >::try_mutate( + round.current_round_index, + |controller_sets| -> DispatchResult { + Ok(controller_sets + .try_push(DelayedControllerSet::new(stash, old, new)) + .map_err(|_| >::TooManyDelayedControllers)?) + }, + ) } /// Adds a new commission set request. The state reflection will be applied in the next round. - pub fn add_to_commission_sets(who: &T::AccountId, old: Perbill, new: Perbill) { - let round = >::get(); - let mut commission_sets = >::get(round.current_round_index); - commission_sets - .try_push(DelayedCommissionSet::new(who.clone(), old, new)) - .expect("DelayedCommissionSets out of bound"); - >::insert(round.current_round_index, commission_sets); + pub fn add_to_commission_sets( + who: &T::AccountId, + old: Perbill, + new: Perbill, + ) -> DispatchResult { + let round = Self::round(); + >::try_mutate( + round.current_round_index, + |commission_sets| -> DispatchResult { + Ok(commission_sets + .try_push(DelayedCommissionSet::new(who.clone(), old, new)) + .map_err(|_| >::TooManyDelayedCommissions)?) + }, + ) } /// Remove the given `who` from the `DelayedControllerSets` of the current round. - pub fn remove_controller_set(who: &T::AccountId) { - let round = >::get(); - let mut controller_sets = - >::get(round.current_round_index).into_inner(); - controller_sets.retain(|c| c.old != *who); - >::insert( - round.current_round_index, - BoundedVec::try_from(controller_sets).expect("DelayedControllerSets out of bound"), - ); + pub fn remove_controller_set(who: &T::AccountId) -> DispatchResult { + let round = Self::round(); + >::mutate(round.current_round_index, |controller_set| { + controller_set.retain(|c| c.old != *who); + }); + Ok(()) } /// Remove the given `who` from the `DelayedCommissionSets` of the current round. - pub fn remove_commission_set(who: &T::AccountId) { - let round = >::get(); - let mut commission_sets = - >::get(round.current_round_index).into_inner(); - commission_sets.retain(|c| c.who != *who); - >::insert( - round.current_round_index, - BoundedVec::try_from(commission_sets).expect("DelayedCommissionSets out of bound"), - ); + pub fn remove_commission_set(who: &T::AccountId) -> DispatchResult { + let round = Self::round(); + >::mutate(round.current_round_index, |commission_sets| { + commission_sets.retain(|c| c.who != *who); + }); + Ok(()) } /// Updates the given candidates voting power persisted in the `CandidatePool` - pub(crate) fn update_active(candidate: &T::AccountId, total: BalanceOf) { - let origin_pool = >::get().into_inner(); - let new_pool = origin_pool + pub(crate) fn update_active(candidate: &T::AccountId, total: BalanceOf) -> DispatchResult { + >::mutate(|pool| { + if let Some(amount) = pool.get_mut(candidate) { + *amount = total; + } + }); + Ok(()) + } + + /// Get vectorized & sorted by voting power in descending order `CandidatePool` + pub fn get_sorted_candidates() -> Vec>> { + let mut candidates = Self::candidate_pool() .into_iter() - .map(|mut c| { - if c.owner == *candidate { - c.amount = total; - } - c - }) + .map(|(owner, amount)| Bond { owner, amount }) .collect::>>>(); - >::put( - BoundedVec::try_from(new_pool).expect("CandidatePool out of bound"), - ); - } + candidates.sort_by(|x, y| y.amount.cmp(&x.amount)); - /// Sort `CandidatePool` candidates by voting power in descending order - pub fn sort_candidates_by_voting_power() { - let mut pool = >::get().into_inner(); - pool.sort_by(|x, y| y.amount.cmp(&x.amount)); - pool.dedup_by(|x, y| x.owner == y.owner); - >::put(BoundedVec::try_from(pool).expect("CandidatePool out of bound")); + candidates } /// Removes the given `candidate` from the `CandidatePool`. Returns `true` if a candidate has /// been removed. pub fn remove_from_candidate_pool(candidate: &T::AccountId) -> bool { - let mut pool = >::get(); - let prev_len = pool.len(); - pool.retain(|c| c.owner != *candidate); - let curr_len = pool.len(); - >::put(pool); - curr_len < prev_len + let mut removed: bool = false; + >::mutate(|pool| { + if let Some(_) = pool.remove(candidate) { + removed = true; + } + }); + + removed } /// Replace the bonded `old` account to the given `new` account from the `CandidatePool` pub fn replace_from_candidate_pool(old: &T::AccountId, new: &T::AccountId) { - let origin_pool = >::get().into_inner(); - let new_pool = origin_pool - .into_iter() - .map(|mut c| { - if c.owner == *old { - c.owner = new.clone(); - } - c - }) - .collect::>>>(); - >::put( - BoundedVec::try_from(new_pool).expect("CandidatePool out of bound"), - ); + >::mutate(|pool| { + if let Some(balance) = pool.remove(old) { + pool.try_insert(new.clone(), balance).expect("CandidatePool out of bound"); + } + }); } /// Adds the given `candidate` to the `SelectedCandidates`. Depends on the given `tier` whether /// it's added to the `SelectedFullCandidates` or `SelectedBasicCandidates`. fn add_to_selected_candidates(candidate: T::AccountId, tier: TierType) { - let mut selected_candidates = >::get(); - selected_candidates - .try_push(candidate.clone()) - .expect("SelectedCandidates out of bound"); - selected_candidates.sort(); - >::put(selected_candidates); + >::mutate(|selected_candidates| { + selected_candidates + .try_insert(candidate.clone()) + .expect("SelectedCandidates out of bound"); + }); match tier { TierType::Full => { - let mut selected_full_candidates = >::get(); - selected_full_candidates - .try_push(candidate.clone()) - .expect("SelectedFullCandidates out of bound"); - selected_full_candidates.sort(); - >::put(selected_full_candidates); + >::mutate(|selected_full_candidates| { + selected_full_candidates + .try_insert(candidate.clone()) + .expect("SelectedFullCandidates out of bound"); + }); }, _ => { - let mut selected_basic_candidates = >::get(); - selected_basic_candidates - .try_push(candidate.clone()) - .expect("SelectedBasicCandidates out of bound"); - selected_basic_candidates.sort(); - >::put(selected_basic_candidates); + >::mutate(|selected_basic_candidates| { + selected_basic_candidates + .try_insert(candidate.clone()) + .expect("SelectedBasicCandidates out of bound"); + }); }, }; } @@ -207,19 +203,19 @@ impl Pallet { /// Removes the given `candidate` from the `SelectedCandidates`. Depends on the given `tier` /// whether it's removed from the `SelectedFullCandidates` or `SelectedBasicCandidates`. fn remove_from_selected_candidates(candidate: &T::AccountId, tier: TierType) { - let mut selected_candidates = >::get(); - selected_candidates.retain(|c| c != candidate); - >::put(selected_candidates); + >::mutate(|selected_candidates| { + selected_candidates.remove(candidate); + }); match tier { TierType::Full => { - let mut selected_full_candidates = >::get(); - selected_full_candidates.retain(|c| c != candidate); - >::put(selected_full_candidates); + >::mutate(|selected_full_candidates| { + selected_full_candidates.remove(candidate); + }); }, _ => { - let mut selected_basic_candidates = >::get(); - selected_basic_candidates.retain(|c| c != candidate); - >::put(selected_basic_candidates); + >::mutate(|selected_basic_candidates| { + selected_basic_candidates.remove(candidate); + }); }, }; } @@ -233,7 +229,7 @@ impl Pallet { /// Compute round issuance based on the total amount of stake of the current round pub fn compute_issuance(staked: BalanceOf) -> BalanceOf { - let config = >::get(); + let config = Self::inflation_config(); let round_issuance = Range { min: config.round.min * staked, ideal: config.round.ideal * staked, @@ -250,9 +246,7 @@ impl Pallet { /// Compute the majority of the selected candidates pub fn compute_majority() -> u32 { - let selected_candidates = >::get(); - let half = (selected_candidates.len() as u32) / 2; - return half + 1; + ((Self::selected_candidates().len() as u32) / 2) + 1 } /// Remove nomination from candidate state @@ -262,10 +256,10 @@ impl Pallet { nominator: T::AccountId, amount: BalanceOf, ) -> DispatchResult { - let mut state = >::get(&candidate).ok_or(Error::::CandidateDNE)?; + let mut state = Self::candidate_info(&candidate).ok_or(Error::::CandidateDNE)?; state.rm_nomination_if_exists::(&candidate, nominator.clone(), amount)?; T::Currency::unreserve(&nominator, amount); - let new_total_locked = >::get().saturating_sub(amount); + let new_total_locked = Self::total().saturating_sub(amount); >::put(new_total_locked); let new_total = state.voting_power; >::insert(&candidate, state); @@ -286,7 +280,7 @@ impl Pallet { return; } let round_to_payout = now - delay; - let total_points = >::get(round_to_payout); + let total_points = Self::points(round_to_payout); if total_points.is_zero() { return; } @@ -299,7 +293,7 @@ impl Pallet { let payout = DelayedPayout { round_issuance, total_staking_reward: round_issuance, - validator_commission: >::get(), + validator_commission: Self::default_basic_validator_commission(), }; >::insert(round_to_payout, payout); } @@ -311,7 +305,7 @@ impl Pallet { stash: T::AccountId, reward: BalanceOf, ) -> Result<(), DispatchError> { - if let Some(mut validator_state) = >::get(&controller) { + if let Some(mut validator_state) = Self::candidate_info(&controller) { // mint rewards to the validators stash account Self::mint_reward(reward, stash); // increment the awarded tokens of this validator @@ -324,8 +318,7 @@ impl Pallet { controller.clone(), reward, )?; - Self::update_active(&controller, validator_state.voting_power); - Self::sort_candidates_by_voting_power(); + Self::update_active(&controller, validator_state.voting_power)?; } >::insert(&controller, validator_state); } @@ -339,7 +332,7 @@ impl Pallet { nominator: T::AccountId, reward: BalanceOf, ) -> Result<(), DispatchError> { - if let Some(mut nominator_state) = >::get(&nominator) { + if let Some(mut nominator_state) = Self::nominator_state(&nominator) { // the nominator must be active and not revoking the current validator if nominator_state.is_active() && !nominator_state.is_revoking(&controller) { // mint rewards to the nominator account @@ -356,7 +349,6 @@ impl Pallet { .is_ok() { >::insert(&nominator, nominator_state); - Self::sort_candidates_by_voting_power(); } }, RewardDestination::Account => { @@ -385,7 +377,7 @@ impl Pallet { } let round_to_payout = now - delay; - if let Some(payout_info) = >::get(round_to_payout) { + if let Some(payout_info) = Self::delayed_payouts(round_to_payout) { let result = Self::pay_one_validator_reward(round_to_payout, payout_info); if result.0.is_none() { // result.0 indicates whether or not a payout was made @@ -408,7 +400,7 @@ impl Pallet { new: &T::AccountId, ) { nominators.into_iter().for_each(|n| { - if let Some(mut nominator) = >::get(n) { + if let Some(mut nominator) = Self::nominator_state(n) { nominator.replace_nominations(old, new); nominator.replace_requests(old, new); >::insert(n, nominator); @@ -421,7 +413,7 @@ impl Pallet { let delayed_round = now - 1; let commission_sets = >::take(delayed_round); commission_sets.into_iter().for_each(|c| { - if let Some(mut candidate) = >::get(&c.who) { + if let Some(mut candidate) = Self::candidate_info(&c.who) { candidate.set_commission(c.new); >::insert(&c.who, candidate); } @@ -435,7 +427,7 @@ impl Pallet { let controller_sets = >::take(delayed_round); if !controller_sets.is_empty() { controller_sets.into_iter().for_each(|c| { - if let Some(candidate) = >::get(&c.old) { + if let Some(candidate) = Self::candidate_info(&c.old) { // replace `CandidateInfo` >::remove(&c.old); >::insert(&c.new, candidate.clone()); @@ -451,16 +443,16 @@ impl Pallet { // replace `TopNominations` if let Some(top_nominations) = >::take(&c.old) { Self::replace_nominator_nominations( - &top_nominations.clone().nominators(), + &top_nominations.nominators(), &c.old, &c.new, ); >::insert(&c.new, top_nominations); } // replace `BottomNominations` - if let Some(bottom_nominations) = >::get(&c.old) { + if let Some(bottom_nominations) = >::take(&c.old) { Self::replace_nominator_nominations( - &bottom_nominations.clone().nominators(), + &bottom_nominations.nominators(), &c.old, &c.new, ); @@ -480,9 +472,9 @@ impl Pallet { /// Mints exactly `amount` native tokens to the `to` account. fn mint_reward(amount: BalanceOf, to: T::AccountId) { if let Ok(amount_transferred) = T::Currency::deposit_into_existing(&to, amount) { - let mut awarded_tokens = >::get(); - awarded_tokens += amount_transferred.peek(); - >::put(awarded_tokens); + >::mutate(|awarded_tokens| { + *awarded_tokens += amount_transferred.peek(); + }); Self::deposit_event(Event::Rewarded { account: to.clone(), rewards: amount_transferred.peek(), @@ -498,14 +490,14 @@ impl Pallet { round_to_payout: RoundIndex, payout_info: DelayedPayout>, ) -> (Option<(T::AccountId, BalanceOf)>, Weight) { - let total_points = >::get(round_to_payout); + let total_points = Self::points(round_to_payout); if total_points.is_zero() { return (None, Weight::from_parts(0u64, 0u64)); } if let Some((validator, pts)) = >::iter_prefix(round_to_payout).drain().next() { - if let Some(state) = >::get(&validator) { + if let Some(state) = Self::candidate_info(&validator) { let validator_issuance = state.commission * payout_info.round_issuance; // compute contribution percentage from given round total points @@ -571,73 +563,51 @@ impl Pallet { /// Compute the top full and basic candidates in the CandidatePool and return /// a vector of their AccountIds (in the order of selection) pub fn compute_top_candidates() -> (Vec, Vec) { - let candidates = >::get(); + let candidates = Self::get_sorted_candidates(); let mut full_candidates = vec![]; let mut basic_candidates = vec![]; - for candidate in candidates { - if let Some(state) = >::get(&candidate.owner) { - let bond = Bond { owner: candidate.owner.clone(), amount: state.voting_power }; + candidates.into_iter().for_each(|candidate| { + if let Some(state) = Self::candidate_info(&candidate.owner) { match state.tier { TierType::Full => { if state.bond >= T::MinFullCandidateStk::get() { - full_candidates.push(bond); + full_candidates.push(candidate); } }, _ => { if state.bond >= T::MinBasicCandidateStk::get() { - basic_candidates.push(bond); + basic_candidates.push(candidate); } }, } } - } - - let full_validators = Self::compute_top_full_candidates(full_candidates); - let basic_validators = Self::compute_top_basic_candidates(basic_candidates); - - let length = (full_validators.len() as u32) + (basic_validators.len() as u32); - - if >::get() > length { - (vec![], vec![]) - } else { - (full_validators, basic_validators) - } - } + }); - /// Compute the top full candidates based on their voting power - pub fn compute_top_full_candidates( - mut candidates: Vec>>, - ) -> Vec { - // order full candidates by voting power (least to greatest so requires `rev()`) - candidates.sort_by(|a, b| a.amount.cmp(&b.amount)); - let top_n = >::get() as usize; + let full_validators = Self::get_top_n_candidates( + full_candidates, + Self::max_full_selected() as usize, + T::MinFullValidatorStk::get(), + ); + let basic_validators = Self::get_top_n_candidates( + basic_candidates, + Self::max_basic_selected() as usize, + T::MinBasicValidatorStk::get(), + ); - // choose the top MaxFullSelected qualified candidates, ordered by voting power - let mut validators = candidates - .into_iter() - .rev() - .filter(|x| x.amount >= T::MinFullValidatorStk::get()) - .take(top_n) - .map(|x| x.owner) - .collect::>(); - validators.sort(); - validators + (full_validators, basic_validators) } - /// Compute the top basic candidates based on their voting power - pub fn compute_top_basic_candidates( - mut candidates: Vec>>, + /// Compute the top basic/full candidates based on their voting power + fn get_top_n_candidates( + candidates: Vec>>, + top_n: usize, + min_stake: BalanceOf, ) -> Vec { - // order candidates by voting power (least to greatest so requires `rev()`) - candidates.sort_by(|a, b| a.amount.cmp(&b.amount)); - let top_n = >::get() as usize; - // choose the top MaxBasicSelected qualified candidates, ordered by voting power let mut validators = candidates .into_iter() - .rev() - .filter(|x| x.amount >= T::MinBasicValidatorStk::get()) + .filter(|x| x.amount >= min_stake) .take(top_n) .map(|x| x.owner) .collect::>(); @@ -654,18 +624,11 @@ impl Pallet { (0u32, 0u32, BalanceOf::::zero()); // snapshot exposure for round for weighting reward distribution for validator in validators.iter() { - let mut state = >::get(validator) - .expect("all members of CandidateQ must be candidates"); - let top_nominations = >::get(validator) + let mut state = Self::candidate_info(validator) .expect("all members of CandidateQ must be candidates"); - let bottom_nominations = BottomNominations::::get(validator) + let top_nominations = Self::top_nominations(validator) .expect("all members of CandidateQ must be candidates"); - // Nominators for each validator - let mut nominators = vec![]; - nominators.append(&mut top_nominations.nominations.clone()); - nominators.append(&mut bottom_nominations.nominations.clone()); - validator_count += 1u32; nomination_count += state.nomination_count; total += state.voting_power; @@ -706,25 +669,32 @@ impl Pallet { nomination_count += full_snapshot.1 + basic_snapshot.1; total += full_snapshot.2 + basic_snapshot.2; - let mut validators = [full_validators.clone(), basic_validators.clone()].concat(); - validators.sort(); + let validators: BoundedBTreeSet> = + [full_validators.clone(), basic_validators.clone()] + .concat() + .into_iter() + .collect::>() + .try_into() + .expect("SelectedCandidates out of bound"); // reset active validator set - >::put( - BoundedVec::try_from(validators.clone()).expect("SelectedCandidates out of bound"), - ); + >::put(validators.clone()); >::put( - BoundedVec::try_from(full_validators.clone()) - .expect("SelectedFullCandidates out of bound"), + BoundedBTreeSet::try_from( + full_validators.clone().into_iter().collect::>(), + ) + .expect("SelectedFullCandidates out of bound"), ); >::put( - BoundedVec::try_from(basic_validators.clone()) - .expect("SelectedBasicCandidates out of bound"), + BoundedBTreeSet::try_from( + basic_validators.into_iter().collect::>(), + ) + .expect("SelectedBasicCandidates out of bound"), ); Self::refresh_cached_selected_candidates(now, validators.clone()); // refresh active relayer set - T::RelayManager::refresh_selected_relayers(now, full_validators.clone()); + T::RelayManager::refresh_selected_relayers(now, full_validators); // active validators count // total nominators count (top + bottom) of active validators @@ -734,18 +704,18 @@ impl Pallet { /// Updates the block productivity and increases block points of the block author pub(crate) fn note_author(author: &T::AccountId) { - let round = >::get(); + let round = Self::round(); let round_index = round.current_round_index; let current_block = round.current_block; - if let Some(mut state) = >::get(author) { + if let Some(mut state) = Self::candidate_info(author) { // rounds current block increases after block authoring state.set_last_block(current_block + T::BlockNumber::from(1u32)); state.increment_blocks_produced(); >::insert(author, state); } - let score_plus_5 = >::get(round_index, &author) + 5; + let score_plus_5 = Self::awarded_pts(round_index, &author) + 5; >::insert(round_index, author, score_plus_5); >::mutate(round_index, |x: &mut RewardPoint| *x += 5); } @@ -763,44 +733,48 @@ impl Pallet { } /// Refresh the `CachedSelectedCandidates` adding the new selected candidates - pub fn refresh_cached_selected_candidates(now: RoundIndex, validators: Vec) { - let mut cached_selected_candidates = >::get(); - if >::get() <= cached_selected_candidates.len() as u32 { - cached_selected_candidates.remove(0); - } - cached_selected_candidates.push((now, validators)); - >::put(cached_selected_candidates); + pub fn refresh_cached_selected_candidates( + now: RoundIndex, + validators: BoundedBTreeSet>, + ) { + >::mutate(|cached_selected_candidates| { + if Self::storage_cache_lifetime() <= cached_selected_candidates.len() as u32 { + cached_selected_candidates.pop_first(); + } + cached_selected_candidates.insert(now, validators); + }); } /// Refresh the latest rounds cached selected candidates to the current state fn refresh_latest_cached_selected_candidates() { - let round = >::get(); - let selected_candidates = >::get(); - let mut cached_selected_candidates = >::get(); - cached_selected_candidates.retain(|r| r.0 != round.current_round_index); - cached_selected_candidates - .push((round.current_round_index, selected_candidates.into_inner())); - >::put(cached_selected_candidates); + >::mutate(|cached_selected_candidates| { + let candidates = Self::selected_candidates(); + cached_selected_candidates + .entry(Self::round().current_round_index) + .and_modify(|c| *c = candidates.clone()) + .or_insert(candidates); + }); } /// Refresh the latest rounds cached majority to the current state fn refresh_latest_cached_majority() { - let round = >::get(); - let majority = >::get(); - let mut cached_majority = >::get(); - cached_majority.retain(|r| r.0 != round.current_round_index); - cached_majority.push((round.current_round_index, majority)); - >::put(cached_majority); + >::mutate(|cached_majority| { + let majority = Self::majority(); + cached_majority + .entry(Self::round().current_round_index) + .and_modify(|m| *m = majority) + .or_insert(majority); + }); } /// Refresh the `Majority` and `CachedMajority` based on the new selected candidates pub fn refresh_majority(now: RoundIndex) { - let mut cached_majority = >::get(); - if >::get() <= cached_majority.len() as u32 { - cached_majority.remove(0); + let mut cached_majority = Self::cached_majority(); + if Self::storage_cache_lifetime() <= cached_majority.len() as u32 { + cached_majority.pop_first(); } let majority: u32 = Self::compute_majority(); - cached_majority.push((now, majority)); + cached_majority.insert(now, majority); >::put(cached_majority); >::put(majority); } @@ -808,47 +782,49 @@ impl Pallet { /// Compute block productivity of the current validators /// - decrease the productivity if the validator produced zero blocks in the current session pub fn compute_productivity(session_validators: Vec) { - for validator in session_validators { - if let Some(mut state) = >::get(&validator) { + session_validators.iter().for_each(|validator| { + if let Some(mut state) = Self::candidate_info(validator) { if state.productivity_status == ProductivityStatus::Idle { state.decrement_productivity::(); } - >::insert(&validator, state); + >::insert(validator, state); } - } + }); } /// Refresh the `ProductivityPerBlock` based on the current round length pub fn refresh_productivity_per_block(validator_count: u32, round_length: u32) { - let blocks_per_validator = { - if validator_count == 0 { - 0u32 - } else { - (round_length / validator_count) + 1 - } - }; - let productivity_per_block = { - if blocks_per_validator == 0 { - Perbill::zero() - } else { - Perbill::from_percent((100 / blocks_per_validator) + 1) - } - }; + let productivity_per_block = + Self::calculate_productivity_per_block(validator_count, round_length); >::put(productivity_per_block); } + fn calculate_productivity_per_block(validator_count: u32, round_length: u32) -> Perbill { + if validator_count == 0 { + return Perbill::zero(); + } + + let blocks_per_validator = (round_length / validator_count) + 1; + + if blocks_per_validator == 0 { + Perbill::zero() + } else { + Perbill::from_percent((100 / blocks_per_validator) + 1) + } + } + /// Refresh the current staking state of the network of the current round pub fn refresh_total_snapshot(now: RoundIndex) { - let selected_candidates = >::get(); + let selected_candidates = Self::selected_candidates(); let mut snapshot: TotalSnapshot> = TotalSnapshot::default(); for candidate in >::iter() { let owner = candidate.0; let state = candidate.1; let top_nominations = - >::get(&owner).expect("Candidate must have top nominations"); - let bottom_nominations = >::get(&owner) - .expect("Candidate must have bottom nominations"); + Self::top_nominations(&owner).expect("Candidate must have top nominations"); + let bottom_nominations = + Self::bottom_nominations(&owner).expect("Candidate must have bottom nominations"); if selected_candidates.contains(&owner) { snapshot.increment_active_self_bond(state.bond); @@ -888,7 +864,7 @@ impl Pallet { // remove from candidate pool Self::remove_from_candidate_pool(who); // update candidate info - let mut candidate_state = CandidateInfo::::get(who).expect("CandidateInfo must exist"); + let mut candidate_state = Self::candidate_info(who).expect("CandidateInfo must exist"); candidate_state.kick_out(); CandidateInfo::::insert(who, &candidate_state); // remove from selected candidates @@ -914,34 +890,27 @@ impl Pallet { offender_slash: BalanceOf, _nominators_slash: &Vec<(T::AccountId, BalanceOf)>, ) { - let mut candidate_state = - CandidateInfo::::get(offender).expect("CandidateInfo must exist"); + let mut candidate_state = Self::candidate_info(offender).expect("CandidateInfo must exist"); candidate_state.slash_bond(offender_slash); candidate_state.slash_voting_power(offender_slash); + // remove validator bond less request amount to prevent integer underflow - if let Some(request) = candidate_state.request { - let mut request_underflow = false; - // bond less amount exceeds current self bond - if candidate_state.bond <= request.amount { - // remove request + if let Some(request) = &candidate_state.request { + let minimum_self_bond = match candidate_state.tier { + TierType::Full => T::MinFullCandidateStk::get(), + _ => T::MinBasicCandidateStk::get(), + }; + + if candidate_state.bond <= request.amount + || candidate_state.bond.saturating_sub(request.amount) < minimum_self_bond + { candidate_state.request = None; - request_underflow = true; - } - if !request_underflow { - let mut minimum_self_bond = T::MinBasicCandidateStk::get(); - if candidate_state.tier == TierType::Full { - minimum_self_bond = T::MinFullCandidateStk::get(); - } - // bond less results to insufficient self bond - if (candidate_state.bond - request.amount) < minimum_self_bond { - // remove request - candidate_state.request = None; - } } } - let new_total_locked = >::get().saturating_sub(offender_slash); + + let new_total_locked = Self::total().saturating_sub(offender_slash); >::put(new_total_locked); - CandidateInfo::::insert(offender, candidate_state.clone()); + CandidateInfo::::insert(offender, candidate_state); } /// Update to the new round. This method will refresh the candidate states and some other @@ -952,7 +921,7 @@ impl Pallet { basic_validators: Vec, ) { // update round - let mut round = >::get(); + let mut round = Self::round(); round.update_round::(now); let current_round = round.current_round_index; // reset candidate states @@ -970,7 +939,7 @@ impl Pallet { // refresh productivity rate per block Self::refresh_productivity_per_block(validator_count, round.round_length); // snapshot total stake and storage state - >::insert(current_round, >::get()); + >::insert(current_round, Self::total()); >::remove(current_round - 1); // handle delayed set requests Self::handle_delayed_controller_sets(current_round); @@ -994,7 +963,7 @@ where fn note_author(author: T::AccountId) { Pallet::::note_author(&author); - if let Some(mut state) = >::get(&author) { + if let Some(mut state) = Self::candidate_info(&author) { state.productivity_status = ProductivityStatus::Active; >::insert(&author, state); } @@ -1024,9 +993,9 @@ where // Update to a new session let new_session = new_index - 1; Session::::put(new_session); - let mut round = Pallet::::round(); + let mut round = Self::round(); round.update_session::(now, new_session); - Round::::put(round); + >::put(round); // Check if the round should update if round.should_update(now) { @@ -1055,10 +1024,10 @@ where } else { // This would brick the chain in the next session log::error!("💥 empty validator set received"); - Some(validators.into_inner()) + Some(validators.into_iter().collect()) } } else { - Some(validators.into_inner()) + Some(validators.into_iter().collect()) } } @@ -1071,7 +1040,7 @@ where impl ShouldEndSession for Pallet { fn should_end_session(now: T::BlockNumber) -> bool { - let round = >::get(); + let round = Self::round(); // always update when a new round should start round.should_update(now) } @@ -1094,7 +1063,7 @@ impl EstimateNextSessionRotation for Pallet { } fn estimate_next_session_rotation(_now: T::BlockNumber) -> (Option, Weight) { - let round = >::get(); + let round = Self::round(); ( Some(round.first_round_block + round.round_length.into()), @@ -1133,10 +1102,10 @@ where slash_session: SessionIndex, _disable_strategy: DisableStrategy, ) -> Weight { - let round = Round::::get(); + let round = Self::round(); for (details, slash_fraction) in offenders.iter().zip(slash_fraction) { let (controller, _snapshot) = &details.offender; - if let Some(candidate_state) = CandidateInfo::::get(controller) { + if let Some(candidate_state) = Self::candidate_info(controller) { // prevent offence handling if the validator is already kicked out (due to session // update delay) if candidate_state.is_kicked_out() { diff --git a/pallets/bfc-staking/src/pallet/mod.rs b/pallets/bfc-staking/src/pallet/mod.rs index 03e2df02..6c07841f 100644 --- a/pallets/bfc-staking/src/pallet/mod.rs +++ b/pallets/bfc-staking/src/pallet/mod.rs @@ -1,10 +1,10 @@ mod impls; use crate::{ - BalanceOf, BlockNumberOf, Bond, CandidateMetadata, DelayedCommissionSet, DelayedControllerSet, - DelayedPayout, InflationInfo, NominationChange, NominationRequest, Nominations, Nominator, - NominatorAdded, Range, Releases, RewardDestination, RewardPoint, RoundIndex, RoundInfo, - TierType, TotalSnapshot, ValidatorSnapshot, WeightInfo, + migrations, BalanceOf, BlockNumberOf, Bond, CandidateMetadata, DelayedCommissionSet, + DelayedControllerSet, DelayedPayout, InflationInfo, NominationRequest, Nominations, Nominator, + NominatorAdded, Range, RewardDestination, RewardPoint, RoundIndex, RoundInfo, TierType, + TotalSnapshot, ValidatorSnapshot, WeightInfo, }; use bp_staking::{ @@ -14,8 +14,8 @@ use bp_staking::{ use frame_support::{ dispatch::DispatchResultWithPostInfo, pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, - Twox64Concat, + traits::{Currency, Get, OnRuntimeUpgrade, ReservableCurrency, StorageVersion}, + BoundedBTreeMap, BoundedBTreeSet, Twox64Concat, }; use frame_system::pallet_prelude::*; use sp_runtime::{ @@ -29,8 +29,12 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub mod pallet { use super::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); + /// Pallet for bfc staking #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// Configuration trait of this pallet. @@ -82,9 +86,6 @@ pub mod pallet { /// Default maximum number of selected basic node candidates every round #[pallet::constant] type DefaultMaxSelectedBasicCandidates: Get; - /// Default minimum number of selected candidates (full and basic) every round - #[pallet::constant] - type DefaultMinSelectedCandidates: Get; /// Maximum top nominations counted per candidate #[pallet::constant] type MaxTopNominationsPerCandidate: Get; @@ -100,10 +101,10 @@ pub mod pallet { /// The default commission rate for a basic validator #[pallet::constant] type DefaultBasicValidatorCommission: Get; - /// The maxmimum commission rate available for a full validator + /// The maximum commission rate available for a full validator #[pallet::constant] type MaxFullValidatorCommission: Get; - /// The maxmimum commission rate available for a basic validator + /// The maximum commission rate available for a basic validator #[pallet::constant] type MaxBasicValidatorCommission: Get; /// Minimum stake required for any full node candidate to be in `SelectedCandidates` for the @@ -140,6 +141,10 @@ pub mod pallet { StashDNE, /// A nomination does not exist with the target nominator and candidate account. NominationDNE, + /// A top nomination does not exist with the target nominator and candidate account. + TopNominationDNE, + /// A bottom nomination does not exist with the target nominator and candidate account. + BottomNominationDNE, /// A commission set request does not exist with the target controller account. CommissionSetDNE, /// A controller set request does not exist with the target controller account. @@ -208,6 +213,10 @@ pub mod pallet { NoWritingSameValue, /// Cannot join candidate pool due to too many candidates. TooManyCandidates, + /// DelayedControllerSets out of bound. + TooManyDelayedControllers, + /// DelayedCommissionSets out of bound. + TooManyDelayedCommissions, /// Cannot join candidate pool due to too low candidate count. TooLowCandidateCountWeightHintJoinCandidates, /// Cannot cancel leave candidate pool due to too low candidate count. @@ -392,8 +401,6 @@ pub mod pallet { MaxFullSelectedSet { old: u32, new: u32 }, /// Set the maximum selected basic candidates to this value. MaxBasicSelectedSet { old: u32, new: u32 }, - /// Set the minimum selected candidates to this value. - MinTotalSelectedSet { old: u32, new: u32 }, /// Set the default validator commission to this value. DefaultValidatorCommissionSet { old: Perbill, new: Perbill, tier: TierType }, /// Set the maximum validator commission to this value. @@ -434,10 +441,6 @@ pub mod pallet { KickedOut(T::AccountId), } - #[pallet::storage] - /// Storage version of the pallet. - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn session)] /// Current session index of current round @@ -488,11 +491,6 @@ pub mod pallet { /// The maximum basic node candidates selected every round pub type MaxBasicSelected = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] - #[pallet::getter(fn min_total_selected)] - /// The minimum candidates selected every round - pub type MinTotalSelected = StorageValue<_, u32, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn productivity_per_block)] /// The productivity rate per block in the current round @@ -554,26 +552,29 @@ pub mod pallet { #[pallet::getter(fn selected_candidates)] /// The active validator set (full and basic) selected for the current round. This storage is sorted by address. pub type SelectedCandidates = - StorageValue<_, BoundedVec>, ValueQuery>; + StorageValue<_, BoundedBTreeSet>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn selected_full_candidates)] /// The active full validator set selected for the current round. This storage is sorted by address. pub type SelectedFullCandidates = - StorageValue<_, BoundedVec>, ValueQuery>; + StorageValue<_, BoundedBTreeSet>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn selected_basic_candidates)] /// The active basic validator set selected for the current round. This storage is sorted by address. pub type SelectedBasicCandidates = - StorageValue<_, BoundedVec>, ValueQuery>; + StorageValue<_, BoundedBTreeSet>, ValueQuery>; #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn cached_selected_candidates)] /// The cached active validator set selected from previous rounds. This storage is sorted by address. - pub type CachedSelectedCandidates = - StorageValue<_, Vec<(RoundIndex, Vec)>, ValueQuery>; + pub type CachedSelectedCandidates = StorageValue< + _, + BTreeMap>>, + ValueQuery, + >; #[pallet::storage] #[pallet::getter(fn majority)] @@ -584,7 +585,7 @@ pub mod pallet { #[pallet::unbounded] #[pallet::getter(fn cached_majority)] /// The cached majority based on the active validator set selected from previous rounds - pub type CachedMajority = StorageValue<_, Vec<(RoundIndex, u32)>, ValueQuery>; + pub type CachedMajority = StorageValue<_, BTreeMap, ValueQuery>; #[pallet::storage] #[pallet::getter(fn total)] @@ -593,10 +594,10 @@ pub mod pallet { #[pallet::storage] #[pallet::getter(fn candidate_pool)] - /// The pool of validator candidates, each with their total voting power. This storage is sorted by amount. + /// The pool of validator candidates, each with their total voting power. pub(crate) type CandidatePool = StorageValue< _, - BoundedVec>, ConstU32>, + BoundedBTreeMap, ConstU32>, ValueQuery, >; @@ -686,18 +687,23 @@ pub mod pallet { fn on_initialize(n: T::BlockNumber) -> Weight { let mut weight = T::WeightInfo::base_on_initialize(); - // Update the current block of the round - let mut round = >::get(); - round.update_block(n); - >::put(round); + >::mutate(|round| { + // Update the current block of the round + round.update_block(n); + + // Refresh the current state of the total stake snapshot + Self::refresh_total_snapshot(round.current_round_index); - // Refresh the current state of the total stake snapshot - Self::refresh_total_snapshot(round.current_round_index); + // Handle the delayed payouts for the previous round + weight += Self::handle_delayed_payouts(round.current_round_index); + }); - // Handle the delayed payouts for the previous round - weight += Self::handle_delayed_payouts(round.current_round_index); weight } + + fn on_runtime_upgrade() -> Weight { + migrations::v4::MigrateToV4::::on_runtime_upgrade() + } } #[pallet::genesis_config] @@ -720,7 +726,6 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - StorageVersion::::put(Releases::V3_0_0); >::put(self.inflation_config.clone()); // Set validator commission to default config >::put(T::DefaultFullValidatorCommission::get()); @@ -790,8 +795,6 @@ pub mod pallet { >::put(T::DefaultMaxSelectedFullCandidates::get()); // Set max selected basic node candidates to maximum config >::put(T::DefaultMaxSelectedBasicCandidates::get()); - // Set min selected candidates to minimum config - >::put(T::DefaultMinSelectedCandidates::get()); // Set storage cache lifetime to default config >::put(T::StorageCacheLifetimeInRounds::get()); // Choose top MaxFullSelected validator candidates @@ -801,7 +804,7 @@ pub mod pallet { // Set majority to initial value let initial_majority: u32 = >::compute_majority(); >::put(initial_majority); - >::put(vec![(1u32, initial_majority)]); + >::put(BTreeMap::from([(1u32, initial_majority)])); T::RelayManager::refresh_majority(1u32); // Start Round 1 at Block 0 let round: RoundInfo = RoundInfo::new( @@ -896,8 +899,8 @@ pub mod pallet { #[pallet::weight(::WeightInfo::set_max_total_selected())] /// Set the maximum number of full validator candidates selected per round pub fn set_max_full_selected(origin: OriginFor, new: u32) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; - ensure!(new >= >::get(), Error::::CannotSetBelowMin); + ensure_root(origin)?; + ensure!(new >= 1u32, Error::::CannotSetBelowMin); let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); ensure!( @@ -917,8 +920,8 @@ pub mod pallet { origin: OriginFor, new: u32, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; - ensure!(new >= >::get(), Error::::CannotSetBelowMin); + ensure_root(origin)?; + ensure!(new >= 1u32, Error::::CannotSetBelowMin); let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); ensure!( @@ -932,26 +935,6 @@ pub mod pallet { } #[pallet::call_index(4)] - #[pallet::weight(::WeightInfo::set_min_total_selected())] - /// Set the minimum number of validator candidates selected per round - pub fn set_min_total_selected( - origin: OriginFor, - new: u32, - ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; - ensure!(new <= >::get(), Error::::CannotSetAboveMax); - let old = >::get(); - ensure!(old != new, Error::::NoWritingSameValue); - ensure!( - new <= >::get().round_length, - Error::::RoundLengthMustBeAtLeastTotalSelectedValidators, - ); - >::put(new); - Self::deposit_event(Event::MinTotalSelectedSet { old, new }); - Ok(().into()) - } - - #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::set_default_validator_commission())] /// Set the default commission rate for all validators of the given tier pub fn set_default_validator_commission( @@ -959,7 +942,7 @@ pub mod pallet { new: Perbill, tier: TierType, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; match tier { TierType::Full => { let old = >::get(); @@ -1006,7 +989,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(6)] + #[pallet::call_index(5)] #[pallet::weight(::WeightInfo::set_max_validator_commission())] /// Set the maximum commission rate for all validators of the given tier pub fn set_max_validator_commission( @@ -1014,7 +997,7 @@ pub mod pallet { new: Perbill, tier: TierType, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; match tier { TierType::Full => { let old = >::get(); @@ -1053,7 +1036,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(7)] + #[pallet::call_index(6)] #[pallet::weight(::WeightInfo::set_validator_commission())] /// Set the commission rate of the given validator /// - origin should be the controller account @@ -1074,12 +1057,12 @@ pub mod pallet { !Self::is_commission_set_requested(&controller), Error::::AlreadyCommissionSetRequested, ); - Self::add_to_commission_sets(&controller, old, new); + Self::add_to_commission_sets(&controller, old, new)?; Self::deposit_event(Event::ValidatorCommissionSet { candidate: controller, old, new }); Ok(().into()) } - #[pallet::call_index(8)] + #[pallet::call_index(7)] #[pallet::weight(::WeightInfo::cancel_validator_commission_set())] /// Cancel the request for (re-)setting the commission rate. /// - origin should be the controller account. @@ -1087,12 +1070,12 @@ pub mod pallet { let controller = ensure_signed(origin)?; ensure!(Self::is_candidate(&controller, TierType::All), Error::::CandidateDNE); ensure!(Self::is_commission_set_requested(&controller), Error::::CommissionSetDNE); - Self::remove_commission_set(&controller); + Self::remove_commission_set(&controller)?; Self::deposit_event(Event::ValidatorCommissionSetCancelled { candidate: controller }); Ok(().into()) } - #[pallet::call_index(9)] + #[pallet::call_index(8)] #[pallet::weight(::WeightInfo::set_validator_tier())] /// Modify validator candidate tier. The actual state reflection will apply at the next /// round @@ -1131,18 +1114,17 @@ pub mod pallet { state.tier = new; state.reset_commission::(); >::insert(&controller, state.clone()); - Self::update_active(&controller, state.voting_power); - Self::sort_candidates_by_voting_power(); + Self::update_active(&controller, state.voting_power)?; Ok(().into()) } - #[pallet::call_index(10)] + #[pallet::call_index(9)] #[pallet::weight(::WeightInfo::set_blocks_per_round())] /// Set blocks per round /// - the `new` round length will be updated immediately in the next block /// - also updates per-round inflation config pub fn set_blocks_per_round(origin: OriginFor, new: u32) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(new >= T::MinBlocksPerRound::get(), Error::::CannotSetBelowMin); let mut round = >::get(); let (current_round, now, first, old) = ( @@ -1153,7 +1135,7 @@ pub mod pallet { ); ensure!(old != new, Error::::NoWritingSameValue); ensure!( - now - first < T::BlockNumber::from(new), + now - first < T::BlockNumber::from(new as u8), Error::::RoundLengthMustBeLongerThanCreatedBlocks, ); ensure!( @@ -1178,14 +1160,14 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(11)] + #[pallet::call_index(10)] #[pallet::weight(::WeightInfo::set_storage_cache_lifetime())] /// Set the `StorageCacheLifetime` round length pub fn set_storage_cache_lifetime( origin: OriginFor, new: u32, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(new >= 1u32, Error::::CannotSetBelowOne); let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); @@ -1194,7 +1176,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(12)] + #[pallet::call_index(11)] #[pallet::weight(::WeightInfo::join_candidates(*candidate_count))] /// Join the set of validator candidates /// - origin should be the stash account @@ -1242,10 +1224,9 @@ pub mod pallet { // insert empty bottom nominations >::insert(&controller, empty_nominations); candidates - .try_push(Bond { owner: controller.clone(), amount: bond }) + .try_insert(controller.clone(), bond) .map_err(|_| Error::::TooManyCandidates)?; >::put(candidates); - Self::sort_candidates_by_voting_power(); let new_total = >::get().saturating_add(bond); >::put(new_total); Self::deposit_event(Event::JoinedValidatorCandidates { @@ -1256,7 +1237,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(13)] + #[pallet::call_index(12)] #[pallet::weight(::WeightInfo::schedule_leave_candidates(*candidate_count))] /// Request to leave the set of candidates. If successful, the account is immediately /// removed from the candidate pool to prevent selection as a validator. @@ -1286,7 +1267,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(14)] + #[pallet::call_index(13)] #[pallet::weight( ::WeightInfo::execute_leave_candidates(*candidate_nomination_count) )] @@ -1304,44 +1285,40 @@ pub mod pallet { Error::::TooLowCandidateNominationCountToLeaveCandidates ); state.can_leave::()?; - let return_stake = |bond: Bond>| { - T::Currency::unreserve(&bond.owner, bond.amount); - // remove nomination from nominator state - let mut nominator = NominatorState::::get(&bond.owner).expect( - "Validator state and nominator state are consistent. - Validator state has a record of this nomination. Therefore, - Nominator state also has a record. qed.", - ); - if let Some(remaining) = nominator.rm_nomination(&controller) { - if remaining.is_zero() { - >::remove(&bond.owner); - } else { - if let Some(request) = nominator.requests.requests.remove(&controller) { - nominator.requests.less_total = - nominator.requests.less_total.saturating_sub(request.amount); - if matches!(request.action, NominationChange::Revoke) { - nominator.requests.revocations_count = - nominator.requests.revocations_count.saturating_sub(1u32); - } + let return_stake = + |bond: Bond>, + mut nominator: Nominator>| { + T::Currency::unreserve(&bond.owner, bond.amount); + // remove nomination from nominator state + if let Some(remaining) = nominator.rm_nomination(&controller) { + if remaining.is_zero() { + >::remove(&bond.owner); + } else { + nominator.requests.remove_request(&controller); + >::insert(&bond.owner, nominator); } - >::insert(&bond.owner, nominator); } - } - }; + }; // total backing stake is at least the candidate self bond let mut total_backing = state.bond; // return all top nominations let top_nominations = - >::take(&controller).expect("CandidateInfo existence checked"); + >::take(&controller).ok_or(>::TopNominationDNE)?; for bond in top_nominations.nominations { - return_stake(bond); + return_stake( + bond.clone(), + NominatorState::::get(&bond.owner).ok_or(>::NominatorDNE)?, + ); } total_backing += top_nominations.total; // return all bottom nominations let bottom_nominations = - >::take(&controller).expect("CandidateInfo existence checked"); + >::take(&controller).ok_or(>::BottomNominationDNE)?; for bond in bottom_nominations.nominations { - return_stake(bond); + return_stake( + bond.clone(), + NominatorState::::get(&bond.owner).ok_or(>::NominatorDNE)?, + ); } total_backing += bottom_nominations.total; // return stake to stash account @@ -1364,7 +1341,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(15)] + #[pallet::call_index(14)] #[pallet::weight(::WeightInfo::cancel_leave_candidates(*candidate_count))] /// Cancel open request to leave candidates /// - only callable by validator account @@ -1384,16 +1361,15 @@ pub mod pallet { Error::::TooLowCandidateCountWeightHintCancelLeaveCandidates, ); candidates - .try_push(Bond { owner: controller.clone(), amount: state.voting_power }) + .try_insert(controller.clone(), state.voting_power) .map_err(|_| Error::::TooManyCandidates)?; >::put(candidates); >::insert(&controller, state); - Self::sort_candidates_by_voting_power(); Self::deposit_event(Event::CancelledCandidateExit { candidate: controller }); Ok(().into()) } - #[pallet::call_index(16)] + #[pallet::call_index(15)] #[pallet::weight(::WeightInfo::set_controller())] /// (Re-)set the bonded controller account. The origin must be the bonded stash account. The /// actual change will apply on the next round update. @@ -1410,12 +1386,12 @@ pub mod pallet { !Self::is_controller_set_requested(old.clone()), Error::::AlreadyControllerSetRequested ); - Self::add_to_controller_sets(stash, old.clone(), new.clone()); - Self::deposit_event(Event::ControllerSet { old: old.clone(), new: new.clone() }); + Self::add_to_controller_sets(stash, old.clone(), new.clone())?; + Self::deposit_event(Event::ControllerSet { old, new }); Ok(().into()) } - #[pallet::call_index(17)] + #[pallet::call_index(16)] #[pallet::weight(::WeightInfo::cancel_controller_set())] /// Cancel the request for (re-)setting the bonded controller account. /// - origin should be the controller account. @@ -1426,12 +1402,12 @@ pub mod pallet { Self::is_controller_set_requested(controller.clone()), Error::::ControllerSetDNE ); - Self::remove_controller_set(&controller); + Self::remove_controller_set(&controller)?; Self::deposit_event(Event::ControllerSetCancelled { candidate: controller }); Ok(().into()) } - #[pallet::call_index(18)] + #[pallet::call_index(17)] #[pallet::weight(::WeightInfo::set_candidate_reward_dst())] /// Set the validator candidate reward destination /// - origin should be the controller account @@ -1453,7 +1429,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(19)] + #[pallet::call_index(18)] #[pallet::weight(::WeightInfo::set_nominator_reward_dst())] /// Set the nominator reward destination pub fn set_nominator_reward_dst( @@ -1474,7 +1450,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(20)] + #[pallet::call_index(19)] #[pallet::weight(::WeightInfo::go_offline())] /// Temporarily leave the set of validator candidates without unbonding /// - removed from candidate pool @@ -1491,33 +1467,28 @@ pub mod pallet { ensure!(state.is_active(), Error::::AlreadyOffline); ensure!(Self::remove_from_candidate_pool(&controller), Error::::AlreadyOffline,); let mut selected_candidates = SelectedCandidates::::get(); - selected_candidates.retain(|v| *v != controller); + selected_candidates.remove(&controller); // refresh selected candidates - let round = >::get(); - let mut cached_selected_candidates = >::get(); - cached_selected_candidates.retain(|r| r.0 != round.current_round_index); - cached_selected_candidates - .push((round.current_round_index, selected_candidates.clone().into_inner())); - >::put(cached_selected_candidates); + let round = >::get().current_round_index; + Self::refresh_cached_selected_candidates(round, selected_candidates.clone()); // refresh majority let majority: u32 = Self::compute_majority(); >::put(majority); let mut cached_majority = >::get(); - cached_majority.retain(|r| r.0 != round.current_round_index); - cached_majority.push((round.current_round_index, majority)); + cached_majority.entry(round).and_modify(|m| *m = majority).or_insert(majority); >::put(cached_majority); if state.tier == TierType::Full { // kickout relayer T::RelayManager::kickout_relayer(&controller); // refresh selected full candidates - let mut selected_full_candidates = >::get(); - selected_full_candidates.retain(|c| *c != controller); - >::put(selected_full_candidates); + >::mutate(|selected_full_candidates| { + selected_full_candidates.remove(&controller); + }); } else { // refresh selected basic candidates - let mut selected_basic_candidates = >::get(); - selected_basic_candidates.retain(|c| *c != controller); - >::put(selected_basic_candidates); + >::mutate(|selected_basic_candidates| { + selected_basic_candidates.remove(&controller); + }); } state.go_offline(); >::insert(&controller, state); @@ -1526,7 +1497,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(21)] + #[pallet::call_index(20)] #[pallet::weight(::WeightInfo::go_online())] /// Rejoin the set of validator candidates if previously been kicked out or went offline /// - state changed to `Active` @@ -1541,7 +1512,7 @@ pub mod pallet { state.go_online(); let mut candidates = >::get(); candidates - .try_push(Bond { owner: controller.clone(), amount: state.voting_power }) + .try_insert(controller.clone(), state.voting_power) .map_err(|_| Error::::TooManyCandidates)?; >::put(candidates); >::insert(&controller, state); @@ -1549,7 +1520,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(22)] + #[pallet::call_index(21)] #[pallet::weight(::WeightInfo::candidate_bond_more())] /// Increase validator candidate self bond by `more` /// - origin should be the stash account @@ -1564,12 +1535,11 @@ pub mod pallet { ensure!(T::Currency::can_reserve(&stash, more), Error::::InsufficientBalance); state.bond_more::(stash.clone(), controller.clone(), more)?; >::insert(&controller, state.clone()); - Self::update_active(&controller, state.voting_power); - Self::sort_candidates_by_voting_power(); + Self::update_active(&controller, state.voting_power)?; Ok(().into()) } - #[pallet::call_index(23)] + #[pallet::call_index(22)] #[pallet::weight(::WeightInfo::schedule_candidate_bond_less())] /// Request by validator candidate to decrease self bond by `less` /// - origin should be the controller account @@ -1590,7 +1560,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(24)] + #[pallet::call_index(23)] #[pallet::weight(::WeightInfo::execute_candidate_bond_less())] /// Execute pending request to adjust the validator candidate self bond /// - origin should be the stash account @@ -1600,11 +1570,10 @@ pub mod pallet { let mut state = >::get(&controller).ok_or(Error::::CandidateDNE)?; state.execute_bond_less::(stash.clone(), controller.clone())?; >::insert(&controller, state); - Self::sort_candidates_by_voting_power(); Ok(().into()) } - #[pallet::call_index(25)] + #[pallet::call_index(24)] #[pallet::weight(::WeightInfo::cancel_candidate_bond_less())] /// Cancel pending request to adjust the validator candidate self bond /// - origin should be the controller account @@ -1616,7 +1585,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(26)] + #[pallet::call_index(25)] #[pallet::weight( ::WeightInfo::nominate( *candidate_nomination_count, @@ -1642,17 +1611,14 @@ pub mod pallet { // nomination after first ensure!(amount >= T::MinNomination::get(), Error::::NominationBelowMin); ensure!( - nomination_count >= state.nominations.0.len() as u32, + nomination_count >= state.nominations.len() as u32, Error::::TooLowNominationCountToNominate ); ensure!( - (state.nominations.0.len() as u32) < T::MaxNominationsPerNominator::get(), + (state.nominations.len() as u32) < T::MaxNominationsPerNominator::get(), Error::::ExceedMaxNominationsPerNominator ); - ensure!( - state.add_nomination(Bond { owner: candidate.clone(), amount }), - Error::::AlreadyNominatedCandidate - ); + state.add_nomination::(candidate.clone(), amount)?; state } else { // first nomination @@ -1670,16 +1636,15 @@ pub mod pallet { ); let (nominator_position, less_total_staked) = state.add_nomination::(&candidate, Bond { owner: nominator.clone(), amount })?; - T::Currency::reserve(&nominator, amount) - .expect("verified can reserve at top of this extrinsic body"); + T::Currency::reserve(&nominator, amount)?; // only is_some if kicked the lowest bottom as a consequence of this new nomination let net_total_increase = if let Some(less) = less_total_staked { amount - less } else { amount }; - let new_total_locked = >::get() + net_total_increase; - >::put(new_total_locked); + >::mutate(|total_locked| { + *total_locked += net_total_increase; + }); >::insert(&candidate, state); >::insert(&nominator, nominator_state); - Self::sort_candidates_by_voting_power(); Self::deposit_event(Event::Nomination { nominator, locked_amount: amount, @@ -1689,7 +1654,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(27)] + #[pallet::call_index(26)] #[pallet::weight(::WeightInfo::schedule_leave_nominators())] /// Request to leave the set of nominators. If successful, the caller is scheduled /// to be allowed to exit. Success forbids future nominator actions until the request is @@ -1709,7 +1674,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(28)] + #[pallet::call_index(27)] #[pallet::weight(::WeightInfo::execute_leave_nominators(*nomination_count))] /// Execute the right to exit the set of nominators and revoke all ongoing nominations. pub fn execute_leave_nominators( @@ -1719,12 +1684,10 @@ pub mod pallet { let nominator = ensure_signed(origin)?; let state = >::get(&nominator).ok_or(Error::::NominatorDNE)?; state.can_execute_leave::(nomination_count)?; - for bond in state.nominations.0 { - if let Err(error) = Self::nominator_leaves_candidate( - bond.owner.clone(), - nominator.clone(), - bond.amount, - ) { + for bond in state.nominations { + if let Err(error) = + Self::nominator_leaves_candidate(bond.0.clone(), nominator.clone(), bond.1) + { log::warn!( "STORAGE CORRUPTED \nNominator leaving validator failed with error: {:?}", error @@ -1732,12 +1695,11 @@ pub mod pallet { } } >::remove(&nominator); - Self::sort_candidates_by_voting_power(); Self::deposit_event(Event::NominatorLeft { nominator, unstaked_amount: state.total }); Ok(().into()) } - #[pallet::call_index(29)] + #[pallet::call_index(28)] #[pallet::weight(::WeightInfo::cancel_leave_nominators())] /// Cancel a pending request to exit the set of nominators. Success clears the pending exit /// request (thereby resetting the delay upon another `leave_nominators` call). @@ -1754,7 +1716,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(30)] + #[pallet::call_index(29)] #[pallet::weight(::WeightInfo::schedule_revoke_nomination())] /// Request to revoke an existing nomination. If successful, the nomination is scheduled /// to be allowed to be revoked via the `execute_nomination_request` extrinsic. @@ -1776,7 +1738,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(31)] + #[pallet::call_index(30)] #[pallet::weight(::WeightInfo::nominator_bond_more())] /// Bond more for nominators wrt a specific validator candidate. pub fn nominator_bond_more( @@ -1787,11 +1749,10 @@ pub mod pallet { let nominator = ensure_signed(origin)?; let mut state = >::get(&nominator).ok_or(Error::::NominatorDNE)?; state.increase_nomination::(candidate.clone(), more)?; - Self::sort_candidates_by_voting_power(); Ok(().into()) } - #[pallet::call_index(32)] + #[pallet::call_index(31)] #[pallet::weight(::WeightInfo::schedule_nominator_bond_less())] /// Request bond less for nominators wrt a specific validator candidate. pub fn schedule_nominator_bond_less( @@ -1813,7 +1774,7 @@ pub mod pallet { Ok(().into()) } - #[pallet::call_index(33)] + #[pallet::call_index(32)] #[pallet::weight(::WeightInfo::execute_nominator_bond_less())] /// Execute pending request to change an existing nomination pub fn execute_nomination_request( @@ -1823,11 +1784,10 @@ pub mod pallet { let nominator = ensure_signed(origin)?; let mut state = >::get(&nominator).ok_or(Error::::NominatorDNE)?; state.execute_pending_request::(candidate)?; - Self::sort_candidates_by_voting_power(); Ok(().into()) } - #[pallet::call_index(34)] + #[pallet::call_index(33)] #[pallet::weight(::WeightInfo::cancel_nominator_bond_less())] /// Cancel request to change an existing nomination. pub fn cancel_nomination_request( diff --git a/pallets/bfc-utility/Cargo.toml b/pallets/bfc-utility/Cargo.toml index e8f3e9b1..c6470dd6 100644 --- a/pallets/bfc-utility/Cargo.toml +++ b/pallets/bfc-utility/Cargo.toml @@ -9,6 +9,7 @@ license = { workspace = true } repository = { workspace = true } [dependencies] +log = { workspace = true } serde = { workspace = true, optional = true } impl-serde = { workspace = true } @@ -32,17 +33,17 @@ sp-io = { workspace = true, features = ["std"] } [features] default = ["std"] std = [ - "impl-serde/std", - "scale-info/std", - "parity-scale-codec/std", - "frame-benchmarking/std", - "frame-support/std", - "frame-system/std", - "serde", - "sp-runtime/std", - "sp-std/std", - "sp-core/std", - "sp-io/std", + "impl-serde/std", + "scale-info/std", + "parity-scale-codec/std", + "frame-benchmarking/std", + "frame-support/std", + "frame-system/std", + "serde", + "sp-runtime/std", + "sp-std/std", + "sp-core/std", + "sp-io/std", ] runtime-benchmarks = ["frame-benchmarking"] try-runtime = ["frame-support/try-runtime"] diff --git a/pallets/bfc-utility/src/lib.rs b/pallets/bfc-utility/src/lib.rs index 21b95bb5..5a25ae23 100644 --- a/pallets/bfc-utility/src/lib.rs +++ b/pallets/bfc-utility/src/lib.rs @@ -21,6 +21,22 @@ pub type BalanceOf = /// The type that indicates the index of a general proposal pub type PropIndex = u32; +pub(crate) const LOG_TARGET: &'static str = "runtime::bfc-utility"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +/// Used for release versioning upto v2_0_0. +/// +/// Obsolete from v3. Keeping around to make encoding/decoding of old migration code easier. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] /// A value placed in storage that represents the current version of the BFC Utility storage. This /// value is used by the `on_runtime_upgrade` logic to determine whether we run storage migration diff --git a/pallets/bfc-utility/src/migrations.rs b/pallets/bfc-utility/src/migrations.rs index ef022802..8e46cc02 100644 --- a/pallets/bfc-utility/src/migrations.rs +++ b/pallets/bfc-utility/src/migrations.rs @@ -1,5 +1,58 @@ use super::*; -use frame_support::pallet_prelude::Weight; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; + +#[storage_alias] +pub type StorageVersion = StorageValue, Releases, ValueQuery>; + +pub mod v3 { + use super::*; + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; + + pub struct MigrateToV3(PhantomData); + + impl OnRuntimeUpgrade for MigrateToV3 { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + + let current = Pallet::::current_storage_version(); + let onchain = StorageVersion::::get(); + + if current == 3 && onchain == Releases::V2_0_0 { + // migrate to new standard storage version + StorageVersion::::kill(); + current.put::>(); + + log!(info, "bfc-utility storage migration passes v3 update ✅"); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + } else { + log!(warn, "Skipping v3, should be removed"); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!( + StorageVersion::::get() == Releases::V2_0_0, + "Required v2_0_0 before upgrading to v3" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + ensure!(Pallet::::on_chain_storage_version() == 3, "v3 not applied"); + + ensure!(!StorageVersion::::exists(), "Storage version not migrated correctly"); + + Ok(()) + } + } +} pub mod v2 { use super::*; diff --git a/pallets/bfc-utility/src/pallet/mod.rs b/pallets/bfc-utility/src/pallet/mod.rs index c82fddbb..1c2132e9 100644 --- a/pallets/bfc-utility/src/pallet/mod.rs +++ b/pallets/bfc-utility/src/pallet/mod.rs @@ -1,8 +1,8 @@ -use crate::{BalanceOf, PropIndex, Proposal, Releases, WeightInfo}; +use crate::{migrations, BalanceOf, PropIndex, Proposal, WeightInfo}; use frame_support::{ pallet_prelude::*, - traits::{Currency, Imbalance, ReservableCurrency}, + traits::{Currency, Imbalance, OnRuntimeUpgrade, ReservableCurrency, StorageVersion}, }; use frame_system::pallet_prelude::*; @@ -12,11 +12,14 @@ use sp_std::prelude::*; #[frame_support::pallet] pub mod pallet { - use super::*; + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(3); + /// Pallet for bfc utility #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(_); /// Configuration trait of this pallet @@ -47,10 +50,6 @@ pub mod pallet { MintNative { beneficiary: T::AccountId, minted: BalanceOf }, } - #[pallet::storage] - /// Storage version of this pallet. - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - #[pallet::storage] #[pallet::unbounded] /// Storage for accepted proposals. Proposal passed by governance will be stored here. @@ -60,6 +59,13 @@ pub mod pallet { /// Storage for proposal index. Whenever proposal is accepted, index will be increased. pub type ProposalIndex = StorageValue<_, PropIndex, ValueQuery>; + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + migrations::v3::MigrateToV3::::on_runtime_upgrade() + } + } + #[pallet::genesis_config] pub struct GenesisConfig {} @@ -73,7 +79,6 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - StorageVersion::::put(Releases::V2_0_0); ProposalIndex::::put(0); } } diff --git a/pallets/relay-manager/src/lib.rs b/pallets/relay-manager/src/lib.rs index 179c728c..5e3566b0 100644 --- a/pallets/relay-manager/src/lib.rs +++ b/pallets/relay-manager/src/lib.rs @@ -33,6 +33,22 @@ pub type IdentificationTuple = ( >>::Identification, ); +pub(crate) const LOG_TARGET: &'static str = "runtime::relay-manager"; + +// syntactic sugar for logging. +#[macro_export] +macro_rules! log { + ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { + log::$level!( + target: crate::LOG_TARGET, + concat!("[{:?}] 💸 ", $patter), >::block_number() $(, $values)* + ) + }; +} + +/// Used for release versioning upto v3_0_0. +/// +/// Obsolete from v4. Keeping around to make encoding/decoding of old migration code easier. #[derive(Encode, Decode, Clone, Copy, PartialEq, Eq, RuntimeDebug, TypeInfo, MaxEncodedLen)] /// A value placed in storage that represents the current version of the Relay Manager storage. This /// value is used by the `on_runtime_upgrade` logic to determine whether we run storage migration @@ -56,7 +72,7 @@ pub enum RelayerStatus { Active, /// It is offline due to unsending heartbeats for the current session Idle, - /// It is kicked out due to continueing unresponsiveness + /// It is kicked out due to continuing unresponsiveness KickedOut, } diff --git a/pallets/relay-manager/src/migrations.rs b/pallets/relay-manager/src/migrations.rs index 332856d2..4d28931a 100644 --- a/pallets/relay-manager/src/migrations.rs +++ b/pallets/relay-manager/src/migrations.rs @@ -1,4 +1,129 @@ use super::*; +use frame_support::{pallet_prelude::*, storage_alias, traits::OnRuntimeUpgrade}; + +#[storage_alias] +pub type StorageVersion = StorageValue, Releases, ValueQuery>; + +pub mod v4 { + use super::*; + use bp_staking::{RoundIndex, MAX_AUTHORITIES}; + use frame_support::BoundedBTreeSet; + use sp_std::collections::{btree_map::BTreeMap, btree_set::BTreeSet}; + + #[cfg(feature = "try-runtime")] + use sp_runtime::TryRuntimeError; + + pub struct MigrateToV4(PhantomData); + + impl OnRuntimeUpgrade for MigrateToV4 { + fn on_runtime_upgrade() -> Weight { + let mut weight = Weight::zero(); + + let current = Pallet::::current_storage_version(); + let onchain = StorageVersion::::get(); + + if current == 4 && onchain == Releases::V3_0_0 { + // closure for translate old selected relayers format to new selected relayers format + let old_selected_to_new = + |old: Option>>| { + let new: BoundedBTreeSet> = old + .expect("") + .into_iter() + .collect::>() + .try_into() + .expect(""); + Some(new) + }; + >::translate::< + BoundedVec>, + _, + >(old_selected_to_new) + .expect(""); + >::translate::< + BoundedVec>, + _, + >(old_selected_to_new) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); + + // closure for translate old Cached*SelectedRelayers format to new Cached*SelectedRelayers format + let old_cache_to_new = |old: Option)>>| { + let new: BTreeMap< + RoundIndex, + BoundedBTreeSet>, + > = old.expect("") + .into_iter() + .map(|(round_index, vec_ids)| { + let set_ids: BoundedBTreeSet> = + vec_ids + .into_iter() + .collect::>() + .try_into() + .expect(""); + (round_index, set_ids) + }) + .collect(); + + Some(new) + }; + >::translate::)>, _>( + old_cache_to_new, + ) + .expect(""); + >::translate::< + Vec<(RoundIndex, Vec)>, + _, + >(old_cache_to_new) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); + + // closure for translate old Cached*Majority format to new Cached*Majority format + let old_cache_to_new = + |old: Option>| -> Option> { + Some(old.expect("").into_iter().collect::>()) + }; + >::translate::, _>(old_cache_to_new) + .expect(""); + >::translate::, _>( + old_cache_to_new, + ) + .expect(""); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(2, 2)); + + // migrate to new standard storage version + StorageVersion::::kill(); + current.put::>(); + + log!(info, "relay-manager storage migration passes v4 update ✅"); + weight = weight.saturating_add(T::DbWeight::get().reads_writes(1, 2)); + } else { + log!(warn, "Skipping v4, should be removed"); + weight = weight.saturating_add(T::DbWeight::get().reads(1)); + } + + weight + } + + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, TryRuntimeError> { + ensure!( + StorageVersion::::get() == Releases::V3_0_0, + "Required v3_0_0 before upgrading to v4" + ); + + Ok(Default::default()) + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), TryRuntimeError> { + ensure!(Pallet::::on_chain_storage_version() == 4, "v4 not applied"); + + ensure!(!StorageVersion::::exists(), "Storage version not migrated correctly"); + + Ok(()) + } + } +} pub mod v3 { use super::*; diff --git a/pallets/relay-manager/src/pallet/impls.rs b/pallets/relay-manager/src/pallet/impls.rs index 334094a3..80ef8805 100644 --- a/pallets/relay-manager/src/pallet/impls.rs +++ b/pallets/relay-manager/src/pallet/impls.rs @@ -6,6 +6,7 @@ use bp_staking::{traits::RelayManager, RoundIndex}; use frame_support::{ pallet_prelude::*, traits::{ValidatorSet, ValidatorSetWithIdentification}, + BoundedBTreeSet, }; use sp_runtime::traits::Convert; use sp_staking::offence::ReportOffence; @@ -14,25 +15,25 @@ use sp_std::{vec, vec::Vec}; impl RelayManager for Pallet where ::AccountId: From< - <::ValidatorSet as ValidatorSet< + <::ValidatorSet as ValidatorSet< ::AccountId, >>::ValidatorId, >, { - fn join_relayers(relayer: T::AccountId, controller: T::AccountId) -> Result<(), DispatchError> { - Self::verify_relayer_existance(&relayer, &controller)?; - Self::add_to_relayer_pool(relayer.clone(), controller.clone()); + fn join_relayers(relayer: T::AccountId, controller: T::AccountId) -> DispatchResult { + Self::verify_relayer_existence(&relayer, &controller)?; + Self::add_to_relayer_pool(relayer.clone(), controller.clone())?; >::insert(&relayer, RelayerMetadata::new(controller.clone())); >::insert(&controller, relayer.clone()); Self::deposit_event(Event::JoinedRelayers { relayer, controller }); - Ok(().into()) + Ok(()) } fn refresh_relayer_pool() { - let pool = >::get(); + let pool = Self::relayer_pool(); pool.iter().for_each(|r| { let mut relayer_state = - >::get(&r.relayer).expect("RelayerState must exist"); + Self::relayer_state(&r.relayer).expect("RelayerState must exist"); if !relayer_state.is_kicked_out() { relayer_state.go_offline(); } @@ -41,62 +42,56 @@ where } fn refresh_selected_relayers(round: RoundIndex, selected_candidates: Vec) { - let mut selected_relayers = vec![]; + let mut selected_relayers: BoundedBTreeSet> = Default::default(); Self::refresh_relayer_pool(); for controller in selected_candidates { - if let Some(relayer) = >::get(&controller) { - selected_relayers.push(relayer.clone()); + if let Some(relayer) = Self::bonded_controller(&controller) { + selected_relayers.try_insert(relayer.clone()).expect(>::TooManySelectedRelayers.as_str()); let mut relayer_state = - >::get(&relayer).expect("RelayerState must exist"); + Self::relayer_state(&relayer).expect("RelayerState must exist"); relayer_state.go_online(); >::insert(&relayer, relayer_state); Self::deposit_event(Event::RelayerChosen { round, - relayer: relayer.clone(), + relayer, controller, }); } } >::put(round); - selected_relayers.sort(); - >::put( - BoundedVec::try_from(selected_relayers.clone()).expect("SelectedRelayers out of bound"), - ); - >::put( - BoundedVec::try_from(selected_relayers.clone()) - .expect("InitialSelectedRelayers out of bound"), - ); - Self::refresh_cached_selected_relayers(round, selected_relayers.clone()); + >::put(selected_relayers.clone()); + >::put(selected_relayers.clone()); + Self::refresh_cached_selected_relayers(round, selected_relayers); } - fn refresh_cached_selected_relayers(round: RoundIndex, relayers: Vec) { - let mut cached_selected_relayers = >::get(); - let mut cached_initial_selected_relayers = >::get(); - if >::get() <= cached_selected_relayers.len() as u32 { - cached_selected_relayers.remove(0); + fn refresh_cached_selected_relayers(round: RoundIndex, relayers: BoundedBTreeSet>) { + let mut cached_selected_relayers = Self::cached_selected_relayers(); + let mut cached_initial_selected_relayers = Self::cached_initial_selected_relayers(); + if Self::storage_cache_lifetime() <= cached_selected_relayers.len() as u32 { + cached_selected_relayers.pop_first(); } - if >::get() <= cached_initial_selected_relayers.len() as u32 { - cached_initial_selected_relayers.remove(0); + if Self::storage_cache_lifetime() <= cached_initial_selected_relayers.len() as u32 { + cached_initial_selected_relayers.pop_first(); } - cached_selected_relayers.push((round, relayers.clone())); - cached_initial_selected_relayers.push((round, relayers.clone())); + cached_selected_relayers.insert(round, relayers.clone()); + cached_initial_selected_relayers.insert(round, relayers.clone()); >::put(cached_selected_relayers); >::put(cached_initial_selected_relayers); } fn refresh_majority(round: RoundIndex) { - let mut cached_majority = >::get(); - let mut cached_initial_majority = >::get(); - if >::get() <= cached_majority.len() as u32 { - cached_majority.remove(0); + let mut cached_majority = Self::cached_majority(); + let mut cached_initial_majority = Self::cached_initial_majority(); + if Self::storage_cache_lifetime() <= cached_majority.len() as u32 { + cached_majority.pop_first(); } - if >::get() <= cached_initial_majority.len() as u32 { - cached_initial_majority.remove(0); + if Self::storage_cache_lifetime() <= cached_initial_majority.len() as u32 { + cached_initial_majority.pop_first(); } let majority: u32 = Self::compute_majority(); - cached_majority.push((round, majority)); - cached_initial_majority.push((round, majority)); + cached_majority.insert(round, majority); + cached_initial_majority.insert(round, majority); >::put(majority); >::put(majority); >::put(cached_majority); @@ -107,11 +102,11 @@ where if let Some(relayer) = >::take(&old) { >::insert(&new, relayer.clone()); let mut relayer_state = - >::get(&relayer).expect("RelayerState must exist"); + Self::relayer_state(&relayer).expect("RelayerState must exist"); relayer_state.set_controller(new.clone()); >::insert(&relayer, relayer_state); Self::remove_from_relayer_pool(&new, false); - Self::add_to_relayer_pool(relayer.clone(), new.clone()); + Self::add_to_relayer_pool(relayer.clone(), new.clone()).expect(>::TooManyRelayers.as_str()); } } @@ -123,9 +118,9 @@ where } fn kickout_relayer(controller: &T::AccountId) { - if let Some(relayer) = >::get(controller) { + if let Some(relayer) = Self::bonded_controller(controller) { let mut relayer_state = - >::get(&relayer).expect("RelayerState must exist"); + Self::relayer_state(&relayer).expect("RelayerState must exist"); relayer_state.kick_out(); >::insert(&relayer, relayer_state); @@ -160,7 +155,7 @@ where let relayer = Self::bonded_controller(&controller).expect("BondedController must exist"); let mut relayer_state = - >::get(&relayer).expect("RelayerState must exist"); + Self::relayer_state(&relayer).expect("RelayerState must exist"); relayer_state.go_offline(); >::insert(&relayer, relayer_state); >::IdentificationOf::convert( @@ -177,7 +172,7 @@ where if offenders.is_empty() { Self::deposit_event(Event::::AllGood); } else { - if >::get() { + if Self::is_heartbeat_offence_active() { let validator_set_count = current_validators.len() as u32; let offence = UnresponsivenessOffence { session_index, @@ -197,7 +192,7 @@ where impl Pallet { /// Verifies if the given account is a (candidate) relayer pub fn is_relayer(relayer: &T::AccountId) -> bool { - if >::get(relayer).is_some() { + if Self::relayer_state(relayer).is_some() { return true; } false @@ -207,22 +202,20 @@ impl Pallet { /// the beginning of the current round pub fn is_selected_relayer(relayer: &T::AccountId, is_initial: bool) -> bool { if is_initial { - >::get().binary_search(relayer).is_ok() + Self::initial_selected_relayers().contains(relayer) } else { - >::get().binary_search(relayer).is_ok() + Self::selected_relayers().contains(relayer) } } /// Compute majority based on the current selected relayers fn compute_majority() -> u32 { - let selected_relayers = >::get(); - let half = (selected_relayers.len() as u32) / 2; - return half + 1; + ((Self::selected_relayers().len() as u32) / 2) + 1 } - /// Verifies the existance of the given relayer and controller account. If it is both not bonded + /// Verifies the existence of the given relayer and controller account. If it is both not bonded /// yet, it will return an `Ok`, if not an `Error` will be returned. - fn verify_relayer_existance( + fn verify_relayer_existence( relayer: &T::AccountId, controller: &T::AccountId, ) -> Result<(), DispatchError> { @@ -234,7 +227,7 @@ impl Pallet { /// Sets the liveness of the requested relayer to `true`. pub fn pulse_heartbeat(relayer: &T::AccountId) -> bool { let session_index = T::ValidatorSet::session_index(); - if !>::get(session_index, relayer) { + if !Self::received_heartbeats(session_index, relayer) { >::insert(session_index, relayer, true); return true; } @@ -245,53 +238,50 @@ impl Pallet { /// `true` if the given relayer sent a heartbeat in the current session. pub fn is_heartbeat_pulsed(relayer: &T::AccountId) -> bool { let session_index = T::ValidatorSet::session_index(); - >::get(session_index, relayer) + Self::received_heartbeats(session_index, relayer) } /// Remove the given `relayer` from the `SelectedRelayers`. Returns `true` if the relayer has /// been removed. fn remove_from_selected_relayers(relayer: &T::AccountId) -> bool { - let mut selected_relayers = >::get(); - let prev_len = selected_relayers.len(); - selected_relayers.retain(|r| r != relayer); - let curr_len = selected_relayers.len(); - >::put(selected_relayers); - curr_len < prev_len + >::mutate(|selected_relayers| -> bool { + selected_relayers.remove(relayer) + }) } /// Add the given `relayer` to the `SelectedRelayers` - fn add_to_selected_relayers(relayer: T::AccountId) { - let mut selected_relayers = >::get(); - selected_relayers.try_push(relayer).expect("SelectedRelayers out of bound"); - selected_relayers.sort(); - >::put(selected_relayers); + fn add_to_selected_relayers(relayer: T::AccountId) -> Result { + >::try_mutate(|selected_relayers| -> Result { + Ok(selected_relayers.try_insert(relayer).map_err(|_| >::TooManyRelayers)?) + }) } /// Refresh the latest rounds cached selected relayers to the current state fn refresh_latest_cached_relayers() { - let round = >::get(); - let selected_relayers = >::get(); - let mut cached_selected_relayers = >::get(); - cached_selected_relayers.retain(|r| r.0 != round); - cached_selected_relayers.push((round, selected_relayers.into_inner())); - >::put(cached_selected_relayers); + let round = Self::round(); + >::mutate(|cached_selected_relayers| { + let selected_relayers = Self::selected_relayers(); + cached_selected_relayers + .entry(round) + .and_modify(|s| *s = selected_relayers.clone()) + .or_insert(selected_relayers); + }); } /// Refresh the latest rounds cached majority to the current state fn refresh_latest_cached_majority() { - let round = >::get(); - let majority = >::get(); - let mut cached_majority = >::get(); - cached_majority.retain(|r| r.0 != round); - cached_majority.push((round, majority)); - >::put(cached_majority); + let round = Self::round(); + >::mutate(|cached_majority| { + let majority = Self::majority(); + cached_majority.entry(round).and_modify(|m| *m = majority).or_insert(majority); + }); } /// Remove the given `acc` from the `RelayerPool`. The `is_relayer` parameter represents whether /// the given `acc` references to the relayer account or not. It it's not, it represents the /// bonded controller account. Returns `true` if the relayer has been removed. fn remove_from_relayer_pool(acc: &T::AccountId, is_relayer: bool) -> bool { - let mut pool = >::get(); + let mut pool = Self::relayer_pool(); let prev_len = pool.len(); pool.retain(|r| if is_relayer { r.relayer != *acc } else { r.controller != *acc }); let curr_len = pool.len(); @@ -300,29 +290,36 @@ impl Pallet { } /// Add the given `relayer` and `controller` pair to the `RelayerPool` - fn add_to_relayer_pool(relayer: T::AccountId, controller: T::AccountId) { - let mut pool = >::get(); - pool.try_push(Relayer { relayer, controller }) - .expect("RelayerPool out of bound"); - >::put(pool); + fn add_to_relayer_pool(relayer: T::AccountId, controller: T::AccountId) -> DispatchResult { + >::try_mutate(|pool| -> DispatchResult { + Ok(pool + .try_push(Relayer { relayer, controller }) + .map_err(|_| >::TooManyRelayers)?) + }) } /// Replace the `old` account that is used as a storage key for `SelectedRelayers` related /// values to the given `new` account. - fn replace_selected_relayers(old: &T::AccountId, new: &T::AccountId) { + fn replace_selected_relayers(old: &T::AccountId, new: &T::AccountId) -> DispatchResult { if Self::remove_from_selected_relayers(old) { - Self::add_to_selected_relayers(new.clone()); + Self::add_to_selected_relayers(new.clone())?; Self::refresh_latest_cached_relayers(); // replace pulsed heartbeats Self::replace_heartbeats(old, new); } + Ok(()) } /// Replace the `old` relayer account to the `new` relayer account from the `RelayerPool` - fn replace_relayer_pool(old: &T::AccountId, new: &T::AccountId, controller: T::AccountId) { + fn replace_relayer_pool( + old: &T::AccountId, + new: &T::AccountId, + controller: T::AccountId, + ) -> DispatchResult { if Self::remove_from_relayer_pool(old, true) { - Self::add_to_relayer_pool(new.clone(), controller); + Self::add_to_relayer_pool(new.clone(), controller)?; } + Ok(()) } /// Replace the `ReceivedHeartbeats` mapped key from `old` to `new` @@ -334,7 +331,10 @@ impl Pallet { /// Try to replace the bonded `old` relayer account to the given `new` relayer account. Returns /// `true` if the bonded relayer has been replaced. - pub fn replace_bonded_relayer(old: &T::AccountId, new: &T::AccountId) -> bool { + pub fn replace_bonded_relayer( + old: &T::AccountId, + new: &T::AccountId, + ) -> Result { if let Some(old_state) = >::take(old) { let controller = old_state.clone().controller; // replace bonded controller @@ -342,11 +342,11 @@ impl Pallet { // replace relayer state >::insert(new, old_state.clone()); // replace relayer pool - Self::replace_relayer_pool(&old, &new, controller.clone()); + Self::replace_relayer_pool(&old, &new, controller.clone())?; // replace selected relayers - Self::replace_selected_relayers(&old, &new); - return true; + Self::replace_selected_relayers(&old, &new)?; + return Ok(true); } - return false; + return Ok(false); } } diff --git a/pallets/relay-manager/src/pallet/mod.rs b/pallets/relay-manager/src/pallet/mod.rs index f375be67..2d2bd57d 100644 --- a/pallets/relay-manager/src/pallet/mod.rs +++ b/pallets/relay-manager/src/pallet/mod.rs @@ -1,23 +1,32 @@ mod impls; use crate::{ - IdentificationTuple, Relayer, RelayerMetadata, Releases, UnresponsivenessOffence, WeightInfo, + migrations, IdentificationTuple, Relayer, RelayerMetadata, UnresponsivenessOffence, WeightInfo, }; -use frame_support::{pallet_prelude::*, traits::ValidatorSetWithIdentification, Twox64Concat}; +use frame_support::{ + pallet_prelude::*, + traits::{OnRuntimeUpgrade, StorageVersion, ValidatorSetWithIdentification}, + Twox64Concat, +}; use frame_system::pallet_prelude::*; use bp_staking::{RoundIndex, MAX_AUTHORITIES}; use sp_runtime::Perbill; use sp_staking::{offence::ReportOffence, SessionIndex}; -use sp_std::prelude::*; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; #[frame_support::pallet] pub mod pallet { use super::*; + use frame_support::BoundedBTreeSet; + + /// The current storage version. + const STORAGE_VERSION: StorageVersion = StorageVersion::new(4); /// Pallet for relay manager #[pallet::pallet] + #[pallet::storage_version(STORAGE_VERSION)] pub struct Pallet(PhantomData); /// Configuration trait of this pallet @@ -62,6 +71,10 @@ pub mod pallet { NoWritingSameValue, /// Cannot set the value below one CannotSetBelowOne, + /// RelayerPool out of bound + TooManyRelayers, + /// SelectedRelayers out of bound + TooManySelectedRelayers, } #[pallet::event] @@ -87,10 +100,6 @@ pub mod pallet { HeartbeatSlashFractionSet { old: Perbill, new: Perbill }, } - #[pallet::storage] - /// Storage version of the pallet. - pub(crate) type StorageVersion = StorageValue<_, Releases, ValueQuery>; - #[pallet::storage] #[pallet::getter(fn storage_cache_lifetime)] /// The max storage lifetime for storage data to be cached @@ -128,27 +137,33 @@ pub mod pallet { #[pallet::getter(fn selected_relayers)] /// The active relayer set selected for the current round. This storage is sorted by address. pub type SelectedRelayers = - StorageValue<_, BoundedVec>, ValueQuery>; + StorageValue<_, BoundedBTreeSet>, ValueQuery>; #[pallet::storage] #[pallet::getter(fn initial_selected_relayers)] /// The active relayer set selected at the beginning of the current round. This storage is sorted by address. pub type InitialSelectedRelayers = - StorageValue<_, BoundedVec>, ValueQuery>; + StorageValue<_, BoundedBTreeSet>, ValueQuery>; #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn cached_selected_relayers)] /// The cached active relayer set selected from previous rounds. This storage is sorted by address. - pub type CachedSelectedRelayers = - StorageValue<_, Vec<(RoundIndex, Vec)>, ValueQuery>; + pub type CachedSelectedRelayers = StorageValue< + _, + BTreeMap>>, + ValueQuery, + >; #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn cached_initial_selected_relayers)] /// The cached active relayer set selected from the beginning of each previous rounds. This storage is sorted by address. - pub type CachedInitialSelectedRelayers = - StorageValue<_, Vec<(RoundIndex, Vec)>, ValueQuery>; + pub type CachedInitialSelectedRelayers = StorageValue< + _, + BTreeMap>>, + ValueQuery, + >; #[pallet::storage] #[pallet::getter(fn majority)] @@ -164,14 +179,14 @@ pub mod pallet { #[pallet::unbounded] #[pallet::getter(fn cached_majority)] /// The cached majority based on the active relayer set selected from previous rounds - pub type CachedMajority = StorageValue<_, Vec<(RoundIndex, u32)>, ValueQuery>; + pub type CachedMajority = StorageValue<_, BTreeMap, ValueQuery>; #[pallet::storage] #[pallet::unbounded] #[pallet::getter(fn cached_initial_majority)] /// The cached majority based on the active relayer set selected from the beginning of each /// previous rounds - pub type CachedInitialMajority = StorageValue<_, Vec<(RoundIndex, u32)>, ValueQuery>; + pub type CachedInitialMajority = StorageValue<_, BTreeMap, ValueQuery>; #[pallet::storage] #[pallet::getter(fn received_heartbeats)] @@ -196,6 +211,13 @@ pub mod pallet { /// The slash fraction for heartbeat offences pub type HeartbeatSlashFraction = StorageValue<_, Perbill, ValueQuery>; + #[pallet::hooks] + impl Hooks> for Pallet { + fn on_runtime_upgrade() -> Weight { + migrations::v4::MigrateToV4::::on_runtime_upgrade() + } + } + #[pallet::genesis_config] pub struct GenesisConfig {} @@ -209,7 +231,6 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { - StorageVersion::::put(Releases::V3_0_0); StorageCacheLifetime::::put(T::StorageCacheLifetimeInRounds::get()); IsHeartbeatOffenceActive::::put(T::IsHeartbeatOffenceActive::get()); HeartbeatSlashFraction::::put(T::DefaultHeartbeatSlashFraction::get()); @@ -225,7 +246,7 @@ pub mod pallet { origin: OriginFor, new: u32, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!(new >= 1u32, Error::::CannotSetBelowOne); let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); @@ -241,7 +262,7 @@ pub mod pallet { origin: OriginFor, is_active: bool, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; ensure!( is_active != >::get(), Error::::NoWritingSameValue @@ -258,7 +279,7 @@ pub mod pallet { origin: OriginFor, new: Perbill, ) -> DispatchResultWithPostInfo { - frame_system::ensure_root(origin)?; + ensure_root(origin)?; let old = >::get(); ensure!(old != new, Error::::NoWritingSameValue); >::put(new); @@ -276,7 +297,7 @@ pub mod pallet { ensure!(old != new, Error::::NoWritingSameValue); ensure!(Self::is_relayer(&old), Error::::RelayerDNE); ensure!(!Self::is_relayer(&new), Error::::RelayerAlreadyJoined); - ensure!(Self::replace_bonded_relayer(&old, &new), Error::::RelayerDNE); + ensure!(Self::replace_bonded_relayer(&old, &new)?, Error::::RelayerDNE); Self::deposit_event(Event::RelayerSet { old, new }); Ok(().into()) } diff --git a/precompiles/bfc-offences/src/lib.rs b/precompiles/bfc-offences/src/lib.rs index 85bb563a..3f4db5e8 100644 --- a/precompiles/bfc-offences/src/lib.rs +++ b/precompiles/bfc-offences/src/lib.rs @@ -7,7 +7,7 @@ use precompile_utils::prelude::*; use bp_staking::TierType; use fp_evm::PrecompileHandle; use sp_core::{H160, H256}; -use sp_std::{marker::PhantomData, vec, vec::Vec}; +use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, vec, vec::Vec}; mod types; use types::{ @@ -65,17 +65,12 @@ where let validator = Runtime::AddressMapping::into_account_id(validator.0); handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut validator_offence = ValidatorOffences::::default(); if let Some(offence) = OffencesOf::::validator_offences(&validator) { - let mut new = ValidatorOffence::::default(); - new.set_offence(validator, offence); - validator_offence.insert_offence(new); + Ok(ValidatorOffence::::set_offence(validator, offence).into()) } else { - validator_offence.insert_empty(validator); + Ok(ValidatorOffence::::set_empty(validator).into()) } - - Ok(validator_offence.into()) } #[precompile::public("validatorOffences(address[])")] @@ -86,26 +81,20 @@ where validators: Vec
, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_validators = validators - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_validators.len(); - unique_validators.sort(); - unique_validators.dedup(); - let current_len = unique_validators.len(); - if current_len < previous_len { + + let unique_validators: BTreeSet = validators + .iter() + .map(|validator| Runtime::AddressMapping::into_account_id(validator.0)) + .collect(); + if unique_validators.len() != validators.len() { return Err(RevertReason::custom("Duplicate validator address received").into()); } let mut validator_offences = ValidatorOffences::::default(); unique_validators.clone().into_iter().for_each(|v| { if let Some(offence) = OffencesOf::::validator_offences(&v) { - let mut new = ValidatorOffence::::default(); - new.set_offence(v, offence); - validator_offences.insert_offence(new); + validator_offences + .insert_offence(ValidatorOffence::::set_offence(v, offence)); } else { validator_offences.insert_empty(v); } diff --git a/precompiles/bfc-offences/src/types.rs b/precompiles/bfc-offences/src/types.rs index 073801f5..bea8e1d2 100644 --- a/precompiles/bfc-offences/src/types.rs +++ b/precompiles/bfc-offences/src/types.rs @@ -48,15 +48,37 @@ where } } + pub fn set_empty(validator: Runtime::AccountId) -> Self { + let mut offence = Self::default(); + offence.validator = Address(validator.into()); + offence + } + pub fn set_offence( - &mut self, validator: Runtime::AccountId, offence: ValidatorOffenceInfo>, - ) { - self.validator = Address(validator.into()); - self.latest_offence_round_index = offence.latest_offence_round_index; - self.latest_offence_session_index = offence.latest_offence_session_index; - self.offence_count = offence.offence_count; + ) -> Self { + Self { + validator: Address(validator.into()), + latest_offence_round_index: offence.latest_offence_round_index, + latest_offence_session_index: offence.latest_offence_session_index, + offence_count: offence.offence_count, + phantom: PhantomData, + } + } +} + +impl From> for EvmValidatorOffenceOf +where + Runtime: pallet_bfc_offences::Config, +{ + fn from(offence: ValidatorOffence) -> Self { + ( + offence.validator, + offence.latest_offence_round_index, + offence.latest_offence_session_index, + offence.offence_count, + ) } } @@ -104,20 +126,6 @@ where } } -impl From> for EvmValidatorOffenceOf -where - Runtime: pallet_bfc_offences::Config, -{ - fn from(offence: ValidatorOffences) -> Self { - ( - offence.validator[0], - offence.latest_offence_round_index[0], - offence.latest_offence_session_index[0], - offence.offence_count[0], - ) - } -} - impl From> for EvmValidatorOffencesOf where Runtime: pallet_bfc_offences::Config, diff --git a/precompiles/bfc-staking/src/lib.rs b/precompiles/bfc-staking/src/lib.rs index 3286e92a..47c50e15 100644 --- a/precompiles/bfc-staking/src/lib.rs +++ b/precompiles/bfc-staking/src/lib.rs @@ -2,7 +2,9 @@ use frame_support::{ dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}, + pallet_prelude::ConstU32, traits::Get, + BoundedBTreeSet, }; use pallet_bfc_staking::{Call as StakingCall, NominationChange, RewardDestination}; @@ -10,11 +12,13 @@ use pallet_evm::AddressMapping; use precompile_utils::prelude::*; -use bp_staking::{RoundIndex, TierType}; +use bp_staking::{RoundIndex, TierType, MAX_AUTHORITIES}; use fp_evm::PrecompileHandle; use sp_core::{H160, U256}; use sp_runtime::Perbill; -use sp_std::{convert::TryInto, marker::PhantomData, vec, vec::Vec}; +use sp_std::{ + collections::btree_set::BTreeSet, convert::TryInto, marker::PhantomData, vec, vec::Vec, +}; mod types; use types::{ @@ -118,22 +122,9 @@ where tier: u32, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_candidates = candidates - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_candidates.len(); - unique_candidates.sort(); - unique_candidates.dedup(); - let current_len = unique_candidates.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); - } Ok(Self::compare_selected_candidates( - candidates, + Self::get_unique_candidates(&candidates)?, match tier { 2 => TierType::Full, 1 => TierType::Basic, @@ -156,22 +147,9 @@ where tier: u32, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_candidates = candidates - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_candidates.len(); - unique_candidates.sort(); - unique_candidates.dedup(); - let current_len = unique_candidates.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); - } Ok(Self::compare_selected_candidates( - candidates, + Self::get_unique_candidates(&candidates)?, match tier { 2 => TierType::Full, 1 => TierType::Basic, @@ -197,31 +175,8 @@ where let candidate = Runtime::AddressMapping::into_account_id(candidate.0); handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut is_previous_selected_candidate: bool = false; - let previous_selected_candidates = >::cached_selected_candidates(); - - let cached_len = previous_selected_candidates.len(); - if cached_len > 0 { - let head_selected = &previous_selected_candidates[0]; - let tail_selected = &previous_selected_candidates[cached_len - 1]; - // out of round index - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - 'outer: for selected_candidates in previous_selected_candidates { - if round_index == selected_candidates.0 { - for selected_candidate in selected_candidates.1 { - if candidate == selected_candidate { - is_previous_selected_candidate = true; - break 'outer; - } - } - break; - } - } - } - Ok(is_previous_selected_candidate) + Ok(Self::get_previous_selected_candidates(&round_index)?.contains(&candidate)) } /// Verifies if each of the address in the given `candidates` parameter @@ -238,51 +193,11 @@ where candidates: Vec
, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_candidates = candidates - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - let previous_len = unique_candidates.len(); - unique_candidates.sort(); - unique_candidates.dedup(); - let current_len = unique_candidates.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); - } - let mut is_previous_selected_candidates: bool = false; - - if candidates.len() > 0 { - let previous_selected_candidates = >::cached_selected_candidates(); - - let cached_len = previous_selected_candidates.len(); - if cached_len > 0 { - let head_selected = &previous_selected_candidates[0]; - let tail_selected = &previous_selected_candidates[cached_len - 1]; - - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - 'outer: for selected_candidates in previous_selected_candidates { - if round_index == selected_candidates.0 { - let mutated_candidates: Vec
= selected_candidates - .1 - .into_iter() - .map(|address| Address(address.into())) - .collect(); - for candidate in candidates { - if !mutated_candidates.contains(&candidate) { - break 'outer; - } - } - is_previous_selected_candidates = true; - break; - } - } - } - } - Ok(is_previous_selected_candidates) + let previous_selected_candidates = Self::get_previous_selected_candidates(&round_index)?; + Ok(Self::get_unique_candidates(&candidates)? + .iter() + .all(|candidate| previous_selected_candidates.contains(candidate))) } // Common storage getters @@ -400,26 +315,12 @@ where round_index: RoundIndex, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut result: u32 = 0; - let previous_majority = >::cached_majority(); - let cached_len = previous_majority.len(); - if cached_len > 0 { - let head_majority = &previous_majority[0]; - let tail_majority = &previous_majority[cached_len - 1]; - - if round_index < head_majority.0 || round_index > tail_majority.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - for majority in previous_majority { - if round_index == majority.0 { - result = majority.1; - break; - } - } + if let Some(previous_majority) = >::cached_majority().get(&round_index) { + Ok(previous_majority.clone()) + } else { + Err(RevertReason::read_out_of_bounds("round_index").into()) } - - Ok(result) } /// Returns total points awarded to all validators in the given `round_index` round @@ -724,28 +625,11 @@ where round_index: RoundIndex, ) -> EvmResult> { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut result: Vec
= vec![]; - let previous_selected_candidates = >::cached_selected_candidates(); - - let cached_len = previous_selected_candidates.len(); - if cached_len > 0 { - let head_selected = &previous_selected_candidates[0]; - let tail_selected = &previous_selected_candidates[cached_len - 1]; - // out of round index - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - for candidates in previous_selected_candidates { - if round_index == candidates.0 { - result = - candidates.1.into_iter().map(|address| Address(address.into())).collect(); - break; - } - } - } - - Ok(result) + Ok(Self::get_previous_selected_candidates(&round_index)? + .into_iter() + .map(|candidate| Address(candidate.into())) + .collect::>()) } /// Returns a vector of the validator candidate addresses @@ -756,7 +640,7 @@ where fn candidate_pool(handle: &mut impl PrecompileHandle) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let candidate_pool = >::candidate_pool(); + let candidate_pool = >::get_sorted_candidates(); let mut candidates: Vec
= vec![]; let mut bonds: Vec = vec![]; @@ -1075,7 +959,7 @@ where handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; let result = if let Some(state) = >::nominator_state(&nominator) { - let nominator_nomination_count: u32 = state.nominations.0.len() as u32; + let nominator_nomination_count: u32 = state.nominations.len() as u32; nominator_nomination_count } else { 0u32 @@ -1444,45 +1328,53 @@ where // Util methods + fn get_unique_candidates(candidates: &Vec
) -> EvmResult> { + let unique_candidates: BTreeSet = candidates + .iter() + .map(|address| Runtime::AddressMapping::into_account_id(address.0)) + .collect(); + if unique_candidates.len() != candidates.len() { + return Err(RevertReason::custom("Duplicate candidate address received").into()); + } + + Ok(unique_candidates) + } + + fn get_previous_selected_candidates( + round_index: &RoundIndex, + ) -> EvmResult>> { + let previous_selected_candidates = >::cached_selected_candidates(); + if let Some(previous_selected_candidates) = previous_selected_candidates.get(round_index) { + Ok(previous_selected_candidates.clone()) + } else { + Err(RevertReason::read_out_of_bounds("round_index").into()) + } + } + fn compare_selected_candidates( - candidates: Vec
, + candidates: BTreeSet, tier: TierType, is_complete: bool, ) -> bool { - let mut result: bool = true; - if candidates.len() < 1 { - result = false; - } else { - let raw_selected_candidates = match tier { - TierType::Full => StakingOf::::selected_full_candidates().into_inner(), - TierType::Basic => StakingOf::::selected_basic_candidates().into_inner(), - TierType::All => StakingOf::::selected_candidates().into_inner(), + if candidates.is_empty() { + return false; + } + + let selected_candidates: BoundedBTreeSet> = + match tier { + TierType::Full => StakingOf::::selected_full_candidates(), + TierType::Basic => StakingOf::::selected_basic_candidates(), + TierType::All => StakingOf::::selected_candidates(), }; - let selected_candidates = raw_selected_candidates - .into_iter() - .map(|address| Address(address.into())) - .collect::>(); - if is_complete { - if selected_candidates.len() != candidates.len() { - result = false; - } else { - for selected_candidate in &selected_candidates { - if !candidates.contains(&selected_candidate) { - result = false; - break; - } - } - } - } else { - for candidate in &candidates { - if !selected_candidates.contains(&candidate) { - result = false; - break; - } - } + + return if is_complete { + if selected_candidates.len() != candidates.len() { + return false; } - } - result + candidates.iter().all(|candidate| selected_candidates.contains(candidate)) + } else { + candidates.iter().all(|candidate| selected_candidates.contains(candidate)) + }; } fn u256_array_to_amount_array(values: Vec) -> MayRevert>> { diff --git a/precompiles/bfc-staking/src/types.rs b/precompiles/bfc-staking/src/types.rs index e6d86688..091082bf 100644 --- a/precompiles/bfc-staking/src/types.rs +++ b/precompiles/bfc-staking/src/types.rs @@ -483,13 +483,13 @@ where } pub fn set_state(&mut self, state: Nominator>) { - for nomination in state.nominations.0 { - self.candidates.push(Address(nomination.owner.into())); - self.nominations.push(nomination.amount); - } - for nomination in state.initial_nominations.0 { - self.initial_nominations.push(nomination.amount); - } + state.nominations.into_iter().for_each(|(owner, amount)| { + self.candidates.push(Address(owner.into())); + self.nominations.push(amount); + }); + state.initial_nominations.into_iter().for_each(|(_, amount)| { + self.initial_nominations.push(amount); + }); self.total = state.total; self.request_revocations_count = state.requests.revocations_count.into(); @@ -505,9 +505,10 @@ where RewardDestination::Account => 1u32.into(), }; - for awarded_token in state.awarded_tokens_per_candidate.0 { - self.awarded_tokens_per_candidate.push(awarded_token.amount); - } + state.awarded_tokens_per_candidate.iter().for_each(|(_, amount)| { + self.awarded_tokens_per_candidate.push(*amount); + }); + self.awarded_tokens = state.awarded_tokens; } diff --git a/precompiles/relay-manager/src/lib.rs b/precompiles/relay-manager/src/lib.rs index d7fa127b..40c1f963 100644 --- a/precompiles/relay-manager/src/lib.rs +++ b/precompiles/relay-manager/src/lib.rs @@ -1,15 +1,17 @@ #![cfg_attr(not(feature = "std"), no_std)] use frame_support::dispatch::{Dispatchable, GetDispatchInfo, PostDispatchInfo}; +use frame_support::pallet_prelude::ConstU32; +use frame_support::BoundedBTreeSet; use pallet_evm::AddressMapping; use pallet_relay_manager::Call as RelayManagerCall; use precompile_utils::prelude::*; -use bp_staking::RoundIndex; +use bp_staking::{RoundIndex, MAX_AUTHORITIES}; use sp_core::{H160, H256, U256}; -use sp_std::{marker::PhantomData, vec, vec::Vec}; +use sp_std::{collections::btree_set::BTreeSet, marker::PhantomData, vec, vec::Vec}; mod types; use types::{EvmRelayerStateOf, EvmRelayerStatesOf, RelayManagerOf, RelayerState, RelayerStates}; @@ -58,34 +60,41 @@ where Ok(is_selected_relayer) } - #[precompile::public("isRelayers(address[])")] - #[precompile::public("is_relayers(address[])")] - #[precompile::view] - fn is_relayers(handle: &mut impl PrecompileHandle, relayers: Vec
) -> EvmResult { - handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_relayers = relayers - .clone() - .into_iter() + fn dedup_sort_validate( + relayers: &Vec
, + is_complete: bool, + validate: F, + ) -> EvmResult + where + F: FnMut(&Runtime::AccountId) -> bool, + { + let unique_relayers: BTreeSet = relayers + .iter() .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_relayers.len(); - unique_relayers.sort(); - unique_relayers.dedup(); - let current_len = unique_relayers.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); + .collect(); + if unique_relayers.len() != relayers.len() { + return Err(RevertReason::custom("Duplicate relayer address received").into()); } - let mut is_relayers = true; - for relayer in unique_relayers { - if !RelayManagerOf::::is_relayer(&relayer) { - is_relayers = false; - break; + if is_complete { + let selected_relayers = RelayManagerOf::::selected_relayers(); + if selected_relayers.len() != unique_relayers.len() { + return Ok(false); } } - Ok(is_relayers) + Ok(unique_relayers.iter().all(validate)) + } + + #[precompile::public("isRelayers(address[])")] + #[precompile::public("is_relayers(address[])")] + #[precompile::view] + fn is_relayers(handle: &mut impl PrecompileHandle, relayers: Vec
) -> EvmResult { + handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; + + Self::dedup_sort_validate(&relayers, false, |relayer| { + RelayManagerOf::::is_relayer(relayer) + }) } #[precompile::public("isSelectedRelayers(address[],bool)")] @@ -97,29 +106,10 @@ where is_initial: bool, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_relayers = relayers - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_relayers.len(); - unique_relayers.sort(); - unique_relayers.dedup(); - let current_len = unique_relayers.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); - } - let mut is_relayers = true; - for relayer in unique_relayers { - if !RelayManagerOf::::is_selected_relayer(&relayer, is_initial) { - is_relayers = false; - break; - } - } - - Ok(is_relayers) + Self::dedup_sort_validate(&relayers, false, |relayer| { + RelayManagerOf::::is_selected_relayer(relayer, is_initial) + }) } #[precompile::public("isCompleteSelectedRelayers(address[],bool)")] @@ -131,34 +121,27 @@ where is_initial: bool, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_relayers = relayers - .clone() - .into_iter() - .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_relayers.len(); - unique_relayers.sort(); - unique_relayers.dedup(); - let current_len = unique_relayers.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); - } - let mut is_relayers = true; - let selected_relayers = RelayManagerOf::::selected_relayers(); - if selected_relayers.len() != unique_relayers.len() { - is_relayers = false; + Self::dedup_sort_validate(&relayers, true, |relayer| { + RelayManagerOf::::is_selected_relayer(relayer, is_initial) + }) + } + + fn get_previous_selected_relayers( + round_index: &RoundIndex, + is_initial: bool, + ) -> EvmResult>> { + let previous_selected_relayers = if is_initial { + RelayManagerOf::::cached_initial_selected_relayers() } else { - for relayer in unique_relayers { - if !RelayManagerOf::::is_selected_relayer(&relayer, is_initial) { - is_relayers = false; - break; - } - } - } + RelayManagerOf::::cached_selected_relayers() + }; - Ok(is_relayers) + if let Some(previous_selected_relayers) = previous_selected_relayers.get(round_index) { + return Ok(previous_selected_relayers.clone()); + } else { + Err(RevertReason::read_out_of_bounds("round_index").into()) + } } #[precompile::public("isPreviousSelectedRelayer(uint256,address,bool)")] @@ -173,35 +156,8 @@ where let relayer = Runtime::AddressMapping::into_account_id(relayer.0); handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut result: bool = false; - let previous_selected_relayers = match is_initial { - true => RelayManagerOf::::cached_initial_selected_relayers(), - false => RelayManagerOf::::cached_selected_relayers(), - }; - - let cached_len = previous_selected_relayers.len(); - if cached_len > 0 { - let head_selected = &previous_selected_relayers[0]; - let tail_selected = &previous_selected_relayers[cached_len - 1]; - - // out of round index - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - 'outer: for selected_relayers in previous_selected_relayers { - if round_index == selected_relayers.0 { - for selected_relayer in selected_relayers.1 { - if relayer == selected_relayer { - result = true; - break 'outer; - } - } - break; - } - } - } - Ok(result) + Ok(Self::get_previous_selected_relayers(&round_index, is_initial)?.contains(&relayer)) } #[precompile::public("isPreviousSelectedRelayers(uint256,address[],bool)")] @@ -214,55 +170,25 @@ where is_initial: bool, ) -> EvmResult { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut unique_relayers = relayers - .clone() - .into_iter() + + let unique_relayers = relayers + .iter() .map(|address| Runtime::AddressMapping::into_account_id(address.0)) - .collect::>(); - - let previous_len = unique_relayers.len(); - unique_relayers.sort(); - unique_relayers.dedup(); - let current_len = unique_relayers.len(); - if current_len < previous_len { - return Err(RevertReason::custom("Duplicate candidate address received").into()); + .collect::>(); + if unique_relayers.len() != relayers.len() { + return Err(RevertReason::custom("Duplicate relayer address received").into()); } - let mut result: bool = false; - if unique_relayers.len() > 0 { - let previous_selected_relayers = match is_initial { - true => RelayManagerOf::::cached_initial_selected_relayers(), - false => RelayManagerOf::::cached_selected_relayers(), - }; - - let cached_len = previous_selected_relayers.len(); - if cached_len > 0 { - let head_selected = &previous_selected_relayers[0]; - let tail_selected = &previous_selected_relayers[cached_len - 1]; - - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - 'outer: for selected_relayers in previous_selected_relayers { - if round_index == selected_relayers.0 { - let mutated_relayers: Vec
= selected_relayers - .1 - .into_iter() - .map(|address| Address(address.into())) - .collect(); - for relayer in relayers { - if !mutated_relayers.contains(&relayer) { - break 'outer; - } - } - result = true; - break; - } - } - } + let previous_selected_relayers = + Self::get_previous_selected_relayers(&round_index, is_initial)?; + if previous_selected_relayers.is_empty() { + return Ok(false); } - Ok(result) + Ok(relayers.iter().all(|relayer| { + previous_selected_relayers + .contains(&Runtime::AddressMapping::into_account_id(relayer.0)) + })) } #[precompile::public("isHeartbeatPulsed(address)")] @@ -312,31 +238,14 @@ where is_initial: bool, ) -> EvmResult> { handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let previous_selected_relayers = match is_initial { - true => RelayManagerOf::::cached_initial_selected_relayers(), - false => RelayManagerOf::::cached_selected_relayers(), - }; - let mut result: Vec
= vec![]; - let cached_len = previous_selected_relayers.len(); - if cached_len > 0 { - let head_selected = &previous_selected_relayers[0]; - let tail_selected = &previous_selected_relayers[cached_len - 1]; + let previous_selected_relayers = + Self::get_previous_selected_relayers(&round_index, is_initial)?; - // out of round index - if round_index < head_selected.0 || round_index > tail_selected.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - for relayers in previous_selected_relayers { - if round_index == relayers.0 { - result = - relayers.1.into_iter().map(|address| Address(address.into())).collect(); - break; - } - } - } - - Ok(result) + Ok(previous_selected_relayers + .into_iter() + .map(|account_id| Address(account_id.into())) + .collect()) } #[precompile::public("relayerPool()")] @@ -383,24 +292,11 @@ where false => RelayManagerOf::::cached_majority(), }; - let mut result = 0u32; - let cached_len = cached_majority.len(); - if cached_len > 0 { - let head_majority = &cached_majority[0]; - let tail_majority = &cached_majority[cached_len - 1]; - - if round_index < head_majority.0 || round_index > tail_majority.0 { - return Err(RevertReason::read_out_of_bounds("round_index").into()); - } - for majority in cached_majority { - if round_index == majority.0 { - result = majority.1; - break; - } - } + if let Some(majority) = cached_majority.get(&round_index) { + Ok(majority.clone().into()) + } else { + Err(RevertReason::read_out_of_bounds("round_index").into()) } - - Ok(result.into()) } #[precompile::public("latestRound()")] @@ -423,17 +319,14 @@ where let relayer = Runtime::AddressMapping::into_account_id(relayer.0); handle.record_cost(RuntimeHelper::::db_read_gas_cost())?; - let mut relayer_state = RelayerStates::::default(); if let Some(state) = RelayManagerOf::::relayer_state(&relayer) { let mut new = RelayerState::::default(); new.set_state(relayer, state); - relayer_state.insert_state(new); + Ok(new.into()) } else { - relayer_state.insert_empty(); + Ok(RelayerState::::default().into()) } - - Ok(relayer_state.into()) } #[precompile::public("relayerStates()")] diff --git a/precompiles/relay-manager/src/types.rs b/precompiles/relay-manager/src/types.rs index eab243f1..1c1beaac 100644 --- a/precompiles/relay-manager/src/types.rs +++ b/precompiles/relay-manager/src/types.rs @@ -52,6 +52,15 @@ where } } +impl From> for EvmRelayerStateOf +where + Runtime: pallet_relay_manager::Config, +{ + fn from(state: RelayerState) -> Self { + (state.relayer, state.controller, state.status) + } +} + /// EVM struct for relayer states pub struct RelayerStates { /// This relayer's account @@ -86,15 +95,6 @@ where } } -impl From> for EvmRelayerStateOf -where - Runtime: pallet_relay_manager::Config, -{ - fn from(state: RelayerStates) -> Self { - (state.relayer[0], state.controller[0], state.status[0]) - } -} - impl From> for EvmRelayerStatesOf where Runtime: pallet_relay_manager::Config, diff --git a/primitives/bfc-staking/src/traits.rs b/primitives/bfc-staking/src/traits.rs index 753fb294..ba4e9eaf 100644 --- a/primitives/bfc-staking/src/traits.rs +++ b/primitives/bfc-staking/src/traits.rs @@ -1,6 +1,7 @@ #![cfg_attr(not(feature = "std"), no_std)] -use crate::{Offence, RoundIndex, TierType}; +use crate::{Offence, RoundIndex, TierType, MAX_AUTHORITIES}; +use frame_support::{pallet_prelude::ConstU32, BoundedBTreeSet}; use sp_runtime::{DispatchError, Perbill}; use sp_std::vec::Vec; @@ -17,7 +18,10 @@ pub trait RelayManager { fn refresh_selected_relayers(round: RoundIndex, selected_candidates: Vec); /// Refresh the `CachedSelectedRelayers` based on the new selected relayers - fn refresh_cached_selected_relayers(round: RoundIndex, relayers: Vec); + fn refresh_cached_selected_relayers( + round: RoundIndex, + relayers: BoundedBTreeSet>, + ); /// Refresh the `Majority` and `CachedMajority` of the selected relayers fn refresh_majority(round: RoundIndex); diff --git a/runtime/dev/src/lib.rs b/runtime/dev/src/lib.rs index 984762e2..9ba5a695 100644 --- a/runtime/dev/src/lib.rs +++ b/runtime/dev/src/lib.rs @@ -140,7 +140,7 @@ pub const VERSION: RuntimeVersion = RuntimeVersion { // The version of the authorship interface. authoring_version: 1, // The version of the runtime spec. - spec_version: 305, + spec_version: 306, // The version of the implementation of the spec. impl_version: 1, // A list of supported runtime APIs along with their versions. @@ -713,8 +713,6 @@ parameter_types! { pub const DefaultMaxSelectedFullCandidates: u32 = 10; /// Default maximum basicvalidators selected per round, default at genesis. pub const DefaultMaxSelectedBasicCandidates: u32 = 10; - /// Default minimum validators selected per round, default at genesis. - pub const DefaultMinSelectedCandidates: u32 = 1; /// Maximum top nominations per candidate. pub const MaxTopNominationsPerCandidate: u32 = 10; /// Maximum bottom nominations per candidate. @@ -760,7 +758,6 @@ impl pallet_bfc_staking::Config for Runtime { type RewardPaymentDelay = RewardPaymentDelay; type DefaultMaxSelectedFullCandidates = DefaultMaxSelectedFullCandidates; type DefaultMaxSelectedBasicCandidates = DefaultMaxSelectedBasicCandidates; - type DefaultMinSelectedCandidates = DefaultMinSelectedCandidates; type MaxTopNominationsPerCandidate = MaxTopNominationsPerCandidate; type MaxBottomNominationsPerCandidate = MaxBottomNominationsPerCandidate; type MaxNominationsPerNominator = MaxNominationsPerNominator; diff --git a/runtime/mainnet/src/lib.rs b/runtime/mainnet/src/lib.rs index ee238391..22053fa8 100644 --- a/runtime/mainnet/src/lib.rs +++ b/runtime/mainnet/src/lib.rs @@ -713,8 +713,6 @@ parameter_types! { pub const DefaultMaxSelectedFullCandidates: u32 = 20; /// Default maximum basic validators selected per round, default at genesis. pub const DefaultMaxSelectedBasicCandidates: u32 = 200; - /// Default minimum validators selected per round, default at genesis. - pub const DefaultMinSelectedCandidates: u32 = 1; /// Maximum top nominations per candidate. pub const MaxTopNominationsPerCandidate: u32 = 100; /// Maximum bottom nominations per candidate. @@ -760,7 +758,6 @@ impl pallet_bfc_staking::Config for Runtime { type RewardPaymentDelay = RewardPaymentDelay; type DefaultMaxSelectedFullCandidates = DefaultMaxSelectedFullCandidates; type DefaultMaxSelectedBasicCandidates = DefaultMaxSelectedBasicCandidates; - type DefaultMinSelectedCandidates = DefaultMinSelectedCandidates; type MaxTopNominationsPerCandidate = MaxTopNominationsPerCandidate; type MaxBottomNominationsPerCandidate = MaxBottomNominationsPerCandidate; type MaxNominationsPerNominator = MaxNominationsPerNominator; diff --git a/runtime/testnet/src/lib.rs b/runtime/testnet/src/lib.rs index ae8e501d..13585072 100644 --- a/runtime/testnet/src/lib.rs +++ b/runtime/testnet/src/lib.rs @@ -719,8 +719,6 @@ parameter_types! { pub const DefaultMaxSelectedFullCandidates: u32 = 30; /// Default maximum basic validators selected per round, default at genesis. pub const DefaultMaxSelectedBasicCandidates: u32 = 170; - /// Default minimum validators selected per round, default at genesis. - pub const DefaultMinSelectedCandidates: u32 = 1; /// Maximum top nominations per candidate. pub const MaxTopNominationsPerCandidate: u32 = 100; /// Maximum bottom nominations per candidate. @@ -766,7 +764,6 @@ impl pallet_bfc_staking::Config for Runtime { type RewardPaymentDelay = RewardPaymentDelay; type DefaultMaxSelectedFullCandidates = DefaultMaxSelectedFullCandidates; type DefaultMaxSelectedBasicCandidates = DefaultMaxSelectedBasicCandidates; - type DefaultMinSelectedCandidates = DefaultMinSelectedCandidates; type MaxTopNominationsPerCandidate = MaxTopNominationsPerCandidate; type MaxBottomNominationsPerCandidate = MaxBottomNominationsPerCandidate; type MaxNominationsPerNominator = MaxNominationsPerNominator; diff --git a/tests/tests/pallets/test_bfc_offences.ts b/tests/tests/pallets/test_bfc_offences.ts index 2e8a4eba..d60d7838 100644 --- a/tests/tests/pallets/test_bfc_offences.ts +++ b/tests/tests/pallets/test_bfc_offences.ts @@ -217,8 +217,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => const candidateState = rawCandidateState.unwrap().toJSON(); const selfBondBeforeSlash = candidateState.bond; - const rawTreauryId: any = await context.polkadotApi.query.treasury.treasuryId(); - const treasuryId = rawTreauryId.toJSON(); + const rawTreasuryId: any = await context.polkadotApi.query.treasury.treasuryId(); + const treasuryId = rawTreasuryId.toJSON(); const potBalanceBefore = new BigNumber((await context.web3.eth.getBalance(treasuryId)).toString()); let offenceLength = 1; @@ -252,11 +252,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === baltathar.address) { - isCandidateFound = true; - break; - } + if (candidatePool[baltathar.address]) { + isCandidateFound = true; } expect(isCandidateFound).equal(false); @@ -276,15 +273,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => const rawCachedSelectedCandidates: any = await context.polkadotApi.query.bfcStaking.cachedSelectedCandidates(); const cachedSelectedCandidates = rawCachedSelectedCandidates.toJSON(); let isCachedCandidate = false; - for (const cache of cachedSelectedCandidates) { - if (cache[0] === currentRound) { - for (const candidate of cache[1]) { - if (candidate === baltathar.address) { - isCachedCandidate = true; - break; - } - } - } + if (cachedSelectedCandidates[currentRound].includes(baltathar.address)) { + isCachedCandidate = true; } expect(isCachedCandidate).equal(false); @@ -296,8 +286,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => expect(new BigNumber(candidateState.votingPower).eq(new BigNumber(selfBondAfterSlash))).equal(true); // check treasury pot - const rawTreauryId: any = await context.polkadotApi.query.treasury.treasuryId(); - const treasuryId = rawTreauryId.toJSON(); + const rawTreasuryId: any = await context.polkadotApi.query.treasury.treasuryId(); + const treasuryId = rawTreasuryId.toJSON(); const potBalance = new BigNumber((await context.web3.eth.getBalance(treasuryId)).toString()); expect(potBalance.toFixed()).equal(new BigNumber(25).multipliedBy(10 ** 18).plus(potBalanceBefore).toFixed()); @@ -395,11 +385,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === baltathar.address) { - isCandidateFound = true; - break; - } + if (candidatePool[baltathar.address]) { + isCandidateFound = true; } expect(isCandidateFound).equal(true); }); @@ -419,11 +406,8 @@ describeDevNode('pallet_bfc_offences - simple validator offences', (context) => const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === alith.address) { - isCandidateFound = true; - break; - } + if (candidatePool[alith.address]) { + isCandidateFound = true; } expect(isCandidateFound).equal(false); @@ -585,11 +569,8 @@ describeDevNode('pallet_bfc_offences - update bond less pending request #1', (co const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === baltathar.address) { - isCandidateFound = true; - break; - } + if (candidatePool[baltathar.address]) { + isCandidateFound = true; } expect(isCandidateFound).equal(false); @@ -609,15 +590,8 @@ describeDevNode('pallet_bfc_offences - update bond less pending request #1', (co const rawCachedSelectedCandidates: any = await context.polkadotApi.query.bfcStaking.cachedSelectedCandidates(); const cachedSelectedCandidates = rawCachedSelectedCandidates.toJSON(); let isCachedCandidate = false; - for (const cache of cachedSelectedCandidates) { - if (cache[0] === currentRound) { - for (const candidate of cache[1]) { - if (candidate === baltathar.address) { - isCachedCandidate = true; - break; - } - } - } + if (cachedSelectedCandidates[currentRound].includes(baltathar.address)) { + isCachedCandidate = true; } expect(isCachedCandidate).equal(false); diff --git a/tests/tests/pallets/test_bfc_staking.ts b/tests/tests/pallets/test_bfc_staking.ts index 5126c72e..e6d043b6 100644 --- a/tests/tests/pallets/test_bfc_staking.ts +++ b/tests/tests/pallets/test_bfc_staking.ts @@ -66,14 +66,11 @@ describeDevNode('pallet_bfc_staking - set controller', (context) => { const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === newAlith.address) { - isCandidateFound = true; - break; - } + if (candidatePool[newAlith.address]) { + isCandidateFound = true; } expect(isCandidateFound).equal(true); - expect(candidatePool.length).equal(1); + expect(Object.keys(candidatePool).length).equal(1); const rawSelectedCandidates: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); const selectedCandidates = rawSelectedCandidates.toJSON(); @@ -103,25 +100,11 @@ describeDevNode('pallet_bfc_staking - set controller', (context) => { const rawNominatorState: any = await context.polkadotApi.query.bfcStaking.nominatorState(charleth.address); const nominatorState = rawNominatorState.unwrap().toJSON(); - expect(nominatorState.nominations.length).equal(1); - expect(nominatorState.initialNominations.length).equal(1); - let isNominationFound = false; - for (const nomination of nominatorState.nominations) { - if (nomination.owner === newAlith.address) { - isNominationFound = true; - break; - } - } - expect(isNominationFound).equal(true); - let isInitialNominationFound = false; - for (const nomination of nominatorState.nominations) { - if (nomination.owner === newAlith.address) { - isInitialNominationFound = true; - break; - } - } - expect(isInitialNominationFound).equal(true); + expect(Object.keys(nominatorState.nominations).length).equal(1); + expect(Object.keys(nominatorState.initialNominations).length).equal(1); + expect(nominatorState.nominations).has.key(newAlith.address); + expect(nominatorState.initialNominations).has.key(newAlith.address); expect(nominatorState.requests.revocationsCount).equal(1); expect(nominatorState.requests.requests).has.key(newAlith.address); expect(Object.keys(nominatorState.requests.requests).length).equal(1); @@ -141,9 +124,10 @@ describeDevNode('pallet_bfc_staking - genesis', (context) => { }); it('should include candidate to pool', async function () { - const candidates = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidates: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidates = rawCandidates.toJSON(); expect(candidates).to.not.empty; - expect(candidates[0].toHuman()!['owner'].toLowerCase()).equal(alith.address.toLowerCase()); + expect(Object.keys(candidates).includes(alith.address)); }); it('should include validator as selected candidate', async function () { @@ -793,86 +777,13 @@ describeDevNode('pallet_bfc_staking - validator selection', (context) => { await context.createBlock(); const rawSelectedCandidates: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); - expect(rawSelectedCandidates.length).equal(1); + expect(rawSelectedCandidates.toJSON().length).equal(1); const rawSelectedFullCandidates: any = await context.polkadotApi.query.bfcStaking.selectedFullCandidates(); - expect(rawSelectedFullCandidates.length).equal(1); + expect(rawSelectedFullCandidates.toJSON().length).equal(1); const rawSelectedBasicCandidates: any = await context.polkadotApi.query.bfcStaking.selectedBasicCandidates(); - expect(rawSelectedBasicCandidates.length).equal(0); - }); - - it('should successfully update min total selected', async function () { - const min = 3; - - await context.polkadotApi.tx.sudo.sudo( - context.polkadotApi.tx.bfcStaking.setMinTotalSelected(min) - ).signAndSend(alith); - await context.createBlock(); - - const rawMinTotalSelected: any = await context.polkadotApi.query.bfcStaking.minTotalSelected(); - const minTotalSelected = rawMinTotalSelected.toNumber(); - expect(minTotalSelected).equal(min); - }); - - it('should fail due to non-root origin', async function () { - const max = 2; - - await context.polkadotApi.tx.sudo.sudo( - context.polkadotApi.tx.bfcStaking.setMinTotalSelected(max) - ).signAndSend(baltathar); - const block = await context.createBlock(); - - const success = await isEventTriggered( - context, - block.block.hash, - [ - { method: 'ExtrinsicFailed', section: 'system' }, - ], - ); - expect(success).equal(true); - }); - - it('should fail due to min above max', async function () { - const prevMin = 3; - const afterMin = 100; - - await context.polkadotApi.tx.sudo.sudo( - context.polkadotApi.tx.bfcStaking.setMinTotalSelected(afterMin) - ).signAndSend(alith); - await context.createBlock(); - - const rawMinTotalSelected: any = await context.polkadotApi.query.bfcStaking.minTotalSelected(); - const minTotalSelected = rawMinTotalSelected.toNumber(); - expect(minTotalSelected).equal(prevMin); - }); - - it('should fail due to full max below min', async function () { - const prevMax = 15; - const afterMax = 1; - - await context.polkadotApi.tx.sudo.sudo( - context.polkadotApi.tx.bfcStaking.setMaxFullSelected(afterMax) - ).signAndSend(alith); - await context.createBlock(); - - const rawMaxFullSelected: any = await context.polkadotApi.query.bfcStaking.maxFullSelected(); - const maxFullSelected = rawMaxFullSelected.toNumber(); - expect(maxFullSelected).equal(prevMax); - }); - - it('should fail due to basic max below min', async function () { - const prevMax = 15; - const afterMax = 1; - - await context.polkadotApi.tx.sudo.sudo( - context.polkadotApi.tx.bfcStaking.setMaxBasicSelected(afterMax) - ).signAndSend(alith); - await context.createBlock(); - - const rawMaxBasicSelected: any = await context.polkadotApi.query.bfcStaking.maxBasicSelected(); - const maxBasicSelected = rawMaxBasicSelected.toNumber(); - expect(maxBasicSelected).equal(prevMax); + expect(rawSelectedBasicCandidates.toJSON().length).equal(0); }); }); @@ -976,7 +887,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { const candidatesBefore = rawCandidatesBefore.toJSON(); await context.polkadotApi.tx.bfcStaking - .joinCandidates(baltathar.address, baltatharRelayer.address, stake.toFixed(), candidatesBefore.length) + .joinCandidates(baltathar.address, baltatharRelayer.address, stake.toFixed(), Object.keys(candidatesBefore).length) .signAndSend(baltatharStash, { nonce: -1 }); await context.createBlock(); @@ -993,8 +904,8 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check candidate pool const rawCandidatesAfter: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatesAfter = rawCandidatesAfter.toJSON(); - expect(candidatesAfter.length).equal(candidatesBefore.length + 1); - expect(candidatesAfter[1].owner).equal(baltathar.address); + expect(Object.keys(candidatesAfter).length).equal(Object.keys(candidatesBefore).length + 1); + expect(Object.keys(candidatesAfter)).includes(baltathar.address); // check candidate info const rawCandidateState: any = await context.polkadotApi.query.bfcStaking.candidateInfo(baltathar.address); @@ -1010,7 +921,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check relayer pool const rawRelayerPool: any = await context.polkadotApi.query.relayManager.relayerPool(); const relayerPool = rawRelayerPool.toJSON(); - expect(relayerPool.length).equal(candidatesBefore.length + 1); + expect(relayerPool.length).equal(Object.keys(candidatesBefore).length + 1); expect(relayerPool[1].relayer).equal(baltatharRelayer.address); // check bonded controller @@ -1021,11 +932,12 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { it('should successfully join candidate pool with identical essential accounts - full node', async function () { const stake = new BigNumber(MIN_FULL_CANDIDATE_STAKING_AMOUNT); - const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidatesBefore = rawCandidatesBefore.toJSON(); // stash, controller, relayer with identical accounts await context.polkadotApi.tx.bfcStaking - .joinCandidates(charleth.address, charleth.address, stake.toFixed(), candidatesBefore.length) + .joinCandidates(charleth.address, charleth.address, stake.toFixed(), Object.keys(candidatesBefore).length) .signAndSend(charleth, { nonce: -1 }); const keys: any = { aura: SESSION_KEYS[2].aura, @@ -1040,8 +952,8 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check candidate pool const rawCandidatesAfter: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatesAfter = rawCandidatesAfter.toJSON(); - expect(candidatesAfter.length).equal(candidatesBefore.length + 1); - expect(candidatesAfter[2].owner).equal(charleth.address); + expect(Object.keys(candidatesAfter).length).equal(Object.keys(candidatesBefore).length + 1); + expect(Object.keys(candidatesAfter)).includes(charleth.address); // check candidate info const rawCandidateState: any = await context.polkadotApi.query.bfcStaking.candidateInfo(charleth.address); @@ -1057,7 +969,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check relayer pool const rawRelayerPool: any = await context.polkadotApi.query.relayManager.relayerPool(); const relayerPool = rawRelayerPool.toJSON(); - expect(relayerPool.length).equal(candidatesBefore.length + 1); + expect(relayerPool.length).equal(Object.keys(candidatesBefore).length + 1); expect(relayerPool[2].relayer).equal(charleth.address); // check bonded controller @@ -1068,10 +980,11 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { it('should successfully join candidate pool - basic node', async function () { const stake = new BigNumber(MIN_BASIC_CANDIDATE_STAKING_AMOUNT); - const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidatesBefore: any = rawCandidatesBefore.toJSON(); await context.polkadotApi.tx.bfcStaking - .joinCandidates(ethan.address, null, stake.toFixed(), candidatesBefore.length) + .joinCandidates(ethan.address, null, stake.toFixed(), Object.keys(candidatesBefore).length) .signAndSend(ethanStash, { nonce: -1 }); const keys: any = { aura: SESSION_KEYS[4].aura, @@ -1086,8 +999,8 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check candidate pool const rawCandidatesAfter: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatesAfter = rawCandidatesAfter.toJSON(); - expect(candidatesAfter.length).equal(candidatesBefore.length + 1); - expect(candidatesAfter[3].owner).equal(ethan.address); + expect(Object.keys(candidatesAfter).length).equal(Object.keys(candidatesBefore).length + 1); + expect(Object.keys(candidatesAfter)).includes(ethan.address); // check candidate info const rawCandidateState: any = await context.polkadotApi.query.bfcStaking.candidateInfo(ethan.address); @@ -1103,7 +1016,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { // check relayer pool const rawRelayerPool: any = await context.polkadotApi.query.relayManager.relayerPool(); const relayerPool = rawRelayerPool.toJSON(); - expect(relayerPool.length).equal(candidatesBefore.length); + expect(relayerPool.length).equal(Object.keys(candidatesBefore).length); // check bonded controller const rawBondedController: any = await context.polkadotApi.query.relayManager.bondedController(ethan.address); @@ -1120,7 +1033,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { await jumpToRound(context, currentRound + 1); const validators: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); - expect(validators.length).equal(1); + expect(Object.keys(validators.toJSON()).length).equal(1); }); it('should successfully join candidate pool and be selected as a validator in the next round - full node', async function () { @@ -1130,7 +1043,7 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); await context.polkadotApi.tx.bfcStaking - .joinCandidates(faith.address, faithRelayer.address, stake.toFixed(), candidatesBefore.length) + .joinCandidates(faith.address, faithRelayer.address, stake.toFixed(), Object.keys(candidatesBefore).length) .signAndSend(faithStash, { nonce: -1 }); const keys: any = { aura: SESSION_KEYS[5].aura, @@ -1180,10 +1093,11 @@ describeDevNode('pallet_bfc_staking - join candidates', (context) => { this.timeout(20000); const stake = new BigNumber(MIN_BASIC_VALIDATOR_STAKING_AMOUNT); - const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidatesBefore = rawCandidatesBefore.toJSON(); await context.polkadotApi.tx.bfcStaking - .joinCandidates(dorothy.address, null, stake.toFixed(), candidatesBefore.length) + .joinCandidates(dorothy.address, null, stake.toFixed(), Object.keys(candidatesBefore).length) .signAndSend(dorothyStash, { nonce: -1 }); const keys: any = { aura: SESSION_KEYS[3].aura, @@ -1256,7 +1170,8 @@ describeDevNode('pallet_bfc_staking - session keys', (context) => { await jumpToRound(context, currentRound + 1); - const validators: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); + const rawValidators: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); + const validators = rawValidators.toJSON(); expect(validators.length).equal(1); expect(validators).not.include(baltathar.address); }); @@ -1276,7 +1191,8 @@ describeDevNode('pallet_bfc_staking - session keys', (context) => { const currentRound = rawCurrentRound.currentRoundIndex.toNumber(); await jumpToRound(context, currentRound + 1); - const validators: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); + const rawValidators: any = await context.polkadotApi.query.bfcStaking.selectedCandidates(); + const validators = rawValidators.toJSON(); expect(validators.length).equal(2); expect(validators).include(baltathar.address); }); @@ -1624,10 +1540,11 @@ describeDevNode('pallet_bfc_staking - candidate leave', (context) => { }); it('should successfully schedule leave candidates', async function () { - const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidatesBefore = await rawCandidatesBefore.toJSON(); await context.polkadotApi.tx.bfcStaking - .scheduleLeaveCandidates(candidatesBefore.length) + .scheduleLeaveCandidates(Object.keys(candidatesBefore).length) .signAndSend(baltathar, { nonce: -1 }); await context.createBlock(); @@ -1670,9 +1587,10 @@ describeDevNode('pallet_bfc_staking - candidate leave', (context) => { this.timeout(20000); // 1. schedule leave candidates - const candidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const rawCandidatesBefore: any = await context.polkadotApi.query.bfcStaking.candidatePool(); + const candidatesBefore = rawCandidatesBefore.toJSON(); await context.polkadotApi.tx.bfcStaking - .scheduleLeaveCandidates(candidatesBefore.length) + .scheduleLeaveCandidates(Object.keys(candidatesBefore).length) .signAndSend(baltathar, { nonce: -1 }); await context.createBlock(); @@ -1708,14 +1626,7 @@ describeDevNode('pallet_bfc_staking - candidate leave', (context) => { // 5. check candidate pool - must be not found const rawCandidatePool: any = await context.polkadotApi.query.bfcStaking.candidatePool(); const candidatePool = rawCandidatePool.toJSON(); - let isCandidateFound = false; - for (const candidate of candidatePool) { - if (candidate.owner === baltathar.address) { - isCandidateFound = true; - break; - } - } - expect(isCandidateFound).equal(false); + expect(candidatePool).not.has.key(baltathar.address); // 6. check balance - self-bond must be unreserved const balanceAfter = new BigNumber((await context.web3.eth.getBalance(baltatharStash.address)).toString()); @@ -1798,10 +1709,10 @@ describeDevNode('pallet_bfc_staking - join nominators', (context) => { await context.createBlock(); const rawNominatorState: any = await context.polkadotApi.query.bfcStaking.nominatorState(charleth.address); - const nominatorState = rawNominatorState.unwrap(); + const nominatorState = rawNominatorState.unwrap().toJSON(); - expect(nominatorState.nominations[0].owner.toString().toLowerCase()).equal(alith.address.toLowerCase()); - expect(nominatorState.nominations[0].amount.toString()).equal(stake.toFixed()); + expect(nominatorState.nominations).has.key(alith.address); + expect(parseInt(nominatorState.nominations[alith.address].toString(), 16).toString()).equal(stake.toFixed()); const rawCandidateState: any = await context.polkadotApi.query.bfcStaking.candidateInfo(alith.address); const candidateState = rawCandidateState.unwrap(); @@ -1874,10 +1785,10 @@ describeDevNode('pallet_bfc_staking - nominator stake management', (context) => const stakeAfter = stake.multipliedBy(2); const rawNominatorState: any = await context.polkadotApi.query.bfcStaking.nominatorState(charleth.address); - const nominatorState = rawNominatorState.unwrap(); + const nominatorState = rawNominatorState.unwrap().toJSON(); - expect(nominatorState.nominations[0].owner.toString().toLowerCase()).equal(alith.address.toLowerCase()); - expect(nominatorState.nominations[0].amount.toString()).equal(stakeAfter.toFixed()); + expect(nominatorState.nominations).has.key(alith.address); + expect(parseInt(nominatorState.nominations[alith.address].toString(), 16).toString()).equal(stakeAfter.toFixed()); const rawCandidateState: any = await context.polkadotApi.query.bfcStaking.candidateInfo(alith.address); const candidateState = rawCandidateState.unwrap(); @@ -1988,15 +1899,15 @@ describeDevNode('pallet_bfc_staking - nominator stake management', (context) => await context.createBlock(); const rawNominatorStateAfter: any = await context.polkadotApi.query.bfcStaking.nominatorState(charleth.address); - const nominatorStateAfter = rawNominatorStateAfter.unwrap(); - const nominatorRequestsAfter = nominatorStateAfter.requests.toJSON(); + const nominatorStateAfter = rawNominatorStateAfter.unwrap().toJSON(); + const nominatorRequestsAfter = nominatorStateAfter.requests; let validatorAfter = null; Object.keys(nominatorRequestsAfter['requests']).forEach(function (key) { validator = key.toLowerCase(); }); expect(validatorAfter).to.be.null; - expect(nominatorStateAfter.nominations[0].amount.toString()).equal(MIN_NOMINATOR_STAKING_AMOUNT); + expect(parseInt(nominatorStateAfter.nominations[alith.address].toString(), 16).toString()).equal(MIN_NOMINATOR_STAKING_AMOUNT); }); }); diff --git a/tests/tests/pallets/test_relay_manager.ts b/tests/tests/pallets/test_relay_manager.ts index 22a808b4..32897b2c 100644 --- a/tests/tests/pallets/test_relay_manager.ts +++ b/tests/tests/pallets/test_relay_manager.ts @@ -56,17 +56,19 @@ describeDevNode('pallet_relay_manager - set relayer', (context) => { expect(initialRelayers.length).equal(1); expect(initialRelayers[0]).equal(alithRelayer.address); + const rawCurrentRound: any = await context.polkadotApi.query.bfcStaking.round(); + const currentRound = rawCurrentRound.currentRoundIndex.toNumber(); + // check `CachedSelectedRelayers` const rawCachedRelayers: any = await context.polkadotApi.query.relayManager.cachedSelectedRelayers(); const cachedRelayers = rawCachedRelayers.toJSON(); - expect(cachedRelayers[0][1].length).equal(1); - expect(cachedRelayers[0][1]).include(alithRelayer.address); + expect(cachedRelayers[currentRound]).includes(alithRelayer.address); // check `CachedInitialSelectedRelayers` const rawCachedInitialRelayers: any = await context.polkadotApi.query.relayManager.cachedInitialSelectedRelayers(); const cachedInitialRelayers = rawCachedInitialRelayers.toJSON(); - expect(cachedInitialRelayers[0][1].length).equal(1); - expect(cachedInitialRelayers[0][1]).include(alithRelayer.address); + // expect(cachedInitialRelayers[0][1].length).equal(1); + expect(cachedInitialRelayers[currentRound]).includes(alithRelayer.address); }); before('should successfully send a heartbeat', async function () { @@ -127,20 +129,22 @@ describeDevNode('pallet_relay_manager - set relayer', (context) => { expect(initialRelayers.length).equal(1); expect(initialRelayers[0]).equal(alithRelayer.address); + const rawCurrentRound: any = await context.polkadotApi.query.bfcStaking.round(); + const currentRound = rawCurrentRound.currentRoundIndex.toNumber(); + // check `CachedSelectedRelayers` const rawCachedRelayers: any = await context.polkadotApi.query.relayManager.cachedSelectedRelayers(); const cachedRelayers = rawCachedRelayers.toJSON(); - expect(cachedRelayers[0][1].length).equal(1); - expect(cachedRelayers[0][1]).include(newRelayer.address); + expect(cachedRelayers[currentRound].length).equal(1); + expect(cachedRelayers[currentRound]).include(newRelayer.address); // check `CachedInitialSelectedRelayers` const rawCachedInitialRelayers: any = await context.polkadotApi.query.relayManager.cachedInitialSelectedRelayers(); const cachedInitialRelayers = rawCachedInitialRelayers.toJSON(); - expect(cachedInitialRelayers[0][1].length).equal(1); - expect(cachedInitialRelayers[0][1]).include(alithRelayer.address); + expect(cachedInitialRelayers[currentRound].length).equal(1); + expect(cachedInitialRelayers[currentRound]).include(alithRelayer.address); // check `ReceivedHeartbeats` - const rawCurrentRound: any = await context.polkadotApi.query.bfcStaking.round(); const currentSession = rawCurrentRound.currentSessionIndex.toNumber(); const rawHeartbeat: any = await context.polkadotApi.query.relayManager.receivedHeartbeats(currentSession, newRelayer.address); const heartbeat = rawHeartbeat.toJSON(); diff --git a/tests/tests/precompiles/test_bfc_offences.ts b/tests/tests/precompiles/test_bfc_offences.ts index eb84c65c..ae6e6a95 100644 --- a/tests/tests/precompiles/test_bfc_offences.ts +++ b/tests/tests/precompiles/test_bfc_offences.ts @@ -15,7 +15,7 @@ const PRECOMPILE_ADDRESS = '0x0000000000000000000000000000000000000500'; describeDevNode('precompile_bfc_offences - precompile view functions', (context) => { const alith: { public: string, private: string } = TEST_CONTROLLERS[0]; - it('should successfully verify offence storage existance', async function () { + it('should successfully verify offence storage existence', async function () { const maximum_offence_count = await callPrecompile( context, alith.public,