openzeppelin_monitor/services/filter/
filter_match.rs

1//! Match handling and processing logic.
2//!
3//! This module implements the processing of matched transactions and events:
4//! - Converts blockchain data to trigger-friendly format
5//! - Prepares notification payloads by converting blockchain-specific data into a generic format
6//! - Handles match execution through configured triggers
7//! - Manages the transformation of complex blockchain data into template variables
8
9use std::collections::HashMap;
10
11use alloy::primitives::Address;
12use serde_json::{json, Value as JsonValue};
13
14use crate::{
15	models::{MonitorMatch, ScriptLanguage},
16	services::{
17		filter::{
18			evm_helpers::{b256_to_string, h160_to_string},
19			FilterError,
20		},
21		trigger::TriggerExecutionServiceTrait,
22	},
23};
24
25/// Process a monitor match by executing associated triggers.
26///
27/// Takes a matched monitor event and processes it through the appropriate trigger service.
28/// Converts blockchain-specific data into a standardized format that can be used in trigger
29/// templates.
30///
31/// # Arguments
32/// * `matching_monitor` - The matched monitor event containing transaction and trigger information
33/// * `trigger_service` - Service responsible for executing triggers
34/// * `trigger_scripts` - Scripts to be executed for each trigger
35///
36/// # Returns
37/// Result indicating success or failure of trigger execution
38///
39/// # Example
40/// The function converts blockchain data into template variables like:
41/// ```text
42/// "monitor.name": "Transfer USDT Token"
43/// "transaction.hash": "0x99139c8f64b9b939678e261e1553660b502d9fd01c2ab1516e699ee6c8cc5791"
44/// "transaction.from": "0xf401346fd255e034a2e43151efe1d68c1e0f8ca5"
45/// "transaction.to": "0x0000000000001ff3684f28c67538d4d072c22734"
46/// "transaction.value": "24504000000000000"
47/// "events.0.signature": "Transfer(address,address,uint256)"
48/// "events.0.args.to": "0x70bf6634ee8cb27d04478f184b9b8bb13e5f4710"
49/// "events.0.args.from": "0x2e8135be71230c6b1b4045696d41c09db0414226"
50/// "events.0.args.value": "88248701"
51/// ```
52pub async fn handle_match<T: TriggerExecutionServiceTrait>(
53	matching_monitor: MonitorMatch,
54	trigger_service: &T,
55	trigger_scripts: &HashMap<String, (ScriptLanguage, String)>,
56) -> Result<(), FilterError> {
57	match &matching_monitor {
58		MonitorMatch::EVM(evm_monitor_match) => {
59			let transaction = evm_monitor_match.transaction.clone();
60			// If sender does not exist, we replace with 0x0000000000000000000000000000000000000000
61			let sender = transaction.sender().unwrap_or(&Address::ZERO);
62
63			// Create structured JSON data
64			let mut data_json = json!({
65				"monitor": {
66					"name": evm_monitor_match.monitor.name.clone(),
67				},
68				"transaction": {
69					"hash": b256_to_string(*transaction.hash()),
70					"from": h160_to_string(*sender),
71					"value": transaction.value().to_string(),
72				},
73				"functions": [],
74				"events": []
75			});
76
77			// Add 'to' address if present
78			if let Some(to) = transaction.to() {
79				data_json["transaction"]["to"] = json!(h160_to_string(*to));
80			}
81
82			// Process matched functions
83			let functions = data_json["functions"].as_array_mut().unwrap();
84			for func in evm_monitor_match.matched_on.functions.iter() {
85				let mut function_data = json!({
86					"signature": func.signature.clone(),
87					"args": {}
88				});
89
90				// Add function arguments if present
91				if let Some(args) = &evm_monitor_match.matched_on_args {
92					if let Some(func_args) = &args.functions {
93						for func_arg in func_args {
94							if func_arg.signature == func.signature {
95								if let Some(arg_entries) = &func_arg.args {
96									let args_obj = function_data["args"].as_object_mut().unwrap();
97									for arg in arg_entries {
98										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
99									}
100								}
101							}
102						}
103					}
104				}
105
106				functions.push(function_data);
107			}
108
109			// Process matched events
110			let events = data_json["events"].as_array_mut().unwrap();
111			for event in evm_monitor_match.matched_on.events.iter() {
112				let mut event_data = json!({
113					"signature": event.signature.clone(),
114					"args": {}
115				});
116
117				// Add event arguments if present
118				if let Some(args) = &evm_monitor_match.matched_on_args {
119					if let Some(event_args) = &args.events {
120						for event_arg in event_args {
121							if event_arg.signature == event.signature {
122								if let Some(arg_entries) = &event_arg.args {
123									let args_obj = event_data["args"].as_object_mut().unwrap();
124									for arg in arg_entries {
125										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
126									}
127								}
128							}
129						}
130					}
131				}
132
133				events.push(event_data);
134			}
135
136			// Swallow any errors since it's logged in the trigger service and we want to continue
137			// processing other matches
138			let _ = trigger_service
139				.execute(
140					&evm_monitor_match
141						.monitor
142						.triggers
143						.iter()
144						.map(|s| s.to_string())
145						.collect::<Vec<_>>(),
146					json_to_hashmap(&data_json),
147					&matching_monitor,
148					trigger_scripts,
149				)
150				.await;
151		}
152		MonitorMatch::Stellar(stellar_monitor_match) => {
153			let transaction = stellar_monitor_match.transaction.clone();
154
155			// Create structured JSON data
156			let mut data_json = json!({
157				"monitor": {
158					"name": stellar_monitor_match.monitor.name.clone(),
159				},
160				"transaction": {
161					"hash": transaction.hash().to_string(),
162				},
163				"functions": [],
164				"events": []
165			});
166
167			// Process matched functions
168			let functions = data_json["functions"].as_array_mut().unwrap();
169			for func in stellar_monitor_match.matched_on.functions.iter() {
170				let mut function_data = json!({
171					"signature": func.signature.clone(),
172					"args": {}
173				});
174
175				// Add function arguments if present
176				if let Some(args) = &stellar_monitor_match.matched_on_args {
177					if let Some(func_args) = &args.functions {
178						for func_arg in func_args {
179							if func_arg.signature == func.signature {
180								if let Some(arg_entries) = &func_arg.args {
181									let args_obj = function_data["args"].as_object_mut().unwrap();
182									for arg in arg_entries {
183										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
184									}
185								}
186							}
187						}
188					}
189				}
190
191				functions.push(function_data);
192			}
193
194			// Process matched events
195			let events = data_json["events"].as_array_mut().unwrap();
196			for event in stellar_monitor_match.matched_on.events.iter() {
197				let mut event_data = json!({
198					"signature": event.signature.clone(),
199					"args": {}
200				});
201
202				// Add event arguments if present
203				if let Some(args) = &stellar_monitor_match.matched_on_args {
204					if let Some(event_args) = &args.events {
205						for event_arg in event_args {
206							if event_arg.signature == event.signature {
207								if let Some(arg_entries) = &event_arg.args {
208									let args_obj = event_data["args"].as_object_mut().unwrap();
209									for arg in arg_entries {
210										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
211									}
212								}
213							}
214						}
215					}
216				}
217
218				events.push(event_data);
219			}
220
221			// Swallow any errors since it's logged in the trigger service and we want to continue
222			// processing other matches
223			let _ = trigger_service
224				.execute(
225					&stellar_monitor_match
226						.monitor
227						.triggers
228						.iter()
229						.map(|s| s.to_string())
230						.collect::<Vec<_>>(),
231					json_to_hashmap(&data_json),
232					&matching_monitor,
233					trigger_scripts,
234				)
235				.await;
236		}
237		MonitorMatch::Midnight(midnight_monitor_match) => {
238			let transaction = midnight_monitor_match.transaction.clone();
239
240			// Create structured JSON data
241			let mut data_json = json!({
242				"monitor": {
243					"name": midnight_monitor_match.monitor.name.clone(),
244				},
245				"transaction": {
246					"hash": transaction.hash().to_string(),
247				},
248				"functions": [],
249				"events": []
250			});
251
252			// Process matched functions
253			let functions = data_json["functions"].as_array_mut().unwrap();
254			for func in midnight_monitor_match.matched_on.functions.iter() {
255				let mut function_data = json!({
256					"signature": func.signature.clone(),
257					"args": {}
258				});
259
260				// Add function arguments if present
261				if let Some(args) = &midnight_monitor_match.matched_on_args {
262					if let Some(func_args) = &args.functions {
263						for func_arg in func_args {
264							if func_arg.signature == func.signature {
265								if let Some(arg_entries) = &func_arg.args {
266									let args_obj = function_data["args"].as_object_mut().unwrap();
267									for arg in arg_entries {
268										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
269									}
270								}
271							}
272						}
273					}
274				}
275
276				functions.push(function_data);
277			}
278
279			// Process matched events
280			let events = data_json["events"].as_array_mut().unwrap();
281			for event in midnight_monitor_match.matched_on.events.iter() {
282				let mut event_data = json!({
283					"signature": event.signature.clone(),
284					"args": {}
285				});
286
287				// Add event arguments if present
288				if let Some(args) = &midnight_monitor_match.matched_on_args {
289					if let Some(event_args) = &args.events {
290						for event_arg in event_args {
291							if event_arg.signature == event.signature {
292								if let Some(arg_entries) = &event_arg.args {
293									let args_obj = event_data["args"].as_object_mut().unwrap();
294									for arg in arg_entries {
295										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
296									}
297								}
298							}
299						}
300					}
301				}
302
303				events.push(event_data);
304			}
305
306			// Swallow any errors since it's logged in the trigger service and we want to continue
307			// processing other matches
308			let _ = trigger_service
309				.execute(
310					&midnight_monitor_match
311						.monitor
312						.triggers
313						.iter()
314						.map(|s| s.to_string())
315						.collect::<Vec<_>>(),
316					json_to_hashmap(&data_json),
317					&matching_monitor,
318					trigger_scripts,
319				)
320				.await;
321		}
322		MonitorMatch::Solana(solana_monitor_match) => {
323			let transaction = solana_monitor_match.transaction.clone();
324
325			// Create structured JSON data
326			let mut data_json = json!({
327				"monitor": {
328					"name": solana_monitor_match.monitor.name.clone(),
329				},
330				"transaction": {
331					"signature": transaction.signature().to_string(),
332				},
333				"functions": [],
334				"events": []
335			});
336
337			// Process matched functions (instructions)
338			let functions = data_json["functions"].as_array_mut().unwrap();
339			for func in solana_monitor_match.matched_on.functions.iter() {
340				let mut function_data = json!({
341					"signature": func.signature.clone(),
342					"args": {}
343				});
344
345				// Add function arguments if present
346				if let Some(args) = &solana_monitor_match.matched_on_args {
347					if let Some(func_args) = &args.functions {
348						for func_arg in func_args {
349							if func_arg.signature == func.signature {
350								if let Some(arg_entries) = &func_arg.args {
351									let args_obj = function_data["args"].as_object_mut().unwrap();
352									for arg in arg_entries {
353										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
354									}
355								}
356							}
357						}
358					}
359				}
360
361				functions.push(function_data);
362			}
363
364			// Process matched events (logs)
365			let events = data_json["events"].as_array_mut().unwrap();
366			for event in solana_monitor_match.matched_on.events.iter() {
367				let mut event_data = json!({
368					"signature": event.signature.clone(),
369					"args": {}
370				});
371
372				// Add event arguments if present
373				if let Some(args) = &solana_monitor_match.matched_on_args {
374					if let Some(event_args) = &args.events {
375						for event_arg in event_args {
376							if event_arg.signature == event.signature {
377								if let Some(arg_entries) = &event_arg.args {
378									let args_obj = event_data["args"].as_object_mut().unwrap();
379									for arg in arg_entries {
380										args_obj.insert(arg.name.clone(), json!(arg.value.clone()));
381									}
382								}
383							}
384						}
385					}
386				}
387
388				events.push(event_data);
389			}
390
391			// Swallow any errors since it's logged in the trigger service and we want to continue
392			// processing other matches
393			let _ = trigger_service
394				.execute(
395					&solana_monitor_match
396						.monitor
397						.triggers
398						.iter()
399						.map(|s| s.to_string())
400						.collect::<Vec<_>>(),
401					json_to_hashmap(&data_json),
402					&matching_monitor,
403					trigger_scripts,
404				)
405				.await;
406		}
407	}
408	Ok(())
409}
410
411/// Converts a JsonValue to a flattened HashMap with dotted path notation
412fn json_to_hashmap(json: &JsonValue) -> HashMap<String, String> {
413	let mut result = HashMap::new();
414	flatten_json_path(json, "", &mut result);
415	result
416}
417
418/// Flattens a JsonValue into a HashMap with dotted path notation
419fn flatten_json_path(value: &JsonValue, prefix: &str, result: &mut HashMap<String, String>) {
420	match value {
421		JsonValue::Object(obj) => {
422			for (key, val) in obj {
423				let new_prefix = if prefix.is_empty() {
424					key.clone()
425				} else {
426					format!("{}.{}", prefix, key)
427				};
428				flatten_json_path(val, &new_prefix, result);
429			}
430		}
431		JsonValue::Array(arr) => {
432			for (idx, val) in arr.iter().enumerate() {
433				let new_prefix = format!("{}.{}", prefix, idx);
434				flatten_json_path(val, &new_prefix, result);
435			}
436		}
437		JsonValue::String(s) => insert_primitive(prefix, result, s),
438		JsonValue::Number(n) => insert_primitive(prefix, result, n.to_string()),
439		JsonValue::Bool(b) => insert_primitive(prefix, result, b.to_string()),
440		JsonValue::Null => insert_primitive(prefix, result, "null".to_string()),
441	}
442}
443
444/// Helper function to insert primitive values with consistent key handling
445fn insert_primitive<T: ToString>(prefix: &str, result: &mut HashMap<String, String>, value: T) {
446	let key = if prefix.is_empty() {
447		"value".to_string()
448	} else {
449		prefix.to_string()
450	};
451	result.insert(key, value.to_string());
452}
453
454#[cfg(test)]
455mod tests {
456	use super::*;
457	use serde_json::json;
458
459	#[test]
460	fn test_json_to_hashmap() {
461		let json = json!({
462			"monitor": {
463				"name": "Test Monitor",
464			},
465			"transaction": {
466				"hash": "0x1234567890abcdef",
467			},
468		});
469
470		let hashmap = json_to_hashmap(&json);
471		assert_eq!(hashmap["monitor.name"], "Test Monitor");
472		assert_eq!(hashmap["transaction.hash"], "0x1234567890abcdef");
473	}
474
475	#[test]
476	fn test_json_to_hashmap_with_functions() {
477		let json = json!({
478			"monitor": {
479				"name": "Test Monitor",
480			},
481			"functions": [
482				{
483					"signature": "function1(uint256)",
484					"args": {
485						"arg1": "100",
486					},
487				},
488			],
489		});
490
491		let hashmap = json_to_hashmap(&json);
492		assert_eq!(hashmap["monitor.name"], "Test Monitor");
493		assert_eq!(hashmap["functions.0.signature"], "function1(uint256)");
494		assert_eq!(hashmap["functions.0.args.arg1"], "100");
495	}
496
497	#[test]
498	fn test_json_to_hashmap_with_events() {
499		let json = json!({
500			"monitor": {
501				"name": "Test Monitor",
502			},
503			"events": [
504				{
505					"signature": "event1(uint256)",
506					"args": {
507						"arg1": "100",
508					},
509				},
510			],
511		});
512
513		let hashmap = json_to_hashmap(&json);
514		assert_eq!(hashmap["monitor.name"], "Test Monitor");
515		assert_eq!(hashmap["events.0.signature"], "event1(uint256)");
516		assert_eq!(hashmap["events.0.args.arg1"], "100");
517	}
518
519	// Add tests for flatten_json_path
520	#[test]
521	fn test_flatten_json_path_object() {
522		let json = json!({
523			"monitor": {
524				"name": "Test Monitor",
525			},
526		});
527
528		let mut result = HashMap::new();
529		flatten_json_path(&json, "", &mut result);
530		assert_eq!(result["monitor.name"], "Test Monitor");
531	}
532
533	#[test]
534	fn test_flatten_json_path_array() {
535		let json = json!({
536			"monitor": {
537				"name": "Test Monitor",
538			},
539		});
540
541		let mut result = HashMap::new();
542		flatten_json_path(&json, "", &mut result);
543		assert_eq!(result["monitor.name"], "Test Monitor");
544	}
545
546	#[test]
547	fn test_flatten_json_path_string() {
548		let json = json!("Test String");
549		let mut result = HashMap::new();
550		flatten_json_path(&json, "test_prefix", &mut result);
551		assert_eq!(result["test_prefix"], "Test String");
552
553		let mut result2 = HashMap::new();
554		flatten_json_path(&json, "", &mut result2);
555		assert_eq!(result2["value"], "Test String");
556	}
557
558	#[test]
559	fn test_flatten_json_path_number() {
560		let json = json!(123);
561		let mut result = HashMap::new();
562		flatten_json_path(&json, "test_prefix", &mut result);
563		assert_eq!(result["test_prefix"], "123");
564
565		let mut result2 = HashMap::new();
566		flatten_json_path(&json, "", &mut result2);
567		assert_eq!(result2["value"], "123");
568	}
569
570	#[test]
571	fn test_flatten_json_path_boolean() {
572		let json = json!(true);
573		let mut result = HashMap::new();
574		flatten_json_path(&json, "test_prefix", &mut result);
575		assert_eq!(result["test_prefix"], "true");
576
577		// Test with empty prefix
578		let mut result2 = HashMap::new();
579		flatten_json_path(&json, "", &mut result2);
580		assert_eq!(result2["value"], "true");
581	}
582
583	#[test]
584	fn test_flatten_json_path_null() {
585		let json = json!(null);
586		let mut result = HashMap::new();
587		flatten_json_path(&json, "test_prefix", &mut result);
588		assert_eq!(result["test_prefix"], "null");
589
590		let mut result2 = HashMap::new();
591		flatten_json_path(&json, "", &mut result2);
592		assert_eq!(result2["value"], "null");
593	}
594
595	#[test]
596	fn test_flatten_json_path_nested_object() {
597		let json = json!({
598			"monitor": {
599				"name": "Test Monitor",
600				"nested": {
601					"key": "value",
602				},
603			},
604		});
605
606		let mut result = HashMap::new();
607		flatten_json_path(&json, "", &mut result);
608		assert_eq!(result["monitor.nested.key"], "value");
609	}
610
611	#[test]
612	fn test_flatten_json_path_nested_array() {
613		let json = json!({
614			"monitor": {
615				"name": "Test Monitor",
616				"nested": [
617					{
618						"key": "value1",
619					},
620					{
621						"key": "value2",
622					},
623				],
624			},
625		});
626
627		let mut result = HashMap::new();
628		flatten_json_path(&json, "", &mut result);
629		assert_eq!(result["monitor.nested.0.key"], "value1");
630		assert_eq!(result["monitor.nested.1.key"], "value2");
631	}
632
633	#[test]
634	fn test_flatten_json_path_with_prefix() {
635		let json = json!({
636			"monitor": {
637				"name": "Test Monitor",
638			},
639		});
640
641		let mut result = HashMap::new();
642		flatten_json_path(&json, "prefix", &mut result);
643		assert_eq!(result["prefix.monitor.name"], "Test Monitor");
644	}
645
646	#[test]
647	fn test_json_to_hashmap_with_primitive_values() {
648		// String
649		let json_string = json!("Test String");
650		let hashmap_string = json_to_hashmap(&json_string);
651		assert_eq!(hashmap_string["value"], "Test String");
652
653		// Number
654		let json_number = json!(123);
655		let hashmap_number = json_to_hashmap(&json_number);
656		assert_eq!(hashmap_number["value"], "123");
657
658		// Boolean
659		let json_bool = json!(true);
660		let hashmap_bool = json_to_hashmap(&json_bool);
661		assert_eq!(hashmap_bool["value"], "true");
662
663		// Null
664		let json_null = json!(null);
665		let hashmap_null = json_to_hashmap(&json_null);
666		assert_eq!(hashmap_null["value"], "null");
667	}
668
669	#[test]
670	fn test_insert_primitive() {
671		let mut result = HashMap::new();
672		insert_primitive("prefix", &mut result, "Test String");
673		assert_eq!(result["prefix"], "Test String");
674
675		let mut result2 = HashMap::new();
676		insert_primitive("", &mut result2, "Test String");
677		assert_eq!(result2["value"], "Test String");
678
679		let mut result3 = HashMap::new();
680		insert_primitive("prefix", &mut result3, 123);
681		assert_eq!(result3["prefix"], "123");
682
683		let mut result4 = HashMap::new();
684		insert_primitive("", &mut result4, 123);
685		assert_eq!(result4["value"], "123");
686
687		let mut result5 = HashMap::new();
688		insert_primitive("prefix", &mut result5, true);
689		assert_eq!(result5["prefix"], "true");
690
691		let mut result6 = HashMap::new();
692		insert_primitive("", &mut result6, true);
693		assert_eq!(result6["value"], "true");
694
695		let mut result7 = HashMap::new();
696		insert_primitive("prefix", &mut result7, JsonValue::Null);
697		assert_eq!(result7["prefix"], "null");
698
699		let mut result8 = HashMap::new();
700		insert_primitive("", &mut result8, JsonValue::Null);
701		assert_eq!(result8["value"], "null");
702	}
703}