alloy_tx_macros/
parse.rs

1use darling::{FromDeriveInput, FromMeta, FromVariant};
2use proc_macro2::TokenStream;
3use quote::quote;
4use syn::{Ident, Path, Type};
5
6/// Container-level arguments for the TransactionEnvelope derive macro.
7#[derive(Debug, FromDeriveInput)]
8#[darling(attributes(envelope))]
9pub(crate) struct EnvelopeArgs {
10    /// The identifier of the input enum.
11    pub ident: Ident,
12
13    /// The generics of the input enum.
14    pub generics: syn::Generics,
15
16    /// Custom name for the generated transaction type enum.
17    /// Defaults to `{EnumName}Type`.
18    #[darling(default)]
19    pub tx_type_name: Option<Ident>,
20
21    /// Custom path to the alloy_consensus crate.
22    /// Defaults to `::alloy_consensus`.
23    #[darling(default)]
24    pub alloy_consensus: Option<Path>,
25
26    /// Custom `cfg_attr` value for serde implementations.
27    #[darling(default)]
28    pub serde_cfg: Option<syn::Meta>,
29
30    /// Custom `cfg_attr` value for arbitrary implementations.
31    #[darling(default)]
32    pub arbitrary_cfg: Option<syn::Meta>,
33
34    /// Optional typed transaction enum name to generate.
35    /// When specified, generates a corresponding TypedTransaction enum.
36    #[darling(default)]
37    pub typed: Option<Ident>,
38
39    /// The enum data (variants).
40    pub data: darling::ast::Data<EnvelopeVariant, ()>,
41}
42
43/// Variant of transaction envelope enum.
44#[derive(Debug, FromVariant)]
45#[darling(attributes(envelope), forward_attrs(serde, doc))]
46pub(crate) struct EnvelopeVariant {
47    /// The identifier of the variant.
48    pub ident: Ident,
49
50    /// The fields of the variant.
51    pub fields: darling::ast::Fields<syn::Type>,
52
53    /// Kind of the variant.
54    #[darling(flatten)]
55    pub kind: VariantKind,
56
57    /// Optional custom typed transaction type for this variant.
58    #[darling(default)]
59    pub typed: Option<Ident>,
60
61    /// Forwarded attributes.
62    pub attrs: Vec<syn::Attribute>,
63}
64
65/// Kind of the envelope variant.
66#[derive(Debug, Clone, FromMeta)]
67pub(crate) enum VariantKind {
68    /// A standalone transaction with a type tag.
69    #[darling(rename = "ty")]
70    Typed(u8),
71    /// Flattened envelope.
72    #[darling(word, rename = "flatten")]
73    Flattened,
74}
75
76impl VariantKind {
77    /// Returns serde transaction enum tag and aliases.
78    pub(crate) fn serde_tag_and_aliases(&self) -> (String, Vec<String>) {
79        let Self::Typed(ty) = self else { return Default::default() };
80
81        let tx_type_hex = format!("{ty:x}");
82
83        let mut aliases = vec![];
84        // Add alias for single digit hex values (e.g., "0x0" for "0x00")
85        if tx_type_hex.len() == 1 {
86            aliases.push(format!("0x0{}", tx_type_hex));
87        }
88
89        // Add alias for uppercase values (e.g., "0x7E" for "0x7e")
90        if tx_type_hex != tx_type_hex.to_uppercase() {
91            aliases.push(format!("0x{}", tx_type_hex.to_uppercase()));
92        }
93
94        (format!("0x{tx_type_hex}"), aliases)
95    }
96}
97
98/// Processed variant information.
99#[derive(Debug, Clone)]
100pub(crate) struct ProcessedVariant {
101    /// The variant name.
102    pub name: Ident,
103    /// The inner type of the variant.
104    pub ty: Type,
105    /// The kind of variant.
106    pub kind: VariantKind,
107    /// The serde attributes for the variant.
108    pub serde_attrs: Option<TokenStream>,
109    /// The doc attributes for the variant.
110    pub doc_attrs: Vec<syn::Attribute>,
111    /// Optional custom typed transaction type for this variant.
112    pub typed: Option<Ident>,
113}
114
115impl ProcessedVariant {
116    /// Returns true if this is a legacy transaction variant (type 0).
117    pub(crate) const fn is_legacy(&self) -> bool {
118        matches!(self.kind, VariantKind::Typed(0))
119    }
120
121    /// Returns the inner type to use as unsigned type for the typed transaction enum.
122    pub(crate) fn inner_type(&self) -> TokenStream {
123        // If a custom type is provided, use it
124        if let Some(custom) = &self.typed {
125            return quote! { #custom };
126        }
127
128        let ty = &self.ty;
129
130        // For most cases, we need to extract T from Signed<T>
131        if let syn::Type::Path(type_path) = ty {
132            if let Some(segment) = type_path.path.segments.last() {
133                if segment.ident == "Signed" || segment.ident == "Sealed" {
134                    if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
135                        if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
136                            return quote! { #inner_ty };
137                        }
138                    }
139                }
140            }
141        }
142        // Fallback to original type
143        quote! { #ty }
144    }
145}
146
147/// Groups variants by their kind for easier code generation.
148pub(crate) struct GroupedVariants {
149    /// All variants.
150    pub all: Vec<ProcessedVariant>,
151    /// Only typed variants (with transaction type IDs).
152    pub typed: Vec<ProcessedVariant>,
153    /// Only flattened variants.
154    pub flattened: Vec<ProcessedVariant>,
155}
156
157impl GroupedVariants {
158    /// Create grouped variants from a list of processed variants.
159    pub(crate) fn from_args(args: EnvelopeArgs) -> darling::Result<Self> {
160        // Validate it's an enum
161        let variants = match args.data {
162            darling::ast::Data::Enum(variants) => variants,
163            _ => {
164                return Err(darling::Error::custom(
165                    "`TransactionEnvelope` can only be derived for enums",
166                )
167                .with_span(&args.ident));
168            }
169        };
170
171        let mut processed = Vec::new();
172        for variant in variants {
173            let EnvelopeVariant { ident, fields, kind, attrs, typed } = variant;
174
175            let mut serde_attrs = None;
176            let mut doc_attrs = Vec::new();
177
178            for attr in attrs {
179                if attr.path().is_ident("serde") {
180                    if let syn::Meta::List(list) = attr.meta {
181                        serde_attrs = Some(list.tokens);
182                    }
183                } else if attr.path().is_ident("doc") {
184                    doc_attrs.push(attr);
185                }
186            }
187
188            // Check that variant has exactly one unnamed field
189            let ty = match &fields.style {
190                darling::ast::Style::Tuple if fields.len() == 1 => fields.fields[0].clone(),
191                darling::ast::Style::Tuple => {
192                    return Err(darling::Error::custom(format!(
193                        "expected exactly one field, found {}",
194                        fields.len()
195                    ))
196                    .with_span(&ident))
197                }
198                _ => {
199                    return Err(darling::Error::custom(
200                        "TransactionEnvelope variants must have a single unnamed field",
201                    )
202                    .with_span(&ident))
203                }
204            };
205
206            processed.push(ProcessedVariant {
207                name: ident,
208                ty,
209                kind,
210                serde_attrs,
211                doc_attrs,
212                typed,
213            });
214        }
215
216        let typed =
217            processed.iter().filter(|v| matches!(v.kind, VariantKind::Typed(_))).cloned().collect();
218
219        let flattened = processed
220            .iter()
221            .filter(|v| matches!(v.kind, VariantKind::Flattened))
222            .cloned()
223            .collect();
224
225        Ok(Self { all: processed, typed, flattened })
226    }
227
228    /// Find the legacy variant if it exists.
229    pub(crate) fn legacy_variant(&self) -> Option<&ProcessedVariant> {
230        self.all.iter().find(|v| v.is_legacy())
231    }
232
233    /// Get all variant names.
234    pub(crate) fn variant_names(&self) -> Vec<&syn::Ident> {
235        self.all.iter().map(|v| &v.name).collect()
236    }
237
238    /// Get all variant types.
239    pub(crate) fn variant_types(&self) -> Vec<&syn::Type> {
240        self.all.iter().map(|v| &v.ty).collect()
241    }
242}
243
244#[cfg(test)]
245mod tests {
246    use super::*;
247
248    #[test]
249    fn serde_tag() {
250        assert_eq!(
251            VariantKind::Typed(126).serde_tag_and_aliases(),
252            ("0x7e".to_string(), vec!["0x7E".to_string()])
253        );
254        assert_eq!(
255            VariantKind::Typed(1).serde_tag_and_aliases(),
256            ("0x1".to_string(), vec!["0x01".to_string()])
257        );
258        assert_eq!(
259            VariantKind::Typed(10).serde_tag_and_aliases(),
260            ("0xa".to_string(), vec!["0x0a".to_string(), "0xA".to_string()])
261        );
262    }
263}