referrerpolicy=no-referrer-when-downgrade

asset_hub_westend_runtime/
staking.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// SPDX-License-Identifier: Apache-2.0
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// 	http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16///! Staking, and election related pallet configurations.
17use super::*;
18use cumulus_primitives_core::relay_chain::SessionIndex;
19use frame_election_provider_support::{ElectionDataProvider, SequentialPhragmen};
20use frame_support::traits::{ConstU128, EitherOf};
21use pallet_election_provider_multi_block::{self as multi_block, SolutionAccuracyOf};
22use pallet_staking_async::UseValidatorsMap;
23use pallet_staking_async_rc_client as rc_client;
24use polkadot_runtime_common::{prod_or_fast, BalanceToU256, U256ToBalance};
25use sp_runtime::{
26	transaction_validity::TransactionPriority, FixedPointNumber, FixedU128, SaturatedConversion,
27};
28use xcm::latest::prelude::*;
29
30parameter_types! {
31	/// Number of election pages that we operate upon. 32 * 6s block = 192s = 3.2min snapshots
32	pub Pages: u32 = 32;
33
34	/// Compatible with Polkadot, we allow up to 22_500 nominators to be considered for election
35	pub MaxElectingVoters: u32 = 22_500;
36
37	/// Maximum number of validators that we may want to elect. 1000 is the end target.
38	pub const MaxValidatorSet: u32 = 1000;
39
40	/// Number of nominators per page of the snapshot, and consequently number of backers in the solution.
41	pub VoterSnapshotPerBlock: u32 = MaxElectingVoters::get() / Pages::get();
42
43	/// Number of validators per page of the snapshot.
44	pub TargetSnapshotPerBlock: u32 = MaxValidatorSet::get();
45
46	// 10 mins for each pages
47	pub storage SignedPhase: u32 = prod_or_fast!(
48		10 * MINUTES,
49		4 * MINUTES
50	);
51	pub storage UnsignedPhase: u32 = prod_or_fast!(
52		10 * MINUTES,
53		(1 * MINUTES)
54	);
55
56	/// validate up to 4 signed solution. Each solution.
57	pub storage SignedValidationPhase: u32 = prod_or_fast!(Pages::get() * 4, Pages::get());
58
59	/// In each page, we may observe up to all of the validators.
60	pub MaxWinnersPerPage: u32 = MaxValidatorSet::get();
61
62	/// In each page of the election, we allow up to all of the nominators of that page to be present.
63	pub MaxBackersPerWinner: u32 = VoterSnapshotPerBlock::get();
64
65	/// Total number of backers per winner across all pages.
66	pub MaxBackersPerWinnerFinal: u32 = MaxElectingVoters::get();
67
68	/// Size of the exposures. This should be small enough to make the reward payouts feasible.
69	pub MaxExposurePageSize: u32 = 64;
70
71	/// Each solution is considered "better" if it is 0.01% better.
72	pub SolutionImprovementThreshold: Perbill = Perbill::from_rational(1u32, 10_000);
73}
74
75frame_election_provider_support::generate_solution_type!(
76	#[compact]
77	pub struct NposCompactSolution16::<
78		// allows up to 4bn nominators
79		VoterIndex = u32,
80		// allows up to 64k validators
81		TargetIndex = u16,
82		Accuracy = sp_runtime::PerU16,
83		MaxVoters = VoterSnapshotPerBlock,
84	>(16)
85);
86
87ord_parameter_types! {
88	// https://westend.subscan.io/account/5GBoBNFP9TA7nAk82i6SUZJimerbdhxaRgyC2PVcdYQMdb8e
89	pub const WestendStakingMiner: AccountId = AccountId::from(hex_literal::hex!("b65991822483a6c3bd24b1dcf6afd3e270525da1f9c8c22a4373d1e1079e236a"));
90}
91
92#[cfg(feature = "runtime-benchmarks")]
93parameter_types! {
94	pub BenchElectionBounds: frame_election_provider_support::bounds::ElectionBounds =
95		frame_election_provider_support::bounds::ElectionBoundsBuilder::default().build();
96}
97
98#[cfg(feature = "runtime-benchmarks")]
99pub struct OnChainConfig;
100
101#[cfg(feature = "runtime-benchmarks")]
102impl frame_election_provider_support::onchain::Config for OnChainConfig {
103	// unbounded
104	type Bounds = BenchElectionBounds;
105	// We should not need sorting, as our bounds are large enough for the number of
106	// nominators/validators in this test setup.
107	type Sort = ConstBool<false>;
108	type DataProvider = Staking;
109	type MaxBackersPerWinner = MaxBackersPerWinner;
110	type MaxWinnersPerPage = MaxWinnersPerPage;
111	type Solver = frame_election_provider_support::SequentialPhragmen<AccountId, Perbill>;
112	type System = Runtime;
113	type WeightInfo = ();
114}
115
116impl multi_block::Config for Runtime {
117	type Pages = Pages;
118	type UnsignedPhase = UnsignedPhase;
119	type SignedPhase = SignedPhase;
120	type SignedValidationPhase = SignedValidationPhase;
121	type VoterSnapshotPerBlock = VoterSnapshotPerBlock;
122	type TargetSnapshotPerBlock = TargetSnapshotPerBlock;
123	type AdminOrigin =
124		EitherOfDiverse<EnsureRoot<AccountId>, EnsureSignedBy<WestendStakingMiner, AccountId>>;
125	type DataProvider = Staking;
126	type MinerConfig = Self;
127	type Verifier = MultiBlockElectionVerifier;
128	// we chill and do nothing in the fallback.
129	#[cfg(not(feature = "runtime-benchmarks"))]
130	type Fallback = multi_block::Continue<Self>;
131	#[cfg(feature = "runtime-benchmarks")]
132	type Fallback = frame_election_provider_support::onchain::OnChainExecution<OnChainConfig>;
133	// Revert back to signed phase if nothing is submitted and queued, so we prolong the election.
134	type AreWeDone = multi_block::RevertToSignedIfNotQueuedOf<Self>;
135	type OnRoundRotation = multi_block::CleanRound<Self>;
136	type WeightInfo = multi_block::weights::westend::MultiBlockWeightInfo<Self>;
137}
138
139impl multi_block::verifier::Config for Runtime {
140	type MaxWinnersPerPage = MaxWinnersPerPage;
141	type MaxBackersPerWinner = MaxBackersPerWinner;
142	type MaxBackersPerWinnerFinal = MaxBackersPerWinnerFinal;
143	type SolutionDataProvider = MultiBlockElectionSigned;
144	type SolutionImprovementThreshold = SolutionImprovementThreshold;
145	type WeightInfo = multi_block::weights::westend::MultiBlockVerifierWeightInfo<Self>;
146}
147
148parameter_types! {
149	pub BailoutGraceRatio: Perbill = Perbill::from_percent(50);
150	pub EjectGraceRatio: Perbill = Perbill::from_percent(50);
151	pub DepositBase: Balance = 5 * UNITS;
152	pub DepositPerPage: Balance = 1 * UNITS;
153	pub RewardBase: Balance = 10 * UNITS;
154	pub MaxSubmissions: u32 = 8;
155}
156
157impl multi_block::signed::Config for Runtime {
158	type Currency = Balances;
159	type BailoutGraceRatio = BailoutGraceRatio;
160	type EjectGraceRatio = EjectGraceRatio;
161	type DepositBase = DepositBase;
162	type DepositPerPage = DepositPerPage;
163	type InvulnerableDeposit = ();
164	type RewardBase = RewardBase;
165	type MaxSubmissions = MaxSubmissions;
166	type EstimateCallFee = TransactionPayment;
167	type WeightInfo = multi_block::weights::westend::MultiBlockSignedWeightInfo<Self>;
168}
169
170parameter_types! {
171	/// Priority of the offchain miner transactions.
172	pub MinerTxPriority: TransactionPriority = TransactionPriority::max_value() / 2;
173	/// Try and run the OCW miner 4 times during the unsigned phase.
174	pub OffchainRepeat: BlockNumber = UnsignedPhase::get() / 4;
175	pub storage MinerPages: u32 = 2;
176}
177
178impl multi_block::unsigned::Config for Runtime {
179	type MinerPages = MinerPages;
180	type OffchainStorage = ConstBool<true>;
181	type OffchainSolver = SequentialPhragmen<AccountId, SolutionAccuracyOf<Runtime>>;
182	type MinerTxPriority = MinerTxPriority;
183	type OffchainRepeat = OffchainRepeat;
184	type WeightInfo = multi_block::weights::westend::MultiBlockUnsignedWeightInfo<Self>;
185}
186
187parameter_types! {
188	/// Miner transaction can fill up to 75% of the block size.
189	pub MinerMaxLength: u32 = Perbill::from_rational(75u32, 100) *
190		*RuntimeBlockLength::get()
191		.max
192		.get(DispatchClass::Normal);
193}
194
195impl multi_block::unsigned::miner::MinerConfig for Runtime {
196	type AccountId = AccountId;
197	type Hash = Hash;
198	type MaxBackersPerWinner = <Self as multi_block::verifier::Config>::MaxBackersPerWinner;
199	type MaxBackersPerWinnerFinal =
200		<Self as multi_block::verifier::Config>::MaxBackersPerWinnerFinal;
201	type MaxWinnersPerPage = <Self as multi_block::verifier::Config>::MaxWinnersPerPage;
202	type MaxVotesPerVoter =
203		<<Self as multi_block::Config>::DataProvider as ElectionDataProvider>::MaxVotesPerVoter;
204	type MaxLength = MinerMaxLength;
205	type Solver = <Runtime as multi_block::unsigned::Config>::OffchainSolver;
206	type Pages = Pages;
207	type Solution = NposCompactSolution16;
208	type VoterSnapshotPerBlock = <Runtime as multi_block::Config>::VoterSnapshotPerBlock;
209	type TargetSnapshotPerBlock = <Runtime as multi_block::Config>::TargetSnapshotPerBlock;
210}
211
212parameter_types! {
213	pub const BagThresholds: &'static [u64] = &bag_thresholds::THRESHOLDS;
214	pub const AutoRebagNumber: u32 = 10;
215}
216
217type VoterBagsListInstance = pallet_bags_list::Instance1;
218impl pallet_bags_list::Config<VoterBagsListInstance> for Runtime {
219	type RuntimeEvent = RuntimeEvent;
220	type ScoreProvider = Staking;
221	type WeightInfo = weights::pallet_bags_list::WeightInfo<Runtime>;
222	type BagThresholds = BagThresholds;
223	type Score = sp_npos_elections::VoteWeight;
224	type MaxAutoRebagPerBlock = AutoRebagNumber;
225}
226
227pub struct EraPayout;
228impl pallet_staking_async::EraPayout<Balance> for EraPayout {
229	fn era_payout(
230		_total_staked: Balance,
231		_total_issuance: Balance,
232		era_duration_millis: u64,
233	) -> (Balance, Balance) {
234		const MILLISECONDS_PER_YEAR: u64 = (1000 * 3600 * 24 * 36525) / 100;
235		// A normal-sized era will have 1 / 365.25 here:
236		let relative_era_len =
237			FixedU128::from_rational(era_duration_millis.into(), MILLISECONDS_PER_YEAR.into());
238
239		// Fixed total TI that we use as baseline for the issuance.
240		let fixed_total_issuance: i128 = 5_216_342_402_773_185_773;
241		let fixed_inflation_rate = FixedU128::from_rational(8, 100);
242		let yearly_emission = fixed_inflation_rate.saturating_mul_int(fixed_total_issuance);
243
244		let era_emission = relative_era_len.saturating_mul_int(yearly_emission);
245		// 15% to treasury, as per Polkadot ref 1139.
246		let to_treasury = FixedU128::from_rational(15, 100).saturating_mul_int(era_emission);
247		let to_stakers = era_emission.saturating_sub(to_treasury);
248
249		(to_stakers.saturated_into(), to_treasury.saturated_into())
250	}
251}
252
253parameter_types! {
254	// Six sessions in an era (6 hours).
255	pub const SessionsPerEra: SessionIndex = prod_or_fast!(6, 2);
256	/// Duration of a relay session in our blocks. Needs to be hardcoded per-runtime.
257	pub const RelaySessionDuration: BlockNumber = 1 * HOURS;
258	// 2 eras for unbonding (12 hours).
259	pub const BondingDuration: sp_staking::EraIndex = 2;
260	// 1 era in which slashes can be cancelled (6 hours).
261	pub const SlashDeferDuration: sp_staking::EraIndex = 1;
262	pub const MaxControllersInDeprecationBatch: u32 = 751;
263	// alias for 16, which is the max nominations per nominator in the runtime.
264	pub const MaxNominations: u32 = <NposCompactSolution16 as frame_election_provider_support::NposSolution>::LIMIT as u32;
265	pub const MaxEraDuration: u64 = RelaySessionDuration::get() as u64 * RELAY_CHAIN_SLOT_DURATION_MILLIS as u64 * SessionsPerEra::get() as u64;
266	pub MaxPruningItems: u32 = 100;
267}
268
269impl pallet_staking_async::Config for Runtime {
270	type Filter = ();
271	type OldCurrency = Balances;
272	type Currency = Balances;
273	type CurrencyBalance = Balance;
274	type RuntimeHoldReason = RuntimeHoldReason;
275	type CurrencyToVote = sp_staking::currency_to_vote::SaturatingCurrencyToVote;
276	type RewardRemainder = ();
277	type Slash = ();
278	type Reward = ();
279	type SessionsPerEra = SessionsPerEra;
280	type BondingDuration = BondingDuration;
281	type SlashDeferDuration = SlashDeferDuration;
282	type AdminOrigin = EitherOf<EnsureRoot<AccountId>, StakingAdmin>;
283	type EraPayout = EraPayout;
284	type MaxExposurePageSize = MaxExposurePageSize;
285	type ElectionProvider = MultiBlockElection;
286	type VoterList = VoterList;
287	type TargetList = UseValidatorsMap<Self>;
288	type MaxValidatorSet = MaxValidatorSet;
289	type NominationsQuota = pallet_staking_async::FixedNominationsQuota<{ MaxNominations::get() }>;
290	type MaxUnlockingChunks = frame_support::traits::ConstU32<32>;
291	type HistoryDepth = frame_support::traits::ConstU32<84>;
292	type MaxControllersInDeprecationBatch = MaxControllersInDeprecationBatch;
293	type EventListeners = (NominationPools, DelegatedStaking);
294	type WeightInfo = weights::pallet_staking_async::WeightInfo<Runtime>;
295	type MaxInvulnerables = frame_support::traits::ConstU32<20>;
296	type PlanningEraOffset =
297		pallet_staking_async::PlanningEraOffsetOf<Runtime, RelaySessionDuration, ConstU32<5>>;
298	type RcClientInterface = StakingRcClient;
299	type MaxEraDuration = MaxEraDuration;
300	type MaxPruningItems = MaxPruningItems;
301}
302
303impl pallet_staking_async_rc_client::Config for Runtime {
304	type RelayChainOrigin = EnsureRoot<AccountId>;
305	type AHStakingInterface = Staking;
306	type SendToRelayChain = StakingXcmToRelayChain;
307	type MaxValidatorSetRetries = ConstU32<64>;
308}
309
310#[derive(Encode, Decode)]
311// Call indices taken from westend-next runtime.
312pub enum RelayChainRuntimePallets {
313	// Audit: index of `AssetHubStakingClient` in westend.
314	#[codec(index = 67)]
315	AhClient(AhClientCalls),
316}
317
318#[derive(Encode, Decode)]
319pub enum AhClientCalls {
320	// index of `fn validator_set` in `staking-async-ah-client`. It has only one call.
321	#[codec(index = 0)]
322	ValidatorSet(rc_client::ValidatorSetReport<AccountId>),
323}
324
325pub struct ValidatorSetToXcm;
326impl sp_runtime::traits::Convert<rc_client::ValidatorSetReport<AccountId>, Xcm<()>>
327	for ValidatorSetToXcm
328{
329	fn convert(report: rc_client::ValidatorSetReport<AccountId>) -> Xcm<()> {
330		Xcm(vec![
331			Instruction::UnpaidExecution {
332				weight_limit: WeightLimit::Unlimited,
333				check_origin: None,
334			},
335			Instruction::Transact {
336				origin_kind: OriginKind::Native,
337				fallback_max_weight: None,
338				call: RelayChainRuntimePallets::AhClient(AhClientCalls::ValidatorSet(report))
339					.encode()
340					.into(),
341			},
342		])
343	}
344}
345
346parameter_types! {
347	pub RelayLocation: Location = Location::parent();
348}
349
350pub struct StakingXcmToRelayChain;
351
352impl rc_client::SendToRelayChain for StakingXcmToRelayChain {
353	type AccountId = AccountId;
354	fn validator_set(report: rc_client::ValidatorSetReport<Self::AccountId>) -> Result<(), ()> {
355		rc_client::XCMSender::<
356			xcm_config::XcmRouter,
357			RelayLocation,
358			rc_client::ValidatorSetReport<Self::AccountId>,
359			ValidatorSetToXcm,
360		>::send(report)
361	}
362}
363
364impl pallet_fast_unstake::Config for Runtime {
365	type RuntimeEvent = RuntimeEvent;
366	type Currency = Balances;
367	type BatchSize = ConstU32<64>;
368	type Deposit = ConstU128<{ UNITS }>;
369	type ControlOrigin = EnsureRoot<AccountId>;
370	type Staking = Staking;
371	type MaxErasToCheckPerBlock = ConstU32<1>;
372	type WeightInfo = weights::pallet_fast_unstake::WeightInfo<Runtime>;
373}
374
375parameter_types! {
376	pub const PoolsPalletId: PalletId = PalletId(*b"py/nopls");
377	pub const MaxPointsToBalance: u8 = 10;
378}
379
380impl pallet_nomination_pools::Config for Runtime {
381	type RuntimeEvent = RuntimeEvent;
382	type WeightInfo = weights::pallet_nomination_pools::WeightInfo<Self>;
383	type Currency = Balances;
384	type RuntimeFreezeReason = RuntimeFreezeReason;
385	type RewardCounter = FixedU128;
386	type BalanceToU256 = BalanceToU256;
387	type U256ToBalance = U256ToBalance;
388	type StakeAdapter =
389		pallet_nomination_pools::adapter::DelegateStake<Self, Staking, DelegatedStaking>;
390	type PostUnbondingPoolsWindow = ConstU32<4>;
391	type MaxMetadataLen = ConstU32<256>;
392	// we use the same number of allowed unlocking chunks as with staking.
393	type MaxUnbonding = <Self as pallet_staking_async::Config>::MaxUnlockingChunks;
394	type PalletId = PoolsPalletId;
395	type MaxPointsToBalance = MaxPointsToBalance;
396	type AdminOrigin = EitherOf<EnsureRoot<AccountId>, StakingAdmin>;
397	type BlockNumberProvider = RelaychainDataProvider<Runtime>;
398	type Filter = Nothing;
399}
400
401parameter_types! {
402	pub const DelegatedStakingPalletId: PalletId = PalletId(*b"py/dlstk");
403	pub const SlashRewardFraction: Perbill = Perbill::from_percent(1);
404}
405
406impl pallet_delegated_staking::Config for Runtime {
407	type RuntimeEvent = RuntimeEvent;
408	type PalletId = DelegatedStakingPalletId;
409	type Currency = Balances;
410	type OnSlash = ();
411	type SlashRewardFraction = SlashRewardFraction;
412	type RuntimeHoldReason = RuntimeHoldReason;
413	type CoreStaking = Staking;
414}
415
416/// The payload being signed in transactions.
417pub type SignedPayload = generic::SignedPayload<RuntimeCall, TxExtension>;
418/// Unchecked extrinsic type as expected by this runtime.
419pub type UncheckedExtrinsic =
420	generic::UncheckedExtrinsic<Address, RuntimeCall, Signature, TxExtension>;
421
422impl frame_system::offchain::SigningTypes for Runtime {
423	type Public = <Signature as Verify>::Signer;
424	type Signature = Signature;
425}
426
427impl<C> frame_system::offchain::CreateTransactionBase<C> for Runtime
428where
429	RuntimeCall: From<C>,
430{
431	type RuntimeCall = RuntimeCall;
432	type Extrinsic = UncheckedExtrinsic;
433}
434
435impl<LocalCall> frame_system::offchain::CreateTransaction<LocalCall> for Runtime
436where
437	RuntimeCall: From<LocalCall>,
438{
439	type Extension = TxExtension;
440
441	fn create_transaction(call: RuntimeCall, extension: TxExtension) -> UncheckedExtrinsic {
442		UncheckedExtrinsic::new_transaction(call, extension)
443	}
444}
445
446/// Submits a transaction with the node's public and signature type. Adheres to the signed extension
447/// format of the chain.
448impl<LocalCall> frame_system::offchain::CreateSignedTransaction<LocalCall> for Runtime
449where
450	RuntimeCall: From<LocalCall>,
451{
452	fn create_signed_transaction<
453		C: frame_system::offchain::AppCrypto<Self::Public, Self::Signature>,
454	>(
455		call: RuntimeCall,
456		public: <Signature as Verify>::Signer,
457		account: AccountId,
458		nonce: <Runtime as frame_system::Config>::Nonce,
459	) -> Option<UncheckedExtrinsic> {
460		use sp_runtime::traits::StaticLookup;
461		// take the biggest period possible.
462		let period =
463			BlockHashCount::get().checked_next_power_of_two().map(|c| c / 2).unwrap_or(2) as u64;
464
465		let current_block = System::block_number()
466			.saturated_into::<u64>()
467			// The `System::block_number` is initialized with `n+1`,
468			// so the actual block number is `n`.
469			.saturating_sub(1);
470		let tip = 0;
471		let tx_ext = TxExtension::from((
472			frame_system::AuthorizeCall::<Runtime>::new(),
473			frame_system::CheckNonZeroSender::<Runtime>::new(),
474			frame_system::CheckSpecVersion::<Runtime>::new(),
475			frame_system::CheckTxVersion::<Runtime>::new(),
476			frame_system::CheckGenesis::<Runtime>::new(),
477			frame_system::CheckEra::<Runtime>::from(generic::Era::mortal(period, current_block)),
478			frame_system::CheckNonce::<Runtime>::from(nonce),
479			frame_system::CheckWeight::<Runtime>::new(),
480			pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),
481			frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(true),
482		));
483		let raw_payload = SignedPayload::new(call, tx_ext)
484			.map_err(|e| {
485				tracing::warn!(target: "runtime::staking", error=?e, "Unable to create signed payload");
486			})
487			.ok()?;
488		let signature = raw_payload.using_encoded(|payload| C::sign(payload, public))?;
489		let (call, tx_ext, _) = raw_payload.deconstruct();
490		let address = <Runtime as frame_system::Config>::Lookup::unlookup(account);
491		let transaction = UncheckedExtrinsic::new_signed(call, address, signature, tx_ext);
492		Some(transaction)
493	}
494}
495
496impl<LocalCall> frame_system::offchain::CreateInherent<LocalCall> for Runtime
497where
498	RuntimeCall: From<LocalCall>,
499{
500	fn create_bare(call: RuntimeCall) -> UncheckedExtrinsic {
501		UncheckedExtrinsic::new_bare(call)
502	}
503}