pub(crate) mod expand;
pub(crate) mod parse;
use crate::pallet::parse::helper::two128_str;
use cfg_expr::Predicate;
use frame_support_procedural_tools::{
generate_access_from_frame_or_crate, generate_crate_access, generate_hidden_includes,
};
use itertools::Itertools;
use parse::{ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration};
use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use std::{collections::HashSet, str::FromStr};
use syn::{spanned::Spanned, Ident, Result};
const SYSTEM_PALLET_NAME: &str = "System";
pub fn construct_runtime(input: TokenStream) -> TokenStream {
let input_copy = input.clone();
let definition = syn::parse_macro_input!(input as RuntimeDeclaration);
let (check_pallet_number_res, res) = match definition {
RuntimeDeclaration::Implicit(implicit_def) => (
check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()),
construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def),
),
RuntimeDeclaration::Explicit(explicit_decl) => (
check_pallet_number(input_copy.clone().into(), explicit_decl.pallets.len()),
construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl),
),
RuntimeDeclaration::ExplicitExpanded(explicit_decl) => (
check_pallet_number(input_copy.into(), explicit_decl.pallets.len()),
construct_runtime_final_expansion(explicit_decl),
),
};
let res = res.unwrap_or_else(|e| e.to_compile_error());
let res = if let Err(error) = check_pallet_number_res {
let error = error.to_compile_error();
quote! {
#error
#res
}
} else {
res
};
let res = expander::Expander::new("construct_runtime")
.dry(std::env::var("EXPAND_MACROS").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: ImplicitRuntimeDeclaration,
) -> Result<TokenStream2> {
let frame_support = generate_access_from_frame_or_crate("frame-support")?;
let mut expansion = quote::quote!(
#frame_support::construct_runtime! { #input }
);
for pallet in definition.pallets.iter().filter(|pallet| pallet.pallet_parts.is_none()) {
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::__private::tt_call! {
macro = [{ #pallet_path::tt_default_parts }]
your_tt_return = [{ #frame_support::__private::tt_return }]
~~> #frame_support::match_and_insert! {
target = [{ #expansion }]
pattern = [{ #pallet_name: #pallet_path #pallet_instance }]
}
}
);
}
Ok(expansion)
}
fn construct_runtime_explicit_to_explicit_expanded(
input: TokenStream2,
definition: ExplicitRuntimeDeclaration,
) -> Result<TokenStream2> {
let frame_support = generate_access_from_frame_or_crate("frame-support")?;
let mut expansion = quote::quote!(
#frame_support::construct_runtime! { #input }
);
for pallet in definition.pallets.iter().filter(|pallet| !pallet.is_expanded) {
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::__private::tt_call! {
macro = [{ #pallet_path::tt_extra_parts }]
your_tt_return = [{ #frame_support::__private::tt_return }]
~~> #frame_support::match_and_insert! {
target = [{ #expansion }]
pattern = [{ #pallet_name: #pallet_path #pallet_instance }]
}
}
);
}
Ok(expansion)
}
fn construct_runtime_final_expansion(
definition: ExplicitRuntimeDeclaration,
) -> Result<TokenStream2> {
let ExplicitRuntimeDeclaration { name, pallets, pallets_token, where_section } = definition;
let system_pallet =
pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| {
syn::Error::new(
pallets_token.span.join(),
"`System` pallet declaration is missing. \
Please add this line: `System: frame_system,`",
)
})?;
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::<HashSet<_>>();
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_access_from_frame_or_crate("frame-system")?;
let block = quote!(<#name as #frame_system::Config>::Block);
let unchecked_extrinsic = quote!(<#block as #scrate::sp_runtime::traits::Block>::Extrinsic);
let outer_event =
expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Event)?;
let outer_error =
expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Error)?;
let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?;
let all_pallets = decl_all_pallets(&name, pallets.iter(), &features);
let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate);
let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
let query = expand::expand_outer_query(&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 freeze_reason = expand::expand_outer_freeze_reason(&pallets, &scrate);
let hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate);
let lock_id = expand::expand_outer_lock_id(&pallets, &scrate);
let slash_reason = expand::expand_outer_slash_reason(&pallets, &scrate);
let integrity_test = decl_integrity_test(&scrate);
let static_assertions = decl_static_assertions(&name, &pallets, &scrate);
let warning = where_section.map_or(None, |where_section| {
Some(
proc_macro_warning::Warning::new_deprecated("WhereSection")
.old("use a `where` clause in `construct_runtime`")
.new(
"use `frame_system::Config` to set the `Block` type and delete this clause.
It is planned to be removed in December 2023",
)
.help_links(&["https://github.com/paritytech/substrate/pull/14437"])
.span(where_section.span)
.build_or_panic(),
)
});
let res = quote!(
#warning
#scrate_decl
const _: () = {
#[allow(unused)]
type __hidden_use_of_unchecked_extrinsic = #unchecked_extrinsic;
};
#[derive(
Clone, Copy, PartialEq, Eq, core::fmt::Debug,
#scrate::__private::scale_info::TypeInfo
)]
pub struct #name;
impl #scrate::sp_runtime::traits::GetRuntimeBlockType for #name {
type RuntimeBlock = #block;
}
#[doc(hidden)]
trait InternalConstructRuntime {
#[inline(always)]
fn runtime_metadata(&self) -> #scrate::__private::Vec<#scrate::__private::metadata_ir::RuntimeApiMetadataIR> {
Default::default()
}
}
#[doc(hidden)]
impl InternalConstructRuntime for &#name {}
use #scrate::__private::metadata_ir::InternalImplRuntimeApis;
#outer_event
#outer_error
#outer_origin
#all_pallets
#pallet_to_index
#dispatch
#tasks
#query
#metadata
#outer_config
#inherent
#validate_unsigned
#freeze_reason
#hold_reason
#lock_id
#slash_reason
#integrity_test
#static_assertions
);
Ok(res)
}
pub(crate) fn decl_all_pallets<'a>(
runtime: &'a Ident,
pallet_declarations: impl Iterator<Item = &'a Pallet>,
features: &HashSet<&str>,
) -> TokenStream2 {
let mut types = TokenStream2::new();
let mut features_to_names = features
.iter()
.map(|f| *f)
.powerset()
.map(|feat| (HashSet::from_iter(feat), Vec::new()))
.collect::<Vec<(HashSet<_>, Vec<_>)>>();
for pallet_declaration in pallet_declarations {
let type_name = &pallet_declaration.name;
let pallet = &pallet_declaration.path;
let docs = &pallet_declaration.docs;
let mut generics = vec![quote!(#runtime)];
generics.extend(pallet_declaration.instance.iter().map(|name| quote!(#pallet::#name)));
let mut attrs = Vec::new();
for cfg in &pallet_declaration.cfg_pattern {
let feat = format!("#[cfg({})]\n", cfg.original());
attrs.extend(TokenStream2::from_str(&feat).expect("was parsed successfully; qed"));
}
let type_decl = quote!(
#( #[doc = #docs] )*
#(#attrs)*
pub type #type_name = #pallet::Pallet <#(#generics),*>;
);
types.extend(type_decl);
if pallet_declaration.cfg_pattern.is_empty() {
for (_, names) in features_to_names.iter_mut() {
names.push(&pallet_declaration.name);
}
} else {
for (feature_set, names) in &mut features_to_names {
let is_feature_active = pallet_declaration.cfg_pattern.iter().all(|expr| {
expr.eval(|pred| match pred {
Predicate::Feature(f) => feature_set.contains(f),
Predicate::Test => feature_set.contains(&"test"),
_ => false,
})
});
if is_feature_active {
names.push(&pallet_declaration.name);
}
}
}
}
let mut all_features = features_to_names
.iter()
.flat_map(|f| f.0.iter().cloned())
.collect::<HashSet<_>>();
let attribute_to_names = features_to_names
.into_iter()
.map(|(mut features, names)| {
if features.is_empty() {
let test_cfg = all_features.remove("test").then_some(quote!(test)).into_iter();
let features = all_features.iter();
let attr = quote!(#[cfg(all( #(not(#test_cfg)),* #(not(feature = #features)),* ))]);
(attr, names)
} else {
let test_cfg = features.remove("test").then_some(quote!(test)).into_iter();
let disabled_features = all_features.difference(&features);
let features = features.iter();
let attr = quote!(#[cfg(all( #(#test_cfg,)* #(feature = #features,)* #(not(feature = #disabled_features)),* ))]);
(attr, names)
}
})
.collect::<Vec<_>>();
let all_pallets_without_system = attribute_to_names.iter().map(|(attr, names)| {
let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME);
quote! {
#attr
pub type AllPalletsWithoutSystem = ( #(#names,)* );
}
});
let all_pallets_with_system = attribute_to_names.iter().map(|(attr, names)| {
quote! {
#attr
pub type AllPalletsWithSystem = ( #(#names,)* );
}
});
quote!(
#types
#( #all_pallets_with_system )*
#( #all_pallets_without_system )*
)
}
pub(crate) fn decl_pallet_runtime_setup(
runtime: &Ident,
pallet_declarations: &[Pallet],
scrate: &TokenStream2,
) -> TokenStream2 {
let names = pallet_declarations.iter().map(|d| &d.name).collect::<Vec<_>>();
let name_strings = pallet_declarations.iter().map(|d| d.name.to_string());
let name_hashes = pallet_declarations.iter().map(|d| two128_str(&d.name.to_string()));
let module_names = pallet_declarations.iter().map(|d| d.path.module_name());
let indices = pallet_declarations.iter().map(|pallet| pallet.index as usize);
let pallet_structs = pallet_declarations
.iter()
.map(|pallet| {
let path = &pallet.path;
match pallet.instance.as_ref() {
Some(inst) => quote!(#path::Pallet<#runtime, #path::#inst>),
None => quote!(#path::Pallet<#runtime>),
}
})
.collect::<Vec<_>>();
let pallet_attrs = pallet_declarations
.iter()
.map(|pallet| pallet.get_attributes())
.collect::<Vec<_>>();
quote!(
pub struct PalletInfo;
impl #scrate::traits::PalletInfo for PalletInfo {
fn index<P: 'static>() -> Option<usize> {
let type_id = core::any::TypeId::of::<P>();
#(
#pallet_attrs
if type_id == core::any::TypeId::of::<#names>() {
return Some(#indices)
}
)*
None
}
fn name<P: 'static>() -> Option<&'static str> {
let type_id = core::any::TypeId::of::<P>();
#(
#pallet_attrs
if type_id == core::any::TypeId::of::<#names>() {
return Some(#name_strings)
}
)*
None
}
fn name_hash<P: 'static>() -> Option<[u8; 16]> {
let type_id = core::any::TypeId::of::<P>();
#(
#pallet_attrs
if type_id == core::any::TypeId::of::<#names>() {
return Some(#name_hashes)
}
)*
None
}
fn module_name<P: 'static>() -> Option<&'static str> {
let type_id = core::any::TypeId::of::<P>();
#(
#pallet_attrs
if type_id == core::any::TypeId::of::<#names>() {
return Some(#module_names)
}
)*
None
}
fn crate_version<P: 'static>() -> Option<#scrate::traits::CrateVersion> {
let type_id = core::any::TypeId::of::<P>();
#(
#pallet_attrs
if type_id == core::any::TypeId::of::<#names>() {
return Some(
<#pallet_structs as #scrate::traits::PalletInfoAccess>::crate_version()
)
}
)*
None
}
}
)
}
pub(crate) fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 {
quote!(
#[cfg(test)]
mod __construct_runtime_integrity_test {
use super::*;
#[test]
pub fn runtime_integrity_tests() {
#scrate::__private::sp_tracing::try_init_simple();
<AllPalletsWithSystem as #scrate::traits::IntegrityTest>::integrity_test();
}
}
)
}
pub(crate) fn decl_static_assertions(
runtime: &Ident,
pallet_decls: &[Pallet],
scrate: &TokenStream2,
) -> TokenStream2 {
let error_encoded_size_check = pallet_decls.iter().map(|decl| {
let path = &decl.path;
let assert_message = format!(
"The maximum encoded size of the error type in the `{}` pallet exceeds \
`MAX_MODULE_ERROR_ENCODED_SIZE`",
decl.name,
);
quote! {
#[allow(deprecated)]
#scrate::__private::tt_call! {
macro = [{ #path::tt_error_token }]
your_tt_return = [{ #scrate::__private::tt_return }]
~~> #scrate::assert_error_encoded_size! {
path = [{ #path }]
runtime = [{ #runtime }]
assert_message = [{ #assert_message }]
}
}
}
});
quote! {
#(#error_encoded_size_check)*
}
}
pub(crate) fn check_pallet_number(input: TokenStream2, pallet_num: usize) -> Result<()> {
let max_pallet_num = {
if cfg!(feature = "tuples-96") {
96
} else if cfg!(feature = "tuples-128") {
128
} else {
64
}
};
if pallet_num > max_pallet_num {
let no_feature = max_pallet_num == 128;
return Err(syn::Error::new(
input.span(),
format!(
"{} To increase this limit, enable the tuples-{} feature of [frame_support]. {}",
"The number of pallets exceeds the maximum number of tuple elements.",
max_pallet_num + 32,
if no_feature {
"If the feature does not exist - it needs to be implemented."
} else {
""
},
),
));
}
Ok(())
}