precompile_utils_macro/precompile/
parse.rs

1// This file is part of Frontier.
2
3// Copyright (c) Moonsong Labs.
4// Copyright (C) Parity Technologies (UK) Ltd.
5// SPDX-License-Identifier: Apache-2.0
6
7// Licensed under the Apache License, Version 2.0 (the "License");
8// you may not use this file except in compliance with the License.
9// You may obtain a copy of the License at
10//
11// 	http://www.apache.org/licenses/LICENSE-2.0
12//
13// Unless required by applicable law or agreed to in writing, software
14// distributed under the License is distributed on an "AS IS" BASIS,
15// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16// See the License for the specific language governing permissions and
17// limitations under the License.
18
19use super::*;
20
21impl Precompile {
22	/// Try to extract information out of an annotated `impl` block.
23	pub fn try_from(impl_: &mut syn::ItemImpl) -> syn::Result<Self> {
24		// Extract the name of the type used in the `impl` block.
25		let impl_ident = Self::extract_impl_ident(impl_)?;
26		let enum_ident = format_ident!("{}Call", impl_ident);
27
28		// We setup the data collection struct.
29		let mut precompile = Precompile {
30			impl_type: impl_.self_ty.as_ref().clone(),
31			impl_ident,
32			enum_ident,
33			generics: impl_.generics.clone(),
34			selector_to_variant: BTreeMap::new(),
35			variants_content: BTreeMap::new(),
36			fallback_to_variant: None,
37			tagged_as_precompile_set: false,
38			precompile_set_discriminant_fn: None,
39			precompile_set_discriminant_type: None,
40			test_concrete_types: None,
41			pre_check: None,
42		};
43
44		precompile.process_impl_attr(impl_)?;
45		for mut item in &mut impl_.items {
46			// We only interact with methods and leave the rest as-is.
47			if let syn::ImplItem::Fn(ref mut method) = &mut item {
48				precompile.process_method(method)?;
49			}
50		}
51
52		// Check constraint of PrecompileSet.
53		if precompile.tagged_as_precompile_set
54			&& precompile.precompile_set_discriminant_fn.is_none()
55		{
56			let msg = "A PrecompileSet must have exactly one function tagged with \
57			`#[precompile::discriminant]`";
58			return Err(syn::Error::new(Span::call_site(), msg));
59		}
60
61		Ok(precompile)
62	}
63
64	/// Process the attributes used on the `impl` block, which allows to declare
65	/// if it is a PrecompileSet or not, and to provide concrete types for tests if necessary.
66	fn process_impl_attr(&mut self, impl_: &mut syn::ItemImpl) -> syn::Result<()> {
67		let attrs = attr::take_attributes::<attr::ImplAttr>(&mut impl_.attrs)?;
68
69		for attr in attrs {
70			match attr {
71				attr::ImplAttr::PrecompileSet(_) => {
72					self.tagged_as_precompile_set = true;
73				}
74				attr::ImplAttr::TestConcreteTypes(span, types) => {
75					if types.len() != self.generics.params.len() {
76						let msg = "The amount of types should match the amount of type parameters \
77						of the impl block";
78						return Err(syn::Error::new(span, msg));
79					}
80
81					if self.test_concrete_types.is_some() {
82						let msg = "Only one set of types can be provided to generate tests";
83						return Err(syn::Error::new(span, msg));
84					}
85
86					self.test_concrete_types = Some(types);
87				}
88			}
89		}
90
91		Ok(())
92	}
93
94	/// Extract the ident of the type of the `impl` block.
95	/// This ident is used to generate new idents such as the name of the Call enum and
96	/// the Solidity selector test.
97	fn extract_impl_ident(impl_: &syn::ItemImpl) -> syn::Result<syn::Ident> {
98		let type_path = match impl_.self_ty.as_ref() {
99			syn::Type::Path(p) => p,
100			_ => {
101				let msg = "The type in the impl block must be a path, like `Precompile` or
102				`example::Precompile`";
103				return Err(syn::Error::new(impl_.self_ty.span(), msg));
104			}
105		};
106
107		let final_path = type_path.path.segments.last().ok_or_else(|| {
108			let msg = "The type path must be non empty.";
109			syn::Error::new(impl_.self_ty.span(), msg)
110		})?;
111
112		Ok(final_path.ident.clone())
113	}
114
115	/// Process a single method, looking for attributes and checking mandatory parameters.
116	fn process_method(&mut self, method: &mut syn::ImplItemFn) -> syn::Result<()> {
117		// Take (remove) all attributes related to this macro.
118		let attrs = attr::take_attributes::<attr::MethodAttr>(&mut method.attrs)?;
119
120		// If there are no attributes it is a private function and we ignore it.
121		if attrs.is_empty() {
122			return Ok(());
123		}
124
125		// A method cannot have modifiers if it isn't a fallback and/or doesn't have a selector.
126		let mut used = false;
127
128		let method_name = method.sig.ident.clone();
129		let mut modifier = Modifier::NonPayable;
130		let mut solidity_arguments_type: Option<String> = None;
131		let mut arguments = vec![];
132		let mut is_fallback = false;
133		let mut selectors = vec![];
134		let initial_arguments = if self.tagged_as_precompile_set { 2 } else { 1 };
135
136		// We first look for unique attributes.
137		if let Some(attr::MethodAttr::Discriminant(span)) = attrs.first() {
138			let span = *span;
139
140			if attrs.len() != 1 {
141				let msg = "The discriminant attribute must be the only precompile attribute of \
142				a function";
143				return Err(syn::Error::new(span, msg));
144			}
145
146			return self.parse_discriminant_fn(span, method);
147		}
148
149		if let Some(attr::MethodAttr::PreCheck(span)) = attrs.first() {
150			let span = *span;
151
152			if attrs.len() != 1 {
153				let msg = "The pre_check attribute must be the only precompile attribute of \
154				a function";
155				return Err(syn::Error::new(span, msg));
156			}
157
158			return self.parse_pre_check_fn(span, method);
159		}
160
161		// We iterate over all attributes of the method.
162		for attr in attrs {
163			match attr {
164				attr::MethodAttr::Discriminant(span) => {
165					let msg = "The discriminant attribute must be the only precompile \
166					attribute of the function";
167					return Err(syn::Error::new(span, msg));
168				}
169				attr::MethodAttr::PreCheck(span) => {
170					let msg = "The pre_check attribute must be the only precompile \
171					attribute of the function";
172					return Err(syn::Error::new(span, msg));
173				}
174				attr::MethodAttr::Fallback(span) => {
175					if self.fallback_to_variant.is_some() {
176						let msg = "A precompile can only have 1 fallback function";
177						return Err(syn::Error::new(span, msg));
178					}
179
180					self.fallback_to_variant = Some(method_name.clone());
181					used = true;
182					is_fallback = true;
183				}
184				attr::MethodAttr::Payable(span) => {
185					if modifier != Modifier::NonPayable {
186						let msg =
187							"A precompile method can have at most one modifier (payable, view)";
188						return Err(syn::Error::new(span, msg));
189					}
190
191					modifier = Modifier::Payable;
192				}
193				attr::MethodAttr::View(span) => {
194					if modifier != Modifier::NonPayable {
195						let msg =
196							"A precompile method can have at most one modifier (payable, view)";
197						return Err(syn::Error::new(span, msg));
198					}
199
200					modifier = Modifier::View;
201				}
202				attr::MethodAttr::Public(_, signature_lit) => {
203					used = true;
204
205					let selector = self.parse_public_attr(
206						signature_lit,
207						&method_name,
208						&mut solidity_arguments_type,
209					)?;
210					selectors.push(selector);
211				}
212			}
213		}
214
215		// A method cannot have attributes without being public or fallback.
216		if !used {
217			let msg =
218				"A precompile method cannot have modifiers without being a fallback or having\
219			a `public` attribute";
220			return Err(syn::Error::new(method.span(), msg));
221		}
222
223		// We forbid type parameters.
224		if let Some(param) = method.sig.generics.params.first() {
225			let msg = "Exposed precompile methods cannot have type parameters";
226			return Err(syn::Error::new(param.span(), msg));
227		}
228
229		// Fallback method cannot have custom parameters.
230		if is_fallback {
231			if let Some(input) = method.sig.inputs.iter().nth(initial_arguments) {
232				let msg = if self.tagged_as_precompile_set {
233					"Fallback methods cannot take any parameter outside of the discriminant and \
234					PrecompileHandle"
235				} else {
236					"Fallback methods cannot take any parameter outside of the PrecompileHandle"
237				};
238
239				return Err(syn::Error::new(input.span(), msg));
240			}
241		}
242
243		let mut method_inputs = method.sig.inputs.iter();
244
245		// We check the first parameters of the method.
246		// If this is a PrecompileSet it will look for a discriminant.
247		// Then for all precompile(set)s it will look for the PrecompileHandle.
248		// We take them from the iterator such that we are only left with the
249		// custom arguments.
250		self.check_initial_parameters(&mut method_inputs, method.sig.span())?;
251
252		// We go through each parameter to collect each name and type that will be used to
253		// generate the input enum and parse the call data.
254		for input in method_inputs {
255			let input = match input {
256				syn::FnArg::Typed(t) => t,
257				_ => {
258					// I don't think it is possible to encounter this error since a self receiver
259					// seems to only be possible in the first position which is checked in
260					// `check_initial_parameters`.
261					let msg = "Exposed precompile methods cannot have a `self` parameter";
262					return Err(syn::Error::new(input.span(), msg));
263				}
264			};
265
266			let msg = "Parameter must be of the form `name: Type`, optionally prefixed by `mut`";
267			let ident = match input.pat.as_ref() {
268				syn::Pat::Ident(pat) => {
269					if pat.by_ref.is_some() || pat.subpat.is_some() {
270						return Err(syn::Error::new(pat.span(), msg));
271					}
272
273					pat.ident.clone()
274				}
275				_ => {
276					return Err(syn::Error::new(input.pat.span(), msg));
277				}
278			};
279			let ty = input.ty.as_ref().clone();
280			self.check_type_parameter_usage(&ty)?;
281
282			arguments.push(Argument { ident, ty })
283		}
284
285		// Function output.
286		let output_type = match &method.sig.output {
287			syn::ReturnType::Type(_, t) => t,
288			_ => {
289				let msg = "A precompile method must have a return type of `EvmResult<_>` (exposed \
290				by `precompile_utils`)";
291				return Err(syn::Error::new(method.sig.span(), msg));
292			}
293		};
294
295		// We insert the collected data in self.
296		if self
297			.variants_content
298			.insert(
299				method_name.clone(),
300				Variant {
301					arguments,
302					solidity_arguments_type: solidity_arguments_type.unwrap_or(String::from("()")),
303					modifier,
304					selectors,
305					fn_output: output_type.as_ref().clone(),
306				},
307			)
308			.is_some()
309		{
310			let msg = "Duplicate method name";
311			return Err(syn::Error::new(method_name.span(), msg));
312		}
313
314		Ok(())
315	}
316
317	/// Check the initial parameters of most methods of a Precompile(Set).
318	fn check_initial_parameters<'a>(
319		&mut self,
320		method_inputs: &mut impl Iterator<Item = &'a syn::FnArg>,
321		method_span: Span,
322	) -> syn::Result<()> {
323		// Discriminant input
324		if self.tagged_as_precompile_set {
325			let input = match method_inputs.next() {
326				Some(a) => a,
327				None => {
328					let msg = "PrecompileSet methods must have at least 2 parameters (the \
329					precompile instance discriminant and the PrecompileHandle)";
330					return Err(syn::Error::new(method_span, msg));
331				}
332			};
333
334			let input = match input {
335				syn::FnArg::Typed(a) => a,
336				_ => {
337					let msg = "self is not allowed in precompile methods";
338					return Err(syn::Error::new(input.span(), msg));
339				}
340			};
341
342			let input_type = input.ty.as_ref();
343
344			self.try_register_discriminant_type(input_type)?;
345		}
346
347		// Precompile handle input
348		{
349			let input = match method_inputs.next() {
350				Some(a) => a,
351				None => {
352					let msg = if self.tagged_as_precompile_set {
353						"PrecompileSet methods must have at least 2 parameters (the precompile \
354						instance discriminant and the PrecompileHandle)"
355					} else {
356						"Precompile methods must have at least 1 parameter (the PrecompileHandle)"
357					};
358
359					return Err(syn::Error::new(method_span, msg));
360				}
361			};
362
363			let input = match input {
364				syn::FnArg::Typed(a) => a,
365				_ => {
366					let msg = "self is not allowed in precompile methods";
367					return Err(syn::Error::new(input.span(), msg));
368				}
369			};
370
371			let input_type = input.ty.as_ref();
372
373			if !is_same_type(input_type, &syn::parse_quote! {&mut impl PrecompileHandle}) {
374				let msg = "This parameter must have type `&mut impl PrecompileHandle`";
375				return Err(syn::Error::new(input_type.span(), msg));
376			}
377		}
378
379		Ok(())
380	}
381
382	/// Records the type of the discriminant and ensure they all have the same type.
383	fn try_register_discriminant_type(&mut self, ty: &syn::Type) -> syn::Result<()> {
384		if let Some(known_type) = &self.precompile_set_discriminant_type {
385			if !is_same_type(known_type, ty) {
386				let msg = format!(
387					"All discriminants must have the same type (found {} before)",
388					known_type.to_token_stream()
389				);
390				return Err(syn::Error::new(ty.span(), msg));
391			}
392		} else {
393			self.precompile_set_discriminant_type = Some(ty.clone());
394		}
395
396		Ok(())
397	}
398
399	/// Process the discriminant function.
400	fn parse_discriminant_fn(&mut self, span: Span, method: &syn::ImplItemFn) -> syn::Result<()> {
401		if !self.tagged_as_precompile_set {
402			let msg = "The impl block must be tagged with `#[precompile::precompile_set]` for
403			the discriminant attribute to be used";
404			return Err(syn::Error::new(span, msg));
405		}
406
407		if self.precompile_set_discriminant_fn.is_some() {
408			let msg = "A PrecompileSet can only have 1 discriminant function";
409			return Err(syn::Error::new(span, msg));
410		}
411
412		let span = method.sig.span();
413
414		if method.sig.inputs.len() != 2 {
415			let msg = "The discriminant function must only take code address (H160) and \
416			remaining gas (u64) as parameters.";
417			return Err(syn::Error::new(span, msg));
418		}
419
420		let msg = "The discriminant function must return an DiscriminantResult<_> (no type alias)";
421
422		let return_type = match &method.sig.output {
423			syn::ReturnType::Type(_, t) => t.as_ref(),
424			_ => return Err(syn::Error::new(span, msg)),
425		};
426
427		let return_path = match return_type {
428			syn::Type::Path(p) => p,
429			_ => return Err(syn::Error::new(span, msg)),
430		};
431
432		if return_path.qself.is_some() {
433			return Err(syn::Error::new(span, msg));
434		}
435
436		let return_path = &return_path.path;
437
438		if return_path.leading_colon.is_some() || return_path.segments.len() != 1 {
439			return Err(syn::Error::new(span, msg));
440		}
441
442		let return_segment = &return_path.segments[0];
443
444		if return_segment.ident != "DiscriminantResult" {
445			return Err(syn::Error::new(return_segment.ident.span(), msg));
446		}
447
448		let result_arguments = match &return_segment.arguments {
449			syn::PathArguments::AngleBracketed(args) => args,
450			_ => return Err(syn::Error::new(return_segment.ident.span(), msg)),
451		};
452
453		if result_arguments.args.len() != 1 {
454			let msg = "DiscriminantResult type should only have 1 type argument";
455			return Err(syn::Error::new(result_arguments.args.span(), msg));
456		}
457
458		let discriminant_type: &syn::Type = match &result_arguments.args[0] {
459			syn::GenericArgument::Type(t) => t,
460			_ => return Err(syn::Error::new(result_arguments.args.span(), msg)),
461		};
462
463		self.try_register_discriminant_type(discriminant_type)?;
464
465		self.precompile_set_discriminant_fn = Some(method.sig.ident.clone());
466
467		Ok(())
468	}
469
470	/// Process the pre_check function.
471	fn parse_pre_check_fn(&mut self, span: Span, method: &syn::ImplItemFn) -> syn::Result<()> {
472		if self.pre_check.is_some() {
473			let msg = "A Precompile can only have 1 pre_check function";
474			return Err(syn::Error::new(span, msg));
475		}
476
477		let span = method.sig.span();
478
479		let mut method_inputs = method.sig.inputs.iter();
480
481		self.check_initial_parameters(&mut method_inputs, span)?;
482
483		if method_inputs.next().is_some() {
484			let msg = if self.tagged_as_precompile_set {
485				"PrecompileSet pre_check method must have exactly 2 parameters (the precompile \
486				instance discriminant and the PrecompileHandle)"
487			} else {
488				"Precompile pre_check method must have exactly 1 parameter (the \
489				PrecompileHandle)"
490			};
491
492			return Err(syn::Error::new(span, msg));
493		}
494
495		self.pre_check = Some(method.sig.ident.clone());
496
497		Ok(())
498	}
499
500	/// Process a `public` attribute on a method.
501	fn parse_public_attr(
502		&mut self,
503		signature_lit: syn::LitStr,
504		method_name: &syn::Ident,
505		solidity_arguments_type: &mut Option<String>,
506	) -> syn::Result<u32> {
507		let signature = signature_lit.value();
508		// Split signature to get arguments type.
509		let split: Vec<_> = signature.splitn(2, '(').collect();
510		if split.len() != 2 {
511			let msg = "Selector must have form \"foo(arg1,arg2,...)\"";
512			return Err(syn::Error::new(signature_lit.span(), msg));
513		}
514
515		let local_args_type = format!("({}", split[1]); // add back initial parenthesis
516
517		// If there are multiple public attributes we check that they all have
518		// the same type.
519		if let Some(ref args_type) = solidity_arguments_type {
520			if args_type != &local_args_type {
521				let msg = "Method cannot have selectors with different types.";
522				return Err(syn::Error::new(signature_lit.span(), msg));
523			}
524		} else {
525			*solidity_arguments_type = Some(local_args_type);
526		}
527
528		// Compute the 4-bytes selector.
529		let digest = keccak_256(signature.as_bytes());
530		let selector = u32::from_be_bytes([digest[0], digest[1], digest[2], digest[3]]);
531
532		if let Some(previous) = self
533			.selector_to_variant
534			.insert(selector, method_name.clone())
535		{
536			let msg = format!("Selector collision with method {previous}");
537			return Err(syn::Error::new(signature_lit.span(), msg));
538		}
539
540		Ok(selector)
541	}
542
543	/// Check that the provided type doesn't depend on one of the type parameters of the
544	/// precompile. Check is skipped if `test_concrete_types` attribute is used.
545	fn check_type_parameter_usage(&self, ty: &syn::Type) -> syn::Result<()> {
546		if self.test_concrete_types.is_some() {
547			return Ok(());
548		}
549
550		const ERR_MESSAGE: &str =
551			"impl type parameter is used in functions arguments. Arguments should not have a type
552depending on a type parameter, unless it is a length bound for BoundedBytes,
553BoundedString or alike, which doesn't affect the Solidity type.
554
555In that case, you must add a #[precompile::test_concrete_types(...)] attribute on the impl
556block to provide concrete types that will be used to run the automatically generated tests
557ensuring the Solidity function signatures are correct.";
558
559		match ty {
560			syn::Type::Array(syn::TypeArray { elem, .. })
561			| syn::Type::Group(syn::TypeGroup { elem, .. })
562			| syn::Type::Paren(syn::TypeParen { elem, .. })
563			| syn::Type::Reference(syn::TypeReference { elem, .. })
564			| syn::Type::Ptr(syn::TypePtr { elem, .. })
565			| syn::Type::Slice(syn::TypeSlice { elem, .. }) => self.check_type_parameter_usage(elem)?,
566
567			syn::Type::Path(syn::TypePath {
568				path: syn::Path { segments, .. },
569				..
570			}) => {
571				let impl_params: Vec<_> = self
572					.generics
573					.params
574					.iter()
575					.filter_map(|param| match param {
576						syn::GenericParam::Type(syn::TypeParam { ident, .. }) => Some(ident),
577						_ => None,
578					})
579					.collect();
580
581				for segment in segments {
582					if impl_params.contains(&&segment.ident) {
583						return Err(syn::Error::new(segment.ident.span(), ERR_MESSAGE));
584					}
585
586					if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
587						let types = args.args.iter().filter_map(|arg| match arg {
588							syn::GenericArgument::Type(ty)
589							| syn::GenericArgument::AssocType(syn::AssocType { ty, .. }) => Some(ty),
590							_ => None,
591						});
592
593						for ty in types {
594							self.check_type_parameter_usage(ty)?;
595						}
596					}
597				}
598			}
599			syn::Type::Tuple(tuple) => {
600				for ty in tuple.elems.iter() {
601					self.check_type_parameter_usage(ty)?;
602				}
603			}
604			// BareFn => very unlikely this appear as parameter
605			// ImplTrait => will cause other errors, it must be a concrete type
606			// TypeInfer => it must be explicit concrete types since it ends up in enum fields
607			// Macro => Cannot check easily
608			// Never => Function will not be callable.
609			ty => println!("Skipping type parameter check for non supported kind of type: {ty:?}"),
610		}
611
612		Ok(())
613	}
614}
615
616/// Helper to check 2 types are equal.
617/// Having a function with explicit type annotation helps type inference at callsite,
618/// which have trouble if `==` is used inline.
619fn is_same_type(a: &syn::Type, b: &syn::Type) -> bool {
620	a == b
621}