diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index 8ecb2199dda71..46b2dc41bbf7c 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -197,6 +197,7 @@ impl pallet_randomness_collective_flip::Config for Runtime {} parameter_types! { pub const MaxAuthorities: u32 = 32; + pub const MaxReportersCount: u32 = 100; } impl pallet_aura::Config for Runtime { @@ -223,6 +224,7 @@ impl pallet_grandpa::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxReportersCount = MaxReportersCount; } parameter_types! { diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 6d04ca8fdca87..0079cc816de1e 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -380,7 +380,8 @@ impl pallet_babe::Config for Runtime { pallet_babe::EquivocationHandler; type WeightInfo = (); - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = MaxValidatorsCount; + type MaxReportersCount = MaxReportersCount; } parameter_types! { @@ -473,10 +474,12 @@ impl pallet_session::Config for Runtime { type SessionHandler = ::KeyTypeIdProviders; type Keys = SessionKeys; type WeightInfo = pallet_session::weights::SubstrateWeight; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; } impl pallet_session::historical::Config for Runtime { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -496,19 +499,30 @@ parameter_types! { pub const BondingDuration: pallet_staking::EraIndex = 24 * 28; pub const SlashDeferDuration: pallet_staking::EraIndex = 24 * 7; // 1/4 the bonding duration. pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 256; + pub const MaxRewardableIndividualExposures: u32 = 256; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); pub OffchainRepeat: BlockNumber = 5; + pub const MaxIndividualExposures: u32 = 10_000; + pub const MaxNominations: u32 = MAX_NOMINATIONS; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 10; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 4_000; + pub const MaxUnlockingChunks: u32 = 32; + pub const MaxKeysEncodingSize: u32 = 1_000; } use frame_election_provider_support::onchain; impl onchain::Config for Runtime { type Accuracy = Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl pallet_staking::Config for Runtime { - const MAX_NOMINATIONS: u32 = MAX_NOMINATIONS; type Currency = Balances; type UnixTime = Timestamp; type CurrencyToVote = U128CurrencyToVote; @@ -528,7 +542,7 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; @@ -536,6 +550,15 @@ impl pallet_staking::Config for Runtime { // Note that the aforementioned does not scale to a very large number of nominators. type SortedListProvider = BagsList; type WeightInfo = pallet_staking::weights::SubstrateWeight; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; } parameter_types! { @@ -591,7 +614,6 @@ impl pallet_election_provider_multi_phase::BenchmarkingConfig for BenchmarkConfi const DESIRED_TARGETS: [u32; 2] = [200, 400]; const SNAPSHOT_MAXIMUM_VOTERS: u32 = 1000; const MINER_MAXIMUM_VOTERS: u32 = 1000; - const MAXIMUM_TARGETS: u32 = 300; } /// Maximum number of iterations for balancing that will be executed in the embedded OCW @@ -651,6 +673,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type ForceOrigin = EnsureRootOrHalfCouncil; type BenchmarkingConfig = BenchmarkConfig; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; + type MaxTargets = MaxValidatorsCount; } parameter_types! { @@ -936,8 +959,6 @@ parameter_types! { pub const ImOnlineUnsignedPriority: TransactionPriority = TransactionPriority::max_value(); /// We prioritize im-online heartbeats over election solution submission. pub const StakingUnsignedPriority: TransactionPriority = TransactionPriority::max_value() / 2; - pub const MaxAuthorities: u32 = 100; - pub const MaxKeys: u32 = 10_000; pub const MaxPeerInHeartbeats: u32 = 10_000; pub const MaxPeerDataEncodingSize: u32 = 1_000; } @@ -1004,19 +1025,21 @@ impl pallet_im_online::Config for Runtime { type ReportUnresponsiveness = Offences; type UnsignedPriority = ImOnlineUnsignedPriority; type WeightInfo = pallet_im_online::weights::SubstrateWeight; - type MaxKeys = MaxKeys; + type MaxKeys = MaxValidatorsCount; type MaxPeerInHeartbeats = MaxPeerInHeartbeats; type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; + type MaxReportersCount = MaxReportersCount; } impl pallet_offences::Config for Runtime { type Event = Event; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; + type MaxReportersCount = MaxReportersCount; } impl pallet_authority_discovery::Config for Runtime { - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = MaxValidatorsCount; } impl pallet_grandpa::Config for Runtime { @@ -1040,7 +1063,8 @@ impl pallet_grandpa::Config for Runtime { >; type WeightInfo = (); - type MaxAuthorities = MaxAuthorities; + type MaxAuthorities = MaxValidatorsCount; + type MaxReportersCount = MaxReportersCount; } parameter_types! { diff --git a/frame/aura/src/lib.rs b/frame/aura/src/lib.rs index a4e55f25df5f6..e8635c352c3a0 100644 --- a/frame/aura/src/lib.rs +++ b/frame/aura/src/lib.rs @@ -72,6 +72,7 @@ pub mod pallet { + Default + MaybeSerializeDeserialize + MaxEncodedLen; + /// The maximum number of authorities that the pallet can hold. type MaxAuthorities: Get; diff --git a/frame/authority-discovery/src/lib.rs b/frame/authority-discovery/src/lib.rs index a6609860d7cf1..70c611ed6fccf 100644 --- a/frame/authority-discovery/src/lib.rs +++ b/frame/authority-discovery/src/lib.rs @@ -202,6 +202,7 @@ mod tests { parameter_types! { pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); pub const MaxAuthorities: u32 = 100; + pub const MaxKeysEncodingSize: u32 = 1_000; } impl Config for Test { @@ -217,6 +218,8 @@ mod tests { type ValidatorId = AuthorityId; type ValidatorIdOf = ConvertInto; type NextSessionRotation = pallet_session::PeriodicSessions; + type MaxValidatorsCount = MaxAuthorities; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } diff --git a/frame/babe/src/equivocation.rs b/frame/babe/src/equivocation.rs index 2397918d1ef13..05b7a7c7089a4 100644 --- a/frame/babe/src/equivocation.rs +++ b/frame/babe/src/equivocation.rs @@ -33,7 +33,7 @@ //! that the `ValidateUnsigned` for the BABE pallet is used in the runtime //! definition. -use frame_support::traits::{Get, KeyOwnerProofSystem}; +use frame_support::traits::{Get, KeyOwnerProofSystem, ReportOffence}; use sp_consensus_babe::{EquivocationProof, Slot}; use sp_runtime::{ transaction_validity::{ @@ -43,7 +43,7 @@ use sp_runtime::{ DispatchResult, Perbill, }; use sp_staking::{ - offence::{Kind, Offence, OffenceError, ReportOffence}, + offence::{Kind, Offence, OffenceError}, SessionIndex, }; use sp_std::prelude::*; @@ -62,7 +62,7 @@ pub trait HandleEquivocation { /// Report an offence proved by the given reporters. fn report_offence( - reporters: Vec, + reporters: frame_support::WeakBoundedVec, offence: BabeEquivocationOffence, ) -> Result<(), OffenceError>; @@ -83,7 +83,7 @@ impl HandleEquivocation for () { type ReportLongevity = (); fn report_offence( - _reporters: Vec, + _reporters: frame_support::WeakBoundedVec, _offence: BabeEquivocationOffence, ) -> Result<(), OffenceError> { Ok(()) @@ -131,6 +131,7 @@ where T::AccountId, T::KeyOwnerIdentification, BabeEquivocationOffence, + T::MaxReportersCount, >, // The longevity (in blocks) that the equivocation report is valid for. When using the staking // pallet this should be the bonding duration. @@ -139,7 +140,7 @@ where type ReportLongevity = L; fn report_offence( - reporters: Vec, + reporters: frame_support::WeakBoundedVec, offence: BabeEquivocationOffence, ) -> Result<(), OffenceError> { R::report_offence(reporters, offence) diff --git a/frame/babe/src/lib.rs b/frame/babe/src/lib.rs index 033d993f4e26d..5aa741bf64288 100644 --- a/frame/babe/src/lib.rs +++ b/frame/babe/src/lib.rs @@ -176,6 +176,10 @@ pub mod pallet { /// Max number of authorities allowed #[pallet::constant] type MaxAuthorities: Get; + + /// Maximum number of reporters. + #[pallet::constant] + type MaxReportersCount: Get; } #[pallet::error] @@ -814,8 +818,11 @@ impl Pallet { BabeEquivocationOffence { slot, validator_set_count, offender, session_index }; let reporters = match reporter { - Some(id) => vec![id], - None => vec![], + Some(id) => WeakBoundedVec::<_, _>::force_from( + vec![id], + Some("pallet_babe.do_report_equivocation"), + ), + None => WeakBoundedVec::default(), }; T::HandleEquivocation::report_offence(reporters, offence) diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index e7ec692689032..590fa88af22ca 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -121,11 +121,13 @@ impl pallet_session::Config for Test { type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = MockSessionKeys; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -184,19 +186,30 @@ parameter_types! { pub const SlashDeferDuration: EraIndex = 0; pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxRewardableIndividualExposures: u32 = 64; + pub const MaxIndividualExposures: u32 = 64; pub const ElectionLookahead: u64 = 0; pub const StakingUnsignedPriority: u64 = u64::MAX / 2; + pub const MaxNominations: u32 = 16; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 10; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 4_000; + pub const MaxKeysEncodingSize: u32 = 1_000; + pub const MaxUnlockingChunks: u32 = 32; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); } impl onchain::Config for Test { type Accuracy = Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; @@ -210,7 +223,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; @@ -223,6 +245,7 @@ impl pallet_offences::Config for Test { type Event = Event; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; + type MaxReportersCount = MaxReportersCount; } parameter_types! { @@ -254,6 +277,7 @@ impl Config for Test { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxReportersCount = MaxReportersCount; } pub fn go_to_block(n: u64, s: u64) { diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 34d861d5d97f7..abdf63c8f43d0 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -409,13 +409,13 @@ fn report_equivocation_current_session_works() { let validators = Session::validators(); // make sure that all authorities have the same balance - for validator in &validators { + for validator in &validators.to_vec() { assert_eq!(Balances::total_balance(validator), 10_000_000); assert_eq!(Staking::slashable_balance_of(validator), 10_000); assert_eq!( Staking::eras_stakers(1, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } @@ -456,11 +456,11 @@ fn report_equivocation_current_session_works() { assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0); assert_eq!( Staking::eras_stakers(2, offending_validator_id), - pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + pallet_staking::Exposure { total: 0, own: 0, others: Default::default() }, ); // check that the balances of all other validators are left intact. - for validator in &validators { + for validator in &validators.to_vec() { if *validator == offending_validator_id { continue } @@ -469,7 +469,7 @@ fn report_equivocation_current_session_works() { assert_eq!(Staking::slashable_balance_of(validator), 10_000); assert_eq!( Staking::eras_stakers(2, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } }) @@ -528,7 +528,7 @@ fn report_equivocation_old_session_works() { assert_eq!(Staking::slashable_balance_of(&offending_validator_id), 0); assert_eq!( Staking::eras_stakers(3, offending_validator_id), - pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + pallet_staking::Exposure { total: 0, own: 0, others: Default::default() }, ); }) } diff --git a/frame/bags-list/remote-tests/src/snapshot.rs b/frame/bags-list/remote-tests/src/snapshot.rs index 0e68a4495edfc..a73f3aeb94bdc 100644 --- a/frame/bags-list/remote-tests/src/snapshot.rs +++ b/frame/bags-list/remote-tests/src/snapshot.rs @@ -57,6 +57,7 @@ pub async fn execute let voters = as ElectionDataProvider< Runtime::AccountId, Runtime::BlockNumber, + Runtime::MaxValidatorsCount, >>::voters(voter_limit) .unwrap(); diff --git a/frame/beefy-mmr/src/mock.rs b/frame/beefy-mmr/src/mock.rs index 95b87c360510a..e7cad02d60a79 100644 --- a/frame/beefy-mmr/src/mock.rs +++ b/frame/beefy-mmr/src/mock.rs @@ -15,12 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::vec; +use std::{convert::TryFrom, vec}; use beefy_primitives::mmr::MmrLeafVersion; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, traits::GenesisBuild, - BasicExternalities, + BasicExternalities, WeakBoundedVec, }; use sp_core::{Hasher, H256}; use sp_runtime::{ @@ -91,6 +91,8 @@ impl frame_system::Config for Test { parameter_types! { pub const Period: u64 = 1; pub const Offset: u64 = 0; + pub const MaxValidatorsCount: u32 = 10; + pub const MaxKeysEncodingSize: u32 = 1_000; } impl pallet_session::Config for Test { @@ -102,6 +104,8 @@ impl pallet_session::Config for Test { type SessionManager = MockSessionManager; type SessionHandler = ::KeyTypeIdProviders; type Keys = MockSessionKeys; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } @@ -149,14 +153,16 @@ impl pallet_beefy_mmr::ParachainHeadsProvider for DummyParaHeads { } pub struct MockSessionManager; -impl pallet_session::SessionManager for MockSessionManager { +impl pallet_session::SessionManager for MockSessionManager { fn end_session(_: sp_staking::SessionIndex) {} fn start_session(_: sp_staking::SessionIndex) {} - fn new_session(idx: sp_staking::SessionIndex) -> Option> { + fn new_session( + idx: sp_staking::SessionIndex, + ) -> Option> { if idx == 0 || idx == 1 { - Some(vec![1, 2]) + Some(WeakBoundedVec::try_from(vec![1, 2]).expect("MaxValidatorsCount >= 2")) } else if idx == 2 { - Some(vec![3, 4]) + Some(WeakBoundedVec::try_from(vec![3, 4]).expect("MaxValidatorsCount >= 2")) } else { None } diff --git a/frame/beefy/src/mock.rs b/frame/beefy/src/mock.rs index a1fbeda4ab35c..225c71b8fc4ae 100644 --- a/frame/beefy/src/mock.rs +++ b/frame/beefy/src/mock.rs @@ -15,11 +15,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::vec; +use std::{convert::TryFrom, vec}; use frame_support::{ construct_runtime, parameter_types, sp_io::TestExternalities, traits::GenesisBuild, - BasicExternalities, + BasicExternalities, WeakBoundedVec, }; use sp_core::H256; use sp_runtime::{ @@ -94,6 +94,8 @@ parameter_types! { pub const Period: u64 = 1; pub const Offset: u64 = 0; pub const DisabledValidatorsThreshold: Perbill = Perbill::from_percent(33); + pub const MaxValidatorsCount: u32 = 100; + pub const MaxKeysEncodingSize: u32 = 1_000; } impl pallet_session::Config for Test { @@ -105,19 +107,23 @@ impl pallet_session::Config for Test { type SessionManager = MockSessionManager; type SessionHandler = ::KeyTypeIdProviders; type Keys = MockSessionKeys; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } pub struct MockSessionManager; -impl pallet_session::SessionManager for MockSessionManager { +impl pallet_session::SessionManager for MockSessionManager { fn end_session(_: sp_staking::SessionIndex) {} fn start_session(_: sp_staking::SessionIndex) {} - fn new_session(idx: sp_staking::SessionIndex) -> Option> { + fn new_session( + idx: sp_staking::SessionIndex, + ) -> Option> { if idx == 0 || idx == 1 { - Some(vec![1, 2]) + Some(WeakBoundedVec::try_from(vec![1, 2]).expect("MaxValidatorsCount >=2")) } else if idx == 2 { - Some(vec![3, 4]) + Some(WeakBoundedVec::try_from(vec![3, 4]).expect("MaxValidatorsCount >=2")) } else { None } diff --git a/frame/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index d9db6c3090994..c4ce4be5c34b4 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -69,11 +69,14 @@ fn solution_with_size( let active_voters = (0..active_voters_count) .map(|i| { // chose a random subset of winners. - let winner_votes = winners - .as_slice() - .choose_multiple(&mut rng, >::LIMIT) - .cloned() - .collect::>(); + let winner_votes = BoundedVec::try_from( + winners + .as_slice() + .choose_multiple(&mut rng, >::LIMIT) + .cloned() + .collect::>(), + ) + .expect("Benchmarking adjustment may be needed"); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, winner_votes) }) @@ -87,10 +90,13 @@ fn solution_with_size( .collect::>(); let rest_voters = (active_voters_count..size.voters) .map(|i| { - let votes = (&non_winners) - .choose_multiple(&mut rng, >::LIMIT) - .cloned() - .collect::>(); + let votes = BoundedVec::try_from( + (&non_winners) + .choose_multiple(&mut rng, >::LIMIT) + .cloned() + .collect::>(), + ) + .expect("Benchmarking adjustment may be needed"); let voter = frame_benchmarking::account::("Voter", i, SEED); (voter, stake, votes) }) @@ -109,6 +115,8 @@ fn solution_with_size( targets: targets.len() as u32, }); >::put(desired_targets); + let targets = + BoundedVec::<_, T::MaxTargets>::try_from(targets).expect("Benchmark adjustment needed"); >::put(RoundSnapshot { voters: all_voters.clone(), targets: targets.clone() }); // write the snapshot to staking or whoever is the data provider, in case it is needed further @@ -146,13 +154,15 @@ fn solution_with_size( Ok(RawSolution { solution, score, round }) } -fn set_up_data_provider(v: u32, t: u32) { +fn set_up_data_provider(v: u32) { + // number of targets in snapshot. Fixed to maximum. + let t = T::MaxTargets::get(); T::DataProvider::clear(); log!( info, "setting up with voters = {} [degree = {}], targets = {}", v, - T::DataProvider::MAXIMUM_VOTES_PER_VOTER, + >::MaximumVotesPerVoter::get(), t ); @@ -165,8 +175,24 @@ fn set_up_data_provider(v: u32, t: u32) { }) .collect::>(); // we should always have enough voters to fill. - assert!(targets.len() > T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize); - targets.truncate(T::DataProvider::MAXIMUM_VOTES_PER_VOTER as usize); + let max_votes = >::MaximumVotesPerVoter::get() as usize; + assert!(targets.len() > max_votes); + targets.truncate(max_votes); + + let targets = + BoundedVec::< + _, + >::MaximumVotesPerVoter, + >::try_from(targets) + .expect("Just truncated"); // fill voters. (0..v).for_each(|i| { @@ -241,11 +267,11 @@ frame_benchmarking::benchmarks! { create_snapshot_internal { // number of votes in snapshot. let v in (T::BenchmarkingConfig::VOTERS[0]) .. T::BenchmarkingConfig::VOTERS[1]; - // number of targets in snapshot. - let t in (T::BenchmarkingConfig::TARGETS[0]) .. T::BenchmarkingConfig::TARGETS[1]; + + let t = T::MaxTargets::get(); // we don't directly need the data-provider to be populated, but it is just easy to use it. - set_up_data_provider::(v, t); + set_up_data_provider::(v); let targets = T::DataProvider::targets(None)?; let voters = T::DataProvider::voters(None)?; let desired_targets = T::DataProvider::desired_targets()?; @@ -285,7 +311,7 @@ frame_benchmarking::benchmarks! { assert!(>::get().is_some()); assert!(>::get().is_some()); }: { - assert_ok!( as ElectionProvider>::elect()); + assert_ok!( as ElectionProvider>::elect()); } verify { assert!(>::queued_solution().is_none()); assert!(>::get().is_none()); @@ -388,10 +414,8 @@ frame_benchmarking::benchmarks! { mine_solution_offchain_memory { // number of votes in snapshot. Fixed to maximum. let v = T::BenchmarkingConfig::MINER_MAXIMUM_VOTERS; - // number of targets in snapshot. Fixed to maximum. - let t = T::BenchmarkingConfig::MAXIMUM_TARGETS; - set_up_data_provider::(v, t); + set_up_data_provider::(v); let now = frame_system::Pallet::::block_number(); >::put(Phase::Unsigned((true, now))); >::create_snapshot().unwrap(); @@ -410,10 +434,9 @@ frame_benchmarking::benchmarks! { create_snapshot_memory { // number of votes in snapshot. Fixed to maximum. let v = T::BenchmarkingConfig::SNAPSHOT_MAXIMUM_VOTERS; - // number of targets in snapshot. Fixed to maximum. - let t = T::BenchmarkingConfig::MAXIMUM_TARGETS; + let t = T::MaxTargets::get(); - set_up_data_provider::(v, t); + set_up_data_provider::(v); assert!(>::snapshot().is_none()); }: { >::create_snapshot().map_err(|_| "could not create snapshot")?; diff --git a/frame/election-provider-multi-phase/src/helpers.rs b/frame/election-provider-multi-phase/src/helpers.rs index 98a14a93a25e0..c4ceeb2d4a1e5 100644 --- a/frame/election-provider-multi-phase/src/helpers.rs +++ b/frame/election-provider-multi-phase/src/helpers.rs @@ -18,6 +18,8 @@ //! Some helper functions/macros for this crate. use super::{Config, SolutionTargetIndexOf, SolutionVoterIndexOf, VoteWeight}; +use frame_election_provider_support::ElectionDataProvider; +use frame_support::{storage::bounded_btree_map::BoundedBTreeMap, BoundedVec}; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; #[macro_export] @@ -30,15 +32,22 @@ macro_rules! log { }; } +type MaximumVotesPerVoter = <::DataProvider as ElectionDataProvider< + ::AccountId, + ::BlockNumber, + ::MaxTargets, +>>::MaximumVotesPerVoter; + /// Generate a btree-map cache of the voters and their indices. /// /// This can be used to efficiently build index getter closures. pub fn generate_voter_cache( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, -) -> BTreeMap { - let mut cache: BTreeMap = BTreeMap::new(); + snapshot: &Vec<(T::AccountId, VoteWeight, BoundedVec>)>, +) -> BoundedBTreeMap> { + let mut cache: BoundedBTreeMap> = + BoundedBTreeMap::new(); snapshot.iter().enumerate().for_each(|(i, (x, _, _))| { - let _existed = cache.insert(x.clone(), i); + let _existed = cache.try_insert(x.clone(), i).expect("Size is MaximumVotesPerVoter"); // if a duplicate exists, we only consider the last one. Defensive only, should never // happen. debug_assert!(_existed.is_none()); @@ -69,7 +78,7 @@ pub fn voter_index_fn( /// Same as [`voter_index_fn`] but the returned function owns all its necessary data; nothing is /// borrowed. pub fn voter_index_fn_owned( - cache: BTreeMap, + cache: BoundedBTreeMap>, ) -> impl Fn(&T::AccountId) -> Option> { move |who| { cache @@ -148,7 +157,7 @@ pub fn target_index_fn_linear( /// Create a function that can map a voter index ([`SolutionVoterIndexOf`]) to the actual voter /// account using a linearly indexible snapshot. pub fn voter_at_fn( - snapshot: &Vec<(T::AccountId, VoteWeight, Vec)>, + snapshot: &Vec<(T::AccountId, VoteWeight, BoundedVec>)>, ) -> impl Fn(SolutionVoterIndexOf) -> Option + '_ { move |i| { as TryInto>::try_into(i) @@ -192,8 +201,12 @@ pub fn stake_of_fn_linear( /// The cache need must be derived from the same snapshot. Zero is returned if a voter is /// non-existent. pub fn stake_of_fn<'a, T: Config>( - snapshot: &'a Vec<(T::AccountId, VoteWeight, Vec)>, - cache: &'a BTreeMap, + snapshot: &'a Vec<( + T::AccountId, + VoteWeight, + BoundedVec>, + )>, + cache: &'a BoundedBTreeMap>, ) -> impl Fn(&T::AccountId) -> VoteWeight + 'a { move |who| { if let Some(index) = cache.get(who) { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 80a13aa99fb70..509dc12ad2e88 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -236,6 +236,7 @@ use frame_support::{ ensure, traits::{Currency, Get, OnUnbalanced, ReservableCurrency}, weights::{DispatchClass, Weight}, + BoundedVec, RuntimeDebugNoBound, }; use frame_system::{ensure_none, offchain::SendTransactionTypes}; use scale_info::TypeInfo; @@ -289,6 +290,7 @@ pub type SolutionAccuracyOf = as NposSolution>::Accuracy; pub type FallbackErrorOf = <::Fallback as ElectionProvider< ::AccountId, ::BlockNumber, + ::MaxTargets, >>::Error; /// Configuration for the benchmarks of the pallet. @@ -305,8 +307,6 @@ pub trait BenchmarkingConfig { const SNAPSHOT_MAXIMUM_VOTERS: u32; /// Maximum number of voters expected. This is used only for memory-benchmarking of miner. const MINER_MAXIMUM_VOTERS: u32; - /// Maximum number of targets expected. This is used only for memory-benchmarking. - const MAXIMUM_TARGETS: u32; } impl BenchmarkingConfig for () { @@ -316,13 +316,12 @@ impl BenchmarkingConfig for () { const DESIRED_TARGETS: [u32; 2] = [400, 800]; const SNAPSHOT_MAXIMUM_VOTERS: u32 = 10_000; const MINER_MAXIMUM_VOTERS: u32 = 10_000; - const MAXIMUM_TARGETS: u32 = 2_000; } /// A fallback implementation that transitions the pallet to the emergency phase. pub struct NoFallback(sp_std::marker::PhantomData); -impl ElectionProvider for NoFallback { +impl ElectionProvider for NoFallback { type DataProvider = T::DataProvider; type Error = &'static str; @@ -458,12 +457,15 @@ pub struct ReadySolution { /// [`ElectionDataProvider`] and are kept around until the round is finished. /// /// These are stored together because they are often accessed together. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, Default, TypeInfo)] -pub struct RoundSnapshot { +/// `MaxVotes` bounds the number of voters per voter +/// `MaxTargets` bounds the number of targets +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebugNoBound, Default, TypeInfo)] +#[scale_info(skip_type_params(MaxVotes, MaxTargets))] +pub struct RoundSnapshot, MaxTargets: Get> { /// All of the voters. - pub voters: Vec<(A, VoteWeight, Vec)>, + pub voters: Vec<(A, VoteWeight, BoundedVec)>, /// All of the targets. - pub targets: Vec, + pub targets: BoundedVec, } /// Encodes the length of a solution or a snapshot. @@ -656,6 +658,9 @@ pub mod pallet { /// Handler for the rewards. type RewardHandler: OnUnbalanced>; + /// Maximum number of targets for the data provider. + type MaxTargets: Get; + /// Maximum length (bytes) that the mined solution should consume. /// /// The miner will ensure that the total length of the unsigned solution will not exceed @@ -664,7 +669,11 @@ pub mod pallet { type MinerMaxLength: Get; /// Something that will provide the election data. - type DataProvider: ElectionDataProvider; + type DataProvider: ElectionDataProvider< + Self::AccountId, + Self::BlockNumber, + Self::MaxTargets, + >; /// The solution type. type Solution: codec::Codec @@ -681,11 +690,20 @@ pub mod pallet { type Fallback: ElectionProvider< Self::AccountId, Self::BlockNumber, + Self::MaxTargets, DataProvider = Self::DataProvider, >; /// OCW election solution miner algorithm implementation. - type Solver: NposSolver; + type Solver: NposSolver< + Self::MaxTargets, + >::MaximumVotesPerVoter, + AccountId = Self::AccountId, + >; /// Origin that can control this pallet. Note that any action taken by this origin (such) /// as providing an emergency solution is not checked. Thus, it must be a trusted origin. @@ -772,7 +790,7 @@ pub mod pallet { Self::on_initialize_open_unsigned(enabled, now); T::WeightInfo::on_initialize_open_unsigned() } - } + }, _ => T::WeightInfo::on_initialize_nothing(), } } @@ -828,7 +846,11 @@ pub mod pallet { // NOTE that this pallet does not really need to enforce this in runtime. The // solution cannot represent any voters more than `LIMIT` anyhow. assert_eq!( - >::MAXIMUM_VOTES_PER_VOTER, + >::MaximumVotesPerVoter::get(), as NposSolution>::LIMIT as u32, ); } @@ -1145,7 +1167,18 @@ pub mod pallet { /// This is created at the beginning of the signed phase and cleared upon calling `elect`. #[pallet::storage] #[pallet::getter(fn snapshot)] - pub type Snapshot = StorageValue<_, RoundSnapshot>; + pub type Snapshot = StorageValue< + _, + RoundSnapshot< + T::AccountId, + >::MaximumVotesPerVoter, + T::MaxTargets, + >, + >; /// Desired number of targets to elect for this round. /// @@ -1260,7 +1293,7 @@ impl Pallet { /// /// Extracted for easier weight calculation. fn create_snapshot_internal( - targets: Vec, + targets: BoundedVec, voters: Vec>, desired_targets: u32, ) { @@ -1291,8 +1324,10 @@ impl Pallet { /// Parts of [`create_snapshot`] that happen outside of this pallet. /// /// Extracted for easier weight calculation. - fn create_snapshot_external( - ) -> Result<(Vec, Vec>, u32), ElectionError> { + fn create_snapshot_external() -> Result< + (BoundedVec, Vec>, u32), + ElectionError, + > { let target_limit = >::max_value().saturated_into::(); // for now we have just a single block snapshot. let voter_limit = T::VoterSnapshotPerBlock::get().saturated_into::(); @@ -1499,7 +1534,7 @@ impl Pallet { } } -impl ElectionProvider for Pallet { +impl ElectionProvider for Pallet { type Error = ElectionError; type DataProvider = T::DataProvider; @@ -1718,6 +1753,7 @@ mod tests { use frame_election_provider_support::ElectionProvider; use frame_support::{assert_noop, assert_ok}; use sp_npos_elections::Support; + use std::convert::TryFrom; #[test] fn phase_rotation_works() { @@ -1959,7 +1995,12 @@ mod tests { fn snapshot_too_big_failure_onchain_fallback() { // the `MockStaking` is designed such that if it has too many targets, it simply fails. ExtBuilder::default().build_and_execute(|| { - Targets::set((0..(TargetIndex::max_value() as AccountId) + 1).collect::>()); + Targets::set( + BoundedVec::try_from( + (0..(TargetIndex::max_value() as AccountId) + 1).collect::>(), + ) + .expect("Testing configuration needs changing"), + ); // Signed phase failed to open. roll_to(15); @@ -1969,19 +2010,49 @@ mod tests { roll_to(25); assert_eq!(MultiPhase::current_phase(), Phase::Off); - // On-chain backup works though. + // On-chain backup works though as long as size is less than MaxValidatorsCount. roll_to(29); let supports = MultiPhase::elect().unwrap(); assert!(supports.len() > 0); }); } + #[test] + fn snapshot_too_big_failure_onchain_fallback_fail() { + // the `MockStaking` is designed such that if it has too many targets, it simply fails. + ExtBuilder::default().build_and_execute(|| { + Targets::set( + BoundedVec::try_from( + (0..(TargetIndex::max_value() as AccountId) + 1).collect::>(), + ) + .expect("Testing configuration needs changing"), + ); + + // Signed phase failed to open. + roll_to(15); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + // Unsigned phase failed to open. + roll_to(25); + assert_eq!(MultiPhase::current_phase(), Phase::Off); + + // On-chain backup will not work when size is more than MaxValidatorsCount. + roll_to(29); + let err = MultiPhase::elect().unwrap_err(); + assert_eq!(err, ElectionError::Fallback("OnChainSequentialPhragmen failed")); + assert_eq!(MultiPhase::current_phase(), Phase::Emergency); + }); + } + #[test] fn snapshot_too_big_failure_no_fallback() { // and if the backup mode is nothing, we go into the emergency mode.. ExtBuilder::default().onchain_fallback(false).build_and_execute(|| { crate::mock::Targets::set( - (0..(TargetIndex::max_value() as AccountId) + 1).collect::>(), + BoundedVec::try_from( + (0..(TargetIndex::max_value() as AccountId) + 1).collect::>(), + ) + .expect("Testing configuration needs changing"), ); // Signed phase failed to open. diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index fbde6ad991706..62acca91c2021 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -21,7 +21,7 @@ use frame_election_provider_support::{ data_provider, onchain, ElectionDataProvider, SequentialPhragmen, }; pub use frame_support::{assert_noop, assert_ok}; -use frame_support::{parameter_types, traits::Hooks, weights::Weight}; +use frame_support::{pallet_prelude::ConstU32, parameter_types, traits::Hooks, weights::Weight}; use multi_phase::unsigned::{IndexAssignmentOf, Voter}; use parking_lot::RwLock; use sp_core::{ @@ -127,10 +127,16 @@ pub fn trim_helpers() -> TrimHelpers { let desired_targets = MultiPhase::desired_targets().unwrap(); + let voters_vec = voters + .clone() + .into_iter() + .map(|(who, voter_stake, votes)| (who, voter_stake, votes.to_vec())) + .collect(); + let ElectionResult { mut assignments, .. } = seq_phragmen::<_, SolutionAccuracyOf>( desired_targets as usize, - targets.clone(), - voters.clone(), + targets.to_vec(), + voters_vec, None, ) .unwrap(); @@ -159,18 +165,24 @@ pub fn raw_solution() -> RawSolution> { let RoundSnapshot { voters, targets } = MultiPhase::snapshot().unwrap(); let desired_targets = MultiPhase::desired_targets().unwrap(); + let voters_vec = voters + .clone() + .into_iter() + .map(|(who, voter_stake, votes)| (who, voter_stake, votes.to_vec())) + .collect::>(); + let ElectionResult { winners: _, assignments } = seq_phragmen::<_, SolutionAccuracyOf>( desired_targets as usize, - targets.clone(), - voters.clone(), + targets.to_vec(), + voters_vec.clone(), None, ) .unwrap(); // closures let cache = helpers::generate_voter_cache::(&voters); - let voter_index = helpers::voter_index_fn_linear::(&voters); + let voter_index = helpers::voter_index_fn_linear::(&voters_vec); let target_index = helpers::target_index_fn_linear::(&targets); let stake_of = helpers::stake_of_fn::(&voters, &cache); @@ -223,6 +235,8 @@ impl frame_system::Config for Runtime { const NORMAL_DISPATCH_RATIO: Perbill = Perbill::from_percent(75); parameter_types! { pub const ExistentialDeposit: u64 = 1; + pub const MaxValidatorsCount: u32 = 100_000; + pub const MaxNominations: u32 = 10; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights ::with_sensible_defaults(2 * frame_support::weights::constants::WEIGHT_PER_SECOND, NORMAL_DISPATCH_RATIO); } @@ -240,17 +254,18 @@ impl pallet_balances::Config for Runtime { } parameter_types! { - pub static Targets: Vec = vec![10, 20, 30, 40]; - pub static Voters: Vec<(AccountId, VoteWeight, Vec)> = vec![ - (1, 10, vec![10, 20]), - (2, 10, vec![30, 40]), - (3, 10, vec![40]), - (4, 10, vec![10, 20, 30, 40]), + pub static Targets: BoundedVec = BoundedVec::try_from( + vec![10, 20, 30, 40]).expect("LIMIT >= 4"); + pub static Voters: Vec<(AccountId, VoteWeight, BoundedVec::LIMIT as u32 }>>)> = vec![ + (1, 10, BoundedVec::try_from(vec![10, 20]).expect("LIMIT >= 2")), + (2, 10, BoundedVec::try_from(vec![30, 40]).expect("LIMIT >= 2")), + (3, 10, BoundedVec::try_from(vec![40]).expect("LIMIT >= 1")), + (4, 10, BoundedVec::try_from(vec![10, 20, 30, 40]).expect("LIMIT >= 4")), // self votes. - (10, 10, vec![10]), - (20, 20, vec![20]), - (30, 30, vec![30]), - (40, 40, vec![40]), + (10, 10, BoundedVec::try_from(vec![10]).expect("LIMIT >= 1")), + (20, 20, BoundedVec::try_from(vec![20]).expect("LIMIT >= 1")), + (30, 30, BoundedVec::try_from(vec![30]).expect("LIMIT >= 1")), + (40, 40, BoundedVec::try_from(vec![40]).expect("LIMIT >= 1")), ]; pub static DesiredTargets: u32 = 2; @@ -271,21 +286,23 @@ parameter_types! { pub static VoterSnapshotPerBlock: VoterIndex = u32::max_value(); pub static EpochLength: u64 = 30; - pub static OnChianFallback: bool = true; + pub static OnChainFallback: bool = true; } impl onchain::Config for Runtime { type Accuracy = sp_runtime::Perbill; type DataProvider = StakingMock; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } pub struct MockFallback; -impl ElectionProvider for MockFallback { +impl ElectionProvider for MockFallback { type Error = &'static str; type DataProvider = StakingMock; fn elect() -> Result, Self::Error> { - if OnChianFallback::get() { + if OnChainFallback::get() { onchain::OnChainSequentialPhragmen::::elect() .map_err(|_| "OnChainSequentialPhragmen failed") } else { @@ -404,6 +421,7 @@ impl crate::Config for Runtime { type Solution = TestNposSolution; type VoterSnapshotPerBlock = VoterSnapshotPerBlock; type Solver = SequentialPhragmen, Balancing>; + type MaxTargets = MaxValidatorsCount; } impl frame_system::offchain::SendTransactionTypes for Runtime @@ -420,9 +438,11 @@ pub type Extrinsic = sp_runtime::testing::TestXt; pub struct ExtBuilder {} pub struct StakingMock; -impl ElectionDataProvider for StakingMock { - const MAXIMUM_VOTES_PER_VOTER: u32 = ::LIMIT as u32; - fn targets(maybe_max_len: Option) -> data_provider::Result> { +impl ElectionDataProvider for StakingMock { + type MaximumVotesPerVoter = ConstU32<{ ::LIMIT as u32 }>; + fn targets( + maybe_max_len: Option, + ) -> data_provider::Result> { let targets = Targets::get(); if maybe_max_len.map_or(false, |max_len| targets.len() > max_len) { @@ -434,13 +454,27 @@ impl ElectionDataProvider for StakingMock { fn voters( maybe_max_len: Option, - ) -> data_provider::Result)>> { + ) -> data_provider::Result< + Vec<(AccountId, VoteWeight, BoundedVec)>, + > { let mut voters = Voters::get(); if let Some(max_len) = maybe_max_len { voters.truncate(max_len) } - Ok(voters) + let voters_bounded = voters + .into_iter() + .map(|(who, voter_stake, votes)| { + ( + who, + voter_stake, + BoundedVec::<_, Self::MaximumVotesPerVoter>::try_from(votes).expect( + "Too many votes per voter, a test configuration adjustment may be needed", + ), + ) + }) + .collect(); + Ok(voters_bounded) } fn desired_targets() -> data_provider::Result { @@ -453,8 +487,8 @@ impl ElectionDataProvider for StakingMock { #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( - voters: Vec<(AccountId, VoteWeight, Vec)>, - targets: Vec, + voters: Vec<(AccountId, VoteWeight, BoundedVec)>, + targets: BoundedVec, _target_stake: Option, ) { Targets::set(targets); @@ -463,12 +497,16 @@ impl ElectionDataProvider for StakingMock { #[cfg(feature = "runtime-benchmarks")] fn clear() { - Targets::set(vec![]); + Targets::set(BoundedVec::default()); Voters::set(vec![]); } #[cfg(feature = "runtime-benchmarks")] - fn add_voter(voter: AccountId, weight: VoteWeight, targets: Vec) { + fn add_voter( + voter: AccountId, + weight: VoteWeight, + targets: BoundedVec, + ) { let mut current = Voters::get(); current.push((voter, weight, targets)); Voters::set(current); @@ -477,13 +515,17 @@ impl ElectionDataProvider for StakingMock { #[cfg(feature = "runtime-benchmarks")] fn add_target(target: AccountId) { let mut current = Targets::get(); - current.push(target); + current.try_push(target).expect("Benchamrk configuration needs adjustment"); Targets::set(current); // to be on-par with staking, we add a self vote as well. the stake is really not that // important. let mut current = Voters::get(); - current.push((target, ExistentialDeposit::get() as u64, vec![target])); + current.push(( + target, + ExistentialDeposit::get() as u64, + BoundedVec::try_from(vec![target]).expect("MaxValidatorsCount >= 1"), + )); Voters::set(current); } } @@ -503,7 +545,7 @@ impl ExtBuilder { self } pub fn onchain_fallback(self, onchain: bool) -> Self { - ::set(onchain); + ::set(onchain); self } pub fn miner_weight(self, weight: Weight) -> Self { @@ -519,6 +561,8 @@ impl ExtBuilder { self } pub fn add_voter(self, who: AccountId, stake: Balance, targets: Vec) -> Self { + let targets = + BoundedVec::try_from(targets).expect("Benchmark configuration may need changing"); VOTERS.with(|v| v.borrow_mut().push((who, stake, targets))); self } diff --git a/frame/election-provider-multi-phase/src/unsigned.rs b/frame/election-provider-multi-phase/src/unsigned.rs index 1770f4343a0a4..c44c5dde54af5 100644 --- a/frame/election-provider-multi-phase/src/unsigned.rs +++ b/frame/election-provider-multi-phase/src/unsigned.rs @@ -18,13 +18,13 @@ //! The unsigned phase, and its miner. use crate::{ - helpers, Call, Config, ElectionCompute, Error, FeasibilityError, Pallet, RawSolution, - ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, SolutionOrSnapshotSize, Weight, - WeightInfo, + helpers, Call, Config, ElectionCompute, ElectionDataProvider, Error, FeasibilityError, Pallet, + RawSolution, ReadySolution, RoundSnapshot, SolutionAccuracyOf, SolutionOf, + SolutionOrSnapshotSize, Weight, WeightInfo, }; use codec::Encode; use frame_election_provider_support::{NposSolver, PerThing128}; -use frame_support::{dispatch::DispatchResult, ensure, traits::Get}; +use frame_support::{dispatch::DispatchResult, ensure, traits::Get, BoundedVec}; use frame_system::offchain::SubmitTransaction; use sp_arithmetic::Perbill; use sp_npos_elections::{ @@ -50,7 +50,14 @@ pub(crate) const OFFCHAIN_CACHED_CALL: &[u8] = b"parity/multi-phase-unsigned-ele pub type Voter = ( ::AccountId, sp_npos_elections::VoteWeight, - Vec<::AccountId>, + BoundedVec< + ::AccountId, + <::DataProvider as ElectionDataProvider< + ::AccountId, + ::BlockNumber, + ::MaxTargets, + >>::MaximumVotesPerVoter, + >, ); /// The relative distribution of a voter's stake among the winning targets. @@ -62,7 +69,14 @@ pub type Assignment = pub type IndexAssignmentOf = sp_npos_elections::IndexAssignmentOf>; /// Error type of the pallet's [`crate::Config::Solver`]. -pub type SolverErrorOf = <::Solver as NposSolver>::Error; +pub type SolverErrorOf = <::Solver as NposSolver< + ::MaxTargets, + <::DataProvider as ElectionDataProvider< + ::AccountId, + ::BlockNumber, + ::MaxTargets, + >>::MaximumVotesPerVoter, +>>::Error; /// Error type for operations related to the OCW npos solution miner. #[derive(frame_support::DebugNoBound, frame_support::PartialEqNoBound)] pub enum MinerError { @@ -275,7 +289,16 @@ impl Pallet { pub fn mine_solution( ) -> Result<(RawSolution>, SolutionOrSnapshotSize), MinerError> where - S: NposSolver>, + S: NposSolver< + T::MaxTargets, + >::MaximumVotesPerVoter, + AccountId = T::AccountId, + Error = SolverErrorOf, + >, { let RoundSnapshot { voters, targets } = Self::snapshot().ok_or(MinerError::SnapshotUnAvailable)?; diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index cb36e025c3bee..daca9c59e01dd 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -84,32 +84,36 @@ //! type AccountId = u64; //! type Balance = u64; //! type BlockNumber = u32; +//! type MaxTargets = frame_support::pallet_prelude::ConstU32<10>; //! //! mod data_provider_mod { //! use super::*; +//! use frame_support::BoundedVec; +//! use sp_std::convert::TryFrom; //! //! pub trait Config: Sized { //! type ElectionProvider: ElectionProvider< //! AccountId, //! BlockNumber, +//! MaxTargets, //! DataProvider = Module, //! >; //! } //! //! pub struct Module(std::marker::PhantomData); //! -//! impl ElectionDataProvider for Module { -//! const MAXIMUM_VOTES_PER_VOTER: u32 = 1; +//! impl ElectionDataProvider for Module { +//! type MaximumVotesPerVoter = frame_support::pallet_prelude::ConstU32<1>; //! fn desired_targets() -> data_provider::Result { //! Ok(1) //! } //! fn voters(maybe_max_len: Option) -//! -> data_provider::Result)>> +//! -> data_provider::Result)>> //! { //! Ok(Default::default()) //! } -//! fn targets(maybe_max_len: Option) -> data_provider::Result> { -//! Ok(vec![10, 20, 30]) +//! fn targets(maybe_max_len: Option) -> data_provider::Result> { +//! Ok(BoundedVec::try_from(vec![10, 20, 30]).expect("MaxTargets >=3")) //! } //! fn next_election_prediction(now: BlockNumber) -> BlockNumber { //! 0 @@ -124,10 +128,10 @@ //! pub struct GenericElectionProvider(std::marker::PhantomData); //! //! pub trait Config { -//! type DataProvider: ElectionDataProvider; +//! type DataProvider: ElectionDataProvider; //! } //! -//! impl ElectionProvider for GenericElectionProvider { +//! impl ElectionProvider for GenericElectionProvider { //! type Error = &'static str; //! type DataProvider = T::DataProvider; //! @@ -161,7 +165,7 @@ #![cfg_attr(not(feature = "std"), no_std)] pub mod onchain; -use frame_support::traits::Get; +use frame_support::{traits::Get, BoundedVec}; use sp_std::{fmt::Debug, prelude::*}; /// Re-export some type as they are used in the interface. @@ -178,9 +182,10 @@ pub mod data_provider { } /// Something that can provide the data to an [`ElectionProvider`]. -pub trait ElectionDataProvider { +/// `MaxTarget` represents the maximum number of candidates. +pub trait ElectionDataProvider { /// Maximum number of votes per voter that this data provider is providing. - const MAXIMUM_VOTES_PER_VOTER: u32; + type MaximumVotesPerVoter: Get; /// All possible targets for the election, i.e. the candidates. /// @@ -189,7 +194,9 @@ pub trait ElectionDataProvider { /// /// This should be implemented as a self-weighing function. The implementor should register its /// appropriate weight at the end of execution with the system pallet directly. - fn targets(maybe_max_len: Option) -> data_provider::Result>; + fn targets( + maybe_max_len: Option, + ) -> data_provider::Result>; /// All possible voters for the election. /// @@ -202,7 +209,9 @@ pub trait ElectionDataProvider { /// appropriate weight at the end of execution with the system pallet directly. fn voters( maybe_max_len: Option, - ) -> data_provider::Result)>>; + ) -> data_provider::Result< + Vec<(AccountId, VoteWeight, BoundedVec)>, + >; /// The number of targets to elect. /// @@ -222,8 +231,8 @@ pub trait ElectionDataProvider { /// else a noop. #[cfg(any(feature = "runtime-benchmarks", test))] fn put_snapshot( - _voters: Vec<(AccountId, VoteWeight, Vec)>, - _targets: Vec, + _voters: Vec<(AccountId, VoteWeight, BoundedVec)>, + _targets: BoundedVec, _target_stake: Option, ) { } @@ -233,7 +242,12 @@ pub trait ElectionDataProvider { /// /// Same as `put_snapshot`, but can add a single voter one by one. #[cfg(any(feature = "runtime-benchmarks", test))] - fn add_voter(_voter: AccountId, _weight: VoteWeight, _targets: Vec) {} + fn add_voter( + _voter: AccountId, + _weight: VoteWeight, + _targets: BoundedVec, + ) { + } /// Utility function only to be used in benchmarking scenarios, to be implemented optionally, /// else a noop. @@ -248,14 +262,20 @@ pub trait ElectionDataProvider { } #[cfg(feature = "std")] -impl ElectionDataProvider for () { - const MAXIMUM_VOTES_PER_VOTER: u32 = 0; - fn targets(_maybe_max_len: Option) -> data_provider::Result> { +impl ElectionDataProvider + for () +{ + type MaximumVotesPerVoter = frame_support::pallet_prelude::ConstU32<0>; + fn targets( + _maybe_max_len: Option, + ) -> data_provider::Result> { Ok(Default::default()) } fn voters( _maybe_max_len: Option, - ) -> data_provider::Result)>> { + ) -> data_provider::Result< + Vec<(AccountId, VoteWeight, BoundedVec)>, + > { Ok(Default::default()) } fn desired_targets() -> data_provider::Result { @@ -271,12 +291,13 @@ impl ElectionDataProvider for () /// This trait only provides an interface to _request_ an election, i.e. /// [`ElectionProvider::elect`]. That data required for the election need to be passed to the /// implemented of this trait through [`ElectionProvider::DataProvider`]. -pub trait ElectionProvider { +/// `MaxTargets` is the maximum number of candidates. +pub trait ElectionProvider { /// The error type that is returned by the provider. type Error: Debug; /// The data provider of the election. - type DataProvider: ElectionDataProvider; + type DataProvider: ElectionDataProvider; /// Elect a new set of winners. /// @@ -288,7 +309,9 @@ pub trait ElectionProvider { } #[cfg(feature = "std")] -impl ElectionProvider for () { +impl ElectionProvider + for () +{ type Error = &'static str; type DataProvider = (); @@ -367,7 +390,9 @@ pub trait VoteWeightProvider { } /// Something that can compute the result to an NPoS solution. -pub trait NposSolver { +/// `MaxTargets` is the maximum number of candidates. +/// `MaximumVotesPerVoter` bounds the voters per voter +pub trait NposSolver, MaximumVotesPerVoter: Get> { /// The account identifier type of this solver. type AccountId: sp_npos_elections::IdentifierT; /// The accuracy of this solver. This will affect the accuracy of the output. @@ -379,8 +404,12 @@ pub trait NposSolver { /// of `targets`. fn solve( to_elect: usize, - targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + targets: BoundedVec, + voters: Vec<( + Self::AccountId, + VoteWeight, + BoundedVec, + )>, ) -> Result, Self::Error>; } @@ -393,18 +422,30 @@ pub struct SequentialPhragmen( impl< AccountId: IdentifierT, Accuracy: PerThing128, + MaxTargets: Get, + MaximumVotesPerVoter: Get, Balancing: Get>, - > NposSolver for SequentialPhragmen + > NposSolver + for SequentialPhragmen { type AccountId = AccountId; type Accuracy = Accuracy; type Error = sp_npos_elections::Error; fn solve( winners: usize, - targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + targets: BoundedVec, + voters: Vec<( + Self::AccountId, + VoteWeight, + BoundedVec, + )>, ) -> Result, Self::Error> { - sp_npos_elections::seq_phragmen(winners, targets, voters, Balancing::get()) + let mut voters_vec: Vec<(Self::AccountId, VoteWeight, Vec)> = + Vec::with_capacity(voters.len()); + for voter in voters { + voters_vec.push((voter.0, voter.1, voter.2.to_vec())); + } + sp_npos_elections::seq_phragmen(winners, targets.to_vec(), voters_vec, Balancing::get()) } } @@ -417,17 +458,28 @@ pub struct PhragMMS( impl< AccountId: IdentifierT, Accuracy: PerThing128, + MaxTargets: Get, + MaximumVotesPerVoter: Get, Balancing: Get>, - > NposSolver for PhragMMS + > NposSolver for PhragMMS { type AccountId = AccountId; type Accuracy = Accuracy; type Error = sp_npos_elections::Error; fn solve( winners: usize, - targets: Vec, - voters: Vec<(Self::AccountId, VoteWeight, Vec)>, + targets: BoundedVec, + voters: Vec<( + Self::AccountId, + VoteWeight, + BoundedVec, + )>, ) -> Result, Self::Error> { - sp_npos_elections::phragmms(winners, targets, voters, Balancing::get()) + let mut voters_vec: Vec<(Self::AccountId, VoteWeight, Vec)> = + Vec::with_capacity(voters.len()); + for voter in voters { + voters_vec.push((voter.0, voter.1, voter.2.to_vec())); + } + sp_npos_elections::phragmms(winners, targets.to_vec(), voters_vec, Balancing::get()) } } diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index fb1ccfdfe2566..5cc8c5f53abed 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -61,11 +61,17 @@ pub struct OnChainSequentialPhragmen(PhantomData); pub trait Config: frame_system::Config { /// The accuracy used to compute the election: type Accuracy: PerThing128; + /// Maximum number of nominations per nominator. + type MaxNominations: Get; + /// Maximum number of potential targets (usually the number of validators). + type MaxTargets: Get; /// Something that provides the data for election. - type DataProvider: ElectionDataProvider; + type DataProvider: ElectionDataProvider; } -impl ElectionProvider for OnChainSequentialPhragmen { +impl ElectionProvider + for OnChainSequentialPhragmen +{ type Error = Error; type DataProvider = T::DataProvider; @@ -79,12 +85,23 @@ impl ElectionProvider for OnChainSequen .map(|(validator, vote_weight, _)| (validator.clone(), *vote_weight)) .collect(); + let voters = voters + .iter() + .map(|(validator, vote_weight, targets)| { + (validator.clone(), *vote_weight, targets.to_vec()) + }) + .collect(); + let stake_of = |w: &T::AccountId| -> VoteWeight { stake_map.get(w).cloned().unwrap_or_default() }; - let ElectionResult { winners: _, assignments } = - seq_phragmen::<_, T::Accuracy>(desired_targets as usize, targets, voters, None) - .map_err(Error::from)?; + let ElectionResult { winners: _, assignments } = seq_phragmen::<_, T::Accuracy>( + desired_targets as usize, + targets.to_vec(), + voters, + None, + ) + .map_err(Error::from)?; let staked = assignment_ratio_to_staked_normalized(assignments, &stake_of)?; @@ -106,6 +123,8 @@ mod tests { type AccountId = u64; type BlockNumber = u64; + type MaxTargets = frame_support::pallet_prelude::ConstU32<10>; + type MaxNominations = frame_support::pallet_prelude::ConstU32<2>; pub type Header = sp_runtime::generic::Header; pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; @@ -150,25 +169,35 @@ mod tests { impl Config for Runtime { type Accuracy = Perbill; type DataProvider = mock_data_provider::DataProvider; + type MaxNominations = MaxNominations; + type MaxTargets = MaxTargets; } type OnChainPhragmen = OnChainSequentialPhragmen; mod mock_data_provider { use super::*; - use crate::data_provider; + use crate::{data_provider, BoundedVec}; + use std::convert::TryFrom; pub struct DataProvider; - impl ElectionDataProvider for DataProvider { - const MAXIMUM_VOTES_PER_VOTER: u32 = 2; + impl ElectionDataProvider for DataProvider { + type MaximumVotesPerVoter = MaxNominations; fn voters( _: Option, - ) -> data_provider::Result)>> { - Ok(vec![(1, 10, vec![10, 20]), (2, 20, vec![30, 20]), (3, 30, vec![10, 30])]) + ) -> data_provider::Result< + Vec<(AccountId, VoteWeight, BoundedVec)>, + > { + let vec_1 = BoundedVec::try_from(vec![10, 20]).expect("MaximumVotesPerVoter >= 2"); + let vec_2 = BoundedVec::try_from(vec![30, 20]).expect("MaximumVotesPerVoter >= 2"); + let vec_3 = BoundedVec::try_from(vec![10, 30]).expect("MaximumVotesPerVoter >= 2"); + Ok(vec![(1, 10, vec_1), (2, 20, vec_2), (3, 30, vec_3)]) } - fn targets(_: Option) -> data_provider::Result> { - Ok(vec![10, 20, 30]) + fn targets( + _: Option, + ) -> data_provider::Result> { + Ok(BoundedVec::try_from(vec![10, 20, 30]).expect("MaxTargets >= 3")) } fn desired_targets() -> data_provider::Result { diff --git a/frame/grandpa/src/equivocation.rs b/frame/grandpa/src/equivocation.rs index 8a23ce6e1ef1e..f87e21aab70c8 100644 --- a/frame/grandpa/src/equivocation.rs +++ b/frame/grandpa/src/equivocation.rs @@ -38,7 +38,7 @@ use sp_std::prelude::*; use codec::{self as codec, Decode, Encode}; -use frame_support::traits::{Get, KeyOwnerProofSystem}; +use frame_support::traits::{Get, KeyOwnerProofSystem, ReportOffence}; use sp_finality_grandpa::{EquivocationProof, RoundNumber, SetId}; use sp_runtime::{ transaction_validity::{ @@ -48,7 +48,7 @@ use sp_runtime::{ DispatchResult, Perbill, }; use sp_staking::{ - offence::{Kind, Offence, OffenceError, ReportOffence}, + offence::{Kind, Offence, OffenceError}, SessionIndex, }; @@ -68,7 +68,7 @@ pub trait HandleEquivocation { /// Report an offence proved by the given reporters. fn report_offence( - reporters: Vec, + reporters: frame_support::WeakBoundedVec, offence: Self::Offence, ) -> Result<(), OffenceError>; @@ -93,7 +93,7 @@ impl HandleEquivocation for () { type ReportLongevity = (); fn report_offence( - _reporters: Vec, + _reporters: frame_support::WeakBoundedVec, _offence: GrandpaEquivocationOffence, ) -> Result<(), OffenceError> { Ok(()) @@ -140,7 +140,7 @@ where T: Config + pallet_authorship::Config + frame_system::offchain::SendTransactionTypes>, // A system for reporting offences after valid equivocation reports are // processed. - R: ReportOffence, + R: ReportOffence, // The longevity (in blocks) that the equivocation report is valid for. When using the staking // pallet this should be the bonding duration. L: Get, @@ -150,7 +150,10 @@ where type Offence = O; type ReportLongevity = L; - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { + fn report_offence( + reporters: frame_support::WeakBoundedVec, + offence: O, + ) -> Result<(), OffenceError> { R::report_offence(reporters, offence) } diff --git a/frame/grandpa/src/lib.rs b/frame/grandpa/src/lib.rs index 9f6967a7d3c85..0f40800a287e5 100644 --- a/frame/grandpa/src/lib.rs +++ b/frame/grandpa/src/lib.rs @@ -126,6 +126,10 @@ pub mod pallet { /// Max Authorities in use #[pallet::constant] type MaxAuthorities: Get; + + /// Maximum number of reporters. + #[pallet::constant] + type MaxReportersCount: Get; } #[pallet::hooks] @@ -579,7 +583,10 @@ impl Pallet { // report to the offences module rewarding the sender. T::HandleEquivocation::report_offence( - reporter.into_iter().collect(), + WeakBoundedVec::<_, _>::force_from( + reporter.into_iter().collect::>(), + Some("frame_granpda.do_report_equivocation"), + ), >::Offence::new( session_index, validator_count, diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index f1996553f02eb..7b8bfd403067b 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -123,11 +123,13 @@ impl pallet_session::Config for Test { type SessionManager = pallet_session::historical::NoteHistoricalRoot; type SessionHandler = ::KeyTypeIdProviders; type Keys = TestSessionKeys; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -186,19 +188,30 @@ parameter_types! { pub const SlashDeferDuration: EraIndex = 0; pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxRewardableIndividualExposures: u32 = 64; + pub const MaxIndividualExposures: u32 = 64; pub const ElectionLookahead: u64 = 0; pub const StakingUnsignedPriority: u64 = u64::MAX / 2; + pub const MaxNominations: u32 = 16; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 10; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 4_000; + pub const MaxKeysEncodingSize: u32 = 1_000; + pub const MaxUnlockingChunks: u32 = 32; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); } impl onchain::Config for Test { type Accuracy = Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; @@ -212,7 +225,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; @@ -225,6 +247,7 @@ impl pallet_offences::Config for Test { type Event = Event; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; + type MaxReportersCount = MaxReportersCount; } parameter_types! { @@ -252,6 +275,7 @@ impl Config for Test { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxReportersCount = MaxReportersCount; } pub fn grandpa_log(log: ConsensusLog) -> DigestItem { diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index 98f54f966fadc..e8203c87d30c2 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -323,13 +323,13 @@ fn report_equivocation_current_set_works() { let validators = Session::validators(); // make sure that all validators have the same balance - for validator in &validators { + for validator in &validators.to_vec() { assert_eq!(Balances::total_balance(validator), 10_000_000); assert_eq!(Staking::slashable_balance_of(validator), 10_000); assert_eq!( Staking::eras_stakers(1, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } @@ -367,11 +367,11 @@ fn report_equivocation_current_set_works() { assert_eq!(Staking::slashable_balance_of(&equivocation_validator_id), 0); assert_eq!( Staking::eras_stakers(2, equivocation_validator_id), - pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + pallet_staking::Exposure { total: 0, own: 0, others: Default::default() }, ); // check that the balances of all other validators are left intact. - for validator in &validators { + for validator in &validators.to_vec() { if *validator == equivocation_validator_id { continue } @@ -381,7 +381,7 @@ fn report_equivocation_current_set_works() { assert_eq!( Staking::eras_stakers(2, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } }); @@ -407,13 +407,13 @@ fn report_equivocation_old_set_works() { start_era(2); // make sure that all authorities have the same balance - for validator in &validators { + for validator in &validators.to_vec() { assert_eq!(Balances::total_balance(validator), 10_000_000); assert_eq!(Staking::slashable_balance_of(validator), 10_000); assert_eq!( Staking::eras_stakers(2, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } @@ -446,11 +446,11 @@ fn report_equivocation_old_set_works() { assert_eq!( Staking::eras_stakers(3, equivocation_validator_id), - pallet_staking::Exposure { total: 0, own: 0, others: vec![] }, + pallet_staking::Exposure { total: 0, own: 0, others: Default::default() }, ); // check that the balances of all other validators are left intact. - for validator in &validators { + for validator in &validators.to_vec() { if *validator == equivocation_validator_id { continue } @@ -460,7 +460,7 @@ fn report_equivocation_old_set_works() { assert_eq!( Staking::eras_stakers(3, validator), - pallet_staking::Exposure { total: 10_000, own: 10_000, others: vec![] }, + pallet_staking::Exposure { total: 10_000, own: 10_000, others: Default::default() }, ); } }); diff --git a/frame/im-online/src/lib.rs b/frame/im-online/src/lib.rs index 2c5a7633c3b4a..c2e988d3ea331 100644 --- a/frame/im-online/src/lib.rs +++ b/frame/im-online/src/lib.rs @@ -77,7 +77,7 @@ pub mod weights; use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ traits::{ - EstimateNextSessionRotation, Get, OneSessionHandler, ValidatorSet, + EstimateNextSessionRotation, Get, OneSessionHandler, ReportOffence, ValidatorSet, ValidatorSetWithIdentification, WrapperOpaque, }, BoundedSlice, WeakBoundedVec, @@ -93,7 +93,7 @@ use sp_runtime::{ PerThing, Perbill, Permill, RuntimeDebug, SaturatedConversion, }; use sp_staking::{ - offence::{Kind, Offence, ReportOffence}, + offence::{Kind, Offence}, SessionIndex, }; use sp_std::{convert::TryInto, prelude::*}; @@ -353,11 +353,15 @@ pub mod pallet { /// chance the authority will produce a block and they won't be necessary. type NextSessionRotation: EstimateNextSessionRotation; + /// Maximum number of reporters of unreponsiveness offences. + type MaxReportersCount: Get; + /// A type that gives us the ability to submit unresponsiveness offence reports. type ReportUnresponsiveness: ReportOffence< Self::AccountId, IdentificationTuple, UnresponsivenessOffence>, + Self::MaxReportersCount, >; /// A configuration for base priority of unsigned transactions. @@ -912,7 +916,7 @@ impl OneSessionHandler for Pallet { let validator_set_count = keys.len() as u32; let offence = UnresponsivenessOffence { session_index, validator_set_count, offenders }; - if let Err(e) = T::ReportUnresponsiveness::report_offence(vec![], offence) { + if let Err(e) = T::ReportUnresponsiveness::report_offence(Default::default(), offence) { sp_runtime::print(e); } } diff --git a/frame/im-online/src/mock.rs b/frame/im-online/src/mock.rs index 1e4d4b43d5789..358a4e98c8ef2 100644 --- a/frame/im-online/src/mock.rs +++ b/frame/im-online/src/mock.rs @@ -21,7 +21,7 @@ use std::cell::RefCell; -use frame_support::{parameter_types, weights::Weight}; +use frame_support::{parameter_types, traits::ReportOffence, weights::Weight, WeakBoundedVec}; use pallet_session::historical as pallet_session_historical; use sp_core::H256; use sp_runtime::{ @@ -29,13 +29,10 @@ use sp_runtime::{ traits::{BlakeTwo256, ConvertInto, IdentityLookup}, Permill, }; -use sp_staking::{ - offence::{OffenceError, ReportOffence}, - SessionIndex, -}; +use sp_staking::{offence::OffenceError, SessionIndex}; use crate as imonline; -use crate::Config; +use crate::{Config, TryFrom}; type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic; type Block = frame_system::mocking::MockBlock; @@ -54,29 +51,23 @@ frame_support::construct_runtime!( ); thread_local! { - pub static VALIDATORS: RefCell>> = RefCell::new(Some(vec![ - 1, - 2, - 3, - ])); + pub static VALIDATORS: RefCell>> = + RefCell::new(Some(WeakBoundedVec::try_from(vec![1, 2, 3]).expect("MaxKeys > 3"))); } pub struct TestSessionManager; -impl pallet_session::SessionManager for TestSessionManager { - fn new_session(_new_index: SessionIndex) -> Option> { +impl pallet_session::SessionManager for TestSessionManager { + fn new_session(_new_index: SessionIndex) -> Option> { VALIDATORS.with(|l| l.borrow_mut().take()) } fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} } -impl pallet_session::historical::SessionManager for TestSessionManager { - fn new_session(_new_index: SessionIndex) -> Option> { - VALIDATORS.with(|l| { - l.borrow_mut() - .take() - .map(|validators| validators.iter().map(|v| (*v, *v)).collect()) - }) +impl pallet_session::historical::SessionManager for TestSessionManager { + fn new_session(_new_index: SessionIndex) -> Option> { + VALIDATORS + .with(|l| l.borrow_mut().take().map(|validators| validators.map_collect(|v| (v, v)))) } fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} @@ -93,9 +84,12 @@ thread_local! { /// A mock offence report handler. pub struct OffenceHandler; -impl ReportOffence for OffenceHandler { - fn report_offence(reporters: Vec, offence: Offence) -> Result<(), OffenceError> { - OFFENCES.with(|l| l.borrow_mut().push((reporters, offence))); +impl ReportOffence for OffenceHandler { + fn report_offence( + reporters: frame_support::WeakBoundedVec, + offence: Offence, + ) -> Result<(), OffenceError> { + OFFENCES.with(|l| l.borrow_mut().push((reporters.to_vec(), offence))); Ok(()) } @@ -156,6 +150,8 @@ impl pallet_session::Config for Runtime { type Keys = UintAuthorityId; type Event = Event; type NextSessionRotation = pallet_session::PeriodicSessions; + type MaxValidatorsCount = MaxKeys; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } @@ -213,8 +209,10 @@ impl frame_support::traits::EstimateNextSessionRotation for TestNextSession parameter_types! { pub const UnsignedPriority: u64 = 1 << 20; pub const MaxKeys: u32 = 10_000; + pub const MaxKeysEncodingSize: u32 = 1_000; pub const MaxPeerInHeartbeats: u32 = 10_000; pub const MaxPeerDataEncodingSize: u32 = 1_000; + pub const MaxReportersCount: u32 = 100; } impl Config for Runtime { @@ -228,6 +226,7 @@ impl Config for Runtime { type MaxKeys = MaxKeys; type MaxPeerInHeartbeats = MaxPeerInHeartbeats; type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; + type MaxReportersCount = MaxReportersCount; } impl frame_system::offchain::SendTransactionTypes for Runtime diff --git a/frame/im-online/src/tests.rs b/frame/im-online/src/tests.rs index bb2c4c7cae548..11191cbf3bbb2 100644 --- a/frame/im-online/src/tests.rs +++ b/frame/im-online/src/tests.rs @@ -21,7 +21,7 @@ use super::*; use crate::mock::*; -use frame_support::{assert_noop, dispatch}; +use frame_support::{assert_noop, dispatch, WeakBoundedVec}; use sp_core::{ offchain::{ testing::{TestOffchainExt, TestTransactionPoolExt}, @@ -65,7 +65,7 @@ fn should_report_offline_validators() { // buffer new validators advance_session(); // enact the change and buffer another one - let validators = vec![1, 2, 3, 4, 5, 6]; + let validators = WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6"); VALIDATORS.with(|l| *l.borrow_mut() = Some(validators.clone())); advance_session(); @@ -89,7 +89,8 @@ fn should_report_offline_validators() { // should not report when heartbeat is sent for (idx, v) in validators.into_iter().take(4).enumerate() { - let _ = heartbeat(block, 3, idx as u32, v.into(), Session::validators()).unwrap(); + let _ = + heartbeat(block, 3, idx as u32, v.into(), Session::validators().to_vec()).unwrap(); } advance_session(); @@ -147,7 +148,10 @@ fn should_mark_online_validator_when_heartbeat_is_received() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + VALIDATORS.with(|l| { + *l.borrow_mut() = + Some(WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6")) + }); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -160,7 +164,7 @@ fn should_mark_online_validator_when_heartbeat_is_received() { assert!(!ImOnline::is_online(2)); // when - let _ = heartbeat(1, 2, 0, 1.into(), Session::validators()).unwrap(); + let _ = heartbeat(1, 2, 0, 1.into(), Session::validators().to_vec()).unwrap(); // then assert!(ImOnline::is_online(0)); @@ -168,7 +172,7 @@ fn should_mark_online_validator_when_heartbeat_is_received() { assert!(!ImOnline::is_online(2)); // and when - let _ = heartbeat(1, 2, 2, 3.into(), Session::validators()).unwrap(); + let _ = heartbeat(1, 2, 2, 3.into(), Session::validators().to_vec()).unwrap(); // then assert!(ImOnline::is_online(0)); @@ -182,7 +186,10 @@ fn late_heartbeat_and_invalid_keys_len_should_fail() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + VALIDATORS.with(|l| { + *l.borrow_mut() = + Some(WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6")) + }); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -192,11 +199,11 @@ fn late_heartbeat_and_invalid_keys_len_should_fail() { // when assert_noop!( - heartbeat(1, 3, 0, 1.into(), Session::validators()), + heartbeat(1, 3, 0, 1.into(), Session::validators().to_vec()), "Transaction is outdated" ); assert_noop!( - heartbeat(1, 1, 0, 1.into(), Session::validators()), + heartbeat(1, 1, 0, 1.into(), Session::validators().to_vec()), "Transaction is outdated" ); @@ -224,7 +231,10 @@ fn should_generate_heartbeats() { // buffer new validators Session::rotate_session(); // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + VALIDATORS.with(|l| { + *l.borrow_mut() = + Some(WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6")) + }); Session::rotate_session(); // when @@ -260,7 +270,9 @@ fn should_cleanup_received_heartbeats_on_session_end() { new_test_ext().execute_with(|| { advance_session(); - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3])); + VALIDATORS.with(|l| { + *l.borrow_mut() = Some(WeakBoundedVec::try_from(vec![1, 2, 3]).expect("MaxKeys >= 3")) + }); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one @@ -270,7 +282,7 @@ fn should_cleanup_received_heartbeats_on_session_end() { assert_eq!(Session::validators(), vec![1, 2, 3]); // send an heartbeat from authority id 0 at session 2 - let _ = heartbeat(1, 2, 0, 1.into(), Session::validators()).unwrap(); + let _ = heartbeat(1, 2, 0, 1.into(), Session::validators().to_vec()).unwrap(); // the heartbeat is stored assert!(!ImOnline::received_heartbeats(&2, &0).is_none()); @@ -291,7 +303,10 @@ fn should_mark_online_validator_when_block_is_authored() { new_test_ext().execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + VALIDATORS.with(|l| { + *l.borrow_mut() = + Some(WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6")) + }); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -328,7 +343,10 @@ fn should_not_send_a_report_if_already_online() { ext.execute_with(|| { advance_session(); // given - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![1, 2, 3, 4, 5, 6])); + VALIDATORS.with(|l| { + *l.borrow_mut() = + Some(WeakBoundedVec::try_from(vec![1, 2, 3, 4, 5, 6]).expect("MaxKeys >= 6")) + }); assert_eq!(Session::validators(), Vec::::new()); // enact the change and buffer another one advance_session(); @@ -391,7 +409,9 @@ fn should_handle_missing_progress_estimates() { Session::rotate_session(); // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![0, 1, 2])); + VALIDATORS.with(|l| { + *l.borrow_mut() = Some(WeakBoundedVec::try_from(vec![0, 1, 2]).expect("MaxKeys >= 3")) + }); Session::rotate_session(); // we will return `None` on the next call to `estimate_current_session_progress` @@ -425,7 +445,9 @@ fn should_handle_non_linear_session_progress() { // mock the session length as being 10 blocks long, // enact the change and buffer another one - VALIDATORS.with(|l| *l.borrow_mut() = Some(vec![0, 1, 2])); + VALIDATORS.with(|l| { + *l.borrow_mut() = Some(WeakBoundedVec::try_from(vec![0, 1, 2]).expect("MaxKeys >= 3")) + }); // mock the session length has being 10 which should make us assume the fallback for half // session will be reached by block 5. diff --git a/frame/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index c920b0b900dff..c501bd80c55e0 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -21,17 +21,20 @@ mod mock; -use sp_std::{prelude::*, vec}; +use sp_std::{convert::TryFrom, prelude::*, vec}; use frame_benchmarking::{account, benchmarks}; -use frame_support::traits::{Currency, ValidatorSet, ValidatorSetWithIdentification}; +use frame_support::{ + traits::{Currency, Get, ReportOffence, ValidatorSet, ValidatorSetWithIdentification}, + WeakBoundedVec, +}; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; use sp_runtime::{ traits::{Convert, Saturating, StaticLookup, UniqueSaturatedInto}, Perbill, }; -use sp_staking::offence::{Offence, ReportOffence}; +use sp_staking::offence::Offence; use pallet_babe::BabeEquivocationOffence; use pallet_balances::Config as BalancesConfig; @@ -49,7 +52,6 @@ use pallet_staking::{ const SEED: u32 = 0; -const MAX_REPORTERS: u32 = 100; const MAX_OFFENDERS: u32 = 100; const MAX_NOMINATORS: u32 = 100; @@ -147,8 +149,9 @@ fn create_offender(n: u32, nominators: u32) -> Result, &' nominator_stashes.push(nominator_stash.clone()); } - let exposure = - Exposure { total: amount.clone() * n.into(), own: amount, others: individual_exposures }; + let others = WeakBoundedVec::<_, T::MaxIndividualExposures>::try_from(individual_exposures) + .map_err(|_| "nominators too big, runtime benchmarks may need adjustment")?; + let exposure = Exposure { total: amount.clone() * n.into(), own: amount, others }; let current_era = 0u32; Staking::::add_era_stakers(current_era.into(), stash.clone().into(), exposure); @@ -272,10 +275,10 @@ fn check_events::Event>>(expec benchmarks! { report_offence_im_online { - let r in 1 .. MAX_REPORTERS; + let r in 1 .. ::MaxReportersCount::get(); // we skip 1 offender, because in such case there is no slashing let o in 2 .. MAX_OFFENDERS; - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // Make r reporters let mut reporters = vec![]; @@ -302,7 +305,7 @@ benchmarks! { assert_eq!(System::::event_count(), 0); }: { let _ = ::ReportUnresponsiveness::report_offence( - reporters.clone(), + WeakBoundedVec::try_from(reporters.clone()).expect("We pushed less than MaxReportersCount"), offence ); } @@ -381,11 +384,11 @@ benchmarks! { } report_offence_grandpa { - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // for grandpa equivocation reports the number of reporters // and offenders is always 1 - let reporters = vec![account("reporter", 1, SEED)]; + let reporters = WeakBoundedVec::try_from(vec![account("reporter", 1, SEED)]).expect("MaxReportersCount > 0"); // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); @@ -416,11 +419,11 @@ benchmarks! { } report_offence_babe { - let n in 0 .. MAX_NOMINATORS.min(::MAX_NOMINATIONS); + let n in 0 .. MAX_NOMINATORS.min(::MaxNominations::get()); // for babe equivocation reports the number of reporters // and offenders is always 1 - let reporters = vec![account("reporter", 1, SEED)]; + let reporters = WeakBoundedVec::try_from(vec![account("reporter", 1, SEED)]).expect("MaxReportersCount > 0"); // make sure reporters actually get rewarded Staking::::set_slash_reward_fraction(Perbill::one()); diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 3097f9b95be3f..018893a4b11fd 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -89,7 +89,7 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -129,6 +129,8 @@ impl pallet_session::Config for Test { type Event = Event; type ValidatorId = AccountId; type ValidatorIdOf = pallet_staking::StashOf; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } @@ -144,10 +146,19 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; - pub const MaxKeys: u32 = 10_000; - pub const MaxPeerInHeartbeats: u32 = 10_000; - pub const MaxPeerDataEncodingSize: u32 = 1_000; + pub const MaxRewardableIndividualExposures: u32 = 64; + pub const MaxIndividualExposures: u32 = 64; + pub const MaxPeerInHeartbeats: u32 = 10_000; + pub const MaxPeerDataEncodingSize: u32 = 1_000; + pub const MaxNominations: u32 = 16; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 10; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 4_000; + pub const MaxKeysEncodingSize: u32 = 1_000; + pub const MaxUnlockingChunks: u32 = 32; } pub type Extrinsic = sp_runtime::testing::TestXt; @@ -155,10 +166,11 @@ pub type Extrinsic = sp_runtime::testing::TestXt; impl onchain::Config for Test { type Accuracy = Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -173,7 +185,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; @@ -189,15 +210,17 @@ impl pallet_im_online::Config for Test { type ReportUnresponsiveness = Offences; type UnsignedPriority = (); type WeightInfo = (); - type MaxKeys = MaxKeys; + type MaxKeys = MaxValidatorsCount; type MaxPeerInHeartbeats = MaxPeerInHeartbeats; type MaxPeerDataEncodingSize = MaxPeerDataEncodingSize; + type MaxReportersCount = MaxReportersCount; } impl pallet_offences::Config for Test { type Event = Event; type IdentificationTuple = pallet_session::historical::IdentificationTuple; type OnOffenceHandler = Staking; + type MaxReportersCount = MaxReportersCount; } impl frame_system::offchain::SendTransactionTypes for Test diff --git a/frame/offences/src/lib.rs b/frame/offences/src/lib.rs index d50bc55f88357..7fa6adca23d53 100644 --- a/frame/offences/src/lib.rs +++ b/frame/offences/src/lib.rs @@ -26,11 +26,16 @@ mod migration; mod mock; mod tests; -use codec::{Decode, Encode}; -use frame_support::weights::Weight; +use codec::{Decode, Encode, MaxEncodedLen}; +use frame_support::{ + pallet_prelude::Get, + traits::{OffenceDetails, OnOffenceHandler, ReportOffence}, + weights::Weight, + WeakBoundedVec, +}; use sp_runtime::{traits::Hash, Perbill}; use sp_staking::{ - offence::{Kind, Offence, OffenceDetails, OffenceError, OnOffenceHandler, ReportOffence}, + offence::{Kind, Offence, OffenceError}, SessionIndex, }; use sp_std::prelude::*; @@ -59,9 +64,16 @@ pub mod pallet { /// The overarching event type. type Event: From + IsType<::Event>; /// Full identification of the validator. - type IdentificationTuple: Parameter + Ord; + type IdentificationTuple: Parameter + Ord + MaxEncodedLen; /// A handler called for every offence report. - type OnOffenceHandler: OnOffenceHandler; + type OnOffenceHandler: OnOffenceHandler< + Self::AccountId, + Self::IdentificationTuple, + Weight, + Self::MaxReportersCount, + >; + /// Maximum number of reporters. + type MaxReportersCount: Get; } /// The primary structure that holds all offence records keyed by report identifiers. @@ -71,7 +83,7 @@ pub mod pallet { _, Twox64Concat, ReportIdOf, - OffenceDetails, + OffenceDetails, >; /// A vector of reports of the same kind that happened at the same time slot. @@ -120,11 +132,14 @@ pub mod pallet { } impl> - ReportOffence for Pallet + ReportOffence for Pallet where T::IdentificationTuple: Clone, { - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { + fn report_offence( + reporters: WeakBoundedVec, + offence: O, + ) -> Result<(), OffenceError> { let offenders = offence.offenders(); let time_slot = offence.time_slot(); let validator_set_count = offence.validator_set_count(); @@ -182,7 +197,7 @@ impl Pallet { /// Triages the offence report and returns the set of offenders that was involved in unique /// reports along with the list of the concurrent offences. fn triage_offence_report>( - reporters: Vec, + reporters: WeakBoundedVec, time_slot: &O::TimeSlot, offenders: Vec, ) -> Option> { @@ -222,7 +237,8 @@ impl Pallet { struct TriageOutcome { /// Other reports for the same report kinds. - concurrent_offenders: Vec>, + concurrent_offenders: + Vec>, } /// An auxiliary struct for working with storage of indexes localized for a specific offence diff --git a/frame/offences/src/migration.rs b/frame/offences/src/migration.rs index b6e32cbe69e26..df4ff048d2bac 100644 --- a/frame/offences/src/migration.rs +++ b/frame/offences/src/migration.rs @@ -15,16 +15,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -use super::{Config, OffenceDetails, Perbill, SessionIndex}; +use super::{Config, Perbill, SessionIndex}; use frame_support::{ - generate_storage_alias, pallet_prelude::ValueQuery, traits::Get, weights::Weight, + generate_storage_alias, + pallet_prelude::ValueQuery, + traits::{Get, OffenceDetails, OnOffenceHandler}, + weights::Weight, }; -use sp_staking::offence::OnOffenceHandler; use sp_std::vec::Vec; /// Type of data stored as a deferred offence type DeferredOffenceOf = ( - Vec::AccountId, ::IdentificationTuple>>, + Vec< + OffenceDetails< + ::AccountId, + ::IdentificationTuple, + ::MaxReportersCount, + >, + >, Vec, SessionIndex, ); @@ -52,9 +60,8 @@ pub fn remove_deferred_storage() -> Weight { mod test { use super::*; use crate::mock::{new_test_ext, with_on_offence_fractions, Offences, Runtime as T}; - use frame_support::traits::OnRuntimeUpgrade; + use frame_support::traits::{OffenceDetails, OnRuntimeUpgrade}; use sp_runtime::Perbill; - use sp_staking::offence::OffenceDetails; #[test] fn should_resubmit_deferred_offences() { @@ -68,9 +75,10 @@ mod test { let offence_details = OffenceDetails::< ::AccountId, ::IdentificationTuple, + ::MaxReportersCount, > { offender: 5, - reporters: vec![], + reporters: Default::default(), }; // push deferred offence diff --git a/frame/offences/src/mock.rs b/frame/offences/src/mock.rs index 5e4c94944b6fd..925f72a4c7a34 100644 --- a/frame/offences/src/mock.rs +++ b/frame/offences/src/mock.rs @@ -21,9 +21,10 @@ use crate as offences; use crate::Config; -use codec::Encode; +use codec::{Encode, MaxEncodedLen}; use frame_support::{ parameter_types, + traits::OffenceDetails, weights::{ constants::{RocksDbWeight, WEIGHT_PER_SECOND}, Weight, @@ -36,7 +37,7 @@ use sp_runtime::{ Perbill, }; use sp_staking::{ - offence::{self, Kind, OffenceDetails}, + offence::{self, Kind}, SessionIndex, }; use std::cell::RefCell; @@ -48,11 +49,14 @@ thread_local! { pub static OFFENCE_WEIGHT: RefCell = RefCell::new(Default::default()); } -impl offence::OnOffenceHandler +impl< + Reporter: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Offender: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + > frame_support::traits::OnOffenceHandler for OnOffenceHandler { fn on_offence( - _offenders: &[OffenceDetails], + _offenders: &[OffenceDetails], slash_fraction: &[Perbill], _offence_session: SessionIndex, ) -> Weight { @@ -84,6 +88,7 @@ frame_support::construct_runtime!( parameter_types! { pub const BlockHashCount: u64 = 250; + pub const MaxReportersCount: u32 = 100; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(2 * WEIGHT_PER_SECOND); } @@ -117,6 +122,7 @@ impl Config for Runtime { type Event = Event; type IdentificationTuple = u64; type OnOffenceHandler = OnOffenceHandler; + type MaxReportersCount = MaxReportersCount; } pub fn new_test_ext() -> sp_io::TestExternalities { @@ -129,7 +135,10 @@ pub fn new_test_ext() -> sp_io::TestExternalities { pub const KIND: [u8; 16] = *b"test_report_1234"; /// Returns all offence details for the specific `kind` happened at the specific time slot. -pub fn offence_reports(kind: Kind, time_slot: u128) -> Vec> { +pub fn offence_reports( + kind: Kind, + time_slot: u128, +) -> Vec> { >::get(&kind, &time_slot.encode()) .into_iter() .map(|report_id| { diff --git a/frame/offences/src/tests.rs b/frame/offences/src/tests.rs index 18cfa9410a6c6..b79909318aa49 100644 --- a/frame/offences/src/tests.rs +++ b/frame/offences/src/tests.rs @@ -37,7 +37,7 @@ fn should_report_an_authority_and_trigger_on_offence() { let offence = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; // when - Offences::report_offence(vec![], offence).unwrap(); + Offences::report_offence(Default::default(), offence).unwrap(); // then with_on_offence_fractions(|f| { @@ -54,7 +54,7 @@ fn should_not_report_the_same_authority_twice_in_the_same_slot() { assert_eq!(offence_reports(KIND, time_slot), vec![]); let offence = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; - Offences::report_offence(vec![], offence.clone()).unwrap(); + Offences::report_offence(Default::default(), offence.clone()).unwrap(); with_on_offence_fractions(|f| { assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); f.clear(); @@ -62,7 +62,10 @@ fn should_not_report_the_same_authority_twice_in_the_same_slot() { // when // report for the second time - assert_eq!(Offences::report_offence(vec![], offence), Err(OffenceError::DuplicateReport)); + assert_eq!( + Offences::report_offence(Default::default(), offence), + Err(OffenceError::DuplicateReport) + ); // then with_on_offence_fractions(|f| { @@ -79,7 +82,7 @@ fn should_report_in_different_time_slot() { assert_eq!(offence_reports(KIND, time_slot), vec![]); let mut offence = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; - Offences::report_offence(vec![], offence.clone()).unwrap(); + Offences::report_offence(Default::default(), offence.clone()).unwrap(); with_on_offence_fractions(|f| { assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); f.clear(); @@ -88,7 +91,7 @@ fn should_report_in_different_time_slot() { // when // report for the second time offence.time_slot += 1; - Offences::report_offence(vec![], offence).unwrap(); + Offences::report_offence(Default::default(), offence).unwrap(); // then with_on_offence_fractions(|f| { @@ -107,7 +110,7 @@ fn should_deposit_event() { let offence = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; // when - Offences::report_offence(vec![], offence).unwrap(); + Offences::report_offence(Default::default(), offence).unwrap(); // then assert_eq!( @@ -129,7 +132,7 @@ fn doesnt_deposit_event_for_dups() { assert_eq!(offence_reports(KIND, time_slot), vec![]); let offence = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; - Offences::report_offence(vec![], offence.clone()).unwrap(); + Offences::report_offence(Default::default(), offence.clone()).unwrap(); with_on_offence_fractions(|f| { assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); f.clear(); @@ -137,7 +140,10 @@ fn doesnt_deposit_event_for_dups() { // when // report for the second time - assert_eq!(Offences::report_offence(vec![], offence), Err(OffenceError::DuplicateReport)); + assert_eq!( + Offences::report_offence(Default::default(), offence), + Err(OffenceError::DuplicateReport) + ); // then // there is only one event. @@ -167,23 +173,23 @@ fn reports_if_an_offence_is_dup() { // the report for authority 0 at time slot 42 should not be a known // offence - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); // we report an offence for authority 0 at time slot 42 - Offences::report_offence(vec![], test_offence.clone()).unwrap(); + Offences::report_offence(Default::default(), test_offence.clone()).unwrap(); // the same report should be a known offence now - assert!(>::is_known_offence( + assert!(>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); // and reporting it again should yield a duplicate report error assert_eq!( - Offences::report_offence(vec![], test_offence.clone()), + Offences::report_offence(Default::default(), test_offence.clone()), Err(OffenceError::DuplicateReport) ); @@ -191,18 +197,18 @@ fn reports_if_an_offence_is_dup() { test_offence.offenders.push(1); // it should not be a known offence anymore - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence.offenders, &test_offence.time_slot )); // and reporting it again should work without any error - assert_eq!(Offences::report_offence(vec![], test_offence.clone()), Ok(())); + assert_eq!(Offences::report_offence(Default::default(), test_offence.clone()), Ok(())); // creating a new offence for the same authorities on the next slot // should be considered a new offence and thefore not known let test_offence_next_slot = offence(time_slot + 1, vec![0, 1]); - assert!(!>::is_known_offence( + assert!(!>::is_known_offence( &test_offence_next_slot.offenders, &test_offence_next_slot.time_slot )); @@ -220,7 +226,7 @@ fn should_properly_count_offences() { let offence1 = Offence { validator_set_count: 5, time_slot, offenders: vec![5] }; let offence2 = Offence { validator_set_count: 5, time_slot, offenders: vec![4] }; - Offences::report_offence(vec![], offence1).unwrap(); + Offences::report_offence(Default::default(), offence1).unwrap(); with_on_offence_fractions(|f| { assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); f.clear(); @@ -228,15 +234,15 @@ fn should_properly_count_offences() { // when // report for the second time - Offences::report_offence(vec![], offence2).unwrap(); + Offences::report_offence(Default::default(), offence2).unwrap(); // then // the 1st authority should have count 2 and the 2nd one should be reported only once. assert_eq!( offence_reports(KIND, time_slot), vec![ - OffenceDetails { offender: 5, reporters: vec![] }, - OffenceDetails { offender: 4, reporters: vec![] }, + OffenceDetails { offender: 5, reporters: Default::default() }, + OffenceDetails { offender: 4, reporters: Default::default() }, ] ); }); @@ -257,7 +263,7 @@ fn should_properly_sort_offences() { Offence { validator_set_count: 5, time_slot: time_slot + 1, offenders: vec![6, 7] }; let offence4 = Offence { validator_set_count: 5, time_slot: time_slot - 1, offenders: vec![3] }; - Offences::report_offence(vec![], offence1).unwrap(); + Offences::report_offence(Default::default(), offence1).unwrap(); with_on_offence_fractions(|f| { assert_eq!(f.clone(), vec![Perbill::from_percent(25)]); f.clear(); @@ -265,9 +271,9 @@ fn should_properly_sort_offences() { // when // report for the second time - Offences::report_offence(vec![], offence2).unwrap(); - Offences::report_offence(vec![], offence3).unwrap(); - Offences::report_offence(vec![], offence4).unwrap(); + Offences::report_offence(Default::default(), offence2).unwrap(); + Offences::report_offence(Default::default(), offence3).unwrap(); + Offences::report_offence(Default::default(), offence4).unwrap(); // then let same_kind_reports = Vec::<(u128, sp_core::H256)>::decode( diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index 8ca713b1bbf61..b2c1e8a79c271 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -27,7 +27,7 @@ use sp_std::{prelude::*, vec}; use frame_benchmarking::benchmarks; use frame_support::{ codec::Decode, - traits::{KeyOwnerProofSystem, OnInitialize}, + traits::{Get, KeyOwnerProofSystem, OnInitialize}, }; use frame_system::RawOrigin; use pallet_session::{historical::Module as Historical, Pallet as Session, *}; @@ -53,10 +53,10 @@ impl OnInitialize for Pallet { benchmarks! { set_keys { - let n = ::MAX_NOMINATIONS; + let n = ::MaxNominations::get(); let (v_stash, _) = create_validator_with_nominators::( n, - ::MAX_NOMINATIONS, + ::MaxNominations::get(), false, RewardDestination::Staked, )?; @@ -69,10 +69,10 @@ benchmarks! { }: _(RawOrigin::Signed(v_controller), keys, proof) purge_keys { - let n = ::MAX_NOMINATIONS; + let n = ::MaxNominations::get(); let (v_stash, _) = create_validator_with_nominators::( n, - ::MAX_NOMINATIONS, + ::MaxNominations::get(), false, RewardDestination::Staked )?; diff --git a/frame/session/benchmarking/src/mock.rs b/frame/session/benchmarking/src/mock.rs index f534cc097e8a0..26fe930681275 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -94,7 +94,7 @@ impl pallet_timestamp::Config for Test { type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -129,6 +129,8 @@ impl pallet_session::Config for Test { type Event = Event; type ValidatorId = AccountId; type ValidatorIdOf = pallet_staking::StashOf; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } pallet_staking_reward_curve::build! { @@ -143,8 +145,18 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxRewardableIndividualExposures: u32 = 64; + pub const MaxIndividualExposures: u32 = 64; pub const UnsignedPriority: u64 = 1 << 20; + pub const MaxNominations: u32 = 16; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 10; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 4_000; + pub const MaxKeysEncodingSize: u32 = 1_000; + pub const MaxUnlockingChunks: u32 = 32; } pub type Extrinsic = sp_runtime::testing::TestXt; @@ -160,10 +172,11 @@ where impl onchain::Config for Test { type Accuracy = sp_runtime::Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type Currency = Balances; type UnixTime = pallet_timestamp::Pallet; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -178,7 +191,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; diff --git a/frame/session/src/historical/mod.rs b/frame/session/src/historical/mod.rs index 0801b2aca1701..c829a970f3b25 100644 --- a/frame/session/src/historical/mod.rs +++ b/frame/session/src/historical/mod.rs @@ -31,7 +31,7 @@ use codec::{Decode, Encode}; use frame_support::{ decl_module, decl_storage, print, traits::{ValidatorSet, ValidatorSetWithIdentification}, - Parameter, + Parameter, WeakBoundedVec, }; use sp_runtime::{ traits::{Convert, OpaqueKeys}, @@ -118,7 +118,7 @@ impl ValidatorSet for Module { } fn validators() -> Vec { - super::Pallet::::validators() + super::Pallet::::validators().to_vec() } } @@ -129,16 +129,18 @@ impl ValidatorSetWithIdentification for Module { /// Specialization of the crate-level `SessionManager` which returns the set of full identification /// when creating a new session. -pub trait SessionManager: - crate::SessionManager +pub trait SessionManager: + crate::SessionManager { /// If there was a validator set change, its returns the set of new validators along with their /// full identifications. - fn new_session(new_index: SessionIndex) -> Option>; + fn new_session( + new_index: SessionIndex, + ) -> Option>; fn new_session_genesis( new_index: SessionIndex, - ) -> Option> { - >::new_session(new_index) + ) -> Option> { + >::new_session(new_index) } fn start_session(start_index: SessionIndex); fn end_session(end_index: SessionIndex); @@ -148,20 +150,27 @@ pub trait SessionManager: /// sets the historical trie root of the ending session. pub struct NoteHistoricalRoot(sp_std::marker::PhantomData<(T, I)>); -impl> NoteHistoricalRoot { - fn do_new_session(new_index: SessionIndex, is_genesis: bool) -> Option> { +impl< + T: Config, + I: SessionManager, + > NoteHistoricalRoot +{ + fn do_new_session( + new_index: SessionIndex, + is_genesis: bool, + ) -> Option> { StoredRange::mutate(|range| { range.get_or_insert_with(|| (new_index, new_index)).1 = new_index + 1; }); let new_validators_and_id = if is_genesis { - >::new_session_genesis(new_index) + >::new_session_genesis(new_index) } else { - >::new_session(new_index) + >::new_session(new_index) }; let new_validators_opt = new_validators_and_id .as_ref() - .map(|new_validators| new_validators.iter().map(|(v, _id)| v.clone()).collect()); + .map(|new_validators| new_validators.map_collect_ref(|(v, _id)| v.clone())); if let Some(new_validators) = new_validators_and_id { let count = new_validators.len() as ValidatorCount; @@ -183,25 +192,30 @@ impl> NoteHi } } -impl crate::SessionManager for NoteHistoricalRoot +impl crate::SessionManager + for NoteHistoricalRoot where - I: SessionManager, + I: SessionManager, { - fn new_session(new_index: SessionIndex) -> Option> { + fn new_session( + new_index: SessionIndex, + ) -> Option> { Self::do_new_session(new_index, false) } - fn new_session_genesis(new_index: SessionIndex) -> Option> { + fn new_session_genesis( + new_index: SessionIndex, + ) -> Option> { Self::do_new_session(new_index, true) } fn start_session(start_index: SessionIndex) { - >::start_session(start_index) + >::start_session(start_index) } fn end_session(end_index: SessionIndex) { onchain::store_session_validator_set_to_offchain::(end_index); - >::end_session(end_index) + >::end_session(end_index) } } diff --git a/frame/session/src/lib.rs b/frame/session/src/lib.rs index 6779285ee3187..d441034d730dc 100644 --- a/frame/session/src/lib.rs +++ b/frame/session/src/lib.rs @@ -95,7 +95,7 @@ //! use pallet_session as session; //! //! fn validators() -> Vec<::ValidatorId> { -//! >::validators() +//! >::validators().to_vec() //! } //! # fn main(){} //! ``` @@ -123,7 +123,7 @@ use frame_support::{ StorageVersion, ValidatorRegistration, ValidatorSet, }, weights::Weight, - Parameter, + Parameter, WeakBoundedVec, }; use sp_runtime::{ traits::{AtLeast32BitUnsigned, Convert, Member, One, OpaqueKeys, Zero}, @@ -223,7 +223,7 @@ impl< } /// A trait for managing creation of new validator set. -pub trait SessionManager { +pub trait SessionManager { /// Plan a new session, and optionally provide the new validator set. /// /// Even if the validator-set is the same as before, if any underlying economic conditions have @@ -237,12 +237,16 @@ pub trait SessionManager { /// /// `new_session(session)` is guaranteed to be called before `end_session(session-1)`. In other /// words, a new session must always be planned before an ongoing one can be finished. - fn new_session(new_index: SessionIndex) -> Option>; + fn new_session( + new_index: SessionIndex, + ) -> Option>; /// Same as `new_session`, but it this should only be called at genesis. /// /// The session manager might decide to treat this in a different way. Default impl is simply /// using [`new_session`](Self::new_session). - fn new_session_genesis(new_index: SessionIndex) -> Option> { + fn new_session_genesis( + new_index: SessionIndex, + ) -> Option> { Self::new_session(new_index) } /// End the session. @@ -256,8 +260,8 @@ pub trait SessionManager { fn start_session(start_index: SessionIndex); } -impl SessionManager for () { - fn new_session(_: SessionIndex) -> Option> { +impl SessionManager for () { + fn new_session(_: SessionIndex) -> Option> { None } fn start_session(_: SessionIndex) {} @@ -368,6 +372,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(super) trait Store)] #[pallet::storage_version(STORAGE_VERSION)] + #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -396,16 +401,27 @@ pub mod pallet { type NextSessionRotation: EstimateNextSessionRotation; /// Handler for managing new session. - type SessionManager: SessionManager; + type SessionManager: SessionManager; /// Handler when a session has changed. type SessionHandler: SessionHandler; /// The keys. - type Keys: OpaqueKeys + Member + Parameter + Default + MaybeSerializeDeserialize; + type Keys: OpaqueKeys + + Member + + Parameter + + Default + + MaybeSerializeDeserialize + + MaxEncodedLen; /// Weight information for extrinsics in this pallet. type WeightInfo: WeightInfo; + + /// Maximum number of validators. + type MaxValidatorsCount: Get; + + /// Maximum size of the encoding of the validator's keys + type MaxKeysEncodingSize: Get; } #[pallet::genesis_config] @@ -458,7 +474,10 @@ pub mod pallet { "No initial validator provided by `SessionManager`, use \ session config keys to generate initial validator set.", ); - self.keys.iter().map(|x| x.1.clone()).collect() + WeakBoundedVec::<_, T::MaxValidatorsCount>::try_from( + self.keys.iter().map(|x| x.1.clone()).collect::>(), + ) + .expect("Number of validators provided for session 0 is too big") }); assert!( !initial_validators_0.is_empty(), @@ -472,14 +491,11 @@ pub mod pallet { "Empty validator set for session 1 in genesis block!" ); - let queued_keys: Vec<_> = initial_validators_1 - .iter() - .cloned() - .map(|v| (v.clone(), >::load_keys(&v).unwrap_or_default())) - .collect(); + let queued_keys = initial_validators_1 + .map_collect(|v| (v.clone(), >::load_keys(&v).unwrap_or_default())); // Tell everyone about the genesis session keys - T::SessionHandler::on_genesis_session::(&queued_keys); + T::SessionHandler::on_genesis_session::(&(queued_keys.to_vec())); >::put(initial_validators_0); >::put(queued_keys); @@ -491,7 +507,8 @@ pub mod pallet { /// The current set of validators. #[pallet::storage] #[pallet::getter(fn validators)] - pub type Validators = StorageValue<_, Vec, ValueQuery>; + pub type Validators = + StorageValue<_, WeakBoundedVec, ValueQuery>; /// Current index of the session. #[pallet::storage] @@ -507,7 +524,11 @@ pub mod pallet { /// will be used to determine the validator's session keys. #[pallet::storage] #[pallet::getter(fn queued_keys)] - pub type QueuedKeys = StorageValue<_, Vec<(T::ValidatorId, T::Keys)>, ValueQuery>; + pub type QueuedKeys = StorageValue< + _, + WeakBoundedVec<(T::ValidatorId, T::Keys), T::MaxValidatorsCount>, + ValueQuery, + >; /// Indices of disabled validators. /// @@ -516,7 +537,8 @@ pub mod pallet { /// a new set of identities. #[pallet::storage] #[pallet::getter(fn disabled_validators)] - pub type DisabledValidators = StorageValue<_, Vec, ValueQuery>; + pub type DisabledValidators = + StorageValue<_, WeakBoundedVec, ValueQuery>; /// The next session keys for a validator. #[pallet::storage] @@ -525,8 +547,13 @@ pub mod pallet { /// The owner of a key. The key is the `KeyTypeId` + the encoded key. #[pallet::storage] - pub type KeyOwner = - StorageMap<_, Twox64Concat, (KeyTypeId, Vec), T::ValidatorId, OptionQuery>; + pub type KeyOwner = StorageMap< + _, + Twox64Concat, + (KeyTypeId, WeakBoundedVec), + T::ValidatorId, + OptionQuery, + >; #[pallet::event] #[pallet::generate_deposit(pub(super) fn deposit_event)] @@ -638,8 +665,7 @@ impl Pallet { // Get queued session keys and validators. let session_keys = >::get(); - let validators = - session_keys.iter().map(|(validator, _)| validator.clone()).collect::>(); + let validators = session_keys.map_collect_ref(|(validator, _)| validator.clone()); >::put(&validators); if changed { @@ -686,14 +712,11 @@ impl Pallet { } } }; - let queued_amalgamated = next_validators - .into_iter() - .map(|a| { - let k = Self::load_keys(&a).unwrap_or_default(); - check_next_changed(&k); - (a, k) - }) - .collect::>(); + let queued_amalgamated = next_validators.map_collect(|a| { + let k = Self::load_keys(&a).unwrap_or_default(); + check_next_changed(&k); + (a, k) + }); (queued_amalgamated, changed) }; @@ -716,7 +739,11 @@ impl Pallet { >::mutate(|disabled| { if let Err(index) = disabled.binary_search(&i) { - disabled.insert(index, i); + if disabled.try_insert(index, i).is_err() { + // This should never fail + log::error!(target: "runtime::session", "disabling validator index {:?}", i); + return false + } T::SessionHandler::on_disabled(i); return true } @@ -779,12 +806,11 @@ impl Pallet { Some(new_keys) }); - let _ = >::translate::, _>(|k| { - k.map(|k| { - k.into_iter() - .map(|(val, old_keys)| (val.clone(), upgrade(val, old_keys))) - .collect::>() - }) + let _ = >::translate::< + WeakBoundedVec<(T::ValidatorId, Old), T::MaxValidatorsCount>, + _, + >(|k| { + k.map(|k| k.map_collect(|(val, old_keys)| (val.clone(), upgrade(val, old_keys)))) }); } @@ -878,15 +904,27 @@ impl Pallet { /// Query the owner of a session key by returning the owner's validator ID. pub fn key_owner(id: KeyTypeId, key_data: &[u8]) -> Option { - >::get((id, key_data)) + let key_data_bounded = WeakBoundedVec::<_, T::MaxKeysEncodingSize>::force_from( + key_data.to_vec(), + Some("frame_session.key_owner"), + ); + >::get((id, key_data_bounded)) } fn put_key_owner(id: KeyTypeId, key_data: &[u8], v: &T::ValidatorId) { - >::insert((id, key_data), v) + let key_data_bounded = WeakBoundedVec::<_, T::MaxKeysEncodingSize>::force_from( + key_data.to_vec(), + Some("frame_session.put_key_owner"), + ); + >::insert((id, key_data_bounded), v) } fn clear_key_owner(id: KeyTypeId, key_data: &[u8]) { - >::remove((id, key_data)); + let key_data_bounded = WeakBoundedVec::<_, T::MaxKeysEncodingSize>::force_from( + key_data.to_vec(), + Some("frame_session.clear_key_owner"), + ); + >::remove((id, key_data_bounded)); } } @@ -905,7 +943,7 @@ impl ValidatorSet for Pallet { } fn validators() -> Vec { - Pallet::::validators() + Pallet::::validators().to_vec() } } diff --git a/frame/session/src/mock.rs b/frame/session/src/mock.rs index 6db7727fa5391..3dd80fd4b0d47 100644 --- a/frame/session/src/mock.rs +++ b/frame/session/src/mock.rs @@ -100,8 +100,10 @@ frame_support::construct_runtime!( ); thread_local! { - pub static VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); - pub static NEXT_VALIDATORS: RefCell> = RefCell::new(vec![1, 2, 3]); + pub static VALIDATORS: RefCell> = + RefCell::new(WeakBoundedVec::try_from(vec![1, 2, 3]).expect("MaxValidatorsCount > 3")); + pub static NEXT_VALIDATORS: RefCell> = + RefCell::new(WeakBoundedVec::try_from(vec![1, 2, 3]).expect("MaxValidatorsCount > 3")); pub static AUTHORITIES: RefCell> = RefCell::new(vec![UintAuthorityId(1), UintAuthorityId(2), UintAuthorityId(3)]); pub static FORCE_SESSION_END: RefCell = RefCell::new(false); @@ -153,10 +155,10 @@ impl SessionHandler for TestSessionHandler { } pub struct TestSessionManager; -impl SessionManager for TestSessionManager { +impl SessionManager for TestSessionManager { fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} - fn new_session(_: SessionIndex) -> Option> { + fn new_session(_: SessionIndex) -> Option> { if !TEST_SESSION_CHANGED.with(|l| *l.borrow()) { VALIDATORS.with(|v| { let mut v = v.borrow_mut(); @@ -174,12 +176,14 @@ impl SessionManager for TestSessionManager { } #[cfg(feature = "historical")] -impl crate::historical::SessionManager for TestSessionManager { +impl crate::historical::SessionManager for TestSessionManager { fn end_session(_: SessionIndex) {} fn start_session(_: SessionIndex) {} - fn new_session(new_index: SessionIndex) -> Option> { - >::new_session(new_index) - .map(|vals| vals.into_iter().map(|val| (val, val)).collect()) + fn new_session( + new_index: SessionIndex, + ) -> Option> { + >::new_session(new_index) + .map(|vals| vals.map_collect(|val| (val, val))) } } @@ -200,6 +204,8 @@ pub fn session_changed() -> bool { } pub fn set_next_validators(next: Vec) { + let next = WeakBoundedVec::<_, MaxValidatorsCount>::try_from(next) + .expect("frame_session.set_next_validators: some test parameters need changing"); NEXT_VALIDATORS.with(|v| *v.borrow_mut() = next); } @@ -236,6 +242,8 @@ pub fn new_test_ext() -> sp_io::TestExternalities { parameter_types! { pub const MinimumPeriod: u64 = 5; pub const BlockHashCount: u64 = 250; + pub const MaxValidatorsCount: u32 = 1_000; + pub const MaxKeysEncodingSize: u32 = 1_000; pub BlockWeights: frame_system::limits::BlockWeights = frame_system::limits::BlockWeights::simple_max(1024); } @@ -296,6 +304,8 @@ impl Config for Test { type ValidatorIdOf = TestValidatorIdOf; type Keys = MockSessionKeys; type Event = Event; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type NextSessionRotation = (); type WeightInfo = (); } diff --git a/frame/staking/README.md b/frame/staking/README.md index 072353b1a586c..a1a0051a1c951 100644 --- a/frame/staking/README.md +++ b/frame/staking/README.md @@ -90,7 +90,7 @@ valid behavior_ while _punishing any misbehavior or lack of availability_. Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the `payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to the -validator as well as its nominators. Only the [`Config::MaxNominatorRewardedPerValidator`] +validator as well as its nominators. Only the [`Config::MaxRewardableIndividualExposures`] biggest stakers can claim their reward. This is to limit the i/o cost to mutate storage for each nominator's account. @@ -133,22 +133,31 @@ The Staking module contains many public storage items and (im)mutable functions. ### Example: Rewarding a validator by id. ```rust -use frame_support::{decl_module, dispatch}; -use frame_system::ensure_signed; +use frame_support::pallet_prelude::*; +use frame_system::{ensure_signed, pallet_prelude::OriginFor}; use pallet_staking::{self as staking}; -pub trait Config: staking::Config {} - -decl_module! { - pub struct Module for enum Call where origin: T::Origin { - /// Reward a validator. - #[weight = 0] - pub fn reward_myself(origin) -> dispatch::DispatchResult { - let reported = ensure_signed(origin)?; - >::reward_by_ids(vec![(reported, 10)]); - Ok(()) - } +#[frame_support::pallet] +pub mod pallet { + use super::*; + + #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config + staking::Config {} + + #[pallet::call] + impl Pallet { + /// Reward a validator. + #[pallet::weight(0)] + pub fn reward_myself(origin: OriginFor) -> DispatchResult { + let reported = ensure_signed(origin)?; + >::reward_by_ids(vec![(reported, 10)]); + Ok(()) } + } } ``` @@ -176,7 +185,7 @@ Validators and nominators are rewarded at the end of each era. The total reward calculated using the era duration and the staking rate (the total amount of tokens staked by nominators and validators, divided by the total token supply). It aims to incentivize toward a defined staking rate. The full specification can be found -[here](https://research.web3.foundation/en/latest/polkadot/economics/1-token-economics.html#inflation-model). +[here](https://w3f-research.readthedocs.io/en/latest/polkadot/overview/2-token-economics.html#inflation-model). Total reward is split among validators and their nominators depending on the number of points they received during the era. Points are added to a validator using @@ -221,7 +230,7 @@ call can be used to actually withdraw the funds. Note that there is a limitation to the number of fund-chunks that can be scheduled to be unlocked in the future via [`unbond`](https://docs.rs/pallet-staking/latest/pallet_staking/enum.Call.html#variant.unbond). In case this maximum -(`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful +(`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a successful call to `withdraw_unbonded` to remove some of the chunks. ### Election Algorithm diff --git a/frame/staking/fuzzer/src/mock.rs b/frame/staking/fuzzer/src/mock.rs index d5ca78193b0c0..c84768fca77cc 100644 --- a/frame/staking/fuzzer/src/mock.rs +++ b/frame/staking/fuzzer/src/mock.rs @@ -117,7 +117,8 @@ impl pallet_session::SessionHandler for TestSessionHandler { _: bool, _: &[(AccountId, Ks)], _: &[(AccountId, Ks)], - ) {} + ) { + } fn on_disabled(_: u32) {} } @@ -146,7 +147,7 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxRewardableIndividualExposures: u32 = 64; pub const MaxIterations: u32 = 20; } @@ -167,10 +168,9 @@ impl frame_election_provider_support::ElectionProvider type Error = (); type DataProvider = pallet_staking::Module; - fn elect() -> Result< - (sp_npos_elections::Supports, frame_support::weights::Weight), - Self::Error - > { + fn elect( + ) -> Result<(sp_npos_elections::Supports, frame_support::weights::Weight), Self::Error> + { Err(()) } } @@ -194,7 +194,7 @@ impl pallet_staking::Config for Test { type Call = Call; type MaxIterations = MaxIterations; type MinSolutionScoreBump = (); - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; type UnsignedPriority = (); type OffchainSolutionWeightLimit = (); type WeightInfo = (); diff --git a/frame/staking/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index 80630818de7e6..4c8b22aa24728 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -32,7 +32,7 @@ use sp_runtime::{ Perbill, Percent, }; use sp_staking::SessionIndex; -use sp_std::prelude::*; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; pub use frame_benchmarking::{ account, benchmarks, impl_benchmark_test_suite, whitelist_account, whitelisted_caller, @@ -42,7 +42,7 @@ use sp_runtime::traits::{Bounded, One}; const SEED: u32 = 0; const MAX_SPANS: u32 = 100; -const MAX_VALIDATORS: u32 = 1000; +const MAX_VALIDATORS: u32 = 100; const MAX_NOMINATORS: u32 = 1000; const MAX_SLASHES: u32 = 1000; @@ -116,10 +116,12 @@ pub fn create_validator_with_nominators( assert_ne!(CounterForNominators::::get(), 0); // Give Era Points - let reward = EraRewardPoints:: { - total: points_total, - individual: points_individual.into_iter().collect(), - }; + let individual = BoundedBTreeMap::<_, _, T::MaxValidatorsCount>::try_from( + points_individual.into_iter().collect::>(), + ) + .map_err(|_| "Something weird, this means T::MaxValidatorsCount is zero")?; + let reward = + EraRewardPoints:: { total: points_total, individual }; let current_era = CurrentEra::::get().unwrap(); ErasRewardPoints::::insert(current_era, reward); @@ -354,17 +356,17 @@ benchmarks! { kick { // scenario: we want to kick `k` nominators from nominating us (we are a validator). // we'll assume that `k` is under 128 for the purposes of determining the slope. - // each nominator should have `T::MAX_NOMINATIONS` validators nominated, and our validator + // each nominator should have `T::MaxNominations` validators nominated, and our validator // should be somewhere in there. let k in 1 .. 128; - // these are the other validators; there are `T::MAX_NOMINATIONS - 1` of them, so - // there are a total of `T::MAX_NOMINATIONS` validators in the system. - let rest_of_validators = create_validators_with_seed::(T::MAX_NOMINATIONS - 1, 100, 415)?; + // these are the other validators; there are `T::MaxNominations - 1` of them, so + // there are a total of `T::MaxNominations` validators in the system. + let rest_of_validators = create_validators_with_seed::(T::MaxNominations::get() - 1, 100, 415)?; // this is the validator that will be kicking. let (stash, controller) = create_stash_controller::( - T::MAX_NOMINATIONS - 1, + T::MaxNominations::get() - 1, 100, Default::default(), )?; @@ -379,7 +381,7 @@ benchmarks! { for i in 0 .. k { // create a nominator stash. let (n_stash, n_controller) = create_stash_controller::( - T::MAX_NOMINATIONS + i, + T::MaxNominations::get() + i, 100, Default::default(), )?; @@ -414,9 +416,9 @@ benchmarks! { } } - // Worst case scenario, T::MAX_NOMINATIONS + // Worst case scenario, T::MaxNominations nominate { - let n in 1 .. T::MAX_NOMINATIONS; + let n in 1 .. T::MaxNominations::get(); // clean up any existing state. clear_validators_and_nominators::(); @@ -427,7 +429,7 @@ benchmarks! { // we are just doing an insert into the origin position. let scenario = ListScenario::::new(origin_weight, true)?; let (stash, controller) = create_stash_controller_with_balance::( - SEED + T::MAX_NOMINATIONS + 1, // make sure the account does not conflict with others + SEED + T::MaxNominations::get() + 1, // make sure the account does not conflict with others origin_weight, Default::default(), ).unwrap(); @@ -536,8 +538,11 @@ benchmarks! { let mut unapplied_slashes = Vec::new(); let era = EraIndex::one(); for _ in 0 .. MAX_SLASHES { - unapplied_slashes.push(UnappliedSlash::>::default()); + unapplied_slashes.push(UnappliedSlash::, T::MaxIndividualExposures, + T::MaxReportersCount>::default()); } + let unapplied_slashes = WeakBoundedVec::<_, T::MaxUnappliedSlashes>::try_from(unapplied_slashes) + .expect("MAX_SLASHES should be <= MaxUnappliedSlashes, runtime benchmarks need adjustment"); UnappliedSlashes::::insert(era, &unapplied_slashes); let slash_indices: Vec = (0 .. s).collect(); @@ -547,10 +552,10 @@ benchmarks! { } payout_stakers_dead_controller { - let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; + let n in 1 .. T::MaxRewardableIndividualExposures::get() as u32; let (validator, nominators) = create_validator_with_nominators::( n, - T::MaxNominatorRewardedPerValidator::get() as u32, + T::MaxRewardableIndividualExposures::get() as u32, true, RewardDestination::Controller, )?; @@ -580,10 +585,10 @@ benchmarks! { } payout_stakers_alive_staked { - let n in 1 .. T::MaxNominatorRewardedPerValidator::get() as u32; + let n in 1 .. T::MaxRewardableIndividualExposures::get() as u32; let (validator, nominators) = create_validator_with_nominators::( n, - T::MaxNominatorRewardedPerValidator::get() as u32, + T::MaxRewardableIndividualExposures::get() as u32, false, RewardDestination::Staked, )?; @@ -616,7 +621,7 @@ benchmarks! { } rebond { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let l in 1 .. ::MaxUnlockingChunks::get() as u32; // clean up any existing state. clear_validators_and_nominators::(); @@ -650,7 +655,8 @@ benchmarks! { let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()) + .expect("Size is smaller than MaxUnlockingChunks, qed"); } Ledger::::insert(controller.clone(), staking_ledger.clone()); let original_bonded: BalanceOf = staking_ledger.active; @@ -668,11 +674,11 @@ benchmarks! { HistoryDepth::::put(e); CurrentEra::::put(e); for i in 0 .. e { - >::insert(i, T::AccountId::default(), Exposure::>::default()); - >::insert(i, T::AccountId::default(), Exposure::>::default()); + >::insert(i, T::AccountId::default(), Exposure::default()); + >::insert(i, T::AccountId::default(), Exposure::default()); >::insert(i, T::AccountId::default(), ValidatorPrefs::default()); >::insert(i, BalanceOf::::one()); - >::insert(i, EraRewardPoints::::default()); + >::insert(i, EraRewardPoints::default()); >::insert(i, BalanceOf::::one()); ErasStartSessionIndex::::insert(i, i); } @@ -714,7 +720,7 @@ benchmarks! { create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -732,7 +738,7 @@ benchmarks! { create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -752,9 +758,13 @@ benchmarks! { } // Give Era Points - let reward = EraRewardPoints:: { + let individual = BoundedBTreeMap::<_, _, T::MaxValidatorsCount>::try_from( + points_individual.into_iter().collect::>(), + ) + .map_err(|_| "Too many validators, some runtime benchmarks may need adjustment")?; + let reward = EraRewardPoints { total: points_total, - individual: points_individual.into_iter().collect(), + individual, }; ErasRewardPoints::::insert(current_era, reward); @@ -778,7 +788,7 @@ benchmarks! { #[extra] do_slash { - let l in 1 .. MAX_UNLOCKING_CHUNKS as u32; + let l in 1 .. ::MaxUnlockingChunks::get() as u32; let (stash, controller) = create_stash_controller::(0, 100, Default::default())?; let mut staking_ledger = Ledger::::get(controller.clone()).unwrap(); let unlock_chunk = UnlockChunk::> { @@ -786,7 +796,8 @@ benchmarks! { era: EraIndex::zero(), }; for _ in 0 .. l { - staking_ledger.unlocking.push(unlock_chunk.clone()) + staking_ledger.unlocking.try_push(unlock_chunk.clone()) + .expect("Size is smaller than MaxUnlockingChunks, qed"); } Ledger::::insert(controller, staking_ledger); let slash_amount = T::Currency::minimum_balance() * 10u32.into(); @@ -812,7 +823,7 @@ benchmarks! { let s in 1 .. 20; let validators = create_validators_with_nominators_for_era::( - v, n, T::MAX_NOMINATIONS as usize, false, None + v, n, T::MaxNominations::get() as usize, false, None )? .into_iter() .map(|v| T::Lookup::lookup(v).unwrap()) @@ -835,7 +846,7 @@ benchmarks! { let n = MAX_NOMINATORS; let _ = create_validators_with_nominators_for_era::( - v, n, T::MAX_NOMINATIONS as usize, false, None + v, n, T::MaxNominations::get() as usize, false, None )?; }: { let targets = >::get_npos_targets(); @@ -849,13 +860,11 @@ benchmarks! { BalanceOf::::max_value(), BalanceOf::::max_value(), Some(u32::MAX), - Some(u32::MAX), Some(Percent::max_value()) ) verify { assert_eq!(MinNominatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MinValidatorBond::::get(), BalanceOf::::max_value()); assert_eq!(MaxNominatorsCount::::get(), Some(u32::MAX)); - assert_eq!(MaxValidatorsCount::::get(), Some(u32::MAX)); assert_eq!(ChillThreshold::::get(), Some(Percent::from_percent(100))); } @@ -877,7 +886,6 @@ benchmarks! { BalanceOf::::max_value(), BalanceOf::::max_value(), Some(0), - Some(0), Some(Percent::from_percent(0)) )?; @@ -910,7 +918,7 @@ mod tests { create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, ) @@ -934,7 +942,7 @@ mod tests { let (validator_stash, nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get() as u32, + ::MaxRewardableIndividualExposures::get() as u32, false, RewardDestination::Staked, ) @@ -959,7 +967,7 @@ mod tests { let (validator_stash, _nominators) = create_validator_with_nominators::( n, - ::MaxNominatorRewardedPerValidator::get() as u32, + ::MaxRewardableIndividualExposures::get() as u32, false, RewardDestination::Staked, ) diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index be02e8d91d326..dcdfc4cbfb15e 100644 --- a/frame/staking/src/lib.rs +++ b/frame/staking/src/lib.rs @@ -114,7 +114,7 @@ //! //! Rewards must be claimed for each era before it gets too old by `$HISTORY_DEPTH` using the //! `payout_stakers` call. Any account can call `payout_stakers`, which pays the reward to the -//! validator as well as its nominators. Only the [`Config::MaxNominatorRewardedPerValidator`] +//! validator as well as its nominators. Only the [`Config::MaxRewardableIndividualExposures`] //! biggest stakers can claim their reward. This is to limit the i/o cost to mutate storage for each //! nominator's account. //! @@ -157,22 +157,31 @@ //! ### Example: Rewarding a validator by id. //! //! ``` -//! use frame_support::{decl_module, dispatch}; -//! use frame_system::ensure_signed; +//! use frame_support::pallet_prelude::*; +//! use frame_system::{ensure_signed, pallet_prelude::OriginFor}; //! use pallet_staking::{self as staking}; //! -//! pub trait Config: staking::Config {} -//! -//! decl_module! { -//! pub struct Module for enum Call where origin: T::Origin { -//! /// Reward a validator. -//! #[weight = 0] -//! pub fn reward_myself(origin) -> dispatch::DispatchResult { -//! let reported = ensure_signed(origin)?; -//! >::reward_by_ids(vec![(reported, 10)]); -//! Ok(()) -//! } +//! #[frame_support::pallet] +//! pub mod pallet { +//! use super::*; +//! +//! #[pallet::pallet] +//! #[pallet::generate_store(pub(crate) trait Store)] +//! pub struct Pallet(_); +//! +//! #[pallet::config] +//! pub trait Config: frame_system::Config + staking::Config {} +//! +//! #[pallet::call] +//! impl Pallet { +//! /// Reward a validator. +//! #[pallet::weight(0)] +//! pub fn reward_myself(origin: OriginFor) -> DispatchResult { +//! let reported = ensure_signed(origin)?; +//! >::reward_by_ids(vec![(reported, 10)]); +//! Ok(()) //! } +//! } //! } //! # fn main() { } //! ``` @@ -201,7 +210,7 @@ //! calculated using the era duration and the staking rate (the total amount of tokens staked by //! nominators and validators, divided by the total token supply). It aims to incentivize toward a //! defined staking rate. The full specification can be found -//! [here](https://research.web3.foundation/en/latest/polkadot/Token%20Economics.html#inflation-model). +//! [here](https://w3f-research.readthedocs.io/en/latest/polkadot/overview/2-token-economics.html#inflation-model). //! //! Total reward is split among validators and their nominators depending on the number of points //! they received during the era. Points are added to a validator using @@ -246,7 +255,7 @@ //! //! Note that there is a limitation to the number of fund-chunks that can be scheduled to be //! unlocked in the future via [`unbond`](Call::unbond). In case this maximum -//! (`MAX_UNLOCKING_CHUNKS`) is reached, the bonded account _must_ first wait until a successful +//! (`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a successful //! call to `withdraw_unbonded` to remove some of the chunks. //! //! ### Election Algorithm @@ -291,10 +300,13 @@ pub mod weights; mod pallet; -use codec::{Decode, Encode, HasCompact}; +use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{Currency, Get}, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{Currency, Get, ReportOffence}, weights::Weight, + BoundedVec, CloneNoBound, DefaultNoBound, EqNoBound, OrdNoBound, PartialEqNoBound, + RuntimeDebugNoBound, WeakBoundedVec, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -303,10 +315,14 @@ use sp_runtime::{ Perbill, RuntimeDebug, }; use sp_staking::{ - offence::{Offence, OffenceError, ReportOffence}, + offence::{Offence, OffenceError}, SessionIndex, }; -use sp_std::{collections::btree_map::BTreeMap, convert::From, prelude::*}; +use sp_std::{ + convert::{From, TryFrom}, + fmt, + prelude::*, +}; pub use weights::WeightInfo; pub use pallet::{pallet::*, *}; @@ -342,7 +358,7 @@ type NegativeImbalanceOf = <::Currency as Currency< >>::NegativeImbalance; /// Information regarding the active era (era in used in session). -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ActiveEraInfo { /// Index of era. pub index: EraIndex, @@ -356,12 +372,21 @@ pub struct ActiveEraInfo { /// Reward points of an era. Used to split era total payout between validators. /// /// This points will be used to reward validators and their respective nominators. -#[derive(PartialEq, Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct EraRewardPoints { +/// `Limit` bounds the number of points earned by a given validator. +#[derive( + PartialEqNoBound, Encode, Decode, DefaultNoBound, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, +)] +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get))] +pub struct EraRewardPoints +where + AccountId: Ord + MaxEncodedLen + Default + PartialEq + fmt::Debug, + Limit: Get, +{ /// Total number of points. Equals the sum of reward points for each validator. total: RewardPoint, /// The reward points earned by a given validator. - individual: BTreeMap, + individual: BoundedBTreeMap, } /// Indicates the initial status of the staker. @@ -377,7 +402,7 @@ pub enum StakerStatus { } /// A destination account for payment. -#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Copy, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub enum RewardDestination { /// Pay into the stash account, increasing the amount at stake accordingly. Staked, @@ -398,7 +423,7 @@ impl Default for RewardDestination { } /// Preference of what happens regarding validation. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct ValidatorPrefs { /// Reward that validator takes up-front; only the rest is split between themselves and /// nominators. @@ -417,7 +442,7 @@ impl Default for ValidatorPrefs { } /// Just a Balance/BlockNumber tuple to encode when a chunk of funds will be unlocked. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] pub struct UnlockChunk { /// Amount of funds to be unlocked. #[codec(compact)] @@ -428,9 +453,28 @@ pub struct UnlockChunk { } /// The ledger of a (bonded) stash. -#[cfg_attr(feature = "runtime-benchmarks", derive(Default))] -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct StakingLedger { +/// `UnlockingLimit` is the size limit of the `WeakBoundedVec` representing `unlocking`. +/// `RewardsLimit` is the size limit of the `WeakBoundedVec` representing `claimed_rewards`. +#[derive( + PartialEqNoBound, + EqNoBound, + Encode, + Decode, + CloneNoBound, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[cfg_attr(feature = "runtime-benchmarks", derive(DefaultNoBound))] +#[codec(mel_bound(UnlockingLimit: Get, RewardsLimit: Get))] +#[scale_info(skip_type_params(UnlockingLimit, RewardsLimit))] +pub struct StakingLedger +where + Balance: HasCompact + MaxEncodedLen + Clone + Default + Eq + fmt::Debug, + AccountId: MaxEncodedLen + Clone + Default + Eq + fmt::Debug, + UnlockingLimit: Get, + RewardsLimit: Get, +{ /// The stash account whose balance is actually locked and at stake. pub stash: AccountId, /// The total amount of the stash's balance that we are currently accounting for. @@ -443,31 +487,40 @@ pub struct StakingLedger { pub active: Balance, /// Any balance that is becoming free, which may eventually be transferred out /// of the stash (assuming it doesn't get slashed first). - pub unlocking: Vec>, + pub unlocking: BoundedVec, UnlockingLimit>, /// List of eras for which the stakers behind a validator have claimed rewards. Only updated /// for validators. - pub claimed_rewards: Vec, + pub claimed_rewards: WeakBoundedVec, } -impl - StakingLedger +impl + StakingLedger +where + Balance: + HasCompact + Copy + Saturating + AtLeast32BitUnsigned + Clone + Default + Eq + fmt::Debug, + AccountId: MaxEncodedLen + Clone + Default + Eq + fmt::Debug, + Balance: MaxEncodedLen + Clone + Default, + UnlockingLimit: Get, + RewardsLimit: Get, { /// Remove entries from `unlocking` that are sufficiently old and reduce the /// total by the sum of their balances. fn consolidate_unlocked(self, current_era: EraIndex) -> Self { let mut total = self.total; - let unlocking = self - .unlocking - .into_iter() - .filter(|chunk| { - if chunk.era > current_era { - true - } else { - total = total.saturating_sub(chunk.value); - false - } - }) - .collect(); + let unlocking = BoundedVec::<_, UnlockingLimit>::try_from( + self.unlocking + .into_iter() + .filter(|chunk| { + if chunk.era > current_era { + true + } else { + total = total.saturating_sub(chunk.value); + false + } + }) + .collect::>>(), + ) + .expect("unlocking vector only reduced in size here."); Self { stash: self.stash, @@ -504,12 +557,7 @@ impl (self, unlocking_balance) } -} -impl StakingLedger -where - Balance: AtLeast32BitUnsigned + Saturating + Copy, -{ /// Slash the validator for a given amount of balance. This can grow the value /// of the slash in the case that the validator has less than `minimum_balance` /// active funds. Returns the amount of funds actually slashed. @@ -559,10 +607,17 @@ where } /// A record of the nominations made by a specific account. -#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Nominations { +/// `Limit` bounds the number of `targets`. +#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get))] +pub struct Nominations +where + AccountId: MaxEncodedLen, + Limit: Get, +{ /// The targets of nomination. - pub targets: Vec, + pub targets: BoundedVec, /// The era the nominations were submitted. /// /// Except for initial nominations which are considered submitted at era 0. @@ -575,7 +630,9 @@ pub struct Nominations { } /// The amount of exposure (to slashing) than an individual nominator has. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[derive( + PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, +)] pub struct IndividualExposure { /// The stash account of the nominator in question. pub who: AccountId, @@ -585,10 +642,27 @@ pub struct IndividualExposure { } /// A snapshot of the stake backing a single validator in the system. +/// `Limit` is the size limit of `others`. #[derive( - PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, Default, RuntimeDebug, TypeInfo, + PartialEqNoBound, + EqNoBound, + OrdNoBound, + Encode, + Decode, + CloneNoBound, + DefaultNoBound, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, )] -pub struct Exposure { +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get, Balance: HasCompact))] +pub struct Exposure +where + AccountId: MaxEncodedLen + Eq + Default + Clone + Ord + fmt::Debug, + Balance: HasCompact + MaxEncodedLen + Eq + Default + Clone + Ord + fmt::Debug, + Limit: Get, +{ /// The total balance backing this validator. #[codec(compact)] pub total: Balance, @@ -596,25 +670,54 @@ pub struct Exposure { #[codec(compact)] pub own: Balance, /// The portions of nominators stashes that are exposed. - pub others: Vec>, + pub others: WeakBoundedVec, Limit>, } /// A pending slash record. The value of the slash has been computed but not applied yet, /// rather deferred for several eras. -#[derive(Encode, Decode, Default, RuntimeDebug, TypeInfo)] -pub struct UnappliedSlash { +/// `SlashedLimit` bounds the number of slashed accounts. +/// `ReportersLimit` bounds the number of reporters. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[codec(mel_bound(SlashedLimit: Get, ReportersLimit: Get))] +#[scale_info(skip_type_params(SlashedLimit, ReportersLimit))] +pub struct UnappliedSlash +where + AccountId: MaxEncodedLen, + Balance: HasCompact + MaxEncodedLen, + SlashedLimit: Get, + ReportersLimit: Get, +{ /// The stash ID of the offending validator. validator: AccountId, /// The validator's own slash. own: Balance, /// All other slashed stakers and amounts. - others: Vec<(AccountId, Balance)>, + others: WeakBoundedVec<(AccountId, Balance), SlashedLimit>, /// Reporters of the offence; bounty payout recipients. - reporters: Vec, + reporters: WeakBoundedVec, /// The amount of payout. payout: Balance, } +impl Default + for UnappliedSlash +where + Balance: HasCompact + MaxEncodedLen + Default, + AccountId: MaxEncodedLen + Default, + SlashedLimit: Get, + ReportersLimit: Get, +{ + fn default() -> Self { + Self { + validator: AccountId::default(), + own: Balance::default(), + others: Default::default(), + reporters: Default::default(), + payout: Balance::default(), + } + } +} + /// Means for interacting with a specialized version of the `session` trait. /// /// This is needed because `Staking` sets the `ValidatorIdOf` of the `pallet_session::Config` @@ -632,11 +735,18 @@ impl SessionInterface<::AccountId> for T where T: pallet_session::Config::AccountId>, T: pallet_session::historical::Config< - FullIdentification = Exposure<::AccountId, BalanceOf>, + FullIdentification = Exposure< + ::AccountId, + BalanceOf, + T::MaxIndividualExposures, + >, FullIdentificationOf = ExposureOf, >, T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, + T::SessionManager: pallet_session::SessionManager< + ::AccountId, + ::MaxValidatorsCount, + >, T::ValidatorIdOf: Convert< ::AccountId, Option<::AccountId>, @@ -647,7 +757,7 @@ where } fn validators() -> Vec<::AccountId> { - >::validators() + >::validators().to_vec() } fn prune_historical_up_to(up_to: SessionIndex) { @@ -702,7 +812,7 @@ impl Convert> for StashOf { /// `active_era`. It can differ from the latest planned exposure in `current_era`. pub struct ExposureOf(sp_std::marker::PhantomData); -impl Convert>>> +impl + Convert, T::MaxIndividualExposures>>> for ExposureOf { - fn convert(validator: T::AccountId) -> Option>> { + fn convert( + validator: T::AccountId, + ) -> Option, T::MaxIndividualExposures>> { >::active_era() .map(|active_era| >::eras_stakers(active_era.index, &validator)) } @@ -775,14 +888,18 @@ pub struct FilterHistoricalOffences { _inner: sp_std::marker::PhantomData<(T, R)>, } -impl ReportOffence - for FilterHistoricalOffences, R> +impl + ReportOffence for FilterHistoricalOffences, R> where T: Config, - R: ReportOffence, + R: ReportOffence, O: Offence, + MaxReportersCount: Get, { - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError> { + fn report_offence( + reporters: WeakBoundedVec, + offence: O, + ) -> Result<(), OffenceError> { // Disallow any slashing from before the current bonding period. let offence_session = offence.session_index(); let bonded_eras = BondedEras::::get(); diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index 95d397359f8d6..80de57d931295 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -22,7 +22,8 @@ use frame_election_provider_support::{onchain, SortedListProvider}; use frame_support::{ assert_ok, parameter_types, traits::{ - Currency, FindAuthor, GenesisBuild, Get, Hooks, Imbalance, OnUnbalanced, OneSessionHandler, + Currency, FindAuthor, GenesisBuild, Get, Hooks, Imbalance, OffenceDetails, + OnOffenceHandler, OnUnbalanced, OneSessionHandler, }, weights::constants::RocksDbWeight, }; @@ -33,7 +34,7 @@ use sp_runtime::{ testing::{Header, TestXt, UintAuthorityId}, traits::{IdentityLookup, Zero}, }; -use sp_staking::offence::{OffenceDetails, OnOffenceHandler}; +use sp_std::collections::btree_map::BTreeMap; use std::cell::RefCell; pub const INIT_TIMESTAMP: u64 = 30_000; @@ -178,11 +179,13 @@ impl pallet_session::Config for Test { type ValidatorId = AccountId; type ValidatorIdOf = crate::StashOf; type NextSessionRotation = pallet_session::PeriodicSessions; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxKeysEncodingSize = MaxKeysEncodingSize; type WeightInfo = (); } impl pallet_session::historical::Config for Test { - type FullIdentification = crate::Exposure; + type FullIdentification = crate::Exposure; type FullIdentificationOf = crate::ExposureOf; } impl pallet_authorship::Config for Test { @@ -213,7 +216,8 @@ pallet_staking_reward_curve::build! { parameter_types! { pub const BondingDuration: EraIndex = 3; pub const RewardCurve: &'static PiecewiseLinear<'static> = &I_NPOS; - pub const MaxNominatorRewardedPerValidator: u32 = 64; + pub const MaxRewardableIndividualExposures: u32 = 64; + pub const MaxIndividualExposures: u32 = 64; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(75); } @@ -237,6 +241,15 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub const MaxNominations: u32 = 16; + pub const MaxUnappliedSlashes: u32 = 1_000; + pub const MaxInvulnerablesCount: u32 = 100; + pub const MaxHistoryDepth: u32 = 10_000; + pub const MaxReportersCount: u32 = 1_000; + pub const MaxPriorSlashingSpans: u32 = 1_000; + pub const MaxValidatorsCount: u32 = 100; + pub const MaxKeysEncodingSize: u32 = 1_000; + pub const MaxUnlockingChunks: u32 = 32; } impl pallet_bags_list::Config for Test { @@ -249,10 +262,11 @@ impl pallet_bags_list::Config for Test { impl onchain::Config for Test { type Accuracy = Perbill; type DataProvider = Staking; + type MaxNominations = MaxNominations; + type MaxTargets = MaxValidatorsCount; } impl crate::pallet::pallet::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type Currency = Balances; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -267,13 +281,22 @@ impl crate::pallet::pallet::Config for Test { type SessionInterface = Self; type EraPayout = ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = MaxRewardableIndividualExposures; + type MaxIndividualExposures = MaxIndividualExposures; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; type WeightInfo = (); // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; + type MaxNominations = MaxNominations; + type MaxUnappliedSlashes = MaxUnappliedSlashes; + type MaxInvulnerablesCount = MaxInvulnerablesCount; + type MaxHistoryDepth = MaxHistoryDepth; + type MaxReportersCount = MaxReportersCount; + type MaxPriorSlashingSpans = MaxPriorSlashingSpans; + type MaxValidatorsCount = MaxValidatorsCount; + type MaxUnlockingChunks = MaxUnlockingChunks; } impl frame_system::offchain::SendTransactionTypes for Test @@ -761,6 +784,7 @@ pub(crate) fn on_offence_in_era( offenders: &[OffenceDetails< AccountId, pallet_session::historical::IdentificationTuple, + MaxReportersCount, >], slash_fraction: &[Perbill], era: EraIndex, @@ -790,6 +814,7 @@ pub(crate) fn on_offence_now( offenders: &[OffenceDetails< AccountId, pallet_session::historical::IdentificationTuple, + MaxReportersCount, >], slash_fraction: &[Perbill], ) { @@ -801,7 +826,7 @@ pub(crate) fn add_slash(who: &AccountId) { on_offence_now( &[OffenceDetails { offender: (who.clone(), Staking::eras_stakers(active_era(), who.clone())), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 7ca1cb1a4a61b..838558d1587f9 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -25,9 +25,10 @@ use frame_support::{ pallet_prelude::*, traits::{ Currency, CurrencyToVote, EstimateNextNewSession, Get, Imbalance, LockableCurrency, - OnUnbalanced, UnixTime, WithdrawReasons, + OffenceDetails, OnOffenceHandler, OnUnbalanced, UnixTime, WithdrawReasons, }, weights::{Weight, WithPostDispatchInfo}, + WeakBoundedVec, }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_session::historical; @@ -35,11 +36,8 @@ use sp_runtime::{ traits::{Bounded, Convert, SaturatedConversion, Saturating, Zero}, Perbill, }; -use sp_staking::{ - offence::{OffenceDetails, OnOffenceHandler}, - SessionIndex, -}; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_staking::SessionIndex; +use sp_std::{collections::btree_map::BTreeMap, convert::TryFrom, prelude::*}; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, Exposure, @@ -117,7 +115,10 @@ impl Pallet { match ledger.claimed_rewards.binary_search(&era) { Ok(_) => Err(Error::::AlreadyClaimed .with_weight(T::WeightInfo::payout_stakers_alive_staked(0)))?, - Err(pos) => ledger.claimed_rewards.insert(pos, era), + Err(pos) => ledger + .claimed_rewards + .try_insert(pos, era) + .map_err(|_| Error::::TooManyRewardsEras)?, } let exposure = >::get(&era, &ledger.stash); @@ -194,7 +195,7 @@ impl Pallet { } } - debug_assert!(nominator_payout_count <= T::MaxNominatorRewardedPerValidator::get()); + debug_assert!(nominator_payout_count <= T::MaxRewardableIndividualExposures::get()); Ok(Some(T::WeightInfo::payout_stakers_alive_staked(nominator_payout_count)).into()) } @@ -203,7 +204,12 @@ impl Pallet { /// This will also update the stash lock. pub(crate) fn update_ledger( controller: &T::AccountId, - ledger: &StakingLedger>, + ledger: &StakingLedger< + T::AccountId, + BalanceOf, + T::MaxUnlockingChunks, + T::MaxHistoryDepth, + >, ) { T::Currency::set_lock(STAKING_ID, &ledger.stash, ledger.total, WithdrawReasons::all()); >::insert(controller, ledger); @@ -242,7 +248,10 @@ impl Pallet { } /// Plan a new session potentially trigger a new era. - fn new_session(session_index: SessionIndex, is_genesis: bool) -> Option> { + fn new_session( + session_index: SessionIndex, + is_genesis: bool, + ) -> Option> { if let Some(current_era) = Self::current_era() { // Initial era has been set. let current_era_start_session_index = Self::eras_start_session_index(current_era) @@ -342,7 +351,7 @@ impl Pallet { let bonding_duration = T::BondingDuration::get(); BondedEras::::mutate(|bonded| { - bonded.push((active_era, start_session)); + bonded.force_push((active_era, start_session), None); if active_era > bonding_duration { let first_kept = active_era - bonding_duration; @@ -397,8 +406,11 @@ impl Pallet { /// Returns the new validator set. pub fn trigger_new_era( start_session_index: SessionIndex, - exposures: Vec<(T::AccountId, Exposure>)>, - ) -> Vec { + exposures: WeakBoundedVec< + (T::AccountId, Exposure, T::MaxIndividualExposures>), + T::MaxValidatorsCount, + >, + ) -> WeakBoundedVec { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { *s = Some(s.map(|s| s + 1).unwrap_or(0)); @@ -424,7 +436,7 @@ impl Pallet { pub(crate) fn try_trigger_new_era( start_session_index: SessionIndex, is_genesis: bool, - ) -> Option> { + ) -> Option> { let election_result = if is_genesis { T::GenesisElectionProvider::elect().map_err(|e| { log!(warn, "genesis election provider failed due to {:?}", e); @@ -473,10 +485,13 @@ impl Pallet { /// /// Store staking information for the new planned era pub fn store_stakers_info( - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: WeakBoundedVec< + (T::AccountId, Exposure, T::MaxIndividualExposures>), + T::MaxValidatorsCount, + >, new_planned_era: EraIndex, - ) -> Vec { - let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::>(); + ) -> WeakBoundedVec { + let elected_stashes = exposures.clone().map_collect(|(x, _)| x); // Populate stakers, exposures, and the snapshot of validator prefs. let mut total_stake: BalanceOf = Zero::zero(); @@ -484,12 +499,21 @@ impl Pallet { total_stake = total_stake.saturating_add(exposure.total); >::insert(new_planned_era, &stash, &exposure); - let mut exposure_clipped = exposure; - let clipped_max_len = T::MaxNominatorRewardedPerValidator::get() as usize; - if exposure_clipped.others.len() > clipped_max_len { - exposure_clipped.others.sort_by(|a, b| a.value.cmp(&b.value).reverse()); - exposure_clipped.others.truncate(clipped_max_len); + let mut others = exposure.others.to_vec(); + let clipped_max_len = T::MaxRewardableIndividualExposures::get() as usize; + if others.len() > clipped_max_len { + others.sort_by(|a, b| a.value.cmp(&b.value).reverse()); + others.truncate(clipped_max_len); } + + let others = WeakBoundedVec::<_, T::MaxRewardableIndividualExposures>::try_from(others) + .expect("Vec was clipped, so this has to work, qed"); + let exposure_clipped = + Exposure::, T::MaxRewardableIndividualExposures> { + total: exposure.total, + own: exposure.own, + others, + }; >::insert(&new_planned_era, &stash, exposure_clipped); }); @@ -497,7 +521,7 @@ impl Pallet { >::insert(&new_planned_era, total_stake); // Collect the pref of all winners. - for stash in &elected_stashes { + for stash in AsRef::>::as_ref(&elected_stashes) { let pref = Self::validators(stash); >::insert(&new_planned_era, stash, pref); } @@ -518,36 +542,47 @@ impl Pallet { /// [`Exposure`]. fn collect_exposures( supports: Supports, - ) -> Vec<(T::AccountId, Exposure>)> { + ) -> WeakBoundedVec< + (T::AccountId, Exposure, T::MaxIndividualExposures>), + T::MaxValidatorsCount, + > { let total_issuance = T::Currency::total_issuance(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) }; - supports - .into_iter() - .map(|(validator, support)| { - // Build `struct exposure` from `support`. - let mut others = Vec::with_capacity(support.voters.len()); - let mut own: BalanceOf = Zero::zero(); - let mut total: BalanceOf = Zero::zero(); - support - .voters - .into_iter() - .map(|(nominator, weight)| (nominator, to_currency(weight))) - .for_each(|(nominator, stake)| { - if nominator == validator { - own = own.saturating_add(stake); - } else { - others.push(IndividualExposure { who: nominator, value: stake }); - } - total = total.saturating_add(stake); - }); - - let exposure = Exposure { own, others, total }; - (validator, exposure) - }) - .collect::)>>() + WeakBoundedVec::<_, T::MaxValidatorsCount>::force_from( + supports + .into_iter() + .map(|(validator, support)| { + // Build `struct exposure` from `support`. + let mut others = Vec::with_capacity(support.voters.len()); + let mut own: BalanceOf = Zero::zero(); + let mut total: BalanceOf = Zero::zero(); + support + .voters + .into_iter() + .map(|(nominator, weight)| (nominator, to_currency(weight))) + .for_each(|(nominator, stake)| { + if nominator == validator { + own = own.saturating_add(stake); + } else { + others.push(IndividualExposure { who: nominator, value: stake }); + } + total = total.saturating_add(stake); + }); + + let others = WeakBoundedVec::<_, T::MaxIndividualExposures>::force_from( + others, + Some("exposure.others"), + ); + + let exposure = Exposure { own, others, total }; + (validator, exposure) + }) + .collect::)>>(), + Some("Too many validators in frame_staking.collect_exposures"), + ) } /// Remove all associated data of a stash account from the staking system. @@ -637,7 +672,7 @@ impl Pallet { pub fn add_era_stakers( current_era: EraIndex, controller: T::AccountId, - exposure: Exposure>, + exposure: Exposure, T::MaxIndividualExposures>, ) { >::insert(¤t_era, &controller, &exposure); } @@ -663,7 +698,7 @@ impl Pallet { /// auto-chilled, but still count towards the limit imposed by `maybe_max_len`. pub fn get_npos_voters( maybe_max_len: Option, - ) -> Vec<(T::AccountId, VoteWeight, Vec)> { + ) -> Vec<(T::AccountId, VoteWeight, BoundedVec)> { let max_allowed_len = { let nominator_count = CounterForNominators::::get() as usize; let validator_count = CounterForValidators::::get() as usize; @@ -677,8 +712,10 @@ impl Pallet { let mut validators_taken = 0u32; for (validator, _) in >::iter().take(max_allowed_len) { // Append self vote. - let self_vote = - (validator.clone(), Self::weight_of(&validator), vec![validator.clone()]); + let validator_bounded = + BoundedVec::<_, T::MaxNominations>::try_from(vec![validator.clone()]) + .expect("T::MaxNominations>0"); + let self_vote = (validator.clone(), Self::weight_of(&validator), validator_bounded); all_voters.push(self_vote); validators_taken.saturating_inc(); } @@ -750,7 +787,7 @@ impl Pallet { /// Get the targets for an upcoming npos election. /// /// This function is self-weighing as [`DispatchClass::Mandatory`]. - pub fn get_npos_targets() -> Vec { + pub fn get_npos_targets() -> BoundedVec { let mut validator_count = 0u32; let targets = Validators::::iter() .map(|(v, _)| { @@ -759,6 +796,9 @@ impl Pallet { }) .collect::>(); + let targets = BoundedVec::<_, T::MaxValidatorsCount>::try_from(targets) + .expect("Size is smaller than MaxValidatorsCount"); + Self::register_weight(T::WeightInfo::get_npos_targets(validator_count)); targets @@ -772,7 +812,10 @@ impl Pallet { /// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access /// to `Nominators`, its counter, or `VoterList` outside of this function is almost certainly /// wrong. - pub fn do_add_nominator(who: &T::AccountId, nominations: Nominations) { + pub fn do_add_nominator( + who: &T::AccountId, + nominations: Nominations, + ) { if !Nominators::::contains_key(who) { // maybe update the counter. CounterForNominators::::mutate(|x| x.saturating_inc()); @@ -854,8 +897,10 @@ impl Pallet { } } -impl ElectionDataProvider> for Pallet { - const MAXIMUM_VOTES_PER_VOTER: u32 = T::MAX_NOMINATIONS; +impl ElectionDataProvider, T::MaxValidatorsCount> + for Pallet +{ + type MaximumVotesPerVoter = T::MaxNominations; fn desired_targets() -> data_provider::Result { Self::register_weight(T::DbWeight::get().reads(1)); @@ -864,7 +909,9 @@ impl ElectionDataProvider> for Pallet fn voters( maybe_max_len: Option, - ) -> data_provider::Result)>> { + ) -> data_provider::Result< + Vec<(T::AccountId, VoteWeight, BoundedVec)>, + > { debug_assert!(>::iter().count() as u32 == CounterForNominators::::get()); debug_assert!(>::iter().count() as u32 == CounterForValidators::::get()); debug_assert_eq!( @@ -880,7 +927,9 @@ impl ElectionDataProvider> for Pallet Ok(voters) } - fn targets(maybe_max_len: Option) -> data_provider::Result> { + fn targets( + maybe_max_len: Option, + ) -> data_provider::Result> { let target_count = CounterForValidators::::get(); // We can't handle this case yet -- return an error. @@ -924,8 +973,16 @@ impl ElectionDataProvider> for Pallet ) } + /// # Panic + /// + /// Panics if `targets` size is bigger than T::MaxNominations, or if it cannot convert `weight` + /// into a `BalanceOf`. #[cfg(feature = "runtime-benchmarks")] - fn add_voter(voter: T::AccountId, weight: VoteWeight, targets: Vec) { + fn add_voter( + voter: T::AccountId, + weight: VoteWeight, + targets: BoundedVec, + ) { let stake = >::try_from(weight).unwrap_or_else(|_| { panic!("cannot convert a VoteWeight into BalanceOf, benchmark needs reconfiguring.") }); @@ -936,10 +993,11 @@ impl ElectionDataProvider> for Pallet stash: voter.clone(), active: stake, total: stake, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); + Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false }); } @@ -953,8 +1011,8 @@ impl ElectionDataProvider> for Pallet stash: target.clone(), active: stake, total: stake, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); Self::do_add_validator( @@ -974,10 +1032,17 @@ impl ElectionDataProvider> for Pallet let _ = T::SortedListProvider::clear(None); } + /// # Panic + /// + /// Panics if `voters` 2nd element cannot be converted into a `BalanceOf`. #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( - voters: Vec<(T::AccountId, VoteWeight, Vec)>, - targets: Vec, + voters: Vec<( + T::AccountId, + VoteWeight, + BoundedVec, + )>, + targets: BoundedVec, target_stake: Option, ) { targets.into_iter().for_each(|v| { @@ -991,8 +1056,8 @@ impl ElectionDataProvider> for Pallet stash: v.clone(), active: stake, total: stake, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); Self::do_add_validator( @@ -1012,10 +1077,11 @@ impl ElectionDataProvider> for Pallet stash: v.clone(), active: stake, total: stake, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); + Self::do_add_nominator( &v, Nominations { targets: t, submitted_in: 0, suppressed: false }, @@ -1029,13 +1095,17 @@ impl ElectionDataProvider> for Pallet /// /// Once the first new_session is planned, all session must start and then end in order, though /// some session can lag in between the newest session planned and the latest session started. -impl pallet_session::SessionManager for Pallet { - fn new_session(new_index: SessionIndex) -> Option> { +impl pallet_session::SessionManager for Pallet { + fn new_session( + new_index: SessionIndex, + ) -> Option> { log!(trace, "planning new session {}", new_index); CurrentPlannedSession::::put(new_index); Self::new_session(new_index, false) } - fn new_session_genesis(new_index: SessionIndex) -> Option> { + fn new_session_genesis( + new_index: SessionIndex, + ) -> Option> { log!(trace, "planning new session {} at genesis", new_index); CurrentPlannedSession::::put(new_index); Self::new_session(new_index, true) @@ -1050,50 +1120,61 @@ impl pallet_session::SessionManager for Pallet { } } -impl historical::SessionManager>> - for Pallet +impl + historical::SessionManager< + T::AccountId, + Exposure, T::MaxIndividualExposures>, + T::MaxValidatorsCount, + > for Pallet { fn new_session( new_index: SessionIndex, - ) -> Option>)>> { - >::new_session(new_index).map(|validators| { + ) -> Option< + WeakBoundedVec< + (T::AccountId, Exposure, T::MaxIndividualExposures>), + T::MaxValidatorsCount, + >, + > { + >::new_session(new_index).map(|validators| { let current_era = Self::current_era() // Must be some as a new era has been created. .unwrap_or(0); - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() + validators.map_collect(|v| { + let exposure = Self::eras_stakers(current_era, &v); + (v, exposure) + }) }) } + fn new_session_genesis( new_index: SessionIndex, - ) -> Option>)>> { - >::new_session_genesis(new_index).map( + ) -> Option< + WeakBoundedVec< + (T::AccountId, Exposure, T::MaxIndividualExposures>), + T::MaxValidatorsCount, + >, + > { + >::new_session_genesis(new_index).map( |validators| { let current_era = Self::current_era() // Must be some as a new era has been created. .unwrap_or(0); - validators - .into_iter() - .map(|v| { - let exposure = Self::eras_stakers(current_era, &v); - (v, exposure) - }) - .collect() + validators.map_collect(|v| { + let exposure = Self::eras_stakers(current_era, &v); + (v, exposure) + }) }, ) } + fn start_session(start_index: SessionIndex) { - >::start_session(start_index) + >::start_session(start_index) } + fn end_session(end_index: SessionIndex) { - >::end_session(end_index) + >::end_session(end_index) } } @@ -1115,16 +1196,27 @@ where /// This is intended to be used with `FilterHistoricalOffences`. impl - OnOffenceHandler, Weight> - for Pallet + OnOffenceHandler< + T::AccountId, + pallet_session::historical::IdentificationTuple, + Weight, + T::MaxReportersCount, + > for Pallet where T: pallet_session::Config::AccountId>, T: pallet_session::historical::Config< - FullIdentification = Exposure<::AccountId, BalanceOf>, + FullIdentification = Exposure< + ::AccountId, + BalanceOf, + T::MaxIndividualExposures, + >, FullIdentificationOf = ExposureOf, >, T::SessionHandler: pallet_session::SessionHandler<::AccountId>, - T::SessionManager: pallet_session::SessionManager<::AccountId>, + T::SessionManager: pallet_session::SessionManager< + ::AccountId, + ::MaxValidatorsCount, + >, T::ValidatorIdOf: Convert< ::AccountId, Option<::AccountId>, @@ -1134,6 +1226,7 @@ where offenders: &[OffenceDetails< T::AccountId, pallet_session::historical::IdentificationTuple, + T::MaxReportersCount, >], slash_fraction: &[Perbill], slash_session: SessionIndex, @@ -1232,7 +1325,7 @@ where } else { // Defer to end of some `slash_defer_duration` from now. ::UnappliedSlashes::mutate(active_era, move |for_later| { - for_later.push(unapplied) + for_later.force_push(unapplied, Some("UnappliedStashes.unapplied")) }); add_db_reads_writes(1, 1); } diff --git a/frame/staking/src/pallet/mod.rs b/frame/staking/src/pallet/mod.rs index 8e97a90e07544..db875331da2ea 100644 --- a/frame/staking/src/pallet/mod.rs +++ b/frame/staking/src/pallet/mod.rs @@ -25,6 +25,7 @@ use frame_support::{ LockableCurrency, OnUnbalanced, UnixTime, }, weights::Weight, + BoundedVec, WeakBoundedVec, }; use frame_system::{ensure_root, ensure_signed, offchain::SendTransactionTypes, pallet_prelude::*}; use sp_runtime::{ @@ -32,20 +33,23 @@ use sp_runtime::{ DispatchError, Perbill, Percent, }; use sp_staking::SessionIndex; -use sp_std::{convert::From, prelude::*, result}; +use sp_std::{ + convert::{From, TryFrom}, + prelude::*, + result, +}; mod impls; pub use impls::*; use crate::{ - log, migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, + migrations, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, EraRewardPoints, Exposure, Forcing, NegativeImbalanceOf, Nominations, PositiveImbalanceOf, Releases, RewardDestination, SessionInterface, StakingLedger, UnappliedSlash, UnlockChunk, ValidatorPrefs, }; -pub const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; #[frame_support::pallet] @@ -54,6 +58,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -78,6 +83,7 @@ pub mod pallet { type ElectionProvider: frame_election_provider_support::ElectionProvider< Self::AccountId, Self::BlockNumber, + Self::MaxValidatorsCount, // we only accept an election provider that has staking as data provider. DataProvider = Pallet, >; @@ -86,11 +92,12 @@ pub mod pallet { type GenesisElectionProvider: frame_election_provider_support::ElectionProvider< Self::AccountId, Self::BlockNumber, + Self::MaxValidatorsCount, DataProvider = Pallet, >; /// Maximum number of nominations per nominator. - const MAX_NOMINATIONS: u32; + type MaxNominations: Get; /// Tokens have been minted and are unused for validator-reward. /// See [Era payout](./index.html#era-payout). @@ -136,10 +143,39 @@ pub mod pallet { /// The maximum number of nominators rewarded for each validator. /// - /// For each validator only the `$MaxNominatorRewardedPerValidator` biggest stakers can + /// For each validator only the `$MaxRewardableIndividualExposures` biggest stakers can /// claim their reward. This used to limit the i/o cost for the nominator payout. #[pallet::constant] - type MaxNominatorRewardedPerValidator: Get; + type MaxRewardableIndividualExposures: Get; + + /// The maximum number of exposure of a certain validator at a given era. + type MaxIndividualExposures: Get; + + /// The maximum number of unapplied slashes to be stored in `UnappliedSlashes`. + type MaxUnappliedSlashes: Get; + + /// The maximum number of invulnerables, we expect no more than four invulnerables and + /// restricted to testnets. + type MaxInvulnerablesCount: Get; + + /// Maximum number of eras for which the stakers behind a validator have claimed rewards. + /// This also corresponds to maximum value that `HistoryDepth` can take. + type MaxHistoryDepth: Get; + + /// Maximum number of validators. + type MaxValidatorsCount: Get; + + /// Maximum number of reporters for slashing. + type MaxReportersCount: Get; + + /// Maximum number of slashing spans that is stored. + type MaxPriorSlashingSpans: Get; + + /// Note that there is a limitation to the number of fund-chunks that can be scheduled to be + /// unlocked in the future via [`unbond`](Call::unbond). In case this maximum + /// (`MaxUnlockingChunks`) is reached, the bonded account _must_ first wait until a + /// successful call to `withdraw_unbonded` to remove some of the chunks. + type MaxUnlockingChunks: Get; /// The fraction of the validator set that is safe to be offending. /// After the threshold is reached a new era will be forced. @@ -159,7 +195,7 @@ pub mod pallet { // TODO: rename to snake case after https://github.com/paritytech/substrate/issues/8826 fixed. #[allow(non_snake_case)] fn MaxNominations() -> u32 { - T::MAX_NOMINATIONS + T::MaxNominations::get() } } @@ -189,12 +225,13 @@ pub mod pallet { #[pallet::getter(fn minimum_validator_count)] pub type MinimumValidatorCount = StorageValue<_, u32, ValueQuery>; - /// Any validators that may never be slashed or forcibly kicked. It's a Vec since they're - /// easy to initialize and the performance hit is minimal (we expect no more than four - /// invulnerables) and restricted to testnets. + /// Any validators that may never be slashed or forcibly kicked. It's a `BoundedVec` + /// since they're easy to initialize and are bounded in size, and the performance hit + /// is minimal (we expect no more than four invulnerables) and restricted to testnets. #[pallet::storage] #[pallet::getter(fn invulnerables)] - pub type Invulnerables = StorageValue<_, Vec, ValueQuery>; + pub type Invulnerables = + StorageValue<_, BoundedVec, ValueQuery>; /// Map from all locked "stash" accounts to the controller account. #[pallet::storage] @@ -212,8 +249,12 @@ pub mod pallet { /// Map from all (unlocked) "controller" accounts to the info regarding the staking. #[pallet::storage] #[pallet::getter(fn ledger)] - pub type Ledger = - StorageMap<_, Blake2_128Concat, T::AccountId, StakingLedger>>; + pub type Ledger = StorageMap< + _, + Blake2_128Concat, + T::AccountId, + StakingLedger, T::MaxUnlockingChunks, T::MaxHistoryDepth>, + >; /// Where the reward payment should be made. Keyed by stash. #[pallet::storage] @@ -233,25 +274,19 @@ pub mod pallet { #[pallet::storage] pub type CounterForValidators = StorageValue<_, u32, ValueQuery>; - /// The maximum validator count before we stop allowing new validators to join. - /// - /// When this value is not set, no limits are enforced. - #[pallet::storage] - pub type MaxValidatorsCount = StorageValue<_, u32, OptionQuery>; - /// The map from nominator stash key to the set of stash keys of all validators to nominate. /// /// When updating this storage item, you must also update the `CounterForNominators`. #[pallet::storage] #[pallet::getter(fn nominators)] pub type Nominators = - StorageMap<_, Twox64Concat, T::AccountId, Nominations>; + StorageMap<_, Twox64Concat, T::AccountId, Nominations>; /// A tracker to keep count of the number of items in the `Nominators` map. #[pallet::storage] pub type CounterForNominators = StorageValue<_, u32, ValueQuery>; - /// The maximum nominator count before we stop allowing new validators to join. + /// The maximum nominator count before we stop allowing new nominators to join. /// /// When this value is not set, no limits are enforced. #[pallet::storage] @@ -295,14 +330,14 @@ pub mod pallet { EraIndex, Twox64Concat, T::AccountId, - Exposure>, + Exposure, T::MaxIndividualExposures>, ValueQuery, >; /// Clipped Exposure of validator at era. /// /// This is similar to [`ErasStakers`] but number of nominators exposed is reduced to the - /// `T::MaxNominatorRewardedPerValidator` biggest stakers. + /// `T::MaxRewardableIndividualExposures` biggest stakers. /// (Note: the field `total` and `own` of the exposure remains unchanged). /// This is used to limit the i/o cost for the nominator payout. /// @@ -318,7 +353,7 @@ pub mod pallet { EraIndex, Twox64Concat, T::AccountId, - Exposure>, + Exposure, T::MaxRewardableIndividualExposures>, ValueQuery, >; @@ -351,8 +386,13 @@ pub mod pallet { /// If reward hasn't been set or has been removed then 0 reward is returned. #[pallet::storage] #[pallet::getter(fn eras_reward_points)] - pub type ErasRewardPoints = - StorageMap<_, Twox64Concat, EraIndex, EraRewardPoints, ValueQuery>; + pub type ErasRewardPoints = StorageMap< + _, + Twox64Concat, + EraIndex, + EraRewardPoints, + ValueQuery, + >; /// The total amount staked for the last `HISTORY_DEPTH` eras. /// If total hasn't been set or has been removed then 0 stake is returned. @@ -385,7 +425,15 @@ pub mod pallet { _, Twox64Concat, EraIndex, - Vec>>, + WeakBoundedVec< + UnappliedSlash< + T::AccountId, + BalanceOf, + T::MaxIndividualExposures, + T::MaxReportersCount, + >, + T::MaxUnappliedSlashes, + >, ValueQuery, >; @@ -395,7 +443,7 @@ pub mod pallet { /// `[active_era - bounding_duration; active_era]` #[pallet::storage] pub(crate) type BondedEras = - StorageValue<_, Vec<(EraIndex, SessionIndex)>, ValueQuery>; + StorageValue<_, WeakBoundedVec<(EraIndex, SessionIndex), T::BondingDuration>, ValueQuery>; /// All slashing events on validators, mapped by era to the highest slash proportion /// and slash value of the era. @@ -416,8 +464,12 @@ pub mod pallet { /// Slashing spans for stash accounts. #[pallet::storage] - pub(crate) type SlashingSpans = - StorageMap<_, Twox64Concat, T::AccountId, slashing::SlashingSpans>; + pub(crate) type SlashingSpans = StorageMap< + _, + Twox64Concat, + T::AccountId, + slashing::SlashingSpans, + >; /// Records information about the maximum slash of a stash within a slashing span, /// as well as how much reward has been paid out. @@ -452,7 +504,8 @@ pub mod pallet { /// the era ends. #[pallet::storage] #[pallet::getter(fn offending_validators)] - pub type OffendingValidators = StorageValue<_, Vec<(u32, bool)>, ValueQuery>; + pub type OffendingValidators = + StorageValue<_, BoundedVec<(u32, bool), T::MaxValidatorsCount>, ValueQuery>; /// True if network has been upgraded to this version. /// Storage version of the pallet. @@ -503,10 +556,14 @@ pub mod pallet { #[pallet::genesis_build] impl GenesisBuild for GenesisConfig { fn build(&self) { + assert!( + self.history_depth < T::MaxHistoryDepth::get(), + "history_depth too big, a runtime adjustment may be needed." + ); + HistoryDepth::::put(self.history_depth); ValidatorCount::::put(self.validator_count); MinimumValidatorCount::::put(self.minimum_validator_count); - Invulnerables::::put(&self.invulnerables); ForceEra::::put(self.force_era); CanceledSlashPayout::::put(self.canceled_payout); SlashRewardFraction::::put(self.slash_reward_fraction); @@ -514,8 +571,15 @@ pub mod pallet { MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); + let invulnerables = + BoundedVec::<_, T::MaxInvulnerablesCount>::try_from(self.invulnerables.clone()) + .expect( + "Too many invulnerables passed, a runtime parameters adjustment may be needed", + ); + Invulnerables::::put(&invulnerables); + for &(ref stash, ref controller, balance, ref status) in &self.stakers { - log!( + crate::log!( trace, "inserting genesis staker: {:?} => {:?} => {:?}", stash, @@ -642,6 +706,10 @@ pub mod pallet { /// There are too many validators in the system. Governance needs to adjust the staking /// settings to keep things safe for the runtime. TooManyValidators, + /// Too many invulnerables are passed, a runtime configuration adjustment may be needed. + TooManyInvulnerables, + /// Too many Rewards Eras are passed, a runtime configuration adjustment may be needed. + TooManyRewardsEras, } #[pallet::hooks] @@ -753,12 +821,17 @@ pub mod pallet { let stash_balance = T::Currency::free_balance(&stash); let value = value.min(stash_balance); Self::deposit_event(Event::::Bonded(stash.clone(), value)); + + let claimed_rewards = WeakBoundedVec::<_, T::MaxHistoryDepth>::force_from( + (last_reward_era..current_era).collect(), + Some("StakingLedger.claimed_rewards"), + ); let item = StakingLedger { stash, total: value, active: value, - unlocking: vec![], - claimed_rewards: (last_reward_era..current_era).collect(), + unlocking: BoundedVec::<_, T::MaxUnlockingChunks>::default(), + claimed_rewards, }; Self::update_ledger(&controller, &item); Ok(()) @@ -822,7 +895,7 @@ pub mod pallet { /// Once the unlock period is done, you can call `withdraw_unbonded` to actually move /// the funds out of management ready for transfer. /// - /// No more than a limited number of unlocking chunks (see `MAX_UNLOCKING_CHUNKS`) + /// No more than a limited number of unlocking chunks (see `MaxUnlockingChunks`) /// can co-exists at the same time. In that case, [`Call::withdraw_unbonded`] need /// to be called first to remove some of the chunks (if possible). /// @@ -839,7 +912,6 @@ pub mod pallet { ) -> DispatchResult { let controller = ensure_signed(origin)?; let mut ledger = Self::ledger(&controller).ok_or(Error::::NotController)?; - ensure!(ledger.unlocking.len() < MAX_UNLOCKING_CHUNKS, Error::::NoMoreChunks,); let mut value = value.min(ledger.active); @@ -866,7 +938,10 @@ pub mod pallet { // Note: in case there is no current era it is fine to bond one era more. let era = Self::current_era().unwrap_or(0) + T::BondingDuration::get(); - ledger.unlocking.push(UnlockChunk { value, era }); + ledger + .unlocking + .try_push(UnlockChunk { value, era }) + .map_err(|_| Error::::NoMoreChunks)?; // NOTE: ledger must be updated prior to calling `Self::weight_of`. Self::update_ledger(&controller, &ledger); @@ -955,12 +1030,10 @@ pub mod pallet { // If this error is reached, we need to adjust the `MinValidatorBond` and start // calling `chill_other`. Until then, we explicitly block new validators to protect // the runtime. - if let Some(max_validators) = MaxValidatorsCount::::get() { - ensure!( - CounterForValidators::::get() < max_validators, - Error::::TooManyValidators - ); - } + ensure!( + CounterForValidators::::get() < T::MaxValidatorsCount::get(), + Error::::TooManyValidators + ); } Self::do_remove_nominator(stash); @@ -976,7 +1049,7 @@ pub mod pallet { /// /// # /// - The transaction's complexity is proportional to the size of `targets` (N) - /// which is capped at CompactAssignments::LIMIT (MAX_NOMINATIONS). + /// which is capped at CompactAssignments::LIMIT (T::MaxNominations). /// - Both the reads and writes follow a similar pattern. /// # #[pallet::weight(T::WeightInfo::nominate(targets.len() as u32))] @@ -1004,9 +1077,8 @@ pub mod pallet { } ensure!(!targets.is_empty(), Error::::EmptyTargets); - ensure!(targets.len() <= T::MAX_NOMINATIONS as usize, Error::::TooManyTargets); - let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets); + let old = Nominators::::get(stash).map_or_else(Vec::new, |x| x.targets.to_vec()); let targets = targets .into_iter() @@ -1022,6 +1094,9 @@ pub mod pallet { }) .collect::, _>>()?; + let targets = BoundedVec::<_, T::MaxNominations>::try_from(targets) + .map_err(|_| Error::::TooManyTargets)?; + let nominations = Nominations { targets, // Initial nominations are considered submitted at era 0. See `Nominations` doc @@ -1225,6 +1300,8 @@ pub mod pallet { invulnerables: Vec, ) -> DispatchResult { ensure_root(origin)?; + let invulnerables = BoundedVec::<_, T::MaxInvulnerablesCount>::try_from(invulnerables) + .map_err(|_| Error::::TooManyInvulnerables)?; >::put(invulnerables); Ok(()) } @@ -1316,14 +1393,14 @@ pub mod pallet { /// Pay out all the stakers behind a single validator for a single era. /// /// - `validator_stash` is the stash account of the validator. Their nominators, up to - /// `T::MaxNominatorRewardedPerValidator`, will also receive their rewards. + /// `T::MaxRewardableIndividualExposures`, will also receive their rewards. /// - `era` may be any era between `[current_era - history_depth; current_era]`. /// /// The origin of this call must be _Signed_. Any account can call this function, even if /// it is not one of the stakers. /// /// # - /// - Time complexity: at most O(MaxNominatorRewardedPerValidator). + /// - Time complexity: at most O(MaxRewardableIndividualExposures). /// - Contains a limited number of reads and writes. /// ----------- /// N is the Number of payouts for the validator (including the validator) @@ -1335,7 +1412,7 @@ pub mod pallet { /// Paying even a dead controller is cheaper weight-wise. We don't do any refunds here. /// # #[pallet::weight(T::WeightInfo::payout_stakers_alive_staked( - T::MaxNominatorRewardedPerValidator::get() + T::MaxRewardableIndividualExposures::get() ))] pub fn payout_stakers( origin: OriginFor, @@ -1352,10 +1429,10 @@ pub mod pallet { /// /// # /// - Time complexity: O(L), where L is unlocking chunks - /// - Bounded by `MAX_UNLOCKING_CHUNKS`. + /// - Bounded by `MaxUnlockingChunks`. /// - Storage changes: Can't increase storage, only decrease it. /// # - #[pallet::weight(T::WeightInfo::rebond(MAX_UNLOCKING_CHUNKS as u32))] + #[pallet::weight(T::WeightInfo::rebond(T::MaxUnlockingChunks::get()))] pub fn rebond( origin: OriginFor, #[pallet::compact] value: BalanceOf, @@ -1412,6 +1489,10 @@ pub mod pallet { #[pallet::compact] _era_items_deleted: u32, ) -> DispatchResult { ensure_root(origin)?; + ensure!( + new_history_depth < T::MaxHistoryDepth::get(), + Error::::IncorrectHistoryDepth + ); if let Some(current_era) = Self::current_era() { HistoryDepth::::mutate(|history_depth| { let last_kept = current_era.checked_sub(*history_depth).unwrap_or(0); @@ -1515,14 +1596,12 @@ pub mod pallet { min_nominator_bond: BalanceOf, min_validator_bond: BalanceOf, max_nominator_count: Option, - max_validator_count: Option, threshold: Option, ) -> DispatchResult { ensure_root(origin)?; MinNominatorBond::::set(min_nominator_bond); MinValidatorBond::::set(min_validator_bond); MaxNominatorsCount::::set(max_nominator_count); - MaxValidatorsCount::::set(max_validator_count); ChillThreshold::::set(threshold); Ok(()) } @@ -1540,8 +1619,8 @@ pub mod pallet { /// must be met: /// * A `ChillThreshold` must be set and checked which defines how close to the max /// nominators or validators we must reach before users can start chilling one-another. - /// * A `MaxNominatorCount` and `MaxValidatorCount` must be set which is used to determine - /// how close we are to the threshold. + /// * A `MaxNominatorCount` must be set which is used to determine how close we are to the + /// threshold. /// * A `MinNominatorBond` and `MinValidatorBond` must be set and checked, which determines /// if this is a person that should be chilled because they have not met the threshold /// bond required. @@ -1558,8 +1637,7 @@ pub mod pallet { // In order for one user to chill another user, the following conditions must be met: // * A `ChillThreshold` is set which defines how close to the max nominators or // validators we must reach before users can start chilling one-another. - // * A `MaxNominatorCount` and `MaxValidatorCount` which is used to determine how close - // we are to the threshold. + // * A `MaxNominatorCount` which is used to determine how close we are to the threshold. // * A `MinNominatorBond` and `MinValidatorBond` which is the final condition checked to // determine this is a person that should be chilled because they have not met the // threshold bond required. @@ -1577,8 +1655,7 @@ pub mod pallet { ); MinNominatorBond::::get() } else if Validators::::contains_key(&stash) { - let max_validator_count = - MaxValidatorsCount::::get().ok_or(Error::::CannotChillOther)?; + let max_validator_count = T::MaxValidatorsCount::get(); let current_validator_count = CounterForValidators::::get(); ensure!( threshold * max_validator_count < current_validator_count, diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index 68088d0e0d777..af78bc2617465 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -50,20 +50,24 @@ //! Based on research at use crate::{ - BalanceOf, Config, EraIndex, Error, Exposure, NegativeImbalanceOf, Pallet, Perbill, - SessionInterface, Store, UnappliedSlash, + BalanceOf, Config, EraIndex, Error, Exposure, Get, NegativeImbalanceOf, Pallet, Perbill, + SessionInterface, Store, UnappliedSlash, Vec, }; -use codec::{Decode, Encode}; +use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ ensure, - traits::{Currency, Get, Imbalance, OnUnbalanced}, + traits::{Currency, Imbalance, OnUnbalanced}, + CloneNoBound, WeakBoundedVec, }; use scale_info::TypeInfo; use sp_runtime::{ traits::{Saturating, Zero}, DispatchResult, RuntimeDebug, }; -use sp_std::vec::Vec; +use sp_std::{convert::TryFrom, ops::Deref}; + +#[cfg(test)] +use frame_support::pallet_prelude::ConstU32; /// The proportion of the slashing reward to be paid out on the first slashing detection. /// This is f_1 in the paper. @@ -88,8 +92,11 @@ impl SlashingSpan { } /// An encoding of all of a nominator's slashing spans. -#[derive(Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct SlashingSpans { +/// `Limit` bounds the size of the prior slashing spans vector. +#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get))] +pub struct SlashingSpans> { // the index of the current slashing span of the nominator. different for // every stash, resets when the account hits free balance 0. span_index: SpanIndex, @@ -99,10 +106,10 @@ pub struct SlashingSpans { last_nonzero_slash: EraIndex, // all prior slashing spans' start indices, in reverse order (most recent first) // encoded as offsets relative to the slashing span after it. - prior: Vec, + prior: WeakBoundedVec, } -impl SlashingSpans { +impl> SlashingSpans { // creates a new record of slashing spans for a stash, starting at the beginning // of the bonding period, relative to now. pub(crate) fn new(window_start: EraIndex) -> Self { @@ -113,7 +120,7 @@ impl SlashingSpans { // the first slash is applied. setting equal to `window_start` would // put a time limit on nominations. last_nonzero_slash: 0, - prior: Vec::new(), + prior: Default::default(), } } @@ -127,7 +134,7 @@ impl SlashingSpans { } let last_length = next_start - self.last_start; - self.prior.insert(0, last_length); + self.prior.force_insert(0, last_length, Some("SlashingSpans.prior")); self.last_start = next_start; self.span_index += 1; true @@ -181,7 +188,7 @@ impl SlashingSpans { } /// A slashing-span record for a particular stash. -#[derive(Encode, Decode, Default, TypeInfo)] +#[derive(Encode, Decode, Default, TypeInfo, MaxEncodedLen)] pub(crate) struct SpanRecord { slashed: Balance, paid_out: Balance, @@ -196,14 +203,14 @@ impl SpanRecord { } /// Parameters for performing a slash. -#[derive(Clone)] +#[derive(CloneNoBound)] pub(crate) struct SlashParams<'a, T: 'a + Config> { /// The stash account being slashed. pub(crate) stash: &'a T::AccountId, /// The proportion of the slash. pub(crate) slash: Perbill, /// The exposure of the stash and all nominators. - pub(crate) exposure: &'a Exposure>, + pub(crate) exposure: &'a Exposure, T::MaxIndividualExposures>, /// The era where the offence occurred. pub(crate) slash_era: EraIndex, /// The first era in the current bonding period. @@ -223,7 +230,9 @@ pub(crate) struct SlashParams<'a, T: 'a + Config> { /// to be set at a higher level, if any. pub(crate) fn compute_slash( params: SlashParams, -) -> Option>> { +) -> Option< + UnappliedSlash, T::MaxIndividualExposures, T::MaxReportersCount>, +> { let SlashParams { stash, slash, exposure, slash_era, window_start, now, reward_proportion } = params.clone(); @@ -285,14 +294,14 @@ pub(crate) fn compute_slash( // the duration of the era add_offending_validator::(params.stash, true); - let mut nominators_slashed = Vec::new(); + let mut nominators_slashed = Default::default(); reward_payout += slash_nominators::(params, prior_slash_p, &mut nominators_slashed); Some(UnappliedSlash { validator: stash.clone(), own: val_slashed, others: nominators_slashed, - reporters: Vec::new(), + reporters: Default::default(), payout: reward_payout, }) } @@ -337,7 +346,9 @@ fn add_offending_validator(stash: &T::AccountId, disable: bool) { match offending.binary_search_by_key(&validator_index_u32, |(index, _)| *index) { // this is a new offending validator Err(index) => { - offending.insert(index, (validator_index_u32, disable)); + offending + .try_insert(index, (validator_index_u32, disable)) + .expect("Cannot be more than MaxValidatorsCount"); let offending_threshold = T::OffendingValidatorsThreshold::get() * validators.len() as u32; @@ -369,15 +380,19 @@ fn add_offending_validator(stash: &T::AccountId, disable: bool) { fn slash_nominators( params: SlashParams, prior_slash_p: Perbill, - nominators_slashed: &mut Vec<(T::AccountId, BalanceOf)>, + nominators_slashed: &mut WeakBoundedVec< + (T::AccountId, BalanceOf), + T::MaxIndividualExposures, + >, ) -> BalanceOf { let SlashParams { stash: _, slash, exposure, slash_era, window_start, now, reward_proportion } = params; let mut reward_payout = Zero::zero(); - nominators_slashed.reserve(exposure.others.len()); - for nominator in &exposure.others { + let mut slashed_nominators = Vec::with_capacity(exposure.others.len()); + + for nominator in exposure.others.to_vec().into_iter() { let stash = &nominator.who; let mut nom_slashed = Zero::zero(); @@ -417,9 +432,14 @@ fn slash_nominators( } } - nominators_slashed.push((stash.clone(), nom_slashed)); + slashed_nominators.push((stash.clone(), nom_slashed)); } + *nominators_slashed = WeakBoundedVec::<_, T::MaxIndividualExposures>::try_from( + slashed_nominators, + ) + .expect("slashed_nominators has a size of exposure.others which is MaxIndividualExposures"); + reward_payout } @@ -434,7 +454,7 @@ struct InspectingSpans<'a, T: Config + 'a> { dirty: bool, window_start: EraIndex, stash: &'a T::AccountId, - spans: SlashingSpans, + spans: SlashingSpans, paid_out: &'a mut BalanceOf, slash_of: &'a mut BalanceOf, reward_proportion: Perbill, @@ -628,7 +648,14 @@ pub fn do_slash( } /// Apply a previously-unapplied slash. -pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash>) { +pub(crate) fn apply_slash( + unapplied_slash: UnappliedSlash< + T::AccountId, + BalanceOf, + T::MaxIndividualExposures, + T::MaxReportersCount, + >, +) { let mut slashed_imbalance = NegativeImbalanceOf::::zero(); let mut reward_payout = unapplied_slash.payout; @@ -639,7 +666,7 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); } @@ -707,11 +734,11 @@ mod tests { #[test] fn single_slashing_span() { - let spans = SlashingSpans { + let spans = SlashingSpans::> { span_index: 0, last_start: 1000, last_nonzero_slash: 0, - prior: Vec::new(), + prior: Default::default(), }; assert_eq!( @@ -726,7 +753,7 @@ mod tests { span_index: 10, last_start: 1000, last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], + prior: WeakBoundedVec::<_, ConstU32<10>>::try_from(vec![10, 9, 8, 10]).expect("10>3"), }; assert_eq!( @@ -747,7 +774,7 @@ mod tests { span_index: 10, last_start: 1000, last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], + prior: WeakBoundedVec::<_, ConstU32<10>>::try_from(vec![10, 9, 8, 10]).expect("10>3"), }; assert_eq!(spans.prune(981), Some((6, 8))); @@ -797,7 +824,7 @@ mod tests { span_index: 10, last_start: 1000, last_nonzero_slash: 0, - prior: vec![10, 9, 8, 10], + prior: WeakBoundedVec::<_, ConstU32<10>>::try_from(vec![10, 9, 8, 10]).expect("10>4"), }; assert_eq!(spans.prune(2000), Some((6, 10))); assert_eq!( @@ -808,11 +835,11 @@ mod tests { #[test] fn ending_span() { - let mut spans = SlashingSpans { + let mut spans = SlashingSpans::> { span_index: 1, last_start: 10, last_nonzero_slash: 0, - prior: Vec::new(), + prior: Default::default(), }; assert!(spans.end_span(10)); diff --git a/frame/staking/src/tests.rs b/frame/staking/src/tests.rs index d6d92d5bd57fc..8efc642260e20 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -23,7 +23,7 @@ use frame_support::{ assert_noop, assert_ok, dispatch::WithPostDispatchInfo, pallet_prelude::*, - traits::{Currency, Get, ReservableCurrency}, + traits::{Currency, Get, OffenceDetails, OnOffenceHandler, ReservableCurrency}, weights::{extract_actual_weight, GetDispatchInfo}, }; use mock::*; @@ -33,11 +33,8 @@ use sp_runtime::{ traits::{BadOrigin, Dispatchable}, Perbill, Percent, }; -use sp_staking::{ - offence::{OffenceDetails, OnOffenceHandler}, - SessionIndex, -}; -use sp_std::prelude::*; +use sp_staking::SessionIndex; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use substrate_test_utils::assert_eq_uvec; #[test] @@ -104,8 +101,8 @@ fn basic_setup_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); // Account 20 controls the stash from account 21, which is 200 * balance_factor units @@ -115,8 +112,8 @@ fn basic_setup_works() { stash: 21, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); // Account 1 does not control any stash @@ -138,8 +135,8 @@ fn basic_setup_works() { stash: 101, total: 500, active: 500, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); @@ -149,7 +146,10 @@ fn basic_setup_works() { Exposure { total: 1125, own: 1000, - others: vec![IndividualExposure { who: 101, value: 125 }] + others: WeakBoundedVec::<_, ::MaxIndividualExposures>::try_from( + vec![IndividualExposure { who: 101, value: 125 }] + ) + .expect("Please adjust testing parameters"), }, ); assert_eq!( @@ -157,7 +157,10 @@ fn basic_setup_works() { Exposure { total: 1375, own: 1000, - others: vec![IndividualExposure { who: 101, value: 375 }] + others: WeakBoundedVec::<_, ::MaxIndividualExposures>::try_from( + vec![IndividualExposure { who: 101, value: 375 }] + ) + .expect("Please adjust testing parameters"), }, ); @@ -235,12 +238,13 @@ fn rewards_should_work() { assert_eq!(Balances::total_balance(&100), init_balance_100); assert_eq!(Balances::total_balance(&101), init_balance_101); assert_eq_uvec!(Session::validators(), vec![11, 21]); + let individual = BoundedBTreeMap::<_, _, ::MaxValidatorsCount>::try_from( + vec![(11, 100), (21, 50)].into_iter().collect::>(), + ) + .expect("Test configuration needs amendment"); assert_eq!( Staking::eras_reward_points(active_era()), - EraRewardPoints { - total: 50 * 3, - individual: vec![(11, 100), (21, 50)].into_iter().collect(), - } + EraRewardPoints { total: 50 * 3, individual } ); let part_for_10 = Perbill::from_rational::(1000, 1125); let part_for_20 = Perbill::from_rational::(1000, 1375); @@ -275,9 +279,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * total_payout_0 * 2 / 3 + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * total_payout_0 * 2 / 3 + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -313,9 +317,9 @@ fn rewards_should_work() { assert_eq_error_rate!(Balances::total_balance(&21), init_balance_21, 2); assert_eq_error_rate!( Balances::total_balance(&100), - init_balance_100 + - part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + - part_for_100_from_20 * total_payout_0 * 1 / 3, + init_balance_100 + + part_for_100_from_10 * (total_payout_0 * 2 / 3 + total_payout_1) + + part_for_100_from_20 * total_payout_0 * 1 / 3, 2 ); assert_eq_error_rate!(Balances::total_balance(&101), init_balance_101, 2); @@ -381,8 +385,11 @@ fn staking_should_work() { stash: 3, total: 1500, active: 1500, - unlocking: vec![], - claimed_rewards: vec![0], + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![0] + ) + .expect("Test configuration needs changing"), }) ); // e.g. it cannot reserve more than 500 that it has free from the total 2000 @@ -535,28 +542,26 @@ fn nominating_and_rewards_should_work() { assert_eq!(Balances::total_balance(&20), initial_balance_20 + total_payout_0 / 2); initial_balance_20 = Balances::total_balance(&20); + let mut others = WeakBoundedVec::<_, MaxIndividualExposures>::try_from(vec![ + IndividualExposure { who: 1, value: 400 }, + IndividualExposure { who: 3, value: 400 }, + ]) + .expect("Test parameter needs changing"); + assert_eq!(ErasStakers::::iter_prefix_values(active_era()).count(), 2); assert_eq!( Staking::eras_stakers(active_era(), 11), - Exposure { - total: 1000 + 800, - own: 1000, - others: vec![ - IndividualExposure { who: 1, value: 400 }, - IndividualExposure { who: 3, value: 400 }, - ] - }, + Exposure { total: 1000 + 800, own: 1000, others }, ); + + others = WeakBoundedVec::<_, MaxIndividualExposures>::try_from(vec![ + IndividualExposure { who: 1, value: 600 }, + IndividualExposure { who: 3, value: 600 }, + ]) + .expect("Test parameter needs changing"); assert_eq!( Staking::eras_stakers(active_era(), 21), - Exposure { - total: 1000 + 1200, - own: 1000, - others: vec![ - IndividualExposure { who: 1, value: 600 }, - IndividualExposure { who: 3, value: 600 }, - ] - }, + Exposure { total: 1000 + 1200, own: 1000, others }, ); // the total reward for era 1 @@ -623,7 +628,10 @@ fn nominators_also_get_slashed_pro_rata() { // 11 goes offline on_offence_now( - &[OffenceDetails { offender: (11, initial_exposure.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, initial_exposure.clone()), + reporters: Default::default(), + }], &[slash_percent], ); @@ -935,8 +943,8 @@ fn reward_destination_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -958,8 +966,11 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0], + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![0] + ) + .expect("Test configuration needs changing"), }) ); @@ -986,8 +997,11 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0, 1], + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![0, 1] + ) + .expect("Test configuration needs changing"), }) ); @@ -1015,8 +1029,11 @@ fn reward_destination_works() { stash: 11, total: 1000 + total_payout_0, active: 1000 + total_payout_0, - unlocking: vec![], - claimed_rewards: vec![0, 1, 2], + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![0, 1, 2] + ) + .expect("Test configuration needs changing"), }) ); // Check that amount in staked account is NOT increased. @@ -1080,8 +1097,8 @@ fn bond_extra_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1097,8 +1114,8 @@ fn bond_extra_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1111,8 +1128,8 @@ fn bond_extra_works() { stash: 11, total: 1000000, active: 1000000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); }); @@ -1149,13 +1166,13 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); assert_eq!( Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000, own: 1000, others: vec![] } + Exposure { total: 1000, own: 1000, others: Default::default() } ); // deposit the extra 100 units @@ -1167,14 +1184,14 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); // Exposure is a snapshot! only updated after the next era update. assert_ne!( Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] } + Exposure { total: 1000 + 100, own: 1000 + 100, others: Default::default() } ); // trigger next era. @@ -1188,14 +1205,14 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 1000 + 100, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); // Exposure is now updated. assert_eq!( Staking::eras_stakers(active_era(), 11), - Exposure { total: 1000 + 100, own: 1000 + 100, others: vec![] } + Exposure { total: 1000 + 100, own: 1000 + 100, others: Default::default() } ); // Unbond almost all of the funds in stash. @@ -1206,8 +1223,11 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 1000, era: 2 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }), ); @@ -1219,8 +1239,11 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 1000, era: 2 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }), ); @@ -1235,8 +1258,11 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 1000 + 100, active: 100, - unlocking: vec![UnlockChunk { value: 1000, era: 2 + 3 }], - claimed_rewards: vec![] + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 1000, era: 2 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }), ); @@ -1251,8 +1277,8 @@ fn bond_extra_and_withdraw_unbonded_works() { stash: 11, total: 100, active: 100, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }), ); }) @@ -1262,7 +1288,7 @@ fn bond_extra_and_withdraw_unbonded_works() { fn too_many_unbond_calls_should_not_work() { ExtBuilder::default().build_and_execute(|| { // locked at era 0 until 3 - for _ in 0..MAX_UNLOCKING_CHUNKS - 1 { + for _ in 0..::MaxUnlockingChunks::get() - 1 { assert_ok!(Staking::unbond(Origin::signed(10), 1)); } @@ -1309,8 +1335,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1328,8 +1354,11 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 2 + 3 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 900, era: 2 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -1341,8 +1370,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1354,8 +1383,11 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 5 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 900, era: 5 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -1367,8 +1399,11 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 5 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 400, era: 5 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -1380,8 +1415,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1395,12 +1430,13 @@ fn rebond_works() { stash: 11, total: 1000, active: 100, - unlocking: vec![ + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ UnlockChunk { value: 300, era: 5 }, UnlockChunk { value: 300, era: 5 }, UnlockChunk { value: 300, era: 5 }, - ], - claimed_rewards: vec![], + ]) + .expect("MaxUnlockingChunks>3"), + claimed_rewards: Default::default(), }) ); @@ -1412,11 +1448,12 @@ fn rebond_works() { stash: 11, total: 1000, active: 600, - unlocking: vec![ + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ UnlockChunk { value: 300, era: 5 }, UnlockChunk { value: 100, era: 5 }, - ], - claimed_rewards: vec![], + ]) + .expect("MaxUnlockingChunks>2"), + claimed_rewards: Default::default(), }) ); }) @@ -1442,8 +1479,8 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1457,8 +1494,11 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 600, - unlocking: vec![UnlockChunk { value: 400, era: 2 + 3 },], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 400, era: 2 + 3 }, + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -1472,11 +1512,12 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 300, - unlocking: vec![ + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 300, era: 3 + 3 }, - ], - claimed_rewards: vec![], + ]) + .expect("MaxUnlockingChunks>2"), + claimed_rewards: Default::default(), }) ); @@ -1490,12 +1531,13 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 100, - unlocking: vec![ + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 300, era: 3 + 3 }, UnlockChunk { value: 200, era: 4 + 3 }, - ], - claimed_rewards: vec![], + ]) + .expect("MaxUnlockingChunks>3"), + claimed_rewards: Default::default(), }) ); @@ -1507,11 +1549,12 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 500, - unlocking: vec![ + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ UnlockChunk { value: 400, era: 2 + 3 }, UnlockChunk { value: 100, era: 3 + 3 }, - ], - claimed_rewards: vec![], + ]) + .expect("MaxUnlockingChunks>2"), + claimed_rewards: Default::default(), }) ); }) @@ -1539,8 +1582,11 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 100, - unlocking: vec![UnlockChunk { value: 900, era: 1 + 3 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 900, era: 1 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -1552,8 +1598,11 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 200, - unlocking: vec![UnlockChunk { value: 800, era: 1 + 3 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from(vec![ + UnlockChunk { value: 800, era: 1 + 3 } + ]) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); // Event emitted should be correct @@ -1567,8 +1616,8 @@ fn rebond_emits_right_value_in_event() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); // Event emitted should be correct, only 800 @@ -1596,15 +1645,19 @@ fn reward_to_stake_works() { let _ = Balances::make_free_balance_be(&20, 1000); // Bypass logic and change current exposure - ErasStakers::::insert(0, 21, Exposure { total: 69, own: 69, others: vec![] }); + ErasStakers::::insert( + 0, + 21, + Exposure { total: 69, own: 69, others: Default::default() }, + ); >::insert( &20, StakingLedger { stash: 21, total: 69, active: 69, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }, ); @@ -1847,8 +1900,11 @@ fn bond_with_no_staked_value() { stash: 1, active: 0, total: 5, - unlocking: vec![UnlockChunk { value: 5, era: 3 }], - claimed_rewards: vec![], + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from( + vec![UnlockChunk { value: 5, era: 3 }] + ) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), }) ); @@ -2066,16 +2122,30 @@ fn reward_validator_slashing_validator_does_not_overflow() { // Set staker let _ = Balances::make_free_balance_be(&11, stake); - let exposure = Exposure:: { total: stake, own: stake, others: vec![] }; - let reward = EraRewardPoints:: { + let exposure = Exposure::::MaxIndividualExposures> { + total: stake, + own: stake, + others: Default::default(), + }; + let reward = EraRewardPoints { total: 1, - individual: vec![(11, 1)].into_iter().collect(), + individual: BoundedBTreeMap::<_, _, ::MaxValidatorsCount>::try_from( + vec![(11, 1)].into_iter().collect::>(), + ) + .expect("MaxValidatorsCount>0"), }; // Check reward ErasRewardPoints::::insert(0, reward); ErasStakers::::insert(0, 11, &exposure); - ErasStakersClipped::::insert(0, 11, exposure); + + let exposure_clipped = + Exposure::::MaxRewardableIndividualExposures> { + total: stake, + own: stake, + others: Default::default(), + }; + ErasStakersClipped::::insert(0, 11, exposure_clipped); ErasValidatorReward::::insert(0, stake); assert_ok!(Staking::payout_stakers(Origin::signed(1337), 11, 0)); assert_eq!(Balances::total_balance(&11), stake * 2); @@ -2094,7 +2164,10 @@ fn reward_validator_slashing_validator_does_not_overflow() { Exposure { total: stake, own: 1, - others: vec![IndividualExposure { who: 2, value: stake - 1 }], + others: WeakBoundedVec::<_, ::MaxIndividualExposures>::try_from( + vec![IndividualExposure { who: 2, value: stake - 1 }], + ) + .expect("MaxIndividualExposures>0"), }, ); @@ -2102,7 +2175,7 @@ fn reward_validator_slashing_validator_does_not_overflow() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(100)], ); @@ -2132,7 +2205,11 @@ fn reward_from_authorship_event_handler_works() { assert_eq!( ErasRewardPoints::::get(active_era()), EraRewardPoints { - individual: vec![(11, 20 + 2 * 2 + 1), (21, 1)].into_iter().collect(), + individual: + BoundedBTreeMap::<_, _, ::MaxValidatorsCount>::try_from( + vec![(11, 20 + 2 * 2 + 1), (21, 1)].into_iter().collect::>() + ) + .expect("MaxValidatorsCount should be > 1"), total: 26, }, ); @@ -2151,7 +2228,14 @@ fn add_reward_points_fns_works() { assert_eq!( ErasRewardPoints::::get(active_era()), - EraRewardPoints { individual: vec![(11, 4), (21, 2)].into_iter().collect(), total: 6 }, + EraRewardPoints { + individual: + BoundedBTreeMap::<_, _, ::MaxValidatorsCount>::try_from( + vec![(11, 4), (21, 2)].into_iter().collect::>() + ) + .expect("MaxValidatorsCount>1"), + total: 6 + }, ); }) } @@ -2206,7 +2290,7 @@ fn offence_forces_new_era() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(5)], ); @@ -2224,7 +2308,7 @@ fn offence_ensures_new_era_without_clobbering() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(5)], ); @@ -2242,7 +2326,7 @@ fn offence_deselects_validator_even_when_slash_is_zero() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(0)], ); @@ -2267,8 +2351,8 @@ fn slashing_performed_according_exposure() { // Handle an offence with a historical exposure. on_offence_now( &[OffenceDetails { - offender: (11, Exposure { total: 500, own: 500, others: vec![] }), - reporters: vec![], + offender: (11, Exposure { total: 500, own: 500, others: Default::default() }), + reporters: Default::default(), }], &[Perbill::from_percent(50)], ); @@ -2289,7 +2373,7 @@ fn slash_in_old_span_does_not_deselect() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(0)], ); @@ -2312,7 +2396,7 @@ fn slash_in_old_span_does_not_deselect() { on_offence_in_era( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(0)], 1, @@ -2327,7 +2411,7 @@ fn slash_in_old_span_does_not_deselect() { on_offence_in_era( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], // NOTE: A 100% slash here would clean up the account, causing de-registration. &[Perbill::from_percent(95)], @@ -2357,7 +2441,8 @@ fn reporters_receive_their_slice() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1, 2], + reporters: WeakBoundedVec::<_, _>::try_from(vec![1, 2]) + .expect("MaxReportersCount >= 2"), }], &[Perbill::from_percent(50)], ); @@ -2384,7 +2469,8 @@ fn subsequent_reports_in_same_span_pay_out_less() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], + reporters: WeakBoundedVec::<_, _>::try_from(vec![1]) + .expect("MaxReportersCount >= 1"), }], &[Perbill::from_percent(20)], ); @@ -2397,7 +2483,8 @@ fn subsequent_reports_in_same_span_pay_out_less() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], + reporters: WeakBoundedVec::<_, _>::try_from(vec![1]) + .expect("MaxReportersCount >= 1"), }], &[Perbill::from_percent(50)], ); @@ -2428,11 +2515,11 @@ fn invulnerables_are_not_slashed() { &[ OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }, OffenceDetails { offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], + reporters: Default::default(), }, ], &[Perbill::from_percent(50), Perbill::from_percent(20)], @@ -2462,7 +2549,7 @@ fn dont_slash_if_fraction_is_zero() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(0)], ); @@ -2483,7 +2570,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(50)], ); @@ -2495,7 +2582,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(25)], ); @@ -2506,7 +2593,7 @@ fn only_slash_for_max_in_era() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(60)], ); @@ -2528,7 +2615,7 @@ fn garbage_collection_after_slashing() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); @@ -2540,7 +2627,7 @@ fn garbage_collection_after_slashing() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(100)], ); @@ -2581,7 +2668,10 @@ fn garbage_collection_on_window_pruning() { let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( - &[OffenceDetails { offender: (11, Staking::eras_stakers(now, 11)), reporters: vec![] }], + &[OffenceDetails { + offender: (11, Staking::eras_stakers(now, 11)), + reporters: Default::default(), + }], &[Perbill::from_percent(10)], ); @@ -2624,7 +2714,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], 2, @@ -2650,7 +2740,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[OffenceDetails { offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(30)], 3, @@ -2671,7 +2761,7 @@ fn slashing_nominators_by_span_max() { on_offence_in_era( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(20)], 2, @@ -2705,7 +2795,7 @@ fn slashes_are_summed_across_spans() { on_offence_now( &[OffenceDetails { offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); @@ -2728,7 +2818,7 @@ fn slashes_are_summed_across_spans() { on_offence_now( &[OffenceDetails { offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); @@ -2758,7 +2848,7 @@ fn deferred_slashes_are_deferred() { on_offence_now( &[OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); @@ -2797,7 +2887,7 @@ fn remove_deferred() { let nominated_value = exposure.others.iter().find(|o| o.who == 101).unwrap().value; on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (11, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(10)], ); @@ -2807,7 +2897,7 @@ fn remove_deferred() { mock::start_active_era(2); on_offence_in_era( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (11, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(15)], 1, ); @@ -2862,30 +2952,30 @@ fn remove_multi_deferred() { assert_eq!(Balances::free_balance(101), 2000); on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (11, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(10)], ); on_offence_now( &[OffenceDetails { offender: (21, Staking::eras_stakers(active_era(), 21)), - reporters: vec![], + reporters: Default::default(), }], &[Perbill::from_percent(10)], ); on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (11, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(25)], ); on_offence_now( - &[OffenceDetails { offender: (42, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (42, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(25)], ); on_offence_now( - &[OffenceDetails { offender: (69, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (69, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(25)], ); @@ -2934,7 +3024,10 @@ fn slash_kicks_validators_not_nominators_and_disables_nominator_for_kicked_valid assert_eq!(exposure_21.total, 1000 + 375); on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: Default::default(), + }], &[Perbill::from_percent(10)], ); @@ -2982,13 +3075,19 @@ fn non_slashable_offence_doesnt_disable_validator() { // offence with no slash associated on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: Default::default(), + }], &[Perbill::zero()], ); // offence that slashes 25% of the bond on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (21, exposure_21.clone()), + reporters: Default::default(), + }], &[Perbill::from_percent(25)], ); @@ -3021,21 +3120,30 @@ fn offence_threshold_triggers_new_era() { let exposure_31 = Staking::eras_stakers(Staking::active_era().unwrap().index, &31); on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: Default::default(), + }], &[Perbill::zero()], ); assert_eq!(ForceEra::::get(), Forcing::NotForcing); on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (21, exposure_21.clone()), + reporters: Default::default(), + }], &[Perbill::zero()], ); assert_eq!(ForceEra::::get(), Forcing::NotForcing); on_offence_now( - &[OffenceDetails { offender: (31, exposure_31.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (31, exposure_31.clone()), + reporters: Default::default(), + }], &[Perbill::zero()], ); @@ -3057,12 +3165,18 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { let exposure_21 = Staking::eras_stakers(Staking::active_era().unwrap().index, &21); on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: Default::default(), + }], &[Perbill::zero()], ); on_offence_now( - &[OffenceDetails { offender: (21, exposure_21.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (21, exposure_21.clone()), + reporters: Default::default(), + }], &[Perbill::from_percent(25)], ); @@ -3079,7 +3193,10 @@ fn disabled_validators_are_kept_disabled_for_whole_era() { // validator 10 should now get disabled on_offence_now( - &[OffenceDetails { offender: (11, exposure_11.clone()), reporters: vec![] }], + &[OffenceDetails { + offender: (11, exposure_11.clone()), + reporters: Default::default(), + }], &[Perbill::from_percent(25)], ); @@ -3192,7 +3309,7 @@ fn zero_slash_keeps_nominators() { assert_eq!(Balances::free_balance(101), 2000); on_offence_now( - &[OffenceDetails { offender: (11, exposure.clone()), reporters: vec![] }], + &[OffenceDetails { offender: (11, exposure.clone()), reporters: Default::default() }], &[Perbill::from_percent(0)], ); @@ -3224,48 +3341,48 @@ fn six_session_delay() { let init_active_era = active_era(); // pallet-session is delaying session by one, thus the next session to plan is +2. - assert_eq!(>::new_session(init_session + 2), None); + assert_eq!(>::new_session(init_session + 2), None); assert_eq!( - >::new_session(init_session + 3), + >::new_session(init_session + 3), Some(val_set.clone()) ); - assert_eq!(>::new_session(init_session + 4), None); - assert_eq!(>::new_session(init_session + 5), None); + assert_eq!(>::new_session(init_session + 4), None); + assert_eq!(>::new_session(init_session + 5), None); assert_eq!( - >::new_session(init_session + 6), + >::new_session(init_session + 6), Some(val_set.clone()) ); - >::end_session(init_session); - >::start_session(init_session + 1); + >::end_session(init_session); + >::start_session(init_session + 1); assert_eq!(active_era(), init_active_era); - >::end_session(init_session + 1); - >::start_session(init_session + 2); + >::end_session(init_session + 1); + >::start_session(init_session + 2); assert_eq!(active_era(), init_active_era); // Reward current era Staking::reward_by_ids(vec![(11, 1)]); // New active era is triggered here. - >::end_session(init_session + 2); - >::start_session(init_session + 3); + >::end_session(init_session + 2); + >::start_session(init_session + 3); assert_eq!(active_era(), init_active_era + 1); - >::end_session(init_session + 3); - >::start_session(init_session + 4); + >::end_session(init_session + 3); + >::start_session(init_session + 4); assert_eq!(active_era(), init_active_era + 1); - >::end_session(init_session + 4); - >::start_session(init_session + 5); + >::end_session(init_session + 4); + >::start_session(init_session + 5); assert_eq!(active_era(), init_active_era + 1); // Reward current era Staking::reward_by_ids(vec![(21, 2)]); // New active era is triggered here. - >::end_session(init_session + 5); - >::start_session(init_session + 6); + >::end_session(init_session + 5); + >::start_session(init_session + 6); assert_eq!(active_era(), init_active_era + 2); // That reward are correct @@ -3277,7 +3394,7 @@ fn six_session_delay() { #[test] fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward() { ExtBuilder::default().build_and_execute(|| { - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=::MaxRewardableIndividualExposures::get() { let stash = 10_000 + i as AccountId; let controller = 20_000 + i as AccountId; let balance = 10_000 + i as Balance; @@ -3300,7 +3417,7 @@ fn test_max_nominator_rewarded_per_validator_and_cant_steal_someone_else_reward( mock::make_all_reward_payment(1); // Assert only nominators from 1 to Max are rewarded - for i in 0..=::MaxNominatorRewardedPerValidator::get() { + for i in 0..=::MaxRewardableIndividualExposures::get() { let stash = 10_000 + i as AccountId; let balance = 10_000 + i as Balance; if stash == 10_000 { @@ -3372,8 +3489,11 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![1] + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![1] + ) + .expect("MaxHistoryDepth>1"), }) ); @@ -3394,8 +3514,11 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (1..=14).collect() + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + (1..=14).collect::>() + ) + .expect("Test configuration should be changed") }) ); @@ -3415,8 +3538,11 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![15, 98] + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![15, 98] + ) + .expect("MaxHistoryDepth should be > 1"), }) ); @@ -3430,8 +3556,11 @@ fn test_payout_stakers() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![15, 23, 42, 69, 98] + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + vec![15, 23, 42, 69, 98] + ) + .expect("MaxHistoryDepth should be > 4") }) ); }); @@ -3508,7 +3637,7 @@ fn payout_stakers_handles_weight_refund() { // Note: this test relies on the assumption that `payout_stakers_alive_staked` is solely used by // `payout_stakers` to calculate the weight of each payout op. ExtBuilder::default().has_stakers(false).build_and_execute(|| { - let max_nom_rewarded = ::MaxNominatorRewardedPerValidator::get(); + let max_nom_rewarded = ::MaxRewardableIndividualExposures::get(); // Make sure the configured value is meaningful for our use. assert!(max_nom_rewarded >= 4); let half_max_nom_rewarded = max_nom_rewarded / 2; @@ -3625,8 +3754,8 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 9, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); mock::start_active_era(5); @@ -3637,8 +3766,11 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (0..5).collect(), + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + (0..5).collect::>() + ) + .expect("MaxHistoryDepth should be >= 5"), }) ); mock::start_active_era(99); @@ -3649,8 +3781,11 @@ fn bond_during_era_correctly_populates_claimed_rewards() { stash: 13, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: (15..99).collect(), + unlocking: BoundedVec::default(), + claimed_rewards: WeakBoundedVec::<_, ::MaxHistoryDepth>::try_from( + (15..99).collect::>() + ) + .expect("Some test configuration may need changing"), }) ); }); @@ -3667,11 +3802,11 @@ fn offences_weight_calculated_correctly() { let n_offence_unapplied_weight = ::DbWeight::get().reads_writes(4, 1) + ::DbWeight::get().reads_writes(4, 5); - let offenders: Vec::AccountId, pallet_session::historical::IdentificationTuple>> + let offenders: Vec::AccountId, pallet_session::historical::IdentificationTuple, _>> = (1..10).map(|i| OffenceDetails { offender: (i, Staking::eras_stakers(active_era(), i)), - reporters: vec![], + reporters: Default::default(), } ).collect(); assert_eq!(Staking::on_offence(&offenders, &[Perbill::from_percent(50)], 0), n_offence_unapplied_weight); @@ -3680,7 +3815,7 @@ fn offences_weight_calculated_correctly() { let one_offender = [ OffenceDetails { offender: (11, Staking::eras_stakers(active_era(), 11)), - reporters: vec![1], + reporters: WeakBoundedVec::<_, _>::try_from(vec![1]).expect("MaxReportersCount > 0"), }, ]; @@ -3868,8 +4003,8 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), } ); @@ -3882,8 +4017,11 @@ fn cannot_rebond_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], - claimed_rewards: vec![] + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from( + vec![UnlockChunk { value: 10 * 1000, era: 3 }] + ) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), } ); @@ -3905,8 +4043,8 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 10 * 1000, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), } ); @@ -3919,8 +4057,11 @@ fn cannot_bond_extra_to_lower_than_ed() { stash: 21, total: 10 * 1000, active: 0, - unlocking: vec![UnlockChunk { value: 10 * 1000, era: 3 }], - claimed_rewards: vec![] + unlocking: BoundedVec::<_, ::MaxUnlockingChunks>::try_from( + vec![UnlockChunk { value: 10 * 1000, era: 3 }] + ) + .expect("MaxUnlockingChunks>1"), + claimed_rewards: Default::default(), } ); @@ -3946,8 +4087,8 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: 1000 * ed, active: 1000 * ed, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), } ); @@ -3963,8 +4104,8 @@ fn do_not_die_when_active_is_ed() { stash: 21, total: ed, active: ed, - unlocking: vec![], - claimed_rewards: vec![] + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), } ); }) @@ -3985,8 +4126,8 @@ mod election_data_provider { #[test] fn targets_2sec_block() { let mut validators = 1000; - while ::WeightInfo::get_npos_targets(validators) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_targets(validators) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { validators += 1; } @@ -4003,8 +4144,8 @@ mod election_data_provider { let slashing_spans = validators; let mut nominators = 1000; - while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) < - 2 * frame_support::weights::constants::WEIGHT_PER_SECOND + while ::WeightInfo::get_npos_voters(validators, nominators, slashing_spans) + < 2 * frame_support::weights::constants::WEIGHT_PER_SECOND { nominators += 1; } @@ -4031,12 +4172,16 @@ mod election_data_provider { ExtBuilder::default().build_and_execute(|| { assert_eq!(Staking::nominators(101).unwrap().targets, vec![11, 21]); assert_eq!( - >::voters(None) - .unwrap() - .iter() - .find(|x| x.0 == 101) - .unwrap() - .2, + >::voters(None) + .unwrap() + .iter() + .find(|x| x.0 == 101) + .unwrap() + .2, vec![11, 21] ); @@ -4046,7 +4191,7 @@ mod election_data_provider { // 11 is gone. start_active_era(2); assert_eq!( - >::voters(None) + >::voters(None) .unwrap() .iter() .find(|x| x.0 == 101) @@ -4058,7 +4203,7 @@ mod election_data_provider { // resubmit and it is back assert_ok!(Staking::nominate(Origin::signed(100), vec![11, 21])); assert_eq!( - >::voters(None) + >::voters(None) .unwrap() .iter() .find(|x| x.0 == 101) @@ -4076,8 +4221,8 @@ mod election_data_provider { .build_and_execute(|| { // sum of all nominators who'd be voters (1), plus the self-votes (4). assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 5 ); @@ -4116,8 +4261,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 7 ); @@ -4161,8 +4306,8 @@ mod election_data_provider { // and total voters assert_eq!( - ::SortedListProvider::count() + - >::iter().count() as u32, + ::SortedListProvider::count() + + >::iter().count() as u32, 6 ); @@ -4267,7 +4412,11 @@ fn count_check_works() { Validators::::insert(987654321, ValidatorPrefs::default()); Nominators::::insert( 987654321, - Nominations { targets: vec![], submitted_in: Default::default(), suppressed: false }, + Nominations { + targets: BoundedVec::default(), + submitted_in: Default::default(), + suppressed: false, + }, ); }) } @@ -4376,7 +4525,7 @@ fn chill_other_works() { ); // Change the minimum bond... but no limits. - assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, None, None, None)); + assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, None, None)); // Still can't chill these users assert_noop!( @@ -4389,14 +4538,7 @@ fn chill_other_works() { ); // Add limits, but no threshold - assert_ok!(Staking::set_staking_limits( - Origin::root(), - 1_500, - 2_000, - Some(10), - Some(10), - None - )); + assert_ok!(Staking::set_staking_limits(Origin::root(), 1_500, 2_000, Some(10), None)); // Still can't chill these users assert_noop!( @@ -4414,7 +4556,6 @@ fn chill_other_works() { 1_500, 2_000, None, - None, Some(Percent::from_percent(0)) )); @@ -4423,10 +4564,8 @@ fn chill_other_works() { Staking::chill_other(Origin::signed(1337), 1), Error::::CannotChillOther ); - assert_noop!( - Staking::chill_other(Origin::signed(1337), 3), - Error::::CannotChillOther - ); + // But validator will succeed because MaxValidatorsCount is always set + assert_ok!(Staking::chill_other(Origin::signed(1337), 3)); // Add threshold and limits assert_ok!(Staking::set_staking_limits( @@ -4434,21 +4573,18 @@ fn chill_other_works() { 1_500, 2_000, Some(10), - Some(10), Some(Percent::from_percent(75)) )); - // 16 people total because tests start with 2 active one + // 16 nominators and 17 validators left (test starts with 1 nominator and 3 validator) assert_eq!(CounterForNominators::::get(), 15 + initial_nominators); - assert_eq!(CounterForValidators::::get(), 15 + initial_validators); + assert_eq!(CounterForValidators::::get(), 14 + initial_validators); // 1 was chilled - // Users can now be chilled down to 7 people, so we try to remove 9 of them (starting - // with 16) + // Nominators can now be chilled down to 7 people, so we try to remove 9 of them + // (starting with 16) for i in 6..15 { let b = 4 * i + 1; - let d = 4 * i + 3; assert_ok!(Staking::chill_other(Origin::signed(1337), b)); - assert_ok!(Staking::chill_other(Origin::signed(1337), d)); } // chill a nominator. Limit is not reached, not chill-able @@ -4457,9 +4593,27 @@ fn chill_other_works() { Staking::chill_other(Origin::signed(1337), 1), Error::::CannotChillOther ); - // chill a validator. Limit is reached, chill-able. - assert_eq!(CounterForValidators::::get(), 9); - assert_ok!(Staking::chill_other(Origin::signed(1337), 3)); + + // Max number of validators is set externally to 100, so need to change threshold + assert_ok!(Staking::set_staking_limits( + Origin::root(), + 1_500, + 2_000, + Some(10), + Some(Percent::from_percent(7)) + )); + + for i in 6..15 { + let d = 4 * i + 3; + assert_ok!(Staking::chill_other(Origin::signed(1337), d)); + } + + // chill a validator. Limit is not reached, not chill-able + assert_eq!(CounterForValidators::::get(), 8); + assert_noop!( + Staking::chill_other(Origin::signed(1337), 3), + Error::::CannotChillOther + ); }) } @@ -4472,19 +4626,17 @@ fn capped_stakers_works() { assert_eq!(nominator_count, 1); // Change the maximums - let max = 10; assert_ok!(Staking::set_staking_limits( Origin::root(), 10, 10, - Some(max), - Some(max), + Some(10), Some(Percent::from_percent(0)) )); // can create `max - validator_count` validators let mut some_existing_validator = AccountId::default(); - for i in 0..max - validator_count { + for i in 0..::MaxValidatorsCount::get() - validator_count { let (_, controller) = testing_utils::create_stash_controller::( i + 10_000_000, 100, @@ -4510,7 +4662,7 @@ fn capped_stakers_works() { // same with nominators let mut some_existing_nominator = AccountId::default(); - for i in 0..max - nominator_count { + for i in 0..10 - nominator_count { let (_, controller) = testing_utils::create_stash_controller::( i + 20_000_000, 100, @@ -4542,9 +4694,13 @@ fn capped_stakers_works() { )); // No problem when we set to `None` again - assert_ok!(Staking::set_staking_limits(Origin::root(), 10, 10, None, None, None)); + assert_ok!(Staking::set_staking_limits(Origin::root(), 10, 10, None, None)); assert_ok!(Staking::nominate(Origin::signed(last_nominator), vec![1])); - assert_ok!(Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default())); + // But fail validators given limit is set externally + assert_noop!( + Staking::validate(Origin::signed(last_validator), ValidatorPrefs::default()), + Error::::TooManyValidators + ); }) } diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 32c8dc80da158..6524bc9684a97 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -145,7 +145,6 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) // Storage: BagsList ListNodes (r:2 w:2) @@ -154,7 +153,7 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { (69_092_000 as Weight) - .saturating_add(T::DbWeight::get().reads(11 as Weight)) + .saturating_add(T::DbWeight::get().reads(10 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -419,13 +418,12 @@ impl WeightInfo for SubstrateWeight { .saturating_add(T::DbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) // Storage: Staking ChillThreshold (r:0 w:1) // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_limits() -> Weight { (6_353_000 as Weight) - .saturating_add(T::DbWeight::get().writes(5 as Weight)) + .saturating_add(T::DbWeight::get().writes(4 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking ChillThreshold (r:1 w:0) @@ -513,7 +511,6 @@ impl WeightInfo for () { // Storage: Staking Ledger (r:1 w:0) // Storage: Staking MinValidatorBond (r:1 w:0) // Storage: Staking Validators (r:1 w:1) - // Storage: Staking MaxValidatorsCount (r:1 w:0) // Storage: Staking Nominators (r:1 w:1) // Storage: Staking CounterForNominators (r:1 w:1) // Storage: BagsList ListNodes (r:2 w:2) @@ -522,7 +519,7 @@ impl WeightInfo for () { // Storage: Staking CounterForValidators (r:1 w:1) fn validate() -> Weight { (69_092_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(11 as Weight)) + .saturating_add(RocksDbWeight::get().reads(10 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -787,13 +784,12 @@ impl WeightInfo for () { .saturating_add(RocksDbWeight::get().reads((1 as Weight).saturating_mul(v as Weight))) } // Storage: Staking MinValidatorBond (r:0 w:1) - // Storage: Staking MaxValidatorsCount (r:0 w:1) // Storage: Staking ChillThreshold (r:0 w:1) // Storage: Staking MaxNominatorsCount (r:0 w:1) // Storage: Staking MinNominatorBond (r:0 w:1) fn set_staking_limits() -> Weight { (6_353_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(5 as Weight)) + .saturating_add(RocksDbWeight::get().writes(4 as Weight)) } // Storage: Staking Ledger (r:1 w:0) // Storage: Staking ChillThreshold (r:1 w:0) diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index d01bbf6ace526..7fedd64043ea7 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -27,6 +27,7 @@ mod default_no_bound; mod dummy_part_checker; mod key_prefix; mod match_and_insert; +mod ord_no_bound; mod pallet; mod partial_eq_no_bound; mod storage; @@ -475,6 +476,12 @@ pub fn derive_runtime_debug_no_bound(input: TokenStream) -> TokenStream { } } +/// Derive [`Ord`] but do not bound any generic. Docs are at `frame_support::OrdNoBound`. +#[proc_macro_derive(OrdNoBound)] +pub fn derive_ord_no_bound(input: TokenStream) -> TokenStream { + ord_no_bound::derive_ord_no_bound(input) +} + /// Derive [`PartialEq`] but do not bound any generic. Docs are at /// `frame_support::PartialEqNoBound`. #[proc_macro_derive(PartialEqNoBound)] diff --git a/frame/support/procedural/src/ord_no_bound.rs b/frame/support/procedural/src/ord_no_bound.rs new file mode 100644 index 0000000000000..fe1b45a466eb5 --- /dev/null +++ b/frame/support/procedural/src/ord_no_bound.rs @@ -0,0 +1,82 @@ +// This file is part of Substrate. + +// Copyright (C) 2020-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use syn::spanned::Spanned; + +/// Derive Ord but do not bound any generic. +pub fn derive_ord_no_bound(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input: syn::DeriveInput = match syn::parse(input) { + Ok(input) => input, + Err(e) => return e.to_compile_error().into(), + }; + + let name = &input.ident; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let impl_ = match input.data { + syn::Data::Struct(struct_) => match struct_.fields { + syn::Fields::Named(named) => { + let fields = named.named.iter().map(|i| &i.ident).map(|i| { + quote::quote_spanned!(i.span() => if self.#i != other.#i { + return self.#i.cmp(&other.#i); + }) + }); + + quote::quote!( #( #fields )* ) + }, + syn::Fields::Unnamed(unnamed) => { + let fields = + unnamed.unnamed.iter().enumerate().map(|(i, _)| syn::Index::from(i)).map(|i| { + quote::quote_spanned!(i.span() => if self.#i != other.#i { + return self.#i.cmp(&other.#i); + }) + }); + + quote::quote!( #( #fields )* ) + }, + syn::Fields::Unit => { + quote::quote!() + }, + }, + syn::Data::Enum(_) => { + let msg = "Enum type not supported by `derive(OrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + syn::Data::Union(_) => { + let msg = "Union type not supported by `derive(OrdNoBound)`"; + return syn::Error::new(input.span(), msg).to_compile_error().into() + }, + }; + + quote::quote!( + const _: () = { + impl #impl_generics core::cmp::Ord for #name #ty_generics #where_clause { + fn cmp(&self, other: &Self) -> core::cmp::Ordering { + #impl_ + core::cmp::Ordering::Equal + } + } + + impl #impl_generics core::cmp::PartialOrd for #name #ty_generics #where_clause { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } + } + }; + ) + .into() +} diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index af9192f6ea836..5ab6d33d4ea4a 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -635,6 +635,24 @@ pub use frame_support_procedural::EqNoBound; /// ``` pub use frame_support_procedural::PartialEqNoBound; +/// Derive [`Ord`] but do not bound any generic. +/// +/// This is useful for type generic over runtime: +/// ``` +/// # use frame_support::{EqNoBound, OrdNoBound, PartialEqNoBound}; +/// trait Config { +/// type C: Ord; +/// } +/// +/// // `Foo` implements [`Ord`] because `C` bounds [`Ord`]. +/// // Otherwise compilation will fail with an output telling `c` doesn't implement [`Ord`]. +/// #[derive(PartialEqNoBound, EqNoBound, OrdNoBound)] +/// struct Foo { +/// c: T::C, +/// } +/// ``` +pub use frame_support_procedural::OrdNoBound; + /// Derive [`Debug`] but do not bound any generic. /// /// This is useful for type generic over runtime: diff --git a/frame/support/src/storage/bounded_btree_map.rs b/frame/support/src/storage/bounded_btree_map.rs index 7a59206aeba0e..b7b02ed7f4aa9 100644 --- a/frame/support/src/storage/bounded_btree_map.rs +++ b/frame/support/src/storage/bounded_btree_map.rs @@ -20,7 +20,10 @@ use crate::{storage::StorageDecodeLength, traits::Get}; use codec::{Decode, Encode, MaxEncodedLen}; use sp_std::{ - borrow::Borrow, collections::btree_map::BTreeMap, convert::TryFrom, marker::PhantomData, + borrow::Borrow, + collections::btree_map::{BTreeMap, Entry}, + convert::TryFrom, + marker::PhantomData, ops::Deref, }; @@ -151,6 +154,15 @@ where { self.0.remove_entry(key) } + + /// Exactly the same semantics as [`BTreeMap::entry`]. + /// Gets the given key's corresponding entry in the map for in-place manipulation. + pub fn entry(&mut self, key: K) -> Entry<'_, K, V> + where + K: Ord, + { + self.0.entry(key) + } } impl Default for BoundedBTreeMap diff --git a/frame/support/src/storage/bounded_vec.rs b/frame/support/src/storage/bounded_vec.rs index 3b5e7bda1651c..9362143a65b2a 100644 --- a/frame/support/src/storage/bounded_vec.rs +++ b/frame/support/src/storage/bounded_vec.rs @@ -26,9 +26,9 @@ use crate::{ use codec::{Decode, Encode, EncodeLike, MaxEncodedLen}; use core::{ ops::{Deref, Index, IndexMut}, - slice::SliceIndex, + slice::{IterMut, SliceIndex}, }; -use sp_std::{marker::PhantomData, prelude::*}; +use sp_std::{marker::PhantomData, ops::RangeBounds, prelude::*, vec::Drain}; /// A bounded vector. /// @@ -116,6 +116,29 @@ impl BoundedVec { self.0.remove(index) } + /// Exactly the same semantics as [`Vec::pop`]. + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Exactly the same semantics as [`Vec::drain`]. + pub fn drain(&mut self, range: R) -> Drain<'_, T> + where + R: RangeBounds, + { + self.0.drain(range) + } + + /// Exactly the same semantics as [`Vec::last_mut`]. + pub fn last_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Exactly the same semantics as [`Vec::iter_mut`]. + pub fn iter_mut(&mut self) -> IterMut<'_, T> { + self.0.iter_mut() + } + /// Exactly the same semantics as [`Vec::swap_remove`]. /// /// # Panics diff --git a/frame/support/src/storage/weak_bounded_vec.rs b/frame/support/src/storage/weak_bounded_vec.rs index 823c50c55d0b9..9a4303c531fa4 100644 --- a/frame/support/src/storage/weak_bounded_vec.rs +++ b/frame/support/src/storage/weak_bounded_vec.rs @@ -22,12 +22,12 @@ use crate::{ storage::{StorageDecodeLength, StorageTryAppend}, traits::Get, }; -use codec::{Decode, Encode, MaxEncodedLen}; +use codec::{alloc::vec::Drain, Decode, Encode, MaxEncodedLen}; use core::{ - ops::{Deref, Index, IndexMut}, + ops::{Deref, Index, IndexMut, RangeBounds}, slice::SliceIndex, }; -use sp_std::{convert::TryFrom, marker::PhantomData, prelude::*}; +use sp_std::{cmp::Ordering, convert::TryFrom, marker::PhantomData, prelude::*}; /// A weakly bounded vector. /// @@ -76,6 +76,23 @@ impl WeakBoundedVec { self.0.remove(index) } + /// Exactly the same semantics as [`Vec::drain`]. + pub fn drain(&mut self, range: R) -> Drain<'_, T> + where + R: RangeBounds, + { + self.0.drain(range) + } + + /// Exactly the same semantics as [`Vec::truncate`]. + /// + /// # Panics + /// + /// Panics if `index` is out of bounds. + pub fn truncate(&mut self, index: usize) { + self.0.truncate(index) + } + /// Exactly the same semantics as [`Vec::swap_remove`]. /// /// # Panics @@ -97,6 +114,24 @@ impl WeakBoundedVec { ) -> Option<&mut >::Output> { self.0.get_mut(index) } + + /// Utility to avoid having to transform `WeakBoundedVec` into an + /// `Iterator` then use `map` and then `collect` to then `try_from` + /// back into a `WeakBoundedVec` + pub fn map_collect(self, f: F) -> WeakBoundedVec + where + F: FnMut(T) -> B, + { + WeakBoundedVec::(self.into_iter().map(f).collect::>(), Default::default()) + } + + /// Same as `map_collect` but taking a reference to `self` + pub fn map_collect_ref(&self, f: F) -> WeakBoundedVec + where + F: FnMut(&T) -> B, + { + WeakBoundedVec::(self.0.iter().map(f).collect::>(), Default::default()) + } } impl> WeakBoundedVec { @@ -105,10 +140,7 @@ impl> WeakBoundedVec { S::get() as usize } - /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being - /// respected. The additional scope can be used to indicate where a potential overflow is - /// happening. - pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { + fn check_bound_and_log(t: &Vec, scope: Option<&'static str>) { if t.len() > Self::bound() { log::warn!( target: crate::LOG_TARGET, @@ -116,10 +148,37 @@ impl> WeakBoundedVec { scope.unwrap_or("UNKNOWN"), ); } + } + + /// Create `Self` from `t` without any checks. Logs warnings if the bound is not being + /// respected. The additional scope can be used to indicate where a potential overflow is + /// happening. + pub fn force_from(t: Vec, scope: Option<&'static str>) -> Self { + Self::check_bound_and_log(&t, scope); Self::unchecked_from(t) } + /// Exactly the same semantics as [`Vec::push`], this function unlike `try_push` + /// does no check, but only logs warnings if the bound is not being respected. + /// The additional scope can be used to indicate where a potential overflow is + /// happening. + pub fn force_push(&mut self, element: T, scope: Option<&'static str>) { + Self::check_bound_and_log(self, scope); + + self.0.push(element); + } + + /// Exactly the same semantics as [`Vec::insert`], this function unlike `try_insert` + /// does no check, but only logs warnings if the bound is not being respected. + /// The additional scope can be used to indicate where a potential overflow is + /// happening. + pub fn force_insert(&mut self, index: usize, element: T, scope: Option<&'static str>) { + Self::check_bound_and_log(self, scope); + + self.0.insert(index, element); + } + /// Consumes self and mutates self via the given `mutate` function. /// /// If the outcome of mutation is within bounds, `Some(Self)` is returned. Else, `None` is @@ -270,6 +329,18 @@ impl codec::DecodeLength for WeakBoundedVec { } } +impl PartialOrd for WeakBoundedVec { + fn partial_cmp(&self, other: &Self) -> Option { + PartialOrd::partial_cmp(&self.0, &other.0) + } +} + +impl Ord for WeakBoundedVec { + fn cmp(&self, other: &Self) -> Ordering { + Ord::cmp(&self.0, &other.0) + } +} + // NOTE: we could also implement this as: // impl, S2: Get> PartialEq> for WeakBoundedVec to allow comparison of bounded vectors with different bounds. @@ -374,6 +445,22 @@ pub mod test { assert_eq!(*bounded, vec![1, 0, 2, 3]); } + #[test] + fn map_collect_works() { + let first: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let second = first.map_collect(|x| x * 2); + assert_eq!(*second, vec![2, 4, 6]); + } + + #[test] + fn map_collect_ref_works() { + let first: WeakBoundedVec = vec![1, 2, 3].try_into().unwrap(); + let second = first.map_collect_ref(|x| x * 2); + assert_eq!(*second, vec![2, 4, 6]); + // we borrowed `first` so this compiles and works + assert_eq!(*first, vec![1, 2, 3]); + } + #[test] #[should_panic(expected = "insertion index (is 9) should be <= len (is 3)")] fn try_inert_panics_if_oob() { diff --git a/frame/support/src/traits.rs b/frame/support/src/traits.rs index bb990e25646db..bcc9b9b0f8507 100644 --- a/frame/support/src/traits.rs +++ b/frame/support/src/traits.rs @@ -45,6 +45,9 @@ pub use validation::{ ValidatorSetWithIdentification, VerifySeal, }; +mod staking; +pub use staking::{OffenceDetails, OnOffenceHandler, ReportOffence}; + mod filter; pub use filter::{ClearFilterGuard, FilterStack, FilterStackGuard, InstanceFilter, IntegrityTest}; diff --git a/frame/support/src/traits/staking.rs b/frame/support/src/traits/staking.rs new file mode 100644 index 0000000000000..10b2e1c19c9e7 --- /dev/null +++ b/frame/support/src/traits/staking.rs @@ -0,0 +1,131 @@ +// This file is part of Substrate. + +// Copyright (C) 2019-2021 Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Common traits and types that are useful for describing staking + +use crate::{traits::Get, CloneNoBound, EqNoBound, PartialEqNoBound, WeakBoundedVec}; +use codec::{Decode, Encode, MaxEncodedLen}; +use sp_runtime::Perbill; +use sp_staking::{ + offence::{Offence, OffenceError}, + SessionIndex, +}; + +/// A trait for decoupling offence reporters from the actual handling of offence reports. +pub trait ReportOffence, MaxReportersCount: Get> { + /// Report an `offence` and reward given `reporters`. + fn report_offence( + reporters: WeakBoundedVec, + offence: O, + ) -> Result<(), OffenceError>; + + /// Returns true iff all of the given offenders have been previously reported + /// at the given time slot. This function is useful to prevent the sending of + /// duplicate offence reports. + fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool; +} + +impl, MaxReportersCount: Get> + ReportOffence for () +{ + fn report_offence( + _reporters: WeakBoundedVec, + _offence: O, + ) -> Result<(), OffenceError> { + Ok(()) + } + + fn is_known_offence(_offenders: &[Offender], _time_slot: &O::TimeSlot) -> bool { + true + } +} + +/// A trait to take action on an offence. +/// +/// Used to decouple the module that handles offences and +/// the one that should punish for those offences. +pub trait OnOffenceHandler< + Reporter: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Offender: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Res, + MaxReportersCount: Get, +> +{ + /// A handler for an offence of a particular kind. + /// + /// Note that this contains a list of all previous offenders + /// as well. The implementer should cater for a case, where + /// the same authorities were reported for the same offence + /// in the past (see `OffenceCount`). + /// + /// The vector of `slash_fraction` contains `Perbill`s + /// the authorities should be slashed and is computed + /// according to the `OffenceCount` already. This is of the same length as `offenders.` + /// Zero is a valid value for a fraction. + /// + /// The `session` parameter is the session index of the offence. + /// + /// The receiver might decide to not accept this offence. In this case, the call site is + /// responsible for queuing the report and re-submitting again. + fn on_offence( + offenders: &[OffenceDetails], + slash_fraction: &[Perbill], + session: SessionIndex, + ) -> Res; +} + +impl< + Reporter: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Offender: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Res: Default, + MaxReportersCount: Get, + > OnOffenceHandler for () +{ + fn on_offence( + _offenders: &[OffenceDetails], + _slash_fraction: &[Perbill], + _session: SessionIndex, + ) -> Res { + Default::default() + } +} + +/// A details about an offending authority for a particular kind of offence. +/// `MaxReportersCount` bounds weakly the number of reporters +#[derive( + CloneNoBound, + PartialEqNoBound, + EqNoBound, + Encode, + Decode, + MaxEncodedLen, + frame_support::RuntimeDebugNoBound, + scale_info::TypeInfo, +)] +#[codec(mel_bound(MaxReportersCount: Get))] +#[scale_info(skip_type_params(MaxReportersCount))] +pub struct OffenceDetails< + Reporter: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + Offender: MaxEncodedLen + Clone + Eq + sp_std::fmt::Debug, + MaxReportersCount: Get, +> { + /// The offending authority id + pub offender: Offender, + /// A list of reporters of offences of this authority ID. Possibly empty where there are no + /// particular reporters. + pub reporters: WeakBoundedVec, +} diff --git a/frame/support/test/tests/derive_no_bound.rs b/frame/support/test/tests/derive_no_bound.rs index 1827844664fa7..78f58f3603335 100644 --- a/frame/support/test/tests/derive_no_bound.rs +++ b/frame/support/test/tests/derive_no_bound.rs @@ -15,11 +15,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, and -//! RuntimeDebugNoBound +//! Tests for DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, OrdNoBound +//! and RuntimeDebugNoBound. use frame_support::{ - CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, + CloneNoBound, DebugNoBound, DefaultNoBound, EqNoBound, OrdNoBound, PartialEqNoBound, + RuntimeDebugNoBound, }; #[derive(RuntimeDebugNoBound)] @@ -32,7 +33,7 @@ fn runtime_debug_no_bound_display_correctly() { } trait Config { - type C: std::fmt::Debug + Clone + Eq + PartialEq + Default; + type C: std::fmt::Debug + Clone + Eq + PartialEq + Default + Ord; } struct Runtime; @@ -42,7 +43,7 @@ impl Config for Runtime { type C = u32; } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, OrdNoBound)] struct StructNamed { a: u32, b: u64, @@ -83,9 +84,10 @@ fn test_struct_named() { }; assert!(b != a_1); + assert!(b > a_1); } -#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] +#[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound, OrdNoBound)] struct StructUnnamed(u32, u64, T::C, core::marker::PhantomData<(U, V)>); #[test] @@ -108,6 +110,7 @@ fn test_struct_unnamed() { let b = StructUnnamed::(1, 2, 4, Default::default()); assert!(b != a_1); + assert!(b > a_1); } #[derive(DebugNoBound, CloneNoBound, EqNoBound, PartialEqNoBound, DefaultNoBound)] diff --git a/frame/support/test/tests/derive_no_bound_ui/ord.rs b/frame/support/test/tests/derive_no_bound_ui/ord.rs new file mode 100644 index 0000000000000..7d4e1305f850f --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/ord.rs @@ -0,0 +1,12 @@ +use frame_support::{EqNoBound, OrdNoBound, PartialEqNoBound}; + +trait Config { + type C: Eq; +} + +#[derive(PartialEqNoBound, EqNoBound, OrdNoBound)] +struct Foo { + c: T::C, +} + +fn main() {} diff --git a/frame/support/test/tests/derive_no_bound_ui/ord.stderr b/frame/support/test/tests/derive_no_bound_ui/ord.stderr new file mode 100644 index 0000000000000..e62c5b988d8d6 --- /dev/null +++ b/frame/support/test/tests/derive_no_bound_ui/ord.stderr @@ -0,0 +1,9 @@ +error[E0599]: the method `cmp` exists for associated type `::C`, but its trait bounds were not satisfied + --> $DIR/ord.rs:9:2 + | +9 | c: T::C, + | ^ method cannot be called on `::C` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `::C: Iterator` + which is required by `&mut ::C: Iterator` diff --git a/primitives/arithmetic/Cargo.toml b/primitives/arithmetic/Cargo.toml index 139a04180828c..ce11823eea4af 100644 --- a/primitives/arithmetic/Cargo.toml +++ b/primitives/arithmetic/Cargo.toml @@ -17,8 +17,11 @@ targets = ["x86_64-unknown-linux-gnu"] [dependencies] codec = { package = "parity-scale-codec", version = "2.0.0", default-features = false, features = [ "derive", + "max-encoded-len", +] } +scale-info = { version = "1.0", default-features = false, features = [ + "derive", ] } -scale-info = { version = "1.0", default-features = false, features = ["derive"] } integer-sqrt = "0.1.2" static_assertions = "1.1.0" num-traits = { version = "0.2.8", default-features = false } diff --git a/primitives/arithmetic/src/per_things.rs b/primitives/arithmetic/src/per_things.rs index f388c19de6b43..de56341874b26 100644 --- a/primitives/arithmetic/src/per_things.rs +++ b/primitives/arithmetic/src/per_things.rs @@ -22,7 +22,7 @@ use crate::traits::{ BaseArithmetic, Bounded, CheckedAdd, CheckedMul, CheckedSub, One, SaturatedConversion, Saturating, UniqueSaturatedInto, Unsigned, Zero, }; -use codec::{CompactAs, Encode}; +use codec::{CompactAs, Encode, MaxEncodedLen}; use num_traits::{Pow, SaturatingAdd, SaturatingSub}; use sp_debug_derive::RuntimeDebug; use sp_std::{ @@ -425,7 +425,7 @@ macro_rules! implement_per_thing { /// #[doc = $title] #[cfg_attr(feature = "std", derive(Serialize, Deserialize))] - #[derive(Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, scale_info::TypeInfo)] + #[derive(Encode, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen)] pub struct $name($type); /// Implementation makes any compact encoding of `PerThing::Inner` valid, diff --git a/primitives/core/src/crypto.rs b/primitives/core/src/crypto.rs index 4f21d62f5850d..d0eededf69fa7 100644 --- a/primitives/core/src/crypto.rs +++ b/primitives/core/src/crypto.rs @@ -928,6 +928,7 @@ pub trait CryptoType { PassByInner, crate::RuntimeDebug, TypeInfo, + MaxEncodedLen, )] #[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))] pub struct KeyTypeId(pub [u8; 4]); diff --git a/primitives/runtime/src/traits.rs b/primitives/runtime/src/traits.rs index 6d79d740dc4e1..12241487159ea 100644 --- a/primitives/runtime/src/traits.rs +++ b/primitives/runtime/src/traits.rs @@ -1326,6 +1326,7 @@ macro_rules! impl_opaque_keys_inner { Default, Clone, PartialEq, Eq, $crate::codec::Encode, $crate::codec::Decode, + $crate::codec::MaxEncodedLen, $crate::scale_info::TypeInfo, $crate::RuntimeDebug, )] diff --git a/primitives/staking/src/offence.rs b/primitives/staking/src/offence.rs index a91cb47c117b6..53950e87c5ac3 100644 --- a/primitives/staking/src/offence.rs +++ b/primitives/staking/src/offence.rs @@ -20,7 +20,6 @@ use sp_std::vec::Vec; -use codec::{Decode, Encode}; use sp_runtime::Perbill; use crate::SessionIndex; @@ -109,72 +108,3 @@ impl sp_runtime::traits::Printable for OffenceError { } } } - -/// A trait for decoupling offence reporters from the actual handling of offence reports. -pub trait ReportOffence> { - /// Report an `offence` and reward given `reporters`. - fn report_offence(reporters: Vec, offence: O) -> Result<(), OffenceError>; - - /// Returns true iff all of the given offenders have been previously reported - /// at the given time slot. This function is useful to prevent the sending of - /// duplicate offence reports. - fn is_known_offence(offenders: &[Offender], time_slot: &O::TimeSlot) -> bool; -} - -impl> ReportOffence for () { - fn report_offence(_reporters: Vec, _offence: O) -> Result<(), OffenceError> { - Ok(()) - } - - fn is_known_offence(_offenders: &[Offender], _time_slot: &O::TimeSlot) -> bool { - true - } -} - -/// A trait to take action on an offence. -/// -/// Used to decouple the module that handles offences and -/// the one that should punish for those offences. -pub trait OnOffenceHandler { - /// A handler for an offence of a particular kind. - /// - /// Note that this contains a list of all previous offenders - /// as well. The implementer should cater for a case, where - /// the same authorities were reported for the same offence - /// in the past (see `OffenceCount`). - /// - /// The vector of `slash_fraction` contains `Perbill`s - /// the authorities should be slashed and is computed - /// according to the `OffenceCount` already. This is of the same length as `offenders.` - /// Zero is a valid value for a fraction. - /// - /// The `session` parameter is the session index of the offence. - /// - /// The receiver might decide to not accept this offence. In this case, the call site is - /// responsible for queuing the report and re-submitting again. - fn on_offence( - offenders: &[OffenceDetails], - slash_fraction: &[Perbill], - session: SessionIndex, - ) -> Res; -} - -impl OnOffenceHandler for () { - fn on_offence( - _offenders: &[OffenceDetails], - _slash_fraction: &[Perbill], - _session: SessionIndex, - ) -> Res { - Default::default() - } -} - -/// A details about an offending authority for a particular kind of offence. -#[derive(Clone, PartialEq, Eq, Encode, Decode, sp_runtime::RuntimeDebug, scale_info::TypeInfo)] -pub struct OffenceDetails { - /// The offending authority id - pub offender: Offender, - /// A list of reporters of offences of this authority ID. Possibly empty where there are no - /// particular reporters. - pub reporters: Vec, -} diff --git a/test-utils/runtime/src/lib.rs b/test-utils/runtime/src/lib.rs index 943c41c247f75..ed5e29b09096a 100644 --- a/test-utils/runtime/src/lib.rs +++ b/test-utils/runtime/src/lib.rs @@ -608,6 +608,7 @@ parameter_types! { pub const EpochDuration: u64 = 6; pub const ExpectedBlockTime: u64 = 10_000; pub const MaxAuthorities: u32 = 10; + pub const MaxReportersCount: u32 = 100; } impl pallet_babe::Config for Runtime { @@ -633,6 +634,7 @@ impl pallet_babe::Config for Runtime { type WeightInfo = (); type MaxAuthorities = MaxAuthorities; + type MaxReportersCount = MaxReportersCount; } /// Adds one to the given input and returns the final result.