diff --git a/prdoc/pr_5274.prdoc b/prdoc/pr_5274.prdoc new file mode 100644 index 000000000000..fb76ce661b4e --- /dev/null +++ b/prdoc/pr_5274.prdoc @@ -0,0 +1,24 @@ +title: Enrich metadata IR with associated types of config traits + +doc: + - audience: Runtime Dev + description: | + This feature is part of the upcoming metadata V16. The associated types of the `Config` trait that require the `TypeInfo` + or `Parameter` bounds are included in the metadata of the pallet. The metadata is not yet exposed to the end-user, however + the metadata intermediate representation (IR) contains these types. + + Developers can opt out of metadata collection of the associated types by specifying `without_metadata` optional attribute + to the `#[pallet::config]`. + + Furthermore, the `without_metadata` argument can be used in combination with the newly added `#[pallet::include_metadata]` + attribute to selectively include only certain associated types in the metadata collection. + +crates: + - name: frame-support + bump: patch + - name: frame-support-procedural + bump: patch + - name: frame-support-procedural-tools + bump: patch + - name: sp-metadata-ir + bump: major diff --git a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs index f3724f4ccb69..54eb290ca6cf 100644 --- a/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs +++ b/substrate/frame/support/procedural/src/construct_runtime/expand/metadata.rs @@ -49,6 +49,7 @@ pub fn expand_runtime_metadata( let event = expand_pallet_metadata_events(&filtered_names, runtime, decl); let constants = expand_pallet_metadata_constants(runtime, decl); let errors = expand_pallet_metadata_errors(runtime, decl); + let associated_types = expand_pallet_metadata_associated_types(runtime, decl); let docs = expand_pallet_metadata_docs(runtime, decl); let attr = decl.cfg_pattern.iter().fold(TokenStream::new(), |acc, pattern| { let attr = TokenStream::from_str(&format!("#[cfg({})]", pattern.original())) @@ -70,6 +71,7 @@ pub fn expand_runtime_metadata( constants: #constants, error: #errors, docs: #docs, + associated_types: #associated_types, deprecation_info: #deprecation_info, } } @@ -261,3 +263,12 @@ fn expand_pallet_metadata_docs(runtime: &Ident, decl: &Pallet) -> TokenStream { #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_documentation_metadata() } } + +fn expand_pallet_metadata_associated_types(runtime: &Ident, decl: &Pallet) -> TokenStream { + let path = &decl.path; + let instance = decl.instance.as_ref().into_iter(); + + quote! { + #path::Pallet::<#runtime #(, #path::#instance)*>::pallet_associated_types_metadata() + } +} diff --git a/substrate/frame/support/procedural/src/lib.rs b/substrate/frame/support/procedural/src/lib.rs index a2c1e6eec7f7..c2f546d92048 100644 --- a/substrate/frame/support/procedural/src/lib.rs +++ b/substrate/frame/support/procedural/src/lib.rs @@ -972,6 +972,15 @@ pub fn event(_: TokenStream, _: TokenStream) -> TokenStream { pallet_macro_stub() } +/// +/// --- +/// +/// Documentation for this macro can be found at `frame_support::pallet_macros::include_metadata`. +#[proc_macro_attribute] +pub fn include_metadata(_: TokenStream, _: TokenStream) -> TokenStream { + pallet_macro_stub() +} + /// /// --- /// diff --git a/substrate/frame/support/procedural/src/pallet/expand/config.rs b/substrate/frame/support/procedural/src/pallet/expand/config.rs index 5cf4035a8f8b..0a583f1359ba 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/config.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/config.rs @@ -95,3 +95,51 @@ Consequently, a runtime that wants to include this pallet must implement this tr _ => Default::default(), } } + +/// Generate the metadata for the associated types of the config trait. +/// +/// Implements the `pallet_associated_types_metadata` function for the pallet. +pub fn expand_config_metadata(def: &Def) -> proc_macro2::TokenStream { + let frame_support = &def.frame_support; + let type_impl_gen = &def.type_impl_generics(proc_macro2::Span::call_site()); + let type_use_gen = &def.type_use_generics(proc_macro2::Span::call_site()); + let pallet_ident = &def.pallet_struct.pallet; + let trait_use_gen = &def.trait_use_generics(proc_macro2::Span::call_site()); + + let mut where_clauses = vec![&def.config.where_clause]; + where_clauses.extend(def.extra_constants.iter().map(|d| &d.where_clause)); + let completed_where_clause = super::merge_where_clauses(&where_clauses); + + let types = def.config.associated_types_metadata.iter().map(|metadata| { + let ident = &metadata.ident; + let span = ident.span(); + let ident_str = ident.to_string(); + let cfgs = &metadata.cfg; + + let no_docs = vec![]; + let doc = if cfg!(feature = "no-metadata-docs") { &no_docs } else { &metadata.doc }; + + quote::quote_spanned!(span => { + #( #cfgs ) * + #frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR { + name: #ident_str, + ty: #frame_support::__private::scale_info::meta_type::< + ::#ident + >(), + docs: #frame_support::__private::sp_std::vec![ #( #doc ),* ], + } + }) + }); + + quote::quote!( + impl<#type_impl_gen> #pallet_ident<#type_use_gen> #completed_where_clause { + + #[doc(hidden)] + pub fn pallet_associated_types_metadata() + -> #frame_support::__private::sp_std::vec::Vec<#frame_support::__private::metadata_ir::PalletAssociatedTypeMetadataIR> + { + #frame_support::__private::sp_std::vec![ #( #types ),* ] + } + } + ) +} diff --git a/substrate/frame/support/procedural/src/pallet/expand/mod.rs b/substrate/frame/support/procedural/src/pallet/expand/mod.rs index 067839c28463..3f9b50f79c0c 100644 --- a/substrate/frame/support/procedural/src/pallet/expand/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/expand/mod.rs @@ -60,6 +60,7 @@ pub fn expand(mut def: Def) -> proc_macro2::TokenStream { let constants = constants::expand_constants(&mut def); let pallet_struct = pallet_struct::expand_pallet_struct(&mut def); let config = config::expand_config(&mut def); + let associated_types = config::expand_config_metadata(&def); let call = call::expand_call(&mut def); let tasks = tasks::expand_tasks(&mut def); let error = error::expand_error(&mut def); @@ -101,6 +102,7 @@ storage item. Otherwise, all storage items are listed among [*Type Definitions*] #constants #pallet_struct #config + #associated_types #call #tasks #error diff --git a/substrate/frame/support/procedural/src/pallet/parse/config.rs b/substrate/frame/support/procedural/src/pallet/parse/config.rs index 9a59d7114202..6b6dcc802e2e 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/config.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/config.rs @@ -16,9 +16,9 @@ // limitations under the License. use super::helper; -use frame_support_procedural_tools::{get_doc_literals, is_using_frame_crate}; +use frame_support_procedural_tools::{get_cfg_attributes, get_doc_literals, is_using_frame_crate}; use quote::ToTokens; -use syn::{spanned::Spanned, token, Token}; +use syn::{spanned::Spanned, token, Token, TraitItemType}; /// List of additional token to be used for parsing. mod keyword { @@ -36,6 +36,7 @@ mod keyword { syn::custom_keyword!(no_default); syn::custom_keyword!(no_default_bounds); syn::custom_keyword!(constant); + syn::custom_keyword!(include_metadata); } #[derive(Default)] @@ -55,6 +56,8 @@ pub struct ConfigDef { pub has_instance: bool, /// Const associated type. pub consts_metadata: Vec, + /// Associated types metadata. + pub associated_types_metadata: Vec, /// Whether the trait has the associated type `Event`, note that those bounds are /// checked: /// * `IsType::RuntimeEvent` @@ -70,6 +73,26 @@ pub struct ConfigDef { pub default_sub_trait: Option, } +/// Input definition for an associated type in pallet config. +pub struct AssociatedTypeMetadataDef { + /// Name of the associated type. + pub ident: syn::Ident, + /// The doc associated. + pub doc: Vec, + /// The cfg associated. + pub cfg: Vec, +} + +impl From<&syn::TraitItemType> for AssociatedTypeMetadataDef { + fn from(trait_ty: &syn::TraitItemType) -> Self { + let ident = trait_ty.ident.clone(); + let doc = get_doc_literals(&trait_ty.attrs); + let cfg = get_cfg_attributes(&trait_ty.attrs); + + Self { ident, doc, cfg } + } +} + /// Input definition for a constant in pallet config. pub struct ConstMetadataDef { /// Name of the associated type. @@ -146,6 +169,8 @@ pub enum PalletAttrType { NoBounds(keyword::no_default_bounds), #[peek(keyword::constant, name = "constant")] Constant(keyword::constant), + #[peek(keyword::include_metadata, name = "include_metadata")] + IncludeMetadata(keyword::include_metadata), } /// Parsing for `#[pallet::X]` @@ -322,12 +347,32 @@ pub fn replace_self_by_t(input: proc_macro2::TokenStream) -> proc_macro2::TokenS .collect() } +/// Check that the trait item requires the `TypeInfo` bound (or similar). +fn contains_type_info_bound(ty: &TraitItemType) -> bool { + const KNOWN_TYPE_INFO_BOUNDS: &[&str] = &[ + // Explicit TypeInfo trait. + "TypeInfo", + // Implicit known substrate traits that implement type info. + // Note: Aim to keep this list as small as possible. + "Parameter", + ]; + + ty.bounds.iter().any(|bound| { + let syn::TypeParamBound::Trait(bound) = bound else { return false }; + + KNOWN_TYPE_INFO_BOUNDS + .iter() + .any(|known| bound.path.segments.last().map_or(false, |last| last.ident == *known)) + }) +} + impl ConfigDef { pub fn try_from( frame_system: &syn::Path, index: usize, item: &mut syn::Item, enable_default: bool, + disable_associated_metadata: bool, ) -> syn::Result { let syn::Item::Trait(item) = item else { let msg = "Invalid pallet::config, expected trait definition"; @@ -368,6 +413,7 @@ impl ConfigDef { let mut has_event_type = false; let mut consts_metadata = vec![]; + let mut associated_types_metadata = vec![]; let mut default_sub_trait = if enable_default { Some(DefaultTrait { items: Default::default(), @@ -383,6 +429,7 @@ impl ConfigDef { let mut already_no_default = false; let mut already_constant = false; let mut already_no_default_bounds = false; + let mut already_collected_associated_type = None; while let Ok(Some(pallet_attr)) = helper::take_first_item_pallet_attr::(trait_item) @@ -403,11 +450,29 @@ impl ConfigDef { trait_item.span(), "Invalid #[pallet::constant] in #[pallet::config], expected type item", )), + // Pallet developer has explicitly requested to include metadata for this associated type. + // + // They must provide a type item that implements `TypeInfo`. + (PalletAttrType::IncludeMetadata(_), syn::TraitItem::Type(ref typ)) => { + if already_collected_associated_type.is_some() { + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Duplicate #[pallet::include_metadata] attribute not allowed.", + )); + } + already_collected_associated_type = Some(pallet_attr._bracket.span.join()); + associated_types_metadata.push(AssociatedTypeMetadataDef::from(AssociatedTypeMetadataDef::from(typ))); + } + (PalletAttrType::IncludeMetadata(_), _) => + return Err(syn::Error::new( + pallet_attr._bracket.span.join(), + "Invalid #[pallet::include_metadata] in #[pallet::config], expected type item", + )), (PalletAttrType::NoDefault(_), _) => { if !enable_default { return Err(syn::Error::new( pallet_attr._bracket.span.join(), - "`#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` \ + "`#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` \ has been specified" )); } @@ -439,6 +504,47 @@ impl ConfigDef { } } + if let Some(span) = already_collected_associated_type { + // Events and constants are already propagated to the metadata + if is_event { + return Err(syn::Error::new( + span, + "Invalid #[pallet::include_metadata] for `type RuntimeEvent`. \ + The associated type `RuntimeEvent` is already collected in the metadata.", + )) + } + + if already_constant { + return Err(syn::Error::new( + span, + "Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. \ + Pallet constant already collect the metadata for the type.", + )) + } + + if let syn::TraitItem::Type(ref ty) = trait_item { + if !contains_type_info_bound(ty) { + let msg = format!( + "Invalid #[pallet::include_metadata] in #[pallet::config], collected type `{}` \ + does not implement `TypeInfo` or `Parameter`", + ty.ident, + ); + return Err(syn::Error::new(span, msg)); + } + } + } else { + // Metadata of associated types is collected by default, if the associated type + // implements `TypeInfo`, or a similar trait that requires the `TypeInfo` bound. + if !disable_associated_metadata && !is_event && !already_constant { + if let syn::TraitItem::Type(ref ty) = trait_item { + // Collect the metadata of the associated type if it implements `TypeInfo`. + if contains_type_info_bound(ty) { + associated_types_metadata.push(AssociatedTypeMetadataDef::from(ty)); + } + } + } + } + if !already_no_default && enable_default { default_sub_trait .as_mut() @@ -481,6 +587,7 @@ impl ConfigDef { index, has_instance, consts_metadata, + associated_types_metadata, has_event_type, where_clause, default_sub_trait, diff --git a/substrate/frame/support/procedural/src/pallet/parse/mod.rs b/substrate/frame/support/procedural/src/pallet/parse/mod.rs index b9c7afcab0f9..5036f691690f 100644 --- a/substrate/frame/support/procedural/src/pallet/parse/mod.rs +++ b/substrate/frame/support/procedural/src/pallet/parse/mod.rs @@ -108,12 +108,13 @@ impl Def { let pallet_attr: Option = helper::take_first_item_pallet_attr(item)?; match pallet_attr { - Some(PalletAttr::Config(_, with_default)) if config.is_none() => + Some(PalletAttr::Config{ with_default, without_automatic_metadata, ..}) if config.is_none() => config = Some(config::ConfigDef::try_from( &frame_system, index, item, with_default, + without_automatic_metadata, )?), Some(PalletAttr::Pallet(span)) if pallet_struct.is_none() => { let p = pallet_struct::PalletStructDef::try_from(span, index, item)?; @@ -547,6 +548,7 @@ mod keyword { syn::custom_keyword!(event); syn::custom_keyword!(config); syn::custom_keyword!(with_default); + syn::custom_keyword!(without_automatic_metadata); syn::custom_keyword!(hooks); syn::custom_keyword!(inherent); syn::custom_keyword!(error); @@ -560,10 +562,37 @@ mod keyword { syn::custom_keyword!(composite_enum); } +/// The possible values for the `#[pallet::config]` attribute. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +enum ConfigValue { + /// `#[pallet::config(with_default)]` + WithDefault(keyword::with_default), + /// `#[pallet::config(without_automatic_metadata)]` + WithoutAutomaticMetadata(keyword::without_automatic_metadata), +} + +impl syn::parse::Parse for ConfigValue { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(keyword::with_default) { + input.parse().map(ConfigValue::WithDefault) + } else if lookahead.peek(keyword::without_automatic_metadata) { + input.parse().map(ConfigValue::WithoutAutomaticMetadata) + } else { + Err(lookahead.error()) + } + } +} + /// Parse attributes for item in pallet module /// syntax must be `pallet::` (e.g. `#[pallet::config]`) enum PalletAttr { - Config(proc_macro2::Span, bool), + Config { + span: proc_macro2::Span, + with_default: bool, + without_automatic_metadata: bool, + }, Pallet(proc_macro2::Span), Hooks(proc_macro2::Span), /// A `#[pallet::call]` with optional attributes to specialize the behaviour. @@ -625,7 +654,7 @@ enum PalletAttr { impl PalletAttr { fn span(&self) -> proc_macro2::Span { match self { - Self::Config(span, _) => *span, + Self::Config { span, .. } => *span, Self::Pallet(span) => *span, Self::Hooks(span) => *span, Self::Tasks(span) => *span, @@ -660,13 +689,49 @@ impl syn::parse::Parse for PalletAttr { let lookahead = content.lookahead1(); if lookahead.peek(keyword::config) { let span = content.parse::()?.span(); - let with_default = content.peek(syn::token::Paren); - if with_default { + if content.peek(syn::token::Paren) { let inside_config; + + // Parse (with_default, without_automatic_metadata) attributes. let _paren = syn::parenthesized!(inside_config in content); - inside_config.parse::()?; + + let fields: syn::punctuated::Punctuated = + inside_config.parse_terminated(ConfigValue::parse, syn::Token![,])?; + let config_values = fields.iter().collect::>(); + + let mut with_default = false; + let mut without_automatic_metadata = false; + for config in config_values { + match config { + ConfigValue::WithDefault(_) => { + if with_default { + return Err(syn::Error::new( + span, + "Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: with_default.", + )); + } + with_default = true; + }, + ConfigValue::WithoutAutomaticMetadata(_) => { + if without_automatic_metadata { + return Err(syn::Error::new( + span, + "Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: without_automatic_metadata.", + )); + } + without_automatic_metadata = true; + }, + } + } + + Ok(PalletAttr::Config { span, with_default, without_automatic_metadata }) + } else { + Ok(PalletAttr::Config { + span, + with_default: false, + without_automatic_metadata: false, + }) } - Ok(PalletAttr::Config(span, with_default)) } else if lookahead.peek(keyword::pallet) { Ok(PalletAttr::Pallet(content.parse::()?.span())) } else if lookahead.peek(keyword::hooks) { diff --git a/substrate/frame/support/procedural/tools/src/lib.rs b/substrate/frame/support/procedural/tools/src/lib.rs index ea53335a88fd..d1d7efaab01d 100644 --- a/substrate/frame/support/procedural/tools/src/lib.rs +++ b/substrate/frame/support/procedural/tools/src/lib.rs @@ -181,3 +181,17 @@ pub fn get_doc_literals(attrs: &[syn::Attribute]) -> Vec { }) .collect() } + +/// Return all cfg attributes literals found. +pub fn get_cfg_attributes(attrs: &[syn::Attribute]) -> Vec { + attrs + .iter() + .filter_map(|attr| { + if let syn::Meta::List(meta) = &attr.meta { + meta.path.get_ident().filter(|ident| *ident == "cfg").map(|_| attr.clone()) + } else { + None + } + }) + .collect() +} diff --git a/substrate/frame/support/src/lib.rs b/substrate/frame/support/src/lib.rs index d76073a97a35..1f2ec71b191f 100644 --- a/substrate/frame/support/src/lib.rs +++ b/substrate/frame/support/src/lib.rs @@ -1565,6 +1565,53 @@ pub mod pallet_macros { /// * [`frame_support::derive_impl`]. /// * [`#[pallet::no_default]`](`no_default`) /// * [`#[pallet::no_default_bounds]`](`no_default_bounds`) + /// + /// ## Optional: `without_automatic_metadata` + /// + /// By default, the associated types of the `Config` trait that require the `TypeInfo` or + /// `Parameter` bounds are included in the metadata of the pallet. + /// + /// The optional `without_automatic_metadata` argument can be used to exclude these + /// associated types from the metadata collection. + /// + /// Furthermore, the `without_automatic_metadata` argument can be used in combination with + /// the [`#[pallet::include_metadata]`](`include_metadata`) attribute to selectively + /// include only certain associated types in the metadata collection. + /// + /// ``` + /// #[frame_support::pallet] + /// mod pallet { + /// # use frame_support::pallet_prelude::*; + /// # use frame_system::pallet_prelude::*; + /// # use core::fmt::Debug; + /// # use frame_support::traits::Contains; + /// # + /// # pub trait SomeMoreComplexBound {} + /// # + /// #[pallet::pallet] + /// pub struct Pallet(_); + /// + /// #[pallet::config(with_default, without_automatic_metadata)] // <- with_default and without_automatic_metadata are optional + /// pub trait Config: frame_system::Config { + /// /// The overarching event type. + /// #[pallet::no_default_bounds] // Default with bounds is not supported for RuntimeEvent + /// type RuntimeEvent: From> + IsType<::RuntimeEvent>; + /// + /// /// A simple type. + /// // Type that would have been included in metadata, but is now excluded. + /// type SimpleType: From + TypeInfo; + /// + /// // The `pallet::include_metadata` is used to selectively include this type in metadata. + /// #[pallet::include_metadata] + /// type SelectivelyInclude: From + TypeInfo; + /// } + /// + /// #[pallet::event] + /// pub enum Event { + /// SomeEvent(u16, u32), + /// } + /// } + /// ``` pub use frame_support_procedural::config; /// Allows defining an enum that gets composed as an aggregate enum by `construct_runtime`. @@ -1962,6 +2009,17 @@ pub mod pallet_macros { /// will be returned. pub use frame_support_procedural::event; + /// Selectively includes associated types in the metadata. + /// + /// The optional attribute allows you to selectively include associated types in the + /// metadata. This can be attached to trait items that implement `TypeInfo`. + /// + /// By default all collectable associated types are included in the metadata. + /// + /// This attribute can be used in combination with the + /// [`#[pallet::config(without_automatic_metadata)]`](`config`). + pub use frame_support_procedural::include_metadata; + /// Allows a pallet to declare a set of functions as a *dispatchable extrinsic*. /// /// In slightly simplified terms, this macro declares the set of "transactions" of a diff --git a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr index 59e36775d464..55b19ac1a652 100644 --- a/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr +++ b/substrate/frame/support/test/tests/construct_runtime_ui/deprecated_where_block.stderr @@ -711,6 +711,31 @@ note: the trait `Config` must be implemented | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0599]: the function or associated item `pallet_associated_types_metadata` exists for struct `Pallet`, but its trait bounds were not satisfied + --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 + | +20 | construct_runtime! { + | __^ + | | _| + | || +21 | || pub struct Runtime where + | ||______________________- doesn't satisfy `Runtime: Config` +22 | | Block = Block, +23 | | NodeBlock = Block, +... | +27 | | } +28 | | } + | |__^ function or associated item cannot be called on `Pallet` due to unsatisfied trait bounds + | + = note: the following trait bounds were not satisfied: + `Runtime: Config` +note: the trait `Config` must be implemented + --> $WORKSPACE/substrate/frame/system/src/lib.rs + | + | pub trait Config: 'static + Eq + Clone { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + = note: this error originates in the macro `frame_support::construct_runtime` (in Nightly builds, run with -Z macro-backtrace for more info) + error[E0277]: the trait bound `Runtime: Config` is not satisfied --> tests/construct_runtime_ui/deprecated_where_block.rs:20:1 | diff --git a/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs b/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs new file mode 100644 index 000000000000..a2b916f54c5e --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_associated_types_metadata.rs @@ -0,0 +1,269 @@ +// 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 frame_support::{derive_impl, traits::ConstU32}; +use scale_info::meta_type; +use sp_metadata_ir::PalletAssociatedTypeMetadataIR; + +pub type BlockNumber = u64; +pub type Header = sp_runtime::generic::Header; +pub type Block = sp_runtime::generic::Block; +pub type UncheckedExtrinsic = sp_runtime::generic::UncheckedExtrinsic; + +/// Pallet without collectable associated types. +#[frame_support::pallet] +pub mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +/// Pallet with default collectable associated types. +#[frame_support::pallet] +pub mod pallet2 { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + #[pallet::config] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + + // Associated type included by default, because it requires TypeInfo bound. + /// Nonce doc. + type Nonce: TypeInfo; + + // Associated type included by default, because it requires + // Parameter bound (indirect TypeInfo). + type AccountData: Parameter; + + // Associated type without metadata bounds, not included. + type NotIncluded: From; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +/// Pallet with implicit collectable associated types. +#[frame_support::pallet] +pub mod pallet3 { + use frame_support::pallet_prelude::*; + + #[pallet::pallet] + pub struct Pallet(_); + + // Associated types are not collected by default. + #[pallet::config(without_automatic_metadata)] + pub trait Config: frame_system::Config { + // Runtime events already propagated to the metadata. + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + // Constants are already propagated. + #[pallet::constant] + type MyGetParam2: Get; + + // Explicitly include associated types. + #[pallet::include_metadata] + type Nonce: TypeInfo; + + type AccountData: Parameter; + + type NotIncluded: From; + } + + #[pallet::event] + pub enum Event { + TestEvent, + } +} + +impl pallet::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; +} + +impl pallet2::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; + type Nonce = u64; + type AccountData = u16; + type NotIncluded = u8; +} + +impl pallet3::Config for Runtime { + type RuntimeEvent = RuntimeEvent; + type MyGetParam2 = ConstU32<10>; + type Nonce = u64; + type AccountData = u16; + type NotIncluded = u8; +} + +#[derive_impl(frame_system::config_preludes::TestDefaultConfig)] +impl frame_system::Config for Runtime { + type BlockWeights = (); + type BlockLength = (); + type DbWeight = (); + type BaseCallFilter = frame_support::traits::Everything; + type RuntimeOrigin = RuntimeOrigin; + type Nonce = u64; + type RuntimeCall = RuntimeCall; + type Hash = sp_runtime::testing::H256; + type Hashing = sp_runtime::traits::BlakeTwo256; + type AccountId = u64; + type Lookup = sp_runtime::traits::IdentityLookup; + type Block = Block; + type RuntimeEvent = RuntimeEvent; + type Version = (); + type PalletInfo = PalletInfo; + type AccountData = (); + type OnNewAccount = (); + type OnKilledAccount = (); + type SystemWeightInfo = (); + type SS58Prefix = (); + type OnSetCode = (); + type MaxConsumers = ConstU32<16>; +} + +frame_support::construct_runtime!( + pub enum Runtime + { + System: frame_system, + Example: pallet, + DefaultInclusion: pallet2, + ExplicitInclusion: pallet3, + } +); + +#[test] +fn associated_types_metadata() { + fn maybe_docs(doc: Vec<&'static str>) -> Vec<&'static str> { + if cfg!(feature = "no-metadata-docs") { + vec![] + } else { + doc + } + } + + let ir = Runtime::metadata_ir(); + + // No associated types to collect. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "Example").unwrap(); + pretty_assertions::assert_eq!(pallet.associated_types, vec![]); + + // Collect by default types that implement TypeInfo or Parameter. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "DefaultInclusion").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![ + PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: maybe_docs(vec![" Nonce doc."]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountData", + ty: meta_type::(), + docs: vec![], + } + ] + ); + + // Explicitly include associated types. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "ExplicitInclusion").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: vec![], + }] + ); + + // Check system pallet. + let pallet = ir.pallets.iter().find(|pallet| pallet.name == "System").unwrap(); + pretty_assertions::assert_eq!( + pallet.associated_types, + vec![ + PalletAssociatedTypeMetadataIR { + name: "RuntimeCall", + ty: meta_type::(), + docs: maybe_docs(vec![" The aggregated `RuntimeCall` type."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Nonce", + ty: meta_type::(), + docs: maybe_docs(vec![" This stores the number of previous transactions associated with a sender account."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Hash", + ty: meta_type::(), + docs: maybe_docs(vec![" The output of the `Hashing` function."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Hashing", + ty: meta_type::(), + docs: maybe_docs(vec![" The hashing system (algorithm) being used in the runtime (e.g. Blake2)."]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountId", + ty: meta_type::(), + docs: maybe_docs(vec![" The user account identifier type for the runtime."]), + }, + PalletAssociatedTypeMetadataIR { + name: "Block", + ty: meta_type::(), + docs: maybe_docs(vec![ + " The Block type used by the runtime. This is used by `construct_runtime` to retrieve the", + " extrinsics or other block specific data as needed.", + ]), + }, + PalletAssociatedTypeMetadataIR { + name: "AccountData", + ty: meta_type::<()>(), + docs: maybe_docs(vec![ + " Data to be associated with an account (other than nonce/transaction counter, which this", + " pallet does regardless).", + ]), + }, + ] + ); +} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs new file mode 100644 index 000000000000..f58e11b02261 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.rs @@ -0,0 +1,39 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default, without_automatic_metadata, without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr new file mode 100644 index 000000000000..46326bde0559 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_duplicate_attr.stderr @@ -0,0 +1,5 @@ +error: Invalid duplicated attribute for `#[pallet::config]`. Please remove duplicates: without_automatic_metadata. + --> tests/pallet_ui/config_duplicate_attr.rs:23:12 + | +23 | #[pallet::config(with_default, without_automatic_metadata, without_automatic_metadata)] + | ^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs new file mode 100644 index 000000000000..38c3870ba735 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.rs @@ -0,0 +1,42 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + + #[pallet::include_metadata] + type MyNonScaleTypeInfo; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr new file mode 100644 index 000000000000..362e97e8bb92 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_non_type_info.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata] in #[pallet::config], collected type `MyNonScaleTypeInfo` does not implement `TypeInfo` or `Parameter` + --> tests/pallet_ui/config_metadata_non_type_info.rs:28:4 + | +28 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs new file mode 100644 index 000000000000..5452479b76e7 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.rs @@ -0,0 +1,40 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config] + pub trait Config: frame_system::Config { + #[pallet::constant] + #[pallet::include_metadata] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr new file mode 100644 index 000000000000..eb943158f38a --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_constants.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata]: conflict with #[pallet::constant]. Pallet constant already collect the metadata for the type. + --> tests/pallet_ui/config_metadata_on_constants.rs:26:10 + | +26 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs new file mode 100644 index 000000000000..d91f86771bf6 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.rs @@ -0,0 +1,43 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + use frame_system::pallet_prelude::*; + + #[pallet::config(with_default)] + pub trait Config: frame_system::Config { + #[pallet::no_default_bounds] + #[pallet::include_metadata] + type RuntimeEvent: From> + IsType<::RuntimeEvent>; + + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(core::marker::PhantomData); + + #[pallet::hooks] + impl Hooks> for Pallet {} + + #[pallet::call] + impl Pallet {} +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr new file mode 100644 index 000000000000..15132ccce04c --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/config_metadata_on_events.stderr @@ -0,0 +1,5 @@ +error: Invalid #[pallet::include_metadata] for `type RuntimeEvent`. The associated type `RuntimeEvent` is already collected in the metadata. + --> tests/pallet_ui/config_metadata_on_events.rs:26:4 + | +26 | #[pallet::include_metadata] + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr b/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr index e8df28a3046f..1b066bbe9fb8 100644 --- a/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr +++ b/substrate/frame/support/test/tests/pallet_ui/no_default_but_missing_with_default.stderr @@ -1,4 +1,4 @@ -error: `#[pallet:no_default]` can only be used if `#[pallet::config(with_default)]` has been specified +error: `#[pallet::no_default]` can only be used if `#[pallet::config(with_default)]` has been specified --> tests/pallet_ui/no_default_but_missing_with_default.rs:26:4 | 26 | #[pallet::no_default] diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs b/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs new file mode 100644 index 000000000000..c016c52181cf --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/pass/config_multiple_attr.rs @@ -0,0 +1,32 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config(with_default, without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +fn main() {} diff --git a/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs b/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs new file mode 100644 index 000000000000..c9f5244d7345 --- /dev/null +++ b/substrate/frame/support/test/tests/pallet_ui/pass/config_without_metadata.rs @@ -0,0 +1,32 @@ +// 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. + +#[frame_support::pallet] +mod pallet { + use frame_support::pallet_prelude::*; + + #[pallet::config(without_automatic_metadata)] + pub trait Config: frame_system::Config { + #[pallet::constant] + type MyGetParam2: Get; + } + + #[pallet::pallet] + pub struct Pallet(_); +} + +fn main() {} diff --git a/substrate/primitives/metadata-ir/src/types.rs b/substrate/primitives/metadata-ir/src/types.rs index 4ebe8c25a675..da4f5d7f3711 100644 --- a/substrate/primitives/metadata-ir/src/types.rs +++ b/substrate/primitives/metadata-ir/src/types.rs @@ -133,6 +133,8 @@ pub struct PalletMetadataIR { pub constants: Vec>, /// Pallet error metadata. pub error: Option>, + /// Config's trait associated types. + pub associated_types: Vec>, /// Define the index of the pallet, this index will be used for the encoding of pallet event, /// call and origin variants. pub index: u8, @@ -153,6 +155,7 @@ impl IntoPortable for PalletMetadataIR { event: self.event.map(|event| event.into_portable(registry)), constants: registry.map_into_portable(self.constants), error: self.error.map(|error| error.into_portable(registry)), + associated_types: registry.map_into_portable(self.associated_types), index: self.index, docs: registry.map_into_portable(self.docs), deprecation_info: self.deprecation_info.into_portable(registry), @@ -197,6 +200,29 @@ impl IntoPortable for ExtrinsicMetadataIR { } } +/// Metadata of a pallet's associated type. +#[derive(Clone, PartialEq, Eq, Encode, Debug)] +pub struct PalletAssociatedTypeMetadataIR { + /// The name of the associated type. + pub name: T::String, + /// The type of the associated type. + pub ty: T::Type, + /// The documentation of the associated type. + pub docs: Vec, +} + +impl IntoPortable for PalletAssociatedTypeMetadataIR { + type Output = PalletAssociatedTypeMetadataIR; + + fn into_portable(self, registry: &mut Registry) -> Self::Output { + PalletAssociatedTypeMetadataIR { + name: self.name.into_portable(registry), + ty: registry.register_type(&self.ty), + docs: registry.map_into_portable(self.docs), + } + } +} + /// Metadata of an extrinsic's signed extension. #[derive(Clone, PartialEq, Eq, Encode, Debug)] pub struct SignedExtensionMetadataIR {