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