precompile_utils_macro/precompile/
expand.rs1use super::*;
20
21impl Precompile {
22 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 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 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 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 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 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 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 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 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 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 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}