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
11pub 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 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 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}