referrerpolicy=no-referrer-when-downgrade

frame_support_procedural/construct_runtime/expand/
outer_enums.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License
17
18use crate::construct_runtime::Pallet;
19use proc_macro2::{Span, TokenStream};
20use quote::{quote, ToTokens};
21use syn::{Generics, Ident};
22
23/// Represents the types supported for creating an outer enum.
24#[derive(Clone, Copy, PartialEq)]
25pub enum OuterEnumType {
26	/// Collects the Event enums from all pallets.
27	Event,
28	/// Collects the Error enums from all pallets.
29	Error,
30}
31
32impl OuterEnumType {
33	/// The name of the structure this enum represents.
34	fn struct_name(&self) -> &str {
35		match self {
36			OuterEnumType::Event => "RuntimeEvent",
37			OuterEnumType::Error => "RuntimeError",
38		}
39	}
40
41	/// The name of the variant (ie `Event` or `Error`).
42	fn variant_name(&self) -> &str {
43		match self {
44			OuterEnumType::Event => "Event",
45			OuterEnumType::Error => "Error",
46		}
47	}
48}
49
50impl ToTokens for OuterEnumType {
51	fn to_tokens(&self, tokens: &mut TokenStream) {
52		match self {
53			OuterEnumType::Event => quote!(Event).to_tokens(tokens),
54			OuterEnumType::Error => quote!(Error).to_tokens(tokens),
55		}
56	}
57}
58
59/// Create an outer enum that encapsulates all pallets as variants.
60///
61/// Each variant represents a pallet and contains the corresponding type declared with either:
62/// - #[pallet::event] for the [`OuterEnumType::Event`] variant
63/// - #[pallet::error] for the [`OuterEnumType::Error`] variant
64///
65/// The name of the outer enum is prefixed with Runtime, resulting in names like RuntimeEvent
66/// or RuntimeError.
67///
68/// This structure facilitates the decoding process by leveraging the metadata.
69///
70/// # Example
71///
72/// The code generate looks like the following for [`OuterEnumType::Event`].
73///
74/// ```ignore
75/// enum RuntimeEvent {
76///     #[codec(index = 0)]
77///     System(pallet_system::Event),
78///
79///     #[codec(index = 5)]
80///     Balances(pallet_system::Event),
81/// }
82/// ```
83///
84/// Notice that the pallet index is preserved using the `#[codec(index = ..)]` attribute.
85pub fn expand_outer_enum(
86	runtime: &Ident,
87	pallet_decls: &[Pallet],
88	scrate: &TokenStream,
89	enum_ty: OuterEnumType,
90) -> syn::Result<TokenStream> {
91	// Stores all pallet variants.
92	let mut enum_variants = TokenStream::new();
93	// Generates the enum conversion between the `Runtime` outer enum and the pallet's enum.
94	let mut enum_conversions = TokenStream::new();
95	// Specific for events to query via `is_event_part_defined!`.
96	let mut query_enum_part_macros = Vec::new();
97
98	let enum_name_str = enum_ty.variant_name();
99	let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site());
100
101	for pallet_decl in pallet_decls {
102		let Some(pallet_entry) = pallet_decl.find_part(enum_name_str) else { continue };
103
104		let path = &pallet_decl.path;
105		let pallet_name = &pallet_decl.name;
106		let index = pallet_decl.index;
107		let instance = pallet_decl.instance.as_ref();
108		let generics = &pallet_entry.generics;
109
110		if instance.is_some() && generics.params.is_empty() {
111			let msg = format!(
112				"Instantiable pallet with no generic `{}` cannot \
113					be constructed: pallet `{}` must have generic `{}`",
114				enum_name_str, pallet_name, enum_name_str,
115			);
116			return Err(syn::Error::new(pallet_name.span(), msg))
117		}
118
119		let part_is_generic = !generics.params.is_empty();
120		let pallet_enum = match (instance, part_is_generic) {
121			(Some(inst), true) => quote!(#path::#enum_ty::<#runtime, #path::#inst>),
122			(Some(inst), false) => quote!(#path::#enum_ty::<#path::#inst>),
123			(None, true) => quote!(#path::#enum_ty::<#runtime>),
124			(None, false) => quote!(#path::#enum_ty),
125		};
126
127		enum_variants.extend(expand_enum_variant(
128			runtime,
129			pallet_decl,
130			index,
131			instance,
132			generics,
133			enum_ty,
134		));
135		enum_conversions.extend(expand_enum_conversion(
136			pallet_decl,
137			&pallet_enum,
138			&enum_name_ident,
139		));
140
141		if enum_ty == OuterEnumType::Event {
142			query_enum_part_macros.push(quote! {
143				#path::__substrate_event_check::is_event_part_defined!(#pallet_name);
144			});
145		}
146	}
147
148	// Derives specific for the event.
149	let event_custom_derives =
150		if enum_ty == OuterEnumType::Event { quote!(Clone, PartialEq, Eq,) } else { quote!() };
151
152	// Implementation specific for errors.
153	let error_custom_impl = generate_error_impl(scrate, enum_ty);
154
155	Ok(quote! {
156		#( #query_enum_part_macros )*
157
158		#[derive(
159			#event_custom_derives
160			#scrate::__private::codec::Encode,
161			#scrate::__private::codec::Decode,
162			#scrate::__private::codec::DecodeWithMemTracking,
163			#scrate::__private::scale_info::TypeInfo,
164			#scrate::__private::Debug,
165		)]
166		#[allow(non_camel_case_types)]
167		pub enum #enum_name_ident {
168			#enum_variants
169		}
170
171		#enum_conversions
172
173		#error_custom_impl
174	})
175}
176
177fn expand_enum_variant(
178	runtime: &Ident,
179	pallet: &Pallet,
180	index: u8,
181	instance: Option<&Ident>,
182	generics: &Generics,
183	enum_ty: OuterEnumType,
184) -> TokenStream {
185	let path = &pallet.path;
186	let variant_name = &pallet.name;
187	let part_is_generic = !generics.params.is_empty();
188	let attr = pallet.get_attributes();
189
190	match instance {
191		Some(inst) if part_is_generic => quote! {
192			#attr
193			#[codec(index = #index)]
194			#variant_name(#path::#enum_ty<#runtime, #path::#inst>),
195		},
196		Some(inst) => quote! {
197			#attr
198			#[codec(index = #index)]
199			#variant_name(#path::#enum_ty<#path::#inst>),
200		},
201		None if part_is_generic => quote! {
202			#attr
203			#[codec(index = #index)]
204			#variant_name(#path::#enum_ty<#runtime>),
205		},
206		None => quote! {
207			#attr
208			#[codec(index = #index)]
209			#variant_name(#path::#enum_ty),
210		},
211	}
212}
213
214fn expand_enum_conversion(
215	pallet: &Pallet,
216	pallet_enum: &TokenStream,
217	enum_name_ident: &Ident,
218) -> TokenStream {
219	let variant_name = &pallet.name;
220	let attr = pallet.get_attributes();
221
222	quote! {
223		#attr
224		#[allow(deprecated)]
225		impl From<#pallet_enum> for #enum_name_ident {
226			fn from(x: #pallet_enum) -> Self {
227				#enum_name_ident
228				::#variant_name(x)
229			}
230		}
231		#attr
232		#[allow(deprecated)]
233		impl TryInto<#pallet_enum> for #enum_name_ident {
234			type Error = ();
235
236			fn try_into(self) -> ::core::result::Result<#pallet_enum, Self::Error> {
237				match self {
238					Self::#variant_name(evt) => Ok(evt),
239					_ => Err(()),
240				}
241			}
242		}
243	}
244}
245
246fn generate_error_impl(scrate: &TokenStream, enum_ty: OuterEnumType) -> TokenStream {
247	// Implementation is specific to `Error`s.
248	if enum_ty == OuterEnumType::Event {
249		return quote! {}
250	}
251
252	let enum_name_ident = Ident::new(enum_ty.struct_name(), Span::call_site());
253
254	quote! {
255		impl #enum_name_ident {
256			/// Optionally convert the `DispatchError` into the `RuntimeError`.
257			///
258			/// Returns `Some` if the error matches the `DispatchError::Module` variant, otherwise `None`.
259			pub fn from_dispatch_error(err: #scrate::sp_runtime::DispatchError) -> Option<Self> {
260				let #scrate::sp_runtime::DispatchError::Module(module_error) = err else { return None };
261
262				let bytes = #scrate::__private::codec::Encode::encode(&module_error);
263				#scrate::__private::codec::Decode::decode(&mut &bytes[..]).ok()
264			}
265		}
266	}
267}