zink_codegen/
event.rs

1use heck::ToSnakeCase;
2use proc_macro::TokenStream;
3use proc_macro2::{Ident, Span};
4use quote::{quote, ToTokens};
5use syn::{
6    parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Arm, Data, DataEnum,
7    DeriveInput, Expr, ExprMatch, Fields, FnArg, ImplItemFn, ItemFn, LitByteStr, Result, Token,
8    Type, Variant, Visibility,
9};
10
11/// Expand the event interface with better error handling
12pub fn parse(item: DeriveInput) -> TokenStream {
13    let name = &item.ident;
14    let name_str = name.to_string();
15    let name_bytes = LitByteStr::new(name_str.as_bytes(), Span::call_site());
16
17    if name_str.len() > 32 {
18        panic!("Event name too long: {name_str}");
19    }
20
21    let Data::Enum(event_enum) = &item.data else {
22        panic!("Event can only be derived for enums");
23    };
24
25    let mut expr_match: ExprMatch = parse_quote!(match self {});
26    let variant_fns = event_enum
27        .variants
28        .iter()
29        .map(|variant| impl_variant_fns(variant, &mut expr_match))
30        .collect::<Vec<_>>();
31
32    let variant_abis = event_enum
33        .variants
34        .iter()
35        .map(|variant| abi_for_variant(&name_str, variant))
36        .collect::<Vec<_>>();
37
38    let combined_abi = format!("[{}]", variant_abis.join(","));
39    let combined_abi_lit = proc_macro2::Literal::string(&combined_abi);
40
41    quote! {
42        impl #name {
43            // Name of the event
44            pub const fn name() -> &'static [u8] {
45                #name_bytes
46            }
47
48            pub fn emit_name() {
49                unsafe { zink::asm::evm::log0(Self::name()) }
50            }
51
52            pub fn register_abi() {
53                let abi = Self::abi();
54                unsafe {
55                    let ptr = abi.as_ptr() as u32;
56                    let len = abi.len() as u32;
57                    zink::asm::emit_abi(ptr, len);
58                }
59            }
60
61            pub fn abi() -> &'static str {
62                #combined_abi_lit
63            }
64
65            #(#variant_fns)*
66
67            pub fn emit(self) {
68                #expr_match
69            }
70        }
71    }
72    .into()
73}
74
75fn abi_for_variant(event_name: &str, variant: &Variant) -> String {
76    let variant_name = variant.ident.to_string();
77    let mut params = Vec::new();
78    let mut indexed_count = 0;
79
80    for (index, field) in variant.fields.iter().enumerate() {
81        let param_name = field
82            .ident
83            .clone()
84            .unwrap_or(Ident::new(&format!("param_{index}"), Span::call_site()));
85
86        let type_str = get_solidity_type(&field.ty)
87            .unwrap_or_else(|e| panic!("Unsupported type for {}: {}", param_name, e));
88
89        let is_indexed = field
90            .attrs
91            .iter()
92            .any(|attr| attr.path().is_ident("indexed"));
93        if is_indexed {
94            indexed_count += 1;
95            if indexed_count > 3
96                && !variant
97                    .attrs
98                    .iter()
99                    .any(|attr| attr.path().is_ident("anonymous"))
100            {
101                panic!(
102                    "Event '{}' exceeds 3 indexed parameters for non-anonymous event",
103                    variant_name
104                );
105            }
106            if indexed_count > 4 {
107                panic!(
108                    "Event '{}' exceeds 4 indexed parameters even for anonymous event",
109                    variant_name
110                );
111            }
112        }
113        params.push(format!(
114            r#"{{"name":"{}","type":"{}","indexed":{}}}"#,
115            param_name, type_str, is_indexed
116        ));
117    }
118
119    let is_anonymous = variant
120        .attrs
121        .iter()
122        .any(|attr| attr.path().is_ident("anonymous"));
123    format!(
124        r#"{{"type":"event","name":"{}","inputs":[{}],"anonymous":{}}}"#,
125        event_name,
126        params.join(","),
127        is_anonymous
128    )
129}
130
131fn get_solidity_type(ty: &Type) -> Result<String> {
132    match ty {
133        Type::Path(type_path) => {
134            let segment = type_path.path.segments.last().ok_or_else(|| {
135                syn::Error::new(ty.span(), "Invalid type path for event parameter")
136            })?;
137            let ident = &segment.ident;
138
139            match ident.to_string().as_str() {
140                "Address" => Ok("address".to_string()),
141                "U256" => Ok("uint256".to_string()),
142                "I256" => Ok("int256".to_string()),
143                "Bytes32" => Ok("bytes32".to_string()),
144                "bool" => Ok("bool".to_string()),
145                "String" => Ok("string".to_string()),
146                "Bytes" => Ok("bytes".to_string()),
147                "Vec" => {
148                    if let syn::PathArguments::AngleBracketed(args) = &segment.arguments {
149                        if let Some(syn::GenericArgument::Type(inner_ty)) = args.args.first() {
150                            let inner_type = get_solidity_type(inner_ty)?;
151                            Ok(format!("{}[]", inner_type))
152                        } else {
153                            Err(syn::Error::new(ty.span(), "Vec requires a type argument"))
154                        }
155                    } else {
156                        Err(syn::Error::new(ty.span(), "Vec requires a type argument"))
157                    }
158                }
159                _ => Err(syn::Error::new(
160                    ty.span(),
161                    "Unsupported type for event parameter",
162                )),
163            }
164        }
165        _ => Err(syn::Error::new(
166            ty.span(),
167            "Unsupported type for event parameter",
168        )),
169    }
170}
171
172fn impl_variant_fns(variant: &Variant, expr_match: &mut ExprMatch) -> ImplItemFn {
173    let name = &variant.ident;
174    let topic = variant.fields.len();
175
176    // Parse function inputs
177    let mut inputs: Punctuated<FnArg, Token![,]> = Punctuated::new();
178    let mut args: Vec<Ident> = Vec::new();
179    let mut fields: Vec<proc_macro2::TokenStream> = Vec::new();
180
181    for (index, field) in variant.fields.iter().enumerate() {
182        let var = field
183            .ident
184            .clone()
185            .unwrap_or(Ident::new(&format!("param_{index}"), Span::call_site()));
186        let ty = &field.ty;
187
188        args.push(var.clone());
189        inputs.push(FnArg::Typed(parse_quote!(#var: #ty)));
190        fields.push(quote!(#var));
191    }
192
193    let name_snake = Ident::new(&name.to_string().to_snake_case(), Span::call_site());
194
195    let arm = match &variant.fields {
196        Fields::Named(_) => parse_quote! {
197            Self::#name { #(#args),* } => Self::#name_snake(#(#args),*),
198        },
199        Fields::Unnamed(_) => parse_quote! {
200            Self::#name(#(#args),*) => Self::#name_snake(#(#args),*),
201        },
202        Fields::Unit => parse_quote! {
203            Self::#name => Self::#name_snake(),
204        },
205    };
206    expr_match.arms.push(arm);
207
208    let logn = Ident::new(&format!("log{topic}"), Span::call_site());
209    let args = args
210        .iter()
211        .map(|arg| quote!(#arg.to_bytes32()))
212        .collect::<Vec<_>>();
213
214    parse_quote! {
215        pub fn #name_snake(#inputs) {
216            unsafe { zink::asm::evm::#logn(#(#args),*, &Self::name()) }
217        }
218    }
219}