mirror of
https://github.com/pezkuwichain/pezkuwi-subxt.git
synced 2026-04-30 00:17:56 +00:00
Improve documentation for fast-unstake pallet (#14101)
* improve documentation of fast-unstake pallet * using Sam's crate now * fix * remove stuff not needed * Some updates * use new prelude. * update * some other fancy docs * Update frame/fast-unstake/src/lib.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * Update frame/support/procedural/src/pallet/expand/mod.rs Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> * update * Update frame/fast-unstake/src/lib.rs Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> * update * fix no_std issue by updating to latest version of docify * get things compiling by adding a use for StakingInterface * fix docify paths to their proper values, still not working because bug * embed working 🎉 * update syn * upgrade to docify v0.1.10 for some fixes * Apply suggestions from code review Co-authored-by: Juan <juangirini@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> * improve docs * Update frame/support/procedural/src/pallet/expand/doc_only.rs Co-authored-by: Juan <juangirini@gmail.com> * fmt * fix * Update frame/support/procedural/src/pallet/expand/doc_only.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/support/procedural/src/pallet/expand/config.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * Update frame/support/procedural/src/pallet/expand/config.rs Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> * remove redundant * update docify rev * update. * update. * update lock file --------- Co-authored-by: Liam Aharon <liam.aharon@hotmail.com> Co-authored-by: Keith Yeung <kungfukeith11@gmail.com> Co-authored-by: Sam Johnson <sam@durosoft.com> Co-authored-by: Juan <juangirini@gmail.com> Co-authored-by: Gonçalo Pestana <g6pestana@gmail.com> Co-authored-by: Muharem Ismailov <ismailov.m.h@gmail.com> Co-authored-by: parity-processbot <>
This commit is contained in:
Generated
+34
@@ -1289,6 +1289,12 @@ dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "common-path"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.2.0"
|
||||
@@ -2028,6 +2034,33 @@ version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "docify"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18b972b74c30cbe838fc6a07665132ff94f257350e26fd01d80bc59ee7fcf129"
|
||||
dependencies = [
|
||||
"docify_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docify_macros"
|
||||
version = "0.1.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c93004d1011191c56df9e853dca42f2012e7488638bcd5078935f5ce43e06cf3"
|
||||
dependencies = [
|
||||
"common-path",
|
||||
"derive-syn-parse",
|
||||
"lazy_static",
|
||||
"prettyplease 0.2.4",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.16",
|
||||
"termcolor",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "downcast"
|
||||
version = "0.11.0"
|
||||
@@ -6418,6 +6451,7 @@ dependencies = [
|
||||
name = "pallet-fast-unstake"
|
||||
version = "4.0.0-dev"
|
||||
dependencies = [
|
||||
"docify",
|
||||
"frame-benchmarking",
|
||||
"frame-election-provider-support",
|
||||
"frame-support",
|
||||
|
||||
@@ -27,6 +27,8 @@ frame-election-provider-support = { default-features = false, path = "../electio
|
||||
|
||||
frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
|
||||
|
||||
docify = "0.1.13"
|
||||
|
||||
[dev-dependencies]
|
||||
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" }
|
||||
sp-core = { version = "8.0.0", default-features = false, path = "../../primitives/core" }
|
||||
|
||||
@@ -15,37 +15,100 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
//! A pallet that's designed to JUST do the following:
|
||||
//! > Made with *Substrate*, for *Polkadot*.
|
||||
//!
|
||||
//! If a nominator is not exposed in any `ErasStakers` (i.e. "has not actively backed any
|
||||
//! validators in the last `BondingDuration` days"), then they can register themselves in this
|
||||
//! pallet, unstake faster than having to wait an entire bonding duration.
|
||||
//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
|
||||
//! [![polkadot]](https://polkadot.network)
|
||||
//!
|
||||
//! Appearing in the exposure of a validator means being exposed equal to that validator from the
|
||||
//! point of view of the staking system. This usually means earning rewards with the validator, and
|
||||
//! also being at the risk of slashing with the validator. This is equivalent to the "Active
|
||||
//! Nominator" role explained in the
|
||||
//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/).
|
||||
//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
|
||||
//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
|
||||
//!
|
||||
//! # Fast Unstake Pallet
|
||||
//!
|
||||
//! A pallet to allow participants of the staking system (represented by [`Config::Staking`], being
|
||||
//! [`StakingInterface`]) to unstake quicker, if and only if they meet the condition of not being
|
||||
//! exposed to any slashes.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! If a nominator is not exposed anywhere in the staking system, checked via
|
||||
//! [`StakingInterface::is_exposed_in_era`] (i.e. "has not actively backed any validators in the
|
||||
//! last [`StakingInterface::bonding_duration`] days"), then they can register themselves in this
|
||||
//! pallet and unstake faster than having to wait an entire bonding duration.
|
||||
//!
|
||||
//! *Being exposed with validator* from the point of view of the staking system means earning
|
||||
//! rewards with the validator, and also being at the risk of slashing with the validator. This is
|
||||
//! equivalent to the "Active Nominator" role explained in
|
||||
//! [here](https://polkadot.network/blog/staking-update-february-2022/).
|
||||
//!
|
||||
//! Stakers who are certain about NOT being exposed can register themselves with
|
||||
//! [`Pallet::register_fast_unstake`]. This will chill, fully unbond the staker and place them
|
||||
//! in the queue to be checked.
|
||||
//!
|
||||
//! A successful registration implies being fully unbonded and chilled in the staking system. These
|
||||
//! effects persist even if the fast-unstake registration is retracted (see [`Pallet::deregister`]
|
||||
//! and further).
|
||||
//!
|
||||
//! Once registered as a fast-unstaker, the staker will be queued and checked by the system. This
|
||||
//! can take a variable number of blocks based on demand, but will almost certainly be "faster" (as
|
||||
//! the name suggest) than waiting the standard bonding duration.
|
||||
//!
|
||||
//! A fast-unstaker is either in [`Queue`] or actively being checked, at which point it lives in
|
||||
//! [`Head`]. Once in [`Head`], the request cannot be retracted anymore. But, once in [`Queue`], it
|
||||
//! can, via [`Pallet::deregister`].
|
||||
//!
|
||||
//! A deposit equal to [`Config::Deposit`] is collected for this process, and is returned in case a
|
||||
//! successful unstake occurs (`Event::Unstaked` signals that).
|
||||
//!
|
||||
//! Once processed, if successful, no additional fee for the checking process is taken, and the
|
||||
//! staker is instantly unbonded.
|
||||
//!
|
||||
//! If unsuccessful, meaning that the staker was exposed, the aforementioned deposit will be slashed
|
||||
//! for the amount of wasted work they have inflicted on the chain.
|
||||
//!
|
||||
//! All in all, this pallet is meant to provide an easy off-ramp for some stakers.
|
||||
//!
|
||||
//! ### Example
|
||||
//!
|
||||
//! 1. Fast-unstake with multiple participants in the queue.
|
||||
#![doc = docify::embed!("frame/fast-unstake/src/tests.rs", successful_multi_queue)]
|
||||
//!
|
||||
//! 2. Fast unstake failing because a nominator is exposed.
|
||||
#![doc = docify::embed!("frame/fast-unstake/src/tests.rs", exposed_nominator_cannot_unstake)]
|
||||
//!
|
||||
//! ## Pallet API
|
||||
//!
|
||||
//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
|
||||
//! including its configuration trait, dispatchables, storage items, events and errors.
|
||||
//!
|
||||
//! ## Low Level / Implementation Details
|
||||
//!
|
||||
//! This pallet works off the basis of `on_idle`, meaning that it provides no guarantee about when
|
||||
//! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of
|
||||
//! congestion, no FIFO ordering is provided.
|
||||
//!
|
||||
//! Stakers who are certain about NOT being exposed can register themselves with
|
||||
//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in
|
||||
//! the queue to be checked.
|
||||
//! A few important considerations can be concluded based on the `on_idle`-based implementation:
|
||||
//!
|
||||
//! Once queued, but not being actively processed, stakers can withdraw their request via
|
||||
//! [`Call::deregister`].
|
||||
//! * It is crucial for the weights of this pallet to be correct. The code inside
|
||||
//! [`Pallet::on_idle`] MUST be able to measure itself and report the remaining weight correctly
|
||||
//! after execution.
|
||||
//!
|
||||
//! Once queued, a staker wishing to unbond can perform no further action in pallet-staking. This is
|
||||
//! to prevent them from accidentally exposing themselves behind a validator etc.
|
||||
//! * If the weight measurement is incorrect, it can lead to perpetual overweight (consequently
|
||||
//! slow) blocks.
|
||||
//!
|
||||
//! Once processed, if successful, no additional fee for the checking process is taken, and the
|
||||
//! staker is instantly unbonded.
|
||||
//! * The amount of weight that `on_idle` consumes is a direct function of [`ErasToCheckPerBlock`].
|
||||
//!
|
||||
//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras
|
||||
//! they will end up being slashed for the amount of wasted work they have inflicted on the chian.
|
||||
//! * Thus, a correct value of [`ErasToCheckPerBlock`] (which can be set via [`Pallet::control`])
|
||||
//! should be chosen, such that a reasonable amount of weight is used `on_idle`. If
|
||||
//! [`ErasToCheckPerBlock`] is too large, `on_idle` will always conclude that it has not enough
|
||||
//! weight to proceed, and will early-return. Nonetheless, this should also be *safe* as long as
|
||||
//! the benchmarking/weights are *accurate*.
|
||||
//!
|
||||
//! * See the inline code-comments on `do_on_idle` (private) for more details.
|
||||
//!
|
||||
//! * For further safety, in case of any unforeseen errors, the pallet will emit
|
||||
//! [`Event::InternalError`] and set [`ErasToCheckPerBlock`] back to 0, which essentially means
|
||||
//! the pallet will halt/disable itself.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
|
||||
@@ -64,9 +127,15 @@ pub mod migrations;
|
||||
pub mod types;
|
||||
pub mod weights;
|
||||
|
||||
// some extra imports for docs to link properly.
|
||||
#[cfg(doc)]
|
||||
pub use frame_support::traits::Hooks;
|
||||
#[cfg(doc)]
|
||||
pub use sp_staking::StakingInterface;
|
||||
|
||||
/// The logging target of this pallet.
|
||||
pub const LOG_TARGET: &'static str = "runtime::fast-unstake";
|
||||
|
||||
// syntactic sugar for logging.
|
||||
#[macro_export]
|
||||
macro_rules! log {
|
||||
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
|
||||
@@ -94,16 +163,6 @@ pub mod pallet {
|
||||
#[cfg(feature = "try-runtime")]
|
||||
use sp_runtime::TryRuntimeError;
|
||||
|
||||
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
|
||||
#[codec(mel_bound(T: Config))]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct MaxChecking<T: Config>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> frame_support::traits::Get<u32> for MaxChecking<T> {
|
||||
fn get() -> u32 {
|
||||
T::Staking::bonding_duration() + 1
|
||||
}
|
||||
}
|
||||
|
||||
const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
|
||||
|
||||
#[pallet::pallet]
|
||||
@@ -125,7 +184,7 @@ pub mod pallet {
|
||||
#[pallet::constant]
|
||||
type Deposit: Get<BalanceOf<Self>>;
|
||||
|
||||
/// The origin that can control this pallet.
|
||||
/// The origin that can control this pallet, in other words invoke [`Pallet::control`].
|
||||
type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
|
||||
|
||||
/// Batch size.
|
||||
@@ -136,40 +195,45 @@ pub mod pallet {
|
||||
/// The access to staking functionality.
|
||||
type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>;
|
||||
|
||||
/// Maximum value for `ErasToCheckPerBlock`, checked in [`Pallet::control`].
|
||||
///
|
||||
/// This should be slightly bigger than the actual value in order to have accurate
|
||||
/// benchmarks.
|
||||
type MaxErasToCheckPerBlock: Get<u32>;
|
||||
|
||||
/// The weight information of this pallet.
|
||||
type WeightInfo: WeightInfo;
|
||||
|
||||
/// Maximum value for `ErasToCheckPerBlock`. This should be as close as possible, but more
|
||||
/// than the actual value, in order to have accurate benchmarks.
|
||||
type MaxErasToCheckPerBlock: Get<u32>;
|
||||
|
||||
/// Use only for benchmarking.
|
||||
#[cfg(feature = "runtime-benchmarks")]
|
||||
type MaxBackersPerValidator: Get<u32>;
|
||||
}
|
||||
|
||||
/// The current "head of the queue" being unstaked.
|
||||
///
|
||||
/// The head in itself can be a batch of up to [`Config::BatchSize`] stakers.
|
||||
#[pallet::storage]
|
||||
pub type Head<T: Config> = StorageValue<_, UnstakeRequest<T>, OptionQuery>;
|
||||
|
||||
/// The map of all accounts wishing to be unstaked.
|
||||
///
|
||||
/// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit.
|
||||
///
|
||||
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
|
||||
// Hasher: Twox safe since `AccountId` is a secure hash.
|
||||
#[pallet::storage]
|
||||
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf<T>>;
|
||||
|
||||
/// Number of eras to check per block.
|
||||
///
|
||||
/// If set to 0, this pallet does absolutely nothing.
|
||||
/// If set to 0, this pallet does absolutely nothing. Cannot be set to more than
|
||||
/// [`Config::MaxErasToCheckPerBlock`].
|
||||
///
|
||||
/// Based on the amount of weight available at `on_idle`, up to this many eras of a single
|
||||
/// nominator might be checked.
|
||||
/// Based on the amount of weight available at [`Pallet::on_idle`], up to this many eras are
|
||||
/// checked. The checking is represented by updating [`UnstakeRequest::checked`], which is
|
||||
/// stored in [`Head`].
|
||||
#[pallet::storage]
|
||||
#[pallet::getter(fn eras_to_check_per_block)]
|
||||
pub type ErasToCheckPerBlock<T: Config> = StorageValue<_, u32, ValueQuery>;
|
||||
|
||||
/// The events of this pallet.
|
||||
#[pallet::event]
|
||||
#[pallet::generate_deposit(pub(super) fn deposit_event)]
|
||||
pub enum Event<T: Config> {
|
||||
@@ -177,8 +241,6 @@ pub mod pallet {
|
||||
Unstaked { stash: T::AccountId, result: DispatchResult },
|
||||
/// A staker was slashed for requesting fast-unstake whilst being exposed.
|
||||
Slashed { stash: T::AccountId, amount: BalanceOf<T> },
|
||||
/// An internal error happened. Operations will be paused now.
|
||||
InternalError,
|
||||
/// A batch was partially checked for the given eras, but the process did not finish.
|
||||
BatchChecked { eras: Vec<EraIndex> },
|
||||
/// A batch of a given size was terminated.
|
||||
@@ -186,6 +248,8 @@ pub mod pallet {
|
||||
/// This is always follows by a number of `Unstaked` or `Slashed` events, marking the end
|
||||
/// of the batch. A new batch will be created upon next block.
|
||||
BatchFinished { size: u32 },
|
||||
/// An internal error happened. Operations will be paused now.
|
||||
InternalError,
|
||||
}
|
||||
|
||||
#[pallet::error]
|
||||
@@ -247,8 +311,12 @@ pub mod pallet {
|
||||
impl<T: Config> Pallet<T> {
|
||||
/// Register oneself for fast-unstake.
|
||||
///
|
||||
/// The dispatch origin of this call must be signed by the controller account, similar to
|
||||
/// `staking::unbond`.
|
||||
/// ## Dispatch Origin
|
||||
///
|
||||
/// The dispatch origin of this call must be *signed* by whoever is permitted to call
|
||||
/// unbond funds by the staking system. See [`Config::Staking`].
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// The stash associated with the origin must have no ongoing unlocking chunks. If
|
||||
/// successful, this will fully unbond and chill the stash. Then, it will enqueue the stash
|
||||
@@ -263,6 +331,10 @@ pub mod pallet {
|
||||
/// If the check fails, the stash remains chilled and waiting for being unbonded as in with
|
||||
/// the normal staking system, but they lose part of their unbonding chunks due to consuming
|
||||
/// the chain's resources.
|
||||
///
|
||||
/// ## Events
|
||||
///
|
||||
/// Some events from the staking and currency system might be emitted.
|
||||
#[pallet::call_index(0)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())]
|
||||
pub fn register_fast_unstake(origin: OriginFor<T>) -> DispatchResult {
|
||||
@@ -288,11 +360,22 @@ pub mod pallet {
|
||||
|
||||
/// Deregister oneself from the fast-unstake.
|
||||
///
|
||||
/// ## Dispatch Origin
|
||||
///
|
||||
/// The dispatch origin of this call must be *signed* by whoever is permitted to call
|
||||
/// unbond funds by the staking system. See [`Config::Staking`].
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// This is useful if one is registered, they are still waiting, and they change their mind.
|
||||
///
|
||||
/// Note that the associated stash is still fully unbonded and chilled as a consequence of
|
||||
/// calling `register_fast_unstake`. This should probably be followed by a call to
|
||||
/// `Staking::rebond`.
|
||||
/// calling [`Pallet::register_fast_unstake`]. Therefore, this should probably be followed
|
||||
/// by a call to `rebond` in the staking system.
|
||||
///
|
||||
/// ## Events
|
||||
///
|
||||
/// Some events from the staking and currency system might be emitted.
|
||||
#[pallet::call_index(1)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::deregister())]
|
||||
pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
|
||||
@@ -318,7 +401,17 @@ pub mod pallet {
|
||||
|
||||
/// Control the operation of this pallet.
|
||||
///
|
||||
/// Dispatch origin must be signed by the [`Config::ControlOrigin`].
|
||||
/// ## Dispatch Origin
|
||||
///
|
||||
/// The dispatch origin of this call must be [`Config::ControlOrigin`].
|
||||
///
|
||||
/// ## Details
|
||||
///
|
||||
/// Can set the number of eras to check per block, and potentially other admin work.
|
||||
///
|
||||
/// ## Events
|
||||
///
|
||||
/// No events are emitted from this dispatch.
|
||||
#[pallet::call_index(2)]
|
||||
#[pallet::weight(<T as Config>::WeightInfo::control())]
|
||||
pub fn control(origin: OriginFor<T>, eras_to_check: EraIndex) -> DispatchResult {
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
use super::*;
|
||||
use crate::{mock::*, types::*, Event};
|
||||
use frame_support::{assert_noop, assert_ok, bounded_vec, pallet_prelude::*, traits::Currency};
|
||||
use frame_support::{pallet_prelude::*, testing_prelude::*, traits::Currency};
|
||||
use pallet_staking::{CurrentEra, RewardDestination};
|
||||
|
||||
use sp_runtime::traits::BadOrigin;
|
||||
@@ -303,6 +303,7 @@ mod on_idle {
|
||||
});
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn successful_multi_queue() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
@@ -356,6 +357,7 @@ mod on_idle {
|
||||
});
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn successful_unstake() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
@@ -693,6 +695,7 @@ mod on_idle {
|
||||
});
|
||||
}
|
||||
|
||||
#[docify::export]
|
||||
#[test]
|
||||
fn exposed_nominator_cannot_unstake() {
|
||||
ExtBuilder::default().build_and_execute(|| {
|
||||
|
||||
@@ -17,25 +17,41 @@
|
||||
|
||||
//! Types used in the Fast Unstake pallet.
|
||||
|
||||
use crate::{Config, MaxChecking};
|
||||
use crate::Config;
|
||||
use codec::{Decode, Encode, MaxEncodedLen};
|
||||
use frame_support::{
|
||||
traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
|
||||
};
|
||||
use scale_info::TypeInfo;
|
||||
use sp_staking::EraIndex;
|
||||
use sp_staking::{EraIndex, StakingInterface};
|
||||
use sp_std::prelude::*;
|
||||
|
||||
pub type BalanceOf<T> =
|
||||
/// Maximum number of eras that we might check for a single staker.
|
||||
///
|
||||
/// In effect, it is the bonding duration, coming from [`Config::Staking`], plus one.
|
||||
#[derive(scale_info::TypeInfo, codec::Encode, codec::Decode, codec::MaxEncodedLen)]
|
||||
#[codec(mel_bound(T: Config))]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct MaxChecking<T: Config>(sp_std::marker::PhantomData<T>);
|
||||
impl<T: Config> frame_support::traits::Get<u32> for MaxChecking<T> {
|
||||
fn get() -> u32 {
|
||||
T::Staking::bonding_duration() + 1
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type BalanceOf<T> =
|
||||
<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
|
||||
/// An unstake request.
|
||||
///
|
||||
/// This is stored in [`crate::Head`] storage item and points to the current unstake request that is
|
||||
/// being processed.
|
||||
#[derive(
|
||||
Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen,
|
||||
)]
|
||||
#[scale_info(skip_type_params(T))]
|
||||
pub struct UnstakeRequest<T: Config> {
|
||||
/// This list of stashes being processed in this request, and their corresponding deposit.
|
||||
pub(crate) stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize>,
|
||||
/// This list of stashes are being processed in this request, and their corresponding deposit.
|
||||
pub stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize>,
|
||||
/// The list of eras for which they have been checked.
|
||||
pub(crate) checked: BoundedVec<EraIndex, MaxChecking<T>>,
|
||||
pub checked: BoundedVec<EraIndex, MaxChecking<T>>,
|
||||
}
|
||||
|
||||
@@ -113,7 +113,13 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
}
|
||||
debug_assert_eq!(fn_weight.len(), methods.len());
|
||||
|
||||
let fn_doc = methods.iter().map(|method| &method.docs).collect::<Vec<_>>();
|
||||
let fn_doc = methods
|
||||
.iter()
|
||||
.map(|method| {
|
||||
let reference = format!("See [`Pallet::{}`].", method.name);
|
||||
quote!(#reference)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let args_name = methods
|
||||
.iter()
|
||||
@@ -175,9 +181,8 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
let default_docs = [syn::parse_quote!(
|
||||
r"Contains one variant per dispatchable that can be called by an extrinsic."
|
||||
)];
|
||||
let default_docs =
|
||||
[syn::parse_quote!(r"Contains a variant per dispatchable extrinsic that this pallet has.")];
|
||||
let docs = if docs.is_empty() { &default_docs[..] } else { &docs[..] };
|
||||
|
||||
let maybe_compile_error = if def.call.is_none() {
|
||||
@@ -274,7 +279,7 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
#frame_support::Never,
|
||||
),
|
||||
#(
|
||||
#( #[doc = #fn_doc] )*
|
||||
#[doc = #fn_doc]
|
||||
#[codec(index = #call_index)]
|
||||
#fn_name {
|
||||
#(
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
// limitations under the License.
|
||||
|
||||
use crate::pallet::Def;
|
||||
use frame_support_procedural_tools::get_doc_literals;
|
||||
|
||||
///
|
||||
/// * Generate default rust doc
|
||||
@@ -31,15 +30,19 @@ pub fn expand_config(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
}
|
||||
};
|
||||
|
||||
if get_doc_literals(&config_item.attrs).is_empty() {
|
||||
config_item.attrs.push(syn::parse_quote!(
|
||||
#[doc = r"
|
||||
Configuration trait of this pallet.
|
||||
config_item.attrs.insert(
|
||||
0,
|
||||
syn::parse_quote!(
|
||||
#[doc = r"Configuration trait of this pallet.
|
||||
|
||||
Implement this type for a runtime in order to customize this pallet.
|
||||
"]
|
||||
));
|
||||
}
|
||||
The main purpose of this trait is to act as an interface between this pallet and the runtime in
|
||||
which it is embedded in. A type, function, or constant in this trait is essentially left to be
|
||||
configured by the runtime that includes this pallet.
|
||||
|
||||
Consequently, a runtime that wants to include this pallet must implement this trait."
|
||||
]
|
||||
),
|
||||
);
|
||||
|
||||
Default::default()
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ use proc_macro2::Span;
|
||||
use crate::pallet::Def;
|
||||
|
||||
pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
let storage_names = def.storages.iter().map(|storage| &storage.ident);
|
||||
let storage_docs = def.storages.iter().map(|storage| &storage.docs);
|
||||
let dispatchables = if let Some(call_def) = &def.call {
|
||||
let type_impl_generics = def.type_impl_generics(Span::call_site());
|
||||
call_def
|
||||
@@ -35,17 +33,16 @@ pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
.map(|(_, arg_name, arg_type)| quote::quote!( #arg_name: #arg_type, ))
|
||||
.collect::<proc_macro2::TokenStream>();
|
||||
let docs = &method.docs;
|
||||
let line_2 =
|
||||
format!(" designed to document the [`{}`][`Call::{}`] variant of", name, name);
|
||||
|
||||
let real = format!(" [`Pallet::{}`].", name);
|
||||
quote::quote!(
|
||||
#( #[doc = #docs] )*
|
||||
///
|
||||
/// ---
|
||||
/// # Warning: Doc-Only
|
||||
///
|
||||
/// NOTE: This function is an automatically generated, doc only, uncallable stub.
|
||||
#[ doc = #line_2 ]
|
||||
/// the pallet [`Call`] enum. You should not attempt to call this function
|
||||
/// directly.
|
||||
/// This function is an automatically generated, and is doc-only, uncallable
|
||||
/// stub. See the real version in
|
||||
#[ doc = #real ]
|
||||
pub fn #name<#type_impl_generics>(#args) { unreachable!(); }
|
||||
)
|
||||
})
|
||||
@@ -54,22 +51,49 @@ pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote!()
|
||||
};
|
||||
|
||||
let storage_types = def
|
||||
.storages
|
||||
.iter()
|
||||
.map(|storage| {
|
||||
let storage_name = &storage.ident;
|
||||
let storage_type_docs = &storage.docs;
|
||||
let real = format!("[`pallet::{}`].", storage_name);
|
||||
quote::quote!(
|
||||
#( #[doc = #storage_type_docs] )*
|
||||
///
|
||||
/// # Warning: Doc-Only
|
||||
///
|
||||
/// This type is automatically generated, and is doc-only. See the real version in
|
||||
#[ doc = #real ]
|
||||
pub struct #storage_name();
|
||||
)
|
||||
})
|
||||
.collect::<proc_macro2::TokenStream>();
|
||||
|
||||
quote::quote!(
|
||||
/// Auto-generated docs-only module listing all defined storage types for this pallet.
|
||||
/// Note that members of this module cannot be used directly and are only provided for
|
||||
/// documentation purposes.
|
||||
/// Auto-generated docs-only module listing all (public and private) defined storage types
|
||||
/// for this pallet.
|
||||
///
|
||||
/// # Warning: Doc-Only
|
||||
///
|
||||
/// Members of this module cannot be used directly and are only provided for documentation
|
||||
/// purposes.
|
||||
///
|
||||
/// To see the actual storage type, find a struct with the same name at the root of the
|
||||
/// pallet, in the list of [*Type Definitions*](../index.html#types).
|
||||
#[cfg(doc)]
|
||||
pub mod storage_types {
|
||||
use super::*;
|
||||
#(
|
||||
#( #[doc = #storage_docs] )*
|
||||
pub struct #storage_names();
|
||||
)*
|
||||
#storage_types
|
||||
}
|
||||
|
||||
/// Auto-generated docs-only module listing all defined dispatchables for this pallet.
|
||||
/// Note that members of this module cannot be used directly and are only provided for
|
||||
/// documentation purposes.
|
||||
///
|
||||
/// # Warning: Doc-Only
|
||||
///
|
||||
/// Members of this module cannot be used directly and are only provided for documentation
|
||||
/// purposes. To see the real version of each dispatchable, look for them in [`Pallet`] or
|
||||
/// [`Call`].
|
||||
#[cfg(doc)]
|
||||
pub mod dispatchables {
|
||||
use super::*;
|
||||
|
||||
@@ -110,10 +110,7 @@ pub fn expand_error(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
|
||||
if get_doc_literals(&error_item.attrs).is_empty() {
|
||||
error_item.attrs.push(syn::parse_quote!(
|
||||
#[doc = r"
|
||||
Custom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/)
|
||||
of this pallet.
|
||||
"]
|
||||
#[doc = "The `Error` enum of this pallet."]
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -97,12 +97,9 @@ pub fn expand_event(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
}
|
||||
|
||||
if get_doc_literals(&event_item.attrs).is_empty() {
|
||||
event_item.attrs.push(syn::parse_quote!(
|
||||
#[doc = r"
|
||||
The [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted
|
||||
by this pallet.
|
||||
"]
|
||||
));
|
||||
event_item
|
||||
.attrs
|
||||
.push(syn::parse_quote!(#[doc = "The `Event` enum of this pallet"]));
|
||||
}
|
||||
|
||||
// derive some traits because system event require Clone, FullCodec, Eq, PartialEq and Debug
|
||||
|
||||
@@ -36,7 +36,6 @@ mod type_value;
|
||||
mod validate_unsigned;
|
||||
|
||||
use crate::pallet::Def;
|
||||
use frame_support_procedural_tools::get_doc_literals;
|
||||
use quote::ToTokens;
|
||||
|
||||
/// Merge where clause together, `where` token span is taken from the first not none one.
|
||||
@@ -75,16 +74,24 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream {
|
||||
let tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def);
|
||||
let doc_only = doc_only::expand_doc_only(&mut def);
|
||||
|
||||
if get_doc_literals(&def.item.attrs).is_empty() {
|
||||
def.item.attrs.push(syn::parse_quote!(
|
||||
#[doc = r"
|
||||
The module that hosts all the
|
||||
[FRAME](https://docs.substrate.io/main-docs/build/events-errors/)
|
||||
types needed to add this pallet to a
|
||||
runtime.
|
||||
"]
|
||||
));
|
||||
}
|
||||
def.item.attrs.insert(
|
||||
0,
|
||||
syn::parse_quote!(
|
||||
#[doc = r"The `pallet` module in each FRAME pallet hosts the most important items needed
|
||||
to construct this pallet.
|
||||
|
||||
The main components of this pallet are:
|
||||
- [`Pallet`], which implements all of the dispatchable extrinsics of the pallet, among
|
||||
other public functions.
|
||||
- The subset of the functions that are dispatchable can be identified either in the
|
||||
[`dispatchables`] module or in the [`Call`] enum.
|
||||
- [`storage_types`], which contains the list of all types that are representing a
|
||||
storage item. Otherwise, all storage items are listed among [*Type Definitions*](#types).
|
||||
- [`Config`], which contains the configuration trait of this pallet.
|
||||
- [`Event`] and [`Error`], which are listed among the [*Enums*](#enums).
|
||||
"]
|
||||
),
|
||||
);
|
||||
|
||||
let new_items = quote::quote!(
|
||||
#metadata_docs
|
||||
|
||||
@@ -62,8 +62,8 @@ pub fn expand_pallet_struct(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
if get_doc_literals(&pallet_item.attrs).is_empty() {
|
||||
pallet_item.attrs.push(syn::parse_quote!(
|
||||
#[doc = r"
|
||||
The [pallet](https://docs.substrate.io/reference/frame-pallets/#pallets) implementing
|
||||
the on-chain logic.
|
||||
The `Pallet` struct, the main type that implements traits and standalone
|
||||
functions within the pallet.
|
||||
"]
|
||||
));
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ use crate::{
|
||||
Def,
|
||||
},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use quote::ToTokens;
|
||||
use std::{collections::HashMap, ops::IndexMut};
|
||||
use syn::spanned::Spanned;
|
||||
@@ -310,6 +311,65 @@ pub fn process_generics(def: &mut Def) -> syn::Result<Vec<ResultOnEmptyStructMet
|
||||
Ok(on_empty_struct_metadata)
|
||||
}
|
||||
|
||||
fn augment_final_docs(def: &mut Def) {
|
||||
// expand the docs with a new line showing the storage type (value, map, double map, etc), and
|
||||
// the key/value type(s).
|
||||
let mut push_string_literal = |doc_line: &str, storage: &mut StorageDef| {
|
||||
let item = &mut def.item.content.as_mut().expect("Checked by def").1[storage.index];
|
||||
let typ_item = match item {
|
||||
syn::Item::Type(t) => t,
|
||||
_ => unreachable!("Checked by def"),
|
||||
};
|
||||
typ_item.attrs.push(syn::parse_quote!(#[doc = ""]));
|
||||
typ_item.attrs.push(syn::parse_quote!(#[doc = #doc_line]));
|
||||
};
|
||||
def.storages.iter_mut().for_each(|storage| match &storage.metadata {
|
||||
Metadata::Value { value } => {
|
||||
let doc_line = format!(
|
||||
"Storage type is [`StorageValue`] with value type `{}`.",
|
||||
value.to_token_stream()
|
||||
);
|
||||
push_string_literal(&doc_line, storage);
|
||||
},
|
||||
Metadata::Map { key, value } => {
|
||||
let doc_line = format!(
|
||||
"Storage type is [`StorageMap`] with key type `{}` and value type `{}`.",
|
||||
key.to_token_stream(),
|
||||
value.to_token_stream()
|
||||
);
|
||||
push_string_literal(&doc_line, storage);
|
||||
},
|
||||
Metadata::DoubleMap { key1, key2, value } => {
|
||||
let doc_line = format!(
|
||||
"Storage type is [`StorageDoubleMap`] with key1 type {}, key2 type {} and value type {}.",
|
||||
key1.to_token_stream(),
|
||||
key2.to_token_stream(),
|
||||
value.to_token_stream()
|
||||
);
|
||||
push_string_literal(&doc_line, storage);
|
||||
},
|
||||
Metadata::NMap { keys, value, .. } => {
|
||||
let doc_line = format!(
|
||||
"Storage type is [`StorageNMap`] with keys type ({}) and value type {}.",
|
||||
keys.iter()
|
||||
.map(|k| k.to_token_stream().to_string())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", "),
|
||||
value.to_token_stream()
|
||||
);
|
||||
push_string_literal(&doc_line, storage);
|
||||
},
|
||||
Metadata::CountedMap { key, value } => {
|
||||
let doc_line = format!(
|
||||
"Storage type is [`CountedStorageMap`] with key type {} and value type {}.",
|
||||
key.to_token_stream(),
|
||||
value.to_token_stream()
|
||||
);
|
||||
push_string_literal(&doc_line, storage);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
///
|
||||
/// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name
|
||||
/// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait.
|
||||
@@ -323,6 +383,8 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
Err(e) => return e.into_compile_error(),
|
||||
};
|
||||
|
||||
augment_final_docs(def);
|
||||
|
||||
// Check for duplicate prefixes
|
||||
let mut prefix_set = HashMap::new();
|
||||
let mut errors = def
|
||||
@@ -365,10 +427,6 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
if let Some(getter) = &storage.getter {
|
||||
let completed_where_clause =
|
||||
super::merge_where_clauses(&[&storage.where_clause, &def.config.where_clause]);
|
||||
let docs = storage
|
||||
.docs
|
||||
.iter()
|
||||
.map(|d| quote::quote_spanned!(storage.attr_span => #[doc = #d]));
|
||||
|
||||
let ident = &storage.ident;
|
||||
let gen = &def.type_use_generics(storage.attr_span);
|
||||
@@ -378,6 +436,13 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
|
||||
let cfg_attrs = &storage.cfg_attrs;
|
||||
|
||||
// If the storage item is public, just link to it rather than copy-pasting the docs.
|
||||
let getter_doc_line = if matches!(storage.vis, syn::Visibility::Public(_)) {
|
||||
format!("An auto-generated getter for [`{}`].", storage.ident)
|
||||
} else {
|
||||
storage.docs.iter().map(|d| d.into_token_stream().to_string()).join("\n")
|
||||
};
|
||||
|
||||
match &storage.metadata {
|
||||
Metadata::Value { value } => {
|
||||
let query = match storage.query_kind.as_ref().expect("Checked by def") {
|
||||
@@ -394,7 +459,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote_spanned!(storage.attr_span =>
|
||||
#(#cfg_attrs)*
|
||||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
|
||||
#( #docs )*
|
||||
#[doc = #getter_doc_line]
|
||||
pub fn #getter() -> #query {
|
||||
<
|
||||
#full_ident as #frame_support::storage::StorageValue<#value>
|
||||
@@ -418,7 +483,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote_spanned!(storage.attr_span =>
|
||||
#(#cfg_attrs)*
|
||||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
|
||||
#( #docs )*
|
||||
#[doc = #getter_doc_line]
|
||||
pub fn #getter<KArg>(k: KArg) -> #query where
|
||||
KArg: #frame_support::codec::EncodeLike<#key>,
|
||||
{
|
||||
@@ -444,7 +509,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote_spanned!(storage.attr_span =>
|
||||
#(#cfg_attrs)*
|
||||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
|
||||
#( #docs )*
|
||||
#[doc = #getter_doc_line]
|
||||
pub fn #getter<KArg>(k: KArg) -> #query where
|
||||
KArg: #frame_support::codec::EncodeLike<#key>,
|
||||
{
|
||||
@@ -470,7 +535,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote_spanned!(storage.attr_span =>
|
||||
#(#cfg_attrs)*
|
||||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
|
||||
#( #docs )*
|
||||
#[doc = #getter_doc_line]
|
||||
pub fn #getter<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> #query where
|
||||
KArg1: #frame_support::codec::EncodeLike<#key1>,
|
||||
KArg2: #frame_support::codec::EncodeLike<#key2>,
|
||||
@@ -498,7 +563,7 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
|
||||
quote::quote_spanned!(storage.attr_span =>
|
||||
#(#cfg_attrs)*
|
||||
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
|
||||
#( #docs )*
|
||||
#[doc = #getter_doc_line]
|
||||
pub fn #getter<KArg>(key: KArg) -> #query
|
||||
where
|
||||
KArg: #frame_support::storage::types::EncodeLikeTuple<
|
||||
|
||||
@@ -1520,6 +1520,17 @@ pub mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// Prelude to be used for pallet testing, for ease of use.
|
||||
#[cfg(feature = "std")]
|
||||
pub mod testing_prelude {
|
||||
pub use super::{
|
||||
assert_err, assert_err_ignore_postinfo, assert_err_with_weight, assert_error_encoded_size,
|
||||
assert_noop, assert_ok, assert_storage_noop, bounded_btree_map, bounded_vec,
|
||||
parameter_types, traits::Get,
|
||||
};
|
||||
pub use sp_arithmetic::assert_eq_error_rate;
|
||||
}
|
||||
|
||||
/// Prelude to be used alongside pallet macro, for ease of use.
|
||||
pub mod pallet_prelude {
|
||||
pub use crate::{
|
||||
|
||||
@@ -34,7 +34,7 @@ pub mod pallet {
|
||||
#[pallet::pallet]
|
||||
pub struct Pallet<T>(_);
|
||||
|
||||
/// The configuration trait
|
||||
/// The configuration trait.
|
||||
#[pallet::config]
|
||||
#[pallet::disable_frame_system_supertrait_check]
|
||||
pub trait Config: 'static + Eq + Clone {
|
||||
|
||||
Reference in New Issue
Block a user