Skip to content

Commit 6b000b9

Browse files
committed
feat: support event sig hashes in filter
1 parent 0f58447 commit 6b000b9

File tree

2 files changed

+195
-60
lines changed

2 files changed

+195
-60
lines changed

src/event_filter.rs

Lines changed: 184 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use std::fmt::{Debug, Display};
22

3-
use alloy::primitives::Address;
3+
use alloy::{
4+
primitives::{Address, keccak256},
5+
rpc::types::{Topic, ValueOrArray},
6+
};
47

58
/// Type representing filters to apply when fetching events from the chain.
69
///
@@ -23,19 +26,34 @@ use alloy::primitives::Address;
2326
pub struct EventFilter {
2427
/// Contract address to filter events from. If None, events from all contracts will be tracked.
2528
pub(crate) contract_address: Option<Address>,
26-
/// Human-readable event signature, e.g. "Transfer(address,address,uint256)".
27-
/// If None, all events from the specified contract(s) will be tracked.
29+
/// Human-readable event signatures, e.g. "Transfer(address,address,uint256)".
2830
pub(crate) events: Vec<String>,
31+
/// Event signature hashes, e.g. `0x123...`.
32+
pub(crate) event_signatures: Topic,
2933
}
3034

3135
impl Display for EventFilter {
3236
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
33-
let address =
34-
self.contract_address.map_or("all contracts".to_string(), |addr| format!("{addr:?}"));
35-
let events =
36-
if self.events.is_empty() { "all events".to_string() } else { self.events.join(", ") };
37+
let mut content = vec![];
38+
if let Some(address) = &self.contract_address {
39+
content.push(format!("contract: {address}"));
40+
}
41+
if !self.events.is_empty() {
42+
content.push(format!("events: [{}]", self.events.join(", ")));
43+
}
44+
if !self.event_signatures.is_empty() {
45+
// No guarantee the order of values returned by `Topic`
46+
let value_or_array = self.event_signatures.to_value_or_array().unwrap();
47+
let event_signatures = match value_or_array {
48+
ValueOrArray::Value(value) => format!("{value}"),
49+
ValueOrArray::Array(arr) => {
50+
arr.iter().map(|t| format!("{t}")).collect::<Vec<_>>().join(", ")
51+
}
52+
};
53+
content.push(format!("event_signatures: [{event_signatures}]"));
54+
}
3755

38-
write!(f, "EventFilter(contract: {address}, events: {events})")
56+
write!(f, "EventFilter({})", content.join(", "))
3957
}
4058
}
4159

@@ -61,106 +79,220 @@ impl EventFilter {
6179
}
6280

6381
/// Sets the event signature to filter specific events.
64-
/// If not set, all events from the specified contract(s) will be tracked.
82+
/// If neither events nor event signature hashes are set, all events from the specified contract(s) will be tracked.
6583
#[must_use]
6684
pub fn with_event(mut self, event: impl Into<String>) -> Self {
67-
self.events.push(event.into());
85+
let event = event.into();
86+
if !event.is_empty() {
87+
self.events.push(event);
88+
}
6889
self
6990
}
7091

71-
/// Sets the event signature to filter specific events.
72-
/// If not set, all events from the specified contract(s) will be tracked.
92+
/// Sets the event signatures to filter specific events.
93+
/// If neither events nor event signature hashes are set, all events from the specified contract(s) will be tracked.
94+
#[must_use]
95+
pub fn with_events(mut self, events: impl IntoIterator<Item = impl Into<String>>) -> Self {
96+
self.events.extend(events.into_iter().map(Into::into));
97+
self
98+
}
99+
100+
/// Sets the event signature hash to filter specific events.
101+
/// If neither event signature hashes nor events are set, all events from the specified contract(s) will be tracked.
73102
#[must_use]
74-
pub fn with_events(mut self, events: impl IntoIterator<Item = String>) -> Self {
75-
self.events.extend(events);
103+
pub fn with_event_signature(mut self, event_signature: impl Into<Topic>) -> Self {
104+
self.event_signatures = self.event_signatures.extend(event_signature.into());
76105
self
77106
}
107+
108+
/// Sets the event signature hashes to filter specific events.
109+
/// If neither event signature hashes nor events are set, all events from the specified contract(s) will be tracked.
110+
#[must_use]
111+
pub fn with_event_signatures(
112+
mut self,
113+
event_signatures: impl IntoIterator<Item = impl Into<Topic>>,
114+
) -> Self {
115+
for event_signature in event_signatures {
116+
self.event_signatures = self.event_signatures.extend(event_signature);
117+
}
118+
self
119+
}
120+
121+
/// Returns a [`Topic`] containing all event signature hashes.
122+
/// If neither events nor event signature hashes are set, an empty [`Topic`] is returned.
123+
#[must_use]
124+
pub fn all_events(&self) -> Topic {
125+
let events = self.events.iter().map(|e| keccak256(e.as_bytes())).collect::<Vec<_>>();
126+
let sigs = self.event_signatures.clone();
127+
let sigs = sigs.extend(events);
128+
sigs
129+
}
78130
}
79131

