1use super::{
21 ConfigFileNetworkType, EvmNetworkConfig, NetworkFileConfig, SolanaNetworkConfig,
22 StellarNetworkConfig,
23};
24use crate::config::ConfigFileError;
25
26pub struct InheritanceResolver<'a> {
28 network_lookup: &'a dyn Fn(&str) -> Option<&'a NetworkFileConfig>,
30}
31
32macro_rules! impl_inheritance_resolver {
37 ($method_name:ident, $config_type:ty, $network_type:ident, $variant:ident, $type_name:expr) => {
38 pub fn $method_name(&self, config: &$config_type, network_name: &str, parent_name: &str) -> Result<$config_type, ConfigFileError> {
48 let parent_network = (self.network_lookup)(parent_name).ok_or_else(|| {
50 ConfigFileError::InvalidReference(format!(
51 "Network '{}' inherits from non-existent network '{}' in inheritance chain",
52 network_name, parent_name
53 ))
54 })?;
55
56 if parent_network.network_type() != ConfigFileNetworkType::$network_type {
58 return Err(ConfigFileError::IncompatibleInheritanceType(format!(
59 "Network '{}' (type {}) tries to inherit from '{}' (type {:?}) - inheritance chain broken due to type mismatch",
60 network_name, $type_name, parent_name, parent_network.network_type()
61 )));
62 }
63
64 let parent_config = match parent_network {
66 NetworkFileConfig::$variant(config) => config,
67 _ => return Err(ConfigFileError::InvalidFormat(format!("Expected {} network configuration", $type_name))),
68 };
69
70 let resolved_parent = if parent_network.inherits_from().is_some() {
72 let grandparent_name = parent_network.inherits_from().unwrap();
73 self.$method_name(parent_config, parent_name, grandparent_name)?
74 } else {
75 parent_config.clone()
76 };
77
78 Ok(config.merge_with_parent(&resolved_parent))
80 }
81 };
82}
83
84impl<'a> InheritanceResolver<'a> {
85 pub fn new(network_lookup: &'a dyn Fn(&str) -> Option<&'a NetworkFileConfig>) -> Self {
93 Self { network_lookup }
94 }
95
96 impl_inheritance_resolver!(resolve_evm_inheritance, EvmNetworkConfig, Evm, Evm, "EVM");
98 impl_inheritance_resolver!(
99 resolve_solana_inheritance,
100 SolanaNetworkConfig,
101 Solana,
102 Solana,
103 "Solana"
104 );
105 impl_inheritance_resolver!(
106 resolve_stellar_inheritance,
107 StellarNetworkConfig,
108 Stellar,
109 Stellar,
110 "Stellar"
111 );
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use crate::config::config_file::network::common::NetworkConfigCommon;
118 use crate::config::config_file::network::test_utils::*;
119 use std::collections::HashMap;
120
121 #[test]
122 fn test_inheritance_resolver_new() {
123 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
124 let lookup_fn = |name: &str| networks.get(name);
125 let resolver = InheritanceResolver::new(&lookup_fn);
126
127 assert!((resolver.network_lookup)("nonexistent").is_none());
130 }
131
132 #[test]
133 fn test_resolve_evm_inheritance_simple_success() {
134 let mut networks = HashMap::new();
135
136 let parent_config = create_evm_network("parent");
138 networks.insert(
139 "parent".to_string(),
140 NetworkFileConfig::Evm(parent_config.clone()),
141 );
142
143 let lookup_fn = |name: &str| networks.get(name);
144 let resolver = InheritanceResolver::new(&lookup_fn);
145
146 let child_config = EvmNetworkConfig {
148 common: NetworkConfigCommon {
149 network: "child".to_string(),
150 from: Some("parent".to_string()),
151 rpc_urls: None, explorer_urls: None, average_blocktime_ms: Some(15000), is_testnet: Some(false), tags: None,
156 },
157 chain_id: None, required_confirmations: Some(2), features: None,
160 symbol: None, gas_price_cache: None,
162 };
163
164 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
165 assert!(result.is_ok());
166
167 let resolved = result.unwrap();
168 assert_eq!(resolved.common.network, "child");
169 assert_eq!(resolved.common.rpc_urls, parent_config.common.rpc_urls); assert_eq!(
171 resolved.common.explorer_urls,
172 parent_config.common.explorer_urls
173 ); assert_eq!(resolved.common.average_blocktime_ms, Some(15000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(resolved.chain_id, parent_config.chain_id); assert_eq!(resolved.required_confirmations, Some(2)); assert_eq!(resolved.symbol, parent_config.symbol); }
180
181 #[test]
182 fn test_resolve_evm_inheritance_multi_level() {
183 let mut networks = HashMap::new();
184
185 let grandparent_config = create_evm_network("grandparent");
187 networks.insert(
188 "grandparent".to_string(),
189 NetworkFileConfig::Evm(grandparent_config.clone()),
190 );
191
192 let parent_config = EvmNetworkConfig {
194 common: NetworkConfigCommon {
195 network: "parent".to_string(),
196 from: Some("grandparent".to_string()),
197 rpc_urls: None,
198 explorer_urls: None,
199 average_blocktime_ms: Some(10000), is_testnet: None,
201 tags: None,
202 },
203 chain_id: None,
204 required_confirmations: Some(3), features: None,
206 symbol: None,
207 gas_price_cache: None,
208 };
209 networks.insert(
210 "parent".to_string(),
211 NetworkFileConfig::Evm(parent_config.clone()),
212 );
213
214 let lookup_fn = |name: &str| networks.get(name);
215 let resolver = InheritanceResolver::new(&lookup_fn);
216
217 let child_config = EvmNetworkConfig {
219 common: NetworkConfigCommon {
220 network: "child".to_string(),
221 from: Some("parent".to_string()),
222 rpc_urls: None,
223 explorer_urls: None,
224 average_blocktime_ms: None,
225 is_testnet: Some(false), tags: None,
227 },
228 chain_id: Some(42), required_confirmations: None,
230 features: None,
231 symbol: None,
232 gas_price_cache: None,
233 };
234
235 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
236 assert!(result.is_ok());
237
238 let resolved = result.unwrap();
239 assert_eq!(resolved.common.network, "child");
240 assert_eq!(resolved.common.rpc_urls, grandparent_config.common.rpc_urls); assert_eq!(
242 resolved.common.explorer_urls,
243 grandparent_config.common.explorer_urls
244 ); assert_eq!(resolved.common.average_blocktime_ms, Some(10000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(resolved.chain_id, Some(42)); assert_eq!(resolved.required_confirmations, Some(3)); assert_eq!(resolved.symbol, grandparent_config.symbol); }
251
252 #[test]
253 fn test_resolve_evm_inheritance_nonexistent_parent() {
254 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
255 let lookup_fn = |name: &str| networks.get(name);
256 let resolver = InheritanceResolver::new(&lookup_fn);
257
258 let child_config = create_evm_network_with_parent("child", "nonexistent");
259
260 let result = resolver.resolve_evm_inheritance(&child_config, "child", "nonexistent");
261 assert!(result.is_err());
262
263 assert!(matches!(
264 result.unwrap_err(),
265 ConfigFileError::InvalidReference(_)
266 ));
267 }
268
269 #[test]
270 fn test_resolve_evm_inheritance_type_mismatch() {
271 let mut networks = HashMap::new();
272
273 let parent_config = create_solana_network("parent");
275 networks.insert(
276 "parent".to_string(),
277 NetworkFileConfig::Solana(parent_config),
278 );
279
280 let lookup_fn = |name: &str| networks.get(name);
281 let resolver = InheritanceResolver::new(&lookup_fn);
282
283 let child_config = create_evm_network_with_parent("child", "parent");
284
285 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
286 assert!(result.is_err());
287
288 assert!(matches!(
289 result.unwrap_err(),
290 ConfigFileError::IncompatibleInheritanceType(_)
291 ));
292 }
293
294 #[test]
295 fn test_resolve_evm_inheritance_no_inheritance() {
296 let mut networks = HashMap::new();
297
298 let parent_config = create_evm_network("parent");
300 networks.insert(
301 "parent".to_string(),
302 NetworkFileConfig::Evm(parent_config.clone()),
303 );
304
305 let lookup_fn = |name: &str| networks.get(name);
306 let resolver = InheritanceResolver::new(&lookup_fn);
307
308 let child_config = create_evm_network_with_parent("child", "parent");
309
310 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
311 assert!(result.is_ok());
312
313 let resolved = result.unwrap();
314 assert_eq!(resolved.common.network, "child");
316 assert_eq!(
317 resolved.chain_id,
318 child_config.chain_id.or(parent_config.chain_id)
319 );
320 }
321
322 #[test]
324 fn test_resolve_solana_inheritance_simple_success() {
325 let mut networks = HashMap::new();
326
327 let parent_config = create_solana_network("parent");
328 networks.insert(
329 "parent".to_string(),
330 NetworkFileConfig::Solana(parent_config.clone()),
331 );
332
333 let lookup_fn = |name: &str| networks.get(name);
334 let resolver = InheritanceResolver::new(&lookup_fn);
335
336 let child_config = SolanaNetworkConfig {
337 common: NetworkConfigCommon {
338 network: "child".to_string(),
339 from: Some("parent".to_string()),
340 rpc_urls: None, explorer_urls: None, average_blocktime_ms: Some(500), is_testnet: None,
344 tags: None,
345 },
346 };
347
348 let result = resolver.resolve_solana_inheritance(&child_config, "child", "parent");
349 assert!(result.is_ok());
350
351 let resolved = result.unwrap();
352 assert_eq!(resolved.common.network, "child");
353 assert_eq!(resolved.common.rpc_urls, parent_config.common.rpc_urls); assert_eq!(
355 resolved.common.explorer_urls,
356 parent_config.common.explorer_urls
357 ); assert_eq!(resolved.common.average_blocktime_ms, Some(500)); }
360
361 #[test]
362 fn test_resolve_solana_inheritance_type_mismatch() {
363 let mut networks = HashMap::new();
364
365 let parent_config = create_evm_network("parent");
367 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
368
369 let lookup_fn = |name: &str| networks.get(name);
370 let resolver = InheritanceResolver::new(&lookup_fn);
371
372 let child_config = create_solana_network_with_parent("child", "parent");
373
374 let result = resolver.resolve_solana_inheritance(&child_config, "child", "parent");
375 assert!(result.is_err());
376
377 assert!(matches!(
378 result.unwrap_err(),
379 ConfigFileError::IncompatibleInheritanceType(_)
380 ));
381 }
382
383 #[test]
384 fn test_resolve_solana_inheritance_nonexistent_parent() {
385 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
386 let lookup_fn = |name: &str| networks.get(name);
387 let resolver = InheritanceResolver::new(&lookup_fn);
388
389 let child_config = create_solana_network_with_parent("child", "nonexistent");
390
391 let result = resolver.resolve_solana_inheritance(&child_config, "child", "nonexistent");
392 assert!(result.is_err());
393
394 assert!(matches!(
395 result.unwrap_err(),
396 ConfigFileError::InvalidReference(_)
397 ));
398 }
399
400 #[test]
401 fn test_resolve_stellar_inheritance_simple_success() {
402 let mut networks = HashMap::new();
403
404 let parent_config = create_stellar_network("parent");
405 networks.insert(
406 "parent".to_string(),
407 NetworkFileConfig::Stellar(parent_config.clone()),
408 );
409
410 let lookup_fn = |name: &str| networks.get(name);
411 let resolver = InheritanceResolver::new(&lookup_fn);
412
413 let child_config = StellarNetworkConfig {
414 common: NetworkConfigCommon {
415 network: "child".to_string(),
416 from: Some("parent".to_string()),
417 rpc_urls: None, explorer_urls: None, average_blocktime_ms: Some(6000), is_testnet: None,
421 tags: None,
422 },
423 passphrase: None, horizon_url: None, };
426
427 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
428 assert!(result.is_ok());
429
430 let resolved = result.unwrap();
431 assert_eq!(resolved.common.network, "child");
432 assert_eq!(resolved.common.rpc_urls, parent_config.common.rpc_urls); assert_eq!(resolved.common.average_blocktime_ms, Some(6000)); assert_eq!(resolved.passphrase, parent_config.passphrase); }
436
437 #[test]
438 fn test_resolve_stellar_inheritance_type_mismatch() {
439 let mut networks = HashMap::new();
440
441 let parent_config = create_solana_network("parent");
443 networks.insert(
444 "parent".to_string(),
445 NetworkFileConfig::Solana(parent_config),
446 );
447
448 let lookup_fn = |name: &str| networks.get(name);
449 let resolver = InheritanceResolver::new(&lookup_fn);
450
451 let child_config = create_stellar_network_with_parent("child", "parent");
452
453 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
454 assert!(result.is_err());
455
456 assert!(matches!(
457 result.unwrap_err(),
458 ConfigFileError::IncompatibleInheritanceType(_)
459 ));
460 }
461
462 #[test]
463 fn test_resolve_stellar_inheritance_nonexistent_parent() {
464 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
465 let lookup_fn = |name: &str| networks.get(name);
466 let resolver = InheritanceResolver::new(&lookup_fn);
467
468 let child_config = create_stellar_network_with_parent("child", "nonexistent");
469
470 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "nonexistent");
471 assert!(result.is_err());
472
473 assert!(matches!(
474 result.unwrap_err(),
475 ConfigFileError::InvalidReference(_)
476 ));
477 }
478
479 #[test]
480 fn test_resolve_inheritance_deep_chain() {
481 let mut networks = HashMap::new();
482
483 let great_grandparent_config = create_evm_network("great-grandparent");
485 networks.insert(
486 "great-grandparent".to_string(),
487 NetworkFileConfig::Evm(great_grandparent_config.clone()),
488 );
489
490 let grandparent_config = EvmNetworkConfig {
491 common: NetworkConfigCommon {
492 network: "grandparent".to_string(),
493 from: Some("great-grandparent".to_string()),
494 rpc_urls: None,
495 explorer_urls: None,
496 average_blocktime_ms: Some(11000),
497 is_testnet: None,
498 tags: None,
499 },
500 chain_id: None,
501 required_confirmations: None,
502 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
503 symbol: None,
504 gas_price_cache: None,
505 };
506 networks.insert(
507 "grandparent".to_string(),
508 NetworkFileConfig::Evm(grandparent_config),
509 );
510
511 let parent_config = EvmNetworkConfig {
512 common: NetworkConfigCommon {
513 network: "parent".to_string(),
514 from: Some("grandparent".to_string()),
515 rpc_urls: None,
516 explorer_urls: None,
517 average_blocktime_ms: None,
518 is_testnet: Some(false),
519 tags: Some(vec!["production".to_string()]),
520 },
521 chain_id: Some(100),
522 required_confirmations: None,
523 features: None,
524 symbol: None,
525 gas_price_cache: None,
526 };
527 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
528
529 let lookup_fn = |name: &str| networks.get(name);
530 let resolver = InheritanceResolver::new(&lookup_fn);
531
532 let child_config = EvmNetworkConfig {
533 common: NetworkConfigCommon {
534 network: "child".to_string(),
535 from: Some("parent".to_string()),
536 rpc_urls: Some(vec!["https://custom-rpc.example.com".to_string()]),
537 explorer_urls: Some(vec!["https://custom-explorer.example.com".to_string()]),
538 average_blocktime_ms: None,
539 is_testnet: None,
540 tags: None,
541 },
542 chain_id: None,
543 required_confirmations: Some(5),
544 features: None,
545 symbol: Some("CUSTOM".to_string()),
546 gas_price_cache: None,
547 };
548
549 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
550 assert!(result.is_ok());
551
552 let resolved = result.unwrap();
553 assert_eq!(resolved.common.network, "child");
554 assert_eq!(
555 resolved.common.rpc_urls,
556 Some(vec!["https://custom-rpc.example.com".to_string()])
557 ); assert_eq!(
559 resolved.common.explorer_urls,
560 Some(vec!["https://custom-explorer.example.com".to_string()])
561 ); assert_eq!(resolved.common.average_blocktime_ms, Some(11000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(
565 resolved.common.tags,
566 Some(vec!["test".to_string(), "production".to_string()])
567 ); assert_eq!(resolved.chain_id, Some(100)); assert_eq!(resolved.required_confirmations, Some(5)); assert_eq!(resolved.symbol, Some("CUSTOM".to_string())); assert_eq!(
572 resolved.features,
573 Some(vec!["eip1559".to_string(), "london".to_string()])
574 );
575 }
576
577 #[test]
578 fn test_resolve_inheritance_with_empty_network_name() {
579 let mut networks = HashMap::new();
580 let parent_config = create_evm_network("parent");
581 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
582
583 let lookup_fn = |name: &str| networks.get(name);
584 let resolver = InheritanceResolver::new(&lookup_fn);
585
586 let child_config = create_evm_network_with_parent("child", "parent");
587
588 let result = resolver.resolve_evm_inheritance(&child_config, "", "parent");
590 assert!(result.is_ok());
591
592 let resolved = result.unwrap();
594 assert_eq!(resolved.common.network, "child"); }
596
597 #[test]
598 fn test_resolve_inheritance_with_empty_parent_name() {
599 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
600 let lookup_fn = |name: &str| networks.get(name);
601 let resolver = InheritanceResolver::new(&lookup_fn);
602
603 let child_config = create_evm_network_with_parent("child", "");
604
605 let result = resolver.resolve_evm_inheritance(&child_config, "child", "");
607 assert!(result.is_err());
608
609 assert!(matches!(
610 result.unwrap_err(),
611 ConfigFileError::InvalidReference(_)
612 ));
613 }
614
615 #[test]
616 fn test_all_network_types_coverage() {
617 let mut networks = HashMap::new();
618
619 let evm_parent = create_evm_network("evm-parent");
621 let solana_parent = create_solana_network("solana-parent");
622 let stellar_parent = create_stellar_network("stellar-parent");
623
624 networks.insert("evm-parent".to_string(), NetworkFileConfig::Evm(evm_parent));
625 networks.insert(
626 "solana-parent".to_string(),
627 NetworkFileConfig::Solana(solana_parent),
628 );
629 networks.insert(
630 "stellar-parent".to_string(),
631 NetworkFileConfig::Stellar(stellar_parent),
632 );
633
634 let lookup_fn = |name: &str| networks.get(name);
635 let resolver = InheritanceResolver::new(&lookup_fn);
636
637 let evm_child = create_evm_network_with_parent("evm-child", "evm-parent");
639 let evm_result = resolver.resolve_evm_inheritance(&evm_child, "evm-child", "evm-parent");
640 assert!(evm_result.is_ok());
641
642 let solana_child = create_solana_network_with_parent("solana-child", "solana-parent");
644 let solana_result =
645 resolver.resolve_solana_inheritance(&solana_child, "solana-child", "solana-parent");
646 assert!(solana_result.is_ok());
647
648 let stellar_child = create_stellar_network_with_parent("stellar-child", "stellar-parent");
650 let stellar_result =
651 resolver.resolve_stellar_inheritance(&stellar_child, "stellar-child", "stellar-parent");
652 assert!(stellar_result.is_ok());
653 }
654
655 #[test]
656 fn test_inheritance_with_complex_merging() {
657 let mut networks = HashMap::new();
658
659 let parent_config = EvmNetworkConfig {
661 common: NetworkConfigCommon {
662 network: "parent".to_string(),
663 from: None,
664 rpc_urls: Some(vec![
665 "https://parent-rpc1.example.com".to_string(),
666 "https://parent-rpc2.example.com".to_string(),
667 ]),
668 explorer_urls: Some(vec![
669 "https://parent-explorer1.example.com".to_string(),
670 "https://parent-explorer2.example.com".to_string(),
671 ]),
672 average_blocktime_ms: Some(12000),
673 is_testnet: Some(true),
674 tags: Some(vec!["parent-tag1".to_string(), "parent-tag2".to_string()]),
675 },
676 chain_id: Some(1),
677 required_confirmations: Some(1),
678 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
679 symbol: Some("ETH".to_string()),
680 gas_price_cache: None,
681 };
682 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
683
684 let lookup_fn = |name: &str| networks.get(name);
685 let resolver = InheritanceResolver::new(&lookup_fn);
686
687 let child_config = EvmNetworkConfig {
689 common: NetworkConfigCommon {
690 network: "child".to_string(),
691 from: Some("parent".to_string()),
692 rpc_urls: Some(vec!["https://child-rpc.example.com".to_string()]), explorer_urls: Some(vec!["https://child-explorer.example.com".to_string()]), average_blocktime_ms: None, is_testnet: Some(false), tags: Some(vec!["child-tag".to_string()]), },
698 chain_id: Some(42), required_confirmations: None, features: Some(vec!["berlin".to_string()]), symbol: None, gas_price_cache: None,
703 };
704
705 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
706 assert!(result.is_ok());
707
708 let resolved = result.unwrap();
709 assert_eq!(resolved.common.network, "child");
710 assert_eq!(
711 resolved.common.rpc_urls,
712 Some(vec!["https://child-rpc.example.com".to_string()])
713 ); assert_eq!(
715 resolved.common.explorer_urls,
716 Some(vec!["https://child-explorer.example.com".to_string()])
717 ); assert_eq!(resolved.common.average_blocktime_ms, Some(12000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(resolved.chain_id, Some(42)); assert_eq!(resolved.required_confirmations, Some(1)); assert_eq!(resolved.symbol, Some("ETH".to_string())); }
724}