referrerpolicy=no-referrer-when-downgrade

sp_api_proc_macro/
runtime_metadata.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 proc_macro2::TokenStream as TokenStream2;
19use quote::quote;
20use syn::{parse_quote, spanned::Spanned, ItemImpl, ItemTrait, Result};
21
22use crate::utils::{
23	extract_api_version, extract_impl_trait, filter_cfg_attributes, generate_crate_access,
24	generate_runtime_mod_name_for_trait, get_doc_literals, RequireQualifiedTraitPath,
25};
26
27/// Get the type parameter argument without lifetime or mutability
28/// of a runtime metadata function.
29///
30/// In the following example, both the `AccountId` and `Nonce` generic
31/// type parameters must implement `scale_info::TypeInfo` because they
32/// are added into the metadata using `scale_info::meta_type`.
33///
34/// ```ignore
35/// trait ExampleAccountNonceApi<AccountId, Nonce> {
36///   fn account_nonce<'a>(account: &'a AccountId) -> Nonce;
37/// }
38/// ```
39///
40/// Instead of returning `&'a AccountId` for the first parameter, this function
41/// returns `AccountId` to place bounds around it.
42fn get_type_param(ty: &syn::Type) -> syn::Type {
43	// Remove the lifetime and mutability of the type T to
44	// place bounds around it.
45	let ty_elem = match &ty {
46		syn::Type::Reference(reference) => &reference.elem,
47		syn::Type::Ptr(ptr) => &ptr.elem,
48		syn::Type::Slice(slice) => &slice.elem,
49		syn::Type::Array(arr) => &arr.elem,
50		_ => ty,
51	};
52
53	ty_elem.clone()
54}
55
56/// Extract the documentation from the provided attributes.
57///
58/// It takes into account the `no-metadata-docs` feature.
59fn collect_docs(attrs: &[syn::Attribute], crate_: &TokenStream2) -> TokenStream2 {
60	if cfg!(feature = "no-metadata-docs") {
61		quote!(#crate_::vec![])
62	} else {
63		let docs = get_doc_literals(&attrs);
64		quote!(#crate_::vec![ #( #docs, )* ])
65	}
66}
67
68/// Generate the runtime metadata of the provided trait.
69///
70/// The metadata is exposed as a generic function on the hidden module
71/// of the trait generated by the `decl_runtime_apis`.
72pub fn generate_decl_runtime_metadata<'a>(
73	decl: &ItemTrait,
74	versioned_methods_iter: impl Iterator<Item = (&'a syn::TraitItemFn, u32)>,
75) -> TokenStream2 {
76	let crate_ = generate_crate_access();
77	let mut methods = Vec::new();
78
79	// Ensure that any function parameter that relies on the `BlockT` bounds
80	// also has `TypeInfo + 'static` bounds (required by `scale_info::meta_type`).
81	//
82	// For example, if a runtime API defines a method that has an input:
83	// `fn func(input: <Block as BlockT>::Header)`
84	// then the runtime metadata will imply `<Block as BlockT>::Header: TypeInfo + 'static`.
85	//
86	// This restricts the bounds at the metadata level, without needing to modify the `BlockT`
87	// itself, since the concrete implementations are already satisfying `TypeInfo`.
88	let mut where_clause = Vec::new();
89	for (method, version) in versioned_methods_iter {
90		let mut inputs = Vec::new();
91		let signature = &method.sig;
92		for input in &signature.inputs {
93			// Exclude `self` from metadata collection.
94			let syn::FnArg::Typed(typed) = input else { continue };
95
96			let pat = &typed.pat;
97			let name = quote!(#pat).to_string();
98			let ty = &typed.ty;
99
100			where_clause.push(get_type_param(ty));
101
102			inputs.push(quote!(
103				#crate_::metadata_ir::RuntimeApiMethodParamMetadataIR {
104					name: #name,
105					ty: #crate_::scale_info::meta_type::<#ty>(),
106				}
107			));
108		}
109
110		let output = match &signature.output {
111			syn::ReturnType::Default => quote!(#crate_::scale_info::meta_type::<()>()),
112			syn::ReturnType::Type(_, ty) => {
113				where_clause.push(get_type_param(ty));
114				quote!(#crate_::scale_info::meta_type::<#ty>())
115			},
116		};
117
118		// String method name including quotes for constructing `v15::RuntimeApiMethodMetadata`.
119		let method_name = signature.ident.to_string();
120		let docs = collect_docs(&method.attrs, &crate_);
121
122		// Include the method metadata only if its `cfg` features are enabled.
123		let attrs = filter_cfg_attributes(&method.attrs);
124		let deprecation = match crate::utils::get_deprecation(&crate_, &method.attrs) {
125			Ok(deprecation) => deprecation,
126			Err(e) => return e.into_compile_error(),
127		};
128
129		// Methods are filtered so that only those whose version is <= the `impl_version` passed to
130		// `runtime_metadata` are kept in the metadata we hand back.
131		methods.push(quote!(
132			#( #attrs )*
133			if #version <= impl_version {
134				Some(#crate_::metadata_ir::RuntimeApiMethodMetadataIR {
135					name: #method_name,
136					inputs: #crate_::vec![ #( #inputs, )* ],
137					output: #output,
138					docs: #docs,
139					deprecation_info: #deprecation,
140				})
141			} else {
142				None
143			}
144		));
145	}
146
147	let trait_name_ident = &decl.ident;
148	let trait_name = trait_name_ident.to_string();
149	let docs = collect_docs(&decl.attrs, &crate_);
150	let deprecation = match crate::utils::get_deprecation(&crate_, &decl.attrs) {
151		Ok(deprecation) => deprecation,
152		Err(e) => return e.into_compile_error(),
153	};
154	let attrs = filter_cfg_attributes(&decl.attrs);
155	// The trait generics where already extended with `Block: BlockT`.
156	let mut generics = decl.generics.clone();
157	for generic_param in generics.params.iter_mut() {
158		let syn::GenericParam::Type(ty) = generic_param else { continue };
159
160		// Default type parameters are not allowed in functions.
161		ty.eq_token = None;
162		ty.default = None;
163	}
164
165	where_clause
166		.into_iter()
167		.map(|ty| parse_quote!(#ty: #crate_::scale_info::TypeInfo + 'static))
168		.for_each(|w| generics.make_where_clause().predicates.push(w));
169
170	let (impl_generics, _, where_clause) = generics.split_for_impl();
171
172	quote!(
173		#crate_::frame_metadata_enabled! {
174			#( #attrs )*
175			#[inline(always)]
176			pub fn runtime_metadata #impl_generics (impl_version: u32) -> #crate_::metadata_ir::RuntimeApiMetadataIR
177				#where_clause
178			{
179				#crate_::metadata_ir::RuntimeApiMetadataIR {
180					name: #trait_name,
181					methods: [ #( #methods, )* ]
182						.into_iter()
183						.filter_map(|maybe_m| maybe_m)
184						.collect(),
185					docs: #docs,
186					deprecation_info: #deprecation,
187					version: impl_version.into(),
188				}
189			}
190		}
191	)
192}
193
194/// Implement the `runtime_metadata` function on the runtime that
195/// generates the metadata for the given traits.
196///
197/// The metadata of each trait is extracted from the generic function
198/// exposed by `generate_decl_runtime_metadata`.
199pub fn generate_impl_runtime_metadata(impls: &[ItemImpl]) -> Result<TokenStream2> {
200	if impls.is_empty() {
201		return Ok(quote!());
202	}
203
204	let crate_ = generate_crate_access();
205
206	// Get the name of the runtime for which the traits are implemented.
207	let runtime_name = &impls
208		.get(0)
209		.expect("Traits should contain at least one implementation; qed")
210		.self_ty;
211
212	let mut metadata = Vec::new();
213
214	for impl_ in impls {
215		let mut trait_ = extract_impl_trait(&impl_, RequireQualifiedTraitPath::Yes)?.clone();
216
217		// Implementation traits are always references with a path `impl client::Core<generics> ...`
218		// The trait name is the last segment of this path.
219		let trait_name_ident = &trait_
220			.segments
221			.last()
222			.as_ref()
223			.expect("Trait path should always contain at least one item; qed")
224			.ident;
225
226		// Extract the generics from the trait to pass to the `runtime_metadata`
227		// function on the hidden module.
228		let generics = trait_
229			.segments
230			.iter()
231			.find_map(|segment| {
232				if let syn::PathArguments::AngleBracketed(generics) = &segment.arguments {
233					Some(generics.clone())
234				} else {
235					None
236				}
237			})
238			.expect("Trait path should always contain at least one generic parameter; qed");
239
240		let mod_name = generate_runtime_mod_name_for_trait(&trait_name_ident);
241		// Get absolute path to the `runtime_decl_for_` module by replacing the last segment.
242		if let Some(segment) = trait_.segments.last_mut() {
243			*segment = parse_quote!(#mod_name);
244		}
245
246		// Build a call to request runtime metadata for the appropriate API version.
247		let runtime_metadata_call = {
248			let api_version = extract_api_version(&impl_.attrs, impl_.span())?;
249
250			// If we've annotated an api_version, that defines the methods we need to impl.
251			// If we haven't, then by default we are implementing methods for whatever the
252			// base version of the declared runtime API is.
253			let base_version = if let Some(version) = api_version.custom {
254				quote! { #version }
255			} else {
256				quote! { #trait_::VERSION }
257			};
258
259			// Handle the case where eg `#[cfg_attr(feature = "foo", api_version(4))]` is
260			// present by using that version only when the feature is enabled and falling
261			// back to the above version if not.
262			if let Some(cfg_version) = api_version.feature_gated {
263				let cfg_feature = cfg_version.0;
264				let cfg_version = cfg_version.1;
265				quote! {{
266					if cfg!(feature = #cfg_feature) {
267						#trait_::runtime_metadata::#generics(#cfg_version)
268					} else {
269						#trait_::runtime_metadata::#generics(#base_version)
270					}
271				}}
272			} else {
273				quote! {
274					#trait_::runtime_metadata::#generics(#base_version)
275				}
276			}
277		};
278
279		let attrs = filter_cfg_attributes(&impl_.attrs);
280		metadata.push(quote!(
281			#( #attrs )*
282			#runtime_metadata_call
283		));
284	}
285
286	// Each runtime must expose the `runtime_metadata()` to fetch the runtime API metadata.
287	// The function is implemented by calling `impl_runtime_apis!`.
288	//
289	// However, the `construct_runtime!` may be called without calling `impl_runtime_apis!`.
290	// Rely on the `Deref` trait to differentiate between a runtime that implements
291	// APIs (by macro impl_runtime_apis!) and a runtime that is simply created (by macro
292	// construct_runtime!).
293	//
294	// Both `InternalConstructRuntime` and `InternalImplRuntimeApis` expose a `runtime_metadata()`
295	// function. `InternalConstructRuntime` is implemented by the `construct_runtime!` for Runtime
296	// references (`& Runtime`), while `InternalImplRuntimeApis` is implemented by the
297	// `impl_runtime_apis!` for Runtime (`Runtime`).
298	//
299	// Therefore, the `Deref` trait will resolve the `runtime_metadata` from `impl_runtime_apis!`
300	// when both macros are called; and will resolve an empty `runtime_metadata` when only the
301	// `construct_runtime!` is called.
302	Ok(quote!(
303		#crate_::frame_metadata_enabled! {
304			#[doc(hidden)]
305			impl #crate_::metadata_ir::InternalImplRuntimeApis for #runtime_name {
306				fn runtime_metadata(&self) -> #crate_::vec::Vec<#crate_::metadata_ir::RuntimeApiMetadataIR> {
307					#crate_::vec![ #( #metadata, )* ]
308				}
309			}
310		}
311	))
312}