1use crate::parse::{GroupedVariants, VariantKind};
2use proc_macro2::TokenStream;
3use quote::{quote, ToTokens};
4use syn::{Ident, Path};
5
6pub(crate) struct Expander {
8 pub(crate) input_type_name: Ident,
10 pub(crate) tx_type_enum_name: Ident,
12 pub(crate) alloy_consensus: Path,
14 pub(crate) generics: syn::Generics,
16 pub(crate) serde_enabled: bool,
18 pub(crate) serde_cfg: TokenStream,
20 pub(crate) arbitrary_cfg: TokenStream,
22 pub(crate) arbitrary_enabled: bool,
24 pub(crate) alloy_primitives: TokenStream,
26 pub(crate) alloy_eips: TokenStream,
28 pub(crate) alloy_rlp: TokenStream,
30 pub(crate) variants: GroupedVariants,
32 pub(crate) typed: Option<Ident>,
34}
35
36impl Expander {
37 pub(crate) fn expand(&self) -> TokenStream {
39 let imports = self.generate_imports();
40 let tx_type_enum = self.generate_tx_type_enum();
41 let trait_impls = self.generate_trait_impls();
42 let serde_impls = self.generate_serde_impls();
43 let arbitrary_impls = self.generate_arbitrary_impls();
44 let typed_transaction = self.generate_typed_transaction();
45
46 quote! {
47 #imports
48 #tx_type_enum
49 #trait_impls
50 #serde_impls
51 #arbitrary_impls
52 #typed_transaction
53 }
54 }
55
56 fn generate_imports(&self) -> TokenStream {
58 let alloy_eips = &self.alloy_eips;
59 quote! {
60 use #alloy_eips::Encodable2718 as _;
61 use #alloy_eips::Decodable2718 as _;
62 }
63 }
64
65 fn generate_tx_type_enum(&self) -> TokenStream {
67 let tx_type_enum_name = &self.tx_type_enum_name;
68 let alloy_eips = &self.alloy_eips;
69 let alloy_consensus = &self.alloy_consensus;
70
71 let variants = self.generate_tx_type_variants();
72 let conversions = self.generate_tx_type_conversions();
73 let typed_impls = self.generate_tx_type_typed_impls();
74 let serde_derive = self.generate_tx_type_serde_derive();
75
76 let doc_comment = format!("Transaction types supported by [`{}`].", self.input_type_name);
77
78 quote! {
79 #[doc = #doc_comment]
80 #[repr(u8)]
81 #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
82 #serde_derive
83 pub enum #tx_type_enum_name {
84 #variants
85 }
86
87 #conversions
88 #typed_impls
89
90 impl #alloy_eips::eip2718::IsTyped2718 for #tx_type_enum_name {
91 fn is_type(type_id: u8) -> bool {
92 Self::try_from(type_id).is_ok()
93 }
94 }
95
96 impl PartialEq<u8> for #tx_type_enum_name {
97 fn eq(&self, other: &u8) -> bool {
98 u8::from(*self) == *other
99 }
100 }
101
102 impl PartialEq<#tx_type_enum_name> for u8 {
103 fn eq(&self, other: &#tx_type_enum_name) -> bool {
104 *self == u8::from(*other)
105 }
106 }
107
108 impl #alloy_consensus::private::alloy_rlp::Encodable for #tx_type_enum_name {
109 fn encode(&self, out: &mut dyn #alloy_consensus::private::alloy_rlp::BufMut) {
110 u8::from(*self).encode(out);
111 }
112
113 fn length(&self) -> usize {
114 u8::from(*self).length()
115 }
116 }
117
118 impl #alloy_consensus::private::alloy_rlp::Decodable for #tx_type_enum_name {
119 fn decode(buf: &mut &[u8]) -> #alloy_consensus::private::alloy_rlp::Result<Self> {
120 let ty = u8::decode(buf)?;
121 Self::try_from(ty).map_err(|_| #alloy_consensus::private::alloy_rlp::Error::Custom("invalid transaction type"))
122 }
123 }
124 }
125 }
126
127 fn generate_tx_type_variants(&self) -> TokenStream {
129 let alloy_consensus = &self.alloy_consensus;
130 let variants = self.variants.all.iter().map(|v| {
131 let name = &v.name;
132 let ty = &v.ty;
133
134 match &v.kind {
135 VariantKind::Flattened => {
136 let doc_comment =
137 format!("Transaction type of an inner `{}`.", ty.to_token_stream());
138 quote! {
139 #[doc = #doc_comment]
140 #name(<#ty as #alloy_consensus::TransactionEnvelope>::TxType)
141 }
142 }
143 VariantKind::Typed(ty_id) => {
144 let doc_comment = format!("Transaction type of `{}`.", ty.to_token_stream());
145 quote! {
146 #[doc = #doc_comment]
147 #name = #ty_id
148 }
149 }
150 }
151 });
152
153 quote! { #(#variants),* }
154 }
155
156 fn generate_tx_type_conversions(&self) -> TokenStream {
158 let tx_type_enum_name = &self.tx_type_enum_name;
159 let alloy_primitives = &self.alloy_primitives;
160 let alloy_eips = &self.alloy_eips;
161
162 let from_arms = self.variants.all.iter().map(|v| {
163 let name = &v.name;
164 match &v.kind {
165 VariantKind::Typed(ty_id) => quote! { #tx_type_enum_name::#name => #ty_id },
166 VariantKind::Flattened => {
167 quote! { #tx_type_enum_name::#name(inner) => inner.into() }
168 }
169 }
170 });
171
172 let try_from_arms = self.variants.all.iter().map(|v| {
173 let name = &v.name;
174 match &v.kind {
175 VariantKind::Flattened => quote! {
176 if let Ok(inner) = TryFrom::try_from(value) {
177 return Ok(Self::#name(inner))
178 }
179 },
180 VariantKind::Typed(ty_id) => quote! {
181 if value == #ty_id {
182 return Ok(Self::#name)
183 }
184 },
185 }
186 });
187
188 quote! {
189 impl From<#tx_type_enum_name> for u8 {
190 fn from(value: #tx_type_enum_name) -> Self {
191 match value {
192 #(#from_arms),*
193 }
194 }
195 }
196
197 impl From<#tx_type_enum_name> for #alloy_primitives::U8 {
198 fn from(value: #tx_type_enum_name) -> Self {
199 Self::from(u8::from(value))
200 }
201 }
202
203 impl TryFrom<u8> for #tx_type_enum_name {
204 type Error = #alloy_eips::eip2718::Eip2718Error;
205
206 fn try_from(value: u8) -> Result<Self, Self::Error> {
207 #(#try_from_arms);*
208 return Err(#alloy_eips::eip2718::Eip2718Error::UnexpectedType(value))
209 }
210 }
211
212 impl TryFrom<u64> for #tx_type_enum_name {
213 type Error = &'static str;
214
215 fn try_from(value: u64) -> Result<Self, Self::Error> {
216 let err = || "invalid tx type";
217 let value: u8 = value.try_into().map_err(|_| err())?;
218 Self::try_from(value).map_err(|_| err())
219 }
220 }
221
222 impl TryFrom<#alloy_primitives::U8> for #tx_type_enum_name {
223 type Error = #alloy_eips::eip2718::Eip2718Error;
224
225 fn try_from(value: #alloy_primitives::U8) -> Result<Self, Self::Error> {
226 value.to::<u8>().try_into()
227 }
228 }
229
230 impl TryFrom<#alloy_primitives::U64> for #tx_type_enum_name {
231 type Error = &'static str;
232
233 fn try_from(value: #alloy_primitives::U64) -> Result<Self, Self::Error> {
234 value.to::<u64>().try_into()
235 }
236 }
237 }
238 }
239
240 fn generate_tx_type_typed_impls(&self) -> TokenStream {
242 let tx_type_enum_name = &self.tx_type_enum_name;
243 let alloy_consensus = &self.alloy_consensus;
244
245 let arms = self.variants.all.iter().map(|v| {
246 let name = &v.name;
247 match &v.kind {
248 VariantKind::Flattened => quote! {
249 Self::#name(inner) => #alloy_consensus::Typed2718::ty(inner)
250 },
251 VariantKind::Typed(ty_id) => quote! {
252 Self::#name => #ty_id
253 },
254 }
255 });
256
257 quote! {
258 impl #alloy_consensus::Typed2718 for #tx_type_enum_name {
259 fn ty(&self) -> u8 {
260 match self {
261 #(#arms),*
262 }
263 }
264 }
265 }
266 }
267
268 fn generate_tx_type_serde_derive(&self) -> TokenStream {
270 if self.serde_enabled {
271 let alloy_primitives = &self.alloy_primitives;
272 let alloy_consensus = &self.alloy_consensus;
273 let u8_path = quote! { #alloy_primitives::U8 }.to_string();
274 let u64_path = quote! { #alloy_primitives::U64 }.to_string();
275 let serde = quote! { #alloy_consensus::private::serde };
276 let serde_str = serde.to_string();
277 let serde_cfg = &self.serde_cfg;
278
279 quote! {
280 #[cfg_attr(#serde_cfg, derive(#serde::Serialize, #serde::Deserialize))]
281 #[cfg_attr(#serde_cfg, serde(into = #u8_path, try_from = #u64_path, crate = #serde_str))]
282 }
283 } else {
284 quote! {}
285 }
286 }
287
288 fn generate_trait_impls(&self) -> TokenStream {
290 let eq_impl = self.generate_eq_impl();
291 let hash_impl = self.generate_hash_impl();
292 let transaction_impl = self.generate_transaction_impl(false);
293 let typed_impl = self.generate_typed_impl();
294 let encodable_impl = self.generate_encodable_impl();
295 let decodable_impl = self.generate_decodable_impl();
296 let envelope_impl = self.generate_envelope_impl();
297
298 quote! {
299 #eq_impl
300 #hash_impl
301 #transaction_impl
302 #typed_impl
303 #encodable_impl
304 #decodable_impl
305 #envelope_impl
306 }
307 }
308
309 fn generate_eq_impl(&self) -> TokenStream {
311 let input_type_name = &self.input_type_name;
312 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
313
314 let variant_names = self.variants.variant_names();
315 let variant_types = self.variants.variant_types();
316
317 quote! {
318 impl #impl_generics PartialEq for #input_type_name #ty_generics
319 where
320 #(#variant_types: PartialEq),*
321 {
322 fn eq(&self, other: &Self) -> bool {
323 match (self, other) {
324 #((Self::#variant_names(tx), Self::#variant_names(other)) => tx.eq(other),)*
325 _ => false,
326 }
327 }
328 }
329
330 impl #impl_generics Eq for #input_type_name #ty_generics where #(#variant_types: PartialEq),* {}
331 }
332 }
333
334 fn generate_hash_impl(&self) -> TokenStream {
336 let input_type_name = &self.input_type_name;
337 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
338 let alloy_eips = &self.alloy_eips;
339
340 quote! {
341 impl #impl_generics core::hash::Hash for #input_type_name #ty_generics
342 where
343 Self: #alloy_eips::Encodable2718,
344 {
345 fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
346 self.trie_hash().hash(state);
347 }
348 }
349 }
350 }
351
352 fn generate_transaction_impl(&self, for_typed: bool) -> TokenStream {
354 let input_type_name =
355 if for_typed { self.typed.as_ref().unwrap() } else { &self.input_type_name };
356 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
357 let alloy_consensus = &self.alloy_consensus;
358 let alloy_primitives = &self.alloy_primitives;
359 let alloy_eips = &self.alloy_eips;
360
361 let variant_names = self.variants.variant_names();
362 let variant_types = if for_typed {
363 self.variants.typed.iter().map(|v| v.inner_type()).collect::<Vec<_>>()
364 } else {
365 self.variants.variant_types().iter().map(|v| v.to_token_stream()).collect()
366 };
367
368 quote! {
369 impl #impl_generics #alloy_consensus::Transaction for #input_type_name #ty_generics
370 where
371 Self: core::fmt::Debug,
372 #(#variant_types: #alloy_consensus::Transaction),*
373 {
374 #[inline]
375 fn chain_id(&self) -> Option<u64> {
376 match self { #(Self::#variant_names(tx) => tx.chain_id(),)* }
377 }
378
379 #[inline]
380 fn nonce(&self) -> u64 {
381 match self { #(Self::#variant_names(tx) => tx.nonce(),)* }
382 }
383
384 #[inline]
385 fn gas_limit(&self) -> u64 {
386 match self { #(Self::#variant_names(tx) => tx.gas_limit(),)* }
387 }
388
389 #[inline]
390 fn gas_price(&self) -> Option<u128> {
391 match self { #(Self::#variant_names(tx) => tx.gas_price(),)* }
392 }
393
394 #[inline]
395 fn max_fee_per_gas(&self) -> u128 {
396 match self { #(Self::#variant_names(tx) => tx.max_fee_per_gas(),)* }
397 }
398
399 #[inline]
400 fn max_priority_fee_per_gas(&self) -> Option<u128> {
401 match self { #(Self::#variant_names(tx) => tx.max_priority_fee_per_gas(),)* }
402 }
403
404 #[inline]
405 fn max_fee_per_blob_gas(&self) -> Option<u128> {
406 match self { #(Self::#variant_names(tx) => tx.max_fee_per_blob_gas(),)* }
407 }
408
409 #[inline]
410 fn priority_fee_or_price(&self) -> u128 {
411 match self { #(Self::#variant_names(tx) => tx.priority_fee_or_price(),)* }
412 }
413
414 #[inline]
415 fn effective_gas_price(&self, base_fee: Option<u64>) -> u128 {
416 match self { #(Self::#variant_names(tx) => tx.effective_gas_price(base_fee),)* }
417 }
418
419 #[inline]
420 fn is_dynamic_fee(&self) -> bool {
421 match self { #(Self::#variant_names(tx) => tx.is_dynamic_fee(),)* }
422 }
423
424 #[inline]
425 fn kind(&self) -> #alloy_primitives::TxKind {
426 match self { #(Self::#variant_names(tx) => tx.kind(),)* }
427 }
428
429 #[inline]
430 fn is_create(&self) -> bool {
431 match self { #(Self::#variant_names(tx) => tx.is_create(),)* }
432 }
433
434 #[inline]
435 fn value(&self) -> #alloy_primitives::U256 {
436 match self { #(Self::#variant_names(tx) => tx.value(),)* }
437 }
438
439 #[inline]
440 fn input(&self) -> &#alloy_primitives::Bytes {
441 match self { #(Self::#variant_names(tx) => tx.input(),)* }
442 }
443
444 #[inline]
445 fn access_list(&self) -> Option<&#alloy_eips::eip2930::AccessList> {
446 match self { #(Self::#variant_names(tx) => tx.access_list(),)* }
447 }
448
449 #[inline]
450 fn blob_versioned_hashes(&self) -> Option<&[#alloy_primitives::B256]> {
451 match self { #(Self::#variant_names(tx) => tx.blob_versioned_hashes(),)* }
452 }
453
454 #[inline]
455 fn authorization_list(&self) -> Option<&[#alloy_eips::eip7702::SignedAuthorization]> {
456 match self { #(Self::#variant_names(tx) => tx.authorization_list(),)* }
457 }
458 }
459 }
460 }
461
462 fn generate_typed_impl(&self) -> TokenStream {
464 let input_type_name = &self.input_type_name;
465 let tx_type_enum_name = &self.tx_type_enum_name;
466 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
467 let alloy_consensus = &self.alloy_consensus;
468 let alloy_eips = &self.alloy_eips;
469
470 let variant_names = self.variants.variant_names();
471 let variant_types = self.variants.variant_types();
472
473 quote! {
474 impl #impl_generics #alloy_eips::eip2718::IsTyped2718 for #input_type_name #ty_generics {
475 fn is_type(type_id: u8) -> bool {
476 <#tx_type_enum_name as #alloy_eips::eip2718::IsTyped2718>::is_type(type_id)
477 }
478 }
479
480 impl #impl_generics #alloy_consensus::Typed2718 for #input_type_name #ty_generics
481 where
482 #(#variant_types: #alloy_eips::eip2718::Typed2718),*
483 {
484 fn ty(&self) -> u8 {
485 match self {
486 #(Self::#variant_names(tx) => tx.ty(),)*
487 }
488 }
489 }
490 }
491 }
492
493 fn generate_encodable_impl(&self) -> TokenStream {
495 let input_type_name = &self.input_type_name;
496 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
497 let alloy_primitives = &self.alloy_primitives;
498 let alloy_eips = &self.alloy_eips;
499 let alloy_rlp = &self.alloy_rlp;
500
501 let variant_names = self.variants.variant_names();
502 let variant_types = self.variants.variant_types();
503
504 quote! {
505 impl #impl_generics #alloy_eips::Encodable2718 for #input_type_name #ty_generics
506 where
507 #(#variant_types: #alloy_eips::Encodable2718),*
508 {
509 fn encode_2718_len(&self) -> usize {
510 match self {
511 #(Self::#variant_names(tx) => tx.encode_2718_len(),)*
512 }
513 }
514
515 fn encode_2718(&self, out: &mut dyn #alloy_rlp::BufMut) {
516 match self {
517 #(Self::#variant_names(tx) => tx.encode_2718(out),)*
518 }
519 }
520
521 fn trie_hash(&self) -> #alloy_primitives::B256 {
522 match self {
523 #(Self::#variant_names(tx) => tx.trie_hash(),)*
524 }
525 }
526 }
527
528 impl #impl_generics #alloy_rlp::Encodable for #input_type_name #ty_generics
529 where
530 #(#variant_types: #alloy_eips::Encodable2718),*
531 {
532 fn encode(&self, out: &mut dyn #alloy_rlp::BufMut) {
533 <Self as #alloy_eips::Encodable2718>::network_encode(self, out)
534 }
535
536 fn length(&self) -> usize {
537 <Self as #alloy_eips::Encodable2718>::network_len(self)
538 }
539 }
540
541 }
542 }
543
544 fn generate_decodable_impl(&self) -> TokenStream {
546 let input_type_name = &self.input_type_name;
547 let tx_type_enum_name = &self.tx_type_enum_name;
548 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
549 let alloy_eips = &self.alloy_eips;
550 let alloy_rlp = &self.alloy_rlp;
551
552 let typed_decode_arms = self.variants.all.iter().map(|v| {
553 let name = &v.name;
554 match &v.kind {
555 VariantKind::Flattened => quote! {
556 #tx_type_enum_name::#name(_) => Ok(Self::#name(#alloy_eips::Decodable2718::typed_decode(ty, buf)?))
557 },
558 VariantKind::Typed(_) => quote! {
559 #tx_type_enum_name::#name => Ok(Self::#name(#alloy_eips::Decodable2718::typed_decode(ty, buf)?))
560 },
561 }
562 });
563
564 let fallback_decode_arms = self.variants.all.iter().map(|v| {
565 let name = &v.name;
566 quote! {
567 if let Ok(tx) = #alloy_eips::Decodable2718::fallback_decode(buf) {
568 return Ok(Self::#name(tx))
569 }
570 }
571 });
572
573 let variant_types = self.variants.variant_types();
574
575 quote! {
576 impl #impl_generics #alloy_eips::Decodable2718 for #input_type_name #ty_generics
577 where
578 #(#variant_types: #alloy_eips::Decodable2718),*
579 {
580 fn typed_decode(ty: u8, buf: &mut &[u8]) -> #alloy_eips::eip2718::Eip2718Result<Self> {
581 match ty.try_into().map_err(|_| #alloy_rlp::Error::Custom("unexpected tx type"))? {
582 #(#typed_decode_arms,)*
583 }
584 }
585
586 fn fallback_decode(buf: &mut &[u8]) -> #alloy_eips::eip2718::Eip2718Result<Self> {
587 #(#fallback_decode_arms)*
588
589 return Err(#alloy_eips::eip2718::Eip2718Error::UnexpectedType(0))
590 }
591 }
592
593 impl #impl_generics #alloy_rlp::Decodable for #input_type_name #ty_generics
594 where
595 #(#variant_types: #alloy_eips::Decodable2718),*
596 {
597 fn decode(buf: &mut &[u8]) -> alloy_rlp::Result<Self> {
598 Ok(<Self as #alloy_eips::Decodable2718>::network_decode(buf)?)
599 }
600 }
601
602 }
603 }
604
605 fn generate_envelope_impl(&self) -> TokenStream {
607 let input_type_name = &self.input_type_name;
608 let tx_type_enum_name = &self.tx_type_enum_name;
609 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
610 let alloy_consensus = &self.alloy_consensus;
611
612 quote! {
613 impl #impl_generics #alloy_consensus::TransactionEnvelope for #input_type_name #ty_generics
614 where
615 Self: #alloy_consensus::Transaction
616 {
617 type TxType = #tx_type_enum_name;
618 }
619 }
620 }
621
622 fn generate_serde_impls(&self) -> TokenStream {
624 if !self.serde_enabled {
625 return quote! {};
626 }
627
628 crate::serde::SerdeGenerator::new(
629 &self.input_type_name,
630 &self.generics,
631 &self.variants,
632 &self.alloy_consensus,
633 &self.serde_cfg,
634 )
635 .generate()
636 }
637
638 fn generate_arbitrary_impls(&self) -> TokenStream {
640 if !self.arbitrary_enabled {
641 return quote! {};
642 }
643
644 let input_type_name = &self.input_type_name;
645 let tx_type_enum_name = &self.tx_type_enum_name;
646 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
647 let alloy_consensus = &self.alloy_consensus;
648 let arbitrary = quote! { #alloy_consensus::private::arbitrary };
649 let arbitrary_cfg = &self.arbitrary_cfg;
650
651 let num_variants = self.variants.all.len();
652
653 let tx_type_arms = self.variants.all.iter().enumerate().map(|(i, v)| {
654 let name = &v.name;
655 match &v.kind {
656 VariantKind::Typed(_) => quote! { #i => Ok(Self::#name) },
657 VariantKind::Flattened => quote! { #i => Ok(Self::#name(u.arbitrary()?)) },
658 }
659 });
660
661 let enum_variant_arms = self.variants.all.iter().enumerate().map(|(i, v)| {
662 let name = &v.name;
663 quote! { #i => Ok(Self::#name(u.arbitrary()?)) }
664 });
665
666 let variant_types = self.variants.variant_types();
667
668 quote! {
669 #[cfg(#arbitrary_cfg)]
670 const _: () = {
671 impl #arbitrary::Arbitrary<'_> for #tx_type_enum_name {
672 fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
673 match u.int_in_range(0..=#num_variants-1)? {
674 #(#tx_type_arms,)*
675 _ => unreachable!(),
676 }
677 }
678 }
679
680 impl #impl_generics #arbitrary::Arbitrary<'_> for #input_type_name #ty_generics
681 where
682 #(#variant_types: for<'a> #arbitrary::Arbitrary<'a>),*
683 {
684 fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
685 match u.int_in_range(0..=#num_variants-1)? {
686 #(#enum_variant_arms,)*
687 _ => unreachable!(),
688 }
689 }
690 }
691 };
692 }
693 }
694
695 fn generate_typed_transaction(&self) -> TokenStream {
697 let Some(typed_name) = &self.typed else {
698 return quote! {};
699 };
700
701 let alloy_consensus = &self.alloy_consensus;
702 let arbitrary = quote! { #alloy_consensus::private::arbitrary };
703 let alloy_eips = &self.alloy_eips;
704 let arbitrary_cfg = &self.arbitrary_cfg;
705 let tx_type_enum_name = &self.tx_type_enum_name;
706 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
707
708 let variant_names = self.variants.variant_names();
709 let variant_types: Vec<_> = self.variants.all.iter().map(|v| v.inner_type()).collect();
710
711 let variants =
713 variant_names.iter().zip(variant_types.iter()).zip(self.variants.all.iter()).map(
714 |((name, inner_type), v)| {
715 let doc_attrs = &v.doc_attrs;
716 quote! {
717 #(#doc_attrs)*
718 #name(#inner_type),
719 }
720 },
721 );
722
723 let doc_comment = format!(
724 "Typed transaction enum corresponding to the [`{}`] envelope.",
725 self.input_type_name
726 );
727
728 let serde_impl = if self.serde_enabled {
729 self.generate_typed_transaction_serde(typed_name)
730 } else {
731 quote! {}
732 };
733
734 let arbitrary_impl = if self.arbitrary_enabled {
736 let num_variants = variant_names.len();
737 let arms = variant_names.iter().enumerate().map(|(i, name)| {
738 quote! { #i => Ok(Self::#name(u.arbitrary()?)) }
739 });
740
741 quote! {
742 #[cfg(#arbitrary_cfg)]
743 const _: () = {
744 impl #impl_generics #arbitrary::Arbitrary<'_> for #typed_name #ty_generics
745 where
746 #(#variant_types: for<'a> #arbitrary::Arbitrary<'a>),*
747 {
748 fn arbitrary(u: &mut #arbitrary::Unstructured<'_>) -> #arbitrary::Result<Self> {
749 match u.int_in_range(0..=#num_variants-1)? {
750 #(#arms,)*
751 _ => unreachable!(),
752 }
753 }
754 }
755 };
756 }
757 } else {
758 quote! {}
759 };
760
761 let transaction_impl = self.generate_transaction_impl(true);
762
763 quote! {
764 #[doc = #doc_comment]
765 #[derive(Clone, Debug, PartialEq, Eq, Hash)]
766 pub enum #typed_name #impl_generics {
767 #(#variants)*
768 }
769
770 #transaction_impl
771 #serde_impl
772 #arbitrary_impl
773
774 impl #impl_generics #alloy_eips::eip2718::Typed2718 for #typed_name #ty_generics
775 where
776 #(#variant_types: #alloy_eips::eip2718::Typed2718,)*
777 {
778 fn ty(&self) -> u8 {
779 match self {
780 #(Self::#variant_names(tx) => tx.ty(),)*
781 }
782 }
783 }
784
785 impl #impl_generics #alloy_eips::eip2718::IsTyped2718 for #typed_name #ty_generics {
786 fn is_type(type_id: u8) -> bool {
787 <#tx_type_enum_name as #alloy_eips::eip2718::IsTyped2718>::is_type(type_id)
788 }
789 }
790 }
791 }
792
793 fn generate_typed_transaction_serde(&self, typed_name: &Ident) -> TokenStream {
795 let (impl_generics, ty_generics, _) = self.generics.split_for_impl();
796 let unwrapped_generics = &self.generics.params;
797 let alloy_consensus = &self.alloy_consensus;
798 let serde_cfg = &self.serde_cfg;
799 let serde = quote! { #alloy_consensus::private::serde };
800 let serde_str = serde.to_string();
801 let reject_if_some =
802 quote! { #alloy_consensus::private::alloy_serde::reject_if_some }.to_string();
803
804 let typed_names = self.variants.typed.iter().map(|v| &v.name).collect::<Vec<_>>();
805
806 let typed_variants: Vec<_> = self
808 .variants
809 .typed
810 .iter()
811 .map(|v| {
812 let name = &v.name;
813
814 let inner_type = v.inner_type();
816
817 let (rename, aliases) = v.kind.serde_tag_and_aliases();
818
819 quote! {
820 #[serde(rename = #rename, #(alias = #aliases,)*)]
821 #name(#inner_type)
822 }
823 })
824 .collect();
825
826 let legacy_variant = self.variants.all.iter().find(|v| v.is_legacy());
828
829 let tagged_enum_name = syn::Ident::new(&format!("Tagged{}", typed_name), typed_name.span());
830 let maybe_tagged_enum_name =
831 syn::Ident::new(&format!("MaybeTagged{}", typed_name), typed_name.span());
832
833 let (legacy_untagged, legacy_conversion) = if let Some(legacy) = legacy_variant {
834 let legacy_name = &legacy.name;
835 let inner_type = legacy.inner_type();
836 (
837 quote! {
838 Untagged {
839 #[serde(default, rename = "type", deserialize_with = #reject_if_some)]
840 _ty: Option<()>,
841 #[serde(flatten)]
842 tx: #inner_type,
843 }
844 },
845 quote! {
846 #maybe_tagged_enum_name::Untagged { tx, .. } => Self::#legacy_name(tx),
847 },
848 )
849 } else {
850 (quote! {}, quote! {})
851 };
852
853 quote! {
854 #[cfg(#serde_cfg)]
855 const _: () = {
856 use super::*;
857
858 #[derive(Debug, #serde::Serialize, #serde::Deserialize)]
860 #[serde(tag = "type", crate = #serde_str)]
861 enum #tagged_enum_name #impl_generics {
862 #(
863 #typed_variants
864 ),*
865 }
866
867 #[derive(Debug, #serde::Deserialize)]
869 #[serde(untagged, crate = #serde_str)]
870 enum #maybe_tagged_enum_name #impl_generics {
871 Tagged(#tagged_enum_name #ty_generics),
872 #legacy_untagged
873 }
874
875 impl #impl_generics From<#maybe_tagged_enum_name #ty_generics> for #typed_name #ty_generics {
876 fn from(value: #maybe_tagged_enum_name #ty_generics) -> Self {
877 match value {
878 #maybe_tagged_enum_name::Tagged(tagged) => tagged.into(),
879 #legacy_conversion
880 }
881 }
882 }
883
884 impl #impl_generics From<#tagged_enum_name #ty_generics> for #typed_name #ty_generics {
885 fn from(value: #tagged_enum_name #ty_generics) -> Self {
886 match value {
887 #(
888 #tagged_enum_name::#typed_names(tx) => Self::#typed_names(tx),
889 )*
890 }
891 }
892 }
893
894 impl #impl_generics From<#typed_name #ty_generics> for #tagged_enum_name #ty_generics {
895 fn from(value: #typed_name #ty_generics) -> Self {
896 match value {
897 #(
898 #typed_name::#typed_names(tx) => Self::#typed_names(tx),
899 )*
900 }
901 }
902 }
903
904 impl #impl_generics #serde::Serialize for #typed_name #ty_generics
905 where
906 #tagged_enum_name #ty_generics: #serde::Serialize,
907 Self: Clone,
908 {
909 fn serialize<S: #serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
910 #tagged_enum_name::from(self.clone()).serialize(serializer)
911 }
912 }
913
914 impl<'de, #unwrapped_generics> #serde::Deserialize<'de> for #typed_name #ty_generics
915 where
916 #maybe_tagged_enum_name #ty_generics: #serde::Deserialize<'de>
917 {
918 fn deserialize<D: #serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
919 #maybe_tagged_enum_name::deserialize(deserializer).map(Into::into)
920 }
921 }
922 };
923 }
924 }
925}