pallet_staking_async/
lib.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17
18//! # Staking Async Pallet
19//!
20//! This pallet is a fork of the original `pallet-staking`, with a number of key differences:
21//!
22//! * It no longer has access to a secure timestamp, previously used to calculate the duration of an
23//!   era.
24//! * It no longer has access to a pallet-session.
25//! * It no longer has access to a pallet-authorship.
26//! * It is capable of working with a multi-page `ElectionProvider``, aka.
27//!   `pallet-election-provider-multi-block`.
28//!
29//! While `pallet-staking` was somewhat general-purpose, this pallet is absolutely NOT right from
30//! the get-go: It is designed to be used ONLY in Polkadot/Kusama AssetHub system parachains.
31//!
32//! The workings of this pallet can be divided into a number of subsystems, as follows.
33//!
34//! ## User Interactions
35//!
36//! TODO
37//!
38//! ## Session and Era Rotation
39//!
40//! TODO
41//!
42//! ## Exposure Collection
43//!
44//! TODO
45//!
46//! ## Slashing of Validators and Exposures
47//!
48//! TODO
49
50#![cfg_attr(not(feature = "std"), no_std)]
51#![recursion_limit = "256"]
52
53#[cfg(feature = "runtime-benchmarks")]
54pub mod benchmarking;
55#[cfg(any(feature = "runtime-benchmarks", test))]
56pub mod testing_utils;
57
58#[cfg(test)]
59pub(crate) mod mock;
60#[cfg(test)]
61mod tests;
62
63pub mod asset;
64pub mod election_size_tracker;
65pub mod ledger;
66mod pallet;
67pub mod session_rotation;
68pub mod slashing;
69pub mod weights;
70
71extern crate alloc;
72use alloc::{vec, vec::Vec};
73use codec::{Decode, DecodeWithMemTracking, Encode, HasCompact, MaxEncodedLen};
74use frame_election_provider_support::ElectionProvider;
75use frame_support::{
76	traits::{
77		tokens::fungible::{Credit, Debt},
78		ConstU32, Contains, Get, LockIdentifier,
79	},
80	BoundedVec, DebugNoBound, DefaultNoBound, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
81	WeakBoundedVec,
82};
83use frame_system::pallet_prelude::BlockNumberFor;
84use ledger::LedgerIntegrityState;
85use scale_info::TypeInfo;
86use sp_runtime::{
87	traits::{AtLeast32BitUnsigned, One, StaticLookup, UniqueSaturatedInto},
88	BoundedBTreeMap, Perbill, RuntimeDebug, Saturating,
89};
90use sp_staking::{EraIndex, ExposurePage, PagedExposureMetadata, SessionIndex};
91pub use sp_staking::{Exposure, IndividualExposure, StakerStatus};
92pub use weights::WeightInfo;
93
94// public exports
95pub use ledger::{StakingLedger, UnlockChunk};
96pub use pallet::{pallet::*, UseNominatorsAndValidatorsMap, UseValidatorsMap};
97
98pub(crate) const STAKING_ID: LockIdentifier = *b"staking ";
99pub(crate) const LOG_TARGET: &str = "runtime::staking-async";
100
101// syntactic sugar for logging.
102#[macro_export]
103macro_rules! log {
104	($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
105		log::$level!(
106			target: crate::LOG_TARGET,
107			concat!("[{:?}] 💸 ", $patter), <frame_system::Pallet<T>>::block_number() $(, $values)*
108		)
109	};
110}
111
112/// Alias for a bounded set of exposures behind a validator, parameterized by this pallet's
113/// election provider.
114pub type BoundedExposuresOf<T> = BoundedVec<
115	(
116		<T as frame_system::Config>::AccountId,
117		Exposure<<T as frame_system::Config>::AccountId, BalanceOf<T>>,
118	),
119	MaxWinnersPerPageOf<<T as Config>::ElectionProvider>,
120>;
121
122/// Alias for the maximum number of winners (aka. active validators), as defined in by this pallet's
123/// config.
124pub type MaxWinnersOf<T> = <T as Config>::MaxValidatorSet;
125
126/// Alias for the maximum number of winners per page, as expected by the election provider.
127pub type MaxWinnersPerPageOf<P> = <P as ElectionProvider>::MaxWinnersPerPage;
128
129/// Maximum number of nominations per nominator.
130pub type MaxNominationsOf<T> =
131	<<T as Config>::NominationsQuota as NominationsQuota<BalanceOf<T>>>::MaxNominations;
132
133/// Counter for the number of "reward" points earned by a given validator.
134pub type RewardPoint = u32;
135
136/// The balance type of this pallet.
137pub type BalanceOf<T> = <T as Config>::CurrencyBalance;
138
139type PositiveImbalanceOf<T> = Debt<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
140pub type NegativeImbalanceOf<T> =
141	Credit<<T as frame_system::Config>::AccountId, <T as Config>::Currency>;
142
143type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
144
145/// Information regarding the active era (era in used in session).
146#[derive(Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, PartialEq, Eq, Clone)]
147pub struct ActiveEraInfo {
148	/// Index of era.
149	pub index: EraIndex,
150	/// Moment of start expressed as millisecond from `$UNIX_EPOCH`.
151	///
152	/// Start can be none if start hasn't been set for the era yet,
153	/// Start is set on the first on_finalize of the era to guarantee usage of `Time`.
154	pub start: Option<u64>,
155}
156
157/// Reward points of an era. Used to split era total payout between validators.
158///
159/// This points will be used to reward validators and their respective nominators.
160#[derive(
161	PartialEqNoBound, Encode, Decode, DebugNoBound, TypeInfo, MaxEncodedLen, DefaultNoBound,
162)]
163#[codec(mel_bound())]
164#[scale_info(skip_type_params(T))]
165pub struct EraRewardPoints<T: Config> {
166	/// Total number of points. Equals the sum of reward points for each validator.
167	pub total: RewardPoint,
168	/// The reward points earned by a given validator.
169	pub individual: BoundedBTreeMap<T::AccountId, RewardPoint, T::MaxValidatorSet>,
170}
171
172/// A destination account for payment.
173#[derive(
174	PartialEq,
175	Eq,
176	Copy,
177	Clone,
178	Encode,
179	Decode,
180	DecodeWithMemTracking,
181	RuntimeDebug,
182	TypeInfo,
183	MaxEncodedLen,
184)]
185pub enum RewardDestination<AccountId> {
186	/// Pay into the stash account, increasing the amount at stake accordingly.
187	Staked,
188	/// Pay into the stash account, not increasing the amount at stake.
189	Stash,
190	#[deprecated(
191		note = "`Controller` will be removed after January 2024. Use `Account(controller)` instead."
192	)]
193	Controller,
194	/// Pay into a specified account.
195	Account(AccountId),
196	/// Receive no reward.
197	None,
198}
199
200/// Preference of what happens regarding validation.
201#[derive(
202	PartialEq,
203	Eq,
204	Clone,
205	Encode,
206	Decode,
207	DecodeWithMemTracking,
208	RuntimeDebug,
209	TypeInfo,
210	Default,
211	MaxEncodedLen,
212)]
213pub struct ValidatorPrefs {
214	/// Reward that validator takes up-front; only the rest is split between themselves and
215	/// nominators.
216	#[codec(compact)]
217	pub commission: Perbill,
218	/// Whether or not this validator is accepting more nominations. If `true`, then no nominator
219	/// who is not already nominating this validator may nominate them. By default, validators
220	/// are accepting nominations.
221	pub blocked: bool,
222}
223
224/// Status of a paged snapshot progress.
225#[derive(PartialEq, Eq, Clone, Encode, Decode, RuntimeDebug, TypeInfo, MaxEncodedLen, Default)]
226pub enum SnapshotStatus<AccountId> {
227	/// Paged snapshot is in progress, the `AccountId` was the last staker iterated in the list.
228	Ongoing(AccountId),
229	/// All the stakers in the system have been consumed since the snapshot started.
230	Consumed,
231	/// Waiting for a new snapshot to be requested.
232	#[default]
233	Waiting,
234}
235
236/// A record of the nominations made by a specific account.
237#[derive(
238	PartialEqNoBound, EqNoBound, Clone, Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen,
239)]
240#[codec(mel_bound())]
241#[scale_info(skip_type_params(T))]
242pub struct Nominations<T: Config> {
243	/// The targets of nomination.
244	pub targets: BoundedVec<T::AccountId, MaxNominationsOf<T>>,
245	/// The era the nominations were submitted.
246	///
247	/// Except for initial nominations which are considered submitted at era 0.
248	pub submitted_in: EraIndex,
249	/// Whether the nominations have been suppressed. This can happen due to slashing of the
250	/// validators, or other events that might invalidate the nomination.
251	///
252	/// NOTE: this for future proofing and is thus far not used.
253	pub suppressed: bool,
254}
255
256/// Facade struct to encapsulate `PagedExposureMetadata` and a single page of `ExposurePage`.
257///
258/// This is useful where we need to take into account the validator's own stake and total exposure
259/// in consideration, in addition to the individual nominators backing them.
260#[derive(Encode, Decode, RuntimeDebug, TypeInfo, PartialEq, Eq)]
261pub struct PagedExposure<AccountId, Balance: HasCompact + codec::MaxEncodedLen> {
262	exposure_metadata: PagedExposureMetadata<Balance>,
263	exposure_page: ExposurePage<AccountId, Balance>,
264}
265
266impl<AccountId, Balance: HasCompact + Copy + AtLeast32BitUnsigned + codec::MaxEncodedLen>
267	PagedExposure<AccountId, Balance>
268{
269	/// Create a new instance of `PagedExposure` from legacy clipped exposures.
270	pub fn from_clipped(exposure: Exposure<AccountId, Balance>) -> Self {
271		Self {
272			exposure_metadata: PagedExposureMetadata {
273				total: exposure.total,
274				own: exposure.own,
275				nominator_count: exposure.others.len() as u32,
276				page_count: 1,
277			},
278			exposure_page: ExposurePage { page_total: exposure.total, others: exposure.others },
279		}
280	}
281
282	/// Returns total exposure of this validator across pages
283	pub fn total(&self) -> Balance {
284		self.exposure_metadata.total
285	}
286
287	/// Returns total exposure of this validator for the current page
288	pub fn page_total(&self) -> Balance {
289		self.exposure_page.page_total + self.exposure_metadata.own
290	}
291
292	/// Returns validator's own stake that is exposed
293	pub fn own(&self) -> Balance {
294		self.exposure_metadata.own
295	}
296
297	/// Returns the portions of nominators stashes that are exposed in this page.
298	pub fn others(&self) -> &Vec<IndividualExposure<AccountId, Balance>> {
299		&self.exposure_page.others
300	}
301}
302
303/// A pending slash record. The value of the slash has been computed but not applied yet,
304/// rather deferred for several eras.
305#[derive(Encode, Decode, RuntimeDebugNoBound, TypeInfo, MaxEncodedLen, PartialEqNoBound)]
306#[scale_info(skip_type_params(T))]
307pub struct UnappliedSlash<T: Config> {
308	/// The stash ID of the offending validator.
309	pub validator: T::AccountId,
310	/// The validator's own slash.
311	pub own: BalanceOf<T>,
312	/// All other slashed stakers and amounts.
313	pub others: WeakBoundedVec<(T::AccountId, BalanceOf<T>), T::MaxExposurePageSize>,
314	/// Reporters of the offence; bounty payout recipients.
315	pub reporter: Option<T::AccountId>,
316	/// The amount of payout.
317	pub payout: BalanceOf<T>,
318}
319
320/// Something that defines the maximum number of nominations per nominator based on a curve.
321///
322/// The method `curve` implements the nomination quota curve and should not be used directly.
323/// However, `get_quota` returns the bounded maximum number of nominations based on `fn curve` and
324/// the nominator's balance.
325pub trait NominationsQuota<Balance> {
326	/// Strict maximum number of nominations that caps the nominations curve. This value can be
327	/// used as the upper bound of the number of votes per nominator.
328	type MaxNominations: Get<u32>;
329
330	/// Returns the voter's nomination quota within reasonable bounds [`min`, `max`], where `min`
331	/// is 1 and `max` is `Self::MaxNominations`.
332	fn get_quota(balance: Balance) -> u32 {
333		Self::curve(balance).clamp(1, Self::MaxNominations::get())
334	}
335
336	/// Returns the voter's nomination quota based on its balance and a curve.
337	fn curve(balance: Balance) -> u32;
338}
339
340/// A nomination quota that allows up to MAX nominations for all validators.
341pub struct FixedNominationsQuota<const MAX: u32>;
342impl<Balance, const MAX: u32> NominationsQuota<Balance> for FixedNominationsQuota<MAX> {
343	type MaxNominations = ConstU32<MAX>;
344
345	fn curve(_: Balance) -> u32 {
346		MAX
347	}
348}
349
350/// Handler for determining how much of a balance should be paid out on the current era.
351pub trait EraPayout<Balance> {
352	/// Determine the payout for this era.
353	///
354	/// Returns the amount to be paid to stakers in this era, as well as whatever else should be
355	/// paid out ("the rest").
356	fn era_payout(
357		total_staked: Balance,
358		total_issuance: Balance,
359		era_duration_millis: u64,
360	) -> (Balance, Balance);
361}
362
363impl<Balance: Default> EraPayout<Balance> for () {
364	fn era_payout(
365		_total_staked: Balance,
366		_total_issuance: Balance,
367		_era_duration_millis: u64,
368	) -> (Balance, Balance) {
369		(Default::default(), Default::default())
370	}
371}
372
373/// Mode of era-forcing.
374#[derive(
375	Copy,
376	Clone,
377	PartialEq,
378	Eq,
379	Encode,
380	Decode,
381	DecodeWithMemTracking,
382	RuntimeDebug,
383	TypeInfo,
384	MaxEncodedLen,
385	serde::Serialize,
386	serde::Deserialize,
387)]
388pub enum Forcing {
389	/// Not forcing anything - just let whatever happen.
390	NotForcing,
391	/// Force a new era, then reset to `NotForcing` as soon as it is done.
392	/// Note that this will force to trigger an election until a new era is triggered, if the
393	/// election failed, the next session end will trigger a new election again, until success.
394	ForceNew,
395	/// Avoid a new era indefinitely.
396	ForceNone,
397	/// Force a new era at the end of all sessions indefinitely.
398	ForceAlways,
399}
400
401impl Default for Forcing {
402	fn default() -> Self {
403		Forcing::NotForcing
404	}
405}
406
407/// A utility struct that provides a way to check if a given account is a staker.
408///
409/// This struct implements the `Contains` trait, allowing it to determine whether
410/// a particular account is currently staking by checking if the account exists in
411/// the staking ledger.
412///
413/// Intended to be used in [`crate::Config::Filter`].
414pub struct AllStakers<T: Config>(core::marker::PhantomData<T>);
415
416impl<T: Config> Contains<T::AccountId> for AllStakers<T> {
417	/// Checks if the given account ID corresponds to a staker.
418	///
419	/// # Returns
420	/// - `true` if the account has an entry in the staking ledger (indicating it is staking).
421	/// - `false` otherwise.
422	fn contains(account: &T::AccountId) -> bool {
423		Ledger::<T>::contains_key(account)
424	}
425}
426
427/// A smart type to determine the [`Config::PlanningEraOffset`], given:
428///
429/// * Expected relay session duration, `RS`
430/// * Time taking into consideration for XCM sending, `S`
431///
432/// It will use the estimated election duration, the relay session duration, and add one as it knows
433/// the relay chain will want to buffer validators for one session. This is needed because we use
434/// this in our calculation based on the "active era".
435pub struct PlanningEraOffsetOf<T, RS, S>(core::marker::PhantomData<(T, RS, S)>);
436impl<T: Config, RS: Get<BlockNumberFor<T>>, S: Get<BlockNumberFor<T>>> Get<SessionIndex>
437	for PlanningEraOffsetOf<T, RS, S>
438{
439	fn get() -> SessionIndex {
440		let election_duration = <T::ElectionProvider as ElectionProvider>::duration_with_export();
441		let sessions_needed = (election_duration + S::get()) / RS::get();
442		// add one, because we know the RC session pallet wants to buffer for one session, and
443		// another one cause we will receive activation report one session after that.
444		sessions_needed
445			.saturating_add(One::one())
446			.saturating_add(One::one())
447			.unique_saturated_into()
448	}
449}