diff --git a/bin/node-template/runtime/src/lib.rs b/bin/node-template/runtime/src/lib.rs index c3375d2ee601a..b2863a2deb7cb 100644 --- a/bin/node-template/runtime/src/lib.rs +++ b/bin/node-template/runtime/src/lib.rs @@ -279,8 +279,23 @@ impl pallet_template::Config for Runtime { } // Create the runtime by composing the FRAME pallets that were previously configured. -construct_runtime!( - pub struct Runtime { +#[frame_support::construct_runtime_v2] +mod runtime { + #[frame::runtime] + pub struct Runtime; + + #[frame::pallets] + #[frame::derive( + RuntimeCall, + RuntimeEvent, + RuntimeError, + RuntimeOrigin, + RuntimeFreezeReason, + RuntimeHoldReason, + RuntimeSlashReason, + RuntimeLockId + )] + pub struct Pallets { System: frame_system, Timestamp: pallet_timestamp, Aura: pallet_aura, @@ -289,9 +304,11 @@ construct_runtime!( TransactionPayment: pallet_transaction_payment, Sudo: pallet_sudo, // Include the custom logic from the pallet-template in the runtime. + #[frame::pallet_index(8)] + #[frame::disable_call] TemplateModule: pallet_template, } -); +} /// The address format for describing accounts. pub type Address = sp_runtime::MultiAddress; diff --git a/bin/node/runtime/src/lib.rs b/bin/node/runtime/src/lib.rs index 4e1b6d4e8bec0..5ca8844d18a80 100644 --- a/bin/node/runtime/src/lib.rs +++ b/bin/node/runtime/src/lib.rs @@ -28,7 +28,6 @@ use frame_election_provider_support::{ onchain, BalancingConfig, ElectionDataProvider, SequentialPhragmen, VoteWeight, }; use frame_support::{ - construct_runtime, dispatch::DispatchClass, instances::{Instance1, Instance2}, ord_parameter_types, @@ -1876,9 +1875,13 @@ impl pallet_statement::Config for Runtime { type MaxAllowedBytes = MaxAllowedBytes; } -construct_runtime!( - pub struct Runtime - { +#[frame_support::construct_runtime_v2] +mod runtime { + #[frame::runtime] + pub struct Runtime; + + #[frame::pallets] + pub struct Pallets { System: frame_system, Utility: pallet_utility, Babe: pallet_babe, @@ -1895,10 +1898,10 @@ construct_runtime!( Staking: pallet_staking, Session: pallet_session, Democracy: pallet_democracy, - Council: pallet_collective::, - TechnicalCommittee: pallet_collective::, + Council: pallet_collective, + TechnicalCommittee: pallet_collective, Elections: pallet_elections_phragmen, - TechnicalMembership: pallet_membership::, + TechnicalMembership: pallet_membership, Grandpa: pallet_grandpa, Treasury: pallet_treasury, AssetRate: pallet_asset_rate, @@ -1907,7 +1910,7 @@ construct_runtime!( ImOnline: pallet_im_online, AuthorityDiscovery: pallet_authority_discovery, Offences: pallet_offences, - Historical: pallet_session_historical::{Pallet}, + Historical: pallet_session_historical + Pallet, RandomnessCollectiveFlip: pallet_insecure_randomness_collective_flip, Identity: pallet_identity, Society: pallet_society, @@ -1920,8 +1923,8 @@ construct_runtime!( Multisig: pallet_multisig, Bounties: pallet_bounties, Tips: pallet_tips, - Assets: pallet_assets::, - PoolAssets: pallet_assets::, + Assets: pallet_assets, + PoolAssets: pallet_assets, Mmr: pallet_mmr, Lottery: pallet_lottery, Nis: pallet_nis, @@ -1931,7 +1934,7 @@ construct_runtime!( Salary: pallet_salary, CoreFellowship: pallet_core_fellowship, TransactionStorage: pallet_transaction_storage, - VoterList: pallet_bags_list::, + VoterList: pallet_bags_list, StateTrieMigration: pallet_state_trie_migration, ChildBounties: pallet_child_bounties, Referenda: pallet_referenda, @@ -1939,10 +1942,10 @@ construct_runtime!( RootTesting: pallet_root_testing, ConvictionVoting: pallet_conviction_voting, Whitelist: pallet_whitelist, - AllianceMotion: pallet_collective::, + AllianceMotion: pallet_collective, Alliance: pallet_alliance, NominationPools: pallet_nomination_pools, - RankedPolls: pallet_referenda::, + RankedPolls: pallet_referenda, RankedCollective: pallet_ranked_collective, AssetConversion: pallet_asset_conversion, FastUnstake: pallet_fast_unstake, @@ -1950,7 +1953,7 @@ construct_runtime!( Pov: frame_benchmarking_pallet_pov, Statement: pallet_statement, } -); +} /// The address format for describing accounts. pub type Address = sp_runtime::MultiAddress; diff --git a/frame/support/procedural/src/construct_runtime/mod.rs b/frame/support/procedural/src/construct_runtime/mod.rs index efc2244154479..20ffcb26b5cdc 100644 --- a/frame/support/procedural/src/construct_runtime/mod.rs +++ b/frame/support/procedural/src/construct_runtime/mod.rs @@ -208,8 +208,8 @@ //! This macro returns the ` :: expanded { Error }` list of additional parts we would like to //! expose. -mod expand; -mod parse; +pub mod expand; +pub mod parse; use cfg_expr::Predicate; use frame_support_procedural_tools::{ @@ -496,7 +496,7 @@ fn construct_runtime_final_expansion( Ok(res) } -fn decl_all_pallets<'a>( +pub fn decl_all_pallets<'a>( runtime: &'a Ident, pallet_declarations: impl Iterator, features: &HashSet<&str>, @@ -660,7 +660,7 @@ fn decl_all_pallets<'a>( ) } -fn decl_pallet_runtime_setup( +pub fn decl_pallet_runtime_setup( runtime: &Ident, pallet_declarations: &[Pallet], scrate: &TokenStream2, @@ -752,7 +752,7 @@ fn decl_pallet_runtime_setup( ) } -fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { +pub fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { quote!( #[cfg(test)] mod __construct_runtime_integrity_test { @@ -767,7 +767,7 @@ fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 { ) } -fn decl_static_assertions( +pub fn decl_static_assertions( runtime: &Ident, pallet_decls: &[Pallet], scrate: &TokenStream2, @@ -798,7 +798,7 @@ fn decl_static_assertions( } } -fn check_pallet_number(input: TokenStream2, pallet_num: usize) -> Result<()> { +pub fn check_pallet_number(input: TokenStream2, pallet_num: usize) -> Result<()> { let max_pallet_num = { if cfg!(feature = "tuples-96") { 96 diff --git a/frame/support/procedural/src/construct_runtime/parse.rs b/frame/support/procedural/src/construct_runtime/parse.rs index 9b08e16469754..b407b897e4106 100644 --- a/frame/support/procedural/src/construct_runtime/parse.rs +++ b/frame/support/procedural/src/construct_runtime/parse.rs @@ -446,7 +446,7 @@ impl Parse for PalletPartKeyword { impl PalletPartKeyword { /// Returns the name of `Self`. - fn name(&self) -> &'static str { + pub fn name(&self) -> &'static str { match self { Self::Pallet(_) => "Pallet", Self::Call(_) => "Call", @@ -465,12 +465,12 @@ impl PalletPartKeyword { } /// Returns `true` if this pallet part is allowed to have generic arguments. - fn allows_generic(&self) -> bool { + pub fn allows_generic(&self) -> bool { Self::all_generic_arg().iter().any(|n| *n == self.name()) } /// Returns the names of all pallet parts that allow to have a generic argument. - fn all_generic_arg() -> &'static [&'static str] { + pub fn all_generic_arg() -> &'static [&'static str] { &["Event", "Error", "Origin", "Config"] } } @@ -552,7 +552,7 @@ fn remove_kind( /// The declaration of a part without its generics #[derive(Debug, Clone)] pub struct PalletPartNoGeneric { - keyword: PalletPartKeyword, + pub keyword: PalletPartKeyword, } impl Parse for PalletPartNoGeneric { diff --git a/frame/support/procedural/src/construct_runtime_v2/expand/mod.rs b/frame/support/procedural/src/construct_runtime_v2/expand/mod.rs new file mode 100644 index 0000000000000..e0953b5f2b4c3 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/expand/mod.rs @@ -0,0 +1,289 @@ +// 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_v2::{ + parse::pallets::{ + AllPalletsDeclaration, ExplicitAllPalletsDeclaration, ImplicitAllPalletsDeclaration, + }, + Def, +}; +use cfg_expr::Predicate; +use frame_support_procedural_tools::{ + generate_crate_access, generate_crate_access_2018, generate_hidden_includes, +}; +use proc_macro2::TokenStream as TokenStream2; +use quote::quote; +use std::collections::HashSet; +use syn::{Ident, Result}; + +use crate::construct_runtime::{check_pallet_number, expand}; + +use crate::construct_runtime::{ + decl_all_pallets, decl_integrity_test, decl_pallet_runtime_setup, decl_static_assertions, +}; + +use super::parse::runtime_types::RuntimeType; + +/// The fixed name of the system pallet. +const SYSTEM_PALLET_NAME: &str = "System"; + +pub fn expand(def: Def) -> proc_macro2::TokenStream { + let input = def.input; + + let res = match def.pallets { + AllPalletsDeclaration::Implicit(ref decl) => + check_pallet_number(input.clone(), decl.pallet_count) + .and_then(|_| construct_runtime_implicit_to_explicit(input.into(), decl.clone())), + AllPalletsDeclaration::Explicit(ref decl) => check_pallet_number(input, decl.pallets.len()) + .and_then(|_| { + construct_runtime_final_expansion( + def.runtime_struct.ident.clone(), + decl.clone(), + def.runtime_types.clone(), + ) + }), + }; + + let res = res.unwrap_or_else(|e| e.to_compile_error()); + + let res = expander::Expander::new("construct_runtime") + .dry(std::env::var("FRAME_EXPAND").is_err()) + .verbose(true) + .write_to_out_dir(res) + .expect("Does not fail because of IO in OUT_DIR; qed"); + + res.into() +} + +fn construct_runtime_implicit_to_explicit( + input: TokenStream2, + definition: ImplicitAllPalletsDeclaration, +) -> Result { + let frame_support = generate_crate_access_2018("frame-support")?; + let mut expansion = quote::quote!( + #[frame_support::construct_runtime_v2] + #input + ); + for pallet in definition.pallet_decls.iter() { + let pallet_path = &pallet.path; + let pallet_name = &pallet.name; + let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(<#instance>)); + expansion = quote::quote!( + #frame_support::tt_call! { + macro = [{ #pallet_path::tt_default_parts_v2 }] + frame_support = [{ #frame_support }] + ~~> #frame_support::match_and_insert! { + target = [{ #expansion }] + pattern = [{ #pallet_name: #pallet_path #pallet_instance }] + } + } + ); + } + + Ok(expansion) +} + +fn construct_runtime_final_expansion( + name: Ident, + definition: ExplicitAllPalletsDeclaration, + runtime_types: Vec, +) -> Result { + let ExplicitAllPalletsDeclaration { pallets, name: pallets_name } = definition; + + let system_pallet = + pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| { + syn::Error::new( + pallets_name.span(), + "`System` pallet declaration is missing. \ + Please add this line: `System: frame_system::{Pallet, Call, Storage, Config, Event},`", + ) + })?; + if !system_pallet.cfg_pattern.is_empty() { + return Err(syn::Error::new( + system_pallet.name.span(), + "`System` pallet declaration is feature gated, please remove any `#[cfg]` attributes", + )) + } + + let features = pallets + .iter() + .filter_map(|decl| { + (!decl.cfg_pattern.is_empty()).then(|| { + decl.cfg_pattern.iter().flat_map(|attr| { + attr.predicates().filter_map(|pred| match pred { + Predicate::Feature(feat) => Some(feat), + Predicate::Test => Some("test"), + _ => None, + }) + }) + }) + }) + .flatten() + .collect::>(); + + let hidden_crate_name = "construct_runtime"; + let scrate = generate_crate_access(hidden_crate_name, "frame-support"); + let scrate_decl = generate_hidden_includes(hidden_crate_name, "frame-support"); + + let frame_system = generate_crate_access_2018("frame-system")?; + let block = quote!(<#name as #frame_system::Config>::Block); + let unchecked_extrinsic = quote!(<#block as #scrate::sp_runtime::traits::Block>::Extrinsic); + + let mut dispatch = quote!(); + let mut outer_event = quote!(); + let mut outer_error = quote!(); + let mut outer_origin = quote!(); + let mut freeze_reason = quote!(); + let mut hold_reason = quote!(); + let mut slash_reason = quote!(); + let mut lock_id = quote!(); + + for runtime_type in runtime_types.iter() { + match runtime_type { + RuntimeType::RuntimeCall(_) => { + dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate); + }, + RuntimeType::RuntimeEvent(_) => { + outer_event = expand::expand_outer_enum( + &name, + &pallets, + &scrate, + expand::OuterEnumType::Event, + )?; + }, + RuntimeType::RuntimeError(_) => { + outer_error = expand::expand_outer_enum( + &name, + &pallets, + &scrate, + expand::OuterEnumType::Error, + )?; + }, + RuntimeType::RuntimeOrigin(_) => { + outer_origin = + expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?; + }, + RuntimeType::RuntimeFreezeReason(_) => { + freeze_reason = expand::expand_outer_freeze_reason(&pallets, &scrate); + }, + RuntimeType::RuntimeHoldReason(_) => { + hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate); + }, + RuntimeType::RuntimeSlashReason(_) => { + slash_reason = expand::expand_outer_slash_reason(&pallets, &scrate); + }, + RuntimeType::RuntimeLockId(_) => { + lock_id = expand::expand_outer_lock_id(&pallets, &scrate); + }, + } + } + + let all_pallets = decl_all_pallets(&name, pallets.iter(), &features); + let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate); + + let metadata = expand::expand_runtime_metadata( + &name, + &pallets, + &scrate, + &unchecked_extrinsic, + &system_pallet.path, + ); + let outer_config = expand::expand_outer_config(&name, &pallets, &scrate); + let inherent = + expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate); + let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate); + let integrity_test = decl_integrity_test(&scrate); + let static_assertions = decl_static_assertions(&name, &pallets, &scrate); + + let res = quote!( + #scrate_decl + + // Prevent UncheckedExtrinsic to print unused warning. + const _: () = { + #[allow(unused)] + type __hidden_use_of_unchecked_extrinsic = #unchecked_extrinsic; + }; + + #[derive( + Clone, Copy, PartialEq, Eq, #scrate::sp_runtime::RuntimeDebug, + #scrate::scale_info::TypeInfo + )] + pub struct #name; + impl #scrate::sp_runtime::traits::GetRuntimeBlockType for #name { + type RuntimeBlock = #block; + } + + // Each runtime must expose the `runtime_metadata()` to fetch the runtime API metadata. + // The function is implemented by calling `impl_runtime_apis!`. + // + // However, the `construct_runtime!` may be called without calling `impl_runtime_apis!`. + // Rely on the `Deref` trait to differentiate between a runtime that implements + // APIs (by macro impl_runtime_apis!) and a runtime that is simply created (by macro construct_runtime!). + // + // Both `InternalConstructRuntime` and `InternalImplRuntimeApis` expose a `runtime_metadata()` function. + // `InternalConstructRuntime` is implemented by the `construct_runtime!` for Runtime references (`& Runtime`), + // while `InternalImplRuntimeApis` is implemented by the `impl_runtime_apis!` for Runtime (`Runtime`). + // + // Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!` + // when both macros are called; and will resolve an empty `runtime_metadata` when only the `construct_runtime!` + // is called. + + #[doc(hidden)] + trait InternalConstructRuntime { + #[inline(always)] + fn runtime_metadata(&self) -> #scrate::sp_std::vec::Vec<#scrate::metadata_ir::RuntimeApiMetadataIR> { + Default::default() + } + } + #[doc(hidden)] + impl InternalConstructRuntime for &#name {} + + #outer_event + + #outer_error + + #outer_origin + + #all_pallets + + #pallet_to_index + + #dispatch + + #metadata + + #outer_config + + #inherent + + #validate_unsigned + + #freeze_reason + + #hold_reason + + #lock_id + + #slash_reason + + #integrity_test + + #static_assertions + ); + + Ok(res) +} diff --git a/frame/support/procedural/src/construct_runtime_v2/mod.rs b/frame/support/procedural/src/construct_runtime_v2/mod.rs new file mode 100644 index 0000000000000..c24de0458bf8b --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/mod.rs @@ -0,0 +1,30 @@ +// 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. + +pub use parse::Def; +use proc_macro::TokenStream; + +mod expand; +mod parse; + +pub fn construct_runtime(_attrs: TokenStream, tokens: TokenStream) -> TokenStream { + let item = syn::parse_macro_input!(tokens as syn::ItemMod); + match parse::Def::try_from(item) { + Ok(def) => expand::expand(def).into(), + Err(e) => e.to_compile_error().into(), + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/helper.rs b/frame/support/procedural/src/construct_runtime_v2/parse/helper.rs new file mode 100644 index 0000000000000..b442370d3a68a --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/helper.rs @@ -0,0 +1,37 @@ +// 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::pallet::parse::helper::MutItemAttrs; +use quote::ToTokens; + +pub(crate) fn take_first_item_runtime_attr( + item: &mut impl MutItemAttrs, +) -> syn::Result> +where + Attr: syn::parse::Parse, +{ + let attrs = if let Some(attrs) = item.mut_item_attrs() { attrs } else { return Ok(None) }; + + if let Some(index) = attrs.iter().position(|attr| { + attr.path().segments.first().map_or(false, |segment| segment.ident == "frame") + }) { + let runtime_attr = attrs.remove(index); + Ok(Some(syn::parse2(runtime_attr.into_token_stream())?)) + } else { + Ok(None) + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/mod.rs b/frame/support/procedural/src/construct_runtime_v2/parse/mod.rs new file mode 100644 index 0000000000000..af871b1d5601a --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/mod.rs @@ -0,0 +1,144 @@ +// 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. + +pub mod helper; +pub mod pallet; +pub mod pallet_decl; +pub mod pallets; +pub mod runtime_struct; +pub mod runtime_types; + +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; +use syn::{spanned::Spanned, Token}; + +use frame_support_procedural_tools::syn_ext as ext; +use runtime_types::RuntimeType; + +mod keyword { + syn::custom_keyword!(frame); + syn::custom_keyword!(runtime); + syn::custom_keyword!(pallets); + syn::custom_keyword!(derive); +} + +enum RuntimeAttr { + Runtime(proc_macro2::Span), + Pallets(proc_macro2::Span), + Derive(proc_macro2::Span, Vec), +} + +impl RuntimeAttr { + fn span(&self) -> proc_macro2::Span { + match self { + Self::Runtime(span) => *span, + Self::Pallets(span) => *span, + Self::Derive(span, _) => *span, + } + } +} + +impl syn::parse::Parse for RuntimeAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::runtime) { + Ok(RuntimeAttr::Runtime(content.parse::()?.span())) + } else if lookahead.peek(keyword::pallets) { + Ok(RuntimeAttr::Pallets(content.parse::()?.span())) + } else if lookahead.peek(keyword::derive) { + let _ = content.parse::(); + let derive_content; + syn::parenthesized!(derive_content in content); + let runtime_types = + derive_content.parse::>()?; + let runtime_types = runtime_types.inner.into_iter().collect(); + Ok(RuntimeAttr::Derive(derive_content.span(), runtime_types)) + } else { + Err(lookahead.error()) + } + } +} + +pub struct Def { + pub input: TokenStream2, + pub item: syn::ItemMod, + pub runtime_struct: runtime_struct::RuntimeStructDef, + pub pallets: pallets::AllPalletsDeclaration, + pub runtime_types: Vec, +} + +impl Def { + pub fn try_from(mut item: syn::ItemMod) -> syn::Result { + let input: TokenStream2 = item.to_token_stream().into(); + let item_span = item.span(); + let items = &mut item + .content + .as_mut() + .ok_or_else(|| { + let msg = "Invalid runtime definition, expected mod to be inlined."; + syn::Error::new(item_span, msg) + })? + .1; + + let mut runtime_struct = None; + let mut pallets = None; + let mut runtime_types = None; + + for item in items.iter_mut() { + while let Some(runtime_attr) = + helper::take_first_item_runtime_attr::(item)? + { + match runtime_attr { + RuntimeAttr::Runtime(span) if runtime_struct.is_none() => { + let p = runtime_struct::RuntimeStructDef::try_from(span, item)?; + runtime_struct = Some(p); + }, + RuntimeAttr::Pallets(span) if pallets.is_none() => { + let p = pallets::AllPalletsDeclaration::try_from(span, item)?; + pallets = Some(p); + }, + RuntimeAttr::Derive(_, types) if runtime_types.is_none() => { + runtime_types = Some(types); + }, + attr => { + let msg = "Invalid duplicated attribute"; + return Err(syn::Error::new(attr.span(), msg)) + }, + } + } + } + + let def = Def { + input, + item, + runtime_struct: runtime_struct + .ok_or_else(|| syn::Error::new(item_span, "Missing `#[frame::runtime]`"))?, + pallets: pallets + .ok_or_else(|| syn::Error::new(item_span, "Missing `#[frame::pallets]`"))?, + runtime_types: runtime_types + .ok_or_else(|| syn::Error::new(item_span, "Missing `#[frame::runtime_types]`"))?, + }; + + Ok(def) + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/pallet.rs b/frame/support/procedural/src/construct_runtime_v2/parse/pallet.rs new file mode 100644 index 0000000000000..27522578c7fb8 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/pallet.rs @@ -0,0 +1,176 @@ +// 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::{Pallet, PalletPart, PalletPartKeyword, PalletPath}; +use quote::ToTokens; +use syn::{punctuated::Punctuated, spanned::Spanned, token, Error}; + +mod keyword { + syn::custom_keyword!(frame); + syn::custom_keyword!(pallet_index); + syn::custom_keyword!(disable_call); +} + +enum PalletAttr { + PalletIndex(proc_macro2::Span, u8), + DisableCall(proc_macro2::Span), +} + +impl PalletAttr { + fn span(&self) -> proc_macro2::Span { + match self { + Self::PalletIndex(span, _) => *span, + Self::DisableCall(span) => *span, + } + } +} + +impl syn::parse::Parse for PalletAttr { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + input.parse::()?; + let content; + syn::bracketed!(content in input); + content.parse::()?; + content.parse::()?; + + let lookahead = content.lookahead1(); + if lookahead.peek(keyword::pallet_index) { + let _ = content.parse::(); + let pallet_index_content; + syn::parenthesized!(pallet_index_content in content); + let pallet_index = pallet_index_content.parse::()?; + if !pallet_index.suffix().is_empty() { + let msg = "Number literal must not have a suffix"; + return Err(syn::Error::new(pallet_index.span(), msg)) + } + Ok(PalletAttr::PalletIndex(pallet_index.span(), pallet_index.base10_parse()?)) + } else if lookahead.peek(keyword::disable_call) { + Ok(PalletAttr::DisableCall(content.parse::()?.span())) + } else { + Err(lookahead.error()) + } + } +} + +fn take_first_item_pallet_attr(item: &mut syn::Field) -> syn::Result> +where + Attr: syn::parse::Parse, +{ + let attrs = &mut item.attrs; + + if let Some(index) = attrs.iter().position(|attr| { + attr.path().segments.first().map_or(false, |segment| segment.ident == "frame") + }) { + let runtime_attr = attrs.remove(index); + Ok(Some(syn::parse2(runtime_attr.into_token_stream())?)) + } else { + Ok(None) + } +} + +impl Pallet { + pub fn try_from( + attr_span: proc_macro2::Span, + index: u8, + item: &mut syn::Field, + bounds: &Punctuated, + ) -> syn::Result { + let name = item + .ident + .clone() + .ok_or(Error::new(attr_span, "Invalid pallet declaration, expected a named field"))?; + + let mut pallet_index = index; + let mut disable_call = false; + + while let Some(pallet_attr) = take_first_item_pallet_attr::(item)? { + match pallet_attr { + PalletAttr::PalletIndex(_, index) => pallet_index = index, + PalletAttr::DisableCall(_) => disable_call = true, + } + } + + let mut pallet_path = None; + let mut pallet_parts = vec![]; + + for (index, bound) in bounds.into_iter().enumerate() { + if let syn::TypeParamBound::Trait(syn::TraitBound { path, .. }) = bound { + if index == 0 { + pallet_path = Some(PalletPath { inner: path.clone() }); + } else { + let pallet_part = syn::parse2::(bound.into_token_stream())?; + pallet_parts.push(pallet_part); + } + } else { + return Err(Error::new( + attr_span, + "Invalid pallet declaration, expected a path or a trait object", + )) + }; + } + + let mut path = pallet_path.ok_or(Error::new( + attr_span, + "Invalid pallet declaration, expected a path or a trait object", + ))?; + + let mut instance = None; + // Todo: revisit this + if let Some(segment) = path.inner.segments.iter_mut().find(|seg| !seg.arguments.is_empty()) + { + if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + args, + .. + }) = segment.arguments.clone() + { + if let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.first() { + instance = Some(syn::Ident::new( + &arg_path.to_token_stream().to_string(), + arg_path.span(), + )); + segment.arguments = syn::PathArguments::None; + } + } + } + + if disable_call { + pallet_parts = + pallet_parts + .into_iter() + .filter(|part| { + if let PalletPartKeyword::Call(_) = part.keyword { + false + } else { + true + } + }) + .collect(); + } + + let cfg_pattern = vec![]; + + Ok(Pallet { + is_expanded: true, + name, + index: pallet_index, + path, + instance, + cfg_pattern, + pallet_parts, + }) + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/pallet_decl.rs b/frame/support/procedural/src/construct_runtime_v2/parse/pallet_decl.rs new file mode 100644 index 0000000000000..4245a7b40e8b0 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/pallet_decl.rs @@ -0,0 +1,72 @@ +// 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, Attribute, Error, Ident}; + +/// The declaration of a pallet. +#[derive(Debug, Clone)] +pub struct PalletDeclaration { + /// The name of the pallet, e.g.`System` in `System: frame_system`. + pub name: Ident, + /// Optional attributes tagged right above a pallet declaration. + pub attrs: Vec, + /// Optional fixed index, e.g. `MyPallet ... = 3,`. + pub index: Option, + /// The path of the pallet, e.g. `frame_system` in `System: frame_system`. + pub path: syn::Path, + /// The instance of the pallet, e.g. `Instance1` in `Council: pallet_collective::`. + pub instance: Option, +} + +impl PalletDeclaration { + pub fn try_from( + attr_span: proc_macro2::Span, + index: usize, + item: &mut syn::Field, + path: &syn::TypePath, + ) -> syn::Result { + let name = item + .ident + .clone() + .ok_or(Error::new(attr_span, "Invalid pallet declaration, expected a named field"))?; + + let mut path = path.path.clone(); + + let mut instance = None; + // Todo: revisit this + if let Some(segment) = path.segments.iter_mut().find(|seg| !seg.arguments.is_empty()) { + if let syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments { + args, + .. + }) = segment.arguments.clone() + { + if let Some(syn::GenericArgument::Type(syn::Type::Path(arg_path))) = args.first() { + instance = Some(syn::Ident::new( + &arg_path.to_token_stream().to_string(), + arg_path.span(), + )); + segment.arguments = syn::PathArguments::None; + } + } + } + + let index = Some(index as u8); + + Ok(Self { name, path, instance, index, attrs: item.attrs.clone() }) + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/pallets.rs b/frame/support/procedural/src/construct_runtime_v2/parse/pallets.rs new file mode 100644 index 0000000000000..19e27a6567d71 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/pallets.rs @@ -0,0 +1,117 @@ +// 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::Pallet, construct_runtime_v2::parse::pallet_decl::PalletDeclaration, +}; +use std::collections::{HashMap, HashSet}; +use syn::{spanned::Spanned, Ident}; + +#[derive(Debug, Clone)] +pub enum AllPalletsDeclaration { + Implicit(ImplicitAllPalletsDeclaration), + Explicit(ExplicitAllPalletsDeclaration), +} + +/// Declaration of a runtime with some pallet with implicit declaration of parts. +#[derive(Debug, Clone)] +pub struct ImplicitAllPalletsDeclaration { + pub name: Ident, + pub pallet_decls: Vec, + pub pallet_count: usize, +} + +/// Declaration of a runtime with all pallet having explicit declaration of parts. +#[derive(Debug, Clone)] +pub struct ExplicitAllPalletsDeclaration { + pub name: Ident, + pub pallets: Vec, +} + +impl AllPalletsDeclaration { + pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result { + let item = if let syn::Item::Struct(item) = item { + item + } else { + let msg = "Invalid frame::pallets, expected struct definition"; + return Err(syn::Error::new(item.span(), msg)) + }; + + let name = item.ident.clone(); + + let mut indices = HashMap::new(); + let mut names = HashMap::new(); + + let mut last_index: Option = None; + + let mut pallet_decls = vec![]; + let mut pallets = vec![]; + + for (index, item) in item.fields.iter_mut().enumerate() { + match item.ty.clone() { + syn::Type::Path(ref path) => { + let pallet_decl = PalletDeclaration::try_from(attr_span, index, item, path)?; + pallet_decls.push(pallet_decl); + }, + syn::Type::TraitObject(syn::TypeTraitObject { bounds, .. }) => { + let current_index = match last_index { + Some(index) => index.checked_add(1).ok_or_else(|| { + let msg = "Pallet index doesn't fit into u8, index is 256"; + syn::Error::new(name.span(), msg) + }), + None => Ok(0), + }?; + let pallet = Pallet::try_from(attr_span, current_index, item, &bounds)?; + + if let Some(used_pallet) = indices.insert(pallet.index, pallet.name.clone()) { + let msg = format!( + "Pallet indices are conflicting: Both pallets {} and {} are at index {}", + used_pallet, pallet.name, pallet.index, + ); + let mut err = syn::Error::new(used_pallet.span(), &msg); + err.combine(syn::Error::new(pallet.name.span(), msg)); + return Err(err) + } + + if let Some(used_pallet) = names.insert(pallet.name.clone(), pallet.name.span()) + { + let msg = "Two pallets with the same name!"; + + let mut err = syn::Error::new(used_pallet, &msg); + err.combine(syn::Error::new(pallet.name.span(), &msg)); + return Err(err) + } + + last_index = Some(pallet.index); + pallets.push(pallet); + }, + _ => continue, + } + } + + let decl_count = pallet_decls.len(); + if decl_count > 0 { + Ok(AllPalletsDeclaration::Implicit(ImplicitAllPalletsDeclaration { + name, + pallet_decls, + pallet_count: decl_count.saturating_add(pallets.len()), + })) + } else { + Ok(AllPalletsDeclaration::Explicit(ExplicitAllPalletsDeclaration { name, pallets })) + } + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/runtime_struct.rs b/frame/support/procedural/src/construct_runtime_v2/parse/runtime_struct.rs new file mode 100644 index 0000000000000..edc2d79eb5939 --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/runtime_struct.rs @@ -0,0 +1,35 @@ +// 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 syn::spanned::Spanned; +pub struct RuntimeStructDef { + pub ident: syn::Ident, + pub attr_span: proc_macro2::Span, +} + +impl RuntimeStructDef { + pub fn try_from(attr_span: proc_macro2::Span, item: &mut syn::Item) -> syn::Result { + let item = if let syn::Item::Struct(item) = item { + item + } else { + let msg = "Invalid frame::runtime, expected struct definition"; + return Err(syn::Error::new(item.span(), msg)) + }; + + Ok(Self { ident: item.ident.clone(), attr_span }) + } +} diff --git a/frame/support/procedural/src/construct_runtime_v2/parse/runtime_types.rs b/frame/support/procedural/src/construct_runtime_v2/parse/runtime_types.rs new file mode 100644 index 0000000000000..a645ade54a40b --- /dev/null +++ b/frame/support/procedural/src/construct_runtime_v2/parse/runtime_types.rs @@ -0,0 +1,70 @@ +// 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 syn::{ + parse::{Parse, ParseStream}, + Result, +}; + +mod keyword { + syn::custom_keyword!(RuntimeCall); + syn::custom_keyword!(RuntimeEvent); + syn::custom_keyword!(RuntimeError); + syn::custom_keyword!(RuntimeOrigin); + syn::custom_keyword!(RuntimeFreezeReason); + syn::custom_keyword!(RuntimeHoldReason); + syn::custom_keyword!(RuntimeSlashReason); + syn::custom_keyword!(RuntimeLockId); +} + +#[derive(Debug, Clone, PartialEq)] +pub enum RuntimeType { + RuntimeCall(keyword::RuntimeCall), + RuntimeEvent(keyword::RuntimeEvent), + RuntimeError(keyword::RuntimeError), + RuntimeOrigin(keyword::RuntimeOrigin), + RuntimeFreezeReason(keyword::RuntimeFreezeReason), + RuntimeHoldReason(keyword::RuntimeHoldReason), + RuntimeSlashReason(keyword::RuntimeSlashReason), + RuntimeLockId(keyword::RuntimeLockId), +} + +impl Parse for RuntimeType { + fn parse(input: ParseStream) -> Result { + let lookahead = input.lookahead1(); + + if lookahead.peek(keyword::RuntimeCall) { + Ok(Self::RuntimeCall(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeEvent) { + Ok(Self::RuntimeEvent(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeError) { + Ok(Self::RuntimeError(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeOrigin) { + Ok(Self::RuntimeOrigin(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeFreezeReason) { + Ok(Self::RuntimeFreezeReason(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeHoldReason) { + Ok(Self::RuntimeHoldReason(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeSlashReason) { + Ok(Self::RuntimeSlashReason(input.parse()?)) + } else if lookahead.peek(keyword::RuntimeLockId) { + Ok(Self::RuntimeLockId(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} diff --git a/frame/support/procedural/src/lib.rs b/frame/support/procedural/src/lib.rs index 2a46696ed4f70..69e94bd836b06 100644 --- a/frame/support/procedural/src/lib.rs +++ b/frame/support/procedural/src/lib.rs @@ -21,6 +21,7 @@ mod benchmark; mod construct_runtime; +mod construct_runtime_v2; mod crate_version; mod derive_impl; mod dummy_part_checker; @@ -186,6 +187,11 @@ pub fn construct_runtime(input: TokenStream) -> TokenStream { construct_runtime::construct_runtime(input) } +#[proc_macro_attribute] +pub fn construct_runtime_v2(attr: TokenStream, item: TokenStream) -> TokenStream { + construct_runtime_v2::construct_runtime(attr, item) +} + /// The pallet struct placeholder `#[pallet::pallet]` is mandatory and allows you to specify /// pallet information. /// diff --git a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs index 356bdbf67e923..371ec6c60ec2c 100644 --- a/frame/support/procedural/src/pallet/expand/tt_default_parts.rs +++ b/frame/support/procedural/src/pallet/expand/tt_default_parts.rs @@ -28,6 +28,8 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { syn::Ident::new(&format!("__tt_default_parts_{}", count), def.item.span()); let extra_parts_unique_id = syn::Ident::new(&format!("__tt_extra_parts_{}", count), def.item.span()); + let default_parts_unique_id_v2 = + syn::Ident::new(&format!("__tt_default_parts_v2_{}", count), def.item.span()); let call_part = def.call.as_ref().map(|_| quote::quote!(Call,)); @@ -79,6 +81,56 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { .any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_))) .then_some(quote::quote!(SlashReason,)); + let call_part_v2 = def.call.as_ref().map(|_| quote::quote!(+ Call)); + + let storage_part_v2 = (!def.storages.is_empty()).then(|| quote::quote!(+ Storage)); + + let event_part_v2 = def.event.as_ref().map(|event| { + let gen = event.gen_kind.is_generic().then(|| quote::quote!()); + quote::quote!(+ Event #gen) + }); + + let error_part_v2 = def.error.as_ref().map(|_| quote::quote!(+ Error)); + + let origin_part_v2 = def.origin.as_ref().map(|origin| { + let gen = origin.is_generic.then(|| quote::quote!()); + quote::quote!(+ Origin #gen) + }); + + let config_part_v2 = def.genesis_config.as_ref().map(|genesis_config| { + let gen = genesis_config.gen_kind.is_generic().then(|| quote::quote!()); + quote::quote!(+ Config #gen) + }); + + let inherent_part_v2 = def.inherent.as_ref().map(|_| quote::quote!(+ Inherent)); + + let validate_unsigned_part_v2 = + def.validate_unsigned.as_ref().map(|_| quote::quote!(+ ValidateUnsigned)); + + let freeze_reason_part_v2 = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::FreezeReason(_))) + .then_some(quote::quote!(+ FreezeReason)); + + let hold_reason_part_v2 = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::HoldReason(_))) + .then_some(quote::quote!(+ HoldReason)); + + let lock_id_part_v2 = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::LockId(_))) + .then_some(quote::quote!(+ LockId)); + + let slash_reason_part_v2 = def + .composites + .iter() + .any(|c| matches!(c.composite_keyword, CompositeKeyword::SlashReason(_))) + .then_some(quote::quote!(+ SlashReason)); + quote::quote!( // This macro follows the conventions as laid out by the `tt-call` crate. It does not // accept any arguments and simply returns the pallet parts, separated by commas, then @@ -138,5 +190,25 @@ pub fn expand_tt_default_parts(def: &mut Def) -> proc_macro2::TokenStream { } pub use #extra_parts_unique_id as tt_extra_parts; + + #[macro_export] + #[doc(hidden)] + macro_rules! #default_parts_unique_id_v2 { + { + $caller:tt + frame_support = [{ $($frame_support:ident)::* }] + } => { + $($frame_support)*::tt_return! { + $caller + tokens = [{ + + Pallet #call_part_v2 #storage_part_v2 #event_part_v2 #error_part_v2 #origin_part_v2 #config_part_v2 + #inherent_part_v2 #validate_unsigned_part_v2 #freeze_reason_part_v2 + #hold_reason_part_v2 #lock_id_part_v2 #slash_reason_part_v2 + }] + } + }; + } + + pub use #default_parts_unique_id_v2 as tt_default_parts_v2; ) } diff --git a/frame/support/procedural/src/pallet/mod.rs b/frame/support/procedural/src/pallet/mod.rs index 3618711051d7f..42d8272fb23ed 100644 --- a/frame/support/procedural/src/pallet/mod.rs +++ b/frame/support/procedural/src/pallet/mod.rs @@ -26,7 +26,7 @@ //! to user defined types. And also crate new types and implement block. mod expand; -mod parse; +pub(crate) mod parse; pub use parse::{composite::keyword::CompositeKeyword, Def}; use syn::spanned::Spanned; diff --git a/frame/support/src/lib.rs b/frame/support/src/lib.rs index 0c416c73766c8..bc4a5ba05f623 100644 --- a/frame/support/src/lib.rs +++ b/frame/support/src/lib.rs @@ -499,7 +499,8 @@ pub fn debug(data: &impl sp_std::fmt::Debug) { #[doc(inline)] pub use frame_support_procedural::{ - construct_runtime, match_and_insert, transactional, PalletError, RuntimeDebugNoBound, + construct_runtime, construct_runtime_v2, match_and_insert, transactional, PalletError, + RuntimeDebugNoBound, }; #[doc(hidden)]