@@ -20,6 +20,7 @@ use tiny_keccak::keccak256;
20
20
use web3:: types:: { Address , Log , H256 } ;
21
21
22
22
use graph:: prelude:: * ;
23
+ use graph:: prelude:: { alloy_address_to_h160, b256_to_h256} ;
23
24
use graph:: {
24
25
blockchain as bc,
25
26
components:: metrics:: { CounterVec , GaugeVec , HistogramVec } ,
@@ -92,6 +93,67 @@ impl EventSignatureWithTopics {
92
93
}
93
94
}
94
95
96
+ /// `EventSignatureWithTopics` is used to match events with
97
+ /// indexed arguments when they are defined in the subgraph
98
+ /// manifest.
99
+ #[ derive( Clone , Debug , PartialEq , Eq , Hash ) ]
100
+ pub struct EventSignatureWithTopicsForAlloy {
101
+ pub address : Option < alloy:: primitives:: Address > ,
102
+ pub signature : alloy:: primitives:: B256 ,
103
+ pub topic1 : Option < Vec < alloy:: primitives:: B256 > > ,
104
+ pub topic2 : Option < Vec < alloy:: primitives:: B256 > > ,
105
+ pub topic3 : Option < Vec < alloy:: primitives:: B256 > > ,
106
+ }
107
+
108
+ impl EventSignatureWithTopicsForAlloy {
109
+ #[ allow( dead_code) ]
110
+ pub fn new (
111
+ address : Option < alloy:: primitives:: Address > ,
112
+ signature : alloy:: primitives:: B256 ,
113
+ topic1 : Option < Vec < alloy:: primitives:: B256 > > ,
114
+ topic2 : Option < Vec < alloy:: primitives:: B256 > > ,
115
+ topic3 : Option < Vec < alloy:: primitives:: B256 > > ,
116
+ ) -> Self {
117
+ EventSignatureWithTopicsForAlloy {
118
+ address,
119
+ signature,
120
+ topic1,
121
+ topic2,
122
+ topic3,
123
+ }
124
+ }
125
+
126
+ /// Checks if an event matches the `EventSignatureWithTopics`
127
+ /// If self.address is None, it's considered a wildcard match.
128
+ /// Otherwise, it must match the provided address.
129
+ /// It must also match the topics if they are Some
130
+ #[ allow( dead_code) ]
131
+ pub fn matches (
132
+ & self ,
133
+ address : Option < & alloy:: primitives:: Address > ,
134
+ sig : alloy:: primitives:: B256 ,
135
+ topics : & Vec < alloy:: primitives:: B256 > ,
136
+ ) -> bool {
137
+ // If self.address is None, it's considered a wildcard match. Otherwise, it must match the provided address.
138
+ let address_matches = match self . address {
139
+ Some ( ref self_addr) => address == Some ( self_addr) ,
140
+ None => true , // self.address is None, so it matches any address.
141
+ } ;
142
+
143
+ address_matches
144
+ && self . signature == sig
145
+ && self . topic1 . as_ref ( ) . map_or ( true , |t1| {
146
+ topics. get ( 1 ) . map_or ( false , |topic| t1. contains ( topic) )
147
+ } )
148
+ && self . topic2 . as_ref ( ) . map_or ( true , |t2| {
149
+ topics. get ( 2 ) . map_or ( false , |topic| t2. contains ( topic) )
150
+ } )
151
+ && self . topic3 . as_ref ( ) . map_or ( true , |t3| {
152
+ topics. get ( 3 ) . map_or ( false , |topic| t3. contains ( topic) )
153
+ } )
154
+ }
155
+ }
156
+
95
157
#[ derive( Error , Debug ) ]
96
158
pub enum EthereumRpcError {
97
159
#[ error( "call error: {0}" ) ]
@@ -147,15 +209,36 @@ enum LogFilterNode {
147
209
/// Corresponds to an `eth_getLogs` call.
148
210
#[ derive( Clone , Debug ) ]
149
211
pub struct EthGetLogsFilter {
150
- pub contracts : Vec < Address > ,
151
- pub event_signatures : Vec < EventSignature > ,
152
- pub topic1 : Option < Vec < EventSignature > > ,
153
- pub topic2 : Option < Vec < EventSignature > > ,
154
- pub topic3 : Option < Vec < EventSignature > > ,
212
+ pub contracts : Vec < alloy :: primitives :: Address > ,
213
+ pub event_signatures : Vec < alloy :: primitives :: B256 > ,
214
+ pub topic1 : Option < Vec < alloy :: primitives :: B256 > > ,
215
+ pub topic2 : Option < Vec < alloy :: primitives :: B256 > > ,
216
+ pub topic3 : Option < Vec < alloy :: primitives :: B256 > > ,
155
217
}
156
218
157
219
impl EthGetLogsFilter {
158
- fn from_contract ( address : Address ) -> Self {
220
+ /// Convert to alloy Filter for the given block range
221
+ pub fn to_alloy_filter ( & self , from : BlockNumber , to : BlockNumber ) -> alloy:: rpc:: types:: Filter {
222
+ let mut filter_builder = alloy:: rpc:: types:: Filter :: new ( )
223
+ . from_block ( alloy:: rpc:: types:: BlockNumberOrTag :: Number ( from as u64 ) )
224
+ . to_block ( alloy:: rpc:: types:: BlockNumberOrTag :: Number ( to as u64 ) )
225
+ . address ( self . contracts . clone ( ) )
226
+ . event_signature ( self . event_signatures . clone ( ) ) ;
227
+
228
+ if let Some ( ref topic1) = self . topic1 {
229
+ filter_builder = filter_builder. topic1 ( topic1. clone ( ) ) ;
230
+ }
231
+ if let Some ( ref topic2) = self . topic2 {
232
+ filter_builder = filter_builder. topic2 ( topic2. clone ( ) ) ;
233
+ }
234
+ if let Some ( ref topic3) = self . topic3 {
235
+ filter_builder = filter_builder. topic3 ( topic3. clone ( ) ) ;
236
+ }
237
+
238
+ filter_builder
239
+ }
240
+
241
+ fn from_contract ( address : alloy:: primitives:: Address ) -> Self {
159
242
EthGetLogsFilter {
160
243
contracts : vec ! [ address] ,
161
244
event_signatures : vec ! [ ] ,
@@ -165,7 +248,7 @@ impl EthGetLogsFilter {
165
248
}
166
249
}
167
250
168
- fn from_event ( event : EventSignature ) -> Self {
251
+ fn from_event ( event : alloy :: primitives :: B256 ) -> Self {
169
252
EthGetLogsFilter {
170
253
contracts : vec ! [ ] ,
171
254
event_signatures : vec ! [ event] ,
@@ -175,7 +258,7 @@ impl EthGetLogsFilter {
175
258
}
176
259
}
177
260
178
- fn from_event_with_topics ( event : EventSignatureWithTopics ) -> Self {
261
+ fn from_event_with_topics ( event : EventSignatureWithTopicsForAlloy ) -> Self {
179
262
EthGetLogsFilter {
180
263
contracts : event. address . map_or ( vec ! [ ] , |a| vec ! [ a] ) ,
181
264
event_signatures : vec ! [ event. signature] ,
@@ -205,7 +288,7 @@ impl fmt::Display for EthGetLogsFilter {
205
288
} ;
206
289
207
290
// Helper to format topics as strings
208
- let format_topics = |topics : & Option < Vec < EventSignature > > | -> String {
291
+ let format_topics = |topics : & Option < Vec < alloy :: primitives :: B256 > > | -> String {
209
292
topics. as_ref ( ) . map_or_else (
210
293
|| "None" . to_string ( ) ,
211
294
|ts| {
@@ -351,11 +434,11 @@ impl From<EthereumLogFilter> for Vec<LogFilter> {
351
434
} | LogFilter {
352
435
addresses : contracts
353
436
. iter ( )
354
- . map ( |addr| addr. to_fixed_bytes ( ) . to_vec ( ) )
437
+ . map ( |addr| addr. to_vec ( ) )
355
438
. collect_vec ( ) ,
356
439
event_signatures : event_signatures
357
440
. iter ( )
358
- . map ( |sig| sig. to_fixed_bytes ( ) . to_vec ( ) )
441
+ . map ( |sig| sig. to_vec ( ) )
359
442
. collect_vec ( ) ,
360
443
} ,
361
444
)
@@ -431,6 +514,55 @@ impl EthereumLogFilter {
431
514
false
432
515
}
433
516
517
+ /// Similar to [`matches`], checks if a transaction receipt is required for this log filter.
518
+ pub fn requires_transaction_receipt_alloy (
519
+ & self ,
520
+ event_signature : & alloy:: primitives:: B256 ,
521
+ contract_address : Option < & alloy:: primitives:: Address > ,
522
+ topics : & [ alloy:: primitives:: B256 ] ,
523
+ ) -> bool {
524
+ // Check for wildcard events first.
525
+ if self . wildcard_events . get ( & b256_to_h256 ( * event_signature) ) == Some ( & true ) {
526
+ return true ;
527
+ }
528
+
529
+ // Next, check events with topic filters.
530
+ if self
531
+ . events_with_topic_filters
532
+ . iter ( )
533
+ . any ( |( event_with_topics, & requires_receipt) | {
534
+ requires_receipt
535
+ && event_with_topics. matches (
536
+ contract_address
537
+ . map ( |addr| alloy_address_to_h160 ( * addr) )
538
+ . as_ref ( ) ,
539
+ b256_to_h256 ( * event_signature) ,
540
+ & topics. iter ( ) . map ( |t| b256_to_h256 ( * t) ) . collect ( ) ,
541
+ )
542
+ } )
543
+ {
544
+ return true ;
545
+ }
546
+
547
+ // Finally, check the contracts_and_events_graph if a contract address is specified.
548
+ if let Some ( address) = contract_address {
549
+ let contract_node = LogFilterNode :: Contract ( alloy_address_to_h160 ( * address) ) ;
550
+ let event_node = LogFilterNode :: Event ( b256_to_h256 ( * event_signature) ) ;
551
+
552
+ // Directly iterate over all edges and return true if a matching edge that requires a receipt is found.
553
+ for ( s, t, & r) in self . contracts_and_events_graph . all_edges ( ) {
554
+ if r && ( ( s == contract_node && t == event_node)
555
+ || ( t == contract_node && s == event_node) )
556
+ {
557
+ return true ;
558
+ }
559
+ }
560
+ }
561
+
562
+ // If none of the conditions above match, return false.
563
+ false
564
+ }
565
+
434
566
pub fn from_data_sources < ' a > ( iter : impl IntoIterator < Item = & ' a DataSource > ) -> Self {
435
567
let mut this = EthereumLogFilter :: default ( ) ;
436
568
for ds in iter {
@@ -530,18 +662,33 @@ impl EthereumLogFilter {
530
662
let mut filters = Vec :: new ( ) ;
531
663
532
664
// Start with the wildcard event filters.
533
- filters. extend (
534
- self . wildcard_events
535
- . into_keys ( )
536
- . map ( EthGetLogsFilter :: from_event) ,
537
- ) ;
665
+ filters. extend ( self . wildcard_events . into_keys ( ) . map ( |h256_event| {
666
+ let alloy_event = alloy:: primitives:: B256 :: from_slice ( h256_event. as_bytes ( ) ) ;
667
+ EthGetLogsFilter :: from_event ( alloy_event)
668
+ } ) ) ;
538
669
539
670
// Handle events with topic filters.
540
671
filters. extend (
541
672
self . events_with_topic_filters
542
673
. into_iter ( )
543
674
. map ( |( event_with_topics, _) | {
544
- EthGetLogsFilter :: from_event_with_topics ( event_with_topics)
675
+ // Convert EventSignatureWithTopics to EventSignatureWithTopicsForAlloy
676
+ let alloy_event = EventSignatureWithTopicsForAlloy {
677
+ address : event_with_topics
678
+ . address
679
+ . map ( |addr| h160_to_alloy_address ( addr) ) ,
680
+ signature : h256_to_b256 ( event_with_topics. signature ) ,
681
+ topic1 : event_with_topics
682
+ . topic1
683
+ . map ( |topics| topics. into_iter ( ) . map ( |t| h256_to_b256 ( t) ) . collect ( ) ) ,
684
+ topic2 : event_with_topics
685
+ . topic2
686
+ . map ( |topics| topics. into_iter ( ) . map ( |t| h256_to_b256 ( t) ) . collect ( ) ) ,
687
+ topic3 : event_with_topics
688
+ . topic3
689
+ . map ( |topics| topics. into_iter ( ) . map ( |t| h256_to_b256 ( t) ) . collect ( ) ) ,
690
+ } ;
691
+ EthGetLogsFilter :: from_event_with_topics ( alloy_event)
545
692
} ) ,
546
693
) ;
547
694
@@ -572,8 +719,14 @@ impl EthereumLogFilter {
572
719
// If there are edges, there are vertexes.
573
720
let max_vertex = g. nodes ( ) . max_by_key ( |& n| g. neighbors ( n) . count ( ) ) . unwrap ( ) ;
574
721
let mut filter = match max_vertex {
575
- LogFilterNode :: Contract ( address) => EthGetLogsFilter :: from_contract ( address) ,
576
- LogFilterNode :: Event ( event_sig) => EthGetLogsFilter :: from_event ( event_sig) ,
722
+ LogFilterNode :: Contract ( address) => {
723
+ let alloy_address = alloy:: primitives:: Address :: from_slice ( address. as_bytes ( ) ) ;
724
+ EthGetLogsFilter :: from_contract ( alloy_address)
725
+ }
726
+ LogFilterNode :: Event ( event_sig) => {
727
+ let alloy_event = alloy:: primitives:: B256 :: from_slice ( event_sig. as_bytes ( ) ) ;
728
+ EthGetLogsFilter :: from_event ( alloy_event)
729
+ }
577
730
} ;
578
731
for neighbor in g. neighbors ( max_vertex) {
579
732
match neighbor {
@@ -584,9 +737,14 @@ impl EthereumLogFilter {
584
737
push_filter ( filter) ;
585
738
filter = EthGetLogsFilter :: from_event ( event) ;
586
739
}
587
- filter. contracts . push ( address) ;
740
+ let alloy_address =
741
+ alloy:: primitives:: Address :: from_slice ( address. as_bytes ( ) ) ;
742
+ filter. contracts . push ( alloy_address) ;
743
+ }
744
+ LogFilterNode :: Event ( event_sig) => {
745
+ let alloy_event = alloy:: primitives:: B256 :: from_slice ( event_sig. as_bytes ( ) ) ;
746
+ filter. event_signatures . push ( alloy_event) ;
588
747
}
589
- LogFilterNode :: Event ( event_sig) => filter. event_signatures . push ( event_sig) ,
590
748
}
591
749
}
592
750
@@ -1775,18 +1933,22 @@ fn complete_log_filter() {
1775
1933
1776
1934
// Test a few combinations of complete graphs.
1777
1935
for i in [ 1 , 2 ] {
1778
- let events: BTreeSet < _ > = ( 0 ..i) . map ( H256 :: from_low_u64_le) . collect ( ) ;
1936
+ let events: BTreeSet < _ > = ( 0 ..i)
1937
+ . map ( |n| alloy:: primitives:: B256 :: from ( [ n as u8 ; 32 ] ) )
1938
+ . collect ( ) ;
1779
1939
1780
1940
for j in [ 1 , 1000 , 2000 , 3000 ] {
1781
- let contracts: BTreeSet < _ > = ( 0 ..j) . map ( Address :: from_low_u64_le) . collect ( ) ;
1941
+ let contracts: BTreeSet < _ > = ( 0 ..j)
1942
+ . map ( |n| alloy:: primitives:: Address :: from ( [ n as u8 ; 20 ] ) )
1943
+ . collect ( ) ;
1782
1944
1783
1945
// Construct the complete bipartite graph with i events and j contracts.
1784
1946
let mut contracts_and_events_graph = GraphMap :: new ( ) ;
1785
1947
for & contract in & contracts {
1786
1948
for & event in & events {
1787
1949
contracts_and_events_graph. add_edge (
1788
- LogFilterNode :: Contract ( contract) ,
1789
- LogFilterNode :: Event ( event) ,
1950
+ LogFilterNode :: Contract ( alloy_address_to_h160 ( contract) ) ,
1951
+ LogFilterNode :: Event ( b256_to_h256 ( event) ) ,
1790
1952
false ,
1791
1953
) ;
1792
1954
}
0 commit comments