Skip to content

Commit 3590bdc

Browse files
committed
chain/ethereum: add unit tests for EthereumLogFilter receipt merge
Cover all six insertion sites (from_data_sources arms, from_mapping, extend) and assert OR semantics across the three internal collections (contracts_and_events_graph, wildcard_events, events_with_topic_filters). Tests use two assertion helpers (assert_from_data_sources_or_merges and assert_extend_or_merges) parameterized over the match-arm or collection variant. Each helper runs both insertion orders so order-independence is verified per variant. The OR-semantics property test catches any flag-sequence regression.
1 parent 757ff94 commit 3590bdc

1 file changed

Lines changed: 266 additions & 1 deletion

File tree

chain/ethereum/src/adapter.rs

Lines changed: 266 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1849,7 +1849,7 @@ fn log_filter_require_transacion_receipt_method() {
18491849
.into_iter()
18501850
.collect();
18511851

1852-
let events_with_topic_filters = HashMap::new(); // TODO(krishna): Test events with topic filters
1852+
let events_with_topic_filters = HashMap::new();
18531853

18541854
let alien_event_signature = b256(8); // those will not be inserted in the graph
18551855
let alien_contract_address = address(9);
@@ -1982,3 +1982,268 @@ fn log_filter_require_transacion_receipt_method() {
19821982
&empty_vec
19831983
));
19841984
}
1985+
1986+
// Tests that `EthereumLogFilter` OR-merges per-handler `receipt` flags across
1987+
// every insertion site (`from_data_sources`, `from_mapping`, `extend`).
1988+
1989+
#[cfg(test)]
1990+
fn receipt_merge_test_addr(n: u64) -> Address {
1991+
Address::left_padding_from(&n.to_be_bytes())
1992+
}
1993+
1994+
#[cfg(test)]
1995+
fn receipt_merge_test_sig(n: u64) -> B256 {
1996+
B256::left_padding_from(&n.to_be_bytes())
1997+
}
1998+
1999+
#[cfg(test)]
2000+
fn receipt_merge_test_mock_abi() -> std::sync::Arc<graph::data_source::common::MappingABI> {
2001+
std::sync::Arc::new(graph::data_source::common::MappingABI {
2002+
name: "mock_abi".to_string(),
2003+
contract: abi::JsonAbi::new(),
2004+
})
2005+
}
2006+
2007+
#[cfg(test)]
2008+
fn receipt_merge_test_event_handler(
2009+
sig: B256,
2010+
topic1: Option<Vec<B256>>,
2011+
topic2: Option<Vec<B256>>,
2012+
topic3: Option<Vec<B256>>,
2013+
receipt: bool,
2014+
) -> crate::data_source::MappingEventHandler {
2015+
crate::data_source::MappingEventHandler {
2016+
event: "Event()".to_string(),
2017+
topic0: Some(sig),
2018+
topic1,
2019+
topic2,
2020+
topic3,
2021+
handler: "handleEvent".to_string(),
2022+
receipt,
2023+
calls: graph::data_source::common::CallDecls::default(),
2024+
}
2025+
}
2026+
2027+
#[cfg(test)]
2028+
fn receipt_merge_test_mapping(
2029+
handlers: Vec<crate::data_source::MappingEventHandler>,
2030+
) -> crate::Mapping {
2031+
crate::Mapping {
2032+
kind: "ethereum/events".to_string(),
2033+
api_version: semver::Version::new(0, 0, 7),
2034+
language: "wasm/assemblyscript".to_string(),
2035+
entities: vec![],
2036+
abis: vec![receipt_merge_test_mock_abi()],
2037+
block_handlers: vec![],
2038+
call_handlers: vec![],
2039+
event_handlers: handlers,
2040+
runtime: std::sync::Arc::new(vec![]),
2041+
link: graph::prelude::Link {
2042+
link: "test".to_string(),
2043+
},
2044+
}
2045+
}
2046+
2047+
#[cfg(test)]
2048+
fn receipt_merge_test_data_source(
2049+
address: Option<Address>,
2050+
handlers: Vec<crate::data_source::MappingEventHandler>,
2051+
) -> crate::data_source::DataSource {
2052+
crate::data_source::DataSource {
2053+
kind: "ethereum/contract".to_string(),
2054+
network: Some("test".to_string()),
2055+
name: "Test".to_string(),
2056+
manifest_idx: 0,
2057+
address,
2058+
start_block: 0,
2059+
end_block: None,
2060+
mapping: receipt_merge_test_mapping(handlers),
2061+
context: std::sync::Arc::new(None),
2062+
creation_block: None,
2063+
contract_abi: receipt_merge_test_mock_abi(),
2064+
}
2065+
}
2066+
2067+
/// Run two data sources with `receipt: true` and `receipt: false` at the
2068+
/// same effective filter key through `from_data_sources` and assert the
2069+
/// merged filter still requires a transaction receipt. Runs both
2070+
/// declaration orders so order-independence is verified per variant.
2071+
#[cfg(test)]
2072+
fn assert_from_data_sources_or_merges(
2073+
label: &str,
2074+
address: Option<Address>,
2075+
topic1: Option<Vec<B256>>,
2076+
log_topics: &[B256],
2077+
) {
2078+
let event_sig = receipt_merge_test_sig(100);
2079+
let ds_yes = receipt_merge_test_data_source(
2080+
address,
2081+
vec![receipt_merge_test_event_handler(
2082+
event_sig,
2083+
topic1.clone(),
2084+
None,
2085+
None,
2086+
true,
2087+
)],
2088+
);
2089+
let ds_no = receipt_merge_test_data_source(
2090+
address,
2091+
vec![receipt_merge_test_event_handler(
2092+
event_sig,
2093+
topic1.clone(),
2094+
None,
2095+
None,
2096+
false,
2097+
)],
2098+
);
2099+
2100+
for (order, dss) in [("yes,no", [&ds_yes, &ds_no]), ("no,yes", [&ds_no, &ds_yes])] {
2101+
let filter = EthereumLogFilter::from_data_sources(dss);
2102+
assert!(
2103+
filter.requires_transaction_receipt(&event_sig, address.as_ref(), log_topics),
2104+
"{label} ({order}): receipt:true must survive a later receipt:false",
2105+
);
2106+
}
2107+
}
2108+
2109+
/// Build two filters via `from_data_sources`, each with one handler at the
2110+
/// same effective key but with opposite receipt flags, then merge them via
2111+
/// `extend` and assert the merged filter still requires a transaction
2112+
/// receipt. Runs both extend directions so order-independence is verified
2113+
/// per variant.
2114+
#[cfg(test)]
2115+
fn assert_extend_or_merges(
2116+
label: &str,
2117+
address: Option<Address>,
2118+
topic1: Option<Vec<B256>>,
2119+
log_topics: &[B256],
2120+
) {
2121+
let event_sig = receipt_merge_test_sig(105);
2122+
let ds_yes = receipt_merge_test_data_source(
2123+
address,
2124+
vec![receipt_merge_test_event_handler(
2125+
event_sig,
2126+
topic1.clone(),
2127+
None,
2128+
None,
2129+
true,
2130+
)],
2131+
);
2132+
let ds_no = receipt_merge_test_data_source(
2133+
address,
2134+
vec![receipt_merge_test_event_handler(
2135+
event_sig,
2136+
topic1.clone(),
2137+
None,
2138+
None,
2139+
false,
2140+
)],
2141+
);
2142+
2143+
for (order, base, ext) in [
2144+
("yes.extend(no)", &ds_yes, &ds_no),
2145+
("no.extend(yes)", &ds_no, &ds_yes),
2146+
] {
2147+
let mut filter = EthereumLogFilter::from_data_sources([base]);
2148+
filter.extend(EthereumLogFilter::from_data_sources([ext]));
2149+
assert!(
2150+
filter.requires_transaction_receipt(&event_sig, address.as_ref(), log_topics),
2151+
"{label} ({order}): extend must OR-merge",
2152+
);
2153+
}
2154+
}
2155+
2156+
#[test]
2157+
fn from_data_sources_or_merges_at_every_insertion_site() {
2158+
let contract = receipt_merge_test_addr(1);
2159+
let event_sig = receipt_merge_test_sig(100);
2160+
let topic = receipt_merge_test_sig(200);
2161+
let with_topic = vec![event_sig, topic];
2162+
2163+
// Each case maps to one arm of the `match ds.address` in `from_data_sources`.
2164+
assert_from_data_sources_or_merges("graph edge", Some(contract), None, &[]);
2165+
assert_from_data_sources_or_merges(
2166+
"addressed topics",
2167+
Some(contract),
2168+
Some(vec![topic]),
2169+
&with_topic,
2170+
);
2171+
assert_from_data_sources_or_merges("wildcard", None, None, &[]);
2172+
assert_from_data_sources_or_merges("wildcard topics", None, Some(vec![topic]), &with_topic);
2173+
}
2174+
2175+
#[test]
2176+
fn from_mapping_or_merges_via_extend() {
2177+
// Two templates handling the same event signature with different receipt
2178+
// flags merged via `from_mapping` + `extend`.
2179+
let event_sig = receipt_merge_test_sig(104);
2180+
2181+
let mapping_yes = receipt_merge_test_mapping(vec![receipt_merge_test_event_handler(
2182+
event_sig, None, None, None, true,
2183+
)]);
2184+
let mapping_no = receipt_merge_test_mapping(vec![receipt_merge_test_event_handler(
2185+
event_sig, None, None, None, false,
2186+
)]);
2187+
2188+
let mut filter = EthereumLogFilter::from_mapping(&mapping_yes);
2189+
filter.extend(EthereumLogFilter::from_mapping(&mapping_no));
2190+
2191+
assert!(filter.requires_transaction_receipt(&event_sig, None, &[]));
2192+
}
2193+
2194+
#[test]
2195+
fn extend_or_merges_at_every_collection() {
2196+
let contract = receipt_merge_test_addr(5);
2197+
let event_sig = receipt_merge_test_sig(105);
2198+
let topic = receipt_merge_test_sig(203);
2199+
let with_topic = vec![event_sig, topic];
2200+
2201+
assert_extend_or_merges("graph edge", Some(contract), None, &[]);
2202+
assert_extend_or_merges(
2203+
"addressed topics",
2204+
Some(contract),
2205+
Some(vec![topic]),
2206+
&with_topic,
2207+
);
2208+
assert_extend_or_merges("wildcard", None, None, &[]);
2209+
assert_extend_or_merges("wildcard topics", None, Some(vec![topic]), &with_topic);
2210+
}
2211+
2212+
#[test]
2213+
fn requires_transaction_receipt_has_or_semantics_across_handlers() {
2214+
// `requires_transaction_receipt` must return true iff at least one handler
2215+
// at the key declared `receipt: true`, regardless of declaration order.
2216+
let event_sig = receipt_merge_test_sig(108);
2217+
2218+
for flags in [
2219+
vec![false],
2220+
vec![true],
2221+
vec![true, false],
2222+
vec![false, true],
2223+
vec![false, false, true],
2224+
vec![true, false, false],
2225+
vec![false, true, false],
2226+
vec![true, true, false],
2227+
vec![false, false, false],
2228+
] {
2229+
let dss: Vec<_> = flags
2230+
.iter()
2231+
.map(|&r| {
2232+
receipt_merge_test_data_source(
2233+
None,
2234+
vec![receipt_merge_test_event_handler(
2235+
event_sig, None, None, None, r,
2236+
)],
2237+
)
2238+
})
2239+
.collect();
2240+
let filter = EthereumLogFilter::from_data_sources(dss.iter());
2241+
2242+
let any_requires_receipt = flags.iter().any(|&r| r);
2243+
assert_eq!(
2244+
filter.requires_transaction_receipt(&event_sig, None, &[]),
2245+
any_requires_receipt,
2246+
"flag sequence {flags:?}",
2247+
);
2248+
}
2249+
}

0 commit comments

Comments
 (0)