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, };
425
426 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
427 assert!(result.is_ok());
428
429 let resolved = result.unwrap();
430 assert_eq!(resolved.common.network, "child");
431 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); }
435
436 #[test]
437 fn test_resolve_stellar_inheritance_type_mismatch() {
438 let mut networks = HashMap::new();
439
440 let parent_config = create_solana_network("parent");
442 networks.insert(
443 "parent".to_string(),
444 NetworkFileConfig::Solana(parent_config),
445 );
446
447 let lookup_fn = |name: &str| networks.get(name);
448 let resolver = InheritanceResolver::new(&lookup_fn);
449
450 let child_config = create_stellar_network_with_parent("child", "parent");
451
452 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
453 assert!(result.is_err());
454
455 assert!(matches!(
456 result.unwrap_err(),
457 ConfigFileError::IncompatibleInheritanceType(_)
458 ));
459 }
460
461 #[test]
462 fn test_resolve_stellar_inheritance_nonexistent_parent() {
463 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
464 let lookup_fn = |name: &str| networks.get(name);
465 let resolver = InheritanceResolver::new(&lookup_fn);
466
467 let child_config = create_stellar_network_with_parent("child", "nonexistent");
468
469 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "nonexistent");
470 assert!(result.is_err());
471
472 assert!(matches!(
473 result.unwrap_err(),
474 ConfigFileError::InvalidReference(_)
475 ));
476 }
477
478 #[test]
479 fn test_resolve_inheritance_deep_chain() {
480 let mut networks = HashMap::new();
481
482 let great_grandparent_config = create_evm_network("great-grandparent");
484 networks.insert(
485 "great-grandparent".to_string(),
486 NetworkFileConfig::Evm(great_grandparent_config.clone()),
487 );
488
489 let grandparent_config = EvmNetworkConfig {
490 common: NetworkConfigCommon {
491 network: "grandparent".to_string(),
492 from: Some("great-grandparent".to_string()),
493 rpc_urls: None,
494 explorer_urls: None,
495 average_blocktime_ms: Some(11000),
496 is_testnet: None,
497 tags: None,
498 },
499 chain_id: None,
500 required_confirmations: None,
501 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
502 symbol: None,
503 gas_price_cache: None,
504 };
505 networks.insert(
506 "grandparent".to_string(),
507 NetworkFileConfig::Evm(grandparent_config),
508 );
509
510 let parent_config = EvmNetworkConfig {
511 common: NetworkConfigCommon {
512 network: "parent".to_string(),
513 from: Some("grandparent".to_string()),
514 rpc_urls: None,
515 explorer_urls: None,
516 average_blocktime_ms: None,
517 is_testnet: Some(false),
518 tags: Some(vec!["production".to_string()]),
519 },
520 chain_id: Some(100),
521 required_confirmations: None,
522 features: None,
523 symbol: None,
524 gas_price_cache: None,
525 };
526 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
527
528 let lookup_fn = |name: &str| networks.get(name);
529 let resolver = InheritanceResolver::new(&lookup_fn);
530
531 let child_config = EvmNetworkConfig {
532 common: NetworkConfigCommon {
533 network: "child".to_string(),
534 from: Some("parent".to_string()),
535 rpc_urls: Some(vec!["https://custom-rpc.example.com".to_string()]),
536 explorer_urls: Some(vec!["https://custom-explorer.example.com".to_string()]),
537 average_blocktime_ms: None,
538 is_testnet: None,
539 tags: None,
540 },
541 chain_id: None,
542 required_confirmations: Some(5),
543 features: None,
544 symbol: Some("CUSTOM".to_string()),
545 gas_price_cache: None,
546 };
547
548 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
549 assert!(result.is_ok());
550
551 let resolved = result.unwrap();
552 assert_eq!(resolved.common.network, "child");
553 assert_eq!(
554 resolved.common.rpc_urls,
555 Some(vec!["https://custom-rpc.example.com".to_string()])
556 ); assert_eq!(
558 resolved.common.explorer_urls,
559 Some(vec!["https://custom-explorer.example.com".to_string()])
560 ); assert_eq!(resolved.common.average_blocktime_ms, Some(11000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(
564 resolved.common.tags,
565 Some(vec!["test".to_string(), "production".to_string()])
566 ); assert_eq!(resolved.chain_id, Some(100)); assert_eq!(resolved.required_confirmations, Some(5)); assert_eq!(resolved.symbol, Some("CUSTOM".to_string())); assert_eq!(
571 resolved.features,
572 Some(vec!["eip1559".to_string(), "london".to_string()])
573 );
574 }
575
576 #[test]
577 fn test_resolve_inheritance_with_empty_network_name() {
578 let mut networks = HashMap::new();
579 let parent_config = create_evm_network("parent");
580 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
581
582 let lookup_fn = |name: &str| networks.get(name);
583 let resolver = InheritanceResolver::new(&lookup_fn);
584
585 let child_config = create_evm_network_with_parent("child", "parent");
586
587 let result = resolver.resolve_evm_inheritance(&child_config, "", "parent");
589 assert!(result.is_ok());
590
591 let resolved = result.unwrap();
593 assert_eq!(resolved.common.network, "child"); }
595
596 #[test]
597 fn test_resolve_inheritance_with_empty_parent_name() {
598 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
599 let lookup_fn = |name: &str| networks.get(name);
600 let resolver = InheritanceResolver::new(&lookup_fn);
601
602 let child_config = create_evm_network_with_parent("child", "");
603
604 let result = resolver.resolve_evm_inheritance(&child_config, "child", "");
606 assert!(result.is_err());
607
608 assert!(matches!(
609 result.unwrap_err(),
610 ConfigFileError::InvalidReference(_)
611 ));
612 }
613
614 #[test]
615 fn test_all_network_types_coverage() {
616 let mut networks = HashMap::new();
617
618 let evm_parent = create_evm_network("evm-parent");
620 let solana_parent = create_solana_network("solana-parent");
621 let stellar_parent = create_stellar_network("stellar-parent");
622
623 networks.insert("evm-parent".to_string(), NetworkFileConfig::Evm(evm_parent));
624 networks.insert(
625 "solana-parent".to_string(),
626 NetworkFileConfig::Solana(solana_parent),
627 );
628 networks.insert(
629 "stellar-parent".to_string(),
630 NetworkFileConfig::Stellar(stellar_parent),
631 );
632
633 let lookup_fn = |name: &str| networks.get(name);
634 let resolver = InheritanceResolver::new(&lookup_fn);
635
636 let evm_child = create_evm_network_with_parent("evm-child", "evm-parent");
638 let evm_result = resolver.resolve_evm_inheritance(&evm_child, "evm-child", "evm-parent");
639 assert!(evm_result.is_ok());
640
641 let solana_child = create_solana_network_with_parent("solana-child", "solana-parent");
643 let solana_result =
644 resolver.resolve_solana_inheritance(&solana_child, "solana-child", "solana-parent");
645 assert!(solana_result.is_ok());
646
647 let stellar_child = create_stellar_network_with_parent("stellar-child", "stellar-parent");
649 let stellar_result =
650 resolver.resolve_stellar_inheritance(&stellar_child, "stellar-child", "stellar-parent");
651 assert!(stellar_result.is_ok());
652 }
653
654 #[test]
655 fn test_inheritance_with_complex_merging() {
656 let mut networks = HashMap::new();
657
658 let parent_config = EvmNetworkConfig {
660 common: NetworkConfigCommon {
661 network: "parent".to_string(),
662 from: None,
663 rpc_urls: Some(vec![
664 "https://parent-rpc1.example.com".to_string(),
665 "https://parent-rpc2.example.com".to_string(),
666 ]),
667 explorer_urls: Some(vec![
668 "https://parent-explorer1.example.com".to_string(),
669 "https://parent-explorer2.example.com".to_string(),
670 ]),
671 average_blocktime_ms: Some(12000),
672 is_testnet: Some(true),
673 tags: Some(vec!["parent-tag1".to_string(), "parent-tag2".to_string()]),
674 },
675 chain_id: Some(1),
676 required_confirmations: Some(1),
677 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
678 symbol: Some("ETH".to_string()),
679 gas_price_cache: None,
680 };
681 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
682
683 let lookup_fn = |name: &str| networks.get(name);
684 let resolver = InheritanceResolver::new(&lookup_fn);
685
686 let child_config = EvmNetworkConfig {
688 common: NetworkConfigCommon {
689 network: "child".to_string(),
690 from: Some("parent".to_string()),
691 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()]), },
697 chain_id: Some(42), required_confirmations: None, features: Some(vec!["berlin".to_string()]), symbol: None, gas_price_cache: None,
702 };
703
704 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
705 assert!(result.is_ok());
706
707 let resolved = result.unwrap();
708 assert_eq!(resolved.common.network, "child");
709 assert_eq!(
710 resolved.common.rpc_urls,
711 Some(vec!["https://child-rpc.example.com".to_string()])
712 ); assert_eq!(
714 resolved.common.explorer_urls,
715 Some(vec!["https://child-explorer.example.com".to_string()])
716 ); 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())); }
723}