referrerpolicy=no-referrer-when-downgrade

sp_api_proc_macro/
mock_impl_runtime_apis.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::utils::{
19	extract_block_type_from_trait_path, extract_impl_trait,
20	extract_parameter_names_types_and_borrows, generate_crate_access, return_type_extract_type,
21	AllowSelfRefInParameters, RequireQualifiedTraitPath,
22};
23
24use proc_macro2::{Span, TokenStream};
25
26use quote::{quote, quote_spanned};
27
28use syn::{
29	fold::{self, Fold},
30	parse::{Error, Parse, ParseStream, Result},
31	parse_macro_input, parse_quote,
32	spanned::Spanned,
33	Attribute, ItemImpl, Pat, Type, TypePath,
34};
35
36/// The `advanced` attribute.
37///
38/// If this attribute is given to a function, the function gets access to the `Hash` as first
39/// parameter and needs to return a `Result` with the appropriate error type.
40const ADVANCED_ATTRIBUTE: &str = "advanced";
41
42/// The structure used for parsing the runtime api implementations.
43struct RuntimeApiImpls {
44	impls: Vec<ItemImpl>,
45}
46
47impl Parse for RuntimeApiImpls {
48	fn parse(input: ParseStream) -> Result<Self> {
49		let mut impls = Vec::new();
50
51		while !input.is_empty() {
52			impls.push(ItemImpl::parse(input)?);
53		}
54
55		if impls.is_empty() {
56			Err(Error::new(Span::call_site(), "No api implementation given!"))
57		} else {
58			Ok(Self { impls })
59		}
60	}
61}
62
63/// Implement the `ApiExt` trait and the `Core` runtime api.
64fn implement_common_api_traits(block_type: TypePath, self_ty: Type) -> Result<TokenStream> {
65	let crate_ = generate_crate_access();
66
67	Ok(quote!(
68		impl #crate_::ApiExt<#block_type> for #self_ty {
69			fn execute_in_transaction<F: FnOnce(&Self) -> #crate_::TransactionOutcome<R>, R>(
70				&self,
71				call: F,
72			) -> R where Self: Sized {
73				call(self).into_inner()
74			}
75
76			fn has_api<A: #crate_::RuntimeApiInfo + ?Sized>(
77				&self,
78				_: <Block as #crate_::BlockT>::Hash,
79			) -> std::result::Result<bool, #crate_::ApiError> where Self: Sized {
80				Ok(true)
81			}
82
83			fn has_api_with<A: #crate_::RuntimeApiInfo + ?Sized, P: Fn(u32) -> bool>(
84				&self,
85				_: <Block as #crate_::BlockT>::Hash,
86				pred: P,
87			) -> std::result::Result<bool, #crate_::ApiError> where Self: Sized {
88				Ok(pred(A::VERSION))
89			}
90
91			fn api_version<A: #crate_::RuntimeApiInfo + ?Sized>(
92				&self,
93				_: <Block as #crate_::BlockT>::Hash,
94			) -> std::result::Result<Option<u32>, #crate_::ApiError> where Self: Sized {
95				Ok(Some(A::VERSION))
96			}
97
98			fn record_proof(&mut self) {
99				unimplemented!("`record_proof` not implemented for runtime api mocks")
100			}
101
102			fn record_proof_with_recorder(&mut self, _: #crate_::ProofRecorder<#block_type>) {
103				unimplemented!("`record_proof_with_recorder` not implemented for runtime api mocks")
104			}
105
106			fn extract_proof(
107				&mut self,
108			) -> Option<#crate_::StorageProof> {
109				unimplemented!("`extract_proof` not implemented for runtime api mocks")
110			}
111
112			fn proof_recorder(&self) -> Option<#crate_::ProofRecorder<#block_type>> {
113				unimplemented!("`proof_recorder` not implemented for runtime api mocks")
114			}
115
116			fn into_storage_changes<B: #crate_::StateBackend<#crate_::HashingFor<#block_type>>>(
117				&self,
118				_: &B,
119				_: <#block_type as #crate_::BlockT>::Hash,
120			) -> std::result::Result<
121				#crate_::StorageChanges<#block_type>,
122				String
123			> where Self: Sized {
124				unimplemented!("`into_storage_changes` not implemented for runtime api mocks")
125			}
126
127			fn set_call_context(&mut self, _: #crate_::CallContext) {
128				unimplemented!("`set_call_context` not implemented for runtime api mocks")
129			}
130
131			fn register_extension<E: #crate_::Extension>(&mut self, _: E) {
132				unimplemented!("`register_extension` not implemented for runtime api mocks")
133			}
134		}
135
136		impl #crate_::Core<#block_type> for #self_ty {
137			fn __runtime_api_internal_call_api_at(
138				&self,
139				_: <#block_type as #crate_::BlockT>::Hash,
140				_: std::vec::Vec<u8>,
141				_: &dyn Fn(#crate_::RuntimeVersion) -> &'static str,
142			) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> {
143				unimplemented!("`__runtime_api_internal_call_api_at` not implemented for runtime api mocks")
144			}
145
146			fn version(
147				&self,
148				_: <#block_type as #crate_::BlockT>::Hash,
149			) -> std::result::Result<#crate_::RuntimeVersion, #crate_::ApiError> {
150				unimplemented!("`Core::version` not implemented for runtime api mocks")
151			}
152
153			fn execute_block(
154				&self,
155				_: <#block_type as #crate_::BlockT>::Hash,
156				_: #block_type,
157			) -> std::result::Result<(), #crate_::ApiError> {
158				unimplemented!("`Core::execute_block` not implemented for runtime api mocks")
159			}
160
161			fn initialize_block(
162				&self,
163				_: <#block_type as #crate_::BlockT>::Hash,
164				_: &<#block_type as #crate_::BlockT>::Header,
165			) -> std::result::Result<#crate_::__private::ExtrinsicInclusionMode, #crate_::ApiError> {
166				unimplemented!("`Core::initialize_block` not implemented for runtime api mocks")
167			}
168		}
169	))
170}
171
172/// Returns if the advanced attribute is present in the given `attributes`.
173///
174/// If the attribute was found, it will be automatically removed from the vec.
175fn has_advanced_attribute(attributes: &mut Vec<Attribute>) -> bool {
176	let mut found = false;
177	attributes.retain(|attr| {
178		if attr.path().is_ident(ADVANCED_ATTRIBUTE) {
179			found = true;
180			false
181		} else {
182			true
183		}
184	});
185
186	found
187}
188
189/// Get the name and type of the `at` parameter that is passed to a runtime api function.
190///
191/// If `is_advanced` is `false`, the name is `_`.
192fn get_at_param_name(
193	is_advanced: bool,
194	param_names: &mut Vec<Pat>,
195	param_types_and_borrows: &mut Vec<(TokenStream, bool)>,
196	function_span: Span,
197	default_hash_type: &TokenStream,
198) -> Result<(TokenStream, TokenStream)> {
199	if is_advanced {
200		if param_names.is_empty() {
201			return Err(Error::new(
202				function_span,
203				format!(
204					"If using the `{}` attribute, it is required that the function \
205					 takes at least one argument, the `Hash`.",
206					ADVANCED_ATTRIBUTE,
207				),
208			))
209		}
210
211		// `param_names` and `param_types` have the same length, so if `param_names` is not empty
212		// `param_types` can not be empty as well.
213		let ptype_and_borrows = param_types_and_borrows.remove(0);
214		let span = ptype_and_borrows.1.span();
215		if ptype_and_borrows.1 {
216			return Err(Error::new(span, "`Hash` needs to be taken by value and not by reference!"))
217		}
218
219		let name = param_names.remove(0);
220		Ok((quote!( #name ), ptype_and_borrows.0))
221	} else {
222		Ok((quote!(_), default_hash_type.clone()))
223	}
224}
225
226/// Auxiliary structure to fold a runtime api trait implementation into the expected format.
227///
228/// This renames the methods, changes the method parameters and extracts the error type.
229struct FoldRuntimeApiImpl<'a> {
230	/// The block type that is being used.
231	block_type: &'a TypePath,
232}
233
234impl<'a> FoldRuntimeApiImpl<'a> {
235	/// Process the given [`syn::ItemImpl`].
236	fn process(mut self, impl_item: syn::ItemImpl) -> syn::ItemImpl {
237		let mut impl_item = self.fold_item_impl(impl_item);
238
239		let crate_ = generate_crate_access();
240
241		let block_type = self.block_type;
242
243		impl_item.items.push(parse_quote! {
244			fn __runtime_api_internal_call_api_at(
245				&self,
246				_: <#block_type as #crate_::BlockT>::Hash,
247				_: std::vec::Vec<u8>,
248				_: &dyn Fn(#crate_::RuntimeVersion) -> &'static str,
249			) -> std::result::Result<std::vec::Vec<u8>, #crate_::ApiError> {
250				unimplemented!(
251					"`__runtime_api_internal_call_api_at` not implemented for runtime api mocks. \
252					 Calling deprecated methods is not supported by mocked runtime api."
253				)
254			}
255		});
256
257		impl_item
258	}
259}
260
261impl<'a> Fold for FoldRuntimeApiImpl<'a> {
262	fn fold_impl_item_fn(&mut self, mut input: syn::ImplItemFn) -> syn::ImplItemFn {
263		let block = {
264			let crate_ = generate_crate_access();
265			let is_advanced = has_advanced_attribute(&mut input.attrs);
266			let mut errors = Vec::new();
267
268			let (mut param_names, mut param_types_and_borrows) =
269				match extract_parameter_names_types_and_borrows(
270					&input.sig,
271					AllowSelfRefInParameters::YesButIgnore,
272				) {
273					Ok(res) => (
274						res.iter().map(|v| v.0.clone()).collect::<Vec<_>>(),
275						res.iter()
276							.map(|v| {
277								let ty = &v.1;
278								let borrow = &v.2;
279								(quote_spanned!(ty.span() => #borrow #ty ), v.2.is_some())
280							})
281							.collect::<Vec<_>>(),
282					),
283					Err(e) => {
284						errors.push(e.to_compile_error());
285
286						(Default::default(), Default::default())
287					},
288				};
289
290			let block_type = &self.block_type;
291			let hash_type = quote!( <#block_type as #crate_::BlockT>::Hash );
292
293			let (at_param_name, hash_type) = match get_at_param_name(
294				is_advanced,
295				&mut param_names,
296				&mut param_types_and_borrows,
297				input.span(),
298				&hash_type,
299			) {
300				Ok(res) => res,
301				Err(e) => {
302					errors.push(e.to_compile_error());
303					(quote!(_), hash_type)
304				},
305			};
306
307			let param_types = param_types_and_borrows.iter().map(|v| &v.0);
308			// Rewrite the input parameters.
309			input.sig.inputs = parse_quote! {
310				&self,
311				#at_param_name: #hash_type,
312				#( #param_names: #param_types ),*
313			};
314
315			// When using advanced, the user needs to declare the correct return type on its own,
316			// otherwise do it for the user.
317			if !is_advanced {
318				let ret_type = return_type_extract_type(&input.sig.output);
319
320				// Generate the correct return type.
321				input.sig.output = parse_quote!(
322					-> std::result::Result<#ret_type, #crate_::ApiError>
323				);
324			}
325
326			let orig_block = input.block.clone();
327
328			let construct_return_value = if is_advanced {
329				quote!( (move || #orig_block)() )
330			} else {
331				quote! {
332					let __fn_implementation__ = move || #orig_block;
333
334					Ok(__fn_implementation__())
335				}
336			};
337
338			// Generate the new method implementation that calls into the runtime.
339			parse_quote!(
340				{
341					// Get the error to the user (if we have one).
342					#( #errors )*
343
344					#construct_return_value
345				}
346			)
347		};
348
349		let mut input = fold::fold_impl_item_fn(self, input);
350		// We need to set the block, after we modified the rest of the ast, otherwise we would
351		// modify our generated block as well.
352		input.block = block;
353		input
354	}
355}
356
357/// Result of [`generate_runtime_api_impls`].
358struct GeneratedRuntimeApiImpls {
359	/// All the runtime api implementations.
360	impls: TokenStream,
361	/// The block type that is being used by the runtime apis.
362	block_type: TypePath,
363	/// The type the traits are implemented for.
364	self_ty: Type,
365}
366
367/// Generate the runtime api implementations from the given trait implementations.
368///
369/// This folds the method names, changes the method parameters, method return type,
370/// extracts the error type, self type and the block type.
371fn generate_runtime_api_impls(impls: &[ItemImpl]) -> Result<GeneratedRuntimeApiImpls> {
372	let mut result = Vec::with_capacity(impls.len());
373	let mut global_block_type: Option<TypePath> = None;
374	let mut self_ty: Option<Box<Type>> = None;
375
376	for impl_ in impls {
377		let impl_trait_path = extract_impl_trait(impl_, RequireQualifiedTraitPath::No)?;
378		let block_type = extract_block_type_from_trait_path(impl_trait_path)?;
379
380		self_ty = match self_ty.take() {
381			Some(self_ty) =>
382				if self_ty == impl_.self_ty {
383					Some(self_ty)
384				} else {
385					let mut error = Error::new(
386						impl_.self_ty.span(),
387						"Self type should not change between runtime apis",
388					);
389
390					error.combine(Error::new(self_ty.span(), "First self type found here"));
391
392					return Err(error)
393				},
394			None => Some(impl_.self_ty.clone()),
395		};
396
397		global_block_type = match global_block_type.take() {
398			Some(global_block_type) =>
399				if global_block_type == *block_type {
400					Some(global_block_type)
401				} else {
402					let mut error = Error::new(
403						block_type.span(),
404						"Block type should be the same between all runtime apis.",
405					);
406
407					error.combine(Error::new(
408						global_block_type.span(),
409						"First block type found here",
410					));
411
412					return Err(error)
413				},
414			None => Some(block_type.clone()),
415		};
416
417		result.push(FoldRuntimeApiImpl { block_type }.process(impl_.clone()));
418	}
419
420	Ok(GeneratedRuntimeApiImpls {
421		impls: quote!( #( #result )* ),
422		block_type: global_block_type.expect("There is a least one runtime api; qed"),
423		self_ty: *self_ty.expect("There is at least one runtime api; qed"),
424	})
425}
426
427/// The implementation of the `mock_impl_runtime_apis!` macro.
428pub fn mock_impl_runtime_apis_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
429	// Parse all impl blocks
430	let RuntimeApiImpls { impls: api_impls } = parse_macro_input!(input as RuntimeApiImpls);
431
432	mock_impl_runtime_apis_impl_inner(&api_impls)
433		.unwrap_or_else(|e| e.to_compile_error())
434		.into()
435}
436
437fn mock_impl_runtime_apis_impl_inner(api_impls: &[ItemImpl]) -> Result<TokenStream> {
438	let GeneratedRuntimeApiImpls { impls, block_type, self_ty } =
439		generate_runtime_api_impls(api_impls)?;
440	let api_traits = implement_common_api_traits(block_type, self_ty)?;
441
442	Ok(quote!(
443		#impls
444
445		#api_traits
446	))
447}