precompile_utils_macro/precompile/
parse.rs1use super::*;
20
21impl Precompile {
22 pub fn try_from(impl_: &mut syn::ItemImpl) -> syn::Result<Self> {
24 let impl_ident = Self::extract_impl_ident(impl_)?;
26 let enum_ident = format_ident!("{}Call", impl_ident);
27
28 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 if let syn::ImplItem::Fn(ref mut method) = &mut item {
48 precompile.process_method(method)?;
49 }
50 }
51
52 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 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 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 fn process_method(&mut self, method: &mut syn::ImplItemFn) -> syn::Result<()> {
117 let attrs = attr::take_attributes::<attr::MethodAttr>(&mut method.attrs)?;
119
120 if attrs.is_empty() {
122 return Ok(());
123 }
124
125 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 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 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 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 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 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 self.check_initial_parameters(&mut method_inputs, method.sig.span())?;
251
252 for input in method_inputs {
255 let input = match input {
256 syn::FnArg::Typed(t) => t,
257 _ => {
258 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 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 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 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 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 {
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 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 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 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 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 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]); 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 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 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 ty => println!("Skipping type parameter check for non supported kind of type: {ty:?}"),
610 }
611
612 Ok(())
613 }
614}
615
616fn is_same_type(a: &syn::Type, b: &syn::Type) -> bool {
620 a == b
621}