@@ -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