1use crate::models::{
6 AddressWithSpec, ContractSpec, EventCondition, FunctionCondition, MatchConditions, Monitor,
7 ScriptLanguage, TransactionCondition, TransactionStatus, TriggerConditions,
8};
9
10pub struct MonitorBuilder {
12 name: String,
13 networks: Vec<String>,
14 paused: bool,
15 addresses: Vec<AddressWithSpec>,
16 match_conditions: MatchConditions,
17 trigger_conditions: Vec<TriggerConditions>,
18 triggers: Vec<String>,
19}
20
21impl Default for MonitorBuilder {
22 fn default() -> Self {
23 Self {
24 name: "TestMonitor".to_string(),
25 networks: vec!["ethereum_mainnet".to_string()],
26 paused: false,
27 addresses: vec![AddressWithSpec {
28 address: "0x0000000000000000000000000000000000000000".to_string(),
29 contract_spec: None,
30 }],
31 match_conditions: MatchConditions {
32 functions: vec![],
33 events: vec![],
34 transactions: vec![],
35 },
36 trigger_conditions: vec![],
37 triggers: vec![],
38 }
39 }
40}
41
42impl MonitorBuilder {
43 pub fn new() -> Self {
44 Self::default()
45 }
46
47 pub fn name(mut self, name: &str) -> Self {
48 self.name = name.to_string();
49 self
50 }
51
52 pub fn networks(mut self, networks: Vec<String>) -> Self {
53 self.networks = networks;
54 self
55 }
56
57 pub fn paused(mut self, paused: bool) -> Self {
58 self.paused = paused;
59 self
60 }
61
62 pub fn address(mut self, address: &str) -> Self {
63 self.addresses = vec![AddressWithSpec {
64 address: address.to_string(),
65 contract_spec: None,
66 }];
67 self
68 }
69
70 pub fn addresses(mut self, addresses: Vec<String>) -> Self {
71 self.addresses = addresses
72 .into_iter()
73 .map(|addr| AddressWithSpec {
74 address: addr,
75 contract_spec: None,
76 })
77 .collect();
78 self
79 }
80
81 pub fn add_address(mut self, address: &str) -> Self {
82 self.addresses.push(AddressWithSpec {
83 address: address.to_string(),
84 contract_spec: None,
85 });
86 self
87 }
88
89 pub fn address_with_spec(mut self, address: &str, spec: Option<ContractSpec>) -> Self {
90 self.addresses = vec![AddressWithSpec {
91 address: address.to_string(),
92 contract_spec: spec,
93 }];
94 self
95 }
96
97 pub fn addresses_with_spec(mut self, addresses: Vec<(String, Option<ContractSpec>)>) -> Self {
98 self.addresses = addresses
99 .into_iter()
100 .map(|(addr, spec)| AddressWithSpec {
101 address: addr.to_string(),
102 contract_spec: spec,
103 })
104 .collect();
105 self
106 }
107
108 pub fn function(mut self, signature: &str, expression: Option<String>) -> Self {
109 self.match_conditions.functions.push(FunctionCondition {
110 signature: signature.to_string(),
111 expression,
112 });
113 self
114 }
115
116 pub fn event(mut self, signature: &str, expression: Option<String>) -> Self {
117 self.match_conditions.events.push(EventCondition {
118 signature: signature.to_string(),
119 expression,
120 });
121 self
122 }
123
124 pub fn transaction(mut self, status: TransactionStatus, expression: Option<String>) -> Self {
125 self.match_conditions
126 .transactions
127 .push(TransactionCondition { status, expression });
128 self
129 }
130
131 pub fn trigger_condition(
132 mut self,
133 script_path: &str,
134 timeout_ms: u32,
135 language: ScriptLanguage,
136 arguments: Option<Vec<String>>,
137 ) -> Self {
138 self.trigger_conditions.push(TriggerConditions {
139 script_path: script_path.to_string(),
140 timeout_ms,
141 arguments,
142 language,
143 });
144 self
145 }
146
147 pub fn triggers(mut self, triggers: Vec<String>) -> Self {
148 self.triggers = triggers;
149 self
150 }
151
152 pub fn match_conditions(mut self, match_conditions: MatchConditions) -> Self {
153 self.match_conditions = match_conditions;
154 self
155 }
156
157 pub fn build(self) -> Monitor {
158 Monitor {
159 name: self.name,
160 networks: self.networks,
161 paused: self.paused,
162 addresses: self.addresses,
163 match_conditions: self.match_conditions,
164 trigger_conditions: self.trigger_conditions,
165 triggers: self.triggers,
166 }
167 }
168}
169
170#[cfg(test)]
171mod tests {
172 use crate::models::EVMContractSpec;
173
174 use super::*;
175 use serde_json::json;
176
177 #[test]
178 fn test_default_monitor() {
179 let monitor = MonitorBuilder::new().build();
180
181 assert_eq!(monitor.name, "TestMonitor");
182 assert_eq!(monitor.networks, vec!["ethereum_mainnet"]);
183 assert!(!monitor.paused);
184 assert_eq!(monitor.addresses.len(), 1);
185 assert_eq!(
186 monitor.addresses[0].address,
187 "0x0000000000000000000000000000000000000000"
188 );
189 assert!(monitor.addresses[0].contract_spec.is_none());
190 assert!(monitor.match_conditions.functions.is_empty());
191 assert!(monitor.match_conditions.events.is_empty());
192 assert!(monitor.match_conditions.transactions.is_empty());
193 assert!(monitor.trigger_conditions.is_empty());
194 assert!(monitor.triggers.is_empty());
195 }
196
197 #[test]
198 fn test_basic_builder_methods() {
199 let monitor = MonitorBuilder::new()
200 .name("MyMonitor")
201 .networks(vec!["polygon".to_string()])
202 .paused(true)
203 .address("0x123")
204 .build();
205
206 assert_eq!(monitor.name, "MyMonitor");
207 assert_eq!(monitor.networks, vec!["polygon"]);
208 assert!(monitor.paused);
209 assert_eq!(monitor.addresses.len(), 1);
210 assert_eq!(monitor.addresses[0].address, "0x123");
211 }
212
213 #[test]
214 fn test_address_methods() {
215 let monitor = MonitorBuilder::new()
216 .addresses(vec!["0x123".to_string(), "0x456".to_string()])
217 .add_address("0x789")
218 .build();
219
220 assert_eq!(monitor.addresses.len(), 3);
221 assert_eq!(monitor.addresses[0].address, "0x123");
222 assert_eq!(monitor.addresses[1].address, "0x456");
223 assert_eq!(monitor.addresses[2].address, "0x789");
224 }
225
226 #[test]
227 fn test_address_with_abi() {
228 let abi = json!({"some": "abi"});
229 let monitor = MonitorBuilder::new()
230 .address_with_spec(
231 "0x123",
232 Some(ContractSpec::EVM(EVMContractSpec::from(abi.clone()))),
233 )
234 .build();
235
236 assert_eq!(monitor.addresses.len(), 1);
237 assert_eq!(monitor.addresses[0].address, "0x123");
238 assert_eq!(
239 monitor.addresses[0].contract_spec,
240 Some(ContractSpec::EVM(EVMContractSpec::from(abi)))
241 );
242 }
243
244 #[test]
245 fn test_addresses_with_abi() {
246 let abi1 = json!({"contract_spec": "1"});
247 let abi2 = json!({"contract_spec": "2"});
248 let monitor = MonitorBuilder::new()
249 .addresses_with_spec(vec![
250 (
251 "0x123".to_string(),
252 Some(ContractSpec::EVM(EVMContractSpec::from(abi1.clone()))),
253 ),
254 ("0x456".to_string(), None),
255 (
256 "0x789".to_string(),
257 Some(ContractSpec::EVM(EVMContractSpec::from(abi2.clone()))),
258 ),
259 ])
260 .build();
261
262 assert_eq!(monitor.addresses.len(), 3);
263 assert_eq!(monitor.addresses[0].address, "0x123");
264 assert_eq!(
265 monitor.addresses[0].contract_spec,
266 Some(ContractSpec::EVM(EVMContractSpec::from(abi1)))
267 );
268 assert_eq!(monitor.addresses[1].address, "0x456");
269 assert_eq!(monitor.addresses[1].contract_spec, None);
270 assert_eq!(monitor.addresses[2].address, "0x789");
271 assert_eq!(
272 monitor.addresses[2].contract_spec,
273 Some(ContractSpec::EVM(EVMContractSpec::from(abi2)))
274 );
275 }
276
277 #[test]
278 fn test_match_conditions() {
279 let monitor = MonitorBuilder::new()
280 .function("transfer(address,uint256)", Some("value >= 0".to_string()))
281 .event("Transfer(address,address,uint256)", None)
282 .transaction(TransactionStatus::Success, None)
283 .build();
284
285 assert_eq!(monitor.match_conditions.functions.len(), 1);
286 assert_eq!(
287 monitor.match_conditions.functions[0].signature,
288 "transfer(address,uint256)"
289 );
290 assert_eq!(
291 monitor.match_conditions.functions[0].expression,
292 Some("value >= 0".to_string())
293 );
294 assert_eq!(monitor.match_conditions.events.len(), 1);
295 assert_eq!(
296 monitor.match_conditions.events[0].signature,
297 "Transfer(address,address,uint256)"
298 );
299 assert_eq!(monitor.match_conditions.transactions.len(), 1);
300 assert_eq!(
301 monitor.match_conditions.transactions[0].status,
302 TransactionStatus::Success
303 );
304 }
305
306 #[test]
307 fn test_match_condition() {
308 let monitor = MonitorBuilder::new()
309 .match_conditions(MatchConditions {
310 functions: vec![FunctionCondition {
311 signature: "transfer(address,uint256)".to_string(),
312 expression: None,
313 }],
314 events: vec![],
315 transactions: vec![],
316 })
317 .build();
318 assert_eq!(monitor.match_conditions.functions.len(), 1);
319 assert_eq!(
320 monitor.match_conditions.functions[0].signature,
321 "transfer(address,uint256)"
322 );
323 assert!(monitor.match_conditions.events.is_empty());
324 assert!(monitor.match_conditions.transactions.is_empty());
325 }
326
327 #[test]
328 fn test_trigger_conditions() {
329 let monitor = MonitorBuilder::new()
330 .trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
331 .trigger_condition(
332 "script.js",
333 2000,
334 ScriptLanguage::JavaScript,
335 Some(vec!["-verbose".to_string()]),
336 )
337 .build();
338
339 assert_eq!(monitor.trigger_conditions.len(), 2);
340 assert_eq!(monitor.trigger_conditions[0].script_path, "script.py");
341 assert_eq!(monitor.trigger_conditions[0].timeout_ms, 1000);
342 assert_eq!(
343 monitor.trigger_conditions[0].language,
344 ScriptLanguage::Python
345 );
346 assert_eq!(monitor.trigger_conditions[1].script_path, "script.js");
347 assert_eq!(monitor.trigger_conditions[1].timeout_ms, 2000);
348 assert_eq!(
349 monitor.trigger_conditions[1].language,
350 ScriptLanguage::JavaScript
351 );
352 assert_eq!(
353 monitor.trigger_conditions[1].arguments,
354 Some(vec!["-verbose".to_string()])
355 );
356 }
357
358 #[test]
359 fn test_triggers() {
360 let monitor = MonitorBuilder::new()
361 .triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
362 .build();
363
364 assert_eq!(monitor.triggers.len(), 2);
365 assert_eq!(monitor.triggers[0], "trigger1");
366 assert_eq!(monitor.triggers[1], "trigger2");
367 }
368
369 #[test]
370 fn test_complex_monitor_build() {
371 let abi = json!({"some": "abi"});
372 let monitor = MonitorBuilder::new()
373 .name("ComplexMonitor")
374 .networks(vec!["ethereum".to_string(), "polygon".to_string()])
375 .paused(true)
376 .addresses(vec!["0x123".to_string(), "0x456".to_string()])
377 .add_address("0x789")
378 .address_with_spec(
379 "0xabc",
380 Some(ContractSpec::EVM(EVMContractSpec::from(abi.clone()))),
381 )
382 .function("transfer(address,uint256)", Some("value >= 0".to_string()))
383 .event("Transfer(address,address,uint256)", None)
384 .transaction(TransactionStatus::Success, None)
385 .trigger_condition("script.py", 1000, ScriptLanguage::Python, None)
386 .triggers(vec!["trigger1".to_string(), "trigger2".to_string()])
387 .build();
388
389 assert_eq!(monitor.name, "ComplexMonitor");
391 assert_eq!(monitor.networks, vec!["ethereum", "polygon"]);
392 assert!(monitor.paused);
393 assert_eq!(monitor.addresses.len(), 1); assert_eq!(monitor.addresses[0].address, "0xabc");
395 assert_eq!(
396 monitor.addresses[0].contract_spec,
397 Some(ContractSpec::EVM(EVMContractSpec::from(abi)))
398 );
399 assert_eq!(monitor.match_conditions.functions.len(), 1);
400 assert_eq!(
401 monitor.match_conditions.functions[0].expression,
402 Some("value >= 0".to_string())
403 );
404 assert_eq!(monitor.match_conditions.events.len(), 1);
405 assert_eq!(monitor.match_conditions.transactions.len(), 1);
406 assert_eq!(monitor.trigger_conditions.len(), 1);
407 assert_eq!(monitor.triggers.len(), 2);
408 }
409}