ethers_contract_abigen/contract/
events.rs

1//! Events expansion
2
3use super::{structs::expand_event_struct, types, Context};
4use crate::util;
5use ethers_core::{
6    abi::{Event, EventExt},
7    macros::{ethers_contract_crate, ethers_core_crate},
8};
9use eyre::Result;
10use inflector::Inflector;
11use proc_macro2::{Ident, TokenStream};
12use quote::quote;
13use std::collections::BTreeMap;
14
15impl Context {
16    /// Expands each event to a struct + its impl Detokenize block
17    pub fn events_declaration(&self) -> Result<TokenStream> {
18        let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
19        let data_types = sorted_events
20            .values()
21            .flatten()
22            .map(|event| self.expand_event(event))
23            .collect::<Result<Vec<_>>>()?;
24
25        // only expand enums when multiple events are present
26        let events_enum_decl =
27            if data_types.len() > 1 { Some(self.expand_events_enum()) } else { None };
28
29        Ok(quote! {
30            #( #data_types )*
31
32            #events_enum_decl
33        })
34    }
35
36    /// Generate the event filter methods for the contract
37    pub fn event_methods(&self) -> Result<TokenStream> {
38        let sorted_events: BTreeMap<_, _> = self.abi.events.iter().collect();
39        let filter_methods = sorted_events
40            .values()
41            .flat_map(std::ops::Deref::deref)
42            .map(|event| self.expand_filter(event));
43
44        let events_method = self.expand_events_method();
45
46        Ok(quote! {
47            #( #filter_methods )*
48
49            #events_method
50        })
51    }
52
53    /// Generate an enum with a variant for each event
54    fn expand_events_enum(&self) -> TokenStream {
55        let variants = self
56            .abi
57            .events
58            .values()
59            .flatten()
60            .map(|e| {
61                event_struct_name(&e.name, self.event_aliases.get(&e.abi_signature()).cloned())
62            })
63            .collect::<Vec<_>>();
64
65        let enum_name = self.expand_event_enum_name();
66
67        let mut derives = self.expand_extra_derives();
68        let params =
69            self.abi.events.values().flatten().flat_map(|err| &err.inputs).map(|param| &param.kind);
70        util::derive_builtin_traits(params, &mut derives, false, true);
71
72        let ethers_core = ethers_core_crate();
73        let ethers_contract = ethers_contract_crate();
74
75        quote! {
76            #[doc = "Container type for all of the contract's events"]
77            #[derive(Clone, #ethers_contract::EthAbiType, #derives)]
78            pub enum #enum_name {
79                #( #variants(#variants), )*
80            }
81
82            impl #ethers_contract::EthLogDecode for #enum_name {
83                fn decode_log(log: &#ethers_core::abi::RawLog) -> ::core::result::Result<Self, #ethers_core::abi::Error> {
84                    #(
85                        if let Ok(decoded) = #variants::decode_log(log) {
86                            return Ok(#enum_name::#variants(decoded))
87                        }
88                    )*
89                    Err(#ethers_core::abi::Error::InvalidData)
90                }
91            }
92
93            impl ::core::fmt::Display for #enum_name {
94                fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
95                    match self {
96                        #(
97                            Self::#variants(element) => ::core::fmt::Display::fmt(element, f),
98                        )*
99                    }
100                }
101            }
102
103            #(
104                impl ::core::convert::From<#variants> for #enum_name {
105                    fn from(value: #variants) -> Self {
106                        Self::#variants(value)
107                    }
108                }
109            )*
110        }
111    }
112
113    /// The name ident of the events enum
114    fn expand_event_enum_name(&self) -> Ident {
115        util::ident(&format!("{}Events", self.contract_ident))
116    }
117
118    /// Expands the `events` function that bundles all declared events of this contract
119    fn expand_events_method(&self) -> Option<TokenStream> {
120        let sorted_events: BTreeMap<_, _> = self.abi.events.clone().into_iter().collect();
121
122        let mut iter = sorted_events.values().flatten();
123        let ethers_contract = ethers_contract_crate();
124
125        if let Some(event) = iter.next() {
126            let ty = if iter.next().is_some() {
127                self.expand_event_enum_name()
128            } else {
129                event_struct_name(
130                    &event.name,
131                    self.event_aliases.get(&event.abi_signature()).cloned(),
132                )
133            };
134
135            Some(quote! {
136                /// Returns an `Event` builder for all the events of this contract.
137                pub fn events(&self) -> #ethers_contract::builders::Event<
138                    ::std::sync::Arc<M>,
139                    M,
140                    #ty,
141                > {
142                    self.0.event_with_filter(::core::default::Default::default())
143                }
144            })
145        } else {
146            None
147        }
148    }
149
150    /// Expands into a single method for contracting an event stream.
151    fn expand_filter(&self, event: &Event) -> TokenStream {
152        let name = &event.name;
153        let sig = event.abi_signature();
154        let alias = self.event_aliases.get(&sig).cloned();
155
156        // append `filter` to disambiguate with potentially conflicting function names
157        let function_name = {
158            let name = if let Some(ref id) = alias {
159                id.to_string().to_snake_case()
160            } else {
161                name.to_snake_case()
162            };
163            util::safe_ident(&format!("{name}_filter"))
164        };
165        let struct_name = event_struct_name(name, alias);
166
167        let doc_str = format!("Gets the contract's `{name}` event");
168
169        let ethers_contract = ethers_contract_crate();
170
171        quote! {
172            #[doc = #doc_str]
173            pub fn #function_name(&self) -> #ethers_contract::builders::Event<
174                ::std::sync::Arc<M>,
175                M,
176                #struct_name
177            > {
178                self.0.event()
179            }
180        }
181    }
182
183    /// Expands an ABI event into a single event data type. This can expand either
184    /// into a structure or a tuple in the case where all event parameters (topics
185    /// and data) are anonymous.
186    fn expand_event(&self, event: &Event) -> Result<TokenStream> {
187        let name = &event.name;
188        let abi_signature = event.abi_signature();
189        let alias = self.event_aliases.get(&abi_signature).cloned();
190
191        let struct_name = event_struct_name(name, alias);
192
193        let fields = types::expand_event_inputs(event, &self.internal_structs)?;
194        // expand as a tuple if all fields are anonymous
195        let all_anonymous_fields = event.inputs.iter().all(|input| input.name.is_empty());
196        let data_type_definition = expand_event_struct(&struct_name, &fields, all_anonymous_fields);
197
198        let mut derives = self.expand_extra_derives();
199        let params = event.inputs.iter().map(|param| &param.kind);
200        util::derive_builtin_traits(params, &mut derives, true, true);
201
202        let ethers_contract = ethers_contract_crate();
203
204        Ok(quote! {
205            #[derive(Clone, #ethers_contract::EthEvent, #ethers_contract::EthDisplay, #derives)]
206            #[ethevent(name = #name, abi = #abi_signature)]
207            pub #data_type_definition
208        })
209    }
210}
211
212/// Expands an ABI event into an identifier for its event data type.
213fn event_struct_name(event_name: &str, alias: Option<Ident>) -> Ident {
214    // TODO: get rid of `Filter` suffix?
215
216    let name = if let Some(id) = alias {
217        format!("{}Filter", id.to_string().to_pascal_case())
218    } else {
219        format!("{}Filter", event_name.to_pascal_case())
220    };
221    util::ident(&name)
222}
223
224/// Returns the alias name for an event
225pub(crate) fn event_struct_alias(event_name: &str) -> Ident {
226    util::ident(&event_name.to_pascal_case())
227}
228
229#[cfg(test)]
230mod tests {
231    use super::*;
232    use crate::Abigen;
233    use ethers_core::abi::{EventParam, ParamType};
234
235    fn test_context() -> Context {
236        Context::from_abigen(Abigen::new("TestToken", "[]").unwrap()).unwrap()
237    }
238
239    fn test_context_with_alias(sig: &str, alias: &str) -> Context {
240        Context::from_abigen(Abigen::new("TestToken", "[]").unwrap().add_event_alias(sig, alias))
241            .unwrap()
242    }
243
244    #[test]
245    fn expand_transfer_filter_with_alias() {
246        let event = Event {
247            name: "Transfer".into(),
248            inputs: vec![
249                EventParam { name: "from".into(), kind: ParamType::Address, indexed: true },
250                EventParam { name: "to".into(), kind: ParamType::Address, indexed: true },
251                EventParam { name: "amount".into(), kind: ParamType::Uint(256), indexed: false },
252            ],
253            anonymous: false,
254        };
255        let sig = "Transfer(address,address,uint256)";
256        let cx = test_context_with_alias(sig, "TransferEvent");
257        #[rustfmt::skip]
258        assert_quote!(cx.expand_filter(&event), {
259            #[doc = "Gets the contract's `Transfer` event"]
260            pub fn transfer_event_filter(
261                &self
262            ) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferEventFilter>
263            {
264                self.0.event()
265            }
266        });
267    }
268
269    #[test]
270    fn expand_transfer_filter() {
271        let event = Event {
272            name: "Transfer".into(),
273            inputs: vec![
274                EventParam { name: "from".into(), kind: ParamType::Address, indexed: true },
275                EventParam { name: "to".into(), kind: ParamType::Address, indexed: true },
276                EventParam { name: "amount".into(), kind: ParamType::Uint(256), indexed: false },
277            ],
278            anonymous: false,
279        };
280        let cx = test_context();
281        #[rustfmt::skip]
282        assert_quote!(cx.expand_filter(&event), {
283            #[doc = "Gets the contract's `Transfer` event"]
284            pub fn transfer_filter(
285                &self
286            ) -> ::ethers_contract::builders::Event<::std::sync::Arc<M>, M, TransferFilter>
287            {
288                self.0.event()
289            }
290        });
291    }
292
293    #[test]
294    fn expand_data_struct_value() {
295        let event = Event {
296            name: "Foo".into(),
297            inputs: vec![
298                EventParam { name: "a".into(), kind: ParamType::Bool, indexed: false },
299                EventParam { name: String::new(), kind: ParamType::Address, indexed: false },
300            ],
301            anonymous: false,
302        };
303
304        let cx = test_context();
305        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
306        let name = event_struct_name(&event.name, None);
307        let definition = expand_event_struct(&name, &params, false);
308
309        assert_quote!(definition, {
310            struct FooFilter {
311                pub a: bool,
312                pub p1: ::ethers_core::types::Address,
313            }
314        });
315    }
316
317    #[test]
318    fn expand_data_struct_with_alias() {
319        let event = Event {
320            name: "Foo".into(),
321            inputs: vec![
322                EventParam { name: "a".into(), kind: ParamType::Bool, indexed: false },
323                EventParam { name: String::new(), kind: ParamType::Address, indexed: false },
324            ],
325            anonymous: false,
326        };
327
328        let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
329        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
330        let alias = Some(util::ident("FooAliased"));
331        let name = event_struct_name(&event.name, alias);
332        let definition = expand_event_struct(&name, &params, false);
333
334        assert_quote!(definition, {
335            struct FooAliasedFilter {
336                pub a: bool,
337                pub p1: ::ethers_core::types::Address,
338            }
339        });
340    }
341
342    #[test]
343    fn expand_data_tuple_value() {
344        let event = Event {
345            name: "Foo".into(),
346            inputs: vec![
347                EventParam { name: String::new(), kind: ParamType::Bool, indexed: false },
348                EventParam { name: String::new(), kind: ParamType::Address, indexed: false },
349            ],
350            anonymous: false,
351        };
352
353        let cx = test_context();
354        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
355        let name = event_struct_name(&event.name, None);
356        let definition = expand_event_struct(&name, &params, true);
357
358        assert_quote!(definition, {
359            struct FooFilter(pub bool, pub ::ethers_core::types::Address);
360        });
361    }
362
363    #[test]
364    fn expand_data_tuple_value_with_alias() {
365        let event = Event {
366            name: "Foo".into(),
367            inputs: vec![
368                EventParam { name: String::new(), kind: ParamType::Bool, indexed: false },
369                EventParam { name: String::new(), kind: ParamType::Address, indexed: false },
370            ],
371            anonymous: false,
372        };
373
374        let cx = test_context_with_alias("Foo(bool,address)", "FooAliased");
375        let params = types::expand_event_inputs(&event, &cx.internal_structs).unwrap();
376        let alias = Some(util::ident("FooAliased"));
377        let name = event_struct_name(&event.name, alias);
378        let definition = expand_event_struct(&name, &params, true);
379
380        assert_quote!(definition, {
381            struct FooAliasedFilter(pub bool, pub ::ethers_core::types::Address);
382        });
383    }
384}