diff --git a/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs new file mode 100644 index 0000000000000..a8024a4206b9b --- /dev/null +++ b/frame/support/procedural/src/construct_runtime/expand/hold_reason.rs @@ -0,0 +1,60 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License + +use crate::construct_runtime::{parse::PalletPath, Pallet}; +use proc_macro2::{Ident, TokenStream}; +use quote::quote; + +pub fn expand_outer_hold_reason(pallet_decls: &[Pallet]) -> TokenStream { + let mut conversion_fns = Vec::new(); + let hold_reason_variants = pallet_decls.iter().filter_map(|decl| { + decl.find_part("HoldReason").map(|_| { + let variant_name = &decl.name; + let path = &decl.path; + let index = &decl.index; + + conversion_fns.push(expand_conversion_fn(path, variant_name)); + + quote! { + #[codec(index = #index)] + #variant_name(#path::HoldReason), + } + }) + }); + + quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, Debug, + TypeInfo, + )] + pub enum RuntimeHoldReason { + #( #hold_reason_variants )* + } + + #( #conversion_fns )* + } +} + +fn expand_conversion_fn(path: &PalletPath, variant_name: &Ident) -> TokenStream { + quote! { + impl From<#path::HoldReason> for RuntimeHoldReason { + fn from(hr: #path::HoldReason) -> Self { + RuntimeHoldReason::#variant_name + } + } + } +} diff --git a/frame/support/procedural/src/construct_runtime/expand/mod.rs b/frame/support/procedural/src/construct_runtime/expand/mod.rs index ace0b23bd7f85..2ec801cfb316c 100644 --- a/frame/support/procedural/src/construct_runtime/expand/mod.rs +++ b/frame/support/procedural/src/construct_runtime/expand/mod.rs @@ -18,6 +18,7 @@ mod call; mod config; mod event; +mod hold_reason; mod inherent; mod metadata; mod origin; @@ -26,6 +27,7 @@ mod unsigned; pub use call::expand_outer_dispatch; pub use config::expand_outer_config; pub use event::expand_outer_event; +pub use hold_reason::expand_outer_hold_reason; pub use inherent::expand_outer_inherent; pub use metadata::expand_runtime_metadata; pub use origin::expand_outer_origin; diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index 37f23efed36c1..931ed9fbf2a49 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -265,6 +265,7 @@ fn construct_runtime_final_expansion( let inherent = expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); + let hold_reason = expand::expand_outer_hold_reason(&pallets); let integrity_test = decl_integrity_test(&scrate); let static_assertions = decl_static_assertions(&name, &pallets, &scrate); @@ -307,6 +308,8 @@ fn construct_runtime_final_expansion( #validate_unsigned + #hold_reason + #integrity_test #static_assertions diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index b0bebcd9e0f21..8af308f23a058 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -38,6 +38,7 @@ mod keyword { syn::custom_keyword!(Origin); syn::custom_keyword!(Inherent); syn::custom_keyword!(ValidateUnsigned); + syn::custom_keyword!(HoldReason); syn::custom_keyword!(exclude_parts); syn::custom_keyword!(use_parts); } @@ -370,6 +371,7 @@ pub enum PalletPartKeyword { Origin(keyword::Origin), Inherent(keyword::Inherent), ValidateUnsigned(keyword::ValidateUnsigned), + HoldReason(keyword::HoldReason), } impl Parse for PalletPartKeyword { @@ -392,6 +394,8 @@ impl Parse for PalletPartKeyword { Ok(Self::Inherent(input.parse()?)) } else if lookahead.peek(keyword::ValidateUnsigned) { Ok(Self::ValidateUnsigned(input.parse()?)) + } else if lookahead.peek(keyword::HoldReason) { + Ok(Self::HoldReason(input.parse()?)) } else { Err(lookahead.error()) } @@ -410,6 +414,7 @@ impl PalletPartKeyword { Self::Origin(_) => "Origin", Self::Inherent(_) => "Inherent", Self::ValidateUnsigned(_) => "ValidateUnsigned", + Self::HoldReason(_) => "HoldReason", } } @@ -435,6 +440,7 @@ impl Spanned for PalletPartKeyword { Self::Origin(inner) => inner.span(), Self::Inherent(inner) => inner.span(), Self::ValidateUnsigned(inner) => inner.span(), + Self::HoldReason(inner) => inner.span(), } } } diff --git a/frame/support/procedural/src/pallet/parse/event.rs b/frame/support/procedural/src/pallet/parse/event.rs index 64c3afd557335..0fb8ee4f54977 100644 --- a/frame/support/procedural/src/pallet/parse/event.rs +++ b/frame/support/procedural/src/pallet/parse/event.rs @@ -104,7 +104,7 @@ impl EventDef { let item = if let syn::Item::Enum(item) = item { item } else { - return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected item enum")) + return Err(syn::Error::new(item.span(), "Invalid pallet::event, expected enum item")) }; let event_attrs: Vec = diff --git a/frame/support/procedural/src/pallet/parse/hold_reason.rs b/frame/support/procedural/src/pallet/parse/hold_reason.rs new file mode 100644 index 0000000000000..6e30f2f6683de --- /dev/null +++ b/frame/support/procedural/src/pallet/parse/hold_reason.rs @@ -0,0 +1,83 @@ +// This file is part of Substrate. + +// Copyright (C) Parity Technologies (UK) Ltd. +// SPDX-License-Identifier: Apache-2.0 + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use quote::ToTokens; +use syn::spanned::Spanned; + +mod keyword { + syn::custom_keyword!(HoldReason); +} + +pub struct HoldReasonDef { + /// The index of the HoldReason item in the pallet module. + pub index: usize, + /// The HoldReason keyword used (contains span). + pub hold_reason: keyword::HoldReason, + /// The span of the pallet::hold_reason attribute. + pub attr_span: proc_macro2::Span, +} + +impl HoldReasonDef { + pub fn try_from( + attr_span: proc_macro2::Span, + index: usize, + item: &mut syn::Item, + ) -> syn::Result { + let item = if let syn::Item::Enum(item) = item { + item + } else { + return Err(syn::Error::new( + item.span(), + "Invalid pallet::hold_reason, expected enum item", + )) + }; + + if !matches!(item.vis, syn::Visibility::Public(_)) { + let msg = "Invalid pallet::hold_reason, `HoldReason` must be public"; + return Err(syn::Error::new(item.span(), msg)) + } + + let has_derive_attr = item + .attrs + .iter() + .find(|attr| { + attr.parse_meta() + .ok() + .map(|meta| match meta { + syn::Meta::List(syn::MetaList { path, .. }) => + path.get_ident().map(|ident| ident == "derive").unwrap_or(false), + _ => false, + }) + .unwrap_or(false) + }) + .is_some(); + + if !has_derive_attr { + let derive_attr: syn::Attribute = syn::parse_quote! { + #[derive( + Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Encode, Decode, MaxEncodedLen, + Debug, TypeInfo, + )] + }; + item.attrs.push(derive_attr); + } + + let hold_reason = syn::parse2::(item.ident.to_token_stream())?; + + Ok(HoldReasonDef { index, hold_reason, attr_span }) + } +} diff --git a/frame/support/procedural/src/pallet/parse/mod.rs b/frame/support/procedural/src/pallet/parse/mod.rs index 94e6f7a7c2f7d..991246f413b21 100644 --- a/frame/support/procedural/src/pallet/parse/mod.rs +++ b/frame/support/procedural/src/pallet/parse/mod.rs @@ -27,6 +27,7 @@ pub mod extra_constants; pub mod genesis_build; pub mod genesis_config; pub mod helper; +pub mod hold_reason; pub mod hooks; pub mod inherent; pub mod origin; @@ -56,6 +57,7 @@ pub struct Def { pub genesis_build: Option, pub validate_unsigned: Option, pub extra_constants: Option, + pub hold_reason: Option, pub type_values: Vec, pub frame_system: syn::Ident, pub frame_support: syn::Ident, @@ -89,6 +91,7 @@ impl Def { let mut genesis_build = None; let mut validate_unsigned = None; let mut extra_constants = None; + let mut hold_reason = None; let mut storages = vec![]; let mut type_values = vec![]; @@ -135,6 +138,8 @@ impl Def { Some(PalletAttr::ExtraConstants(_)) => extra_constants = Some(extra_constants::ExtraConstantsDef::try_from(index, item)?), + Some(PalletAttr::HoldReason(span)) => + hold_reason = Some(hold_reason::HoldReasonDef::try_from(span, index, item)?), Some(attr) => { let msg = "Invalid duplicated attribute"; return Err(syn::Error::new(attr.span(), msg)) @@ -171,6 +176,7 @@ impl Def { origin, inherent, storages, + hold_reason, type_values, frame_system, frame_support, @@ -385,6 +391,7 @@ mod keyword { syn::custom_keyword!(generate_store); syn::custom_keyword!(Store); syn::custom_keyword!(extra_constants); + syn::custom_keyword!(hold_reason); } /// Parse attributes for item in pallet module @@ -404,6 +411,7 @@ enum PalletAttr { ValidateUnsigned(proc_macro2::Span), TypeValue(proc_macro2::Span), ExtraConstants(proc_macro2::Span), + HoldReason(proc_macro2::Span), } impl PalletAttr { @@ -423,6 +431,7 @@ impl PalletAttr { Self::ValidateUnsigned(span) => *span, Self::TypeValue(span) => *span, Self::ExtraConstants(span) => *span, + Self::HoldReason(span) => *span, } } } @@ -464,6 +473,8 @@ impl syn::parse::Parse for PalletAttr { Ok(PalletAttr::TypeValue(content.parse::()?.span())) } else if lookahead.peek(keyword::extra_constants) { Ok(PalletAttr::ExtraConstants(content.parse::()?.span())) + } else if lookahead.peek(keyword::hold_reason) { + Ok(PalletAttr::HoldReason(content.parse::()?.span())) } else { Err(lookahead.error()) } diff --git a/frame/support/test/tests/pallet.rs b/frame/support/test/tests/pallet.rs index 52acdc5c26e03..cf92c5bb89d5b 100644 --- a/frame/support/test/tests/pallet.rs +++ b/frame/support/test/tests/pallet.rs @@ -476,6 +476,11 @@ pub mod pallet { } } + #[pallet::hold_reason] + pub enum HoldReason { + Staking, + } + #[derive(codec::Encode, sp_runtime::RuntimeDebug)] #[cfg_attr(feature = "std", derive(codec::Decode))] pub enum InherentError { diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs new file mode 100644 index 0000000000000..8001844eb2242 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hold_reason] + pub struct HoldReason; +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr new file mode 100644 index 0000000000000..fc0dff3e9e19e --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_non_enum.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::hold_reason, expected enum item + --> tests/pallet_ui/hold_reason_non_enum.rs:10:2 + | +10 | pub struct HoldReason; + | ^^^ diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs new file mode 100644 index 0000000000000..bb884d014f4d0 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.rs @@ -0,0 +1,14 @@ +#[frame_support::pallet] +mod pallet { + #[pallet::config] + pub trait Config: frame_system::Config {} + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hold_reason] + enum HoldReason {} +} + +fn main() { +} \ No newline at end of file diff --git a/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr new file mode 100644 index 0000000000000..402e188b1eab3 --- /dev/null +++ b/frame/support/test/tests/pallet_ui/hold_reason_not_pub.stderr @@ -0,0 +1,5 @@ +error: Invalid pallet::hold_reason, `HoldReason` must be public + --> tests/pallet_ui/hold_reason_not_pub.rs:10:5 + | +10 | enum HoldReason {} + | ^^^^