Skip to content

Commit 0728e7c

Browse files
committed
checkpoint
1 parent e295cfd commit 0728e7c

File tree

2 files changed

+227
-59
lines changed

2 files changed

+227
-59
lines changed

chain/ethereum/src/adapter.rs

Lines changed: 187 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ use tiny_keccak::keccak256;
2020
use web3::types::{Address, Log, H256};
2121

2222
use graph::prelude::*;
23+
use graph::prelude::{alloy_address_to_h160, b256_to_h256};
2324
use graph::{
2425
blockchain as bc,
2526
components::metrics::{CounterVec, GaugeVec, HistogramVec},
@@ -92,6 +93,67 @@ impl EventSignatureWithTopics {
9293
}
9394
}
9495

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+
95157
#[derive(Error, Debug)]
96158
pub enum EthereumRpcError {
97159
#[error("call error: {0}")]
@@ -147,15 +209,36 @@ enum LogFilterNode {
147209
/// Corresponds to an `eth_getLogs` call.
148210
#[derive(Clone, Debug)]
149211
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>>,
155217
}
156218

157219
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 {
159242
EthGetLogsFilter {
160243
contracts: vec![address],
161244
event_signatures: vec![],
@@ -165,7 +248,7 @@ impl EthGetLogsFilter {
165248
}
166249
}
167250

168-
fn from_event(event: EventSignature) -> Self {
251+
fn from_event(event: alloy::primitives::B256) -> Self {
169252
EthGetLogsFilter {
170253
contracts: vec![],
171254
event_signatures: vec![event],
@@ -175,7 +258,7 @@ impl EthGetLogsFilter {
175258
}
176259
}
177260

178-
fn from_event_with_topics(event: EventSignatureWithTopics) -> Self {
261+
fn from_event_with_topics(event: EventSignatureWithTopicsForAlloy) -> Self {
179262
EthGetLogsFilter {
180263
contracts: event.address.map_or(vec![], |a| vec![a]),
181264
event_signatures: vec![event.signature],
@@ -205,7 +288,7 @@ impl fmt::Display for EthGetLogsFilter {
205288
};
206289

207290
// 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 {
209292
topics.as_ref().map_or_else(
210293
|| "None".to_string(),
211294
|ts| {
@@ -351,11 +434,11 @@ impl From<EthereumLogFilter> for Vec<LogFilter> {
351434
}| LogFilter {
352435
addresses: contracts
353436
.iter()
354-
.map(|addr| addr.to_fixed_bytes().to_vec())
437+
.map(|addr| addr.to_vec())
355438
.collect_vec(),
356439
event_signatures: event_signatures
357440
.iter()
358-
.map(|sig| sig.to_fixed_bytes().to_vec())
441+
.map(|sig| sig.to_vec())
359442
.collect_vec(),
360443
},
361444
)
@@ -431,6 +514,55 @@ impl EthereumLogFilter {
431514
false
432515
}
433516

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+
434566
pub fn from_data_sources<'a>(iter: impl IntoIterator<Item = &'a DataSource>) -> Self {
435567
let mut this = EthereumLogFilter::default();
436568
for ds in iter {
@@ -530,18 +662,33 @@ impl EthereumLogFilter {
530662
let mut filters = Vec::new();
531663

532664
// 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+
}));
538669

539670
// Handle events with topic filters.
540671
filters.extend(
541672
self.events_with_topic_filters
542673
.into_iter()
543674
.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)
545692
}),
546693
);
547694

@@ -572,8 +719,14 @@ impl EthereumLogFilter {
572719
// If there are edges, there are vertexes.
573720
let max_vertex = g.nodes().max_by_key(|&n| g.neighbors(n).count()).unwrap();
574721
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+
}
577730
};
578731
for neighbor in g.neighbors(max_vertex) {
579732
match neighbor {
@@ -584,9 +737,14 @@ impl EthereumLogFilter {
584737
push_filter(filter);
585738
filter = EthGetLogsFilter::from_event(event);
586739
}
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);
588747
}
589-
LogFilterNode::Event(event_sig) => filter.event_signatures.push(event_sig),
590748
}
591749
}
592750

@@ -1775,18 +1933,22 @@ fn complete_log_filter() {
17751933

17761934
// Test a few combinations of complete graphs.
17771935
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();
17791939

17801940
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();
17821944

17831945
// Construct the complete bipartite graph with i events and j contracts.
17841946
let mut contracts_and_events_graph = GraphMap::new();
17851947
for &contract in &contracts {
17861948
for &event in &events {
17871949
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)),
17901952
false,
17911953
);
17921954
}

0 commit comments

Comments
 (0)