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 use crate::models::RpcConfig;
166 EvmNetworkConfig {
167 common: NetworkConfigCommon {
168 network: name.to_string(),
169 from: None,
170 rpc_urls: Some(vec![RpcConfig::new("https://rpc.example.com".to_string())]),
171 explorer_urls: Some(vec!["https://explorer.example.com".to_string()]),
172 average_blocktime_ms: Some(12000),
173 is_testnet: Some(false),
174 tags: Some(vec!["mainnet".to_string()]),
175 },
176 chain_id: Some(chain_id),
177 required_confirmations: Some(12),
178 features: Some(vec!["eip1559".to_string()]),
179 symbol: Some(symbol.to_string()),
180 gas_price_cache: None,
181 }
182 }
183
184 fn create_solana_config(name: &str, is_testnet: bool) -> SolanaNetworkConfig {
185 use crate::models::RpcConfig;
186 SolanaNetworkConfig {
187 common: NetworkConfigCommon {
188 network: name.to_string(),
189 from: None,
190 rpc_urls: Some(vec![RpcConfig::new(
191 "https://api.mainnet-beta.solana.com".to_string(),
192 )]),
193 explorer_urls: Some(vec!["https://explorer.solana.com".to_string()]),
194 average_blocktime_ms: Some(400),
195 is_testnet: Some(is_testnet),
196 tags: Some(vec!["solana".to_string()]),
197 },
198 }
199 }
200
201 fn create_stellar_config(name: &str, passphrase: Option<&str>) -> StellarNetworkConfig {
202 use crate::models::RpcConfig;
203 StellarNetworkConfig {
204 common: NetworkConfigCommon {
205 network: name.to_string(),
206 from: None,
207 rpc_urls: Some(vec![RpcConfig::new(
208 "https://horizon.stellar.org".to_string(),
209 )]),
210 explorer_urls: Some(vec!["https://stellarchain.io".to_string()]),
211 average_blocktime_ms: Some(5000),
212 is_testnet: Some(passphrase.is_none()),
213 tags: Some(vec!["stellar".to_string()]),
214 },
215 passphrase: passphrase.map(|s| s.to_string()),
216 horizon_url: Some("https://horizon.stellar.org".to_string()),
217 }
218 }
219
220 #[test]
221 fn test_network_config_data_evm() {
222 let config = create_evm_config("mainnet", 1, "ETH");
223 let config_data = NetworkConfigData::Evm(config);
224
225 assert_eq!(config_data.network_name(), "mainnet");
226 assert_eq!(config_data.network_type(), NetworkType::Evm);
227 assert_eq!(config_data.common().network, "mainnet");
228 assert_eq!(config_data.common().is_testnet, Some(false));
229 }
230
231 #[test]
232 fn test_network_config_data_solana() {
233 let config = create_solana_config("devnet", true);
234 let config_data = NetworkConfigData::Solana(config);
235
236 assert_eq!(config_data.network_name(), "devnet");
237 assert_eq!(config_data.network_type(), NetworkType::Solana);
238 assert_eq!(config_data.common().is_testnet, Some(true));
239 }
240
241 #[test]
242 fn test_network_config_data_stellar() {
243 let config = create_stellar_config("testnet", None);
244 let config_data = NetworkConfigData::Stellar(config);
245
246 assert_eq!(config_data.network_name(), "testnet");
247 assert_eq!(config_data.network_type(), NetworkType::Stellar);
248 assert_eq!(config_data.common().is_testnet, Some(true));
249 }
250
251 #[test]
252 fn test_new_evm() {
253 let config = create_evm_config("mainnet", 1, "ETH");
254 let network_repo = NetworkRepoModel::new_evm(config);
255
256 assert_eq!(network_repo.name, "mainnet");
257 assert_eq!(network_repo.network_type, NetworkType::Evm);
258 assert_eq!(network_repo.id, "evm:mainnet");
259
260 match network_repo.config() {
261 NetworkConfigData::Evm(evm_config) => {
262 assert_eq!(evm_config.chain_id, Some(1));
263 assert_eq!(evm_config.symbol, Some("ETH".to_string()));
264 }
265 _ => panic!("Expected EVM config"),
266 }
267 }
268
269 #[test]
270 fn test_new_solana() {
271 let config = create_solana_config("devnet", true);
272 let network_repo = NetworkRepoModel::new_solana(config);
273
274 assert_eq!(network_repo.name, "devnet");
275 assert_eq!(network_repo.network_type, NetworkType::Solana);
276 assert_eq!(network_repo.id, "solana:devnet");
277
278 match network_repo.config() {
279 NetworkConfigData::Solana(solana_config) => {
280 assert_eq!(solana_config.common.is_testnet, Some(true));
281 }
282 _ => panic!("Expected Solana config"),
283 }
284 }
285
286 #[test]
287 fn test_new_stellar() {
288 let config = create_stellar_config(
289 "mainnet",
290 Some("Public Global Stellar Network ; September 2015"),
291 );
292 let network_repo = NetworkRepoModel::new_stellar(config);
293
294 assert_eq!(network_repo.name, "mainnet");
295 assert_eq!(network_repo.network_type, NetworkType::Stellar);
296 assert_eq!(network_repo.id, "stellar:mainnet");
297
298 match network_repo.config() {
299 NetworkConfigData::Stellar(stellar_config) => {
300 assert_eq!(
301 stellar_config.passphrase,
302 Some("Public Global Stellar Network ; September 2015".to_string())
303 );
304 }
305 _ => panic!("Expected Stellar config"),
306 }
307 }
308
309 #[test]
310 fn test_create_id() {
311 assert_eq!(
312 NetworkRepoModel::create_id(NetworkType::Evm, "Mainnet"),
313 "evm:mainnet"
314 );
315 assert_eq!(
316 NetworkRepoModel::create_id(NetworkType::Solana, "DEVNET"),
317 "solana:devnet"
318 );
319 assert_eq!(
320 NetworkRepoModel::create_id(NetworkType::Stellar, "TestNet"),
321 "stellar:testnet"
322 );
323 }
324
325 #[test]
326 fn test_create_id_with_special_characters() {
327 assert_eq!(
328 NetworkRepoModel::create_id(NetworkType::Evm, "My-Network_123"),
329 "evm:my-network_123"
330 );
331 assert_eq!(
332 NetworkRepoModel::create_id(NetworkType::Solana, "Test Network"),
333 "solana:test network"
334 );
335 }
336
337 #[test]
338 fn test_common_method() {
339 let config = create_evm_config("mainnet", 1, "ETH");
340 let network_repo = NetworkRepoModel::new_evm(config);
341
342 let common = network_repo.common();
343 assert_eq!(common.network, "mainnet");
344 assert_eq!(common.is_testnet, Some(false));
345 assert_eq!(common.average_blocktime_ms, Some(12000));
346 use crate::models::RpcConfig;
347 assert_eq!(
348 common.rpc_urls,
349 Some(vec![RpcConfig::new("https://rpc.example.com".to_string())])
350 );
351 }
352
353 #[test]
354 fn test_config_method() {
355 let config = create_evm_config("mainnet", 1, "ETH");
356 let network_repo = NetworkRepoModel::new_evm(config);
357
358 let config_data = network_repo.config();
359 assert!(matches!(config_data, NetworkConfigData::Evm(_)));
360 assert_eq!(config_data.network_type(), NetworkType::Evm);
361 assert_eq!(config_data.network_name(), "mainnet");
362 }
363
364 #[test]
365 fn test_try_from_evm() {
366 let evm_config = create_evm_config("mainnet", 1, "ETH");
367 let network_file_config = NetworkFileConfig::Evm(evm_config);
368
369 let result = NetworkRepoModel::try_from(network_file_config);
370 assert!(result.is_ok());
371
372 let network_repo = result.unwrap();
373 assert_eq!(network_repo.name, "mainnet");
374 assert_eq!(network_repo.network_type, NetworkType::Evm);
375 assert_eq!(network_repo.id, "evm:mainnet");
376 }
377
378 #[test]
379 fn test_try_from_solana() {
380 let solana_config = create_solana_config("devnet", true);
381 let network_file_config = NetworkFileConfig::Solana(solana_config);
382
383 let result = NetworkRepoModel::try_from(network_file_config);
384 assert!(result.is_ok());
385
386 let network_repo = result.unwrap();
387 assert_eq!(network_repo.name, "devnet");
388 assert_eq!(network_repo.network_type, NetworkType::Solana);
389 assert_eq!(network_repo.id, "solana:devnet");
390 }
391
392 #[test]
393 fn test_try_from_stellar() {
394 let stellar_config = create_stellar_config("testnet", None);
395 let network_file_config = NetworkFileConfig::Stellar(stellar_config);
396
397 let result = NetworkRepoModel::try_from(network_file_config);
398 assert!(result.is_ok());
399
400 let network_repo = result.unwrap();
401 assert_eq!(network_repo.name, "testnet");
402 assert_eq!(network_repo.network_type, NetworkType::Stellar);
403 assert_eq!(network_repo.id, "stellar:testnet");
404 }
405
406 #[test]
407 fn test_serialization_roundtrip() {
408 let config = create_evm_config("mainnet", 1, "ETH");
409 let network_repo = NetworkRepoModel::new_evm(config);
410
411 let serialized = serde_json::to_string(&network_repo).unwrap();
412 let deserialized: NetworkRepoModel = serde_json::from_str(&serialized).unwrap();
413
414 assert_eq!(network_repo.id, deserialized.id);
415 assert_eq!(network_repo.name, deserialized.name);
416 assert_eq!(network_repo.network_type, deserialized.network_type);
417 }
418
419 #[test]
420 fn test_clone() {
421 let config = create_evm_config("mainnet", 1, "ETH");
422 let network_repo = NetworkRepoModel::new_evm(config);
423 let cloned = network_repo.clone();
424
425 assert_eq!(network_repo.id, cloned.id);
426 assert_eq!(network_repo.name, cloned.name);
427 assert_eq!(network_repo.network_type, cloned.network_type);
428 }
429
430 #[test]
431 fn test_debug() {
432 let config = create_evm_config("mainnet", 1, "ETH");
433 let network_repo = NetworkRepoModel::new_evm(config);
434
435 let debug_str = format!("{network_repo:?}");
436 assert!(debug_str.contains("NetworkRepoModel"));
437 assert!(debug_str.contains("mainnet"));
438 assert!(debug_str.contains("Evm"));
439 }
440
441 #[test]
442 fn test_network_types_consistency() {
443 let evm_config = create_evm_config("mainnet", 1, "ETH");
444 let solana_config = create_solana_config("devnet", true);
445 let stellar_config = create_stellar_config("testnet", None);
446
447 let evm_repo = NetworkRepoModel::new_evm(evm_config);
448 let solana_repo = NetworkRepoModel::new_solana(solana_config);
449 let stellar_repo = NetworkRepoModel::new_stellar(stellar_config);
450
451 assert_eq!(evm_repo.network_type, evm_repo.config().network_type());
452 assert_eq!(
453 solana_repo.network_type,
454 solana_repo.config().network_type()
455 );
456 assert_eq!(
457 stellar_repo.network_type,
458 stellar_repo.config().network_type()
459 );
460 }
461
462 #[test]
463 fn test_empty_optional_fields() {
464 use crate::models::RpcConfig;
465 let minimal_config = EvmNetworkConfig {
466 common: NetworkConfigCommon {
467 network: "minimal".to_string(),
468 from: None,
469 rpc_urls: Some(vec![RpcConfig::new("https://rpc.example.com".to_string())]),
470 explorer_urls: None,
471 average_blocktime_ms: None,
472 is_testnet: None,
473 tags: None,
474 },
475 chain_id: Some(1),
476 required_confirmations: Some(1),
477 features: None,
478 symbol: Some("ETH".to_string()),
479 gas_price_cache: None,
480 };
481
482 let network_repo = NetworkRepoModel::new_evm(minimal_config);
483 assert_eq!(network_repo.name, "minimal");
484 assert_eq!(network_repo.common().explorer_urls, None);
485 assert_eq!(network_repo.common().tags, None);
486 }
487}