openzeppelin_relayer/config/config_file/network/
inheritance.rs

1//! Network Configuration Inheritance Resolution
2//!
3//! This module provides inheritance resolution for network configurations, enabling
4//! hierarchical configuration management where child networks inherit and override
5//! properties from parent networks.
6//!
7//! ## Key Features
8//!
9//! - **Type safety**: Ensures inheritance only between compatible network types
10//! - **Recursive resolution**: Supports multi-level inheritance chains
11//! - **Smart merging**: Child values override parents, collections merge intelligently
12//! - **Error handling**: Detailed errors for circular references and type mismatches
13//!
14//! ## Resolution Process
15//!
16//! 1. **Validation**: Verify parent exists and types are compatible
17//! 2. **Recursive resolution**: Resolve parent's inheritance chain first
18//! 3. **Merging**: Combine child with resolved parent configuration
19
20use super::{
21    ConfigFileNetworkType, EvmNetworkConfig, NetworkFileConfig, SolanaNetworkConfig,
22    StellarNetworkConfig,
23};
24use crate::config::ConfigFileError;
25
26/// Resolves network configuration inheritance by recursively merging child configurations with their parents.
27pub struct InheritanceResolver<'a> {
28    /// Function to lookup network configurations by name
29    network_lookup: &'a dyn Fn(&str) -> Option<&'a NetworkFileConfig>,
30}
31
32/// Macro to generate inheritance resolution methods for different network types.
33///
34/// Generates: resolve_evm_inheritance, resolve_solana_inheritance, resolve_stellar_inheritance
35/// This eliminates code duplication while maintaining type safety across all network types.
36macro_rules! impl_inheritance_resolver {
37    ($method_name:ident, $config_type:ty, $network_type:ident, $variant:ident, $type_name:expr) => {
38        /// Resolves inheritance for network configurations by recursively merging with parent configurations.
39        ///
40        /// # Arguments
41        /// * `config` - The child network configuration to resolve inheritance for
42        /// * `network_name` - The name of the child network (used for error reporting)
43        /// * `parent_name` - The name of the parent network to inherit from
44        ///
45        /// # Returns
46        /// Configuration with all inheritance applied, or an error if resolution fails
47        pub fn $method_name(&self, config: &$config_type, network_name: &str, parent_name: &str) -> Result<$config_type, ConfigFileError> {
48            // Get the parent network
49            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            // Verify parent is the same type
57            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            // Extract the parent configuration
65            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            // Recursively resolve parent inheritance first
71            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            // Merge child with resolved parent
79            Ok(config.merge_with_parent(&resolved_parent))
80        }
81    };
82}
83
84impl<'a> InheritanceResolver<'a> {
85    /// Creates a new inheritance resolver.
86    ///
87    /// # Arguments
88    /// * `network_lookup` - Function to lookup network configurations by name
89    ///
90    /// # Returns
91    /// A new `InheritanceResolver` instance
92    pub fn new(network_lookup: &'a dyn Fn(&str) -> Option<&'a NetworkFileConfig>) -> Self {
93        Self { network_lookup }
94    }
95
96    // Generate the three inheritance resolution methods using the macro
97    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        // Test that the resolver was created successfully
128        // We can't directly test the function pointer, but we can test that it works
129        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        // Create parent network
137        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        // Create child network that inherits from parent
147        let child_config = EvmNetworkConfig {
148            common: NetworkConfigCommon {
149                network: "child".to_string(),
150                from: Some("parent".to_string()),
151                rpc_urls: None,                    // Will inherit from parent
152                explorer_urls: None,               // Will inherit from parent
153                average_blocktime_ms: Some(15000), // Override parent value
154                is_testnet: Some(false),           // Override parent value
155                tags: None,
156            },
157            chain_id: None,                  // Will inherit from parent
158            required_confirmations: Some(2), // Override parent value
159            features: None,
160            symbol: None, // Will inherit from parent
161        };
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); // Inherited
169        assert_eq!(
170            resolved.common.explorer_urls,
171            parent_config.common.explorer_urls
172        ); // Inherited
173        assert_eq!(resolved.common.average_blocktime_ms, Some(15000)); // Overridden
174        assert_eq!(resolved.common.is_testnet, Some(false)); // Overridden
175        assert_eq!(resolved.chain_id, parent_config.chain_id); // Inherited
176        assert_eq!(resolved.required_confirmations, Some(2)); // Overridden
177        assert_eq!(resolved.symbol, parent_config.symbol); // Inherited
178    }
179
180    #[test]
181    fn test_resolve_evm_inheritance_multi_level() {
182        let mut networks = HashMap::new();
183
184        // Create grandparent network
185        let grandparent_config = create_evm_network("grandparent");
186        networks.insert(
187            "grandparent".to_string(),
188            NetworkFileConfig::Evm(grandparent_config.clone()),
189        );
190
191        // Create parent network that inherits from grandparent
192        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), // Override grandparent
199                is_testnet: None,
200                tags: None,
201            },
202            chain_id: None,
203            required_confirmations: Some(3), // Override grandparent
204            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        // Create child network that inherits from parent
216        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), // Override
224                tags: None,
225            },
226            chain_id: Some(42), // Override
227            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); // From grandparent
238        assert_eq!(
239            resolved.common.explorer_urls,
240            grandparent_config.common.explorer_urls
241        ); // From grandparent
242        assert_eq!(resolved.common.average_blocktime_ms, Some(10000)); // From parent
243        assert_eq!(resolved.common.is_testnet, Some(false)); // From child
244        assert_eq!(resolved.chain_id, Some(42)); // From child
245        assert_eq!(resolved.required_confirmations, Some(3)); // From parent
246        assert_eq!(resolved.symbol, grandparent_config.symbol); // From grandparent
247    }
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        // Create a Solana parent network
271        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        // Create parent network with no inheritance
296        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        // Should merge child with parent (parent has no inheritance)
312        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    // Solana Inheritance Tests
320    #[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,                  // Will inherit
338                explorer_urls: None,             // Will inherit
339                average_blocktime_ms: Some(500), // Override
340                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); // Inherited
351        assert_eq!(
352            resolved.common.explorer_urls,
353            parent_config.common.explorer_urls
354        ); // Inherited
355        assert_eq!(resolved.common.average_blocktime_ms, Some(500)); // Overridden
356    }
357
358    #[test]
359    fn test_resolve_solana_inheritance_type_mismatch() {
360        let mut networks = HashMap::new();
361
362        // Create an EVM parent network
363        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,                   // Will inherit
415                explorer_urls: None,              // Will inherit
416                average_blocktime_ms: Some(6000), // Override
417                is_testnet: None,
418                tags: None,
419            },
420            passphrase: None, // Will inherit from parent
421        };
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); // Inherited
429        assert_eq!(resolved.common.average_blocktime_ms, Some(6000)); // Overridden
430        assert_eq!(resolved.passphrase, parent_config.passphrase); // Inherited
431    }
432
433    #[test]
434    fn test_resolve_stellar_inheritance_type_mismatch() {
435        let mut networks = HashMap::new();
436
437        // Create a Solana parent network
438        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        // Create a 4-level inheritance chain: great-grandparent -> grandparent -> parent -> child
480        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        ); // From child
551        assert_eq!(
552            resolved.common.explorer_urls,
553            Some(vec!["https://custom-explorer.example.com".to_string()])
554        ); // From child
555        assert_eq!(resolved.common.average_blocktime_ms, Some(11000)); // From grandparent
556        assert_eq!(resolved.common.is_testnet, Some(false)); // From parent
557        assert_eq!(
558            resolved.common.tags,
559            Some(vec!["test".to_string(), "production".to_string()])
560        ); // Merged from great-grandparent and parent
561        assert_eq!(resolved.chain_id, Some(100)); // From parent
562        assert_eq!(resolved.required_confirmations, Some(5)); // From child
563        assert_eq!(resolved.symbol, Some("CUSTOM".to_string())); // From child
564        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        // Test with empty network name - this should succeed since parent exists
582        let result = resolver.resolve_evm_inheritance(&child_config, "", "parent");
583        assert!(result.is_ok());
584
585        // The resolved config should have the child's network name (empty string in this case)
586        let resolved = result.unwrap();
587        assert_eq!(resolved.common.network, "child"); // Network name comes from the child config, not the parameter
588    }
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        // Test with empty parent name
599        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        // Create parent networks for all types
613        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        // Test EVM inheritance
631        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        // Test Solana inheritance
636        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        // Test Stellar inheritance
642        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        // Create parent with comprehensive configuration
653        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        // Create child that partially overrides parent
680        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()]), // Override
685                explorer_urls: Some(vec!["https://child-explorer.example.com".to_string()]), // Override
686                average_blocktime_ms: None,                // Inherit
687                is_testnet: Some(false),                   // Override
688                tags: Some(vec!["child-tag".to_string()]), // Override (merge behavior depends on implementation)
689            },
690            chain_id: Some(42),                         // Override
691            required_confirmations: None,               // Inherit
692            features: Some(vec!["berlin".to_string()]), // Override (merge behavior depends on implementation)
693            symbol: None,                               // Inherit
694        };
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        ); // Child override
705        assert_eq!(
706            resolved.common.explorer_urls,
707            Some(vec!["https://child-explorer.example.com".to_string()])
708        ); // Child override
709        assert_eq!(resolved.common.average_blocktime_ms, Some(12000)); // Inherited from parent
710        assert_eq!(resolved.common.is_testnet, Some(false)); // Child override
711        assert_eq!(resolved.chain_id, Some(42)); // Child override
712        assert_eq!(resolved.required_confirmations, Some(1)); // Inherited from parent
713        assert_eq!(resolved.symbol, Some("ETH".to_string())); // Inherited from parent
714    }
715}