zink_codegen/
event.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
use heck::ToSnakeCase;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span};
use quote::{quote, ToTokens};
use syn::{
    parse::Parse, parse_quote, punctuated::Punctuated, spanned::Spanned, Arm, Data, DataEnum,
    DeriveInput, Expr, ExprMatch, Fields, FnArg, ImplItemFn, ItemFn, LitByteStr, Result, Token,
    Type, Variant, Visibility,
};

/// Expand the event interface with better error handling
pub fn parse(item: DeriveInput) -> TokenStream {
    let name = &item.ident;
    let name_str = name.to_string();
    let name_bytes = LitByteStr::new(name_str.as_bytes(), Span::call_site());

    // 1. Check if the name is too long
    if name_str.len() > 32 {
        panic!("Event name too long: {name_str}");
    }

    // 2. Ensure we are working with an enum
    let Data::Enum(event_enum) = &item.data else {
        panic!("Event can only be derived for enums");
    };

    // 3. Generate variant implementations
    let mut expr_match: ExprMatch = parse_quote!(match self {});
    let variant_fns = event_enum
        .variants
        .iter()
        .map(|variant| impl_variant_fns(variant, &mut expr_match))
        .collect::<Vec<_>>();

    // 4. Generate the impl block
    quote! {
        impl #name {
            /// Name of the event
            pub const fn name() -> &'static [u8] {
                #name_bytes
            }

            /// Emit the event name
            pub fn emit_name() {
                unsafe { zink::ffi::evm::log0(Self::name()) }
            }

            #(#variant_fns)*

            /// Emit the event
            pub fn emit(self) {
                #expr_match
            }
        }
    }
    .into()
}

/// Generate Variant Implementation with validation
fn impl_variant_fns(variant: &Variant, expr_match: &mut ExprMatch) -> ImplItemFn {
    let name = &variant.ident;
    let topic = variant.fields.len();

    // Parse function inputs
    let mut inputs: Punctuated<FnArg, Token![,]> = Punctuated::new();
    let mut args: Vec<Ident> = Vec::new();
    for (index, field) in variant.fields.iter().enumerate() {
        let var = field
            .ident
            .clone()
            .unwrap_or(Ident::new(&format!("param_{index}"), Span::call_site()));
        let ty = &field.ty;

        args.push(var.clone());
        inputs.push(FnArg::Typed(parse_quote!(#var: #ty)));
    }

    // Generate the snake case name
    let name_snake: Ident = Ident::new(&name.to_string().to_snake_case(), Span::call_site());

    // Generate the match arm
    let arm: Arm = parse_quote! {
        Self::#name( #(#args),* ) => Self::#name_snake( #(#args),* ),
    };
    expr_match.arms.push(arm);

    // Generate the impl block
    let logn = Ident::new(&format!("log{topic}"), Span::call_site());
    let args = args
        .iter()
        .map(|arg| quote!(#arg.bytes32()))
        .collect::<Vec<_>>();
    parse_quote! {
        pub fn #name_snake(#inputs) {
            unsafe {zink::ffi::evm::#logn(#(#args),*, &Self::name()) }
        }
    }
}