event_scanner/
event_filter.rs

1use std::fmt::{Debug, Display};
2
3use alloy::{
4    primitives::{Address, keccak256},
5    rpc::types::{Filter, Topic, ValueOrArray},
6};
7
8/// Type representing filters to apply when fetching events from the chain.
9///
10/// # Examples
11///
12/// ```rust
13/// use alloy::primitives::address;
14/// use event_scanner::EventFilter;
15///
16/// pub async fn create_event_filter() -> EventFilter {
17///     let contract_address = address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045");
18///     let filter = EventFilter::new()
19///         .with_contract_address(contract_address)
20///         .with_event("Transfer(address,address,uint256)");
21///
22///     filter
23/// }
24/// ```
25#[derive(Clone, Default)]
26pub struct EventFilter {
27    /// Contract addresses to filter events from. If empty, events from all contracts will be
28    /// tracked.
29    pub(crate) contract_addresses: Vec<Address>,
30    /// Human-readable event signatures, e.g. "Transfer(address,address,uint256)".
31    pub(crate) events: Vec<String>,
32    /// Event signature hashes, e.g. `0x123...`.
33    pub(crate) event_signatures: Topic,
34}
35
36impl Display for EventFilter {
37    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
38        let mut content = vec![];
39        if !self.contract_addresses.is_empty() {
40            let contracts = self
41                .contract_addresses
42                .iter()
43                .map(|addr| format!("{addr}"))
44                .collect::<Vec<_>>()
45                .join(", ");
46            content.push(format!("contracts: [{contracts}]",));
47        }
48        if !self.events.is_empty() {
49            content.push(format!("events: [{}]", self.events.join(", ")));
50        }
51        if !self.event_signatures.is_empty() {
52            // No guarantee the order of values returned by `Topic`
53            let value_or_array = self.event_signatures.to_value_or_array().unwrap();
54            let event_signatures = match value_or_array {
55                ValueOrArray::Value(value) => format!("{value}"),
56                ValueOrArray::Array(arr) => {
57                    arr.iter().map(|t| format!("{t}")).collect::<Vec<_>>().join(", ")
58                }
59            };
60            content.push(format!("event_signatures: [{event_signatures}]"));
61        }
62
63        write!(f, "EventFilter({})", content.join(", "))
64    }
65}
66
67impl Debug for EventFilter {
68    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
69        write!(f, "{self}")
70    }
71}
72
73impl EventFilter {
74    /// Creates a new [`EventFilter`].
75    #[must_use]
76    pub fn new() -> Self {
77        EventFilter::default()
78    }
79
80    /// Sets the contract address to filter events from.
81    /// If not set, events from all contracts will be tracked.
82    #[must_use]
83    pub fn with_contract_address(mut self, contract_address: impl Into<Address>) -> Self {
84        self.contract_addresses.push(contract_address.into());
85        self
86    }
87
88    /// Sets the contract address to filter events from.
89    /// If not set, events from all contracts will be tracked.
90    #[must_use]
91    pub fn with_contract_addresses(
92        mut self,
93        contract_addresses: impl IntoIterator<Item = impl Into<Address>>,
94    ) -> Self {
95        self.contract_addresses.extend(contract_addresses.into_iter().map(Into::into));
96        self
97    }
98
99    /// Sets the event signature to filter specific events.
100    /// If neither events nor event signature hashes are set, all events from the specified
101    /// contract(s) will be tracked.
102    #[must_use]
103    pub fn with_event(mut self, event: impl Into<String>) -> Self {
104        let event = event.into();
105        if !event.is_empty() {
106            self.events.push(event);
107        }
108        self
109    }
110
111    /// Sets the event signatures to filter specific events.
112    /// If neither events nor event signature hashes are set, all events from the specified
113    /// contract(s) will be tracked.
114    #[must_use]
115    pub fn with_events(mut self, events: impl IntoIterator<Item = impl Into<String>>) -> Self {
116        self.events.extend(events.into_iter().map(Into::into));
117        self
118    }
119
120    /// Sets the event signature hash to filter specific events.
121    /// If neither event signature hashes nor events are set, all events from the specified
122    /// contract(s) will be tracked.
123    #[must_use]
124    pub fn with_event_signature(mut self, event_signature: impl Into<Topic>) -> Self {
125        self.event_signatures = self.event_signatures.extend(event_signature.into());
126        self
127    }
128
129    /// Sets the event signature hashes to filter specific events.
130    /// If neither event signature hashes nor events are set, all events from the specified
131    /// contract(s) will be tracked.
132    #[must_use]
133    pub fn with_event_signatures(
134        mut self,
135        event_signatures: impl IntoIterator<Item = impl Into<Topic>>,
136    ) -> Self {
137        for event_signature in event_signatures {
138            self.event_signatures = self.event_signatures.extend(event_signature);
139        }
140        self
141    }
142
143    /// Returns a [`Topic`] containing all event signature hashes.
144    /// If neither events nor event signature hashes are set, an empty [`Topic`] is returned.
145    #[must_use]
146    fn all_events(&self) -> Topic {
147        let events = self.events.iter().map(|e| keccak256(e.as_bytes())).collect::<Vec<_>>();
148        let sigs = self.event_signatures.clone();
149        sigs.extend(events)
150    }
151}
152
153impl From<EventFilter> for Filter {
154    fn from(value: EventFilter) -> Self {
155        let mut filter = Filter::new();
156        let events = value.all_events();
157        if !events.is_empty() {
158            filter = filter.event_signature(events);
159        }
160        if !value.contract_addresses.is_empty() {
161            filter = filter.address(value.contract_addresses);
162        }
163        filter
164    }
165}
166
167impl From<&EventFilter> for Filter {
168    fn from(value: &EventFilter) -> Self {
169        let mut filter = Filter::new();
170        let events = value.all_events();
171        if !events.is_empty() {
172            filter = filter.event_signature(events);
173        }
174        if !value.contract_addresses.is_empty() {
175            filter = filter.address(value.contract_addresses.clone());
176        }
177        filter
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::EventFilter;
184    use alloy::{
185        primitives::{Address, address},
186        rpc::types::Topic,
187        sol,
188        sol_types::SolEvent,
189    };
190
191    sol! {
192        contract SomeContract {
193            event EventOne();
194            event EventTwo();
195        }
196    }
197
198    #[test]
199    fn display_default_no_address_no_events() {
200        let filter = EventFilter::new();
201        let got = format!("{filter}");
202        let expected = "EventFilter()";
203        assert_eq!(got, expected);
204
205        // Debug should equal Display
206        assert_eq!(format!("{filter:?}"), got);
207    }
208
209    #[test]
210    fn display_with_address() {
211        let address = address!("0x000000000000000000000000000000000000dEaD");
212        let filter = EventFilter::new().with_contract_address(address);
213        let got = format!("{filter}");
214        let expected = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD])";
215        assert_eq!(got, expected);
216
217        // verify batch address addition works the same
218        let filter = EventFilter::new().with_contract_addresses(vec![address]);
219        let got = format!("{filter}");
220        assert_eq!(got, expected);
221
222        // Debug should equal Display
223        assert_eq!(format!("{filter:?}"), got);
224    }
225
226    #[test]
227    fn display_with_multiple_addresses() {
228        let address_1 = address!("0x000000000000000000000000000000000000dEaD");
229        let address_2 = address!("0x0000000000000000000000000000000000000001");
230        let filter =
231            EventFilter::new().with_contract_address(address_1).with_contract_address(address_2);
232
233        let got = format!("{filter}");
234        let expected = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD, 0x0000000000000000000000000000000000000001])";
235        assert_eq!(got, expected);
236
237        // verify batch address addition works the same
238        let filter = EventFilter::new().with_contract_addresses(vec![address_1, address_2]);
239        let got = format!("{filter}");
240        assert_eq!(got, expected);
241
242        // Debug should equal Display
243        assert_eq!(format!("{filter:?}"), got);
244    }
245
246    #[test]
247    fn display_single_event() {
248        let event = SomeContract::EventOne::SIGNATURE;
249        let filter = EventFilter::new().with_event(event);
250        let got = format!("{filter}");
251        let expected = "EventFilter(events: [EventOne()])";
252        assert_eq!(got, expected);
253
254        // Debug should equal Display
255        assert_eq!(format!("{filter:?}"), got);
256    }
257
258    #[test]
259    fn display_single_event_signature() {
260        let event_signature = SomeContract::EventOne::SIGNATURE_HASH;
261        let filter = EventFilter::new().with_event_signature(event_signature);
262        let got = format!("{filter}");
263        let expected = "EventFilter(event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])";
264        assert_eq!(got, expected);
265
266        // Debug should equal Display
267        assert_eq!(format!("{filter:?}"), got);
268    }
269
270    #[test]
271    fn display_multiple_events_with_address() {
272        let address = address!("0x000000000000000000000000000000000000dEaD");
273        let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
274        let filter = EventFilter::new().with_contract_address(address).with_events(events.clone());
275
276        let got = format!("{filter}");
277        let expected = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD], events: [EventOne(), EventTwo()])";
278        assert_eq!(got, expected);
279
280        // Debug should equal Display
281        assert_eq!(format!("{filter:?}"), got);
282    }
283
284    #[test]
285    fn display_multiple_event_signatures_with_address() {
286        let address = address!("0x000000000000000000000000000000000000dEaD");
287        let event_signatures =
288            vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
289        let filter = EventFilter::new()
290            .with_contract_address(address)
291            .with_event_signatures(event_signatures.clone());
292
293        let got = format!("{filter}");
294        let expected_1 = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD], event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])";
295        let expected_2 = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD], event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])";
296        assert!(
297            got == expected_1 || got == expected_2,
298            "got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
299        );
300
301        // Debug should equal Display
302        assert_eq!(format!("{filter:?}"), got);
303    }
304
305    #[test]
306    fn display_multiple_events_and_event_signatures() {
307        let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
308        let event_signatures =
309            vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
310        let filter = EventFilter::new()
311            .with_events(events.clone())
312            .with_event_signatures(event_signatures.clone());
313
314        let got = format!("{filter}");
315        let expected_1 = "EventFilter(events: [EventOne(), EventTwo()], event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])";
316        let expected_2 = "EventFilter(events: [EventOne(), EventTwo()], event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])";
317        assert!(
318            got == expected_1 || got == expected_2,
319            "got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
320        );
321
322        // Debug should equal Display
323        assert_eq!(format!("{filter:?}"), got);
324    }
325
326    #[test]
327    fn display_multiple_events_and_event_signatures_with_addresses() {
328        let addresses = vec![
329            address!("0x000000000000000000000000000000000000dEaD"),
330            address!("0x0000000000000000000000000000000000000001"),
331        ];
332        let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
333        let event_signatures =
334            vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
335        let filter = EventFilter::new()
336            .with_contract_addresses(addresses)
337            .with_events(events.clone())
338            .with_event_signatures(event_signatures.clone());
339
340        let got = format!("{filter}");
341        let expected_1 = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD, 0x0000000000000000000000000000000000000001], events: [EventOne(), EventTwo()], event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])";
342        let expected_2 = "EventFilter(contracts: [0x000000000000000000000000000000000000dEaD, 0x0000000000000000000000000000000000000001], events: [EventOne(), EventTwo()], event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])";
343        assert!(
344            got == expected_1 || got == expected_2,
345            "got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
346        );
347
348        // Debug should equal Display
349        assert_eq!(format!("{filter:?}"), got);
350    }
351
352    #[test]
353    fn display_with_empty_address_vector_noop() {
354        // Providing an empty events vector should behave as if no events were set.
355        let filter = EventFilter::new().with_contract_addresses(Vec::<Address>::new());
356        let got = format!("{filter}");
357        let expected = "EventFilter()";
358        assert_eq!(got, expected);
359
360        // Debug should equal Display
361        assert_eq!(format!("{filter:?}"), got);
362    }
363
364    #[test]
365    fn display_with_empty_events_vector_noop() {
366        // Providing an empty events vector should behave as if no events were set.
367        let filter = EventFilter::new().with_events(Vec::<String>::new());
368        let got = format!("{filter}");
369        let expected = "EventFilter()";
370        assert_eq!(got, expected);
371
372        // Debug should equal Display
373        assert_eq!(format!("{filter:?}"), got);
374    }
375
376    #[test]
377    fn display_with_empty_event_signatures_vector_noop() {
378        // Providing an empty events vector should behave as if no events were set.
379        let filter = EventFilter::new().with_event_signatures(Vec::<Topic>::new());
380        let got = format!("{filter}");
381        let expected = "EventFilter()";
382        assert_eq!(got, expected);
383
384        // Debug should equal Display
385        assert_eq!(format!("{filter:?}"), got);
386    }
387}