precompile_utils_macro/precompile/
expand.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	/// Main expand function, which expands everything else.
23	pub fn expand(&self) -> impl ToTokens {
24		let enum_ = self.expand_enum_decl();
25		let enum_impl = self.expand_enum_impl();
26		let precomp_impl = self.expand_precompile_impl();
27		let test_signature = self.expand_test_solidity_signature();
28
29		quote! {
30			#enum_
31			#enum_impl
32			#precomp_impl
33			#test_signature
34		}
35	}
36
37	/// Expands the call enum declaration.
38	pub fn expand_enum_decl(&self) -> impl ToTokens {
39		let enum_ident = &self.enum_ident;
40		let (_impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
41
42		let type_parameters = self.generics.type_params().map(|p| &p.ident);
43
44		let variants: Vec<_> = self.variants_content.keys().collect();
45		let idents: Vec<Vec<_>> = self
46			.variants_content
47			.values()
48			.map(|v| v.arguments.iter().map(|a| &a.ident).collect())
49			.collect();
50		let types: Vec<Vec<_>> = self
51			.variants_content
52			.values()
53			.map(|v| v.arguments.iter().map(|a| &a.ty).collect())
54			.collect();
55
56		quote!(
57			#[allow(non_camel_case_types)]
58			pub enum #enum_ident #ty_generics #where_clause {
59				#(
60					#variants {
61						#(
62							#idents: #types
63						),*
64					},
65				)*
66
67				#[doc(hidden)]
68				__phantom(
69					::core::marker::PhantomData<( #( #type_parameters ),* )>,
70					::core::convert::Infallible
71				),
72			}
73		)
74	}
75
76	/// Expands the parse function for each variants.
77	pub fn expand_variants_parse_fn(&self) -> impl ToTokens {
78		let span = Span::call_site();
79
80		let fn_parse = self
81			.variants_content
82			.keys()
83			.map(Self::variant_ident_to_parse_fn);
84
85		let modifier_check = self.variants_content.values().map(|variant| {
86			let modifier = match variant.modifier {
87				Modifier::NonPayable => "NonPayable",
88				Modifier::Payable => "Payable",
89				Modifier::View => "View",
90			};
91
92			let modifier = syn::Ident::new(modifier, span);
93
94			quote!(
95				use ::precompile_utils::solidity::modifier::FunctionModifier;
96				use ::precompile_utils::evm::handle::PrecompileHandleExt;
97				handle.check_function_modifier(FunctionModifier::#modifier)?;
98			)
99		});
100
101		let variant_parsing = self
102			.variants_content
103			.iter()
104			.map(|(variant_ident, variant)| {
105				Self::expand_variant_parsing_from_handle(variant_ident, variant)
106			});
107
108		quote!(
109			#(
110				fn #fn_parse(
111					handle: &mut impl PrecompileHandle
112				) -> ::precompile_utils::EvmResult<Self> {
113					use ::precompile_utils::solidity::revert::InjectBacktrace;
114
115					#modifier_check
116					#variant_parsing
117				}
118			)*
119		)
120	}
121
122	/// Generates the parsing code for a variant, reading the input from the handle and
123	/// parsing it using Reader.
124	fn expand_variant_parsing_from_handle(
125		variant_ident: &syn::Ident,
126		variant: &Variant,
127	) -> impl ToTokens {
128		if variant.arguments.is_empty() {
129			quote!( Ok(Self::#variant_ident {})).to_token_stream()
130		} else {
131			use case::CaseExt;
132
133			let args_parse = variant.arguments.iter().map(|arg| {
134				let ident = &arg.ident;
135				let span = ident.span();
136				let name = ident.to_string().to_camel_lowercase();
137
138				quote_spanned!(span=> #ident: input.read().in_field(#name)?,)
139			});
140			let args_count = variant.arguments.len();
141
142			quote!(
143				let mut input = handle.read_after_selector()?;
144				input.expect_arguments(#args_count)?;
145
146				Ok(Self::#variant_ident {
147					#(#args_parse)*
148				})
149			)
150			.to_token_stream()
151		}
152	}
153
154	/// Expands the call enum impl block.
155	pub fn expand_enum_impl(&self) -> impl ToTokens {
156		let enum_ident = &self.enum_ident;
157		let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
158
159		let match_selectors = self.selector_to_variant.keys();
160		let match_selectors2 = self.selector_to_variant.keys();
161
162		let variants_parsing = self.expand_variants_parse_fn();
163
164		let variants_ident2: Vec<_> = self.variants_content.keys().collect();
165		let variants_selectors_fn: Vec<_> = self
166			.variants_content
167			.keys()
168			.map(|name| format_ident!("{}_selectors", name))
169			.collect();
170		let variants_selectors: Vec<_> = self
171			.variants_content
172			.values()
173			.map(|variant| &variant.selectors)
174			.collect();
175
176		let variants_list: Vec<Vec<_>> = self
177			.variants_content
178			.values()
179			.map(|variant| variant.arguments.iter().map(|arg| &arg.ident).collect())
180			.collect();
181
182		let variants_encode: Vec<_> = self
183			.variants_content
184			.values()
185			.map(Self::expand_variant_encoding)
186			.collect();
187
188		let parse_call_data_fn = self.expand_enum_parse_call_data();
189		let execute_fn = self.expand_enum_execute_fn();
190
191		quote!(
192			impl #impl_generics #enum_ident #ty_generics #where_clause {
193				#parse_call_data_fn
194
195				#variants_parsing
196
197				#execute_fn
198
199				pub fn supports_selector(selector: u32) -> bool {
200					match selector {
201						#(
202							#match_selectors => true,
203						)*
204						_ => false,
205					}
206				}
207
208				pub fn selectors() -> &'static [u32] {
209					&[#(
210						#match_selectors2
211					),*]
212				}
213
214				#(
215					pub fn #variants_selectors_fn() -> &'static [u32] {
216						&[#(
217							#variants_selectors
218						),*]
219					}
220				)*
221
222				pub fn encode(self) -> ::precompile_utils::__alloc::vec::Vec<u8> {
223					use ::precompile_utils::solidity::codec::Writer;
224					match self {
225						#(
226							Self::#variants_ident2 { #(#variants_list),* } => {
227								#variants_encode
228							},
229						)*
230						Self::__phantom(_, _) => panic!("__phantom variant should not be used"),
231					}
232				}
233			}
234
235			impl #impl_generics From<#enum_ident #ty_generics> for ::precompile_utils::__alloc::vec::Vec<u8>
236			#where_clause
237			{
238				fn from(a: #enum_ident #ty_generics) -> ::precompile_utils::__alloc::vec::Vec<u8> {
239					a.encode()
240				}
241			}
242		)
243	}
244
245	/// Expand the execute fn of the enum.
246	fn expand_enum_execute_fn(&self) -> impl ToTokens {
247		let impl_type = &self.impl_type;
248
249		let variants_ident: Vec<_> = self.variants_content.keys().collect();
250
251		let variants_arguments: Vec<Vec<_>> = self
252			.variants_content
253			.values()
254			.map(|variant| variant.arguments.iter().map(|arg| &arg.ident).collect())
255			.collect();
256
257		// If there is no precompile set there is no discriminant.
258		let opt_discriminant_arg = self
259			.precompile_set_discriminant_type
260			.as_ref()
261			.map(|ty| quote!( discriminant: #ty,));
262
263		let variants_call = self
264			.variants_content
265			.iter()
266			.map(|(variant_ident, variant)| {
267				let arguments = variant.arguments.iter().map(|arg| &arg.ident);
268
269				let output_span = variant.fn_output.span();
270				let opt_discriminant_arg = self
271					.precompile_set_discriminant_fn
272					.as_ref()
273					.map(|_| quote!(discriminant,));
274
275				let write_output = quote_spanned!(output_span=>
276					::precompile_utils::solidity::encode_return_value(output?)
277				);
278
279				quote!(
280					let output = <#impl_type>::#variant_ident(
281						#opt_discriminant_arg
282						handle,
283						#(#arguments),*
284					);
285					#write_output
286				)
287			});
288
289		quote!(
290			pub fn execute(
291				self,
292				#opt_discriminant_arg
293				handle: &mut impl PrecompileHandle
294			) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> {
295				use ::precompile_utils::solidity::codec::Writer;
296				use ::fp_evm::{PrecompileOutput, ExitSucceed};
297
298				let output = match self {
299					#(
300						Self::#variants_ident { #(#variants_arguments),* } => {
301							#variants_call
302						},
303					)*
304					Self::__phantom(_, _) => panic!("__phantom variant should not be used"),
305				};
306
307				Ok(PrecompileOutput {
308					exit_status: ExitSucceed::Returned,
309					output
310				})
311			}
312		)
313	}
314
315	/// Expand how a variant can be Solidity encoded.
316	fn expand_variant_encoding(variant: &Variant) -> impl ToTokens {
317		match variant.selectors.first() {
318			Some(selector) => {
319				let write_arguments = variant.arguments.iter().map(|arg| {
320					let ident = &arg.ident;
321					let span = ident.span();
322					quote_spanned!(span=> .write(#ident))
323				});
324
325				quote!(
326					Writer::new_with_selector(#selector)
327					#(#write_arguments)*
328					.build()
329				)
330				.to_token_stream()
331			}
332			None => quote!(Default::default()).to_token_stream(),
333		}
334	}
335
336	/// Expand the main parsing function that, based on the selector in the
337	/// input, dispatch the decoding to one of the variants parsing function.
338	fn expand_enum_parse_call_data(&self) -> impl ToTokens {
339		let selectors = self.selector_to_variant.keys();
340		let parse_fn = self
341			.selector_to_variant
342			.values()
343			.map(Self::variant_ident_to_parse_fn);
344
345		let match_fallback = match &self.fallback_to_variant {
346			Some(variant) => {
347				let parse_fn = Self::variant_ident_to_parse_fn(variant);
348				quote!(_ => Self::#parse_fn(handle),).to_token_stream()
349			}
350			None => quote!(
351				Some(_) => Err(RevertReason::UnknownSelector.into()),
352				None => Err(RevertReason::read_out_of_bounds("selector").into()),
353			)
354			.to_token_stream(),
355		};
356
357		quote!(
358			pub fn parse_call_data(
359				handle: &mut impl PrecompileHandle
360			) -> ::precompile_utils::EvmResult<Self> {
361				use ::precompile_utils::solidity::revert::RevertReason;
362
363				let input = handle.input();
364
365				let selector = input.get(0..4).map(|s| {
366					let mut buffer = [0u8; 4];
367					buffer.copy_from_slice(s);
368					u32::from_be_bytes(buffer)
369				});
370
371				match selector {
372					#(
373						Some(#selectors) => Self::#parse_fn(handle),
374					)*
375					#match_fallback
376				}
377			}
378		)
379	}
380
381	fn variant_ident_to_parse_fn(ident: &syn::Ident) -> syn::Ident {
382		format_ident!("_parse_{}", ident)
383	}
384
385	/// Expands the impl of the Precomile(Set) trait.
386	pub fn expand_precompile_impl(&self) -> impl ToTokens {
387		let impl_type = &self.impl_type;
388		let enum_ident = &self.enum_ident;
389		let (impl_generics, ty_generics, where_clause) = self.generics.split_for_impl();
390
391		if let Some(discriminant_fn) = &self.precompile_set_discriminant_fn {
392			let opt_pre_check = self.pre_check.as_ref().map(|ident| {
393				let span = ident.span();
394				quote_spanned!(span=>
395					let _: () = <#impl_type>::#ident(discriminant, handle)
396						.map_err(|err| Some(err))?;
397				)
398			});
399
400			quote!(
401				impl #impl_generics ::fp_evm::PrecompileSet for #impl_type #where_clause {
402					fn execute(
403						&self,
404						handle: &mut impl PrecompileHandle
405					) -> Option<::precompile_utils::EvmResult<::fp_evm::PrecompileOutput>> {
406						use ::precompile_utils::precompile_set::DiscriminantResult;
407
408						let discriminant = <#impl_type>::#discriminant_fn(
409							handle.code_address(),
410							handle.remaining_gas()
411						);
412
413						if let DiscriminantResult::Some(_, cost) | DiscriminantResult::None(cost) = discriminant {
414							let result = handle.record_cost(cost);
415							if let Err(e) = result {
416								return Some(Err(e.into()));
417							}
418						}
419
420						let discriminant = match discriminant {
421							DiscriminantResult::Some(d, _) => d,
422							DiscriminantResult::None(cost) => return None,
423							DiscriminantResult::OutOfGas => return Some(Err(ExitError::OutOfGas.into()))
424						};
425
426						#opt_pre_check
427
428						Some(
429							<#enum_ident #ty_generics>::parse_call_data(handle)
430								.and_then(|call| call.execute(discriminant, handle))
431						)
432					}
433
434					fn is_precompile(&self, address: H160, gas: u64) -> ::fp_evm::IsPrecompileResult {
435						<#impl_type>::#discriminant_fn(address, gas).into()
436					}
437				}
438			)
439			.to_token_stream()
440		} else {
441			let opt_pre_check = self.pre_check.as_ref().map(|ident| {
442				let span = ident.span();
443				quote_spanned!(span=>let _: () = <#impl_type>::#ident(handle)?;)
444			});
445
446			quote!(
447				impl #impl_generics ::fp_evm::Precompile for #impl_type #where_clause {
448					fn execute(
449						handle: &mut impl PrecompileHandle
450					) -> ::precompile_utils::EvmResult<::fp_evm::PrecompileOutput> {
451						#opt_pre_check
452
453						<#enum_ident #ty_generics>::parse_call_data(handle)?.execute(handle)
454					}
455				}
456			)
457			.to_token_stream()
458		}
459	}
460
461	/// Expands the Solidity signature test.
462	/// The macro expands an "inner" function in all build profiles, which is
463	/// then called by a test in test profile. This allows to display errors that occurs in
464	/// the expansion of the test without having to build in test profile, which is usually
465	/// related to the use of a type parameter in one of the parsed parameters of a method.
466	pub fn expand_test_solidity_signature(&self) -> impl ToTokens {
467		let variant_test: Vec<_> = self
468			.variants_content
469			.iter()
470			.map(|(ident, variant)| {
471				let span = ident.span();
472
473				let solidity = &variant.solidity_arguments_type;
474				let name = ident.to_string();
475				let types: Vec<_> = variant.arguments.iter().map(|arg| &arg.ty).collect();
476
477				quote_spanned!(span=>
478					assert_eq!(
479						#solidity,
480						<(#(#types,)*) as Codec>::signature(),
481						"{} function signature doesn't match (left: attribute, right: computed \
482						from Rust types)",
483						#name
484					);
485				)
486			})
487			.collect();
488
489		let test_name = format_ident!("__{}_test_solidity_signatures", self.impl_ident);
490		let inner_name = format_ident!("__{}_test_solidity_signatures_inner", self.impl_ident);
491
492		if let Some(test_types) = &self.test_concrete_types {
493			let (impl_generics, _ty_generics, where_clause) = self.generics.split_for_impl();
494
495			quote!(
496				#[allow(non_snake_case)]
497				pub(crate) fn #inner_name #impl_generics () #where_clause {
498					use ::precompile_utils::solidity::Codec;
499					#(#variant_test)*
500				}
501
502				#[test]
503				#[allow(non_snake_case)]
504				fn #test_name() {
505					#inner_name::< #(#test_types),* >();
506				}
507			)
508			.to_token_stream()
509		} else {
510			quote!(
511				#[allow(non_snake_case)]
512				pub(crate) fn #inner_name() {
513					use ::precompile_utils::solidity::Codec;
514					#(#variant_test)*
515				}
516
517				#[test]
518				#[allow(non_snake_case)]
519				fn #test_name() {
520					#inner_name();
521				}
522			)
523			.to_token_stream()
524		}
525	}
526}