80132
#[cfg(test)]
81133
mod tests {
82134
use super::EventFilter;
83-
use alloy::primitives::{Address, address};
135+
use alloy::{primitives::address, sol, sol_types::SolEvent};
84136

85-
fn addr_str(addr: Address) -> String {
86-
format!("{:?}", addr)
137+
sol! {
138+
contract SomeContract {
139+
event EventOne();
140+
event EventTwo();
141+
}
87142
}
88143

89144
#[test]
90145
fn display_default_no_address_no_events() {
91146
let filter = EventFilter::new();
92-
let got = format!("{}", filter);
93-
let expected = "EventFilter(contract: all contracts, events: all events)";
147+
let got = format!("{filter}");
148+
let expected = "EventFilter()";
94149
assert_eq!(got, expected);
95150

96151
// Debug should equal Display
97-
assert_eq!(format!("{:?}", filter), got);
152+
assert_eq!(format!("{filter:?}"), got);
98153
}
99154

100155
#[test]
101-
fn display_with_address_no_events() {
102-
let address = address!("0xd8dA6BF26964af9d7eed9e03e53415d37aa96045");
156+
fn display_with_address() {
157+
let address = address!("0x000000000000000000000000000000000000dEaD");
103158
let filter = EventFilter::new().with_contract_address(address);
104-
let got = format!("{}", filter);
105-
let expected = format!("EventFilter(contract: {}, events: all events)", addr_str(address));
159+
let got = format!("{filter}");
160+
let expected = format!("EventFilter(contract: 0x000000000000000000000000000000000000dEaD)");
106161
assert_eq!(got, expected);
107162

108163
// Debug should equal Display
109-
assert_eq!(format!("{:?}", filter), got);
164+
assert_eq!(format!("{filter:?}"), got);
110165
}
111166

112167
#[test]
113-
fn display_single_event_no_address() {
114-
let event = "Transfer(address,address,uint256)";
168+
fn display_single_event() {
169+
let event = SomeContract::EventOne::SIGNATURE;
115170
let filter = EventFilter::new().with_event(event);
116-
let got = format!("{}", filter);
117-
let expected = format!("EventFilter(contract: all contracts, events: {})", event);
171+
let got = format!("{filter}");
172+
let expected = format!("EventFilter(events: [EventOne()])");
173+
assert_eq!(got, expected);
174+
175+
// Debug should equal Display
176+
assert_eq!(format!("{filter:?}"), got);
177+
}
178+
179+
#[test]
180+
fn display_single_event_signature() {
181+
let event_signature = SomeContract::EventOne::SIGNATURE_HASH;
182+
let filter = EventFilter::new().with_event_signature(event_signature);
183+
let got = format!("{filter}");
184+
let expected = format!(
185+
"EventFilter(event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])"
186+
);
118187
assert_eq!(got, expected);
119188

120189
// Debug should equal Display
121-
assert_eq!(format!("{:?}", filter), got);
190+
assert_eq!(format!("{filter:?}"), got);
122191
}
123192

124193
#[test]
125194
fn display_multiple_events_with_address() {
126195
let address = address!("0x000000000000000000000000000000000000dEaD");
127-
let events = vec![
128-
"Transfer(address,address,uint256)".to_string(),
129-
"Approval(address,address,uint256)".to_string(),
130-
"Sync(uint112,uint112)".to_string(),
131-
];
196+
let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
132197
let filter = EventFilter::new().with_contract_address(address).with_events(events.clone());
133198

134-
let got = format!("{}", filter);
135-
let expected =
136-
format!("EventFilter(contract: {}, events: {})", addr_str(address), events.join(", "));
199+
let got = format!("{filter}");
200+
let expected = format!(
201+
"EventFilter(contract: 0x000000000000000000000000000000000000dEaD, events: [EventOne(), EventTwo()])"
202+
);
137203
assert_eq!(got, expected);
138204

139205
// Debug should equal Display
140-
assert_eq!(format!("{:?}", filter), got);
206+
assert_eq!(format!("{filter:?}"), got);
141207
}
142208

143209
#[test]
144-
fn display_with_empty_events_vector_noop() {
145-
// Providing an empty events vector should behave as if no events were set.
146-
let filter = EventFilter::new().with_events(Vec::<String>::new());
147-
let got = format!("{}", filter);
148-
let expected = "EventFilter(contract: all contracts, events: all events)";
149-
assert_eq!(got, expected);
210+
fn display_multiple_event_signatures_with_address() {
211+
let address = address!("0x000000000000000000000000000000000000dEaD");
212+
let event_signatures =
213+
vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
214+
let filter = EventFilter::new()
215+
.with_contract_address(address)
216+
.with_event_signatures(event_signatures.clone());
217+
218+
let got = format!("{filter}");
219+
// `Topic` doesn't guarantee the order of values it returns
220+
let expected_1 = format!(
221+
"EventFilter(contract: 0x000000000000000000000000000000000000dEaD, event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])"
222+
);
223+
let expected_2 = format!(
224+
"EventFilter(contract: 0x000000000000000000000000000000000000dEaD, event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])"
225+
);
226+
assert!(
227+
got == expected_1 || got == expected_2,
228+
"got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
229+
);
230+
231+
// Debug should equal Display
232+
assert_eq!(format!("{filter:?}"), got);
233+
}
234+
235+
#[test]
236+
fn display_multiple_events_and_event_signatures() {
237+
let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
238+
let event_signatures =
239+
vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
240+
let filter = EventFilter::new()
241+
.with_events(events.clone())
242+
.with_event_signatures(event_signatures.clone());
243+
244+
let got = format!("{filter}");
245+
let expected_1 = format!(
246+
"EventFilter(events: [EventOne(), EventTwo()], event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])",
247+
);
248+
let expected_2 = format!(
249+
"EventFilter(events: [EventOne(), EventTwo()], event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])",
250+
);
251+
assert!(
252+
got == expected_1 || got == expected_2,
253+
"got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
254+
);
150255

151256
// Debug should equal Display
152-
assert_eq!(format!("{:?}", filter), got);
257+
assert_eq!(format!("{filter:?}"), got);
153258
}
154259

155260
#[test]
156-
fn display_with_empty_event_string_prints_empty() {
157-
// An explicitly empty event string results in an empty events field when joined.
158-
let filter = EventFilter::new().with_event("");
159-
let got = format!("{}", filter);
160-
let expected = "EventFilter(contract: all contracts, events: )";
261+
fn display_multiple_events_and_event_signatures_with_address() {
262+
let address = address!("0x000000000000000000000000000000000000dEaD");
263+
let events = vec![SomeContract::EventOne::SIGNATURE, SomeContract::EventTwo::SIGNATURE];
264+
let event_signatures =
265+
vec![SomeContract::EventOne::SIGNATURE_HASH, SomeContract::EventTwo::SIGNATURE_HASH];
266+
let filter = EventFilter::new()
267+
.with_contract_address(address)
268+
.with_events(events.clone())
269+
.with_event_signatures(event_signatures.clone());
270+
271+
let got = format!("{filter}");
272+
let expected_1 = format!(
273+
"EventFilter(contract: 0x000000000000000000000000000000000000dEaD, events: [EventOne(), EventTwo()], event_signatures: [0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff, 0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91])",
274+
);
275+
let expected_2 = format!(
276+
"EventFilter(contract: 0x000000000000000000000000000000000000dEaD, events: [EventOne(), EventTwo()], event_signatures: [0xa08dd6fd0d644da5df33d075cb9256203802f6948ab81b87079960711810dc91, 0x16eb4fc7651e068f1c31303645026f82d5fced11a8d5209bbf272072be23ddff])",
277+
);
278+
assert!(
279+
got == expected_1 || got == expected_2,
280+
"got: {got},\nexpected_1: {expected_1},\nexpected_2: {expected_2}"
281+
);
282+
283+
// Debug should equal Display
284+
assert_eq!(format!("{filter:?}"), got);
285+
}
286+
287+
#[test]
288+
fn display_with_empty_events_vector_noop() {
289+
// Providing an empty events vector should behave as if no events were set.
290+
let filter = EventFilter::new().with_events(Vec::<String>::new());
291+
let got = format!("{filter}");
292+
let expected = "EventFilter()";
161293
assert_eq!(got, expected);
162294

163295
// Debug should equal Display
164-
assert_eq!(format!("{:?}", filter), got);
296+
assert_eq!(format!("{filter:?}"), got);
165297
}
166298
}

src/event_scanner.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,20 +168,23 @@ impl<N: Network> ConnectedEventScanner<N> {
168168
let sender = listener.sender.clone();
169169
let mut sub = range_tx.subscribe();
170170

171+
let mut log_filter = Filter::new();
172+
if let Some(contract_address) = filter.contract_address {
173+
log_filter = log_filter.address(contract_address);
174+
}
175+
let events = filter.all_events();
176+
if !events.is_empty() {
177+
log_filter = log_filter.event_signature(events);
178+
}
179+
171180
tokio::spawn(async move {
172181
loop {
173182
match sub.recv().await {
174183
Ok(Ok(range)) => {
175184
let (from_block, to_block) = (*range.start(), *range.end());
176185

177-
let mut log_filter = Filter::new()
178-
.from_block(from_block)
179-
.to_block(to_block)
180-
.events(filter.events.clone());
181-
182-
if let Some(contract_address) = filter.contract_address {
183-
log_filter = log_filter.address(contract_address);
184-
}
186+
let log_filter =
187+
log_filter.clone().from_block(from_block).to_block(to_block);
185188

186189
match provider.get_logs(&log_filter).await {
187190
Ok(logs) => {

0 commit comments

Comments
 (0)