openzeppelin_monitor/utils/tests/builders/evm/
monitor.rs

1//! Test helper utilities for the EVM Monitor
2//!
3//! - `MonitorBuilder`: Builder for creating test Monitor instances
4
5use crate::models::{
6	AddressWithSpec, ChainConfiguration, ContractSpec, EVMMonitorConfig, EventCondition,
7	FunctionCondition, MatchConditions, Monitor, ScriptLanguage, TransactionCondition,
8	TransactionStatus, TriggerConditions,
9};
10
11/// Builder for creating test Monitor instances
12pub 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		// Verify final state
397		assert_eq!(monitor.name, "ComplexMonitor");
398		assert_eq!(monitor.networks, vec!["ethereum", "polygon"]);
399		assert!(monitor.paused);
400		assert_eq!(monitor.addresses.len(), 1); // address_with_abi overwrites previous addresses
401		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}