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