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, ContractSpec, EventCondition, FunctionCondition, MatchConditions, Monitor,
7	ScriptLanguage, TransactionCondition, TransactionStatus, TriggerConditions,
8};
9
10/// Builder for creating test Monitor instances
11pub 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		// Verify final state
390		assert_eq!(monitor.name, "ComplexMonitor");
391		assert_eq!(monitor.networks, vec!["ethereum", "polygon"]);
392		assert!(monitor.paused);
393		assert_eq!(monitor.addresses.len(), 1); // address_with_abi overwrites previous addresses
394		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}