diff --git a/bin/node/cli/Cargo.toml b/bin/node/cli/Cargo.toml index f955076e122b5..c729c98b77d96 100644 --- a/bin/node/cli/Cargo.toml +++ b/bin/node/cli/Cargo.toml @@ -133,7 +133,7 @@ criterion = { version = "0.3.5", features = [ "async_tokio" ] } tokio = { version = "1.15", features = ["macros", "time"] } jsonrpsee-ws-client = "0.4.1" wait-timeout = "0.2" -remote-externalities = { path = "../../../utils/frame/remote-externalities" } +remote-externalities = { version = "0.10.0-dev", path = "../../../utils/frame/remote-externalities" } pallet-timestamp = { version = "4.0.0-dev", path = "../../../frame/timestamp" } [build-dependencies] diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 1a4dad1071e15..640c8e441e74f 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -26,9 +26,9 @@ use codec::{Decode, Encode, MaxEncodedLen}; use frame_support::{ construct_runtime, parameter_types, traits::{ - ConstU128, ConstU16, ConstU32, Currency, EnsureOneOf, EqualPrivilegeOnly, Everything, - Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, OnUnbalanced, - U128CurrencyToVote, + ConstU128, ConstU16, ConstU32, ConstU64, Currency, EnsureOneOf, EqualPrivilegeOnly, + Everything, Imbalance, InstanceFilter, KeyOwnerProofSystem, LockIdentifier, Nothing, + OnUnbalanced, U128CurrencyToVote, }, weights::{ constants::{BlockExecutionWeight, ExtrinsicBaseWeight, RocksDbWeight, WEIGHT_PER_SECOND}, @@ -503,7 +503,7 @@ impl pallet_session::Config for Runtime { } impl pallet_session::historical::Config for Runtime { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -523,8 +523,8 @@ 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 OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); + pub const MaxIndividualExposures: u32 = 10_000; pub OffchainRepeat: BlockNumber = 5; } @@ -541,7 +541,6 @@ impl pallet_staking::BenchmarkingConfig for StakingBenchmarkingConfig { } impl pallet_staking::Config for Runtime { - const MAX_NOMINATIONS: u32 = MAX_NOMINATIONS; type Currency = Balances; type UnixTime = Timestamp; type CurrencyToVote = U128CurrencyToVote; @@ -560,7 +559,7 @@ impl pallet_staking::Config for Runtime { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = MaxNominatorRewardedPerValidator; + type MaxRewardableIndividualExposures = ConstU32<256>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = ElectionProviderMultiPhase; type GenesisElectionProvider = onchain::OnChainSequentialPhragmen; @@ -568,6 +567,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 = ConstU32; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<10>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<4_000>; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = StakingBenchmarkingConfig; } @@ -584,7 +592,6 @@ parameter_types! { pub SolutionImprovementThreshold: Perbill = Perbill::from_rational(1u32, 10_000); // miner configs - pub const MultiPhaseUnsignedPriority: TransactionPriority = StakingUnsignedPriority::get() - 1u64; pub MinerMaxWeight: Weight = RuntimeBlockWeights::get() .get(DispatchClass::Normal) .max_extrinsic.expect("Normal extrinsics have a weight limit configured; qed") @@ -657,7 +664,7 @@ impl pallet_election_provider_multi_phase::Config for Runtime { type OffchainRepeat = OffchainRepeat; type MinerMaxWeight = MinerMaxWeight; type MinerMaxLength = MinerMaxLength; - type MinerTxPriority = MultiPhaseUnsignedPriority; + type MinerTxPriority = ConstU64<{ TransactionPriority::max_value() / 2 - 1u64 }>; type SignedMaxSubmissions = ConstU32<10>; type SignedRewardBase = SignedRewardBase; type SignedDepositBase = SignedDepositBase; @@ -969,7 +976,6 @@ impl pallet_sudo::Config for Runtime { 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; diff --git a/frame/babe/src/mock.rs b/frame/babe/src/mock.rs index 90051770b80f2..6aa5b4fb8e811 100644 --- a/frame/babe/src/mock.rs +++ b/frame/babe/src/mock.rs @@ -125,7 +125,7 @@ impl pallet_session::Config for Test { } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -172,6 +172,7 @@ parameter_types! { pub const SlashDeferDuration: EraIndex = 0; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(16); + pub const MaxIndividualExposures: u32 = 64; } impl onchain::Config for Test { @@ -180,7 +181,6 @@ impl onchain::Config for Test { } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; @@ -194,7 +194,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = ConstU32<64>; + type MaxRewardableIndividualExposures = ConstU32<64>; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = ConstU32<16>; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<10>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<4_000>; + type MaxUnlockingChunks = ConstU32<32>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; diff --git a/frame/babe/src/tests.rs b/frame/babe/src/tests.rs index 34d861d5d97f7..bb25d1792935c 100644 --- a/frame/babe/src/tests.rs +++ b/frame/babe/src/tests.rs @@ -415,7 +415,7 @@ fn report_equivocation_current_session_works() { 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,7 +456,7 @@ 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. @@ -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/election-provider-multi-phase/src/benchmarking.rs b/frame/election-provider-multi-phase/src/benchmarking.rs index 04101f886cecf..4cb7b1fe2f5b3 100644 --- a/frame/election-provider-multi-phase/src/benchmarking.rs +++ b/frame/election-provider-multi-phase/src/benchmarking.rs @@ -152,7 +152,7 @@ fn set_up_data_provider(v: u32, t: u32) { info, "setting up with voters = {} [degree = {}], targets = {}", v, - T::DataProvider::MAXIMUM_VOTES_PER_VOTER, + ::MaximumVotesPerVoter::get(), t ); @@ -165,8 +165,9 @@ 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); // fill voters. (0..v).for_each(|i| { diff --git a/frame/election-provider-multi-phase/src/lib.rs b/frame/election-provider-multi-phase/src/lib.rs index 175de0eeb0c69..801e7e392d324 100644 --- a/frame/election-provider-multi-phase/src/lib.rs +++ b/frame/election-provider-multi-phase/src/lib.rs @@ -820,7 +820,7 @@ 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, ); } diff --git a/frame/election-provider-multi-phase/src/mock.rs b/frame/election-provider-multi-phase/src/mock.rs index 4e494322b062a..73104dd43f3e2 100644 --- a/frame/election-provider-multi-phase/src/mock.rs +++ b/frame/election-provider-multi-phase/src/mock.rs @@ -440,10 +440,11 @@ pub type Extrinsic = sp_runtime::testing::TestXt; pub struct ExtBuilder {} pub struct StakingMock; + impl ElectionDataProvider for StakingMock { type AccountId = AccountId; type BlockNumber = u64; - const MAXIMUM_VOTES_PER_VOTER: u32 = ::LIMIT as u32; + type MaximumVotesPerVoter = ConstU32<{ ::LIMIT as u32 }>; fn targets(maybe_max_len: Option) -> data_provider::Result> { let targets = Targets::get(); diff --git a/frame/election-provider-support/src/lib.rs b/frame/election-provider-support/src/lib.rs index a4fce64ff1d74..a96b0f6458cbd 100644 --- a/frame/election-provider-support/src/lib.rs +++ b/frame/election-provider-support/src/lib.rs @@ -101,7 +101,7 @@ //! impl ElectionDataProvider for Pallet { //! type AccountId = AccountId; //! type BlockNumber = BlockNumber; -//! const MAXIMUM_VOTES_PER_VOTER: u32 = 1; +//! type MaximumVotesPerVoter = frame_support::pallet_prelude::ConstU32<1>; //! //! fn desired_targets() -> data_provider::Result { //! Ok(1) @@ -191,7 +191,7 @@ pub trait ElectionDataProvider { type BlockNumber; /// 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. /// @@ -266,8 +266,8 @@ pub struct TestDataProvider(sp_std::marker::PhantomData); impl ElectionDataProvider for TestDataProvider<(AccountId, BlockNumber)> { type AccountId = AccountId; type BlockNumber = BlockNumber; + type MaximumVotesPerVoter = frame_support::pallet_prelude::ConstU32<0>; - const MAXIMUM_VOTES_PER_VOTER: u32 = 0; fn targets(_maybe_max_len: Option) -> data_provider::Result> { Ok(Default::default()) } diff --git a/frame/election-provider-support/src/onchain.rs b/frame/election-provider-support/src/onchain.rs index ce15edd592add..236cdbad2ba00 100644 --- a/frame/election-provider-support/src/onchain.rs +++ b/frame/election-provider-support/src/onchain.rs @@ -165,10 +165,12 @@ mod tests { use crate::data_provider; pub struct DataProvider; + impl ElectionDataProvider for DataProvider { type AccountId = AccountId; type BlockNumber = BlockNumber; - const MAXIMUM_VOTES_PER_VOTER: u32 = 2; + type MaximumVotesPerVoter = frame_support::pallet_prelude::ConstU32<2>; + fn voters( _: Option, ) -> data_provider::Result)>> { diff --git a/frame/grandpa/src/mock.rs b/frame/grandpa/src/mock.rs index 6f7c57cad0b57..70bbf37469cfc 100644 --- a/frame/grandpa/src/mock.rs +++ b/frame/grandpa/src/mock.rs @@ -129,7 +129,7 @@ impl pallet_session::Config for Test { } impl pallet_session::historical::Config for Test { - type FullIdentification = pallet_staking::Exposure; + type FullIdentification = pallet_staking::Exposure; type FullIdentificationOf = pallet_staking::ExposureOf; } @@ -176,9 +176,8 @@ parameter_types! { pub const SlashDeferDuration: EraIndex = 0; pub const AttestationPeriod: u64 = 100; pub const RewardCurve: &'static PiecewiseLinear<'static> = &REWARD_CURVE; - pub const ElectionLookahead: u64 = 0; - pub const StakingUnsignedPriority: u64 = u64::MAX / 2; pub const OffendingValidatorsThreshold: Perbill = Perbill::from_percent(17); + pub const MaxIndividualExposures: u32 = 64; } impl onchain::Config for Test { @@ -187,7 +186,6 @@ impl onchain::Config for Test { } impl pallet_staking::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type RewardRemainder = (); type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; type Event = Event; @@ -201,7 +199,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type UnixTime = pallet_timestamp::Pallet; type EraPayout = pallet_staking::ConvertCurve; - type MaxNominatorRewardedPerValidator = ConstU32<64>; + type MaxRewardableIndividualExposures = ConstU32<64>; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = ConstU32<16>; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<10>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<4_000>; + type MaxUnlockingChunks = ConstU32<32>; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type NextNewSession = Session; type ElectionProvider = onchain::OnChainSequentialPhragmen; diff --git a/frame/grandpa/src/tests.rs b/frame/grandpa/src/tests.rs index 6dc0a26da8bd3..4c71e188c3e16 100644 --- a/frame/grandpa/src/tests.rs +++ b/frame/grandpa/src/tests.rs @@ -335,7 +335,7 @@ fn report_equivocation_current_set_works() { 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() }, ); } @@ -373,7 +373,7 @@ 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. @@ -387,7 +387,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() }, ); } }); @@ -419,7 +419,7 @@ fn report_equivocation_old_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() }, ); } @@ -452,7 +452,7 @@ 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. @@ -466,7 +466,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/offences/benchmarking/src/lib.rs b/frame/offences/benchmarking/src/lib.rs index 4d042cfd9997f..d8071ae93b979 100644 --- a/frame/offences/benchmarking/src/lib.rs +++ b/frame/offences/benchmarking/src/lib.rs @@ -21,10 +21,13 @@ 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, ValidatorSet, ValidatorSetWithIdentification}, + WeakBoundedVec, +}; use frame_system::{Config as SystemConfig, Pallet as System, RawOrigin}; use sp_runtime::{ @@ -147,8 +150,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); @@ -275,7 +279,7 @@ benchmarks! { let r in 1 .. MAX_REPORTERS; // 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![]; @@ -381,7 +385,7 @@ 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 @@ -416,7 +420,7 @@ 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 diff --git a/frame/offences/benchmarking/src/mock.rs b/frame/offences/benchmarking/src/mock.rs index 1a5fdcd61bee7..f6906da1d3036 100644 --- a/frame/offences/benchmarking/src/mock.rs +++ b/frame/offences/benchmarking/src/mock.rs @@ -91,7 +91,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; } @@ -146,6 +146,7 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub const MaxIndividualExposures: u32 = 64; } pub type Extrinsic = sp_runtime::testing::TestXt; @@ -156,7 +157,6 @@ impl onchain::Config for Test { } 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; @@ -171,7 +171,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = ConstU32<64>; + type MaxRewardableIndividualExposures = ConstU32<64>; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = ConstU32<16>; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<10>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<4_000>; + type MaxUnlockingChunks = ConstU32<32>; type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; diff --git a/frame/session/benchmarking/src/lib.rs b/frame/session/benchmarking/src/lib.rs index 4e0e51b76fe18..130072fd0bf72 100644 --- a/frame/session/benchmarking/src/lib.rs +++ b/frame/session/benchmarking/src/lib.rs @@ -28,7 +28,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::Pallet 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, )?; @@ -70,10 +70,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 2560563511e6a..ce2747b9b0339 100644 --- a/frame/session/benchmarking/src/mock.rs +++ b/frame/session/benchmarking/src/mock.rs @@ -95,7 +95,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; } @@ -144,6 +144,7 @@ pallet_staking_reward_curve::build! { } parameter_types! { pub const RewardCurve: &'static sp_runtime::curve::PiecewiseLinear<'static> = &I_NPOS; + pub const MaxIndividualExposures: u32 = 64; } pub type Extrinsic = sp_runtime::testing::TestXt; @@ -162,7 +163,6 @@ impl onchain::Config for Test { } 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; @@ -177,7 +177,16 @@ impl pallet_staking::Config for Test { type SessionInterface = Self; type EraPayout = pallet_staking::ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = ConstU32<64>; + type MaxRewardableIndividualExposures = ConstU32<64>; + type MaxIndividualExposures = MaxIndividualExposures; + type MaxNominations = ConstU32<16>; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<10>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<4_000>; + type MaxUnlockingChunks = ConstU32<32>; type OffendingValidatorsThreshold = (); type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; diff --git a/frame/staking/README.md b/frame/staking/README.md index bbd5bd18f6e81..075119954c99b 100644 --- a/frame/staking/README.md +++ b/frame/staking/README.md @@ -15,6 +15,7 @@ at pain of _slash_ (expropriation) should the staked maintainer be found not to its duties properly. ### Terminology + - Staking: The process of locking up funds for some time, placing them at risk of slashing @@ -30,6 +31,7 @@ its duties properly. - Slash: The punishment of a staker by reducing its funds. ### Goals + The staking system in Substrate NPoS is designed to make the following possible: @@ -43,7 +45,7 @@ The staking system in Substrate NPoS is designed to make the following possible: #### Staking Almost any interaction with the Staking module requires a process of _**bonding**_ (also known -as being a _staker_). To become *bonded*, a fund-holding account known as the _stash account_, +as being a _staker_). To become _bonded_, a fund-holding account known as the _stash account_, which holds some or all of the funds that become frozen in place as part of the staking process, is paired with an active **controller** account, which issues instructions on how they shall be used. @@ -74,7 +76,7 @@ An account can become a validator candidate via the #### Nomination A **nominator** does not take any _direct_ role in maintaining the network, instead, it votes on -a set of validators to be elected. Once interest in nomination is stated by an account, it +a set of validators to be elected. Once interest in nomination is stated by an account, it takes effect at the next election round. The funds in the nominator's stash account indicate the _weight_ of its vote. Both the rewards and any punishment that a validator earns are shared between the validator and its nominators. This rule incentivizes the nominators to NOT vote for @@ -90,7 +92,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. @@ -142,6 +144,7 @@ pub mod pallet { use frame_system::pallet_prelude::*; #[pallet::pallet] + #[pallet::generate_store(pub(crate) trait Store)] pub struct Pallet(_); #[pallet::config] @@ -170,11 +173,13 @@ The era payout is computed using yearly inflation curve defined at ```nocompile staker_payout = yearly_inflation(npos_token_staked / total_tokens) * total_tokens / era_per_year ``` + This payout is used to reward stakers as defined in next section ```nocompile remaining_payout = max_yearly_inflation * total_tokens / era_per_year - staker_payout ``` + The remaining reward is send to the configurable end-point [`T::RewardRemainder`](https://docs.rs/pallet-staking/latest/pallet_staking/trait.Config.html#associatedtype.RewardRemainder). @@ -184,7 +189,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 @@ -229,7 +234,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/src/benchmarking.rs b/frame/staking/src/benchmarking.rs index cd9755366b0f6..0e9d8fcc4f0bc 100644 --- a/frame/staking/src/benchmarking.rs +++ b/frame/staking/src/benchmarking.rs @@ -33,7 +33,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, @@ -117,10 +117,12 @@ pub fn create_validator_with_nominators( assert_ne!(Nominators::::count(), 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); @@ -355,17 +357,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(), )?; @@ -380,7 +382,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(), )?; @@ -415,9 +417,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::(); @@ -428,7 +430,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(); @@ -538,8 +540,11 @@ benchmarks! { let era = EraIndex::one(); let dummy = || T::AccountId::decode(&mut TrailingZeroInput::zeroes()).unwrap(); for _ in 0 .. MAX_SLASHES { - unapplied_slashes.push(UnappliedSlash::>::default_from(dummy())); + unapplied_slashes.push(UnappliedSlash::, T::MaxIndividualExposures, + T::MaxReportersCount>::default_from(dummy())); } + 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(); @@ -549,10 +554,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, )?; @@ -582,10 +587,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, )?; @@ -618,7 +623,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::(); @@ -652,7 +657,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; @@ -671,11 +677,11 @@ benchmarks! { CurrentEra::::put(e); let dummy = || -> T::AccountId { codec::Decode::decode(&mut TrailingZeroInput::zeroes()).unwrap() }; for i in 0 .. e { - >::insert(i, dummy(), Exposure::>::default()); - >::insert(i, dummy(), Exposure::>::default()); + >::insert(i, dummy(), Exposure::default()); + >::insert(i, dummy(), Exposure::default()); >::insert(i, dummy(), ValidatorPrefs::default()); >::insert(i, BalanceOf::::one()); - >::insert(i, EraRewardPoints::::default()); + >::insert(i, EraRewardPoints::default()); >::insert(i, BalanceOf::::one()); ErasStartSessionIndex::::insert(i, i); } @@ -702,8 +708,8 @@ benchmarks! { stash: stash.clone(), active: T::Currency::minimum_balance() - One::one(), total: T::Currency::minimum_balance() - One::one(), - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }; Ledger::::insert(&controller, l); @@ -724,7 +730,7 @@ benchmarks! { create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -742,7 +748,7 @@ benchmarks! { create_validators_with_nominators_for_era::( v, n, - ::MAX_NOMINATIONS as usize, + ::MaxNominations::get() as usize, false, None, )?; @@ -762,9 +768,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); @@ -788,7 +798,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::> { @@ -796,7 +806,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(); @@ -822,7 +833,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()) @@ -845,7 +856,7 @@ benchmarks! { let n = MaxNominators::::get(); 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(); @@ -859,14 +870,12 @@ benchmarks! { BalanceOf::::max_value(), BalanceOf::::max_value(), Some(u32::MAX), - Some(u32::MAX), Some(Percent::max_value()), Perbill::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))); assert_eq!(MinCommission::::get(), Perbill::from_percent(100)); } @@ -889,7 +898,6 @@ benchmarks! { BalanceOf::::max_value(), BalanceOf::::max_value(), Some(0), - Some(0), Some(Percent::from_percent(0)), Zero::zero(), )?; @@ -919,15 +927,10 @@ mod tests { ExtBuilder::default().build_and_execute(|| { let v = 10; let n = 100; + let max: u32 = ::MaxNominations::get(); - create_validators_with_nominators_for_era::( - v, - n, - ::MAX_NOMINATIONS as usize, - false, - None, - ) - .unwrap(); + create_validators_with_nominators_for_era::(v, n, max as usize, false, None) + .unwrap(); let count_validators = Validators::::iter().count(); let count_nominators = Nominators::::iter().count(); @@ -944,14 +947,11 @@ mod tests { fn create_validator_with_nominators_works() { ExtBuilder::default().build_and_execute(|| { let n = 10; + let max: u32 = ::MaxRewardableIndividualExposures::get(); - let (validator_stash, nominators) = create_validator_with_nominators::( - n, - ::MaxNominatorRewardedPerValidator::get(), - false, - RewardDestination::Staked, - ) - .unwrap(); + let (validator_stash, nominators) = + create_validator_with_nominators::(n, max, false, RewardDestination::Staked) + .unwrap(); assert_eq!(nominators.len() as u32, n); @@ -969,14 +969,11 @@ mod tests { fn add_slashing_spans_works() { ExtBuilder::default().build_and_execute(|| { let n = 10; + let max: u32 = ::MaxRewardableIndividualExposures::get(); - let (validator_stash, _nominators) = create_validator_with_nominators::( - n, - ::MaxNominatorRewardedPerValidator::get(), - false, - RewardDestination::Staked, - ) - .unwrap(); + let (validator_stash, _nominators) = + create_validator_with_nominators::(n, max, false, RewardDestination::Staked) + .unwrap(); // Add 20 slashing spans let num_of_slashing_spans = 20; diff --git a/frame/staking/src/lib.rs b/frame/staking/src/lib.rs index 7bc3ac8aa7075..b58370095c6bf 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. //! @@ -161,26 +161,27 @@ //! //! #[frame_support::pallet] //! pub mod pallet { -//! use super::*; -//! use frame_support::pallet_prelude::*; -//! use frame_system::pallet_prelude::*; -//! -//! #[pallet::pallet] -//! 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(()) -//! } +//! use super::*; +//! use frame_support::pallet_prelude::*; +//! use frame_system::pallet_prelude::*; +//! +//! #[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() { } //! ``` @@ -209,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 @@ -254,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 @@ -299,10 +300,13 @@ pub mod weights; mod pallet; -use codec::{Decode, Encode, HasCompact}; +use codec::{Decode, Encode, HasCompact, MaxEncodedLen}; use frame_support::{ - traits::{ConstU32, Currency, Get}, + storage::bounded_btree_map::BoundedBTreeMap, + traits::{Currency, Get}, weights::Weight, + BoundedVec, CloneNoBound, EqNoBound, OrdNoBound, PartialEqNoBound, RuntimeDebugNoBound, + WeakBoundedVec, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -314,7 +318,11 @@ use sp_staking::{ offence::{Offence, OffenceError, ReportOffence}, 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::*, *}; @@ -350,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, @@ -364,17 +372,28 @@ 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, RuntimeDebug, TypeInfo)] -pub struct EraRewardPoints { +/// `Limit` bounds the number of points earned by a given validator. +#[derive(PartialEqNoBound, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen)] +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get))] +pub struct EraRewardPoints +where + AccountId: Ord + MaxEncodedLen + 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, } -impl Default for EraRewardPoints { +impl Default for EraRewardPoints +where + AccountId: Ord + MaxEncodedLen + PartialEq + fmt::Debug, + Limit: Get, +{ fn default() -> Self { - EraRewardPoints { total: Default::default(), individual: BTreeMap::new() } + EraRewardPoints { total: Default::default(), individual: Default::default() } } } @@ -391,7 +410,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, @@ -412,7 +431,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. @@ -431,7 +450,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)] @@ -442,8 +461,27 @@ pub struct UnlockChunk { } /// The ledger of a (bonded) stash. -#[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, +)] +#[codec(mel_bound(UnlockingLimit: Get, RewardsLimit: Get))] +#[scale_info(skip_type_params(UnlockingLimit, RewardsLimit))] +pub struct StakingLedger +where + Balance: HasCompact + MaxEncodedLen + Clone + Eq + fmt::Debug, + AccountId: MaxEncodedLen + Clone + 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. @@ -456,14 +494,20 @@ 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 + Eq + fmt::Debug + Zero, + AccountId: MaxEncodedLen + Clone + Eq + fmt::Debug, + Balance: MaxEncodedLen + Clone, + UnlockingLimit: Get, + RewardsLimit: Get, { /// Initializes the default object using the given `validator`. pub fn default_from(stash: AccountId) -> Self { @@ -471,8 +515,8 @@ impl 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, @@ -528,12 +574,7 @@ 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. @@ -583,10 +624,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. @@ -599,7 +647,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, @@ -609,8 +659,26 @@ pub struct IndividualExposure { } /// A snapshot of the stake backing a single validator in the system. -#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Encode, Decode, RuntimeDebug, TypeInfo)] -pub struct Exposure { +/// `Limit` is the size limit of `others`. +#[derive( + PartialEqNoBound, + EqNoBound, + OrdNoBound, + Encode, + Decode, + CloneNoBound, + RuntimeDebugNoBound, + TypeInfo, + MaxEncodedLen, +)] +#[scale_info(skip_type_params(Limit))] +#[codec(mel_bound(Limit: Get, Balance: HasCompact))] +pub struct Exposure +where + AccountId: MaxEncodedLen + Eq + Clone + Ord + fmt::Debug, + Balance: HasCompact + MaxEncodedLen + Eq + Clone + Ord + fmt::Debug, + Limit: Get, +{ /// The total balance backing this validator. #[codec(compact)] pub total: Balance, @@ -618,39 +686,60 @@ pub struct Exposure { #[codec(compact)] pub own: Balance, /// The portions of nominators stashes that are exposed. - pub others: Vec>, + pub others: WeakBoundedVec, Limit>, } -impl Default for Exposure { +impl Default for Exposure +where + AccountId: MaxEncodedLen + Eq + Clone + Ord + fmt::Debug, + Balance: HasCompact + MaxEncodedLen + Eq + Clone + Ord + fmt::Debug + Default, + Limit: Get, +{ fn default() -> Self { - Self { total: Default::default(), own: Default::default(), others: vec![] } + Self { total: Default::default(), own: Default::default(), others: Default::default() } } } /// A pending slash record. The value of the slash has been computed but not applied yet, /// rather deferred for several eras. -#[derive(Encode, Decode, 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 UnappliedSlash { - /// Initializes the default object using the given `validator`. +impl + UnappliedSlash +where + Balance: HasCompact + MaxEncodedLen + Zero, + AccountId: MaxEncodedLen, + SlashedLimit: Get, + ReportersLimit: Get, +{ pub fn default_from(validator: AccountId) -> Self { Self { validator, own: Zero::zero(), - others: vec![], - reporters: vec![], + others: Default::default(), + reporters: Default::default(), payout: Zero::zero(), } } @@ -673,7 +762,11 @@ 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>, @@ -743,7 +836,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)) } @@ -857,6 +953,6 @@ pub struct TestBenchmarkingConfig; #[cfg(feature = "std")] impl BenchmarkingConfig for TestBenchmarkingConfig { - type MaxValidators = ConstU32<100>; - type MaxNominators = ConstU32<100>; + type MaxValidators = frame_support::traits::ConstU32<100>; + type MaxNominators = frame_support::traits::ConstU32<100>; } diff --git a/frame/staking/src/mock.rs b/frame/staking/src/mock.rs index ea9b9f05f1059..af9a91e4d4539 100644 --- a/frame/staking/src/mock.rs +++ b/frame/staking/src/mock.rs @@ -35,7 +35,7 @@ use sp_runtime::{ traits::{IdentityLookup, Zero}, }; use sp_staking::offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}; -use std::cell::RefCell; +use std::{cell::RefCell, collections::BTreeMap}; pub const INIT_TIMESTAMP: u64 = 30_000; pub const BLOCK_TIME: u64 = 1000; @@ -181,7 +181,7 @@ impl pallet_session::Config for Test { } 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 { @@ -234,6 +234,7 @@ const THRESHOLDS: [sp_npos_elections::VoteWeight; 9] = parameter_types! { pub static BagThresholds: &'static [sp_npos_elections::VoteWeight] = &THRESHOLDS; + pub const MaxIndividualExposures: u32 = 64; } impl pallet_bags_list::Config for Test { @@ -249,7 +250,6 @@ impl onchain::Config for Test { } impl crate::pallet::pallet::Config for Test { - const MAX_NOMINATIONS: u32 = 16; type Currency = Balances; type UnixTime = Timestamp; type CurrencyToVote = frame_support::traits::SaturatingCurrencyToVote; @@ -264,12 +264,21 @@ impl crate::pallet::pallet::Config for Test { type SessionInterface = Self; type EraPayout = ConvertCurve; type NextNewSession = Session; - type MaxNominatorRewardedPerValidator = ConstU32<64>; + type MaxRewardableIndividualExposures = ConstU32<64>; + type MaxIndividualExposures = MaxIndividualExposures; type OffendingValidatorsThreshold = OffendingValidatorsThreshold; type ElectionProvider = onchain::OnChainSequentialPhragmen; type GenesisElectionProvider = Self::ElectionProvider; // NOTE: consider a macro and use `UseNominatorsMap` as well. type SortedListProvider = BagsList; + type MaxNominations = ConstU32<16>; + type MaxUnappliedSlashes = ConstU32<1_000>; + type MaxInvulnerablesCount = ConstU32<100>; + type MaxHistoryDepth = ConstU32<10_000>; + type MaxReportersCount = ConstU32<1_000>; + type MaxPriorSlashingSpans = ConstU32<1_000>; + type MaxValidatorsCount = ConstU32<100>; + type MaxUnlockingChunks = ConstU32<32>; type BenchmarkingConfig = TestBenchmarkingConfig; type WeightInfo = (); } diff --git a/frame/staking/src/pallet/impls.rs b/frame/staking/src/pallet/impls.rs index 95af3d223e009..f0f8c31b0d2a1 100644 --- a/frame/staking/src/pallet/impls.rs +++ b/frame/staking/src/pallet/impls.rs @@ -28,6 +28,7 @@ use frame_support::{ OnUnbalanced, UnixTime, WithdrawReasons, }, weights::{Weight, WithPostDispatchInfo}, + WeakBoundedVec, }; use frame_system::pallet_prelude::BlockNumberFor; use pallet_session::historical; @@ -39,7 +40,7 @@ use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, SessionIndex, }; -use sp_std::{collections::btree_map::BTreeMap, prelude::*}; +use sp_std::{collections::btree_map::BTreeMap, convert::TryFrom, prelude::*}; use crate::{ log, slashing, weights::WeightInfo, ActiveEraInfo, BalanceOf, EraIndex, EraPayout, Exposure, @@ -117,7 +118,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 +198,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 +207,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); @@ -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,7 +406,10 @@ impl Pallet { /// Returns the new validator set. pub fn trigger_new_era( start_session_index: SessionIndex, - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: Vec<( + T::AccountId, + Exposure, T::MaxIndividualExposures>, + )>, ) -> Vec { // Increment or set current era. let new_planned_era = CurrentEra::::mutate(|s| { @@ -473,7 +485,10 @@ impl Pallet { /// /// Store staking information for the new planned era pub fn store_stakers_info( - exposures: Vec<(T::AccountId, Exposure>)>, + exposures: Vec<( + T::AccountId, + Exposure, T::MaxIndividualExposures>, + )>, new_planned_era: EraIndex, ) -> Vec { let elected_stashes = exposures.iter().cloned().map(|(x, _)| x).collect::>(); @@ -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); }); @@ -518,7 +542,7 @@ impl Pallet { /// [`Exposure`]. fn collect_exposures( supports: Supports, - ) -> Vec<(T::AccountId, Exposure>)> { + ) -> Vec<(T::AccountId, Exposure, T::MaxIndividualExposures>)> { let total_issuance = T::Currency::total_issuance(); let to_currency = |e: frame_election_provider_support::ExtendedBalance| { T::CurrencyToVote::to_currency(e, total_issuance) @@ -544,10 +568,15 @@ impl Pallet { 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::)>>() + .collect::)>>() } /// Remove all associated data of a stash account from the staking system. @@ -637,7 +666,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); } @@ -720,7 +749,7 @@ impl Pallet { .map_or(true, |spans| submitted_in >= spans.last_nonzero_slash()) }); if !targets.len().is_zero() { - all_voters.push((nominator.clone(), weight_of(&nominator), targets)); + all_voters.push((nominator.clone(), weight_of(&nominator), targets.to_vec())); nominators_taken.saturating_inc(); } } else { @@ -772,7 +801,10 @@ impl Pallet { /// NOTE: you must ALWAYS use this function to add nominator or update their targets. Any access /// to `Nominators` 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 sorted list. Error checking is defensive-only - this should never fail. if T::SortedListProvider::on_insert(who.clone(), Self::weight_of(who)).is_err() { @@ -847,7 +879,7 @@ impl Pallet { impl ElectionDataProvider for Pallet { type AccountId = T::AccountId; type BlockNumber = BlockNumberFor; - const MAXIMUM_VOTES_PER_VOTER: u32 = T::MAX_NOMINATIONS; + type MaximumVotesPerVoter = T::MaxNominations; fn desired_targets() -> data_provider::Result { Self::register_weight(T::DbWeight::get().reads(1)); @@ -908,6 +940,10 @@ 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) { let stake = >::try_from(weight).unwrap_or_else(|_| { @@ -920,10 +956,13 @@ impl ElectionDataProvider for Pallet { stash: voter.clone(), active: stake, total: stake, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); + + let targets = BoundedVec::<_, T::MaxNominations>::try_from(targets) + .expect("targets Vec length too large, benchmark needs reconfiguring"); Self::do_add_nominator(&voter, Nominations { targets, submitted_in: 0, suppressed: false }); } @@ -937,8 +976,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( @@ -957,6 +996,10 @@ impl ElectionDataProvider for Pallet { T::SortedListProvider::unsafe_clear(); } + /// # Panic + /// + /// Panics if `voters` 3rd element is larger than T::MaxNominations, or if the 2nd element + /// cannot be converted into a `BalanceOf`. #[cfg(feature = "runtime-benchmarks")] fn put_snapshot( voters: Vec<(T::AccountId, VoteWeight, Vec)>, @@ -974,8 +1017,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( @@ -995,14 +1038,13 @@ 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 }, - ); + let targets = BoundedVec::<_, T::MaxNominations>::try_from(t) + .expect("targets vec too large, benchmark needs reconfiguring."); + Self::do_add_nominator(&v, Nominations { targets, submitted_in: 0, suppressed: false }); }); } } @@ -1033,12 +1075,16 @@ impl pallet_session::SessionManager for Pallet { } } -impl historical::SessionManager>> - for Pallet +impl + historical::SessionManager< + T::AccountId, + Exposure, T::MaxIndividualExposures>, + > for Pallet { fn new_session( new_index: SessionIndex, - ) -> Option>)>> { + ) -> Option, T::MaxIndividualExposures>)>> + { >::new_session(new_index).map(|validators| { let current_era = Self::current_era() // Must be some as a new era has been created. @@ -1055,7 +1101,8 @@ impl historical::SessionManager Option>)>> { + ) -> Option, T::MaxIndividualExposures>)>> + { >::new_session_genesis(new_index).map( |validators| { let current_era = Self::current_era() @@ -1108,7 +1155,11 @@ impl 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>, @@ -1207,7 +1258,10 @@ where let rw = upper_bound + nominators_len * upper_bound; add_db_reads_writes(rw, rw); } - unapplied.reporters = details.reporters.clone(); + unapplied.reporters = WeakBoundedVec::<_, T::MaxReportersCount>::force_from( + details.reporters.clone(), + Some("unapplied.reporters"), + ); if slash_defer_duration == 0 { // Apply right away. slashing::apply_slash::(unapplied); @@ -1222,7 +1276,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 d03a6198c7cc4..a1ef8b9170a3c 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,7 +33,11 @@ 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; @@ -45,7 +50,6 @@ use crate::{ ValidatorPrefs, }; -pub const MAX_UNLOCKING_CHUNKS: usize = 32; const STAKING_ID: LockIdentifier = *b"staking "; #[frame_support::pallet] @@ -56,6 +60,7 @@ pub mod pallet { #[pallet::pallet] #[pallet::generate_store(pub(crate) trait Store)] + #[pallet::generate_storage_info] pub struct Pallet(_); #[pallet::config] @@ -93,7 +98,7 @@ pub mod 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). @@ -139,10 +144,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. @@ -165,7 +199,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() } } @@ -195,12 +229,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] @@ -224,8 +259,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] @@ -239,19 +278,17 @@ pub mod pallet { pub type Validators = CountedStorageMap<_, Twox64Concat, T::AccountId, ValidatorPrefs, 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. #[pallet::storage] #[pallet::getter(fn nominators)] - pub type Nominators = - CountedStorageMap<_, Twox64Concat, T::AccountId, Nominations>; + pub type Nominators = CountedStorageMap< + _, + Twox64Concat, + T::AccountId, + Nominations, + >; - /// 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 +332,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 +355,7 @@ pub mod pallet { EraIndex, Twox64Concat, T::AccountId, - Exposure>, + Exposure, T::MaxRewardableIndividualExposures>, ValueQuery, >; @@ -351,8 +388,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 +427,15 @@ pub mod pallet { _, Twox64Concat, EraIndex, - Vec>>, + WeakBoundedVec< + UnappliedSlash< + T::AccountId, + BalanceOf, + T::MaxIndividualExposures, + T::MaxReportersCount, + >, + T::MaxUnappliedSlashes, + >, ValueQuery, >; @@ -395,7 +445,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 +466,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 +506,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. @@ -480,8 +535,6 @@ pub mod pallet { Vec<(T::AccountId, T::AccountId, BalanceOf, crate::StakerStatus)>, pub min_nominator_bond: BalanceOf, pub min_validator_bond: BalanceOf, - pub max_validator_count: Option, - pub max_nominator_count: Option, } #[cfg(feature = "std")] @@ -498,8 +551,6 @@ pub mod pallet { stakers: Default::default(), min_nominator_bond: Default::default(), min_validator_bond: Default::default(), - max_validator_count: None, - max_nominator_count: None, } } } @@ -507,22 +558,27 @@ 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); StorageVersion::::put(Releases::V7_0_0); MinNominatorBond::::put(self.min_nominator_bond); MinValidatorBond::::put(self.min_validator_bond); - if let Some(x) = self.max_validator_count { - MaxValidatorsCount::::put(x); - } - if let Some(x) = self.max_nominator_count { - MaxNominatorsCount::::put(x); - } + + 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!( @@ -654,6 +710,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, /// Commission is too low. Must be at least `MinCommission`. CommissionTooLow, } @@ -767,12 +827,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(()) @@ -836,7 +901,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). /// @@ -853,7 +918,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); @@ -880,7 +944,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); @@ -973,12 +1040,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!( - Validators::::count() < max_validators, - Error::::TooManyValidators - ); - } + ensure!( + Validators::::count() < T::MaxValidatorsCount::get(), + Error::::TooManyValidators + ); } Self::do_remove_nominator(stash); @@ -994,7 +1059,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))] @@ -1022,9 +1087,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() @@ -1040,6 +1104,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 @@ -1243,6 +1310,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(()) } @@ -1334,14 +1403,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) @@ -1353,7 +1422,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, @@ -1370,10 +1439,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, @@ -1430,6 +1499,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); @@ -1541,7 +1614,6 @@ pub mod pallet { min_nominator_bond: BalanceOf, min_validator_bond: BalanceOf, max_nominator_count: Option, - max_validator_count: Option, chill_threshold: Option, min_commission: Perbill, ) -> DispatchResult { @@ -1549,7 +1621,6 @@ pub mod pallet { MinNominatorBond::::set(min_nominator_bond); MinValidatorBond::::set(min_validator_bond); MaxNominatorsCount::::set(max_nominator_count); - MaxValidatorsCount::::set(max_validator_count); ChillThreshold::::set(chill_threshold); MinCommission::::set(min_commission); Ok(()) @@ -1568,8 +1639,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. @@ -1586,8 +1657,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. @@ -1605,11 +1675,9 @@ pub mod pallet { ); MinNominatorBond::::get() } else if Validators::::contains_key(&stash) { - let max_validator_count = - MaxValidatorsCount::::get().ok_or(Error::::CannotChillOther)?; let current_validator_count = Validators::::count(); ensure!( - threshold * max_validator_count < current_validator_count, + threshold * T::MaxValidatorsCount::get() < current_validator_count, Error::::CannotChillOther ); MinValidatorBond::::get() diff --git a/frame/staking/src/slashing.rs b/frame/staking/src/slashing.rs index acfb30fb81482..6fa366a8b3293 100644 --- a/frame/staking/src/slashing.rs +++ b/frame/staking/src/slashing.rs @@ -50,13 +50,14 @@ //! 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::{ @@ -64,7 +65,10 @@ use sp_runtime::{ DispatchResult, RuntimeDebug, }; use sp_staking::offence::DisableStrategy; -use sp_std::vec::Vec; +use sp_std::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. @@ -89,8 +93,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, @@ -100,10 +107,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 { @@ -114,7 +121,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(), } } @@ -128,7 +135,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 @@ -182,7 +189,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, @@ -197,14 +204,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. @@ -226,7 +233,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 mut reward_payout = Zero::zero(); let mut val_slashed = Zero::zero(); @@ -288,14 +297,14 @@ pub(crate) fn compute_slash( let disable_when_slashed = params.disable_strategy != DisableStrategy::Never; add_offending_validator::(params.stash, disable_when_slashed); - let mut nominators_slashed = Vec::new(); + let mut nominators_slashed = Default::default(); reward_payout += slash_nominators::(params.clone(), prior_slash_p, &mut nominators_slashed); Some(UnappliedSlash { validator: params.stash.clone(), own: val_slashed, others: nominators_slashed, - reporters: Vec::new(), + reporters: Default::default(), payout: reward_payout, }) } @@ -339,7 +348,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; @@ -371,12 +382,15 @@ 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 mut reward_payout = Zero::zero(); + let mut slashed_nominators = Vec::with_capacity(params.exposure.others.len()); - nominators_slashed.reserve(params.exposure.others.len()); - for nominator in ¶ms.exposure.others { + for nominator in params.exposure.others.to_vec().into_iter() { let stash = &nominator.who; let mut nom_slashed = Zero::zero(); @@ -417,9 +431,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 +453,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 +647,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 +665,7 @@ pub(crate) fn apply_slash(unapplied_slash: UnappliedSlash(&nominator, nominator_slash, &mut reward_payout, &mut slashed_imbalance); } @@ -707,11 +733,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 +752,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 +773,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 +823,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 +834,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 74d5de7bf449e..29b4c2e07b2d5 100644 --- a/frame/staking/src/tests.rs +++ b/frame/staking/src/tests.rs @@ -37,7 +37,7 @@ use sp_staking::{ offence::{DisableStrategy, OffenceDetails, OnOffenceHandler}, SessionIndex, }; -use sp_std::prelude::*; +use sp_std::{collections::btree_map::BTreeMap, prelude::*}; use substrate_test_utils::assert_eq_uvec; #[test] @@ -104,8 +104,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 +115,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 +138,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 +149,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 +160,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 +241,14 @@ fn rewards_should_work() { assert_eq!(Balances::total_balance(&21), init_balance_21); 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); @@ -382,8 +390,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 @@ -536,28 +547,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 @@ -936,8 +945,8 @@ fn reward_destination_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -959,8 +968,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"), }) ); @@ -987,8 +999,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"), }) ); @@ -1016,8 +1031,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. @@ -1081,8 +1099,8 @@ fn bond_extra_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1098,8 +1116,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(), }) ); @@ -1112,8 +1130,8 @@ fn bond_extra_works() { stash: 11, total: 1000000, active: 1000000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); }); @@ -1150,13 +1168,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 @@ -1168,14 +1186,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. @@ -1189,14 +1207,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. @@ -1207,8 +1225,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(), }), ); @@ -1220,8 +1241,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(), }), ); @@ -1236,8 +1260,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(), }), ); @@ -1252,8 +1279,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(), }), ); }) @@ -1263,7 +1290,8 @@ 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 { + let max: u32 = ::MaxUnlockingChunks::get(); + for _ in 0..max - 1 { assert_ok!(Staking::unbond(Origin::signed(10), 1)); } @@ -1310,8 +1338,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1329,8 +1357,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(), }) ); @@ -1342,8 +1373,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1355,8 +1386,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(), }) ); @@ -1368,8 +1402,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(), }) ); @@ -1381,8 +1418,8 @@ fn rebond_works() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1396,12 +1433,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(), }) ); @@ -1413,11 +1451,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(), }) ); }) @@ -1443,8 +1482,8 @@ fn rebond_is_fifo() { stash: 11, total: 1000, active: 1000, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: BoundedVec::default(), + claimed_rewards: Default::default(), }) ); @@ -1458,8 +1497,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(), }) ); @@ -1473,11 +1515,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(), }) ); @@ -1491,12 +1534,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(), }) ); @@ -1508,11 +1552,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(), }) ); }) @@ -1540,8 +1585,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(), }) ); @@ -1553,8 +1601,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 @@ -1568,8 +1619,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 @@ -1597,15 +1648,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(), }, ); @@ -1665,8 +1720,8 @@ fn reap_stash_works() { stash: 11, total: 5, active: 5, - unlocking: vec![], - claimed_rewards: vec![], + unlocking: Default::default(), + claimed_rewards: Default::default(), }, ); @@ -1784,8 +1839,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(), }) ); @@ -2008,16 +2066,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); @@ -2036,7 +2108,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"), }, ); @@ -2074,7 +2149,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, }, ); @@ -2093,7 +2172,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 + }, ); }) } @@ -2209,7 +2295,7 @@ 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![] }), + offender: (11, Exposure { total: 500, own: 500, others: Default::default() }), reporters: vec![], }], &[Perbill::from_percent(50)], @@ -3259,7 +3345,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; @@ -3282,7 +3368,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 { @@ -3354,8 +3440,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"), }) ); @@ -3376,8 +3465,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") }) ); @@ -3397,8 +3489,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"), }) ); @@ -3412,8 +3507,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") }) ); }); @@ -3490,7 +3588,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; @@ -3607,8 +3705,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); @@ -3619,8 +3717,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); @@ -3631,8 +3732,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"), }) ); }); @@ -3850,8 +3954,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(), } ); @@ -3864,8 +3968,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(), } ); @@ -3887,8 +3994,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(), } ); @@ -3901,8 +4008,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(), } ); @@ -3928,8 +4038,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(), } ); @@ -3945,8 +4055,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(), } ); }) @@ -4249,7 +4359,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, + }, ); }) } @@ -4364,7 +4478,6 @@ fn chill_other_works() { 2_000, None, None, - None, Zero::zero() )); @@ -4384,7 +4497,6 @@ fn chill_other_works() { 1_500, 2_000, Some(10), - Some(10), None, Zero::zero() )); @@ -4405,7 +4517,6 @@ fn chill_other_works() { 1_500, 2_000, None, - None, Some(Percent::from_percent(0)), Zero::zero() )); @@ -4415,10 +4526,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_configs( @@ -4426,22 +4535,19 @@ fn chill_other_works() { 1_500, 2_000, Some(10), - Some(10), Some(Percent::from_percent(75)), Zero::zero() )); - // 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!(Nominators::::count(), 15 + initial_nominators); - assert_eq!(Validators::::count(), 15 + initial_validators); + assert_eq!(Validators::::count(), 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 @@ -4450,9 +4556,28 @@ fn chill_other_works() { Staking::chill_other(Origin::signed(1337), 1), Error::::CannotChillOther ); - // chill a validator. Limit is reached, chill-able. - assert_eq!(Validators::::count(), 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_configs( + Origin::root(), + 1_500, + 2_000, + Some(10), + Some(Percent::from_percent(7)), + Zero::zero() + )); + + 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!(Validators::::count(), 8); + assert_noop!( + Staking::chill_other(Origin::signed(1337), 3), + Error::::CannotChillOther + ); }) } @@ -4465,19 +4590,18 @@ fn capped_stakers_works() { assert_eq!(nominator_count, 1); // Change the maximums - let max = 10; assert_ok!(Staking::set_staking_configs( Origin::root(), 10, 10, - Some(max), - Some(max), + Some(10), Some(Percent::from_percent(0)), Zero::zero(), )); // can create `max - validator_count` validators let mut some_existing_validator = AccountId::default(); + let max: u32 = ::MaxValidatorsCount::get(); for i in 0..max - validator_count { let (_, controller) = testing_utils::create_stash_controller::( i + 10_000_000, @@ -4504,7 +4628,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, @@ -4536,17 +4660,13 @@ fn capped_stakers_works() { )); // No problem when we set to `None` again - assert_ok!(Staking::set_staking_configs( - Origin::root(), - 10, - 10, - None, - None, - None, - Zero::zero(), - )); + assert_ok!(Staking::set_staking_configs(Origin::root(), 10, 10, None, None, Zero::zero(),)); 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 + ); }) } @@ -4564,7 +4684,6 @@ fn min_commission_works() { 0, None, None, - None, Perbill::from_percent(10), )); diff --git a/frame/staking/src/weights.rs b/frame/staking/src/weights.rs index 0ca819898fbb0..0cb8a2d37472c 100644 --- a/frame/staking/src/weights.rs +++ b/frame/staking/src/weights.rs @@ -145,7 +145,6 @@ impl WeightInfo for SubstrateWeight { // Storage: Staking MinValidatorBond (r:1 w:0) // Storage: Staking MinCommission (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 { (66_587_000 as Weight) - .saturating_add(T::DbWeight::get().reads(12 as Weight)) + .saturating_add(T::DbWeight::get().reads(11 as Weight)) .saturating_add(T::DbWeight::get().writes(8 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -418,13 +417,12 @@ impl WeightInfo for SubstrateWeight { } // Storage: Staking MinCommission (r:0 w:1) // 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_configs() -> Weight { (6_187_000 as Weight) - .saturating_add(T::DbWeight::get().writes(6 as Weight)) + .saturating_add(T::DbWeight::get().writes(5 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 MinValidatorBond (r:1 w:0) // Storage: Staking MinCommission (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 { (66_587_000 as Weight) - .saturating_add(RocksDbWeight::get().reads(12 as Weight)) + .saturating_add(RocksDbWeight::get().reads(11 as Weight)) .saturating_add(RocksDbWeight::get().writes(8 as Weight)) } // Storage: Staking Ledger (r:1 w:0) @@ -786,13 +783,12 @@ impl WeightInfo for () { } // Storage: Staking MinCommission (r:0 w:1) // 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_configs() -> Weight { (6_187_000 as Weight) - .saturating_add(RocksDbWeight::get().writes(6 as Weight)) + .saturating_add(RocksDbWeight::get().writes(5 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 2a23d203adf63..ebff7d141f574 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 224ff496a990a..dfbd8eb6821b1 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 decf2cb341bf8..d1f8b1b6269f2 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 the underlying [`[]::last_mut`]. + pub fn last_mut(&mut self) -> Option<&mut T> { + self.0.last_mut() + } + + /// Exactly the same semantics as [`[]::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 641b623053939..37959415c727f 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 @@ -105,10 +122,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 +130,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 +311,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. 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..d8b484d82ffdc --- /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() {} \ No newline at end of file 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 b1ec8639248e9..920db4f77b7d8 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 783b331b33532..dfa04717ec6e6 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,