alloy_sol_macro_expander/expand/
event.rs1use super::{ExpCtxt, anon_name, expand_event_tokenize, expand_tuple_types};
4use alloy_sol_macro_input::{ContainsSolAttrs, mk_doc};
5use ast::{EventParameter, ItemEvent, SolIdent, Spanned};
6use proc_macro2::TokenStream;
7use quote::{quote, quote_spanned};
8use syn::Result;
9
10pub(super) fn expand(cx: &ExpCtxt<'_>, event: &ItemEvent) -> Result<TokenStream> {
22 let params = event.params();
23
24 let (sol_attrs, mut attrs) = event.split_attrs()?;
25 cx.derives(&mut attrs, ¶ms, true);
26 let docs = sol_attrs.docs.or(cx.attrs.docs).unwrap_or(true);
27 let abi = sol_attrs.abi.or(cx.attrs.abi).unwrap_or(false);
28
29 cx.assert_resolved(¶ms)?;
30 event.assert_valid()?;
31
32 let name = cx.overloaded_name(event.into());
33 let signature = cx.event_signature(event);
34 let selector = crate::utils::event_selector(&signature);
35 let anonymous = event.is_anonymous();
36
37 let first_topic = (!anonymous).then(|| quote!(alloy_sol_types::sol_data::FixedBytes<32>));
39 let topic_list = event.indexed_params().map(|p| expand_event_topic_type(p, cx));
40 let topic_list = first_topic.into_iter().chain(topic_list);
41
42 let (data_tuple, _) = expand_tuple_types(event.non_indexed_params().map(|p| &p.ty), cx);
43
44 let mut topic_i = !anonymous as usize;
46 let mut data_i = 0usize;
47 let new_impl = event.parameters.iter().enumerate().map(|(i, p)| {
48 let name = anon_name((i, p.name.as_ref()));
49 let param;
50 if p.is_indexed() {
51 let i = syn::Index::from(topic_i);
52 param = quote!(topics.#i);
53 topic_i += 1;
54 } else {
55 let i = syn::Index::from(data_i);
56 param = quote!(data.#i);
57 data_i += 1;
58 }
59 quote!(#name: #param)
60 });
61
62 let topic_tuple_names = event
64 .parameters
65 .iter()
66 .enumerate()
67 .filter(|(_, p)| p.is_indexed())
68 .map(|(i, p)| (i, p.name.as_ref()))
69 .map(anon_name);
70
71 let topics_impl = if anonymous {
72 quote! {(#(self.#topic_tuple_names.clone(),)*)}
73 } else {
74 quote! {(Self::SIGNATURE_HASH.into(), #(self.#topic_tuple_names.clone(),)*)}
75 };
76
77 let encode_first_topic =
78 (!anonymous).then(|| quote!(alloy_sol_types::abi::token::WordToken(Self::SIGNATURE_HASH)));
79
80 let encode_topics_impl = event.parameters.iter().enumerate().filter(|(_, p)| p.is_indexed()).map(|(i, p)| {
81 let name = anon_name((i, p.name.as_ref()));
82 let ty = cx.expand_type(&p.ty);
83
84 if cx.indexed_as_hash(p) {
85 quote! {
86 <alloy_sol_types::sol_data::FixedBytes<32> as alloy_sol_types::EventTopic>::encode_topic(&self.#name)
87 }
88 } else {
89 quote! {
90 <#ty as alloy_sol_types::EventTopic>::encode_topic(&self.#name)
91 }
92 }
93 });
94
95 let fields = event
96 .parameters
97 .iter()
98 .enumerate()
99 .map(|(i, p)| expand_event_topic_field(i, p, p.name.as_ref(), cx));
100
101 let check_signature = (!anonymous).then(|| {
102 quote! {
103 #[inline]
104 fn check_signature(topics: &<Self::TopicList as alloy_sol_types::SolType>::RustType) -> alloy_sol_types::Result<()> {
105 if topics.0 != Self::SIGNATURE_HASH {
106 return Err(alloy_sol_types::Error::invalid_event_signature_hash(Self::SIGNATURE, topics.0, Self::SIGNATURE_HASH));
107 }
108 Ok(())
109 }
110 }
111 });
112
113 let tokenize_body_impl = expand_event_tokenize(
114 &event.parameters,
115 cx,
116 event.parameters.len(),
117 super::FieldKind::Original,
118 );
119
120 let encode_topics_impl = encode_first_topic
121 .into_iter()
122 .chain(encode_topics_impl)
123 .enumerate()
124 .map(|(i, assign)| quote!(out[#i] = #assign;));
125
126 let doc = docs.then(|| {
127 let selector = hex::encode_prefixed(selector.array.as_slice());
128 mk_doc(format!(
129 "Event with signature `{signature}` and selector `{selector}`.\n\
130 ```solidity\n{event}\n```"
131 ))
132 });
133
134 let abi: Option<TokenStream> = abi.then(|| {
135 if_json! {
136 let event = super::to_abi::generate(event, cx);
137 quote! {
138 #[automatically_derived]
139 impl alloy_sol_types::JsonAbiExt for #name {
140 type Abi = alloy_sol_types::private::alloy_json_abi::Event;
141
142 fn abi() -> Self::Abi {
143 #event
144 }
145 }
146 }
147 }
148 });
149
150 let alloy_sol_types = &cx.crates.sol_types;
151
152 let event_struct = if event.parameters.is_empty() {
153 quote! {
154 pub struct #name;
155 }
156 } else {
157 quote! {
158 pub struct #name {
159 #(#fields,)*
160 }
161 }
162 };
163
164 let tokens = quote! {
165 #(#attrs)*
166 #doc
167 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
168 #[derive(Clone)]
169 #event_struct
170
171 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
172 const _: () = {
173 use #alloy_sol_types as alloy_sol_types;
174
175 #[automatically_derived]
176 impl alloy_sol_types::SolEvent for #name {
177 type DataTuple<'a> = #data_tuple;
178 type DataToken<'a> = <Self::DataTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
179
180 type TopicList = (#(#topic_list,)*);
181
182 const SIGNATURE: &'static str = #signature;
183 const SIGNATURE_HASH: alloy_sol_types::private::B256 =
184 alloy_sol_types::private::B256::new(#selector);
185
186 const ANONYMOUS: bool = #anonymous;
187
188 #[allow(unused_variables)]
189 #[inline]
190 fn new(
191 topics: <Self::TopicList as alloy_sol_types::SolType>::RustType,
192 data: <Self::DataTuple<'_> as alloy_sol_types::SolType>::RustType,
193 ) -> Self {
194 Self {
195 #(#new_impl,)*
196 }
197 }
198
199 #check_signature
200
201 #[inline]
202 fn tokenize_body(&self) -> Self::DataToken<'_> {
203 #tokenize_body_impl
204 }
205
206 #[inline]
207 fn topics(&self) -> <Self::TopicList as alloy_sol_types::SolType>::RustType {
208 #topics_impl
209 }
210
211 #[inline]
212 fn encode_topics_raw(
213 &self,
214 out: &mut [alloy_sol_types::abi::token::WordToken],
215 ) -> alloy_sol_types::Result<()> {
216 if out.len() < <Self::TopicList as alloy_sol_types::TopicList>::COUNT {
217 return Err(alloy_sol_types::Error::Overrun);
218 }
219 #(#encode_topics_impl)*
220 Ok(())
221 }
222 }
223
224 #[automatically_derived]
225 impl alloy_sol_types::private::IntoLogData for #name {
226 fn to_log_data(&self) -> alloy_sol_types::private::LogData {
227 From::from(self)
228 }
229
230 fn into_log_data(self) -> alloy_sol_types::private::LogData {
231 From::from(&self)
232 }
233 }
234
235
236 #[automatically_derived]
237 impl From<&#name> for alloy_sol_types::private::LogData {
238 #[inline]
239 fn from(this: &#name) -> alloy_sol_types::private::LogData {
240 alloy_sol_types::SolEvent::encode_log_data(this)
241 }
242 }
243
244 #abi
245 };
246 };
247 Ok(tokens)
248}
249
250fn expand_event_topic_type(param: &EventParameter, cx: &ExpCtxt<'_>) -> TokenStream {
251 let alloy_sol_types = &cx.crates.sol_types;
252 assert!(param.is_indexed());
253 if cx.indexed_as_hash(param) {
254 quote_spanned! {param.ty.span()=> #alloy_sol_types::sol_data::FixedBytes<32> }
255 } else {
256 cx.expand_type(¶m.ty)
257 }
258}
259
260fn expand_event_topic_field(
261 i: usize,
262 param: &EventParameter,
263 name: Option<&SolIdent>,
264 cx: &ExpCtxt<'_>,
265) -> TokenStream {
266 let name = anon_name((i, name));
267 let ty = cx.expand_event_param_type(param);
268 let attrs = ¶m.attrs;
269 quote! {
270 #(#attrs)*
271 #[allow(missing_docs)]
272 pub #name: #ty
273 }
274}