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, };
162
163 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
164 assert!(result.is_ok());
165
166 let resolved = result.unwrap();
167 assert_eq!(resolved.common.network, "child");
168 assert_eq!(resolved.common.rpc_urls, parent_config.common.rpc_urls); assert_eq!(
170 resolved.common.explorer_urls,
171 parent_config.common.explorer_urls
172 ); 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); }
179
180 #[test]
181 fn test_resolve_evm_inheritance_multi_level() {
182 let mut networks = HashMap::new();
183
184 let grandparent_config = create_evm_network("grandparent");
186 networks.insert(
187 "grandparent".to_string(),
188 NetworkFileConfig::Evm(grandparent_config.clone()),
189 );
190
191 let parent_config = EvmNetworkConfig {
193 common: NetworkConfigCommon {
194 network: "parent".to_string(),
195 from: Some("grandparent".to_string()),
196 rpc_urls: None,
197 explorer_urls: None,
198 average_blocktime_ms: Some(10000), is_testnet: None,
200 tags: None,
201 },
202 chain_id: None,
203 required_confirmations: Some(3), features: None,
205 symbol: None,
206 };
207 networks.insert(
208 "parent".to_string(),
209 NetworkFileConfig::Evm(parent_config.clone()),
210 );
211
212 let lookup_fn = |name: &str| networks.get(name);
213 let resolver = InheritanceResolver::new(&lookup_fn);
214
215 let child_config = EvmNetworkConfig {
217 common: NetworkConfigCommon {
218 network: "child".to_string(),
219 from: Some("parent".to_string()),
220 rpc_urls: None,
221 explorer_urls: None,
222 average_blocktime_ms: None,
223 is_testnet: Some(false), tags: None,
225 },
226 chain_id: Some(42), required_confirmations: None,
228 features: None,
229 symbol: None,
230 };
231
232 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
233 assert!(result.is_ok());
234
235 let resolved = result.unwrap();
236 assert_eq!(resolved.common.network, "child");
237 assert_eq!(resolved.common.rpc_urls, grandparent_config.common.rpc_urls); assert_eq!(
239 resolved.common.explorer_urls,
240 grandparent_config.common.explorer_urls
241 ); 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); }
248
249 #[test]
250 fn test_resolve_evm_inheritance_nonexistent_parent() {
251 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
252 let lookup_fn = |name: &str| networks.get(name);
253 let resolver = InheritanceResolver::new(&lookup_fn);
254
255 let child_config = create_evm_network_with_parent("child", "nonexistent");
256
257 let result = resolver.resolve_evm_inheritance(&child_config, "child", "nonexistent");
258 assert!(result.is_err());
259
260 assert!(matches!(
261 result.unwrap_err(),
262 ConfigFileError::InvalidReference(_)
263 ));
264 }
265
266 #[test]
267 fn test_resolve_evm_inheritance_type_mismatch() {
268 let mut networks = HashMap::new();
269
270 let parent_config = create_solana_network("parent");
272 networks.insert(
273 "parent".to_string(),
274 NetworkFileConfig::Solana(parent_config),
275 );
276
277 let lookup_fn = |name: &str| networks.get(name);
278 let resolver = InheritanceResolver::new(&lookup_fn);
279
280 let child_config = create_evm_network_with_parent("child", "parent");
281
282 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
283 assert!(result.is_err());
284
285 assert!(matches!(
286 result.unwrap_err(),
287 ConfigFileError::IncompatibleInheritanceType(_)
288 ));
289 }
290
291 #[test]
292 fn test_resolve_evm_inheritance_no_inheritance() {
293 let mut networks = HashMap::new();
294
295 let parent_config = create_evm_network("parent");
297 networks.insert(
298 "parent".to_string(),
299 NetworkFileConfig::Evm(parent_config.clone()),
300 );
301
302 let lookup_fn = |name: &str| networks.get(name);
303 let resolver = InheritanceResolver::new(&lookup_fn);
304
305 let child_config = create_evm_network_with_parent("child", "parent");
306
307 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
308 assert!(result.is_ok());
309
310 let resolved = result.unwrap();
311 assert_eq!(resolved.common.network, "child");
313 assert_eq!(
314 resolved.chain_id,
315 child_config.chain_id.or(parent_config.chain_id)
316 );
317 }
318
319 #[test]
321 fn test_resolve_solana_inheritance_simple_success() {
322 let mut networks = HashMap::new();
323
324 let parent_config = create_solana_network("parent");
325 networks.insert(
326 "parent".to_string(),
327 NetworkFileConfig::Solana(parent_config.clone()),
328 );
329
330 let lookup_fn = |name: &str| networks.get(name);
331 let resolver = InheritanceResolver::new(&lookup_fn);
332
333 let child_config = SolanaNetworkConfig {
334 common: NetworkConfigCommon {
335 network: "child".to_string(),
336 from: Some("parent".to_string()),
337 rpc_urls: None, explorer_urls: None, average_blocktime_ms: Some(500), is_testnet: None,
341 tags: None,
342 },
343 };
344
345 let result = resolver.resolve_solana_inheritance(&child_config, "child", "parent");
346 assert!(result.is_ok());
347
348 let resolved = result.unwrap();
349 assert_eq!(resolved.common.network, "child");
350 assert_eq!(resolved.common.rpc_urls, parent_config.common.rpc_urls); assert_eq!(
352 resolved.common.explorer_urls,
353 parent_config.common.explorer_urls
354 ); assert_eq!(resolved.common.average_blocktime_ms, Some(500)); }
357
358 #[test]
359 fn test_resolve_solana_inheritance_type_mismatch() {
360 let mut networks = HashMap::new();
361
362 let parent_config = create_evm_network("parent");
364 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
365
366 let lookup_fn = |name: &str| networks.get(name);
367 let resolver = InheritanceResolver::new(&lookup_fn);
368
369 let child_config = create_solana_network_with_parent("child", "parent");
370
371 let result = resolver.resolve_solana_inheritance(&child_config, "child", "parent");
372 assert!(result.is_err());
373
374 assert!(matches!(
375 result.unwrap_err(),
376 ConfigFileError::IncompatibleInheritanceType(_)
377 ));
378 }
379
380 #[test]
381 fn test_resolve_solana_inheritance_nonexistent_parent() {
382 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
383 let lookup_fn = |name: &str| networks.get(name);
384 let resolver = InheritanceResolver::new(&lookup_fn);
385
386 let child_config = create_solana_network_with_parent("child", "nonexistent");
387
388 let result = resolver.resolve_solana_inheritance(&child_config, "child", "nonexistent");
389 assert!(result.is_err());
390
391 assert!(matches!(
392 result.unwrap_err(),
393 ConfigFileError::InvalidReference(_)
394 ));
395 }
396
397 #[test]
398 fn test_resolve_stellar_inheritance_simple_success() {
399 let mut networks = HashMap::new();
400
401 let parent_config = create_stellar_network("parent");
402 networks.insert(
403 "parent".to_string(),
404 NetworkFileConfig::Stellar(parent_config.clone()),
405 );
406
407 let lookup_fn = |name: &str| networks.get(name);
408 let resolver = InheritanceResolver::new(&lookup_fn);
409
410 let child_config = StellarNetworkConfig {
411 common: NetworkConfigCommon {
412 network: "child".to_string(),
413 from: Some("parent".to_string()),
414 rpc_urls: None, explorer_urls: None, average_blocktime_ms: Some(6000), is_testnet: None,
418 tags: None,
419 },
420 passphrase: None, };
422
423 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
424 assert!(result.is_ok());
425
426 let resolved = result.unwrap();
427 assert_eq!(resolved.common.network, "child");
428 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); }
432
433 #[test]
434 fn test_resolve_stellar_inheritance_type_mismatch() {
435 let mut networks = HashMap::new();
436
437 let parent_config = create_solana_network("parent");
439 networks.insert(
440 "parent".to_string(),
441 NetworkFileConfig::Solana(parent_config),
442 );
443
444 let lookup_fn = |name: &str| networks.get(name);
445 let resolver = InheritanceResolver::new(&lookup_fn);
446
447 let child_config = create_stellar_network_with_parent("child", "parent");
448
449 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "parent");
450 assert!(result.is_err());
451
452 assert!(matches!(
453 result.unwrap_err(),
454 ConfigFileError::IncompatibleInheritanceType(_)
455 ));
456 }
457
458 #[test]
459 fn test_resolve_stellar_inheritance_nonexistent_parent() {
460 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
461 let lookup_fn = |name: &str| networks.get(name);
462 let resolver = InheritanceResolver::new(&lookup_fn);
463
464 let child_config = create_stellar_network_with_parent("child", "nonexistent");
465
466 let result = resolver.resolve_stellar_inheritance(&child_config, "child", "nonexistent");
467 assert!(result.is_err());
468
469 assert!(matches!(
470 result.unwrap_err(),
471 ConfigFileError::InvalidReference(_)
472 ));
473 }
474
475 #[test]
476 fn test_resolve_inheritance_deep_chain() {
477 let mut networks = HashMap::new();
478
479 let great_grandparent_config = create_evm_network("great-grandparent");
481 networks.insert(
482 "great-grandparent".to_string(),
483 NetworkFileConfig::Evm(great_grandparent_config.clone()),
484 );
485
486 let grandparent_config = EvmNetworkConfig {
487 common: NetworkConfigCommon {
488 network: "grandparent".to_string(),
489 from: Some("great-grandparent".to_string()),
490 rpc_urls: None,
491 explorer_urls: None,
492 average_blocktime_ms: Some(11000),
493 is_testnet: None,
494 tags: None,
495 },
496 chain_id: None,
497 required_confirmations: None,
498 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
499 symbol: None,
500 };
501 networks.insert(
502 "grandparent".to_string(),
503 NetworkFileConfig::Evm(grandparent_config),
504 );
505
506 let parent_config = EvmNetworkConfig {
507 common: NetworkConfigCommon {
508 network: "parent".to_string(),
509 from: Some("grandparent".to_string()),
510 rpc_urls: None,
511 explorer_urls: None,
512 average_blocktime_ms: None,
513 is_testnet: Some(false),
514 tags: Some(vec!["production".to_string()]),
515 },
516 chain_id: Some(100),
517 required_confirmations: None,
518 features: None,
519 symbol: None,
520 };
521 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
522
523 let lookup_fn = |name: &str| networks.get(name);
524 let resolver = InheritanceResolver::new(&lookup_fn);
525
526 let child_config = EvmNetworkConfig {
527 common: NetworkConfigCommon {
528 network: "child".to_string(),
529 from: Some("parent".to_string()),
530 rpc_urls: Some(vec!["https://custom-rpc.example.com".to_string()]),
531 explorer_urls: Some(vec!["https://custom-explorer.example.com".to_string()]),
532 average_blocktime_ms: None,
533 is_testnet: None,
534 tags: None,
535 },
536 chain_id: None,
537 required_confirmations: Some(5),
538 features: None,
539 symbol: Some("CUSTOM".to_string()),
540 };
541
542 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
543 assert!(result.is_ok());
544
545 let resolved = result.unwrap();
546 assert_eq!(resolved.common.network, "child");
547 assert_eq!(
548 resolved.common.rpc_urls,
549 Some(vec!["https://custom-rpc.example.com".to_string()])
550 ); assert_eq!(
552 resolved.common.explorer_urls,
553 Some(vec!["https://custom-explorer.example.com".to_string()])
554 ); assert_eq!(resolved.common.average_blocktime_ms, Some(11000)); assert_eq!(resolved.common.is_testnet, Some(false)); assert_eq!(
558 resolved.common.tags,
559 Some(vec!["test".to_string(), "production".to_string()])
560 ); assert_eq!(resolved.chain_id, Some(100)); assert_eq!(resolved.required_confirmations, Some(5)); assert_eq!(resolved.symbol, Some("CUSTOM".to_string())); assert_eq!(
565 resolved.features,
566 Some(vec!["eip1559".to_string(), "london".to_string()])
567 );
568 }
569
570 #[test]
571 fn test_resolve_inheritance_with_empty_network_name() {
572 let mut networks = HashMap::new();
573 let parent_config = create_evm_network("parent");
574 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
575
576 let lookup_fn = |name: &str| networks.get(name);
577 let resolver = InheritanceResolver::new(&lookup_fn);
578
579 let child_config = create_evm_network_with_parent("child", "parent");
580
581 let result = resolver.resolve_evm_inheritance(&child_config, "", "parent");
583 assert!(result.is_ok());
584
585 let resolved = result.unwrap();
587 assert_eq!(resolved.common.network, "child"); }
589
590 #[test]
591 fn test_resolve_inheritance_with_empty_parent_name() {
592 let networks: HashMap<String, NetworkFileConfig> = HashMap::new();
593 let lookup_fn = |name: &str| networks.get(name);
594 let resolver = InheritanceResolver::new(&lookup_fn);
595
596 let child_config = create_evm_network_with_parent("child", "");
597
598 let result = resolver.resolve_evm_inheritance(&child_config, "child", "");
600 assert!(result.is_err());
601
602 assert!(matches!(
603 result.unwrap_err(),
604 ConfigFileError::InvalidReference(_)
605 ));
606 }
607
608 #[test]
609 fn test_all_network_types_coverage() {
610 let mut networks = HashMap::new();
611
612 let evm_parent = create_evm_network("evm-parent");
614 let solana_parent = create_solana_network("solana-parent");
615 let stellar_parent = create_stellar_network("stellar-parent");
616
617 networks.insert("evm-parent".to_string(), NetworkFileConfig::Evm(evm_parent));
618 networks.insert(
619 "solana-parent".to_string(),
620 NetworkFileConfig::Solana(solana_parent),
621 );
622 networks.insert(
623 "stellar-parent".to_string(),
624 NetworkFileConfig::Stellar(stellar_parent),
625 );
626
627 let lookup_fn = |name: &str| networks.get(name);
628 let resolver = InheritanceResolver::new(&lookup_fn);
629
630 let evm_child = create_evm_network_with_parent("evm-child", "evm-parent");
632 let evm_result = resolver.resolve_evm_inheritance(&evm_child, "evm-child", "evm-parent");
633 assert!(evm_result.is_ok());
634
635 let solana_child = create_solana_network_with_parent("solana-child", "solana-parent");
637 let solana_result =
638 resolver.resolve_solana_inheritance(&solana_child, "solana-child", "solana-parent");
639 assert!(solana_result.is_ok());
640
641 let stellar_child = create_stellar_network_with_parent("stellar-child", "stellar-parent");
643 let stellar_result =
644 resolver.resolve_stellar_inheritance(&stellar_child, "stellar-child", "stellar-parent");
645 assert!(stellar_result.is_ok());
646 }
647
648 #[test]
649 fn test_inheritance_with_complex_merging() {
650 let mut networks = HashMap::new();
651
652 let parent_config = EvmNetworkConfig {
654 common: NetworkConfigCommon {
655 network: "parent".to_string(),
656 from: None,
657 rpc_urls: Some(vec![
658 "https://parent-rpc1.example.com".to_string(),
659 "https://parent-rpc2.example.com".to_string(),
660 ]),
661 explorer_urls: Some(vec![
662 "https://parent-explorer1.example.com".to_string(),
663 "https://parent-explorer2.example.com".to_string(),
664 ]),
665 average_blocktime_ms: Some(12000),
666 is_testnet: Some(true),
667 tags: Some(vec!["parent-tag1".to_string(), "parent-tag2".to_string()]),
668 },
669 chain_id: Some(1),
670 required_confirmations: Some(1),
671 features: Some(vec!["eip1559".to_string(), "london".to_string()]),
672 symbol: Some("ETH".to_string()),
673 };
674 networks.insert("parent".to_string(), NetworkFileConfig::Evm(parent_config));
675
676 let lookup_fn = |name: &str| networks.get(name);
677 let resolver = InheritanceResolver::new(&lookup_fn);
678
679 let child_config = EvmNetworkConfig {
681 common: NetworkConfigCommon {
682 network: "child".to_string(),
683 from: Some("parent".to_string()),
684 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()]), },
690 chain_id: Some(42), required_confirmations: None, features: Some(vec!["berlin".to_string()]), symbol: None, };
695
696 let result = resolver.resolve_evm_inheritance(&child_config, "child", "parent");
697 assert!(result.is_ok());
698
699 let resolved = result.unwrap();
700 assert_eq!(resolved.common.network, "child");
701 assert_eq!(
702 resolved.common.rpc_urls,
703 Some(vec!["https://child-rpc.example.com".to_string()])
704 ); assert_eq!(
706 resolved.common.explorer_urls,
707 Some(vec!["https://child-explorer.example.com".to_string()])
708 ); 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())); }
715}