frame_support_procedural/construct_runtime/
mod.rs1pub(crate) mod expand;
212pub(crate) mod parse;
213
214use crate::pallet::parse::helper::two128_str;
215use cfg_expr::Predicate;
216use frame_support_procedural_tools::{
217 generate_access_from_frame_or_crate, generate_crate_access, generate_hidden_includes,
218};
219use itertools::Itertools;
220use parse::{ExplicitRuntimeDeclaration, ImplicitRuntimeDeclaration, Pallet, RuntimeDeclaration};
221use proc_macro::TokenStream;
222use proc_macro2::TokenStream as TokenStream2;
223use quote::quote;
224use std::{collections::HashSet, str::FromStr};
225use syn::{spanned::Spanned, Ident, Result};
226
227const SYSTEM_PALLET_NAME: &str = "System";
229
230pub fn construct_runtime(input: TokenStream) -> TokenStream {
233 let input_copy = input.clone();
234 let definition = syn::parse_macro_input!(input as RuntimeDeclaration);
235
236 let (check_pallet_number_res, res) = match definition {
237 RuntimeDeclaration::Implicit(implicit_def) => (
238 check_pallet_number(input_copy.clone().into(), implicit_def.pallets.len()),
239 construct_runtime_implicit_to_explicit(input_copy.into(), implicit_def),
240 ),
241 RuntimeDeclaration::Explicit(explicit_decl) => (
242 check_pallet_number(input_copy.clone().into(), explicit_decl.pallets.len()),
243 construct_runtime_explicit_to_explicit_expanded(input_copy.into(), explicit_decl),
244 ),
245 RuntimeDeclaration::ExplicitExpanded(explicit_decl) => (
246 check_pallet_number(input_copy.into(), explicit_decl.pallets.len()),
247 construct_runtime_final_expansion(explicit_decl),
248 ),
249 };
250
251 let res = res.unwrap_or_else(|e| e.to_compile_error());
252
253 let res = if let Err(error) = check_pallet_number_res {
257 let error = error.to_compile_error();
258
259 quote! {
260 #error
261
262 #res
263 }
264 } else {
265 res
266 };
267
268 let res = expander::Expander::new("construct_runtime")
269 .dry(std::env::var("EXPAND_MACROS").is_err())
270 .verbose(true)
271 .write_to_out_dir(res)
272 .expect("Does not fail because of IO in OUT_DIR; qed");
273
274 res.into()
275}
276
277fn construct_runtime_implicit_to_explicit(
285 input: TokenStream2,
286 definition: ImplicitRuntimeDeclaration,
287) -> Result<TokenStream2> {
288 let frame_support = generate_access_from_frame_or_crate("frame-support")?;
289 let mut expansion = quote::quote!(
290 #frame_support::construct_runtime! { #input }
291 );
292 for pallet in definition.pallets.iter().filter(|pallet| pallet.pallet_parts.is_none()) {
293 let pallet_path = &pallet.path;
294 let pallet_name = &pallet.name;
295 let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>));
296 expansion = quote::quote!(
297 #frame_support::__private::tt_call! {
298 macro = [{ #pallet_path::tt_default_parts }]
299 your_tt_return = [{ #frame_support::__private::tt_return }]
300 ~~> #frame_support::match_and_insert! {
301 target = [{ #expansion }]
302 pattern = [{ #pallet_name: #pallet_path #pallet_instance }]
303 }
304 }
305 );
306 }
307
308 Ok(expansion)
309}
310
311fn construct_runtime_explicit_to_explicit_expanded(
321 input: TokenStream2,
322 definition: ExplicitRuntimeDeclaration,
323) -> Result<TokenStream2> {
324 let frame_support = generate_access_from_frame_or_crate("frame-support")?;
325 let mut expansion = quote::quote!(
326 #frame_support::construct_runtime! { #input }
327 );
328 for pallet in definition.pallets.iter().filter(|pallet| !pallet.is_expanded) {
329 let pallet_path = &pallet.path;
330 let pallet_name = &pallet.name;
331 let pallet_instance = pallet.instance.as_ref().map(|instance| quote::quote!(::<#instance>));
332 expansion = quote::quote!(
333 #frame_support::__private::tt_call! {
334 macro = [{ #pallet_path::tt_extra_parts }]
335 your_tt_return = [{ #frame_support::__private::tt_return }]
336 ~~> #frame_support::match_and_insert! {
337 target = [{ #expansion }]
338 pattern = [{ #pallet_name: #pallet_path #pallet_instance }]
339 }
340 }
341 );
342 }
343
344 Ok(expansion)
345}
346
347fn construct_runtime_final_expansion(
349 definition: ExplicitRuntimeDeclaration,
350) -> Result<TokenStream2> {
351 let ExplicitRuntimeDeclaration { name, pallets, pallets_token, where_section } = definition;
352
353 let system_pallet =
354 pallets.iter().find(|decl| decl.name == SYSTEM_PALLET_NAME).ok_or_else(|| {
355 syn::Error::new(
356 pallets_token.span.join(),
357 "`System` pallet declaration is missing. \
358 Please add this line: `System: frame_system,`",
359 )
360 })?;
361 if !system_pallet.cfg_pattern.is_empty() {
362 return Err(syn::Error::new(
363 system_pallet.name.span(),
364 "`System` pallet declaration is feature gated, please remove any `#[cfg]` attributes",
365 ))
366 }
367
368 let features = pallets
369 .iter()
370 .filter_map(|decl| {
371 (!decl.cfg_pattern.is_empty()).then(|| {
372 decl.cfg_pattern.iter().flat_map(|attr| {
373 attr.predicates().filter_map(|pred| match pred {
374 Predicate::Feature(feat) => Some(feat),
375 Predicate::Test => Some("test"),
376 _ => None,
377 })
378 })
379 })
380 })
381 .flatten()
382 .collect::<HashSet<_>>();
383
384 let hidden_crate_name = "construct_runtime";
385 let scrate = generate_crate_access(hidden_crate_name, "frame-support");
386 let scrate_decl = generate_hidden_includes(hidden_crate_name, "frame-support");
387
388 let frame_system = generate_access_from_frame_or_crate("frame-system")?;
389 let block = quote!(<#name as #frame_system::Config>::Block);
390 let unchecked_extrinsic = quote!(<#block as #scrate::sp_runtime::traits::Block>::Extrinsic);
391
392 let outer_event =
393 expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Event)?;
394 let outer_error =
395 expand::expand_outer_enum(&name, &pallets, &scrate, expand::OuterEnumType::Error)?;
396
397 let outer_origin = expand::expand_outer_origin(&name, system_pallet, &pallets, &scrate)?;
398 let all_pallets = decl_all_pallets(&name, pallets.iter(), &features);
399 let pallet_to_index = decl_pallet_runtime_setup(&name, &pallets, &scrate);
400
401 let dispatch = expand::expand_outer_dispatch(&name, system_pallet, &pallets, &scrate);
402 let tasks = expand::expand_outer_task(&name, &pallets, &scrate);
403 let metadata = expand::expand_runtime_metadata(
404 &name,
405 &pallets,
406 &scrate,
407 &unchecked_extrinsic,
408 &system_pallet.path,
409 );
410 let outer_config = expand::expand_outer_config(&name, &pallets, &scrate);
411 let inherent =
412 expand::expand_outer_inherent(&name, &block, &unchecked_extrinsic, &pallets, &scrate);
413 let validate_unsigned = expand::expand_outer_validate_unsigned(&name, &pallets, &scrate);
414 let freeze_reason = expand::expand_outer_freeze_reason(&pallets, &scrate);
415 let hold_reason = expand::expand_outer_hold_reason(&pallets, &scrate);
416 let lock_id = expand::expand_outer_lock_id(&pallets, &scrate);
417 let slash_reason = expand::expand_outer_slash_reason(&pallets, &scrate);
418 let integrity_test = decl_integrity_test(&scrate);
419 let static_assertions = decl_static_assertions(&name, &pallets, &scrate);
420
421 let warning = where_section.map_or(None, |where_section| {
422 Some(
423 proc_macro_warning::Warning::new_deprecated("WhereSection")
424 .old("use a `where` clause in `construct_runtime`")
425 .new(
426 "use `frame_system::Config` to set the `Block` type and delete this clause.
427 It is planned to be removed in December 2023",
428 )
429 .help_links(&["https://github.com/paritytech/substrate/pull/14437"])
430 .span(where_section.span)
431 .build_or_panic(),
432 )
433 });
434
435 let res = quote!(
436 #warning
437
438 #scrate_decl
439
440 const _: () = {
442 #[allow(unused)]
443 type __hidden_use_of_unchecked_extrinsic = #unchecked_extrinsic;
444 };
445
446 #[derive(
447 Clone, Copy, PartialEq, Eq, #scrate::sp_runtime::RuntimeDebug,
448 #scrate::__private::scale_info::TypeInfo
449 )]
450 pub struct #name;
451 impl #scrate::sp_runtime::traits::GetRuntimeBlockType for #name {
452 type RuntimeBlock = #block;
453 }
454
455 #[doc(hidden)]
471 trait InternalConstructRuntime {
472 #[inline(always)]
473 fn runtime_metadata(&self) -> #scrate::__private::Vec<#scrate::__private::metadata_ir::RuntimeApiMetadataIR> {
474 Default::default()
475 }
476 }
477 #[doc(hidden)]
478 impl InternalConstructRuntime for &#name {}
479
480 #outer_event
481
482 #outer_error
483
484 #outer_origin
485
486 #all_pallets
487
488 #pallet_to_index
489
490 #dispatch
491
492 #tasks
493
494 #metadata
495
496 #outer_config
497
498 #inherent
499
500 #validate_unsigned
501
502 #freeze_reason
503
504 #hold_reason
505
506 #lock_id
507
508 #slash_reason
509
510 #integrity_test
511
512 #static_assertions
513 );
514
515 Ok(res)
516}
517
518pub(crate) fn decl_all_pallets<'a>(
519 runtime: &'a Ident,
520 pallet_declarations: impl Iterator<Item = &'a Pallet>,
521 features: &HashSet<&str>,
522) -> TokenStream2 {
523 let mut types = TokenStream2::new();
524
525 let mut features_to_names = features
527 .iter()
528 .map(|f| *f)
529 .powerset()
530 .map(|feat| (HashSet::from_iter(feat), Vec::new()))
531 .collect::<Vec<(HashSet<_>, Vec<_>)>>();
532
533 for pallet_declaration in pallet_declarations {
534 let type_name = &pallet_declaration.name;
535 let pallet = &pallet_declaration.path;
536 let docs = &pallet_declaration.docs;
537 let mut generics = vec![quote!(#runtime)];
538 generics.extend(pallet_declaration.instance.iter().map(|name| quote!(#pallet::#name)));
539 let mut attrs = Vec::new();
540 for cfg in &pallet_declaration.cfg_pattern {
541 let feat = format!("#[cfg({})]\n", cfg.original());
542 attrs.extend(TokenStream2::from_str(&feat).expect("was parsed successfully; qed"));
543 }
544 let type_decl = quote!(
545 #( #[doc = #docs] )*
546 #(#attrs)*
547 pub type #type_name = #pallet::Pallet <#(#generics),*>;
548 );
549 types.extend(type_decl);
550
551 if pallet_declaration.cfg_pattern.is_empty() {
552 for (_, names) in features_to_names.iter_mut() {
553 names.push(&pallet_declaration.name);
554 }
555 } else {
556 for (feature_set, names) in &mut features_to_names {
557 let is_feature_active = pallet_declaration.cfg_pattern.iter().all(|expr| {
561 expr.eval(|pred| match pred {
562 Predicate::Feature(f) => feature_set.contains(f),
563 Predicate::Test => feature_set.contains(&"test"),
564 _ => false,
565 })
566 });
567
568 if is_feature_active {
569 names.push(&pallet_declaration.name);
570 }
571 }
572 }
573 }
574
575 let mut all_features = features_to_names
577 .iter()
578 .flat_map(|f| f.0.iter().cloned())
579 .collect::<HashSet<_>>();
580 let attribute_to_names = features_to_names
581 .into_iter()
582 .map(|(mut features, names)| {
583 if features.is_empty() {
587 let test_cfg = all_features.remove("test").then_some(quote!(test)).into_iter();
588 let features = all_features.iter();
589 let attr = quote!(#[cfg(all( #(not(#test_cfg)),* #(not(feature = #features)),* ))]);
590
591 (attr, names)
592 } else {
593 let test_cfg = features.remove("test").then_some(quote!(test)).into_iter();
594 let disabled_features = all_features.difference(&features);
595 let features = features.iter();
596 let attr = quote!(#[cfg(all( #(#test_cfg,)* #(feature = #features,)* #(not(feature = #disabled_features)),* ))]);
597
598 (attr, names)
599 }
600 })
601 .collect::<Vec<_>>();
602
603 let all_pallets_without_system = attribute_to_names.iter().map(|(attr, names)| {
604 let names = names.iter().filter(|n| **n != SYSTEM_PALLET_NAME);
605 quote! {
606 #attr
607 pub type AllPalletsWithoutSystem = ( #(#names,)* );
610 }
611 });
612
613 let all_pallets_with_system = attribute_to_names.iter().map(|(attr, names)| {
614 quote! {
615 #attr
616 pub type AllPalletsWithSystem = ( #(#names,)* );
618 }
619 });
620
621 quote!(
622 #types
623
624 #( #all_pallets_with_system )*
625
626 #( #all_pallets_without_system )*
627 )
628}
629
630pub(crate) fn decl_pallet_runtime_setup(
631 runtime: &Ident,
632 pallet_declarations: &[Pallet],
633 scrate: &TokenStream2,
634) -> TokenStream2 {
635 let names = pallet_declarations.iter().map(|d| &d.name).collect::<Vec<_>>();
636 let name_strings = pallet_declarations.iter().map(|d| d.name.to_string());
637 let name_hashes = pallet_declarations.iter().map(|d| two128_str(&d.name.to_string()));
638 let module_names = pallet_declarations.iter().map(|d| d.path.module_name());
639 let indices = pallet_declarations.iter().map(|pallet| pallet.index as usize);
640 let pallet_structs = pallet_declarations
641 .iter()
642 .map(|pallet| {
643 let path = &pallet.path;
644 match pallet.instance.as_ref() {
645 Some(inst) => quote!(#path::Pallet<#runtime, #path::#inst>),
646 None => quote!(#path::Pallet<#runtime>),
647 }
648 })
649 .collect::<Vec<_>>();
650 let pallet_attrs = pallet_declarations
651 .iter()
652 .map(|pallet| {
653 pallet.cfg_pattern.iter().fold(TokenStream2::new(), |acc, pattern| {
654 let attr = TokenStream2::from_str(&format!("#[cfg({})]", pattern.original()))
655 .expect("was successfully parsed before; qed");
656 quote! {
657 #acc
658 #attr
659 }
660 })
661 })
662 .collect::<Vec<_>>();
663
664 quote!(
665 pub struct PalletInfo;
668
669 impl #scrate::traits::PalletInfo for PalletInfo {
670
671 fn index<P: 'static>() -> Option<usize> {
672 let type_id = core::any::TypeId::of::<P>();
673 #(
674 #pallet_attrs
675 if type_id == core::any::TypeId::of::<#names>() {
676 return Some(#indices)
677 }
678 )*
679
680 None
681 }
682
683 fn name<P: 'static>() -> Option<&'static str> {
684 let type_id = core::any::TypeId::of::<P>();
685 #(
686 #pallet_attrs
687 if type_id == core::any::TypeId::of::<#names>() {
688 return Some(#name_strings)
689 }
690 )*
691
692 None
693 }
694
695 fn name_hash<P: 'static>() -> Option<[u8; 16]> {
696 let type_id = core::any::TypeId::of::<P>();
697 #(
698 #pallet_attrs
699 if type_id == core::any::TypeId::of::<#names>() {
700 return Some(#name_hashes)
701 }
702 )*
703
704 None
705 }
706
707 fn module_name<P: 'static>() -> Option<&'static str> {
708 let type_id = core::any::TypeId::of::<P>();
709 #(
710 #pallet_attrs
711 if type_id == core::any::TypeId::of::<#names>() {
712 return Some(#module_names)
713 }
714 )*
715
716 None
717 }
718
719 fn crate_version<P: 'static>() -> Option<#scrate::traits::CrateVersion> {
720 let type_id = core::any::TypeId::of::<P>();
721 #(
722 #pallet_attrs
723 if type_id == core::any::TypeId::of::<#names>() {
724 return Some(
725 <#pallet_structs as #scrate::traits::PalletInfoAccess>::crate_version()
726 )
727 }
728 )*
729
730 None
731 }
732 }
733 )
734}
735
736pub(crate) fn decl_integrity_test(scrate: &TokenStream2) -> TokenStream2 {
737 quote!(
738 #[cfg(test)]
739 mod __construct_runtime_integrity_test {
740 use super::*;
741
742 #[test]
743 pub fn runtime_integrity_tests() {
744 #scrate::__private::sp_tracing::try_init_simple();
745 <AllPalletsWithSystem as #scrate::traits::IntegrityTest>::integrity_test();
746 }
747 }
748 )
749}
750
751pub(crate) fn decl_static_assertions(
752 runtime: &Ident,
753 pallet_decls: &[Pallet],
754 scrate: &TokenStream2,
755) -> TokenStream2 {
756 let error_encoded_size_check = pallet_decls.iter().map(|decl| {
757 let path = &decl.path;
758 let assert_message = format!(
759 "The maximum encoded size of the error type in the `{}` pallet exceeds \
760 `MAX_MODULE_ERROR_ENCODED_SIZE`",
761 decl.name,
762 );
763
764 quote! {
765 #scrate::__private::tt_call! {
766 macro = [{ #path::tt_error_token }]
767 your_tt_return = [{ #scrate::__private::tt_return }]
768 ~~> #scrate::assert_error_encoded_size! {
769 path = [{ #path }]
770 runtime = [{ #runtime }]
771 assert_message = [{ #assert_message }]
772 }
773 }
774 }
775 });
776
777 quote! {
778 #(#error_encoded_size_check)*
779 }
780}
781
782pub(crate) fn check_pallet_number(input: TokenStream2, pallet_num: usize) -> Result<()> {
783 let max_pallet_num = {
784 if cfg!(feature = "tuples-96") {
785 96
786 } else if cfg!(feature = "tuples-128") {
787 128
788 } else {
789 64
790 }
791 };
792
793 if pallet_num > max_pallet_num {
794 let no_feature = max_pallet_num == 128;
795 return Err(syn::Error::new(
796 input.span(),
797 format!(
798 "{} To increase this limit, enable the tuples-{} feature of [frame_support]. {}",
799 "The number of pallets exceeds the maximum number of tuple elements.",
800 max_pallet_num + 32,
801 if no_feature {
802 "If the feature does not exist - it needs to be implemented."
803 } else {
804 ""
805 },
806 ),
807 ))
808 }
809
810 Ok(())
811}