1use std::collections::{HashMap, HashSet};
34use std::sync::{Arc, Mutex};
35
36use serde::{Deserialize, Serialize};
37
38use crate::handler::DecodedEvent;
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct FactoryConfig {
48 pub factory_address: String,
50 pub creation_event_topic0: String,
53 pub child_address_field: String,
56 pub name: Option<String>,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct DiscoveredChild {
65 pub address: String,
67 pub factory_address: String,
69 pub discovered_at_block: u64,
71 pub discovered_at_tx: String,
73 pub creation_event: serde_json::Value,
75}
76
77#[derive(Debug, Clone, Serialize, Deserialize)]
84pub struct FactorySnapshot {
85 pub configs: Vec<FactoryConfig>,
87 pub children: HashMap<String, Vec<DiscoveredChild>>,
89}
90
91#[derive(Debug)]
95struct RegistryInner {
96 configs: HashMap<(String, String), FactoryConfig>,
98 factory_addresses: HashSet<String>,
100 children: HashMap<String, Vec<DiscoveredChild>>,
102 child_addresses: HashSet<String>,
104}
105
106impl RegistryInner {
107 fn new() -> Self {
108 Self {
109 configs: HashMap::new(),
110 factory_addresses: HashSet::new(),
111 children: HashMap::new(),
112 child_addresses: HashSet::new(),
113 }
114 }
115}
116
117pub struct FactoryRegistry {
129 inner: Arc<Mutex<RegistryInner>>,
130}
131
132impl FactoryRegistry {
133 pub fn new() -> Self {
135 Self {
136 inner: Arc::new(Mutex::new(RegistryInner::new())),
137 }
138 }
139
140 pub fn register(&self, config: FactoryConfig) {
145 let mut inner = self.inner.lock().expect("factory registry lock poisoned");
146 let key = (
147 config.factory_address.to_lowercase(),
148 config.creation_event_topic0.to_lowercase(),
149 );
150 inner
151 .factory_addresses
152 .insert(config.factory_address.to_lowercase());
153 inner.configs.insert(key, config);
154 }
155
156 pub fn process_event(&self, event: &DecodedEvent) -> Option<DiscoveredChild> {
165 let mut inner = self.inner.lock().expect("factory registry lock poisoned");
166 let addr = event.address.to_lowercase();
167
168 if !inner.factory_addresses.contains(&addr) {
170 return None;
171 }
172
173 let matching_config = inner
177 .configs
178 .iter()
179 .find(|((fa, _), _)| fa == &addr)
180 .map(|(_, cfg)| cfg.clone());
181
182 let config = matching_config?;
183
184 let child_addr = extract_field(&event.fields_json, &config.child_address_field)?;
186 let child_addr_lower = child_addr.to_lowercase();
187
188 if inner.child_addresses.contains(&child_addr_lower) {
190 return None;
191 }
192
193 let child = DiscoveredChild {
194 address: child_addr_lower.clone(),
195 factory_address: addr.clone(),
196 discovered_at_block: event.block_number,
197 discovered_at_tx: event.tx_hash.clone(),
198 creation_event: event.fields_json.clone(),
199 };
200
201 inner.child_addresses.insert(child_addr_lower);
202 inner.children.entry(addr).or_default().push(child.clone());
203
204 Some(child)
205 }
206
207 pub fn get_all_addresses(&self) -> Vec<String> {
212 let inner = self.inner.lock().expect("factory registry lock poisoned");
213 let mut addrs: Vec<String> = inner.factory_addresses.iter().cloned().collect();
214 addrs.extend(inner.child_addresses.iter().cloned());
215 addrs.sort();
216 addrs
217 }
218
219 pub fn children_of(&self, factory_address: &str) -> Vec<DiscoveredChild> {
223 let inner = self.inner.lock().expect("factory registry lock poisoned");
224 inner
225 .children
226 .get(&factory_address.to_lowercase())
227 .cloned()
228 .unwrap_or_default()
229 }
230
231 pub fn snapshot(&self) -> FactorySnapshot {
235 let inner = self.inner.lock().expect("factory registry lock poisoned");
236 let configs: Vec<FactoryConfig> = inner.configs.values().cloned().collect();
237 let children = inner.children.clone();
238 FactorySnapshot { configs, children }
239 }
240
241 pub fn restore(&self, snapshot: FactorySnapshot) {
246 let mut inner = self.inner.lock().expect("factory registry lock poisoned");
247
248 for config in snapshot.configs {
250 let key = (
251 config.factory_address.to_lowercase(),
252 config.creation_event_topic0.to_lowercase(),
253 );
254 inner
255 .factory_addresses
256 .insert(config.factory_address.to_lowercase());
257 inner.configs.insert(key, config);
258 }
259
260 for (factory_addr, children) in snapshot.children {
262 let factory_lower = factory_addr.to_lowercase();
263 for child in children {
264 let child_lower = child.address.to_lowercase();
265 if inner.child_addresses.insert(child_lower) {
266 inner
267 .children
268 .entry(factory_lower.clone())
269 .or_default()
270 .push(child);
271 }
272 }
273 }
274 }
275
276 pub fn factory_count(&self) -> usize {
278 let inner = self.inner.lock().expect("factory registry lock poisoned");
279 inner.factory_addresses.len()
280 }
281
282 pub fn child_count(&self) -> usize {
284 let inner = self.inner.lock().expect("factory registry lock poisoned");
285 inner.child_addresses.len()
286 }
287}
288
289impl Default for FactoryRegistry {
290 fn default() -> Self {
291 Self::new()
292 }
293}
294
295impl Clone for FactoryRegistry {
296 fn clone(&self) -> Self {
297 let inner = self.inner.lock().expect("factory registry lock poisoned");
298 let new_inner = RegistryInner {
299 configs: inner.configs.clone(),
300 factory_addresses: inner.factory_addresses.clone(),
301 children: inner.children.clone(),
302 child_addresses: inner.child_addresses.clone(),
303 };
304 Self {
305 inner: Arc::new(Mutex::new(new_inner)),
306 }
307 }
308}
309
310fn extract_field(json: &serde_json::Value, path: &str) -> Option<String> {
317 let mut current = json;
318 for segment in path.split('.') {
319 current = current.get(segment)?;
320 }
321 match current {
322 serde_json::Value::String(s) => Some(s.clone()),
323 _ => None,
325 }
326}
327
328#[cfg(test)]
331mod tests {
332 use super::*;
333
334 const FACTORY_ADDR: &str = "0xfactory";
335 const TOPIC0: &str = "0xpoolcreated";
336
337 fn make_config() -> FactoryConfig {
338 FactoryConfig {
339 factory_address: FACTORY_ADDR.into(),
340 creation_event_topic0: TOPIC0.into(),
341 child_address_field: "pool".into(),
342 name: Some("Test Factory".into()),
343 }
344 }
345
346 fn make_event(factory: &str, pool_addr: &str, block: u64) -> DecodedEvent {
347 DecodedEvent {
348 chain: "ethereum".into(),
349 schema: "PoolCreated".into(),
350 address: factory.into(),
351 tx_hash: format!("0xtx{block}"),
352 block_number: block,
353 log_index: 0,
354 fields_json: serde_json::json!({ "pool": pool_addr }),
355 }
356 }
357
358 #[test]
359 fn register_factory() {
360 let registry = FactoryRegistry::new();
361 assert_eq!(registry.factory_count(), 0);
362
363 registry.register(make_config());
364 assert_eq!(registry.factory_count(), 1);
365
366 let addrs = registry.get_all_addresses();
367 assert!(addrs.contains(&FACTORY_ADDR.to_lowercase().to_string()));
368 }
369
370 #[test]
371 fn discover_child_from_event() {
372 let registry = FactoryRegistry::new();
373 registry.register(make_config());
374
375 let event = make_event(FACTORY_ADDR, "0xchild1", 100);
376 let child = registry.process_event(&event);
377
378 assert!(child.is_some());
379 let child = child.unwrap();
380 assert_eq!(child.address, "0xchild1");
381 assert_eq!(child.factory_address, FACTORY_ADDR.to_lowercase());
382 assert_eq!(child.discovered_at_block, 100);
383 }
384
385 #[test]
386 fn duplicate_child_ignored() {
387 let registry = FactoryRegistry::new();
388 registry.register(make_config());
389
390 let event = make_event(FACTORY_ADDR, "0xchild1", 100);
391 assert!(registry.process_event(&event).is_some());
392
393 let event2 = make_event(FACTORY_ADDR, "0xchild1", 101);
395 assert!(registry.process_event(&event2).is_none());
396
397 assert_eq!(registry.child_count(), 1);
398 }
399
400 #[test]
401 fn event_from_unknown_factory_ignored() {
402 let registry = FactoryRegistry::new();
403 registry.register(make_config());
404
405 let event = make_event("0xunknown", "0xchild1", 100);
406 assert!(registry.process_event(&event).is_none());
407 }
408
409 #[test]
410 fn multiple_factories() {
411 let registry = FactoryRegistry::new();
412
413 registry.register(FactoryConfig {
414 factory_address: "0xfactory_a".into(),
415 creation_event_topic0: "0xtopic_a".into(),
416 child_address_field: "pool".into(),
417 name: Some("Factory A".into()),
418 });
419
420 registry.register(FactoryConfig {
421 factory_address: "0xfactory_b".into(),
422 creation_event_topic0: "0xtopic_b".into(),
423 child_address_field: "vault".into(),
424 name: Some("Factory B".into()),
425 });
426
427 assert_eq!(registry.factory_count(), 2);
428
429 let ev_a = DecodedEvent {
431 chain: "ethereum".into(),
432 schema: "PoolCreated".into(),
433 address: "0xfactory_a".into(),
434 tx_hash: "0xtx1".into(),
435 block_number: 50,
436 log_index: 0,
437 fields_json: serde_json::json!({ "pool": "0xchild_a" }),
438 };
439 assert!(registry.process_event(&ev_a).is_some());
440
441 let ev_b = DecodedEvent {
443 chain: "ethereum".into(),
444 schema: "VaultCreated".into(),
445 address: "0xfactory_b".into(),
446 tx_hash: "0xtx2".into(),
447 block_number: 55,
448 log_index: 0,
449 fields_json: serde_json::json!({ "vault": "0xchild_b" }),
450 };
451 assert!(registry.process_event(&ev_b).is_some());
452
453 assert_eq!(registry.child_count(), 2);
454 assert_eq!(registry.children_of("0xfactory_a").len(), 1);
455 assert_eq!(registry.children_of("0xfactory_b").len(), 1);
456 }
457
458 #[test]
459 fn get_all_addresses_includes_factories_and_children() {
460 let registry = FactoryRegistry::new();
461 registry.register(make_config());
462
463 let event = make_event(FACTORY_ADDR, "0xchild1", 100);
464 registry.process_event(&event);
465
466 let event2 = make_event(FACTORY_ADDR, "0xchild2", 101);
467 registry.process_event(&event2);
468
469 let addrs = registry.get_all_addresses();
470 assert_eq!(addrs.len(), 3); assert!(addrs.contains(&FACTORY_ADDR.to_lowercase().to_string()));
472 assert!(addrs.contains(&"0xchild1".to_string()));
473 assert!(addrs.contains(&"0xchild2".to_string()));
474 }
475
476 #[test]
477 fn snapshot_and_restore() {
478 let registry = FactoryRegistry::new();
479 registry.register(make_config());
480
481 let event = make_event(FACTORY_ADDR, "0xchild1", 100);
482 registry.process_event(&event);
483 let event2 = make_event(FACTORY_ADDR, "0xchild2", 101);
484 registry.process_event(&event2);
485
486 let snap = registry.snapshot();
488 assert_eq!(snap.children.len(), 1); let children = snap.children.get(&FACTORY_ADDR.to_lowercase()).unwrap();
490 assert_eq!(children.len(), 2);
491
492 let registry2 = FactoryRegistry::new();
494 registry2.restore(snap);
495
496 assert_eq!(registry2.factory_count(), 1);
497 assert_eq!(registry2.child_count(), 2);
498 assert_eq!(registry2.get_all_addresses().len(), 3);
499 assert_eq!(registry2.children_of(FACTORY_ADDR).len(), 2);
500 }
501
502 #[test]
503 fn snapshot_restore_roundtrip_json() {
504 let registry = FactoryRegistry::new();
505 registry.register(make_config());
506 registry.process_event(&make_event(FACTORY_ADDR, "0xchild1", 100));
507
508 let snap = registry.snapshot();
510 let json = serde_json::to_string(&snap).unwrap();
511 let restored: FactorySnapshot = serde_json::from_str(&json).unwrap();
512
513 let registry2 = FactoryRegistry::new();
514 registry2.restore(restored);
515
516 assert_eq!(registry2.child_count(), 1);
517 assert_eq!(registry2.children_of(FACTORY_ADDR).len(), 1);
518 }
519
520 #[test]
521 fn nested_field_extraction() {
522 let registry = FactoryRegistry::new();
523 registry.register(FactoryConfig {
524 factory_address: "0xnested_factory".into(),
525 creation_event_topic0: "0xtopic".into(),
526 child_address_field: "args.pool".into(),
527 name: Some("Nested Factory".into()),
528 });
529
530 let event = DecodedEvent {
531 chain: "ethereum".into(),
532 schema: "PoolCreated".into(),
533 address: "0xnested_factory".into(),
534 tx_hash: "0xtx1".into(),
535 block_number: 200,
536 log_index: 0,
537 fields_json: serde_json::json!({ "args": { "pool": "0xdeep_child" } }),
538 };
539
540 let child = registry.process_event(&event);
541 assert!(child.is_some());
542 assert_eq!(child.unwrap().address, "0xdeep_child");
543 }
544
545 #[test]
546 fn missing_field_returns_none() {
547 let registry = FactoryRegistry::new();
548 registry.register(make_config());
549
550 let event = DecodedEvent {
552 chain: "ethereum".into(),
553 schema: "PoolCreated".into(),
554 address: FACTORY_ADDR.into(),
555 tx_hash: "0xtx1".into(),
556 block_number: 100,
557 log_index: 0,
558 fields_json: serde_json::json!({ "token0": "0xabc" }),
559 };
560
561 assert!(registry.process_event(&event).is_none());
562 }
563
564 #[test]
565 fn case_insensitive_address_matching() {
566 let registry = FactoryRegistry::new();
567 registry.register(FactoryConfig {
568 factory_address: "0xAbCdEf".into(),
569 creation_event_topic0: TOPIC0.into(),
570 child_address_field: "pool".into(),
571 name: None,
572 });
573
574 let event = DecodedEvent {
576 chain: "ethereum".into(),
577 schema: "PoolCreated".into(),
578 address: "0xabcdef".into(),
579 tx_hash: "0xtx1".into(),
580 block_number: 100,
581 log_index: 0,
582 fields_json: serde_json::json!({ "pool": "0xchild_case" }),
583 };
584
585 let child = registry.process_event(&event);
586 assert!(child.is_some());
587 assert_eq!(child.unwrap().address, "0xchild_case");
588 }
589
590 #[test]
591 fn children_of_unknown_factory_returns_empty() {
592 let registry = FactoryRegistry::new();
593 assert!(registry.children_of("0xnonexistent").is_empty());
594 }
595}