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