1use crate::{
2 config::{
3 EvmNetworkConfig, NetworkConfigCommon, NetworkFileConfig, SolanaNetworkConfig,
4 StellarNetworkConfig,
5 },
6 models::NetworkType,
7};
8use eyre;
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
13pub enum NetworkConfigData {
14 Evm(EvmNetworkConfig),
16 Solana(SolanaNetworkConfig),
18 Stellar(StellarNetworkConfig),
20}
21
22impl NetworkConfigData {
23 pub fn common(&self) -> &NetworkConfigCommon {
25 match self {
26 NetworkConfigData::Evm(config) => &config.common,
27 NetworkConfigData::Solana(config) => &config.common,
28 NetworkConfigData::Stellar(config) => &config.common,
29 }
30 }
31
32 pub fn network_type(&self) -> NetworkType {
34 match self {
35 NetworkConfigData::Evm(_) => NetworkType::Evm,
36 NetworkConfigData::Solana(_) => NetworkType::Solana,
37 NetworkConfigData::Stellar(_) => NetworkType::Stellar,
38 }
39 }
40
41 pub fn network_name(&self) -> &str {
43 &self.common().network
44 }
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize)]
52pub struct NetworkRepoModel {
53 pub id: String,
55 pub name: String,
57 pub network_type: NetworkType,
59 pub config: NetworkConfigData,
61}
62
63impl NetworkRepoModel {
64 pub fn new_evm(config: EvmNetworkConfig) -> Self {
72 let name = config.common.network.clone();
73 let id = format!("evm:{name}").to_lowercase();
74 Self {
75 id,
76 name,
77 network_type: NetworkType::Evm,
78 config: NetworkConfigData::Evm(config),
79 }
80 }
81
82 pub fn new_solana(config: SolanaNetworkConfig) -> Self {
90 let name = config.common.network.clone();
91 let id = format!("solana:{name}").to_lowercase();
92 Self {
93 id,
94 name,
95 network_type: NetworkType::Solana,
96 config: NetworkConfigData::Solana(config),
97 }
98 }
99
100 pub fn new_stellar(config: StellarNetworkConfig) -> Self {
108 let name = config.common.network.clone();
109 let id = format!("stellar:{name}").to_lowercase();
110 Self {
111 id,
112 name,
113 network_type: NetworkType::Stellar,
114 config: NetworkConfigData::Stellar(config),
115 }
116 }
117
118 pub fn create_id(network_type: NetworkType, name: &str) -> String {
127 format!("{network_type:?}:{name}").to_lowercase()
128 }
129
130 pub fn common(&self) -> &NetworkConfigCommon {
132 self.config.common()
133 }
134
135 pub fn config(&self) -> &NetworkConfigData {
137 &self.config
138 }
139}
140
141impl TryFrom<NetworkFileConfig> for NetworkRepoModel {
142 type Error = eyre::Report;
143
144 fn try_from(network_config: NetworkFileConfig) -> Result<Self, Self::Error> {
152 match network_config {
153 NetworkFileConfig::Evm(evm_config) => Ok(Self::new_evm(evm_config)),
154 NetworkFileConfig::Solana(solana_config) => Ok(Self::new_solana(solana_config)),
155 NetworkFileConfig::Stellar(stellar_config) => Ok(Self::new_stellar(stellar_config)),
156 }
157 }
158}
159
160#[cfg(test)]
161mod tests {
162 use super::*;
163
164 fn create_evm_config(name: &str, chain_id: u64, symbol: &str) -> EvmNetworkConfig {
165 EvmNetworkConfig {
166 common: NetworkConfigCommon {
167 network: name.to_string(),
168 from: None,
169 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
170 explorer_urls: Some(vec!["https://explorer.example.com".to_string()]),
171 average_blocktime_ms: Some(12000),
172 is_testnet: Some(false),
173 tags: Some(vec!["mainnet".to_string()]),
174 },
175 chain_id: Some(chain_id),
176 required_confirmations: Some(12),
177 features: Some(vec!["eip1559".to_string()]),
178 symbol: Some(symbol.to_string()),
179 gas_price_cache: None,
180 }
181 }
182
183 fn create_solana_config(name: &str, is_testnet: bool) -> SolanaNetworkConfig {
184 SolanaNetworkConfig {
185 common: NetworkConfigCommon {
186 network: name.to_string(),
187 from: None,
188 rpc_urls: Some(vec!["https://api.mainnet-beta.solana.com".to_string()]),
189 explorer_urls: Some(vec!["https://explorer.solana.com".to_string()]),
190 average_blocktime_ms: Some(400),
191 is_testnet: Some(is_testnet),
192 tags: Some(vec!["solana".to_string()]),
193 },
194 }
195 }
196
197 fn create_stellar_config(name: &str, passphrase: Option<&str>) -> StellarNetworkConfig {
198 StellarNetworkConfig {
199 common: NetworkConfigCommon {
200 network: name.to_string(),
201 from: None,
202 rpc_urls: Some(vec!["https://horizon.stellar.org".to_string()]),
203 explorer_urls: Some(vec!["https://stellarchain.io".to_string()]),
204 average_blocktime_ms: Some(5000),
205 is_testnet: Some(passphrase.is_none()),
206 tags: Some(vec!["stellar".to_string()]),
207 },
208 passphrase: passphrase.map(|s| s.to_string()),
209 horizon_url: Some("https://horizon.stellar.org".to_string()),
210 }
211 }
212
213 #[test]
214 fn test_network_config_data_evm() {
215 let config = create_evm_config("mainnet", 1, "ETH");
216 let config_data = NetworkConfigData::Evm(config);
217
218 assert_eq!(config_data.network_name(), "mainnet");
219 assert_eq!(config_data.network_type(), NetworkType::Evm);
220 assert_eq!(config_data.common().network, "mainnet");
221 assert_eq!(config_data.common().is_testnet, Some(false));
222 }
223
224 #[test]
225 fn test_network_config_data_solana() {
226 let config = create_solana_config("devnet", true);
227 let config_data = NetworkConfigData::Solana(config);
228
229 assert_eq!(config_data.network_name(), "devnet");
230 assert_eq!(config_data.network_type(), NetworkType::Solana);
231 assert_eq!(config_data.common().is_testnet, Some(true));
232 }
233
234 #[test]
235 fn test_network_config_data_stellar() {
236 let config = create_stellar_config("testnet", None);
237 let config_data = NetworkConfigData::Stellar(config);
238
239 assert_eq!(config_data.network_name(), "testnet");
240 assert_eq!(config_data.network_type(), NetworkType::Stellar);
241 assert_eq!(config_data.common().is_testnet, Some(true));
242 }
243
244 #[test]
245 fn test_new_evm() {
246 let config = create_evm_config("mainnet", 1, "ETH");
247 let network_repo = NetworkRepoModel::new_evm(config);
248
249 assert_eq!(network_repo.name, "mainnet");
250 assert_eq!(network_repo.network_type, NetworkType::Evm);
251 assert_eq!(network_repo.id, "evm:mainnet");
252
253 match network_repo.config() {
254 NetworkConfigData::Evm(evm_config) => {
255 assert_eq!(evm_config.chain_id, Some(1));
256 assert_eq!(evm_config.symbol, Some("ETH".to_string()));
257 }
258 _ => panic!("Expected EVM config"),
259 }
260 }
261
262 #[test]
263 fn test_new_solana() {
264 let config = create_solana_config("devnet", true);
265 let network_repo = NetworkRepoModel::new_solana(config);
266
267 assert_eq!(network_repo.name, "devnet");
268 assert_eq!(network_repo.network_type, NetworkType::Solana);
269 assert_eq!(network_repo.id, "solana:devnet");
270
271 match network_repo.config() {
272 NetworkConfigData::Solana(solana_config) => {
273 assert_eq!(solana_config.common.is_testnet, Some(true));
274 }
275 _ => panic!("Expected Solana config"),
276 }
277 }
278
279 #[test]
280 fn test_new_stellar() {
281 let config = create_stellar_config(
282 "mainnet",
283 Some("Public Global Stellar Network ; September 2015"),
284 );
285 let network_repo = NetworkRepoModel::new_stellar(config);
286
287 assert_eq!(network_repo.name, "mainnet");
288 assert_eq!(network_repo.network_type, NetworkType::Stellar);
289 assert_eq!(network_repo.id, "stellar:mainnet");
290
291 match network_repo.config() {
292 NetworkConfigData::Stellar(stellar_config) => {
293 assert_eq!(
294 stellar_config.passphrase,
295 Some("Public Global Stellar Network ; September 2015".to_string())
296 );
297 }
298 _ => panic!("Expected Stellar config"),
299 }
300 }
301
302 #[test]
303 fn test_create_id() {
304 assert_eq!(
305 NetworkRepoModel::create_id(NetworkType::Evm, "Mainnet"),
306 "evm:mainnet"
307 );
308 assert_eq!(
309 NetworkRepoModel::create_id(NetworkType::Solana, "DEVNET"),
310 "solana:devnet"
311 );
312 assert_eq!(
313 NetworkRepoModel::create_id(NetworkType::Stellar, "TestNet"),
314 "stellar:testnet"
315 );
316 }
317
318 #[test]
319 fn test_create_id_with_special_characters() {
320 assert_eq!(
321 NetworkRepoModel::create_id(NetworkType::Evm, "My-Network_123"),
322 "evm:my-network_123"
323 );
324 assert_eq!(
325 NetworkRepoModel::create_id(NetworkType::Solana, "Test Network"),
326 "solana:test network"
327 );
328 }
329
330 #[test]
331 fn test_common_method() {
332 let config = create_evm_config("mainnet", 1, "ETH");
333 let network_repo = NetworkRepoModel::new_evm(config);
334
335 let common = network_repo.common();
336 assert_eq!(common.network, "mainnet");
337 assert_eq!(common.is_testnet, Some(false));
338 assert_eq!(common.average_blocktime_ms, Some(12000));
339 assert_eq!(
340 common.rpc_urls,
341 Some(vec!["https://rpc.example.com".to_string()])
342 );
343 }
344
345 #[test]
346 fn test_config_method() {
347 let config = create_evm_config("mainnet", 1, "ETH");
348 let network_repo = NetworkRepoModel::new_evm(config);
349
350 let config_data = network_repo.config();
351 assert!(matches!(config_data, NetworkConfigData::Evm(_)));
352 assert_eq!(config_data.network_type(), NetworkType::Evm);
353 assert_eq!(config_data.network_name(), "mainnet");
354 }
355
356 #[test]
357 fn test_try_from_evm() {
358 let evm_config = create_evm_config("mainnet", 1, "ETH");
359 let network_file_config = NetworkFileConfig::Evm(evm_config);
360
361 let result = NetworkRepoModel::try_from(network_file_config);
362 assert!(result.is_ok());
363
364 let network_repo = result.unwrap();
365 assert_eq!(network_repo.name, "mainnet");
366 assert_eq!(network_repo.network_type, NetworkType::Evm);
367 assert_eq!(network_repo.id, "evm:mainnet");
368 }
369
370 #[test]
371 fn test_try_from_solana() {
372 let solana_config = create_solana_config("devnet", true);
373 let network_file_config = NetworkFileConfig::Solana(solana_config);
374
375 let result = NetworkRepoModel::try_from(network_file_config);
376 assert!(result.is_ok());
377
378 let network_repo = result.unwrap();
379 assert_eq!(network_repo.name, "devnet");
380 assert_eq!(network_repo.network_type, NetworkType::Solana);
381 assert_eq!(network_repo.id, "solana:devnet");
382 }
383
384 #[test]
385 fn test_try_from_stellar() {
386 let stellar_config = create_stellar_config("testnet", None);
387 let network_file_config = NetworkFileConfig::Stellar(stellar_config);
388
389 let result = NetworkRepoModel::try_from(network_file_config);
390 assert!(result.is_ok());
391
392 let network_repo = result.unwrap();
393 assert_eq!(network_repo.name, "testnet");
394 assert_eq!(network_repo.network_type, NetworkType::Stellar);
395 assert_eq!(network_repo.id, "stellar:testnet");
396 }
397
398 #[test]
399 fn test_serialization_roundtrip() {
400 let config = create_evm_config("mainnet", 1, "ETH");
401 let network_repo = NetworkRepoModel::new_evm(config);
402
403 let serialized = serde_json::to_string(&network_repo).unwrap();
404 let deserialized: NetworkRepoModel = serde_json::from_str(&serialized).unwrap();
405
406 assert_eq!(network_repo.id, deserialized.id);
407 assert_eq!(network_repo.name, deserialized.name);
408 assert_eq!(network_repo.network_type, deserialized.network_type);
409 }
410
411 #[test]
412 fn test_clone() {
413 let config = create_evm_config("mainnet", 1, "ETH");
414 let network_repo = NetworkRepoModel::new_evm(config);
415 let cloned = network_repo.clone();
416
417 assert_eq!(network_repo.id, cloned.id);
418 assert_eq!(network_repo.name, cloned.name);
419 assert_eq!(network_repo.network_type, cloned.network_type);
420 }
421
422 #[test]
423 fn test_debug() {
424 let config = create_evm_config("mainnet", 1, "ETH");
425 let network_repo = NetworkRepoModel::new_evm(config);
426
427 let debug_str = format!("{:?}", network_repo);
428 assert!(debug_str.contains("NetworkRepoModel"));
429 assert!(debug_str.contains("mainnet"));
430 assert!(debug_str.contains("Evm"));
431 }
432
433 #[test]
434 fn test_network_types_consistency() {
435 let evm_config = create_evm_config("mainnet", 1, "ETH");
436 let solana_config = create_solana_config("devnet", true);
437 let stellar_config = create_stellar_config("testnet", None);
438
439 let evm_repo = NetworkRepoModel::new_evm(evm_config);
440 let solana_repo = NetworkRepoModel::new_solana(solana_config);
441 let stellar_repo = NetworkRepoModel::new_stellar(stellar_config);
442
443 assert_eq!(evm_repo.network_type, evm_repo.config().network_type());
444 assert_eq!(
445 solana_repo.network_type,
446 solana_repo.config().network_type()
447 );
448 assert_eq!(
449 stellar_repo.network_type,
450 stellar_repo.config().network_type()
451 );
452 }
453
454 #[test]
455 fn test_empty_optional_fields() {
456 let minimal_config = EvmNetworkConfig {
457 common: NetworkConfigCommon {
458 network: "minimal".to_string(),
459 from: None,
460 rpc_urls: Some(vec!["https://rpc.example.com".to_string()]),
461 explorer_urls: None,
462 average_blocktime_ms: None,
463 is_testnet: None,
464 tags: None,
465 },
466 chain_id: Some(1),
467 required_confirmations: Some(1),
468 features: None,
469 symbol: Some("ETH".to_string()),
470 gas_price_cache: None,
471 };
472
473 let network_repo = NetworkRepoModel::new_evm(minimal_config);
474 assert_eq!(network_repo.name, "minimal");
475 assert_eq!(network_repo.common().explorer_urls, None);
476 assert_eq!(network_repo.common().tags, None);
477 }
478}