1use crate::config::{
11 ConfigFileRelayerSolanaFeePaymentStrategy, ConfigFileRelayerSolanaSwapPolicy,
12 ConfigFileRelayerSolanaSwapStrategy,
13};
14use crate::models::{
15 JupiterSwapOptions, PaginationQuery, RelayerSolanaSwapConfig, SolanaAllowedTokensSwapConfig,
16 SolanaFeePaymentStrategy, SolanaSwapStrategy,
17};
18use crate::{
19 config::{ConfigFileNetworkType, ConfigFileRelayerNetworkPolicy, RelayerFileConfig},
20 constants::{
21 DEFAULT_EVM_MIN_BALANCE, DEFAULT_SOLANA_MIN_BALANCE, DEFAULT_STELLAR_MIN_BALANCE,
22 MAX_SOLANA_TX_DATA_SIZE,
23 },
24 domain::RelayerUpdateRequest,
25 models::{
26 NetworkType, RelayerEvmPolicy, RelayerNetworkPolicy, RelayerRepoModel, RelayerSolanaPolicy,
27 RelayerStellarPolicy, RepositoryError, SolanaAllowedTokensPolicy,
28 },
29};
30use async_trait::async_trait;
31use eyre::Result;
32use std::collections::HashMap;
33use std::sync::Arc;
34use thiserror::Error;
35use tokio::sync::{Mutex, MutexGuard};
36
37use super::{PaginatedResult, Repository};
38
39#[async_trait]
40pub trait RelayerRepository: Repository<RelayerRepoModel, String> + Send + Sync {
41 async fn list_active(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError>;
42 async fn partial_update(
43 &self,
44 id: String,
45 update: RelayerUpdateRequest,
46 ) -> Result<RelayerRepoModel, RepositoryError>;
47 async fn enable_relayer(&self, relayer_id: String)
48 -> Result<RelayerRepoModel, RepositoryError>;
49 async fn disable_relayer(
50 &self,
51 relayer_id: String,
52 ) -> Result<RelayerRepoModel, RepositoryError>;
53 async fn update_policy(
54 &self,
55 id: String,
56 policy: RelayerNetworkPolicy,
57 ) -> Result<RelayerRepoModel, RepositoryError>;
58}
59
60#[derive(Debug)]
61pub struct InMemoryRelayerRepository {
62 store: Mutex<HashMap<String, RelayerRepoModel>>,
63}
64
65impl InMemoryRelayerRepository {
66 pub fn new() -> Self {
67 Self {
68 store: Mutex::new(HashMap::new()),
69 }
70 }
71 async fn acquire_lock<T>(lock: &Mutex<T>) -> Result<MutexGuard<T>, RepositoryError> {
72 Ok(lock.lock().await)
73 }
74}
75
76impl Default for InMemoryRelayerRepository {
77 fn default() -> Self {
78 Self::new()
79 }
80}
81
82#[async_trait]
83impl RelayerRepository for InMemoryRelayerRepository {
84 async fn list_active(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
85 let store = Self::acquire_lock(&self.store).await?;
86 let active_relayers: Vec<RelayerRepoModel> = store
87 .values()
88 .filter(|&relayer| !relayer.paused)
89 .cloned()
90 .collect();
91 Ok(active_relayers)
92 }
93
94 async fn partial_update(
95 &self,
96 id: String,
97 update: RelayerUpdateRequest,
98 ) -> Result<RelayerRepoModel, RepositoryError> {
99 let mut store = Self::acquire_lock(&self.store).await?;
100 if let Some(relayer) = store.get_mut(&id) {
101 if let Some(paused) = update.paused {
102 relayer.paused = paused;
103 }
104 Ok(relayer.clone())
105 } else {
106 Err(RepositoryError::NotFound(format!(
107 "Relayer with ID {} not found",
108 id
109 )))
110 }
111 }
112
113 async fn update_policy(
114 &self,
115 id: String,
116 policy: RelayerNetworkPolicy,
117 ) -> Result<RelayerRepoModel, RepositoryError> {
118 let mut store = Self::acquire_lock(&self.store).await?;
119 let relayer = store.get_mut(&id).ok_or_else(|| {
120 RepositoryError::NotFound(format!("Relayer with ID {} not found", id))
121 })?;
122 relayer.policies = policy;
123 Ok(relayer.clone())
124 }
125
126 async fn disable_relayer(
127 &self,
128 relayer_id: String,
129 ) -> Result<RelayerRepoModel, RepositoryError> {
130 let mut store = self.store.lock().await;
131 if let Some(relayer) = store.get_mut(&relayer_id) {
132 relayer.system_disabled = true;
133 Ok(relayer.clone())
134 } else {
135 Err(RepositoryError::NotFound(format!(
136 "Relayer with ID {} not found",
137 relayer_id
138 )))
139 }
140 }
141
142 async fn enable_relayer(
143 &self,
144 relayer_id: String,
145 ) -> Result<RelayerRepoModel, RepositoryError> {
146 let mut store = self.store.lock().await;
147 if let Some(relayer) = store.get_mut(&relayer_id) {
148 relayer.system_disabled = false;
149 Ok(relayer.clone())
150 } else {
151 Err(RepositoryError::NotFound(format!(
152 "Relayer with ID {} not found",
153 relayer_id
154 )))
155 }
156 }
157}
158
159#[async_trait]
160impl Repository<RelayerRepoModel, String> for InMemoryRelayerRepository {
161 async fn create(&self, relayer: RelayerRepoModel) -> Result<RelayerRepoModel, RepositoryError> {
162 let mut store = Self::acquire_lock(&self.store).await?;
163 if store.contains_key(&relayer.id) {
164 return Err(RepositoryError::ConstraintViolation(format!(
165 "Relayer with ID {} already exists",
166 relayer.id
167 )));
168 }
169 store.insert(relayer.id.clone(), relayer.clone());
170 Ok(relayer)
171 }
172
173 async fn get_by_id(&self, id: String) -> Result<RelayerRepoModel, RepositoryError> {
174 let store = Self::acquire_lock(&self.store).await?;
175 match store.get(&id) {
176 Some(relayer) => Ok(relayer.clone()),
177 None => Err(RepositoryError::NotFound(format!(
178 "Relayer with ID {} not found",
179 id
180 ))),
181 }
182 }
183 #[allow(clippy::map_entry)]
184 async fn update(
185 &self,
186 id: String,
187 relayer: RelayerRepoModel,
188 ) -> Result<RelayerRepoModel, RepositoryError> {
189 let mut store = Self::acquire_lock(&self.store).await?;
190 if store.contains_key(&id) {
191 let mut updated_relayer = relayer;
193 updated_relayer.id = id.clone(); store.insert(id, updated_relayer.clone());
195 Ok(updated_relayer)
196 } else {
197 Err(RepositoryError::NotFound(format!(
198 "Relayer with ID {} not found",
199 id
200 )))
201 }
202 }
203
204 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
205 let mut store = Self::acquire_lock(&self.store).await?;
206 if store.remove(&id).is_some() {
207 Ok(())
208 } else {
209 Err(RepositoryError::NotFound(format!(
210 "Relayer with ID {} not found",
211 id
212 )))
213 }
214 }
215
216 async fn list_all(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
217 let store = Self::acquire_lock(&self.store).await?;
218 Ok(store.values().cloned().collect())
219 }
220
221 async fn list_paginated(
222 &self,
223 query: PaginationQuery,
224 ) -> Result<PaginatedResult<RelayerRepoModel>, RepositoryError> {
225 let total = self.count().await?;
226 let start = ((query.page - 1) * query.per_page) as usize;
227 let items = self
228 .store
229 .lock()
230 .await
231 .values()
232 .skip(start)
233 .take(query.per_page as usize)
234 .cloned()
235 .collect();
236 Ok(PaginatedResult {
237 items,
238 total: total as u64,
239 page: query.page,
240 per_page: query.per_page,
241 })
242 }
243
244 async fn count(&self) -> Result<usize, RepositoryError> {
245 Ok(self.store.lock().await.len())
246 }
247}
248
249#[derive(Error, Debug)]
250pub enum ConversionError {
251 #[error("Invalid network type: {0}")]
252 InvalidNetworkType(String),
253 #[error("Invalid config: {0}")]
254 InvalidConfig(String),
255}
256
257impl TryFrom<RelayerFileConfig> for RelayerRepoModel {
258 type Error = ConversionError;
259
260 fn try_from(config: RelayerFileConfig) -> Result<Self, Self::Error> {
261 let network_type = match config.network_type {
262 ConfigFileNetworkType::Evm => NetworkType::Evm,
263 ConfigFileNetworkType::Stellar => NetworkType::Stellar,
264 ConfigFileNetworkType::Solana => NetworkType::Solana,
265 };
266
267 let policies = if let Some(config_policies) = &config.policies {
268 RelayerNetworkPolicy::try_from(config_policies.clone()).map_err(|_| {
269 ConversionError::InvalidNetworkType("Failed to convert network policy".to_string())
270 })?
271 } else {
272 match network_type {
274 NetworkType::Evm => RelayerNetworkPolicy::Evm(RelayerEvmPolicy::default()),
275 NetworkType::Stellar => {
276 RelayerNetworkPolicy::Stellar(RelayerStellarPolicy::default())
277 }
278 NetworkType::Solana => RelayerNetworkPolicy::Solana(RelayerSolanaPolicy::default()),
279 }
280 };
281
282 Ok(RelayerRepoModel {
283 id: config.id,
284 name: config.name,
285 network: config.network,
286 paused: config.paused,
287 network_type,
288 signer_id: config.signer_id,
289 policies,
290 address: "".to_string(), notification_id: config.notification_id,
293 system_disabled: false,
294 custom_rpc_urls: config.custom_rpc_urls,
295 })
296 }
297}
298
299impl TryFrom<ConfigFileRelayerSolanaSwapStrategy> for SolanaSwapStrategy {
300 type Error = eyre::Error;
301
302 fn try_from(config: ConfigFileRelayerSolanaSwapStrategy) -> Result<Self, Self::Error> {
303 match config {
304 ConfigFileRelayerSolanaSwapStrategy::JupiterSwap => Ok(SolanaSwapStrategy::JupiterSwap),
305 ConfigFileRelayerSolanaSwapStrategy::JupiterUltra => {
306 Ok(SolanaSwapStrategy::JupiterUltra)
307 }
308 }
309 }
310}
311
312impl TryFrom<ConfigFileRelayerSolanaSwapPolicy> for RelayerSolanaSwapConfig {
313 type Error = eyre::Error;
314
315 fn try_from(config: ConfigFileRelayerSolanaSwapPolicy) -> Result<Self, Self::Error> {
316 Ok(RelayerSolanaSwapConfig {
317 cron_schedule: config.cron_schedule,
318 min_balance_threshold: config.min_balance_threshold,
319 strategy: config
320 .strategy
321 .map(SolanaSwapStrategy::try_from)
322 .transpose()?,
323 jupiter_swap_options: config
324 .jupiter_swap_options
325 .map(|options| JupiterSwapOptions {
326 priority_fee_max_lamports: options.priority_fee_max_lamports,
327 dynamic_compute_unit_limit: options.dynamic_compute_unit_limit,
328 priority_level: options.priority_level,
329 }),
330 })
331 }
332}
333
334impl TryFrom<ConfigFileRelayerNetworkPolicy> for RelayerNetworkPolicy {
335 type Error = eyre::Error;
336
337 fn try_from(policy: ConfigFileRelayerNetworkPolicy) -> Result<Self, Self::Error> {
338 match &policy {
339 ConfigFileRelayerNetworkPolicy::Evm(evm) => {
340 Ok(RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
341 gas_price_cap: evm.gas_price_cap,
342 whitelist_receivers: evm.whitelist_receivers.clone(),
343 eip1559_pricing: evm.eip1559_pricing,
344 private_transactions: evm.private_transactions.unwrap_or(false),
345 min_balance: evm.min_balance.unwrap_or(DEFAULT_EVM_MIN_BALANCE),
346 }))
347 }
348 ConfigFileRelayerNetworkPolicy::Solana(solana) => {
349 let mapped_allowed_tokens = solana
354 .allowed_tokens
355 .as_ref()
356 .filter(|tokens| !tokens.is_empty())
357 .map(|tokens| {
358 tokens
359 .iter()
360 .map(|token| {
361 let swap_config = token.swap_config.as_ref().map(|sc| {
362 SolanaAllowedTokensSwapConfig {
363 slippage_percentage: sc.slippage_percentage,
364 min_amount: sc.min_amount,
365 max_amount: sc.max_amount,
366 retain_min_amount: sc.retain_min_amount,
367 }
368 });
369
370 SolanaAllowedTokensPolicy::new_partial(
371 token.mint.clone(),
372 token.max_allowed_fee,
373 swap_config,
374 )
375 })
376 .collect::<Vec<_>>()
377 });
378 let fee_payment_strategy = solana.fee_payment_strategy.clone().map_or(
379 SolanaFeePaymentStrategy::User,
380 |fp| match fp {
381 ConfigFileRelayerSolanaFeePaymentStrategy::User => {
382 SolanaFeePaymentStrategy::User
383 }
384 ConfigFileRelayerSolanaFeePaymentStrategy::Relayer => {
385 SolanaFeePaymentStrategy::Relayer
386 }
387 },
388 );
389 let swap_config = solana
390 .swap_config
391 .as_ref()
392 .map(|sc| RelayerSolanaSwapConfig::try_from(sc.clone()))
393 .transpose()?;
394
395 Ok(RelayerNetworkPolicy::Solana(RelayerSolanaPolicy {
396 fee_payment_strategy,
397 fee_margin_percentage: solana.fee_margin_percentage,
398 min_balance: solana.min_balance.unwrap_or(DEFAULT_SOLANA_MIN_BALANCE),
399 allowed_accounts: solana.allowed_accounts.clone(),
400 allowed_programs: solana.allowed_programs.clone(),
401 allowed_tokens: mapped_allowed_tokens,
402 disallowed_accounts: solana.disallowed_accounts.clone(),
403 max_signatures: solana.max_signatures,
404 max_tx_data_size: solana.max_tx_data_size.unwrap_or(MAX_SOLANA_TX_DATA_SIZE),
405 max_allowed_fee_lamports: solana.max_allowed_fee_lamports,
406 swap_config,
407 }))
408 }
409 ConfigFileRelayerNetworkPolicy::Stellar(stellar) => {
410 Ok(RelayerNetworkPolicy::Stellar(RelayerStellarPolicy {
411 max_fee: stellar.max_fee,
412 timeout_seconds: stellar.timeout_seconds,
413 min_balance: stellar.min_balance.unwrap_or(DEFAULT_STELLAR_MIN_BALANCE),
414 }))
415 }
416 }
417 }
418}
419
420#[derive(Debug)]
440pub struct RelayerRepositoryStorage<T: Repository<RelayerRepoModel, String>> {
441 pub repository: Arc<T>,
442}
443
444impl RelayerRepositoryStorage<InMemoryRelayerRepository> {
445 pub fn in_memory(repository: InMemoryRelayerRepository) -> Self {
455 Self {
456 repository: Arc::new(repository),
457 }
458 }
459}
460
461#[async_trait]
462impl<T> Repository<RelayerRepoModel, String> for RelayerRepositoryStorage<T>
463where
464 T: Repository<RelayerRepoModel, String> + Send + Sync,
465{
466 async fn create(&self, entity: RelayerRepoModel) -> Result<RelayerRepoModel, RepositoryError> {
467 self.repository.create(entity).await
468 }
469
470 async fn get_by_id(&self, id: String) -> Result<RelayerRepoModel, RepositoryError> {
471 self.repository.get_by_id(id).await
472 }
473
474 async fn list_all(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
475 self.repository.list_all().await
476 }
477
478 async fn list_paginated(
479 &self,
480 query: PaginationQuery,
481 ) -> Result<PaginatedResult<RelayerRepoModel>, RepositoryError> {
482 self.repository.list_paginated(query).await
483 }
484
485 async fn update(
486 &self,
487 id: String,
488 entity: RelayerRepoModel,
489 ) -> Result<RelayerRepoModel, RepositoryError> {
490 self.repository.update(id, entity).await
491 }
492
493 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError> {
494 self.repository.delete_by_id(id).await
495 }
496
497 async fn count(&self) -> Result<usize, RepositoryError> {
498 self.repository.count().await
499 }
500}
501
502#[async_trait]
503impl<T> RelayerRepository for RelayerRepositoryStorage<T>
504where
505 T: RelayerRepository + Send + Sync,
506{
507 async fn list_active(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError> {
508 self.repository.list_active().await
509 }
510
511 async fn partial_update(
512 &self,
513 id: String,
514 update: RelayerUpdateRequest,
515 ) -> Result<RelayerRepoModel, RepositoryError> {
516 self.repository.partial_update(id, update).await
517 }
518
519 async fn enable_relayer(
520 &self,
521 relayer_id: String,
522 ) -> Result<RelayerRepoModel, RepositoryError> {
523 self.repository.enable_relayer(relayer_id).await
524 }
525
526 async fn disable_relayer(
527 &self,
528 relayer_id: String,
529 ) -> Result<RelayerRepoModel, RepositoryError> {
530 self.repository.disable_relayer(relayer_id).await
531 }
532
533 async fn update_policy(
534 &self,
535 id: String,
536 policy: RelayerNetworkPolicy,
537 ) -> Result<RelayerRepoModel, RepositoryError> {
538 self.repository.update_policy(id, policy).await
539 }
540}
541
542#[cfg(test)]
543mockall::mock! {
544 pub RelayerRepository {}
545
546 #[async_trait]
547 impl Repository<RelayerRepoModel, String> for RelayerRepository {
548 async fn create(&self, entity: RelayerRepoModel) -> Result<RelayerRepoModel, RepositoryError>;
549 async fn get_by_id(&self, id: String) -> Result<RelayerRepoModel, RepositoryError>;
550 async fn list_all(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError>;
551 async fn list_paginated(&self, query: PaginationQuery) -> Result<PaginatedResult<RelayerRepoModel>, RepositoryError>;
552 async fn update(&self, id: String, entity: RelayerRepoModel) -> Result<RelayerRepoModel, RepositoryError>;
553 async fn delete_by_id(&self, id: String) -> Result<(), RepositoryError>;
554 async fn count(&self) -> Result<usize, RepositoryError>;
555 }
556
557 #[async_trait]
558 impl RelayerRepository for RelayerRepository {
559 async fn list_active(&self) -> Result<Vec<RelayerRepoModel>, RepositoryError>;
560 async fn partial_update(&self, id: String, update: RelayerUpdateRequest) -> Result<RelayerRepoModel, RepositoryError>;
561 async fn enable_relayer(&self, relayer_id: String) -> Result<RelayerRepoModel, RepositoryError>;
562 async fn disable_relayer(&self, relayer_id: String) -> Result<RelayerRepoModel, RepositoryError>;
563 async fn update_policy(&self, id: String, policy: RelayerNetworkPolicy) -> Result<RelayerRepoModel, RepositoryError>;
564 }
565}
566
567#[cfg(test)]
568mod tests {
569 use super::*;
570
571 fn create_test_relayer(id: String) -> RelayerRepoModel {
572 RelayerRepoModel {
573 id: id.clone(),
574 name: format!("Relayer {}", id.clone()),
575 network: "TestNet".to_string(),
576 paused: false,
577 network_type: NetworkType::Evm,
578 policies: RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
579 gas_price_cap: None,
580 whitelist_receivers: None,
581 eip1559_pricing: Some(false),
582 private_transactions: false,
583 min_balance: 0,
584 }),
585 signer_id: "test".to_string(),
586 address: "0x".to_string(),
587 notification_id: None,
588 system_disabled: false,
589 custom_rpc_urls: None,
590 }
591 }
592
593 #[actix_web::test]
594 async fn test_new_repository_is_empty() {
595 let repo = InMemoryRelayerRepository::new();
596 assert_eq!(repo.count().await.unwrap(), 0);
597 }
598
599 #[actix_web::test]
600 async fn test_add_relayer() {
601 let repo = InMemoryRelayerRepository::new();
602 let relayer = create_test_relayer("test".to_string());
603
604 repo.create(relayer.clone()).await.unwrap();
605 assert_eq!(repo.count().await.unwrap(), 1);
606
607 let stored = repo.get_by_id("test".to_string()).await.unwrap();
608 assert_eq!(stored.id, relayer.id);
609 assert_eq!(stored.name, relayer.name);
610 }
611
612 #[actix_web::test]
613 async fn test_update_relayer() {
614 let repo = InMemoryRelayerRepository::new();
615 let mut relayer = create_test_relayer("test".to_string());
616
617 repo.create(relayer.clone()).await.unwrap();
618
619 relayer.name = "Updated Name".to_string();
620 repo.update("test".to_string(), relayer.clone())
621 .await
622 .unwrap();
623
624 let updated = repo.get_by_id("test".to_string()).await.unwrap();
625 assert_eq!(updated.name, "Updated Name");
626 }
627
628 #[actix_web::test]
629 async fn test_list_relayers() {
630 let repo = InMemoryRelayerRepository::new();
631 let relayer1 = create_test_relayer("test".to_string());
632 let relayer2 = create_test_relayer("test2".to_string());
633
634 repo.create(relayer1.clone()).await.unwrap();
635 repo.create(relayer2).await.unwrap();
636
637 let relayers = repo.list_all().await.unwrap();
638 assert_eq!(relayers.len(), 2);
639 }
640
641 #[actix_web::test]
642 async fn test_list_active_relayers() {
643 let repo = InMemoryRelayerRepository::new();
644 let relayer1 = create_test_relayer("test".to_string());
645 let mut relayer2 = create_test_relayer("test2".to_string());
646
647 relayer2.paused = true;
648
649 repo.create(relayer1.clone()).await.unwrap();
650 repo.create(relayer2).await.unwrap();
651
652 let active_relayers = repo.list_active().await.unwrap();
653 assert_eq!(active_relayers.len(), 1);
654 assert_eq!(active_relayers[0].id, "test".to_string());
655 }
656
657 #[actix_web::test]
658 async fn test_update_nonexistent_relayer() {
659 let repo = InMemoryRelayerRepository::new();
660 let relayer = create_test_relayer("test".to_string());
661
662 let result = repo.update("test".to_string(), relayer).await;
663 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
664 }
665
666 #[actix_web::test]
667 async fn test_get_nonexistent_relayer() {
668 let repo = InMemoryRelayerRepository::new();
669
670 let result = repo.get_by_id("test".to_string()).await;
671 assert!(matches!(result, Err(RepositoryError::NotFound(_))));
672 }
673
674 #[actix_web::test]
675 async fn test_partial_update_relayer() {
676 let repo = InMemoryRelayerRepository::new();
677
678 let relayer_id = "test_relayer".to_string();
680 let initial_relayer = create_test_relayer(relayer_id.clone());
681
682 repo.create(initial_relayer.clone()).await.unwrap();
683
684 let update_req = RelayerUpdateRequest { paused: Some(true) };
686
687 let updated_relayer = repo
688 .partial_update(relayer_id.clone(), update_req)
689 .await
690 .unwrap();
691
692 assert_eq!(updated_relayer.id, initial_relayer.id);
693 assert!(updated_relayer.paused);
694 }
695
696 #[actix_web::test]
697 async fn test_disable_relayer() {
698 let repo = InMemoryRelayerRepository::new();
699
700 let relayer_id = "test_relayer".to_string();
702 let initial_relayer = create_test_relayer(relayer_id.clone());
703
704 repo.create(initial_relayer.clone()).await.unwrap();
705
706 let disabled_relayer = repo.disable_relayer(relayer_id.clone()).await.unwrap();
708
709 assert_eq!(disabled_relayer.id, initial_relayer.id);
710 assert!(disabled_relayer.system_disabled);
711 }
712
713 #[actix_web::test]
714 async fn test_enable_relayer() {
715 let repo = InMemoryRelayerRepository::new();
716
717 let relayer_id = "test_relayer".to_string();
719 let mut initial_relayer = create_test_relayer(relayer_id.clone());
720
721 initial_relayer.system_disabled = true;
722
723 repo.create(initial_relayer.clone()).await.unwrap();
724
725 let enabled_relayer = repo.enable_relayer(relayer_id.clone()).await.unwrap();
727
728 assert_eq!(enabled_relayer.id, initial_relayer.id);
729 assert!(!enabled_relayer.system_disabled);
730 }
731
732 #[actix_web::test]
733 async fn test_update_policy() {
734 let repo = InMemoryRelayerRepository::new();
735 let relayer = create_test_relayer("test".to_string());
736
737 repo.create(relayer.clone()).await.unwrap();
738
739 let new_policy = RelayerNetworkPolicy::Evm(RelayerEvmPolicy {
741 gas_price_cap: Some(50000000000),
742 whitelist_receivers: Some(vec!["0x1234".to_string()]),
743 eip1559_pricing: Some(true),
744 private_transactions: true,
745 min_balance: 1000000,
746 });
747
748 let updated_relayer = repo
750 .update_policy("test".to_string(), new_policy.clone())
751 .await
752 .unwrap();
753
754 match updated_relayer.policies {
756 RelayerNetworkPolicy::Evm(policy) => {
757 assert_eq!(policy.gas_price_cap, Some(50000000000));
758 assert_eq!(policy.whitelist_receivers, Some(vec!["0x1234".to_string()]));
759 assert_eq!(policy.eip1559_pricing, Some(true));
760 assert!(policy.private_transactions);
761 assert_eq!(policy.min_balance, 1000000);
762 }
763 _ => panic!("Unexpected policy type"),
764 }
765 }
766}