alloy_sol_macro_expander/expand/
event.rs
1use super::{anon_name, expand_event_tokenize, expand_tuple_types, ExpCtxt};
4use alloy_sol_macro_input::{mk_doc, ContainsSolAttrs};
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(&event.parameters, cx);
114
115 let encode_topics_impl = encode_first_topic
116 .into_iter()
117 .chain(encode_topics_impl)
118 .enumerate()
119 .map(|(i, assign)| quote!(out[#i] = #assign;));
120
121 let doc = docs.then(|| {
122 let selector = hex::encode_prefixed(selector.array.as_slice());
123 mk_doc(format!(
124 "Event with signature `{signature}` and selector `{selector}`.\n\
125 ```solidity\n{event}\n```"
126 ))
127 });
128
129 let abi: Option<TokenStream> = abi.then(|| {
130 if_json! {
131 let event = super::to_abi::generate(event, cx);
132 quote! {
133 #[automatically_derived]
134 impl alloy_sol_types::JsonAbiExt for #name {
135 type Abi = alloy_sol_types::private::alloy_json_abi::Event;
136
137 #[inline]
138 fn abi() -> Self::Abi {
139 #event
140 }
141 }
142 }
143 }
144 });
145
146 let alloy_sol_types = &cx.crates.sol_types;
147
148 let tokens = quote! {
149 #(#attrs)*
150 #doc
151 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
152 #[derive(Clone)]
153 pub struct #name {
154 #( #fields, )*
155 }
156
157 #[allow(non_camel_case_types, non_snake_case, clippy::pub_underscore_fields, clippy::style)]
158 const _: () = {
159 use #alloy_sol_types as alloy_sol_types;
160
161 #[automatically_derived]
162 impl alloy_sol_types::SolEvent for #name {
163 type DataTuple<'a> = #data_tuple;
164 type DataToken<'a> = <Self::DataTuple<'a> as alloy_sol_types::SolType>::Token<'a>;
165
166 type TopicList = (#(#topic_list,)*);
167
168 const SIGNATURE: &'static str = #signature;
169 const SIGNATURE_HASH: alloy_sol_types::private::B256 =
170 alloy_sol_types::private::B256::new(#selector);
171
172 const ANONYMOUS: bool = #anonymous;
173
174 #[allow(unused_variables)]
175 #[inline]
176 fn new(
177 topics: <Self::TopicList as alloy_sol_types::SolType>::RustType,
178 data: <Self::DataTuple<'_> as alloy_sol_types::SolType>::RustType,
179 ) -> Self {
180 Self {
181 #(#new_impl,)*
182 }
183 }
184
185 #check_signature
186
187 #[inline]
188 fn tokenize_body(&self) -> Self::DataToken<'_> {
189 #tokenize_body_impl
190 }
191
192 #[inline]
193 fn topics(&self) -> <Self::TopicList as alloy_sol_types::SolType>::RustType {
194 #topics_impl
195 }
196
197 #[inline]
198 fn encode_topics_raw(
199 &self,
200 out: &mut [alloy_sol_types::abi::token::WordToken],
201 ) -> alloy_sol_types::Result<()> {
202 if out.len() < <Self::TopicList as alloy_sol_types::TopicList>::COUNT {
203 return Err(alloy_sol_types::Error::Overrun);
204 }
205 #(#encode_topics_impl)*
206 Ok(())
207 }
208 }
209
210 #[automatically_derived]
211 impl alloy_sol_types::private::IntoLogData for #name {
212 fn to_log_data(&self) -> alloy_sol_types::private::LogData {
213 From::from(self)
214 }
215
216 fn into_log_data(self) -> alloy_sol_types::private::LogData {
217 From::from(&self)
218 }
219 }
220
221
222 #[automatically_derived]
223 impl From<&#name> for alloy_sol_types::private::LogData {
224 #[inline]
225 fn from(this: &#name) -> alloy_sol_types::private::LogData {
226 alloy_sol_types::SolEvent::encode_log_data(this)
227 }
228 }
229
230 #abi
231 };
232 };
233 Ok(tokens)
234}
235
236fn expand_event_topic_type(param: &EventParameter, cx: &ExpCtxt<'_>) -> TokenStream {
237 let alloy_sol_types = &cx.crates.sol_types;
238 assert!(param.is_indexed());
239 if cx.indexed_as_hash(param) {
240 quote_spanned! {param.ty.span()=> #alloy_sol_types::sol_data::FixedBytes<32> }
241 } else {
242 cx.expand_type(¶m.ty)
243 }
244}
245
246fn expand_event_topic_field(
247 i: usize,
248 param: &EventParameter,
249 name: Option<&SolIdent>,
250 cx: &ExpCtxt<'_>,
251) -> TokenStream {
252 let name = anon_name((i, name));
253 let ty = if cx.indexed_as_hash(param) {
254 let bytes32 = ast::Type::FixedBytes(name.span(), core::num::NonZeroU16::new(32).unwrap());
255 cx.expand_rust_type(&bytes32)
256 } else {
257 cx.expand_rust_type(¶m.ty)
258 };
259 let attrs = ¶m.attrs;
260 quote! {
261 #(#attrs)*
262 #[allow(missing_docs)]
263 pub #name: #ty
264 }
265}