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:
Kian Paimani
2023-05-29 19:44:42 +02:00
committed by GitHub
parent 50cf22b0bb
commit b8bca85e9d
15 changed files with 378 additions and 121 deletions
+34
View File
@@ -1289,6 +1289,12 @@ dependencies = [
"unicode-width", "unicode-width",
] ]
[[package]]
name = "common-path"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2382f75942f4b3be3690fe4f86365e9c853c1587d6ee58212cebf6e2a9ccd101"
[[package]] [[package]]
name = "concurrent-queue" name = "concurrent-queue"
version = "2.2.0" version = "2.2.0"
@@ -2028,6 +2034,33 @@ version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 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]] [[package]]
name = "downcast" name = "downcast"
version = "0.11.0" version = "0.11.0"
@@ -6418,6 +6451,7 @@ dependencies = [
name = "pallet-fast-unstake" name = "pallet-fast-unstake"
version = "4.0.0-dev" version = "4.0.0-dev"
dependencies = [ dependencies = [
"docify",
"frame-benchmarking", "frame-benchmarking",
"frame-election-provider-support", "frame-election-provider-support",
"frame-support", "frame-support",
+2
View File
@@ -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" } frame-benchmarking = { version = "4.0.0-dev", default-features = false, optional = true, path = "../benchmarking" }
docify = "0.1.13"
[dev-dependencies] [dev-dependencies]
pallet-staking-reward-curve = { version = "4.0.0-dev", path = "../staking/reward-curve" } 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" } sp-core = { version = "8.0.0", default-features = false, path = "../../primitives/core" }
+142 -49
View File
@@ -15,37 +15,100 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // 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 //! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
//! validators in the last `BondingDuration` days"), then they can register themselves in this //! [![polkadot]](https://polkadot.network)
//! pallet, unstake faster than having to wait an entire bonding duration.
//! //!
//! Appearing in the exposure of a validator means being exposed equal to that validator from the //! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
//! point of view of the staking system. This usually means earning rewards with the validator, and //! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
//! also being at the risk of slashing with the validator. This is equivalent to the "Active //!
//! Nominator" role explained in the //! # Fast Unstake Pallet
//! [February Staking Update](https://polkadot.network/blog/staking-update-february-2022/). //!
//! 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 //! 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 //! it will succeed, if at all. Moreover, the queue implementation is unordered. In case of
//! congestion, no FIFO ordering is provided. //! congestion, no FIFO ordering is provided.
//! //!
//! Stakers who are certain about NOT being exposed can register themselves with //! A few important considerations can be concluded based on the `on_idle`-based implementation:
//! [`Call::register_fast_unstake`]. This will chill, and fully unbond the staker, and place them in
//! the queue to be checked.
//! //!
//! Once queued, but not being actively processed, stakers can withdraw their request via //! * It is crucial for the weights of this pallet to be correct. The code inside
//! [`Call::deregister`]. //! [`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 //! * If the weight measurement is incorrect, it can lead to perpetual overweight (consequently
//! to prevent them from accidentally exposing themselves behind a validator etc. //! slow) blocks.
//! //!
//! Once processed, if successful, no additional fee for the checking process is taken, and the //! * The amount of weight that `on_idle` consumes is a direct function of [`ErasToCheckPerBlock`].
//! staker is instantly unbonded.
//! //!
//! If unsuccessful, meaning that the staker was exposed sometime in the last `BondingDuration` eras //! * Thus, a correct value of [`ErasToCheckPerBlock`] (which can be set via [`Pallet::control`])
//! they will end up being slashed for the amount of wasted work they have inflicted on the chian. //! 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)] #![cfg_attr(not(feature = "std"), no_std)]
@@ -64,9 +127,15 @@ pub mod migrations;
pub mod types; pub mod types;
pub mod weights; 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"; pub const LOG_TARGET: &'static str = "runtime::fast-unstake";
// syntactic sugar for logging.
#[macro_export] #[macro_export]
macro_rules! log { macro_rules! log {
($level:tt, $patter:expr $(, $values:expr)* $(,)?) => { ($level:tt, $patter:expr $(, $values:expr)* $(,)?) => {
@@ -94,16 +163,6 @@ pub mod pallet {
#[cfg(feature = "try-runtime")] #[cfg(feature = "try-runtime")]
use sp_runtime::TryRuntimeError; 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); const STORAGE_VERSION: StorageVersion = StorageVersion::new(1);
#[pallet::pallet] #[pallet::pallet]
@@ -125,7 +184,7 @@ pub mod pallet {
#[pallet::constant] #[pallet::constant]
type Deposit: Get<BalanceOf<Self>>; 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>; type ControlOrigin: frame_support::traits::EnsureOrigin<Self::RuntimeOrigin>;
/// Batch size. /// Batch size.
@@ -136,40 +195,45 @@ pub mod pallet {
/// The access to staking functionality. /// The access to staking functionality.
type Staking: StakingInterface<Balance = BalanceOf<Self>, AccountId = Self::AccountId>; 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. /// The weight information of this pallet.
type WeightInfo: WeightInfo; 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. /// Use only for benchmarking.
#[cfg(feature = "runtime-benchmarks")] #[cfg(feature = "runtime-benchmarks")]
type MaxBackersPerValidator: Get<u32>; type MaxBackersPerValidator: Get<u32>;
} }
/// The current "head of the queue" being unstaked. /// The current "head of the queue" being unstaked.
///
/// The head in itself can be a batch of up to [`Config::BatchSize`] stakers.
#[pallet::storage] #[pallet::storage]
pub type Head<T: Config> = StorageValue<_, UnstakeRequest<T>, OptionQuery>; pub type Head<T: Config> = StorageValue<_, UnstakeRequest<T>, OptionQuery>;
/// The map of all accounts wishing to be unstaked. /// The map of all accounts wishing to be unstaked.
/// ///
/// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit. /// Keeps track of `AccountId` wishing to unstake and it's corresponding deposit.
/// // Hasher: Twox safe since `AccountId` is a secure hash.
/// TWOX-NOTE: SAFE since `AccountId` is a secure hash.
#[pallet::storage] #[pallet::storage]
pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf<T>>; pub type Queue<T: Config> = CountedStorageMap<_, Twox64Concat, T::AccountId, BalanceOf<T>>;
/// Number of eras to check per block. /// 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 /// Based on the amount of weight available at [`Pallet::on_idle`], up to this many eras are
/// nominator might be checked. /// checked. The checking is represented by updating [`UnstakeRequest::checked`], which is
/// stored in [`Head`].
#[pallet::storage] #[pallet::storage]
#[pallet::getter(fn eras_to_check_per_block)]
pub type ErasToCheckPerBlock<T: Config> = StorageValue<_, u32, ValueQuery>; pub type ErasToCheckPerBlock<T: Config> = StorageValue<_, u32, ValueQuery>;
/// The events of this pallet.
#[pallet::event] #[pallet::event]
#[pallet::generate_deposit(pub(super) fn deposit_event)] #[pallet::generate_deposit(pub(super) fn deposit_event)]
pub enum Event<T: Config> { pub enum Event<T: Config> {
@@ -177,8 +241,6 @@ pub mod pallet {
Unstaked { stash: T::AccountId, result: DispatchResult }, Unstaked { stash: T::AccountId, result: DispatchResult },
/// A staker was slashed for requesting fast-unstake whilst being exposed. /// A staker was slashed for requesting fast-unstake whilst being exposed.
Slashed { stash: T::AccountId, amount: BalanceOf<T> }, 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. /// A batch was partially checked for the given eras, but the process did not finish.
BatchChecked { eras: Vec<EraIndex> }, BatchChecked { eras: Vec<EraIndex> },
/// A batch of a given size was terminated. /// 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 /// 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. /// of the batch. A new batch will be created upon next block.
BatchFinished { size: u32 }, BatchFinished { size: u32 },
/// An internal error happened. Operations will be paused now.
InternalError,
} }
#[pallet::error] #[pallet::error]
@@ -247,8 +311,12 @@ pub mod pallet {
impl<T: Config> Pallet<T> { impl<T: Config> Pallet<T> {
/// Register oneself for fast-unstake. /// Register oneself for fast-unstake.
/// ///
/// The dispatch origin of this call must be signed by the controller account, similar to /// ## Dispatch Origin
/// `staking::unbond`. ///
/// 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 /// 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 /// 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 /// 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 normal staking system, but they lose part of their unbonding chunks due to consuming
/// the chain's resources. /// the chain's resources.
///
/// ## Events
///
/// Some events from the staking and currency system might be emitted.
#[pallet::call_index(0)] #[pallet::call_index(0)]
#[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())] #[pallet::weight(<T as Config>::WeightInfo::register_fast_unstake())]
pub fn register_fast_unstake(origin: OriginFor<T>) -> DispatchResult { pub fn register_fast_unstake(origin: OriginFor<T>) -> DispatchResult {
@@ -288,11 +360,22 @@ pub mod pallet {
/// Deregister oneself from the fast-unstake. /// 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. /// 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 /// 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 /// calling [`Pallet::register_fast_unstake`]. Therefore, this should probably be followed
/// `Staking::rebond`. /// 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::call_index(1)]
#[pallet::weight(<T as Config>::WeightInfo::deregister())] #[pallet::weight(<T as Config>::WeightInfo::deregister())]
pub fn deregister(origin: OriginFor<T>) -> DispatchResult { pub fn deregister(origin: OriginFor<T>) -> DispatchResult {
@@ -318,7 +401,17 @@ pub mod pallet {
/// Control the operation of this 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::call_index(2)]
#[pallet::weight(<T as Config>::WeightInfo::control())] #[pallet::weight(<T as Config>::WeightInfo::control())]
pub fn control(origin: OriginFor<T>, eras_to_check: EraIndex) -> DispatchResult { pub fn control(origin: OriginFor<T>, eras_to_check: EraIndex) -> DispatchResult {
+4 -1
View File
@@ -19,7 +19,7 @@
use super::*; use super::*;
use crate::{mock::*, types::*, Event}; 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 pallet_staking::{CurrentEra, RewardDestination};
use sp_runtime::traits::BadOrigin; use sp_runtime::traits::BadOrigin;
@@ -303,6 +303,7 @@ mod on_idle {
}); });
} }
#[docify::export]
#[test] #[test]
fn successful_multi_queue() { fn successful_multi_queue() {
ExtBuilder::default().build_and_execute(|| { ExtBuilder::default().build_and_execute(|| {
@@ -356,6 +357,7 @@ mod on_idle {
}); });
} }
#[docify::export]
#[test] #[test]
fn successful_unstake() { fn successful_unstake() {
ExtBuilder::default().build_and_execute(|| { ExtBuilder::default().build_and_execute(|| {
@@ -693,6 +695,7 @@ mod on_idle {
}); });
} }
#[docify::export]
#[test] #[test]
fn exposed_nominator_cannot_unstake() { fn exposed_nominator_cannot_unstake() {
ExtBuilder::default().build_and_execute(|| { ExtBuilder::default().build_and_execute(|| {
+22 -6
View File
@@ -17,25 +17,41 @@
//! Types used in the Fast Unstake pallet. //! Types used in the Fast Unstake pallet.
use crate::{Config, MaxChecking}; use crate::Config;
use codec::{Decode, Encode, MaxEncodedLen}; use codec::{Decode, Encode, MaxEncodedLen};
use frame_support::{ use frame_support::{
traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound, traits::Currency, BoundedVec, EqNoBound, PartialEqNoBound, RuntimeDebugNoBound,
}; };
use scale_info::TypeInfo; use scale_info::TypeInfo;
use sp_staking::EraIndex; use sp_staking::{EraIndex, StakingInterface};
use sp_std::prelude::*; 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; <<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
/// An unstake request. /// An unstake request.
///
/// This is stored in [`crate::Head`] storage item and points to the current unstake request that is
/// being processed.
#[derive( #[derive(
Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen, Encode, Decode, EqNoBound, PartialEqNoBound, Clone, TypeInfo, RuntimeDebugNoBound, MaxEncodedLen,
)] )]
#[scale_info(skip_type_params(T))] #[scale_info(skip_type_params(T))]
pub struct UnstakeRequest<T: Config> { pub struct UnstakeRequest<T: Config> {
/// This list of stashes being processed in this request, and their corresponding deposit. /// This list of stashes are being processed in this request, and their corresponding deposit.
pub(crate) stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize>, pub stashes: BoundedVec<(T::AccountId, BalanceOf<T>), T::BatchSize>,
/// The list of eras for which they have been checked. /// 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()); 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 let args_name = methods
.iter() .iter()
@@ -175,9 +181,8 @@ pub fn expand_call(def: &mut Def) -> proc_macro2::TokenStream {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}); });
let default_docs = [syn::parse_quote!( let default_docs =
r"Contains one variant per dispatchable that can be called by an extrinsic." [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 docs = if docs.is_empty() { &default_docs[..] } else { &docs[..] };
let maybe_compile_error = if def.call.is_none() { 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, #frame_support::Never,
), ),
#( #(
#( #[doc = #fn_doc] )* #[doc = #fn_doc]
#[codec(index = #call_index)] #[codec(index = #call_index)]
#fn_name { #fn_name {
#( #(
@@ -16,7 +16,6 @@
// limitations under the License. // limitations under the License.
use crate::pallet::Def; use crate::pallet::Def;
use frame_support_procedural_tools::get_doc_literals;
/// ///
/// * Generate default rust doc /// * 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.insert(
config_item.attrs.push(syn::parse_quote!( 0,
#[doc = r" syn::parse_quote!(
Configuration trait of this pallet. #[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() Default::default()
} }
@@ -20,8 +20,6 @@ use proc_macro2::Span;
use crate::pallet::Def; use crate::pallet::Def;
pub fn expand_doc_only(def: &mut Def) -> proc_macro2::TokenStream { 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 dispatchables = if let Some(call_def) = &def.call {
let type_impl_generics = def.type_impl_generics(Span::call_site()); let type_impl_generics = def.type_impl_generics(Span::call_site());
call_def 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, )) .map(|(_, arg_name, arg_type)| quote::quote!( #arg_name: #arg_type, ))
.collect::<proc_macro2::TokenStream>(); .collect::<proc_macro2::TokenStream>();
let docs = &method.docs; let docs = &method.docs;
let line_2 =
format!(" designed to document the [`{}`][`Call::{}`] variant of", name, name); let real = format!(" [`Pallet::{}`].", name);
quote::quote!( quote::quote!(
#( #[doc = #docs] )* #( #[doc = #docs] )*
/// ///
/// --- /// # Warning: Doc-Only
/// ///
/// NOTE: This function is an automatically generated, doc only, uncallable stub. /// This function is an automatically generated, and is doc-only, uncallable
#[ doc = #line_2 ] /// stub. See the real version in
/// the pallet [`Call`] enum. You should not attempt to call this function #[ doc = #real ]
/// directly.
pub fn #name<#type_impl_generics>(#args) { unreachable!(); } 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!() 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!( quote::quote!(
/// Auto-generated docs-only module listing all defined storage types for this pallet. /// Auto-generated docs-only module listing all (public and private) defined storage types
/// Note that members of this module cannot be used directly and are only provided for /// for this pallet.
/// documentation purposes. ///
/// # 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)] #[cfg(doc)]
pub mod storage_types { pub mod storage_types {
use super::*; use super::*;
#( #storage_types
#( #[doc = #storage_docs] )*
pub struct #storage_names();
)*
} }
/// Auto-generated docs-only module listing all defined dispatchables for this pallet. /// 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)] #[cfg(doc)]
pub mod dispatchables { pub mod dispatchables {
use super::*; 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() { if get_doc_literals(&error_item.attrs).is_empty() {
error_item.attrs.push(syn::parse_quote!( error_item.attrs.push(syn::parse_quote!(
#[doc = r" #[doc = "The `Error` enum of this pallet."]
Custom [dispatch errors](https://docs.substrate.io/main-docs/build/events-errors/)
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() { if get_doc_literals(&event_item.attrs).is_empty() {
event_item.attrs.push(syn::parse_quote!( event_item
#[doc = r" .attrs
The [event](https://docs.substrate.io/main-docs/build/events-errors/) emitted .push(syn::parse_quote!(#[doc = "The `Event` enum of this pallet"]));
by this pallet.
"]
));
} }
// derive some traits because system event require Clone, FullCodec, Eq, PartialEq and Debug // derive some traits because system event require Clone, FullCodec, Eq, PartialEq and Debug
@@ -36,7 +36,6 @@ mod type_value;
mod validate_unsigned; mod validate_unsigned;
use crate::pallet::Def; use crate::pallet::Def;
use frame_support_procedural_tools::get_doc_literals;
use quote::ToTokens; use quote::ToTokens;
/// Merge where clause together, `where` token span is taken from the first not none one. /// 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 tt_default_parts = tt_default_parts::expand_tt_default_parts(&mut def);
let doc_only = doc_only::expand_doc_only(&mut def); let doc_only = doc_only::expand_doc_only(&mut def);
if get_doc_literals(&def.item.attrs).is_empty() { def.item.attrs.insert(
def.item.attrs.push(syn::parse_quote!( 0,
#[doc = r" syn::parse_quote!(
The module that hosts all the #[doc = r"The `pallet` module in each FRAME pallet hosts the most important items needed
[FRAME](https://docs.substrate.io/main-docs/build/events-errors/) to construct this pallet.
types needed to add this pallet to a
runtime. 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!( let new_items = quote::quote!(
#metadata_docs #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() { if get_doc_literals(&pallet_item.attrs).is_empty() {
pallet_item.attrs.push(syn::parse_quote!( pallet_item.attrs.push(syn::parse_quote!(
#[doc = r" #[doc = r"
The [pallet](https://docs.substrate.io/reference/frame-pallets/#pallets) implementing The `Pallet` struct, the main type that implements traits and standalone
the on-chain logic. functions within the pallet.
"] "]
)); ));
} }
@@ -22,6 +22,7 @@ use crate::{
Def, Def,
}, },
}; };
use itertools::Itertools;
use quote::ToTokens; use quote::ToTokens;
use std::{collections::HashMap, ops::IndexMut}; use std::{collections::HashMap, ops::IndexMut};
use syn::spanned::Spanned; use syn::spanned::Spanned;
@@ -310,6 +311,65 @@ pub fn process_generics(def: &mut Def) -> syn::Result<Vec<ResultOnEmptyStructMet
Ok(on_empty_struct_metadata) 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 /// * generate StoragePrefix structs (e.g. for a storage `MyStorage` a struct with the name
/// `_GeneratedPrefixForStorage$NameOfStorage` is generated) and implements StorageInstance trait. /// `_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(), Err(e) => return e.into_compile_error(),
}; };
augment_final_docs(def);
// Check for duplicate prefixes // Check for duplicate prefixes
let mut prefix_set = HashMap::new(); let mut prefix_set = HashMap::new();
let mut errors = def let mut errors = def
@@ -365,10 +427,6 @@ pub fn expand_storages(def: &mut Def) -> proc_macro2::TokenStream {
if let Some(getter) = &storage.getter { if let Some(getter) = &storage.getter {
let completed_where_clause = let completed_where_clause =
super::merge_where_clauses(&[&storage.where_clause, &def.config.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 ident = &storage.ident;
let gen = &def.type_use_generics(storage.attr_span); 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; 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 { match &storage.metadata {
Metadata::Value { value } => { Metadata::Value { value } => {
let query = match storage.query_kind.as_ref().expect("Checked by def") { 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 => quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)* #(#cfg_attrs)*
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
#( #docs )* #[doc = #getter_doc_line]
pub fn #getter() -> #query { pub fn #getter() -> #query {
< <
#full_ident as #frame_support::storage::StorageValue<#value> #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 => quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)* #(#cfg_attrs)*
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { 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 pub fn #getter<KArg>(k: KArg) -> #query where
KArg: #frame_support::codec::EncodeLike<#key>, 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 => quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)* #(#cfg_attrs)*
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { 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 pub fn #getter<KArg>(k: KArg) -> #query where
KArg: #frame_support::codec::EncodeLike<#key>, 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 => quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)* #(#cfg_attrs)*
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { 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 pub fn #getter<KArg1, KArg2>(k1: KArg1, k2: KArg2) -> #query where
KArg1: #frame_support::codec::EncodeLike<#key1>, KArg1: #frame_support::codec::EncodeLike<#key1>,
KArg2: #frame_support::codec::EncodeLike<#key2>, 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 => quote::quote_spanned!(storage.attr_span =>
#(#cfg_attrs)* #(#cfg_attrs)*
impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause {
#( #docs )* #[doc = #getter_doc_line]
pub fn #getter<KArg>(key: KArg) -> #query pub fn #getter<KArg>(key: KArg) -> #query
where where
KArg: #frame_support::storage::types::EncodeLikeTuple< KArg: #frame_support::storage::types::EncodeLikeTuple<
+11
View File
@@ -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. /// Prelude to be used alongside pallet macro, for ease of use.
pub mod pallet_prelude { pub mod pallet_prelude {
pub use crate::{ pub use crate::{
+1 -1
View File
@@ -34,7 +34,7 @@ pub mod pallet {
#[pallet::pallet] #[pallet::pallet]
pub struct Pallet<T>(_); pub struct Pallet<T>(_);
/// The configuration trait /// The configuration trait.
#[pallet::config] #[pallet::config]
#[pallet::disable_frame_system_supertrait_check] #[pallet::disable_frame_system_supertrait_check]
pub trait Config: 'static + Eq + Clone { pub trait Config: 'static + Eq + Clone {