@@ -67,6 +67,120 @@ async fn send_request_body_400_status() {
6767 mock. assert_async ( ) . await ;
6868}
6969
70+ // This replaces live tests that probed a provider whose
71+ // block-range limits and future-`to_block` clamping vary by plan tier and
72+ // backend node.
73+ // see <https://www.alchemy.com/docs/chains/ethereum/ethereum-api-endpoints/eth-get-logs#supported-block-ranges>
74+ #[ tokio:: test]
75+ async fn get_logs_by_range_serializes_request_and_parses_response ( ) {
76+ use edr_eth:: { filter:: OneOrMore , BlockSpec } ;
77+ use edr_primitives:: Address ;
78+
79+ const MAX_BLOCK_NUMBER : u64 = u64:: MAX >> 1 ;
80+
81+ // Expected request parameters. EDR forwards `from_block`/`to_block` verbatim
82+ // and does no clamping of its own; a "future" `to_block` such as
83+ // `MAX_BLOCK_NUMBER` is left for the server to resolve.
84+ let from_block = 10496585_u64 ;
85+ let to_block = MAX_BLOCK_NUMBER ;
86+ let address = "0xffffffffffffffffffffffffffffffffffffffff" ;
87+
88+ // Expected fields of the single log in the response.
89+ let block_number = 0xa74fde_u64 ;
90+ let log_index = 0x653b_u64 ;
91+ let transaction_index = 0x1f_u64 ;
92+
93+ let mut server = mockito:: Server :: new_async ( ) . await ;
94+
95+ // `get_logs_by_range` runs through the caching layer, which fires
96+ // `eth_chainId` (to build the cache path) and may fire `eth_blockNumber` (to
97+ // decide whether the response is cacheable) around the actual `eth_getLogs`
98+ // request. Register these as optional responders (we never assert them, so
99+ // the test stays decoupled from the exact caching behavior) to keep those
100+ // requests from falling through to an unmatched-request error.
101+ let _chain_id_mock = server
102+ . mock ( "POST" , "/" )
103+ . match_body ( mockito:: Matcher :: PartialJson ( serde_json:: json!( {
104+ "method" : "eth_chainId"
105+ } ) ) )
106+ . with_status ( 200 )
107+ . with_header ( "content-type" , "application/json" )
108+ . with_body ( r#"{"jsonrpc": "2.0", "id": 0, "result": "0x1"}"# )
109+ . create_async ( )
110+ . await ;
111+
112+ let _block_number_mock = server
113+ . mock ( "POST" , "/" )
114+ . match_body ( mockito:: Matcher :: PartialJson ( serde_json:: json!( {
115+ "method" : "eth_blockNumber"
116+ } ) ) )
117+ . with_status ( 200 )
118+ . with_header ( "content-type" , "application/json" )
119+ . with_body ( r#"{"jsonrpc": "2.0", "id": 0, "result": "0xffffff"}"# )
120+ . create_async ( )
121+ . await ;
122+
123+ // The assertion that matters: EDR serializes the request with the expected
124+ // `eth_getLogs` parameters, including the future `to_block` as `0x7fff…`.
125+ let get_logs_mock = server
126+ . mock ( "POST" , "/" )
127+ . match_body ( mockito:: Matcher :: PartialJson ( serde_json:: json!( {
128+ "method" : "eth_getLogs" ,
129+ "params" : [ {
130+ "fromBlock" : format!( "{from_block:#x}" ) ,
131+ "toBlock" : format!( "{to_block:#x}" ) ,
132+ "address" : address,
133+ } ] ,
134+ } ) ) )
135+ . with_status ( 200 )
136+ . with_header ( "content-type" , "application/json" )
137+ . with_body (
138+ serde_json:: json!( {
139+ "jsonrpc" : "2.0" ,
140+ "id" : 0 ,
141+ "result" : [ {
142+ "address" : "0x0000000000000000000000000000000000000011" ,
143+ "topics" : [
144+ "0x000000000000000000000000000000000000000000000000000000000000dead" ,
145+ "0x000000000000000000000000000000000000000000000000000000000000beef"
146+ ] ,
147+ "data" : "0x0100ff" ,
148+ "transactionHash" : "0xc008e9f9bb92057dd0035496fbf4fb54f66b4b18b370928e46d6603933054d5a" ,
149+ "blockHash" : "0x88fadbb673928c61b9ede3694ae0589ac77ae38ec90a24a6e12e83f42f18c7e8" ,
150+ "blockNumber" : format!( "{block_number:#x}" ) ,
151+ "logIndex" : format!( "{log_index:#x}" ) ,
152+ "transactionIndex" : format!( "{transaction_index:#x}" ) ,
153+ "removed" : false ,
154+ } ] ,
155+ } )
156+ . to_string ( ) ,
157+ )
158+ . create_async ( )
159+ . await ;
160+
161+ let logs = TestRpcClient :: new ( & server. url ( ) )
162+ . get_logs_by_range (
163+ BlockSpec :: Number ( from_block) ,
164+ BlockSpec :: Number ( to_block) ,
165+ Some ( OneOrMore :: One (
166+ Address :: from_str ( address) . expect ( "failed to parse address" ) ,
167+ ) ) ,
168+ None ,
169+ )
170+ . await
171+ . expect ( "should have parsed the response" ) ;
172+
173+ // The request was serialized with the expected `eth_getLogs` parameters.
174+ get_logs_mock. assert_async ( ) . await ;
175+
176+ // The response was parsed into the expected log.
177+ assert_eq ! ( logs. len( ) , 1 ) ;
178+ assert_eq ! ( logs[ 0 ] . block_number, block_number) ;
179+ assert_eq ! ( logs[ 0 ] . log_index, log_index) ;
180+ assert_eq ! ( logs[ 0 ] . transaction_index, transaction_index) ;
181+ assert ! ( !logs[ 0 ] . removed) ;
182+ }
183+
70184#[ cfg( feature = "test-remote" ) ]
71185mod alchemy {
72186 use std:: { fs:: File , path:: PathBuf } ;
@@ -309,54 +423,6 @@ mod alchemy {
309423 // TODO: consider asserting something about the logs bloom
310424 }
311425
312- #[ tokio:: test]
313- async fn get_logs_future_from_block ( ) {
314- let provider_url = json_rpc_url_provider:: ethereum_mainnet ( ) ;
315- let result = TestRpcClient :: new ( & provider_url)
316- . get_logs_by_range (
317- BlockSpec :: Number ( MAX_BLOCK_NUMBER ) ,
318- BlockSpec :: Number ( MAX_BLOCK_NUMBER ) ,
319- Some ( OneOrMore :: One (
320- Address :: from_str ( "0xffffffffffffffffffffffffffffffffffffffff" )
321- . expect ( "failed to parse data" ) ,
322- ) ) ,
323- None ,
324- )
325- . await ;
326-
327- // TODO: https://github.com/NomicFoundation/edr/issues/903
328- // Alchemy enabled [EIP-4444](https://eips.ethereum.org/EIPS/eip-4444) for part of their clients. As a
329- // result, it's possible that we get the updated result `[]` instead of the old
330- // JSON-RPC error.
331- match result {
332- Ok ( response) => {
333- assert ! ( response. is_empty( ) ) ;
334- }
335- Err ( error) => {
336- assert ! ( matches!( error, RpcClientError :: JsonRpcError { .. } ) ) ;
337- }
338- }
339- }
340-
341- #[ tokio:: test]
342- async fn get_logs_future_to_block ( ) {
343- let provider_url = json_rpc_url_provider:: ethereum_mainnet ( ) ;
344- let logs = TestRpcClient :: new ( & provider_url)
345- . get_logs_by_range (
346- BlockSpec :: Number ( 10496585 ) ,
347- BlockSpec :: Number ( MAX_BLOCK_NUMBER ) ,
348- Some ( OneOrMore :: One (
349- Address :: from_str ( "0xffffffffffffffffffffffffffffffffffffffff" )
350- . expect ( "failed to parse data" ) ,
351- ) ) ,
352- None ,
353- )
354- . await
355- . expect ( "should have succeeded" ) ;
356-
357- assert_eq ! ( logs, [ ] ) ;
358- }
359-
360426 #[ tokio:: test]
361427 async fn get_transaction_by_hash_some ( ) {
362428 let provider_url = json_rpc_url_provider:: ethereum_mainnet ( ) ;
0 commit comments