|
4 | 4 | // - Ethereum's reference: https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_newfilter |
5 | 5 | use crate::{ |
6 | 6 | rpc::{RpcApiContext, RpcHandler}, |
7 | | - types::{block_identifier::BlockIdentifier, receipt::RpcLog}, |
| 7 | + types::{ |
| 8 | + block_identifier::{BlockIdentifier, BlockTag}, |
| 9 | + receipt::RpcLog, |
| 10 | + }, |
8 | 11 | utils::RpcErr, |
9 | 12 | }; |
10 | 13 | use ethrex_common::{H160, H256}; |
11 | 14 | use ethrex_storage::Store; |
12 | 15 | use serde::Deserialize; |
13 | 16 | use serde_json::Value; |
14 | 17 | use std::collections::HashSet; |
| 18 | + |
15 | 19 | #[derive(Deserialize, Debug, Clone)] |
16 | 20 | #[serde(untagged)] |
17 | 21 | pub enum AddressFilter { |
18 | 22 | Single(H160), |
19 | 23 | Many(Vec<H160>), |
20 | 24 | } |
21 | 25 |
|
22 | | -#[derive(Deserialize, Debug, Clone)] |
| 26 | +impl AsRef<[H160]> for AddressFilter { |
| 27 | + fn as_ref(&self) -> &[H160] { |
| 28 | + match self { |
| 29 | + AddressFilter::Single(address) => std::slice::from_ref(address), |
| 30 | + AddressFilter::Many(addresses) => addresses.as_ref(), |
| 31 | + } |
| 32 | + } |
| 33 | +} |
| 34 | + |
| 35 | +#[derive(Deserialize, Debug, Clone, PartialEq, Eq)] |
23 | 36 | #[serde(untagged)] |
24 | 37 | pub enum TopicFilter { |
25 | 38 | Topic(Option<H256>), |
@@ -49,21 +62,24 @@ impl RpcHandler for LogsFilter { |
49 | 62 | .ok_or(RpcErr::BadParams("Param is not a object".to_owned()))?; |
50 | 63 | let from_block = param |
51 | 64 | .get("fromBlock") |
52 | | - .ok_or_else(|| RpcErr::MissingParam("fromBlock".to_string())) |
53 | | - .and_then(|block_number| BlockIdentifier::parse(block_number.clone(), 0))?; |
| 65 | + .map(|block_number| BlockIdentifier::parse(block_number.clone(), 0)) |
| 66 | + .transpose()? |
| 67 | + .unwrap_or(BlockIdentifier::Tag(BlockTag::Latest)); |
54 | 68 | let to_block = param |
55 | 69 | .get("toBlock") |
56 | | - .ok_or_else(|| RpcErr::MissingParam("toBlock".to_string())) |
57 | | - .and_then(|block_number| BlockIdentifier::parse(block_number.clone(), 0))?; |
| 70 | + .map(|block_number| BlockIdentifier::parse(block_number.clone(), 0)) |
| 71 | + .transpose()? |
| 72 | + .unwrap_or(BlockIdentifier::Tag(BlockTag::Latest)); |
58 | 73 | let address_filters = param |
59 | 74 | .get("address") |
60 | | - .ok_or_else(|| RpcErr::MissingParam("address".to_string())) |
61 | | - .and_then(|address| { |
| 75 | + .map(|address| { |
62 | 76 | match serde_json::from_value::<Option<AddressFilter>>(address.clone()) { |
63 | 77 | Ok(filters) => Ok(filters), |
64 | 78 | _ => Err(RpcErr::WrongParam("address".to_string())), |
65 | 79 | } |
66 | | - })?; |
| 80 | + }) |
| 81 | + .transpose()? |
| 82 | + .flatten(); |
67 | 83 | let topics_filters = param |
68 | 84 | .get("topics") |
69 | 85 | .ok_or_else(|| RpcErr::MissingParam("topics".to_string())) |
@@ -214,3 +230,54 @@ pub(crate) async fn fetch_logs_with_filter( |
214 | 230 |
|
215 | 231 | Ok(filtered_logs) |
216 | 232 | } |
| 233 | + |
| 234 | +#[cfg(test)] |
| 235 | +mod tests { |
| 236 | + use super::*; |
| 237 | + use serde_json::json; |
| 238 | + |
| 239 | + #[test] |
| 240 | + fn test_get_logs_with_defaults() { |
| 241 | + let params = Some(vec![ |
| 242 | + json!({"topics": ["0x0000000000000000000000000000000000000000000000000000000000000000"]}), |
| 243 | + ]); |
| 244 | + let request = LogsFilter::parse(¶ms).unwrap(); |
| 245 | + |
| 246 | + assert!(request.address_filters.is_none(), "{request:?}"); |
| 247 | + assert!( |
| 248 | + matches!(request.from_block, BlockIdentifier::Tag(BlockTag::Latest)), |
| 249 | + "{request:?}" |
| 250 | + ); |
| 251 | + assert!( |
| 252 | + matches!(request.to_block, BlockIdentifier::Tag(BlockTag::Latest)), |
| 253 | + "{request:?}" |
| 254 | + ); |
| 255 | + assert_eq!(request.topics, vec![TopicFilter::Topic(Some(H256::zero()))]); |
| 256 | + } |
| 257 | + |
| 258 | + #[test] |
| 259 | + fn test_get_logs_multiple_addresses() { |
| 260 | + let params = Some(vec![json!({ |
| 261 | + "address": [ |
| 262 | + "0x0000000000000000000000000000000000000001", |
| 263 | + "0x0000000000000000000000000000000000000002" |
| 264 | + ], |
| 265 | + "topics": ["0x0000000000000000000000000000000000000000000000000000000000000000"] |
| 266 | + })]); |
| 267 | + let request = LogsFilter::parse(¶ms).unwrap(); |
| 268 | + |
| 269 | + assert_eq!( |
| 270 | + request.address_filters.as_ref().unwrap().as_ref(), |
| 271 | + [H160::from_low_u64_be(1), H160::from_low_u64_be(2)], |
| 272 | + ); |
| 273 | + assert!( |
| 274 | + matches!(request.from_block, BlockIdentifier::Tag(BlockTag::Latest)), |
| 275 | + "{request:?}" |
| 276 | + ); |
| 277 | + assert!( |
| 278 | + matches!(request.to_block, BlockIdentifier::Tag(BlockTag::Latest)), |
| 279 | + "{request:?}" |
| 280 | + ); |
| 281 | + assert_eq!(request.topics, vec![TopicFilter::Topic(Some(H256::zero()))]); |
| 282 | + } |
| 283 | +} |
0 commit comments