1use display_json::DebugAsJson;
2
3use crate::eth::primitives::Address;
4use crate::eth::primitives::BlockNumber;
5use crate::eth::primitives::Log;
6use crate::eth::primitives::LogFilterInput;
7use crate::ext::not;
8
9#[derive(Clone, DebugAsJson, serde::Serialize, Eq, Hash, PartialEq)]
10#[cfg_attr(test, derive(serde::Deserialize, fake::Dummy))]
11#[cfg_attr(test, derive(Default))]
12pub struct LogFilter {
13 pub from_block: BlockNumber,
14 pub to_block: Option<BlockNumber>,
15 pub addresses: Vec<Address>,
16
17 #[cfg_attr(not(test), serde(skip))]
19 pub original_input: LogFilterInput,
20}
21
22impl LogFilter {
23 pub fn matches(&self, log: &Log, block_number: BlockNumber) -> bool {
25 if block_number < self.from_block {
27 return false;
28 }
29 if self.to_block.as_ref().is_some_and(|to_block| block_number > *to_block) {
30 return false;
31 }
32
33 let has_addresses = not(self.addresses.is_empty());
35 if has_addresses && not(self.addresses.contains(&log.address)) {
36 return false;
37 }
38
39 let filter_topics = &self.original_input.topics;
40 let log_topics = log.topics();
41
42 if filter_topics.is_empty() {
67 return true;
68 }
69
70 for (log_topic, filter_topic) in log_topics.into_iter().zip(filter_topics) {
71 if filter_topic.is_empty() {
73 continue; }
75 if filter_topic.contains(&None) {
77 continue; }
79 if !filter_topic.contains(&log_topic) {
81 return false; }
83 }
84
85 true
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use std::sync::Arc;
92
93 use itertools::Itertools;
94
95 use super::*;
96 use crate::eth::primitives::Log;
97 use crate::eth::primitives::LogFilterInputTopic;
98 use crate::eth::primitives::LogTopic;
99 use crate::eth::storage::StratusStorage;
100 use crate::utils::test_utils::fake_first;
101 use crate::utils::test_utils::fake_list;
102
103 fn build_filter(addresses: Vec<Address>, topics_nested: Vec<Vec<Option<LogTopic>>>) -> LogFilter {
104 let topics_map = |topics: Vec<Option<LogTopic>>| LogFilterInputTopic(topics.into_iter().collect());
105
106 let storage = StratusStorage::new_test().unwrap();
107
108 LogFilterInput {
109 address: addresses,
110 topics: topics_nested.into_iter().map(topics_map).collect(),
111 ..LogFilterInput::default()
112 }
113 .parse(&Arc::new(storage))
114 .unwrap()
115 }
116
117 fn log_with_topics(topics: [Option<LogTopic>; 4]) -> Log {
118 let log = fake_first::<Log>();
119 Log {
120 topic0: topics[0],
121 topic1: topics[1],
122 topic2: topics[2],
123 topic3: topics[3],
124 ..log
125 }
126 }
127
128 fn log_with_address(address: Address) -> Log {
129 let log = fake_first::<Log>();
130 Log { address, ..log }
131 }
132
133 #[test]
134 fn log_filtering_by_topic() {
135 let topics = fake_list::<LogTopic>(8).into_iter().map(Some).collect_vec();
136
137 let filter = build_filter(
138 vec![],
139 vec![
140 vec![topics[1], topics[2], topics[3]],
141 vec![None],
142 vec![topics[4], topics[5]],
143 vec![topics[6], topics[7]],
144 ],
145 );
146
147 assert!(filter.matches(&log_with_topics([topics[1], None, topics[4], topics[6]]), BlockNumber::ZERO));
148 assert!(filter.matches(&log_with_topics([topics[2], None, topics[4], topics[6]]), BlockNumber::ZERO));
149 assert!(filter.matches(&log_with_topics([topics[3], None, topics[4], topics[6]]), BlockNumber::ZERO));
150 assert!(filter.matches(&log_with_topics([topics[3], None, topics[5], topics[6]]), BlockNumber::ZERO));
151 assert!(filter.matches(&log_with_topics([topics[3], topics[0], topics[5], topics[7]]), BlockNumber::ZERO));
152 assert!(filter.matches(&log_with_topics([topics[1], topics[2], topics[4], topics[6]]), BlockNumber::ZERO));
153 assert!(filter.matches(&log_with_topics([topics[2], topics[4], topics[4], topics[7]]), BlockNumber::ZERO));
154 assert!(filter.matches(&log_with_topics([topics[2], topics[7], topics[5], topics[6]]), BlockNumber::ZERO));
155
156 assert!(not(filter.matches(&log_with_topics([None, None, None, None]), BlockNumber::ZERO)));
157 assert!(not(filter.matches(&log_with_topics([topics[0], None, None, None]), BlockNumber::ZERO)));
158 assert!(not(filter.matches(&log_with_topics([None, topics[0], None, None]), BlockNumber::ZERO)));
159 assert!(not(filter.matches(&log_with_topics([None, None, topics[0], None]), BlockNumber::ZERO)));
160 assert!(not(filter.matches(&log_with_topics([None, None, None, topics[0]]), BlockNumber::ZERO)));
161 assert!(not(
162 filter.matches(&log_with_topics([topics[2], topics[2], topics[4], topics[0]]), BlockNumber::ZERO)
163 ));
164 assert!(not(filter.matches(&log_with_topics([topics[3], None, topics[5], None]), BlockNumber::ZERO)));
165 assert!(not(filter.matches(&log_with_topics([topics[2], topics[4], None, topics[6]]), BlockNumber::ZERO)));
166 assert!(not(filter.matches(&log_with_topics([None, topics[0], topics[4], topics[6]]), BlockNumber::ZERO)));
167 assert!(not(filter.matches(&log_with_topics([topics[3], topics[0], topics[4], None]), BlockNumber::ZERO)));
168
169 let filter = build_filter(vec![], vec![vec![None], vec![topics[1], topics[2]]]);
170
171 assert!(filter.matches(&log_with_topics([topics[1], topics[1], topics[1], topics[1]]), BlockNumber::ZERO));
172 assert!(filter.matches(&log_with_topics([topics[1], topics[1], topics[1], None]), BlockNumber::ZERO));
173 assert!(filter.matches(&log_with_topics([topics[1], topics[1], None, topics[1]]), BlockNumber::ZERO));
174 assert!(filter.matches(&log_with_topics([None, topics[1], topics[1], topics[1]]), BlockNumber::ZERO));
175 assert!(filter.matches(&log_with_topics([topics[0], topics[1], None, topics[2]]), BlockNumber::ZERO));
176 assert!(filter.matches(&log_with_topics([None, topics[2], None, topics[1]]), BlockNumber::ZERO));
177
178 assert!(not(filter.matches(&log_with_topics([topics[1], None, topics[1], topics[1]]), BlockNumber::ZERO)));
179 assert!(not(filter.matches(&log_with_topics([topics[1], None, None, None]), BlockNumber::ZERO)));
180 assert!(not(filter.matches(&log_with_topics([topics[1], topics[3], None, None]), BlockNumber::ZERO)));
181 assert!(not(filter.matches(&log_with_topics([None, topics[3], None, None]), BlockNumber::ZERO)));
182 assert!(not(filter.matches(&log_with_topics([None, None, None, None]), BlockNumber::ZERO)));
183 }
184
185 #[test]
186 fn log_filtering_by_address() {
187 let addresses = fake_list::<Address>(4);
188
189 let filter = build_filter(vec![addresses[1], addresses[2]], vec![]);
190
191 assert!(filter.matches(&log_with_address(addresses[1]), BlockNumber::ZERO));
192 assert!(filter.matches(&log_with_address(addresses[2]), BlockNumber::ZERO));
193
194 assert!(not(filter.matches(&log_with_address(addresses[0]), BlockNumber::ZERO)));
195 assert!(not(filter.matches(&log_with_address(addresses[3]), BlockNumber::ZERO)));
196
197 let filter = build_filter(vec![], vec![]);
198
199 assert!(filter.matches(&log_with_address(addresses[0]), BlockNumber::ZERO));
200 assert!(filter.matches(&log_with_address(addresses[1]), BlockNumber::ZERO));
201 assert!(filter.matches(&log_with_address(addresses[2]), BlockNumber::ZERO));
202 assert!(filter.matches(&log_with_address(addresses[3]), BlockNumber::ZERO));
203 }
204}